WebSocket通訊使用剖析
這次帶給大家WebSocket通訊使用剖析,WebSocket通訊使用剖析的注意事項有哪些,下面就是實戰案例,一起來看一下。
什麼是 WebSocket ?
WebSocket 是一種標準協議,用於在客戶端和服務端之間進行雙向資料傳輸。但它跟 HTTP 沒什麼關係,它是基於 TCP 的獨立實作。
以前客戶端想知道服務端的處理進度,要不停地使用 Ajax 進行輪詢,讓瀏覽器隔幾秒鐘就向伺服器發送一次請求,這對伺服器壓力較大。另外一種輪詢就是採用long poll 的方式,這就跟打電話差不多,沒收到訊息就一直不掛電話,也就是說,客戶端發起連線後,如果沒訊息,就一直不回傳Response 給客戶端,連接階段一直是阻塞的。
而 WebSocket 解決了 HTTP 的這幾個難題。當伺服器完成協定升級後( HTTP -> WebSocket ),服務端可以主動推送訊息給客戶端,解決了輪詢造成的同步延遲問題。由於 WebSocket 只需要一次 HTTP 握手,服務端就能一直與客戶端保持通信,直到關閉連接,這樣就解決了伺服器需要反覆解析 HTTP 協議,減少了資源的開銷。
隨著新標準的推進,WebSocket 已經比較成熟了,並且各個主流瀏覽器對WebSocket 的支援情況比較好(不相容低版本IE,IE 10 以下),有空可以看看。
使用WebSocket 的時候,前端使用是比較規範的,js 支援ws 協議,感覺類似於一個輕度封裝的Socket 協議,只是以前需要自己維護Socket 的連接,現在能夠以比較標準的方法進行。
下面我們就結合上圖具體來聊聊 WebSocket 的通訊過程。
建立連線
客戶端請求封包Header
客戶端請求封包:
GET / HTTP/1.1 Upgrade: websocket Connection: Upgrade Host: example.com Origin: http://example.com 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
我們一行行來解釋
首先,101 狀態碼表示伺服器已經瞭解了客戶端的請求,並將透過Upgrade 訊息標頭通知客戶端採用不同的協定來完成這個請求;
然後,Sec-WebSocket-Accept 這個則是經過伺服器確認,並且加密過後的Sec-WebSocket-Key;
最後,Sec-WebSocket-Protocol 則是表示最終使用的協議。
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 包格式
FIN: 占 1bit
0:不是消息的最后一个分片
1:是消息的最后一个分片
RSV1, RSV2, RSV3:各占 1bit
一般情况下全为 0。当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非
0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用 WebSocket 扩展,连接出错。
Opcode: 4bit
%x0:表示一个延续帧。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片;
%x1:表示这是一个文本帧(text frame);
%x2:表示这是一个二进制帧(binary frame);
%x3-7:保留的操作代码,用于后续定义的非控制帧;
%x8:表示连接断开;
%x9:表示这是一个心跳请求(ping);
%xA:表示这是一个心跳响应(pong);
%xB-F:保留的操作代码,用于后续定义的控制帧。
Mask: 1bit
表示是否要对数据载荷进行掩码异或操作。
0:否
1:是
Payload length: 7bit or (7 + 16)bit or (7 + 64)bit
表示数据载荷的长度
0~126:数据的长度等于该值;
126:后续 2 个字节代表一个 16 位的无符号整数,该无符号整数的值为数据的长度;
127:后续 8 个字节代表一个 64 位的无符号整数(最高位为 0),该无符号整数的值为数据的长度。
Masking-key: 0 or 4bytes
当 Mask 为 1,则携带了 4 字节的 Masking-key;
当 Mask 为 0,则没有 Masking-key。
掩码算法:按位做循环异或运算,先对该位的索引取模来获得 Masking-key 中对应的值 x,然后对该位与 x 做异或,从而得到真实的 byte 数据。
注意:掩码的作用并不是为了防止数据泄密,而是为了防止早期版本的协议中存在的代理缓存污染攻击(proxy cache poisoning attacks)等问题。
Payload Data: 载荷数据
解析 WebSocket 报文代码如下:
def read_msg(data): logging.debug(data) msg_len = data[1] & 127 # 数据载荷的长度 if msg_len == 126: mask = data[4:8] # Mask 掩码 content = data[8:] # 消息内容 elif msg_len == 127: mask = data[10:14] content = data[14:] else: mask = data[2:6] content = data[6:] raw_str = '' # 解码后的内容 for i, d in enumerate(content): raw_str += chr(d ^ mask[i % 4]) return raw_str
服务端发送 WebSocket 报文
返回时不携带掩码,所以 Mask 位为 0,再按载荷数据的大小写入长度,最后写入载荷数据。
struct 模块解析
struct.pack(fmt, v1, v2, ...)
按照给定的格式 fmt,把数据封装成字符串 ( 实际上是类似于 C 结构体的字节流 )
struct 中支持的格式如下表:
Format | C Type | Python type | Standard size |
---|---|---|---|
x | pad byte | no value | |
c | char | bytes of length 1 | 1 |
b | signed char | integer | 1 |
B | unsigned char | integer | 1 |
? | _Bool | bool | 1 |
h | short | integer | 2 |
H | unsigned short | integer | 2 |
i | int | integer | 4 |
I | unsigned int | integer | 4 |
l | long | integer | 4 |
L | unsigned long | integer | 4 |
q | long long | integer | 8 |
Q | unsigned long long | integer | 8 |
n | ssize_t | integer | |
N | size_t | integer | |
e | -7 | float | 2 |
f | float | float | 4 |
d | double | float | 8 |
s | char[] | bytes | |
p | char[] | bytes | |
P | void * | integer |
为了同 C 语言中的结构体交换数据,还要考虑有的 C 或 C++ 编译器使用了字节对齐,通常是以 4 个字节为单位的 32 位系统,故而 struct 根据本地机器字节顺序转换。可以用格式中的第一个字符来改变对齐方式,定义如下:
Character | Byte order | Size | Alignment |
---|---|---|---|
@ | native | native | native |
= | native | standard | none |
< | little-endian | standard | none |
> | big-endian | standard | none |
! | network (= big-endian) | standard | none |
发送 WebSocket 报文代码如下:
def write_msg(message): data = struct.pack('B', 129) # 写入第一个字节,10000001 # 写入包长度 msg_len = len(message) if msg_len <= 125: data += struct.pack('B', msg_len) elif msg_len <= (2 ** 16 - 1): data += struct.pack('!BH', 126, msg_len) elif msg_len <= (2 ** 64 - 1): data += struct.pack('!BQ', 127, msg_len) else: logging.error('Message is too long!') return data += bytes(message, encoding='utf-8') # 写入消息内容 logging.debug(data) return data
总结
没有其他能像 WebSocket 一样实现全双工传输的技术了,迄今为止,大部分开发者还是使用 Ajax 轮询来实现,但这是个不太优雅的解决办法,WebSocket 虽然用的人不多,可能是因为协议刚出来的时候有安全性的问题以及兼容的浏览器比较少,但现在都有解决。如果你有这些需求可以考虑使用 WebSocket:
多个用户之间进行交互;
需要频繁地向服务端请求更新数据。
比如弹幕、消息订阅、多玩家游戏、协同编辑、股票基金实时报价、视频会议、在线教育等需要高实时的场景。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上是WebSocket通訊使用剖析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

