網絡協議:WebSocket協議的握手和數據幀
WebSocket是定義服務器和客戶端如何通過Web通信的一種網絡協議。協議是通信的議定規則。組成互聯網的協議組由IETF(互聯網工程任務組)發布。IETF發布評議請求(Request for Comments,RFC),精確地規定了協議(包括RFC 6455):WebSocket協議。RFC 6455于2011年12月發布,包含了實現WebSocket客戶端和服務器時必須遵循的規則。
websocket基本上是一個很簡單的協議, 主要流程非常少, 實現起來也很簡單。
為簡單起見, 下面只分析握手和數據幀的報文.
一. 握手(handshake).
握手協議由客戶端發起, 服務器響應, 一來一回就完成了. 基本上是為了兼容現有的http基礎設施.
下面是一個客戶端發起的握手請求:
47 45 54 20 2F 20 48 54 54 50 2F 31 2E 31 0D 0A GET./.HTTP/1.1..
55 70 67 72 61 64 65 3A 20 77 65 62 73 6F 63 6B Upgrade:.websock
65 74 0D 0A 43 6F 6E 6E 65 63 74 69 6F 6E 3A 20 et..Connection:.
55 70 67 72 61 64 65 0D 0A 48 6F 73 74 3A 20 31 Upgrade..Host:.1
39 32 2E 31 36 38 2E 38 2E 31 32 38 3A 31 33 30 92.168.8.128:130
30 0D 0A 4F 72 69 67 69 6E 3A 20 6E 75 6C 6C 0D 0..Origin:.null.
0A 50 72 61 67 6D 61 3A 20 6E 6F 2D 63 61 63 68 .Pragma:.no-cach
65 0D 0A 43 61 63 68 65 2D 43 6F 6E 74 72 6F 6C e..Cache-Control
3A 20 6E 6F 2D 63 61 63 68 65 0D 0A 53 65 63 2D :.no-cache..Sec-
57 65 62 53 6F 63 6B 65 74 2D 4B 65 79 3A 20 64 WebSocket-Key:.d
33 35 39 46 64 6F 36 6F 6D 79 71 66 78 79 59 46 359Fdo6omyqfxyYF
37 59 61 63 77 3D 3D 0D 0A 53 65 63 2D 57 65 62 7Yacw==..Sec-Web
53 6F 63 6B 65 74 2D 56 65 72 73 69 6F 6E 3A 20 Socket-Version:.
31 33 0D 0A 53 65 63 2D 57 65 62 53 6F 63 6B 65 13..Sec-WebSocke
74 2D 45 78 74 65 6E 73 69 6F 6E 73 3A 20 78 2D t-Extensions:.x-
77 65 62 6B 69 74 2D 64 65 66 6C 61 74 65 2D 66 webkit-deflate-f
72 61 6D 65 0D 0A 55 73 65 72 2D 41 67 65 6E 74 rame..User-Agent
3A 20 4D 6F 7A 69 6C 6C 61 2F 35 2E 30 20 28 57 :.Mozilla/5.0.(W
69 6E 64 6F 77 73 20 4E 54 20 36 2E 31 3B 20 57 indows.NT.6.1;.W
4F 57 36 34 29 20 41 70 70 6C 65 57 65 62 4B 69 OW64).AppleWebKi
74 2F 35 33 37 2E 33 36 20 28 4B 48 54 4D 4C 2C t/537.36.(KHTML,
20 6C 69 6B 65 20 47 65 63 6B 6F 29 20 43 68 72 .like.Gecko).Chr
6F 6D 65 2F 33 32 2E 30 2E 31 36 35 33 2E 30 20 ome/32.0.1653.0.
53 61 66 61 72 69 2F 35 33 37 2E 33 36 0D 0A 0D Safari/537.36...
0A
0D 0A 0D 0A, 也就是用"\r\n\r\n"收尾, 這和http頭沒什么區別. 轉換成字符串就是:
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 192.168.8.128:1300
Origin: null
Pragma: no-cache
Cache-Control: no-cache
Sec-WebSocket-Key: d359Fdo6omyqfxyYF7Yacw==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1653.0 Safari/537.36
其中有一對重要的kv, 就是Sec-WebSocket-Key: d359Fdo6omyqfxyYF7Yacw==, 看上去是一個base64編碼后的結果, 服務器需要對這個sec-key作一些處理, 并返回握手響應, 這個處理是:
- byte[] sha = sha1(("d359Fdo6omyqfxyYF7Yacw==" + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes());
- System.out.println(new String(Base64.getEncoder().encode(sha)));
也就是原封不動的拿著這個sec-key和另一個神奇的字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"相連, 再經過sha1摘要
算法處理, 最后再經過base64編碼輸出即可, 上面的輸出結果應該是: pLO2KC7b5t0TZl1E6A3sqJ6EzU4=
服務器在收到握手請求后, 如果愿意提供服務, 則返回一個握手響應, 如下:
- HTTP/1.1 101 Switching Protocols
- Connection: Upgrade
- Upgrade: WebSocket
- Sec-WebSocket-Accept: pLO2KC7b5t0TZl1E6A3sqJ6EzU4=
遵循http的規則, 字節流上一樣是要以"\r\n\r\n"收尾.
二. 數據幀
rfc6455上叫做非控制幀, 除了非控制幀之外, 就是控制幀. 包括connection close, ping, pong等幀, 這里只講非控制幀, 也就是數據幀.
數據幀從長度上可以分為三種. 幀中的靜荷數據(payload data)長度小于0x7E的為小幀, 靜荷數據長度 >=0x7E又<=0x10000的為中幀,
再長的叫大幀.
數據幀從類型上暫時可以分為兩種, 文本幀和二進制幀.
例子:
a). 一個從客戶端發向服務端的小幀.
- 82 B0 6A F7 C6 30 0A D9 C6 34 D4 18 78 C1 6E F5 ..j..0...4..x.n.
- C6 30 6C D5 CC 10 23 87 AF 48 3C A2 9C 64 01 C4 .0l...#..H<..d..
- AE 59 04 C5 B1 5B 35 85 A3 41 18 B0 F5 5C 13 8E .Y...[5..A...\..
- 92 42 02 84 85 53 .B...S
82
二進制為: 1000 0010, 最高位(FIN)為1, 表示這是最后一幀, 第一個幀也可能是最后一幀. 身后還有三位為預留. 低位四0010為操作碼.
也就是0x02, 表示這是一個二進制幀, 0x01為文本幀.
B0
二進制為: 1011 0000, 最高位(MASK)為1, 表示當前幀的靜荷數據部分使用了掩碼, 事實上, rfc6455規定從客戶端發往服務器端的數據幀
必需使用掩碼, 反過來, 從服務器發回來的, 則必需不使用掩碼. 低7位為靜荷數據長度字段, 這里是011 0000, 也就是0x30, 從上面的報文上
看, 這個0x30沒有包含后面的掩碼.
6A F7 C6 30
掩碼, 掩碼總是四個字節.
0A D9 C6...一直到最后為經過掩碼加工后的靜荷數據. 要回到數據本來的面目, 使用下面的算法:
- byte by[] = new byte[]{0x82, 0xB0, 0x6A, 0xF7, 0xC6, 0x30, 0x0A....};
- byte mask[] = new byte[] { 0x6A, (byte) 0xF7, (byte) 0xC6, 0x30 };
- for (int i = 6 /* 越過掩碼. */; i < by.length; i++)
- by[i] = (byte) (by[i] ^ mask[(i - 6) % 4]);
得到的結果應該是:
- 82 B0 6A F7 C6 30 60 2E 00 04 BE EF BE F1 04 02 ..j..0`.........
- 00 00 06 22 0A 20 49 70 69 78 56 55 5A 54 6B 33 ..."..IpixVUZTk3
- 68 69 6E 32 77 6B 5F 72 65 71 72 47 33 6C 79 79 hin2wk_reqrG3lyy
- 54 72 68 73 43 63 TrhsCc
b). 一個從服務器發給客戶端的小幀.
- 82 29 61 27 01 04 BE EF BE F1 05 02 00 00 06 1B .)a'............
- 0A 08 55 3B 02 19 39 35 E2 44 12 0F 21 EC BC 47 ..U;..95.D..!..G
- 02 F3 EC 70 ED 5B 7B 07 C7 F4 D0 ...p.[{....
更簡單了, 還是82, 最后一幀, 二進制幀, 29, 0010 1001, 無掩碼, 也就是身后全長為0x29.
c). 未使用掩碼的中幀.
81 7E 01 00 66 77 88 ..., 幀長為 0x0100, 也就是256個字節.
d). 未使用掩碼的大幀.
82 7F 00 00 00 00 11 22 33 44 66 77 88 ..., 幀長為0x0000000011223344, 直接跳過4字節, 而使用8字節來表示長度, 非常暴力.
這里需要注意的是, websocket要求使用最小幀原則, 也就是靜荷數據長度小于0x7E幀, 不能使用中幀或大幀的來表示. 長度小于
0x10000的幀也不能用大幀來表示.