RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

什么是WebSocket?進(jìn)行通信解析 WebSocket 報(bào)文及實(shí)現(xiàn)

馬哥Linux運(yùn)維 ? 來源:未知 ? 作者:李倩 ? 2018-05-15 16:59 ? 次閱讀

什么是 WebSocket ?

WebSocket 是一種標(biāo)準(zhǔn)協(xié)議,用于在客戶端和服務(wù)端之間進(jìn)行雙向數(shù)據(jù)傳輸。但它跟 HTTP 沒什么關(guān)系,它是一種基于 TCP 的一種獨(dú)立實(shí)現(xiàn)。

以前客戶端想知道服務(wù)端的處理進(jìn)度,要不停地使用 Ajax 進(jìn)行輪詢,讓瀏覽器隔個(gè)幾秒就向服務(wù)器發(fā)一次請(qǐng)求,這對(duì)服務(wù)器壓力較高。另外一種輪詢就是采用 long poll 的方式,這就跟打電話差不多,沒收到消息就一直不掛電話,也就是說,客戶端發(fā)起連接后,如果沒消息,就一直不返回 Response 給客戶端,連接階段一直是阻塞的。

而 WebSocket 解決了 HTTP 的這幾個(gè)難題。首先,當(dāng)服務(wù)器完成協(xié)議升級(jí)后( HTTP -> WebSocket ),服務(wù)端可以主動(dòng)推送信息給客戶端,解決了輪詢?cè)斐傻耐窖舆t問題。由于 WebSocket 只需要一次 HTTP 握手,服務(wù)端就能一直與客戶端保持通訊,直到關(guān)閉連接,這樣就解決了服務(wù)器需要反復(fù)解析 HTTP 協(xié)議,減少了資源的開銷。

隨著新標(biāo)準(zhǔn)的推進(jìn),WebSocket 已經(jīng)比較成熟了,并且各個(gè)瀏覽器對(duì) WebSocket 的支持情況比較好,有空可以看看。

使用 WebSocket 的時(shí)候,前端使用是比較規(guī)范的,js 支持 ws 協(xié)議,感覺類似于一個(gè)輕度封裝的 Socket 協(xié)議,只是以前需要自己維護(hù) Socket 的連接,現(xiàn)在能夠以比較標(biāo)準(zhǔn)的方法來進(jìn)行。

客戶端請(qǐng)求報(bào)文及實(shí)現(xiàn)

客戶端請(qǐng)求報(bào)文:

GET / HTTP/1.1Upgrade: websocketConnection: UpgradeHost: example.comOrigin: http://example.comSec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==Sec-WebSocket-Version: 13

與傳統(tǒng) HTTP 報(bào)文不同的地方:

Upgrade: websocket Connection: Upgrade

這兩行表示發(fā)起的是 WebSocket 協(xié)議。

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

Sec-WebSocket-Key是由瀏覽器隨機(jī)生成的,提供基本的防護(hù),防止惡意或者無意的連接。

Sec-WebSocket-Version表示 WebSocket 的版本,最初 WebSocket 協(xié)議太多,不同廠商都有自己的協(xié)議版本,不過現(xiàn)在已經(jīng)定下來了。如果服務(wù)端不支持該版本,需要返回一個(gè)Sec-WebSocket-Versionheader,里面包含服務(wù)端支持的版本號(hào)。

創(chuàng)建 WebSocket 對(duì)象:

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

ws 表示使用 WebSocket 協(xié)議,后面接地址及端口

完整的客戶端代碼:

服務(wù)端響應(yīng)報(bào)文及實(shí)現(xiàn)

首先我們來看看服務(wù)端的響應(yīng)報(bào)文

HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=Sec-WebSocket-Protocol: chat

我們一行行來解釋

1、首先,101 狀態(tài)碼表示服務(wù)器已經(jīng)理解了客戶端的請(qǐng)求,并將通過Upgrade消息頭通知客戶端采用不同的協(xié)議來完成這個(gè)請(qǐng)求;

2、然后,Sec-WebSocket-Accept這個(gè)則是經(jīng)過服務(wù)器確認(rèn),并且加密過后的Sec-WebSocket-Key;

3、最后,Sec-WebSocket-Protocol則是表示最終使用的協(xié)議。

Sec-WebSocket-Accept的計(jì)算方法:

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

2、通過 SHA1 計(jì)算出摘要,并轉(zhuǎn)成 base64 字符串。

