这次给大家带来如何使用WebSocket通信,使用WebSocket通信的注意事项有哪些,下面就是实战案例,一起来看一下。

什么是 WebSocket ?

WebSocket 是一种标准协议,用于在客户端和服务端之间进行双向数据传输。但它跟 HTTP 没什么关系,它是基于 TCP 的一种独立实现。

以前客户端想知道服务端的处理进度,要不停地使用 Ajax 进行轮询,让浏览器隔个几秒就向服务器发一次请求,这对服务器压力较大。另外一种轮询就是采用 long poll 的方式,这就跟打电话差不多,没收到消息就一直不挂电话,也就是说,客户端发起连接后,如果没消息,就一直不返回 Response 给客户端,连接阶段一直是阻塞的。

而 WebSocket 解决了 HTTP 的这几个难题。当服务器完成协议升级后( HTTP -> WebSocket ),服务端可以主动推送信息给客户端,解决了轮询造成的同步延迟问题。由于 WebSocket 只需要一次 HTTP 握手,服务端就能一直与客户端保持通信,直到关闭连接,这样就解决了服务器需要反复解析 HTTP 协议,减少了资源的开销。

如何使用WebSocket通信

随着新标准的推进,WebSocket 已经比较成熟了,并且各个主流浏览器对 WebSocket 的支持情况比较好(不兼容低版本 IE,IE 10 以下),有空可以看看。

如何使用WebSocket通信

使用 WebSocket 的时候,前端使用是比较规范的,js 支持 ws 协议,感觉类似于一个轻度封装的 Socket 协议,只是以前需要自己维护 Socket 的连接,现在能够以比较标准的方法来进行。

如何使用WebSocket通信

下面我们就结合上图具体来聊一下 WebSocket 的通信过程。

建立连接

客户端请求报文 Header

客户端请求报文:

GET / HTTP/1.1 Upgrade: websocket Connection: Upgrade Host: example.com Origin: Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== Sec-WebSocket-Version: 13

与传统 HTTP 报文不同的地方:

Upgrade: websocket Connection: Upgrade

这两行表示发起的是 WebSocket 协议。

Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== Sec-WebSocket-Version: 13

Sec-WebSocket-Key 是由浏览器随机生成的,提供基本的防护,防止恶意或者无意的连接。

Sec-WebSocket-Version 表示 WebSocket 的版本,最初 WebSocket 协议太多,不同厂商都有自己的协议版本,不过现在已经定下来了。如果服务端不支持该版本,需要返回一个 Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。

创建 WebSocket 对象:

var ws = new websocket("ws://127.0.0.1:8001");

ws 表示使用 WebSocket 协议,后面接地址及端口

完整的客户端代码:

<script type="text/javascript"> var ws; var box = document.getElementById('box'); function startWS() { ws = new WebSocket('ws://127.0.0.1:8001'); ws.onopen = function (msg) { console.log('WebSocket opened!'); }; ws.onmessage = function (message) { console.log('receive message: ' + message.data); box.insertAdjacentHTML('beforeend', '<p>' + message.data + '</p>'); }; ws.onerror = function (error) { console.log('Error: ' + error.name + error.number); }; ws.onclose = function () { console.log('WebSocket closed!'); }; } function sendMessage() { console.log('Sending a message...'); var text = document.getElementById('text'); ws.send(text.value); } window.onbeforeunload = function () { ws.onclose = function () {}; // 首先关闭 WebSocket ws.close() }; </script>

服务端响应报文 Header

首先我们来看看服务端的响应报文:

HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat

我们一行行来解释

Sec-WebSocket-Accept 的计算方法:

将 Sec-WebSocket-Key 跟 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接;

通过 SHA1 计算出摘要,并转成 base64 字符串。

注意:Sec-WebSocket-Key/Sec-WebSocket-Accept 的换算,只能带来基本的保障,但连接是否安全、数据是否安全、客户端 / 服务端是否合法的 ws 客户端、ws 服务端,其实并没有实际性的保证。

创建主线程,用于实现接受 WebSocket 建立请求:

def create_socket(): # 启动 Socket 并监听连接 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.bind(('127.0.0.1', 8001)) # 操作系统会在服务器 Socket 被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口。 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.listen(5) except Exception as e: logging.error(e) return else: logging.info('Server running...') # 等待访问 while True: conn, addr = sock.accept() # 此时会进入 waiting 状态 data = str(conn.recv(1024)) logging.debug(data) header_dict = {} header, _ = data.split(r'\r\n\r\n', 1) for line in header.split(r'\r\n')[1:]: key, val = line.split(': ', 1) header_dict[key] = val if 'Sec-WebSocket-Key' not in header_dict: logging.error('This socket is not websocket, client close.') conn.close() return magic_key = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' sec_key = header_dict['Sec-WebSocket-Key'] + magic_key key = base64.b64encode(hashlib.sha1(bytes(sec_key, encoding='utf-8')).digest()) key_str = str(key)[2:30] logging.debug(key_str) response = 'HTTP/1.1 101 Switching Protocols\r\n' \ 'Connection: Upgrade\r\n' \ 'Upgrade: websocket\r\n' \ 'Sec-WebSocket-Accept: {0}\r\n' \ 'WebSocket-Protocol: chat\r\n\r\n'.format(key_str) conn.send(bytes(response, encoding='utf-8')) logging.debug('Send the handshake data') WebSocketThread(conn).start()

进行通信

服务端解析 WebSocket 报文

Server 端接收到 Client 发来的报文需要进行解析

Client 包格式

如何使用WebSocket通信

FIN: 占 1bit

0:不是消息的最后一个分片
1:是消息的最后一个分片

RSV1, RSV2, RSV3:各占 1bit

一般情况下全为 0。当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非
0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用 WebSocket 扩展,连接出错。

Opcode: 4bit