最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

WebSocket的通信過程與實現(xiàn)方法詳解

 更新時間:2018年04月29日 14:48:17   作者:wzhvictor  
這篇文章主要給大家愛介紹了關于WebSocket的通信過程與實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起看看吧

什么是 WebSocket ?

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

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

而 WebSocket 解決了 HTTP 的這幾個難題。當服務器完成協(xié)議升級后( HTTP -> WebSocket ),服務端可以主動推送信息給客戶端,解決了輪詢造成的同步延遲問題。由于 WebSocket 只需要一次 HTTP 握手,服務端就能一直與客戶端保持通信,直到關閉連接,這樣就解決了服務器需要反復解析 HTTP 協(xié)議,減少了資源的開銷。

隨著新標準的推進,WebSocket 已經(jīng)比較成熟了,并且各個主流瀏覽器對 WebSocket 的支持情況比較好(不兼容低版本 IE,IE 10 以下),有空可以看看。

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

下面我們就結合上圖具體來聊一下 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

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

Upgrade: websocket
Connection: Upgrade

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

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

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

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

創(chuàng)建 WebSocket 對象:

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

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

完整的客戶端代碼:

<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 狀態(tài)碼表示服務器已經(jīng)理解了客戶端的請求,并將通過 Upgrade 消息頭通知客戶端采用不同的協(xié)議來完成這個請求;
  • 然后,Sec-WebSocket-Accept 這個則是經(jīng)過服務器確認,并且加密過后的 Sec-WebSocket-Key;
  • 最后,Sec-WebSocket-Protocol 則是表示最終使用的協(xié)議。

Sec-WebSocket-Accept 的計算方法:

  • 將 Sec-WebSocket-Key 跟 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接;
  • 通過 SHA1 計算出摘要,并轉成 base64 字符串。

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

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

