91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

WebSocket中通信過程的示例分析

發布時間:2021-08-02 10:34:50 來源:億速云 閱讀:155 作者:小新 欄目:web開發

小編給大家分享一下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: 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 包格式

WebSocket中通信過程的示例分析

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 中支持的格式如下表:

FormatC TypePython typeStandard size
xpad byteno value
ccharbytes of length 11
bsigned charinteger1
Bunsigned charinteger1
?_Boolbool1
hshortinteger2
Hunsigned shortinteger2
iintinteger4
Iunsigned intinteger4
llonginteger4
Lunsigned longinteger4
qlong longinteger8
Qunsigned long longinteger8
nssize_tinteger
Nsize_tinteger
e-7float2
ffloatfloat4
ddoublefloat8
schar[]bytes
pchar[]bytes
Pvoid *integer

為了同 C 語言中的結構體交換數據,還要考慮有的 C 或 C++ 編譯器使用了字節對齊,通常是以 4 個字節為單位的 32 位系統,故而 struct 根據本地機器字節順序轉換。可以用格式中的第一個字符來改變對齊方式,定義如下:

CharacterByte orderSizeAlignment
@nativenativenative
=nativestandardnone
<little-endianstandardnone
>big-endianstandardnone
!network (= big-endian)standardnone

發送 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中通信過程的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

江山市| 乌兰浩特市| 宜兰市| 金昌市| 沾化县| 措勤县| 南木林县| 洪雅县| 平利县| 衡阳市| 三穗县| 遵义市| 宝兴县| 通化县| 全州县| 石渠县| 静宁县| 长兴县| 丹东市| 慈利县| 阜宁县| 大姚县| 宣武区| 甘孜| 驻马店市| 东安县| 界首市| 得荣县| 南涧| 湟源县| 会东县| 永川市| 固始县| 济宁市| 陆河县| 福泉市| 许昌县| 沛县| 山西省| 肃北| 全州县|