中国领先的IT技术网站
|
|

WebSocket实现原理

WebSocket 和 Socket 虽然名称上很像,但两者是完全不同的东西, WebSocket 是建立在 TCP/IP 协议之上,属于应用层的协议,而 Socket 是在应用层和传输层中的一个抽象层,它是将 TCP/IP 层的复杂操作抽象成几个简单的接口来提供给应用层调用。

作者:佚名来源:iOS大全|2017-07-11 13:58

Tech Neo技术沙龙 | 11月25号,九州云/ZStack与您一起探讨云时代网络边界管理实践


背景

之前我们将 CocoaAsyncSocket 作为底层实现,在其上面封装了一套 Socket 通信机制以及业务接口,最近我们开始研究 WebSocket ,并用来替换掉原先的 CocoaAsyncSocket ,简单来说一下两者的关系,WebSocket 和 Socket 虽然名称上很像,但两者是完全不同的东西, WebSocket 是建立在 TCP/IP 协议之上,属于应用层的协议,而 Socket 是在应用层和传输层中的一个抽象层,它是将 TCP/IP 层的复杂操作抽象成几个简单的接口来提供给应用层调用。为什么要做这次替换呢?原因是我们服务端在做改造,同时网页版 IM 已经使用了 WebSocket ,客户端也采用的话对于服务端来说维护一套代码会更好更方便,而且 WebSocket 在体积、实时性和扩展上都具有一定的优势。

WebSocket 最新的协议是 13 RFC 6455 ,要理解 WebSocket 的实现,一定要去理解它的协议!~

前言

WebSocket 的实现分为握手,数据发送/读取,关闭连接。

这里首先放上一张我们组 @省长 (推荐大家去读一读省长的博客,干货很多👍)整理出来的流程图,方便大家去理解:

握手

握手要从请求头去理解。

WebSocket 首先发起一个 HTTP 请求,在请求头加上 Upgrade 字段,该字段用于改变 HTTP 协议版本或者是换用其他协议,这里我们把 Upgrade 的值设为 websocket ,将它升级为 WebSocket 协议。

同时要注意 Sec-WebSocket-Key 字段,它由客户端生成并发给服务端,用于证明服务端接收到的是一个可受信的连接握手,可以帮助服务端排除自身接收到的由非 WebSocket 客户端发起的连接,该值是一串随机经过 base64 编码的字符串。

  1. GET /chat HTTP/1.1 
  2.  
  3. Host: server.example.com 
  4.  
  5. Upgrade: websocket 
  6.  
  7. Connection: Upgrade 
  8.  
  9. Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== 
  10.  
  11. Origin: http://example.com 
  12.  
  13. Sec-WebSocket-Protocol: chat, superchat 
  14.  
  15. Sec-WebSocket-Version: 13  

我们可以简化请求头,将请求以字符串方式发送出去,当然别忘了最后的两个空行作为包结束:

  1. const char * fmt = "GET %s HTTP/1.1\r\n" 
  2.  
  3. "Upgrade: websocket\r\n" 
  4.  
  5. "Connection: Upgrade\r\n" 
  6.  
  7. "Host: %s\r\n" 
  8.  
  9. "Sec-WebSocket-Key: %s\r\n" 
  10.  
  11. "Sec-WebSocket-Version: 13\r\n" 
  12.  
  13. "\r\n"
  14.  
  15. size = strlen(fmt) + strlen(path) + strlen(host) + strlen(ws->key); 
  16.  
  17. buf = (char *)malloc(size); 
  18.  
  19. sprintf(buf, fmt, path, host, ws->key); 
  20.  
  21. size = strlen(buf); 
  22.  
  23. nbytes = ws->io_send(ws, ws->context, buf, size);  

收到请求后,服务端也会做一次响应:

  1. HTTP/1.1 101 Switching Protocols 
  2.  
  3. Upgrade: websocket 
  4.  
  5. Connection: Upgrade 
  6.  
  7. Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  

里面重要的是 Sec-WebSocket-Accept ,服务端通过从客户端请求头中读取 Sec-WebSocket-Key 与一串全局唯一的标识字符串(俗称魔串)“258EAFA5-E914-47DA- 95CA-C5AB0DC85B11”做拼接,生成长度为160位的 SHA-1 字符串,然后进行 base64 编码,作为 Sec-WebSocket-Accept 的值回传给客户端。

