本項目地址:gof 一個支持百萬連接的websocket框架
本文提及的內(nèi)容包含在:conn.go
在上一章節(jié)中,我們創(chuàng)建了一個epoll結(jié)構(gòu)體,并在該結(jié)構(gòu)體中記錄了一個epoll對象。接下來我們就需要通過初始化該對象,從而實現(xiàn)消息的接收。
一、WebSocket的流程
首先,websocket也是作用在tcp連接之上的。因此,websocket的工作過程如下:
1、三次握手
這個過程是在Tcp層完成的。而我們的gof是應用層,所以這份工作,linux底層 已經(jīng)幫我們完成了。
2、發(fā)送header頭
握手完成之后,會在服務端建立一個socket句柄,首先接收到的消息就是客戶端發(fā)送的header頭信息。其中包含一下幾個內(nèi)容:
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: Upgrade
Host: 127.0.0.1:8801
Origin: http://www.easyswoole.com
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: qHHCi/x3CScAZASynxuiJA==
Sec-WebSocket-Version: 13
Upgrade: websocket
3、服務端回應消息頭
服務端在收到消息頭之后需要對客戶端進行回應,其中必須要包含的內(nèi)容為:
Connection: Upgrade
Sec-WebSocket-Accept: SzZpB9M8fiQtfbE4F1iAthSkY9k=
Sec-WebSocket-Extensions: permessage-deflate
Upgrade: websocket
在完成了這些工作之后,客戶端和服務端就可以進行通信了。
二、websocket消息幀
在完成了握手和消息頭的回應之后,websocket收發(fā)消息是通過消息幀來進行的。結(jié)構(gòu)如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
對于消息幀的解釋,在這里參考知乎的一篇文章:websocket 協(xié)議幀 解析
FIN:1 bit
表示這是不是消息的最后一幀。 %x0:還有后續(xù)幀 %x1:最后一幀
RSV1、RSV2、RSV3:1 bit
擴展字段,除非一個擴展經(jīng)過協(xié)商賦予了非零值的某種含義,否則必須為0
opcode:4 bit
解釋 payload data 的類型,如果收到識別不了的opcode,直接斷開。分類值如下: %x0:連續(xù)的幀 %x1:text幀 %x2:binary幀 %x3 - 7:為非控制幀而預留的 %x8:關閉握手幀 %x9:ping幀 %xA:pong幀 %xB - F:為非控制幀而預留的
MASK:1 bit
標識 Payload data 是否經(jīng)過掩碼處理,如果是 1,Masking-key域的數(shù)據(jù)即為掩碼密鑰,用于解碼Payload data。協(xié)議規(guī)定客戶端數(shù)據(jù)需要進行掩碼處理,所以此位為1
Payload len:7 bit | 7+16 bit | 7+64 bit
表示了 “有效負荷數(shù)據(jù) Payload data”,以字節(jié)為單位: - 如果是 0~125,那么就直接表示了 payload 長度 - 如果是 126,那么 先存儲 0x7E(=126)接下來的兩個字節(jié)表示的 16位無符號整型數(shù)的值就是 payload 長度 - 如果是 127,那么 先存儲 0x7E(=126)接下來的八個字節(jié)表示的 64位無符號整型數(shù)的值就是 payload 長度
Masking-key:0 | 4 bytes
掩碼密鑰,所有從客戶端發(fā)送到服務端的幀都包含一個 32bits 的掩碼(如果mask被設置成1),否則為0。一旦掩碼被設置,所有接收到的 payload data 都必須與該值以一種算法做異或運算來獲取真實值。
ws協(xié)議中,數(shù)據(jù)掩碼的作用是增強協(xié)議的安全性。但數(shù)據(jù)掩碼并不是為了保護數(shù)據(jù)本身,因為算法本身是公開的,運算也不復雜。除了加密通道本身,似乎沒有太多有效的保護通信安全的辦法,那么為什么還要引入掩碼計算呢,除了增加計算機器的運算量外似乎并沒有太多的收益(這也是不少同學疑惑的點)
答案還是兩個字:安全。但并不是為了防止數(shù)據(jù)泄密,而是為了防止早期版本的協(xié)議中存在的代理緩存污染攻擊(proxy cache poisoning attacks)等問題
Payload data:(x+y) bytes
它是 Extension data 和 Application data 數(shù)據(jù)的總和,但是一般擴展數(shù)據(jù)為空。
Extension data:x bytes
除非擴展被定義,否則就是0
Application data:y bytes
占據(jù) Extension data 后面的所有空間
在下一章節(jié)中,我們就通過實際的代碼來對于websocket的發(fā)送數(shù)據(jù)和接收數(shù)據(jù)進行詳細的說明。