def create_socket():
 # 啟動 Socket 并監(jiān)聽連接
 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 try:
 sock.bind(('127.0.0.1', 8001))

 # 操作系統(tǒng)會在服務器 Socket 被關閉或服務器進程終止后馬上釋放該服務器的端口,否則操作系統(tǒng)會保留幾分鐘該端口。
 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 狀態(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()

進行通信

服務端解析 WebSocket 報文

Server 端接收到 Client 發(fā)來的報文需要進行解析

Client 包格式

FIN: 占 1bit

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

RSV1, RSV2, RSV3:各占 1bit

一般情況下全為 0。當客戶端、服務端協(xié)商采用 WebSocket 擴展時,這三個標志位可以非
0,且值的含義由擴展進行定義。如果出現(xiàn)非零的值,且并沒有采用 WebSocket 擴展,連接出錯。

Opcode: 4bit

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

Mask: 1bit

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

Payload length: 7bit or (7 + 16)bit or (7 + 64)bit

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

Masking-key: 0 or 4bytes

當 Mask 為 1,則攜帶了 4 字節(jié)的 Masking-key;
當 Mask 為 0,則沒有 Masking-key。
掩碼算法:按位做循環(huán)異或運算,先對該位的索引取模來獲得 Masking-key 中對應的值 x,然后對該位與 x 做異或,從而得到真實的 byte 數(shù)據(jù)。
注意:掩碼的作用并不是為了防止數(shù)據(jù)泄密,而是為了防止早期版本的協(xié)議中存在的代理緩存污染攻擊(proxy cache poisoning attacks)等問題。

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

解析 WebSocket 報文代碼如下:

def read_msg(data):
 logging.debug(data)

 msg_len = data[1] & 127 # 數(shù)據(jù)載荷的長度
 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

服務端發(fā)送 WebSocket 報文

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

struct 模塊解析

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

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

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 語言中的結構體交換數(shù)據(jù),還要考慮有的 C 或 C++ 編譯器使用了字節(jié)對齊,通常是以 4 個字節(jié)為單位的 32 位系統(tǒng),故而 struct 根據(jù)本地機器字節(jié)順序轉換??梢杂酶袷街械牡谝粋€字符來改變對齊方式,定義如下:

Character Byte order Size Alignment
@ native native native
= native standard none
< little-endian standard none
> big-endian standard none
! network (= big-endian) standard none

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

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

總結

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

  • 多個用戶之間進行交互;
  • 需要頻繁地向服務端請求更新數(shù)據(jù)。

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

參考文章

https://www.zhihu.com/question/20215561/answer/40316953

http://fullstackpython.atjiang.com/websockets.html

http://www.52im.net/thread-1341-1-1.html

總結

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關文章

  • 什么是JavaScript中的結果值?

    什么是JavaScript中的結果值?

    你知道JavaScript中的結果值是什么嗎?這篇文章主要介紹了JavaScript結果值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • 使用egg.js實現(xiàn)手機、驗證碼注冊的項目實踐

    使用egg.js實現(xiàn)手機、驗證碼注冊的項目實踐

    本文主要介紹了使用egg.js實現(xiàn)手機、驗證碼注冊的項目實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-04-04
  • 詳解JS函數(shù)stack size計算方法

    詳解JS函數(shù)stack size計算方法

    本篇文章給大家分享了關于JS函數(shù)中stack size計算方法的相關知識點,對此有需要的朋友可以跟著學習下。
    2018-06-06
  • 微信小程序仿抖音短視頻切換效果的實例代碼

    微信小程序仿抖音短視頻切換效果的實例代碼

    這篇文章主要介紹了微信小程序仿抖音短視頻切換效果,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06
  • JS如何實現(xiàn)動態(tài)添加的元素綁定事件

    JS如何實現(xiàn)動態(tài)添加的元素綁定事件

    這篇文章主要介紹了JS如何實現(xiàn)動態(tài)添加的元素綁定事件,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11
  • Javascript 瀏覽器事件小結

    Javascript 瀏覽器事件小結

    Javascript中的事件都是與對象相關聯(lián)的,而不是語言本身所有,當Javascript在瀏覽器中運行的時候,事件與每個瀏覽器實現(xiàn)的DOM模型有關。
    2010-02-02
  • 詳解小程序云開發(fā)數(shù)據(jù)庫

    詳解小程序云開發(fā)數(shù)據(jù)庫

    這篇文章主要介紹了小程序云開發(fā)數(shù)據(jù)庫,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-05-05
  • 將函數(shù)的實際參數(shù)轉換成數(shù)組的方法

    將函數(shù)的實際參數(shù)轉換成數(shù)組的方法

    實際參數(shù)在函數(shù)中我們可以使用 arguments 對象獲得 (注:形參可通過 arguments.callee 獲得),雖然 arguments 對象與數(shù)組形似,但仍不是真正意義上的數(shù)組。
    2010-01-01
  • 基于JS實現(xiàn)頁面懸浮框的實例代碼

    基于JS實現(xiàn)頁面懸浮框的實例代碼

    這篇文章主要介紹了基于JS實現(xiàn)頁面懸浮框的實例代碼,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-12-12
  • canvas簡單快速的實現(xiàn)知乎登錄頁背景效果

    canvas簡單快速的實現(xiàn)知乎登錄頁背景效果

    本篇文章主要介紹了canvas簡單快速實現(xiàn)知乎登錄頁背景效果的相關知識,具有很好的參考價值。下面跟著小編一起來看下吧
    2017-05-05

最新評論

晋宁县| 蒲江县| 内江市| 德令哈市| 比如县| 乐山市| 共和县| 泸州市| 虎林市| 曲沃县| 万源市| 方城县| 宁夏| 马尔康县| 新田县| 安达市| 茂名市| 墨玉县| 绍兴县| 三江| 成武县| 刚察县| 珲春市| 涟源市| 台江县| 彭州市| 泰和县| 香格里拉县| 凤庆县| 永州市| 宿迁市| 于田县| 河北省| 南开区| 江孜县| 禹州市| 南城县| 韶关市| 凉山| 长武县| 吕梁市|