Python3 websocket通訊

一. 客戶端服務端進行握手

1.客戶端向服務端發送握手包web

GET ws://localhost:8000/ HTTP/1.1
Host: localhost:8000
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:63342
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8
Sec-WebSocket-Key: 60jj9YDlrvPB+DzLmVJzcg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits複製代碼

2.服務端進行迴應,返回給客戶端瀏覽器

HTTP/1.1 101 Switching Protocols
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Accept: Cx7gYudkmGmF8rMFP1W1HbLkWRA=複製代碼

客戶端發送握手請求,而後服務端返回正確的數據,通過客戶端的確認完成握手,而後就能夠進行通訊了;
這裏客戶端發送的Sec-WebSocket-Key是一個Base64 encode的值,它是瀏覽器隨機生成的,而後服務端經過這個值生成一個Sec-WebSocket-Accept;
方法: key+'258EAFA5-E914-47DA-95CA-C5AB0DC85B11',而後SHA-1加密,再進行base-64加密;
下面是握手代碼:緩存

HANDSHAKE_STR = (
   "HTTP/1.1 101 Switching Protocols\r\n"
   "Upgrade: WebSocket\r\n"
   "Connection: Upgrade\r\n"
   "Sec-WebSocket-Accept: %(token)s\r\n\r\n"
)
GUID_STR = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
def parseHeaders(self, msg):
    # print(msg)
    headers = {}
    msg = str(msg, encoding="utf-8")
    header, data = msg.split('\r\n\r\n', 1)
    for x in header.split("\r\n")[1:]:
        # 這裏是 「:+space」
        key, value = x.split(": ", 1)
        headers[key] = value
    return headers
# 握手數據
def handShakeData(self,c_req):
    sec_key = self.parseHeaders(c_req)['Sec-WebSocket-Key']
    key = sec_key.encode("ascii") + GUID_STR.encode("ascii")
    accept_token = base64.b64encode(hashlib.sha1(key).digest()).decode("ascii")
    s_res = HANDSHAKE_STR % {'token': accept_token}
    return s_res複製代碼

二. 進行通訊

大概就是握手完成後,客戶端發送消息到服務端,而後服務端將消息發送到每一個鏈接上的客戶端;bash

數據交互協議

0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+複製代碼
第一個字節:
  • FIN:描述消息是否結束;佔1bit;若是是1表示這是數據包的最後一幀,0則表示後續還有數據;
  • RSV1, RSV2, RSV3:擴展定義,默認0,各佔1bit;
  • opcode:表示傳輸的數據包類型,佔4bit,(15種狀況)
    • 0x0 表示爲附加數據
    • 0x1 表示爲text類型數據
    • 0x2 表示binary類型數據
    • 0x3~7 暫未定義
    • 0x8 表示鏈接關閉
    • 0x9 表示ping類型數據
    • 0xA 表示pong類型數據
    • 0xB~F 暫未定義
第二個字節:
  • MASK:表示是否通過掩碼處理;若是是1,表示通過掩碼處理,Masking-key的數據爲掩碼密碼
  • Payload length:
    • 若是值爲0~125,則就是payload的真實長度;(若MASK=1,則Masking-key爲第3~6個字節)
    • 若是值爲126,則後面的2個字節(16bit)爲payload的真實長度;(若MASK=1,則Masking-key爲第5~8個字節)
    • 若是值爲127,則後面的8個字節(64bit)爲payload的真實長度;(若MASK=1,則Masking-key爲第11~14個字節)

1. 將客戶端發送的數據解析出真實的數據

方法:將Payload Data數據的每一位x,與Masking-key的第i%4位進行異或運算,i是x在Payload Data中的索引;
下面是解析的代碼:websocket

def parseData(self,msg):
    g_code_length = msg[1] & 127
    if g_code_length == 126:
        g_code_length = struct.unpack('!H', msg[2:4])[0]
        masks = msg[4:8]
        data = msg[8:]
    elif g_code_length == 127:
        g_code_length = struct.unpack('!Q', msg[2:10])[0]
        masks = msg[10:14]
        data = msg[14:]
    else:
        masks = msg[2:6]
        data = msg[6:]

    i = 0
    raw_by = bytearray()
    for d in data:
        raw_by.append( int(d) ^ int(masks[i % 4]) )
        i += 1
    print(u"總長度是:%d" % int(g_code_length))
    raw_str = raw_by.decode()
    return raw_str複製代碼

2. 將數據發送到客戶端

發送的數據格式:固定字節+包長度字節+原始數據
例:\x81\x0cHello World!app

三. 完整的代碼
服務端:socket

import socket
import base64,hashlib
import threading
import struct
from collections import deque

HANDSHAKE_STR = (
   "HTTP/1.1 101 Switching Protocols\r\n"
   "Upgrade: WebSocket\r\n"
   "Connection: Upgrade\r\n"
   "Sec-WebSocket-Accept: %(token)s\r\n\r\n"
)
GUID_STR = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
STREAM = 0x0
TEXT = 0x1
BINARY = 0x2
CLOSE = 0x8
PING = 0x9
PONG = 0xA


