亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

什么是 WebSocket ?

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

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

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

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

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

下面我們就結(jié)合上圖具體來(lái)聊一下 WebSocket 的通信過(guò)程。

建立連接

客戶(hù)端請(qǐng)求報(bào)文 Header

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

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 報(bào)文不同的地方:

Upgrade: websocket
Connection: Upgrade

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

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

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

Sec-WebSocket-Version 表示 WebSocket 的版本,最初 WebSocket 協(xié)議太多,不同廠(chǎng)商都有自己的協(xié)議版本,不過(guò)現(xiàn)在已經(jīng)定下來(lái)了。如果服務(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é)議,后面接地址及端口

完整的客戶(hù)端代碼:

<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 () {}; // 首先關(guān)閉 WebSocket
 ws.close()
 };
</script>

服務(wù)端響應(yīng)報(bào)文 Header

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

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

我們一行行來(lái)解釋

  • 首先,101 狀態(tài)碼表示服務(wù)器已經(jīng)理解了客戶(hù)端的請(qǐng)求,并將通過(guò) Upgrade 消息頭通知客戶(hù)端采用不同的協(xié)議來(lái)完成這個(gè)請(qǐng)求;
  • 然后,Sec-WebSocket-Accept 這個(gè)則是經(jīng)過(guò)服務(wù)器確認(rèn),并且加密過(guò)后的 Sec-WebSocket-Key;
  • 最后,Sec-WebSocket-Protocol 則是表示最終使用的協(xié)議。

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

  • 將 Sec-WebSocket-Key 跟 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接;
  • 通過(guò) SHA1 計(jì)算出摘要,并轉(zhuǎn)成 base64 字符串。

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

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

def create_socket():
 # 啟動(dòng) Socket 并監(jiān)聽(tīng)連接
 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...')

 # 等待訪(fǎng)問(wèn)
 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)行通信

服務(wù)端解析 WebSocket 報(bào)文

Server 端接收到 Client 發(fā)來(lái)的報(bào)文需要進(jìn)行解析

Client 包格式

FIN: 占 1bit

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

RSV1, RSV2, RSV3:各占 1bit

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

Opcode: 4bit

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

Mask: 1bit

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

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

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

Masking-key: 0 or 4bytes

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

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ù)的大小寫(xiě)入長(zhǎng)度,最后寫(xiě)入載荷數(shù)據(jù)。

struct 模塊解析

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

按照給定的格式 fmt,把數(shù)據(jù)封裝成字符串 ( 實(shí)際上是類(lèi)似于 C 結(jié)構(gòu)體的字節(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 語(yǔ)言中的結(jié)構(gòu)體交換數(shù)據(jù),還要考慮有的 C 或 C++ 編譯器使用了字節(jié)對(duì)齊,通常是以 4 個(gè)字節(jié)為單位的 32 位系統(tǒng),故而 struct 根據(jù)本地機(jī)器字節(jié)順序轉(zhuǎn)換??梢杂酶袷街械牡谝粋€(gè)字符來(lái)改變對(duì)齊方式,定義如下:

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 報(bào)文代碼如下:

def write_msg(message):
 data = struct.pack('B', 129) # 寫(xiě)入第一個(gè)字節(jié),10000001

 # 寫(xiě)入包長(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') # 寫(xiě)入消息內(nèi)容
 logging.debug(data)
 return data

總結(jié)

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

  • 多個(gè)用戶(hù)之間進(jìn)行交互;
  • 需要頻繁地向服務(wù)端請(qǐng)求更新數(shù)據(jù)。

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

參考文章

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

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

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

總結(jié)

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

相關(guān)文章

最新評(píng)論