处理握手 HTTP 响应解析的时候,可以用 nodejs 的 http-paser ,解析方式也比较简单,就是对头信息的逐字读取再处理,具体处理你可以看一下它的状态机实现。解析完成后你需要对其内容进行解析,看返回是否正确,同时去管理你的握手状态。

数据发送/读取

数据的处理就要拿这个帧协议图来说明了:

首先我们来看看数字的含义,数字表示位,0-7表示有8位,等于1个字节。

  1. 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 

所以如果要组装一个帧数据可以这样子:

  1. char *rev = (rev *)malloc(4); 
  2.  
  3. rev[0] = (char)(0x81 & 0xff); 
  4.  
  5. rev[1] = 126 & 0x7f; 
  6.  
  7. rev[2] = 1; 
  8.  
  9. rev[3] = 0;  

ok,了解了帧数据的样子,我们反过来去理解值对应的帧字段。

首先0x81是什么,这个是十六进制数据,转换成二进制就是1000 0001, 是一个字节的长度,也就是这一段里面每一位的值:

  • FIN 表示该帧是不是消息的最后一帧,1表示结束,0表示还有下一帧。
  • RSV1, RSV2, RSV3 必须为0,除非扩展协商定义了一个非0的值,如果没有定义非0值,且收到了非0的 RSV ,那么 WebSocket 的连接会失效。
  • opcode 用来描述 Payload data 的定义,如果收到了一个未知的 opcode ,同样会使 WebSocket 连接失效,协议定义了以下值:
    • %x0 表示连续的帧
    • %x1 表示 text 帧
    • %x2 表示二进制帧
    • %x3-7 预留给非控制帧
    • %x8 表示关闭连接帧
    • %x9 表示 ping
    • %xA 表示 pong
    • %xB-F 预留给控制帧

0xff 作用就是取出需要的二进制值。

下面再来看126,126则表示的是 Payload len ,也就是 Payload 的长度:

  • MASK 表示Playload data 是否要加掩码,如果设成1,则需要赋值 Masking-key 。所有从客户端发到服务端的帧都要加掩码
  • Playload len 表示 Payload 的长度,这里分为三种情况
    • 长度小于126,则只需要7位
    • 长度是126,则需要额外2个字节的大小,也就是 Extended payload length
    • 长度是127,则需要额外8个字节的大小,也就是 Extended payload length + Extended payload length continued ,Extended payload length 是2个字节,Extended payload length continued 是6个字节
  • Playload len 则表示 Extension data 与 Application data 的和

而数据的发送和读取就是对帧的封装和解析。