PS:Sec-WebSocket-Key/Sec-WebSocket-Accept的換算,只能帶來基本的保障,但連接是否安全、數(shù)據(jù)是否安全、客戶端 / 服務(wù)端是否合法的 ws 客戶端、ws 服務(wù)端,其實(shí)并沒有實(shí)際性的保證。

創(chuàng)建主線程,用于實(shí)現(xiàn)接受 WebSocket 建立請(qǐng)求:

def create_socket():
# 啟動(dòng) Socket 并監(jiān)聽連接 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try:
sock.bind(('127.0.0.1', 8001))
# 操作系統(tǒng)會(huì)在服務(wù)器 Socket 被關(guān)閉或服務(wù)器進(jìn)程終止后馬上釋放該服務(wù)器的端口,否則操作系統(tǒng)會(huì)保留幾分鐘該端口。
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() # 此時(shí)會(huì)進(jìn)入 waiting 狀態(tài)
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()

進(jìn)行通信解析 WebSocket 報(bào)文及實(shí)現(xiàn)

Server 端接收到瀏覽器發(fā)來的報(bào)文需要進(jìn)行解析

瀏覽器包格式

1、FIN: 占 1 個(gè) bit

0:不是消息的最后一個(gè)分片1:是消息的最后一個(gè)分片

2、RSV1, RSV2, RSV3:各占 1 個(gè) bit

一般情況下全為 0。當(dāng)客戶端、服務(wù)端協(xié)商采用 WebSocket 擴(kuò)展時(shí),這三個(gè)標(biāo)志位可以非0,且值的含義由擴(kuò)展進(jìn)行定義。如果出現(xiàn)非零的值,且并沒有采用 WebSocket 擴(kuò)展,連接出錯(cuò)。

3、Opcode: 4 個(gè) bit

%x0:表示一個(gè)延續(xù)幀。當(dāng) Opcode 為 0 時(shí),表示本次數(shù)據(jù)傳輸采用了數(shù)據(jù)分片,當(dāng)前收到的數(shù)據(jù)幀為其中一個(gè)數(shù)據(jù)分片;%x1:表示這是一個(gè)文本幀(frame);%x2:表示這是一個(gè)二進(jìn)制幀(frame);%x3-7:保留的操作代碼,用于后續(xù)定義的非控制幀;%x8:表示連接斷開;%x9:表示這是一個(gè) ping 操作;%xA:表示這是一個(gè) pong 操作;%xB-F:保留的操作代碼,用于后續(xù)定義的控制幀。

4、Mask: 1 個(gè) bit

表示是否要對(duì)數(shù)據(jù)載荷進(jìn)行掩碼異或操作。0:否1:是

5、Payload length: 7bit or 7 + 16bit or 7 + 64bit

表示數(shù)據(jù)載荷的長(zhǎng)度x為 0~126:數(shù)據(jù)的長(zhǎng)度為x字節(jié);x為 126:后續(xù) 2 個(gè)字節(jié)代表一個(gè) 16 位的無符號(hào)整數(shù),該無符號(hào)整數(shù)的值為數(shù)據(jù)的長(zhǎng)度;x為 127:后續(xù) 8 個(gè)字節(jié)代表一個(gè) 64 位的無符號(hào)整數(shù)(最高位為 0),該無符號(hào)整數(shù)的值為數(shù)據(jù)的長(zhǎng)度。

6、Masking-key: 0 or 4bytes

當(dāng) Mask 為 1,則攜帶了 4 字節(jié)的 Masking-key;當(dāng) Mask 為 0,則沒有 Masking-key。PS:掩碼的作用并不是為了防止數(shù)據(jù)泄密,而是為了防止早期版本的協(xié)議中存在的代理緩存污染攻擊(proxy cache poisoning attacks)等問題。

7、Payload Data: 載荷數(shù)據(jù)

解析 WebSocket 報(bào)文代碼如下:

def read_msg(data):
logging.debug(data)
msg_len = data[1] & 127 # 數(shù)據(jù)載荷的長(zhǎng)度
if msg_len == 126:
mask = data[4:8] # Mask 掩碼
content = data[8:] # 消息內(nèi)容
elif msg_len == 127:
mask = data[10:14]
content = data[14:]
else:
mask = data[2:6]
content = data[6:]
raw_str = '' # 解碼后的內(nèi)容 for i, d in enumerate(content):
raw_str += chr(d ^ mask[i % 4])
return raw_str