隨著網路科技的不斷發展,即時通訊已經成為了日常生活中不可或缺的一部分。利用WebSockets技術可以實現高效、低延遲的即時通信,而PHP作為互聯網領域使用最廣泛的開發語言之一,也提供了相應的WebSocket支援。本文將為大家介紹如何使用PHP和WebSocket實現即時通信,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單

PHP和WebSocket:實現即時資料傳輸的最佳實踐方法引言:在Web應用程式開發中,即時資料傳輸是一項非常重要的技術需求。傳統的HTTP協定是一種請求-回應模式的協議,不能有效地實現即時資料傳輸。為了滿足即時資料傳輸的需求,WebSocket協定應運而生。 WebSocket是一種全雙工通訊協議,它提供了一種在單一TCP連接上進行全雙工通訊的方式。相比於H

如何利用Java和WebSocket實現即時股票行情推播引言:隨著網路的快速發展,股票行情即時推播成為了投資人關注的焦點之一。傳統的股票行情推送方式存在延遲較高、刷新速度慢等問題,對於投資人來說,無法及時獲得最新的股票行情資訊可能會導致投資決策的誤差。而基於Java和WebSocket的即時股票行情推送可以有效解決這個問題,使投資者能夠第一時間獲取到最新的

JavaWebsocket如何實現線上白板功能?在現代網路時代,人們越來越注重即時協作和互動的體驗。線上白板就是一種基於Websocket實現的功能,它能夠使多個使用者即時協作編輯同一個畫板,完成繪圖和標註等操作,為線上教育、遠端會議、團隊協作等場景提供了便捷的解決方案。一、技術背景WebSocket是HTML5提供的一種新的協議,它在同一條TCP連接上實

在本文中,我們將比較伺服器發送事件(SSE)和 WebSocket,兩者都是用於傳遞資料的可靠方法。我們將在八個方面對它們進行分析,包括通訊方向、底層協定、安全性、易用性、效能、訊息結構、易用性和測試工具。這些方面的比較總結如下:類別伺服器發送事件(SSE)WebSocket通訊方向單向雙向底層協定HTTPWebSocket 協定安全性與HTTP 相同存在安全漏洞易用性設定簡單設定複雜效能訊息傳送速度快受訊息處理和連線管理影響訊息結構純文字文字或二進位易用性廣泛可用對WebSocket 整合有

Golang是一種功能強大的程式語言,它在WebSocket程式設計中的使用越來越受到開發者的重視。 WebSocket是一種基於TCP的協議,它允許在客戶端和伺服器之間進行雙向通訊。在本文中,我們將介紹如何使用Golang編寫高效的WebSocket伺服器,同時處理多個並發連線。在介紹技巧前,我們先來學習什麼是WebSocket。 WebSocket簡介Web

如何在golang中使用WebSocket進行文件傳輸WebSocket是一種支援雙向通訊的網路協議,能夠在瀏覽器和伺服器之間建立持久的連線。在golang中,我們可以使用第三方函式庫gorilla/websocket來實作WebSocket功能。本文將介紹如何使用golang和gorilla/websocket函式庫來進行檔案傳輸。首先,我們需要安裝gorilla

PHPWebsocket開發指南:實現即時翻譯功能引言:隨著網路的發展,即時通訊在各種應用場景中變得越來越重要。而Websocket作為一種新興的通訊協議,為實現即時通訊提供了良好的支援。本篇文章將帶您詳細了解如何使用PHP開發Websocket應用,並結合即時翻譯功能來示範其具體應用。一、什麼是Websocket協定? Websocket協定是一種在單一
