较为原生的WebSocket服务端

  1. 概念html

    • 我的理解它是客户端和服务端之间的通讯通道
    • 肯定惟一一个socket(套接字)的属性须要4个
      • 服务端IP,服务端Port,客户端IP,客户端Port
    • 经过这4个属性不难在脑壳里抽象出通道的概念,两端分别是通道的入口和出口
  2. 函数解释(python import socket)python

    1. 建立socket(基础socket,写明协议编号,socket类型等,没必要深究)
      • s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    2. 服务端
      • s.bind(address) address:(host,port)
      • s.listen(TCP链接数量限制)
      • s.accept() 接受客户端TCP链接并返回(conn,address),conn是创建好的socket对象,也就是一个完整的已知入口出口的通道,address是与上文address同格式的客户端地址
    3. 客户端
      • s.connect(address)创建链接,错误时返回socket.error
    4. 公共函数
      • s.recv(bufsize[,flag]) 接受管道传来的信息,bufsize指定接收的最大数据量,flag提供有关消息的其余信息,一般能够忽略。
      • s.send(string[,flag]) 发送数据,将string中的数据发送到链接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小,也就是说管子不够大,不能将数据一次性传出。
      • s.close() 关闭socket
  3. socket建立分析 先分析一下哪些代码是堵塞的web

    • s.accept()等不到就堵着
    • s.send()确定要有等待输入的数据变量,没有数据就还得堵着呗
    • s.recv()接不到就堵着

    啊,好烦,习惯单进程的我真是醉了,这让人咋写! (╯‵□′)╯︵┻━┻ 首先咱们要先分析一下咱们要创建什么样的对话redis

    1:1对话

    • 代码交互大概是这个流程
      • server:(省略s=s.socket.socket())
        • s.bind->s.listen->s.accept 好,到这里堵住,等待链接到来
      • client:
        • s.connect()
      • 创建链接,server端从s.accept()获得返回值conn通道对象与client_adress,而后咱们存起来
      • 如今开始咱们的数据传输,写web的时候,历来都是client攻,server受,这回逆转一下!(๑•̀ㅂ•́)و✧
      • server:
        • while 1: msg=input(意思意思,就是接受终端输入) s.send(msg)
      • client:
        • while 1: msg=s.resv() print msg //可能这就是伪代码吧 _(:з」∠)_
      • 这样一来,咱们就能够在服务端疯狂输出,而后客户端就能够打印出咱们传递的信息了
    • 爽过以后咱们再想,但是这样只能由服务端单方面访♂问客户端,客户端连点反应都没有,没意思,但是两我的都在那堵的不亦乐乎,该怎么办呢.....
    • 别想了,一个进程确定不够用
    • 看以下代码
    # Server.py
    import socket
    import sys,os
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.bind(("127.0.0.1",8000))
    s.listen(5)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    conn,address=s.accept()
    #开启子进程,找儿子帮忙
    pid=os.fork()
    if(pid>0):
        #读取输入,发送给client
        while 1:
            msg=sys.stdin.readline()
            if msg == "quit":
                sys.exit()
            conn.send(bytes(msg,encoding="utf-8"))
    else:
        while 1:
            log_file=open('./client_input.log','a')
            msg=conn.recv(1024) 
            print ("client:"+msg.decode('utf-8'))
            log_file.write(msg.decode('utf-8'))
            if msg == "bye":
                log_file.close()
                sys.exit()
    # Client.py
    import socket
    import sys,os
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect(("127.0.0.1",8000))
    
    pid=os.fork()
    if(pid>0):
        while 1:
            msg=sys.stdin.readline()
            if msg == "quit":
                sys.exit()
            s.send(msg.encode('utf-8'))
    else:
        while 1:
            log_file=open('./server_input.log','a')
            msg=s.recv(1024) 
            print ("server:"+msg.decode('utf-8'))
            log_file.write(msg.decode('utf-8'))
            if msg == "bye":
                log_file.close()
                sys.exit()
    
    复制代码
    • 让咱们来看看使用效果

    • oh,这可真是太妙了

    多人聊天室

    • 仍是一点点分析现状 1.全部人都知道咱们的服务端地址和端口他们不知道彼此的地址和端口,因此,套接字的创建,只可能存在与服务器与客户端之间客户端与客户端之间是没法创建链接的 2.这样咱们就有了一个前提:咱们的服务端能够与全部人创建链接,若是想要作一个聊天室,须要哪些功能呢?
      • 一个用户发出消息,发给了服务端,多人聊天室要干什么?固然是让其余人接受到这我的发出的消息,一句话归纳,将一我的发给咱们服务端的消息,广播给聊天室里的其余人编程

      • 给单一客户端发送消息须要咱们存储与这个客户端的聊天通道,也就是socket,那广播消息呢?就须要咱们把这些管道都给存起来,一条管道来了消息,把消息广播出去bash

      • 好了,思路有了,咱们来想一下有哪些问题,首先从聊天室的步骤提及,第一步是加入聊天室,咱们以前的代码,accept以后就不会再调用这个方法,也就是说,服务端不会接受新的客户端connect,怎么解决这个问题的呢,固然是监听accept(或者说不断询问)这里,有返回值的时候就生成一个新的套接字,并把这个套接字存到咱们的用户列表里,也就是把全部通道都进行记录服务器

      • 获得与全部用户的联系通道以后,咱们还要同时监听全部的消息发送,而后进行咱们以前说的步骤,接受用户消息,而后进行广播多线程

      • 下面是代码部分,因为要同时监听accept与recv,咱们选择select这个库(select可真是个神奇的东西)app

        import socket, select
        
        #广播函数
        def broadcast_data (sock, message):
         	#不给发送消息者和accept广播 
            for socket in CONNECTION_LIST:
                if socket != server_socket and socket != sock :
                    try :
                        socket.send(message)
                    except :
                        socket.close()
                        CONNECTION_LIST.remove(socket)
        
        if __name__ == "__main__":
        
            #监听列表,包括用户列表和accept事件
            CONNECTION_LIST = []
            RECV_BUFFER = 4096 # Advisable to keep it as an exponent of 2
            PORT = 5000
        
            server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        
            server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            server_socket.bind(("0.0.0.0", PORT))
            server_socket.listen(10)
            #监听accept的返回
            CONNECTION_LIST.append(server_socket)
        
            print "Chat server started on port " + str(PORT)
        
            while 1:
                #若是监听列表里有事件触发,结果会返回到read_sockets里,告知咱们有哪些消息来了
                read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])
         		 #而后咱们就能够进行以下处理
                for sock in read_sockets:
                    #若是消息来自server_socket,说明有新链接到来
                    if sock == server_socket:
                        sockfd, addr = server_socket.accept()
                        CONNECTION_LIST.append(sockfd)
                        print "Client (%s, %s) connected" % addr
                        broadcast_data(sockfd, "[%s:%s] entered room\n" % addr)
        
                    else:
                       #来消息了
                        try:
                            data = sock.recv(RECV_BUFFER)
                            if data:
                                broadcast_data(sock, "\r" + '<' + str(sock.getpeername()) + '> ' + data)
                                #当client掉线时,recv会不断受到空消息,因此关闭socket 
                            if not data :
                                broadcast_data(sock, "\r" + '<' + str(sock.getpeername()) + '> ' + "下线了")
                                sock.close()
                        except:
                            broadcast_data(sock, "Client (%s, %s) is offline" % addr)
                            print "Client (%s, %s) is offline" % addr
                            sock.close()
                            CONNECTION_LIST.remove(sock)
                            continue
            server_socket.close()
        #client.py
        import socket, select, string, sys,signal
        def prompt() :
            sys.stdout.write('<You> ')
            sys.stdout.flush()
        def sighandler(signum,frame):
                sys.stdout.write("Shutdown Server......\n")
                #向已经链接客户端发送关系信息,并主动关闭socket
                #关闭listen
                sys.stdout.flush()
                sys.exit()
        if __name__ == "__main__":
            signal.signal(signal.SIGINT,sighandler)
            signal.signal(signal.SIGHUP,sighandler)
            signal.signal(signal.SIGTERM, sighandler)
            if(len(sys.argv) < 3) :
                print('Usage : python telnet.py hostname port')
                sys.exit()
        	host = sys.argv[1]
            port = int(sys.argv[2])
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(2)
            try :
                s.connect((host, port))
            except :
                print('Unable to connect')
                sys.exit()
            print('Connected to remote host. Start sending messages')
            prompt()
            while 1:
                rlist = [sys.stdin,s]
                read_list, write_list, error_list = select.select(rlist , [], [])
                for sock in read_list:
                    if sock == s:
                        data = sock.recv(4096)
                        if not data :
                            print('\nDisconnected from chat server')
                            sys.exit()
                        else :
                            print (data.decode('utf-8'))
                            sys.stdin.flush()
                            prompt()
                    else :
                        msg = sys.stdin.readline()
                        s.send(msg.encode('utf-8'))
                        prompt()
        复制代码
    • 查看代码以后你会发现,这个写法是单进程的,一个select帮咱们解决了堵塞的问题,他将许多个堵塞集中到了一个堵塞身上,使得功能得以实现
    • 不过这种单进程的模式,我的分析会有反应不及时的问题,毕竟它是一个进程负责转发多个消息,若是消息多了,for循环的状况下响应速度会降下来
    • 因此还能够有另外一种模式,作一下简单设想:

多线程模式

  • 依然是一个进程负责不断接受用户的链接请求,可是当它接收到请求以后的处理方式发生变化,咱们开一个线程来专门负责这个新通道的消息监听与发送,以前那个进程接受到新的用户链接以后将用户列表存储到一个全部线程均可以访问的地方(我只知道redis,感受这样可行),这样一来,咱们为每个用户创建一个专属线程,负责接发这个用户的消息接受和转发,响应速度的问题也就解决了
参考文章:[Python Socket 编程——聊天室示例程序 By--hazir](https://www.cnblogs.com/hazir/p/python_chat_room.html)复制代码
相关文章
相关标签/搜索