服務(wù)端發(fā)送 WebSocket 報(bào)文

返回時(shí)不攜帶掩碼,所以 Mask 位為 0,再按載荷數(shù)據(jù)的大小寫入長(zhǎng)度,最后寫入載荷數(shù)據(jù)。

struct 模塊解析

struct.pack(fmt, v1, v2, ...)

按照給定的格式 (fmt),把數(shù)據(jù)封裝成字符串 ( 實(shí)際上是類似于 c 結(jié)構(gòu)體的字節(jié)流 )

struct 中支持的格式如下表:

為了同 C 語言中的結(jié)構(gòu)體交換數(shù)據(jù),還要考慮有的 C 或 C++ 編譯器使用了字節(jié)對(duì)齊,通常是以 4 個(gè)字節(jié)為單位的 32 位系統(tǒng),故而 struct 根據(jù)本地機(jī)器字節(jié)順序轉(zhuǎn)換。可以用格式中的第一個(gè)字符來改變對(duì)齊方式,定義如下:

發(fā)送 WebSocket 報(bào)文代碼如下:

def write_msg(message):
data = struct.pack('B', 129) # 寫入第一個(gè)字節(jié),10000001
# 寫入包長(zhǎng)度 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')
# 寫入消息內(nèi)容
logging.debug(data) return data

總結(jié)

沒有其他能像 WebSocket 一樣實(shí)現(xiàn)雙向通信的技術(shù)了,迄今為止,大部分開發(fā)者還是使用 Ajax 輪詢來實(shí)現(xiàn),但這是個(gè)不太優(yōu)雅的解決辦法,WebSocket 雖然用的人不多,可能是因?yàn)閰f(xié)議剛出來的時(shí)候有安全性的問題以及兼容的瀏覽器比較少,但現(xiàn)在都有解決。如果你有這些需求可以考慮使用 WebSocket:

1 、多個(gè)用戶之間進(jìn)行交互;

2、需要頻繁地向服務(wù)端請(qǐng)求更新數(shù)據(jù)。

比如彈幕、消息訂閱、多玩家游戲、協(xié)同編輯、股票基金實(shí)時(shí)報(bào)價(jià)、視頻會(huì)議、在線教育等需要高實(shí)時(shí)的場(chǎng)景。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 通信
    +關(guān)注

    關(guān)注

    18

    文章

    6024

    瀏覽量

    135949
  • TCP
    TCP
    +關(guān)注

    關(guān)注

    8

    文章

    1353

    瀏覽量

    79055
  • WebSocket
    +關(guān)注

    關(guān)注

    0

    文章

    29

    瀏覽量

    3745

原文標(biāo)題:一文讀懂 WebSocket 通信過程與實(shí)現(xiàn)

