代码改变世界

websockt实现机制

2019-08-21 20:39  美丽的名字  阅读(293)  评论(0)    收藏  举报

websocket是基于TCP的一种协议,它实现了在浏览器与服务器之间全双工的长连接,其底层同样是基于socket通信,但是与TCP有区别,却别就在于TCP在进行三次握手完成之后就开始进行数据传输,传输完毕的一端自动断开连接,而websocket在三次握手完成之后还需要进行一次数据的验证,而且验证成功之后连接建立开始进行数据传输,并且数据传输完毕不会自动断开,需要主动断开连接。

服务端:

#!usr/bin/env python  
#-*- coding:utf-8 _*-  

import socket
import hashlib
import base64




def get_headers(data):
    """
    将请求头格式化成字典
    :param data:
    :return:
    """
    header_dict = {}
    data = str(data, encoding='utf-8')

    for i in data.split('\r\n'):
        print(i)
    header, body = data.split('\r\n\r\n', 1)
    header_list = header.split('\r\n')
    for i in range(0, len(header_list)):
        if i == 0:
            if len(header_list[i].split(' ')) == 3:
                header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
        else:
            k, v = header_list[i].split(':', 1)
            header_dict[k] = v.strip()
    return header_dict




sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8000))
sock.listen(5)

conn, address = sock.accept()

data = conn.recv(8096)

headers = get_headers(data)

magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'   #约定的固定的魔法字符串,相当于salt

response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
      "Upgrade:websocket\r\n" \
      "Connection: Upgrade\r\n" \
      "Sec-WebSocket-Accept: %s\r\n" \
      "WebSocket-Location: ws://%s%s\r\n\r\n"

value = headers['Sec-WebSocket-Key'] + magic_string     #headers['Sec-WebSocket-Key']这个数据是浏览器随机生成发到服务端,
                                                        # 服务端拿到后使用magic_string进行sha1加密后返给浏览器,浏览器收到解密后完成验证
ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])
# 响应【握手】信息,进行验证
conn.send(bytes(response_str, encoding='utf-8'))

 

浏览器端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>
    ws = new WebSocket("ws://127.0.0.1:8000");
   ws.onopen = function () {
    alert(连接成功!)
  }
</script> </body> </html>

 


 

由于客户端与服务端之间的数据传输都以字节流方式,所以发送数据和接收数据都需要进行解包和封包操作。

解包与封包的依据:

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 ...                |
+---------------------------------------------------------------+

第一行的数据没八位为一个字节,前面一部分为包头,后面有一部分是数据部分,数据部分具体从多少位到多少位需要根据Payload len的长度决定。

Decoding Payload Length

To read the payload data, you must know when to stop reading. That's why the payload length is important to know. Unfortunately, this is somewhat complicated. To read it, follow these steps:

Read bits 9-15 (inclusive) and interpret that as an unsigned integer. 
If it's 125 or less, then that's the length; you're done.
1、If it's 126, go to step 2. If it's 127, go to step 3. 2、Read the next 16 bits and interpret those as an unsigned integer. You're done. 3、Read the next 64 bits and interpret those as an unsigned integer (The most significant bit MUST be 0). You're done. Reading and Unmasking the Data If the MASK bit was set (and it should be, for client-to-server messages), read the next 4 octets (32 bits); this is the masking key. Once the payload length and masking key is decoded, you can go ahead and read that number of bytes from the socket. Let's call the data ENCODED, and the key MASK. To get DECODED, loop through the octets (bytes a.k.a. characters for text data) of ENCODED and XOR the octet with the (i modulo 4)th octet of MASK. In pseudo-code (that happens to be valid JavaScript):


var DECODED = "";
for (var i = 0; i < ENCODED.length; i++) {
    DECODED[i] = ENCODED[i] ^ MASK[i % 4];
}

服务端解包过程:

   info = conn.recv(8096)

    payload_len = info[1] & 127
    if payload_len == 126:
        extend_payload_len = info[2:4]
        mask = info[4:8]
        decoded = info[8:]
    elif payload_len == 127:
        extend_payload_len = info[2:10]
        mask = info[10:14]
        decoded = info[14:]
    else:
        extend_payload_len = None
        mask = info[2:6]
        decoded = info[6:]

    bytes_list = bytearray()
    for i in range(len(decoded)):
        chunk = decoded[i] ^ mask[i % 4]
        bytes_list.append(chunk)
    body = str(bytes_list, encoding='utf-8')
    print(body)

封包:

def send_msg(conn, msg_bytes):
    """
    WebSocket服务端向客户端发送消息
    :param conn: 客户端连接到服务器端的socket对象,即:conn,address = socket.accept()
    :param msg_bytes: 向客户端发送的字节
    :return: 
    """
    import struct

    token = b"\x81"
    length = len(msg_bytes)
    if length < 126:
        token += struct.pack("B", length)
    elif length <= 0xFFFF:
        token += struct.pack("!BH", 126, length)
    else:
        token += struct.pack("!BQ", 127, length)

    msg = token + msg_bytes
    conn.send(msg)
    return True