class WebSocket(threading.Thread):
    def __init__(self,conn,addr,index):
        threading.Thread.__init__(self)
        self.index = index
        self.conn = conn
        self.addr = addr
        #發送的數據緩存
        self.buffer = bytearray()
        self.sendToClientData = deque()

    # 組裝header 獲取‘Sec-WebSocket-Key’
    def parseHeaders(self, msg):
        # print(msg)
        headers = {}
        msg = str(msg, encoding="utf-8")
        header, data = msg.split('\r\n\r\n', 1)
        for x in header.split("\r\n")[1:]:
            # 這裏是 「:+space」
            key, value = x.split(": ", 1)
            headers[key] = value
        return headers
    # 握手數據
    def handShakeData(self,c_req):
        sec_key = self.parseHeaders(c_req)['Sec-WebSocket-Key']
        key = sec_key.encode("ascii") + GUID_STR.encode("ascii")
        accept_token = base64.b64encode(hashlib.sha1(key).digest()).decode("ascii")
        s_res = HANDSHAKE_STR % {'token': accept_token}
        return s_res
    # 獲得數據長度 (包含描述字節)
    def getMsglen(self,msg):
        g_code_length = msg[1] & 127
        if g_code_length == 126:
            g_code_length = struct.unpack('!H', msg[2:4])[0]
            g_code_length += 8
        elif g_code_length == 127:
            g_code_length = struct.unpack('!Q', msg[2:10])[0]
            g_code_length += 14
        else:
            g_code_length += 6
        g_code_length = int(g_code_length)
        print(g_code_length)
        return g_code_length
    # 解析數據
    def parseData(self,msg):
        g_code_length = msg[1] & 127
        if g_code_length == 126:
            g_code_length = struct.unpack('!H', msg[2:4])[0]
            masks = msg[4:8]
            data = msg[8:]
        elif g_code_length == 127:
            g_code_length = struct.unpack('!Q', msg[2:10])[0]
            masks = msg[10:14]
            data = msg[14:]
        else:
            masks = msg[2:6]
            data = msg[6:]

        i = 0
        raw_by = bytearray()
        for d in data:
            raw_by.append( int(d) ^ int(masks[i % 4]) )
            i += 1
        print(raw_by)
        print(u"總長度是:%d" % int(g_code_length))
        raw_str = raw_by.decode()
        # raw_str = str(raw_by)
        return raw_str
    # 發送消息
    def sendMessage(self,message):
        # 遍歷鏈接上的列表
        for conn in connections.values():
            # 發送消息到client (除了本身)
            if conn != self.conn:
                self._sendMessage(False,TEXT,message)
                while self.sendToClientData:
                    data = self.sendToClientData.popleft()
                    conn.send(data[1])

    # FIN 後面是否還有數據;opcode 傳輸的數據包類型;message 傳輸的數據(str)
    def _sendMessage(self,FIN,opcode,message):
        payload = bytearray()
        b1 = 0
        b2 = 0
        if FIN is False:
            b1 |= 0x80
        b1 |= opcode        # 若opcode=TEXT b'0x81'

        payload.append(b1)      #
        msg_utf = message.encode('utf-8')
        msg_len = len(msg_utf)

        if msg_len <= 125:
            b2 |= msg_len

            payload.append(b2)
        elif msg_len >= 126 and msg_len <= 65535:
            b2 |= 126
            payload.append(b2)
            payload.extend(struct.pack("!H", msg_len))
        elif msg_len <= (2 ^ 64 - 1):
            b2 |= 127
            payload.append(b2)
            payload.extend(struct.pack("!Q", msg_len))
        else:
            print("傳輸的數據太長了! ——(_sendMessage)")
        # 拼接上須要發送的數據
        # 格式大概這樣:bytearray(b'\x81\x0cHello World!') '\x0c'是發送的數據長度
        if msg_len > 0:
            payload.extend(msg_utf)

        self.sendToClientData.append((opcode,payload))

    # 端開鏈接
    def _disConnected(self):
        self.conn.close()
        print("斷開的鏈接conn%s" % (self.index))
        print(connections)
        del connections['conn%s' % (self.index)]
        print(connections)
    # 
    def run(self):
        # print(self.listeners)
        self.isHandShake = False
        while True:
            if self.isHandShake == False:
                print("start handshake %s" % (self.addr[0]))
                # 接收客戶端內容
                c_req = self.conn.recv(1024)
                # print(c_req)
                handData = self.handShakeData(c_req)
                self.conn.sendall(str.encode(handData))
                self.isHandShake = True
            else:
                message = self.conn.recv(16384)    # 16 * 1024 16384 # bytes
                print(message)
                # 斷開鏈接
                if message[0]&127 == CLOSE:    # 0x88 136
                    self._disConnected()
                    return
                self.buffer.extend(message)  # bytes
                code_length = self.getMsglen(self.buffer)  # 數據中帶的 數據長度(包含描述字節)
                # 數據完整就發送
                if code_length == len(self.buffer):
                    buffer_utf = self.parseData(self.buffer)      # str
                    self.sendMessage(buffer_utf)
                    print(message)
                    self.buffer = bytearray()

connections = {}
class websocketServer(object):
    def __init__(self,host, port):
        self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.serversocket.bind((host, port))
        self.serversocket.listen(5)

    def server(self):
        print(self.serversocket)
        index =0
        while True:
            conn,addr = self.serversocket.accept()
            print(conn)
            print(addr)
            # 新建線程
            newSocket = WebSocket(conn,addr,index)
            # 開始線程,執行run函數
            newSocket.start()
            connections['conn%s'% (index)] = conn
            index += 1
            print(connections)


websocketServer = websocketServer("127.0.0.1",8000)
websocketServer.server()複製代碼

感謝您的閱讀;函數

相關文章
相關標籤/搜索