文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Django3如何使用WebSocket實(shí)現(xiàn)WebShell

    websocket 服務(wù)。 大致看了下覺得這不夠有趣,翻了翻 django 的官方文檔發(fā)現(xiàn) django 原生是不支持 websocket 的,但 django3 之后支持了 asgi 協(xié)議可以自己實(shí)現(xiàn)
    的頭像 發(fā)表于 11-17 09:58 ?4365次閱讀

    鴻蒙原生應(yīng)用開發(fā)-網(wǎng)絡(luò)管理WebSocket連接

    ,客戶端會(huì)收到open事件的回調(diào),之后客戶端就可以通過send()方法與服務(wù)器進(jìn)行通信。當(dāng)服務(wù)器發(fā)信息給客戶端時(shí),客戶端會(huì)收到message事件的回調(diào)。當(dāng)客戶端不要此連接時(shí),可以通過調(diào)用close
    發(fā)表于 04-07 09:46

    ESP32進(jìn)行websocket通信接收數(shù)據(jù)出錯(cuò)的原因?

    I (104793) WEBSOCKET: WEBSOCKET_EVENT_CONNECTED I (104953) WEBSOCKET: WEBSOCKET_EVENT_DATA I
    發(fā)表于 06-14 07:42

    如何通過Java來使用WebSocket

    很酷的新技術(shù),可以實(shí)現(xiàn)瀏覽器與服務(wù)器之間實(shí)時(shí)、雙向的通信,幾乎沒有任何額外的代價(jià)。我這里要做的事情就是提供一個(gè)非常簡(jiǎn)潔,但卻內(nèi)容豐富的概覽,介紹如何開始使用這門技術(shù)。首先讀者需要了解如下一些事情:
    發(fā)表于 07-11 07:19

    初探實(shí)現(xiàn)websocket的心跳重連

    初探和實(shí)現(xiàn)websocket心跳重連
    發(fā)表于 05-25 15:07

    基于TCP的一種新的網(wǎng)絡(luò)協(xié)議WebSocket

    開啟 WebSocket 服務(wù)WebSocket 服務(wù)是網(wǎng)頁(yè)程序、安卓 App、微信小程序等獲得數(shù)據(jù)和服務(wù)的接口,是基于TCP 的一種新的網(wǎng)絡(luò)協(xié)議,它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信。通
    發(fā)表于 12-16 07:38

    ESP32進(jìn)行websocket通信接收數(shù)據(jù)出錯(cuò)的原因?

    I (104793) WEBSOCKET: WEBSOCKET_EVENT_CONNECTEDI (104953) WEBSOCKET: WEBSOCKET_EVENT_DATAI (
    發(fā)表于 02-13 06:46

    ESP32進(jìn)行websocket通信接收數(shù)據(jù)出錯(cuò)怎么解決?

    I (104793) WEBSOCKET: WEBSOCKET_EVENT_CONNECTEDI (104953) WEBSOCKET: WEBSOCKET_EVENT_DATAI (
    發(fā)表于 03-07 06:59

    根據(jù)WebSocket協(xié)議完全使用C++實(shí)現(xiàn)函數(shù)

    由于需要在項(xiàng)目中增加Websocket協(xié)議,與客戶端進(jìn)行通信,不想使用開源的庫(kù),比如WebSocketPP,就自己根據(jù)WebSocket協(xié)議實(shí)現(xiàn)
    的頭像 發(fā)表于 11-28 14:29 ?4887次閱讀

    Python如何爬取實(shí)時(shí)變化的WebSocket數(shù)據(jù)

    Python 中的網(wǎng)絡(luò)請(qǐng)求庫(kù)非常多,Requests 是最常用的請(qǐng)求庫(kù)之一,它可以模擬發(fā)送網(wǎng)絡(luò)請(qǐng)求。但是這些請(qǐng)求都是基于 HTTP 協(xié)議的。在面對(duì) WebSocket 的時(shí)候 Requests 就發(fā)揮不料作用了,必須使用能夠連接 WebSocket 的庫(kù)。
    的頭像 發(fā)表于 03-11 09:31 ?3568次閱讀
    Python如何爬取實(shí)時(shí)變化的<b class='flag-5'>WebSocket</b>數(shù)據(jù)

    WebSocket有什么優(yōu)點(diǎn)

    WebSocket是一種在單個(gè)TCP連接上進(jìn)行全雙工通信的協(xié)議。WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,并
    的頭像 發(fā)表于 02-15 15:53 ?8297次閱讀
    <b class='flag-5'>WebSocket</b>有什么優(yōu)點(diǎn)

    WebSocket工作原理及使用方法

    它有很多名字; WebSocket,WebSocket協(xié)議和WebSocket API。從首選的消息傳遞應(yīng)用程序到流行的在線多人游戲,WebSocket在當(dāng)今最常用的Web應(yīng)用程序中是
    的頭像 發(fā)表于 05-05 22:12 ?7868次閱讀
    <b class='flag-5'>WebSocket</b>工作原理及使用方法

    鴻蒙上WebSocket的使用方法

    WebSocket 是一種網(wǎng)絡(luò)通訊協(xié)議,很多網(wǎng)絡(luò)開發(fā)工作者都需要它。本文介紹在 OpenHarmony 上 WebSocket 協(xié)議的使用方法。
    的頭像 發(fā)表于 03-08 14:17 ?1875次閱讀

    WebSocket的6種集成方式介紹

    由于前段時(shí)間我實(shí)現(xiàn)了一個(gè)庫(kù)【Spring Cloud】一個(gè)配置注解實(shí)現(xiàn) WebSocket 集群方案
    的頭像 發(fā)表于 09-02 16:52 ?1505次閱讀

    websocket協(xié)議的原理

    WebSocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信——允許服務(wù)器主動(dòng)發(fā)送信息給客戶端。 WebSocket
    的頭像 發(fā)表于 11-09 15:13 ?1170次閱讀
    <b class='flag-5'>websocket</b>協(xié)議的原理
    RM新时代网站-首页