較爲原生的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)複製代碼
相關文章
相關標籤/搜索