数据发送:

  1. void ws__wrap_packet(_WS_IN websocket_t *ws, 
  2.  
  3. _WS_IN const char *payload, 
  4.  
  5. _WS_IN unsigned long long payload_size, 
  6.  
  7. _WS_IN int flags, 
  8.  
  9. _WS_OUT char** out
  10.  
  11. _WS_OUT uint64_t *out_size) { 
  12.  
  13.   
  14.  
  15. struct timeval tv; 
  16.  
  17. char mask[4]; 
  18.  
  19. unsigned int mask_int; 
  20.  
  21. unsigned int payload_len_bits; 
  22.  
  23. unsigned int payload_bit_offset = 6; 
  24.  
  25. unsigned int extend_payload_len_bits, i; 
  26.  
  27. unsigned long long frame_size; 
  28.  
  29.   
  30.  
  31. const int MASK_BIT_LEN = 4; 
  32.  
  33.   
  34.  
  35. gettimeofday(&tv, NULL); 
  36.  
  37. srand(tv.tv_usec * tv.tv_sec); 
  38.  
  39. mask_int = rand(); 
  40.  
  41. memcpy(mask, &mask_int, 4); 
  42.  
  43.   
  44.  
  45. /** 
  46.  
  47. * payload_len bits 
  48.  
  49. * ref to https://tools.ietf.org/html/rfc6455#section-5.2 
  50.  
  51. * If 0-125, that is the payload length 
  52.  
  53.  
  54. * If payload length is equals 126, the following 2 bytes interpreted as a 
  55.  
  56. * 16-bit unsigned integer are the payload length 
  57.  
  58.  
  59. * If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the 
  60.  
  61. * most significant bit MUST be 0) are the payload length. 
  62.  
  63. */ 
  64.  
  65. if (payload_size 125) { 
  66.  
  67. // consts of ((fin + rsv1/2/3 + opcode) + payload-len bits + mask bit len + payload len) 
  68.  
  69. extend_payload_len_bits = 0; 
  70.  
  71. frame_size = 1 + 1 + MASK_BIT_LEN + payload_size; 
  72.  
  73.   
  74.  
  75. payload_len_bits = payload_size; 
  76.  
  77. else if (payload_size > 125 && payload_size 0xffff) { 
  78.  
  79. extend_payload_len_bits = 2; 
  80.  
  81. // consts of ((fin + rsv1/2/3 + opcode) + payload-len bits + extend-payload-len bites + mask bit len + payload len) 
  82.  
  83. frame_size = 1 + 1 + extend_payload_len_bits + MASK_BIT_LEN + payload_size; 
  84.  
  85. payload_len_bits = 126; 
  86.  
  87.   
  88.  
  89. payload_bit_offset += extend_payload_len_bits; 
  90.  
  91. else if (payload_size > 0xffff && payload_size 0xffffffffffffffffLL) { 
  92.  
  93. extend_payload_len_bits = 8; 
  94.  
  95. // consts of ((fin + rsv1/2/3 + opcode) + payload-len bits + extend-payload-len bites + mask bit len + payload len) 
  96.  
  97. frame_size = 1 + 1 + extend_payload_len_bits + MASK_BIT_LEN + payload_size; 
  98.  
  99. payload_len_bits = 127; 
  100.  
  101. payload_bit_offset += extend_payload_len_bits; 
  102.  
  103. else { 
  104.  
  105. if (ws->error_cb) { 
  106.  
  107. ws_error_t *err = ws_new_error(WS_SEND_DATA_TOO_LARGE_ERR); 
  108.  
  109. ws->error_cb(ws, err); 
  110.  
  111. free(err); 
  112.  
  113.  
  114. return ; 
  115.  
  116.  
  117.   
  118.  
  119. *out_size = frame_size; 
  120.  
  121. char *data = (*out) = (char *)malloc(frame_size); 
  122.  
  123. char *buf_offset = data; 
  124.  
  125.   
  126.  
  127. bzero(data, frame_size); 
  128.  
  129. *data = flags & 0xff; 
  130.  
  131.   
  132.  
  133. buf_offset = data + 1; 
  134.  
  135.   
  136.  
  137. // set mask bit = 1 
  138.  
  139. *(buf_offset) = payload_len_bits | 0x80; //payload length with mask bit on 
  140.  
  141.   
  142.  
  143. buf_offset = data + 2; 
  144.  
  145. if (payload_len_bits == 126) { 
  146.  
  147. payload_size &= 0xffff; 
  148.  
  149. else if (payload_len_bits == 127) { 
  150.  
  151. payload_size &= 0xffffffffffffffffLL; 
  152.  
  153.  
  154.   
  155.  
  156. for (i = 0; i 
  157.  
  158. *(buf_offset + i) = *((char *)&payload_size + (extend_payload_len_bits - i - 1)); 
  159.  
  160.  
  161.   
  162.  
  163.   
  164.  
  165. /** 
  166.  
  167. * according to https://tools.ietf.org/html/rfc6455#section-5.3 
  168.  
  169.  
  170. * buf_offset is set to mask bit 
  171.  
  172. */ 
  173.  
  174. buf_offset = data + payload_bit_offset - 4; 
  175.  
  176. for (i = 0; i 4; i++) { 
  177.  
  178. *(buf_offset + i) = mask[i] & 0xff; 
  179.  
  180.  
  181.   
  182.  
  183. /** 
  184.  
  185. * mask the payload data 
  186.  
  187. */ 
  188.  
  189. buf_offset = data + payload_bit_offset; 
  190.  
  191. memcpy(buf_offset, payload, payload_size); 
  192.  
  193. mask_payload(mask, buf_offset, payload_size); 
  194.  
  195.  
  196.   
  197.  
  198. void mask_payload(char mask[4], char *payload, unsigned long long payload_size) { 
  199.  
  200. unsigned long long i; 
  201.  
  202. for(i = 0; i 
  203.  
  204. *(payload + i) ^= mask[i % 4] & 0xff; 
  205.  
  206.  
  207.  

数据解析:

  1. int ws_recv(websocket_t *ws) { 
  2.  
  3. if (ws->state 
  4.  
  5. return ws_do_handshake(ws); 
  6.  
  7.  
  8.   
  9.  
  10. int ret; 
  11.  
  12. while(true) { 
  13.  
  14. ret = ws__recv(ws); 
  15.  
  16. if (ret != OK) { 
  17.  
  18. break; 
  19.  
  20.  
  21.  
  22.   
  23.  
  24. return ret; 
  25.  
  26.  
  27.   
  28.  
  29. int ws__recv(websocket_t *ws) { 
  30.  
  31. if (ws->state 
  32.  
  33. return ws_do_handshake(ws); 
  34.  
  35.  
  36.   
  37.  
  38. int ret = OK, i; 
  39.  
  40. int state = ws->rd_state; 
  41.  
  42. char *rd_buf; 
  43.  
  44.   
  45.  
  46. switch(state) { 
  47.  
  48. case WS_READ_IDLE: { 
  49.  
  50. ret = ws__make_up(ws, 2); 
  51.  
  52. if (ret != OK) { 
  53.  
  54. return ret; 
  55.  
  56.  
  57.   
  58.  
  59. ws_frame_t * frame; 
  60.  
  61. if (ws->c_frame == NULL) { 
  62.  
  63. ws__append_frame(ws); 
  64.  
  65.  
  66. frame = ws->c_frame; 
  67.  
  68. rd_buf = ws->buf; 
  69.  
  70. frame->fin = (*(rd_buf) & 0x80) == 0x80 ? 1 : 0; 
  71.  
  72. frame->op_code = *(rd_buf) & 0x0fu; 
  73.  
  74. frame->payload_len = *(rd_buf + 1) & 0x7fu; 
  75.  
  76.   
  77.  
  78. if (frame->payload_len 126) { 
  79.  
  80. frame->payload_bit_offset = 2; 
  81.  
  82. ws->rd_state = WS_READ_PAYLOAD; 
  83.  
  84. else if (frame -> payload_len == 126) { 
  85.  
  86. frame->payload_bit_offset = 4; 
  87.  
  88. ws->rd_state = WS_READ_EXTEND_PAYLOAD_2_WORDS; 
  89.  
  90. else { 
  91.  
  92. frame->payload_bit_offset = 8; 
  93.  
  94. ws->rd_state = WS_READ_EXTEND_PAYLOAD_8_WORDS; 
  95.  
  96.  
  97.   
  98.  
  99. ws__reset_buf(ws, 2); 
  100.  
  101. break; 
  102.  
  103.  
  104. case WS_READ_EXTEND_PAYLOAD_2_WORDS: { 
  105.  
  106. #define PAYLOAD_LEN_BITS 2 
  107.  
  108. ret = ws__make_up(ws, PAYLOAD_LEN_BITS); 
  109.  
  110. if (ret != OK) { 
  111.  
  112. return ret; 
  113.  
  114.  
  115. rd_buf = ws->buf; 
  116.  
  117. ws_frame_t * frame = ws->c_frame; 
  118.  
  119.   
  120.  
  121. char *payload_len_bytes = (char *)&frame->payload_len; 
  122.  
  123. for (i = 0; i 
  124.  
  125. *(payload_len_bytes + i) = rd_buf[PAYLOAD_LEN_BITS - 1 - i]; 
  126.  
  127.  
  128.   
  129.  
  130. ws__reset_buf(ws, PAYLOAD_LEN_BITS); 
  131.  
  132. ws->rd_state = WS_READ_PAYLOAD; 
  133.  
  134. #undef PAYLOAD_LEN_BITS 
  135.  
  136. break; 
  137.  
  138.  
  139. case WS_READ_EXTEND_PAYLOAD_8_WORDS: { 
  140.  
  141. #define PAYLOAD_LEN_BITS 8 
  142.  
  143. ret = ws__make_up(ws, PAYLOAD_LEN_BITS); 
  144.  
  145. if (ret != OK) { 
  146.  
  147. return ret; 
  148.  
  149.  
  150.   
  151.  
  152. rd_buf = ws->buf; 
  153.  
  154. ws_frame_t * frame = ws->c_frame; 
  155.  
  156. char *payload_len_bytes = (char *)&frame->payload_len; 
  157.  
  158. for (i = 0; i 
  159.  
  160. *(payload_len_bytes + i) = rd_buf[PAYLOAD_LEN_BITS - 1 - i]; 
  161.  
  162.  
  163.   
  164.  
  165. ws__reset_buf(ws, PAYLOAD_LEN_BITS); 
  166.  
  167. ws->rd_state = WS_READ_PAYLOAD; 
  168.  
  169. #undef PAYLOAD_LEN_BITS 
  170.  
  171. break; 
  172.  
  173.  
  174. case WS_READ_PAYLOAD: { 
  175.  
  176. ws_frame_t * frame = ws->c_frame; 
  177.  
  178. uint64_t payload_len = frame->payload_len; 
  179.  
  180. ret = ws__make_up(ws, payload_len); 
  181.  
  182. if (ret != OK) { 
  183.  
  184. return ret; 
  185.  
  186.  
  187.   
  188.  
  189.   
  190.  
  191. rd_buf = ws->buf; 
  192.  
  193. frame->payload = malloc(payload_len); 
  194.  
  195. memcpy(frame->payload, rd_buf, payload_len); 
  196.  
  197.   
  198.  
  199. ws__reset_buf(ws, payload_len); 
  200.  
  201.   
  202.  
  203. if (frame->fin == 1) { 
  204.  
  205. // is control frame 
  206.  
  207. ws__dispatch_msg(ws, frame); 
  208.  
  209. ws__clean_frame(ws); 
  210.  
  211. else { 
  212.  
  213. ws__append_frame(ws); 
  214.  
  215.  
  216.   
  217.  
  218. ws->rd_state = WS_READ_IDLE; 
  219.  
  220.   
  221.  
  222. break; 
  223.  
  224.  
  225.  
  226.   
  227.  
  228. return ret; 
  229.  
  230.  

关闭连接

关闭连接分为两种:服务端发起关闭和客户端主动关闭。

服务端跟客户端的处理基本一致,以服务端为例:

服务端发起关闭的时候,会客户端发送一个关闭帧,客户端在接收到帧的时候通过解析出帧的opcode来判断是否是关闭帧,然后同样向服务端再发送一个关闭帧作为回应。

  1. if (op_code == OP_CLOSE) { 
  2.  
  3. int status_code; 
  4.  
  5. char *reason; 
  6.  
  7. char *status_code_buf = (char *)&status_code; 
  8.  
  9. status_code_buf[0] = payload[1]; 
  10.  
  11. status_code_buf[1] = payload[0]; 
  12.  
  13. reason = payload + 2; 
  14.  
  15.   
  16.  
  17. if (ws->state != WS_STATE_CLOSED) { 
  18.  
  19. /** 
  20.  
  21. * should send response to remote server 
  22.  
  23. */ 
  24.  
  25.   
  26.  
  27. ws_send(ws, NULL, 0, OP_CLOSE | FLAG_FIN); 
  28.  
  29. ws->state = WS_STATE_CLOSED; 
  30.  
  31.  
  32.   
  33.  
  34. // close connection 
  35.  
  36. if (ws->close_cb) { 
  37.  
  38. ws->close_cb(ws, status_code, reason); 
  39.  
  40.  
  41.  

总结

对WebSocket的学习主要是对协议的理解,理解了协议,上面复杂的代码自然而然就会明白~

后记

对于I/O操作的原理,推荐大家可以看看这个:epoll 或者 kqueue 的原理是什么?

https://www.zhihu.com/question/20122137/answer/14049112#

【编辑推荐】

  1. JavaEE7 Websockets和GlassFish4建聊天室_开发技术周刊第103期_51CTO.com
  2. HTML5 WebSockets初探
  3. .NET 的WebSocket开发包详细比较
  4. Shou.TV 背后基于 Node.js 和 WebSocket 的技术架构
  5. Java EE 7当中的编程式Websocket端点
【责任编辑:枯木 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Wicked Cool Java中文版

本书主要介绍由Sun微系统公司创建的Java编程语言。 除了核心内容外,Java还有许多免费的财富,即开放源代码的库。本书就是为了介绍这些库...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊
× CTO训练营(深圳站)