135-1821-9792

WebSocket实现原理

背景

之前我们将 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-C***B0DC85B11”做拼接,生成长度为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.  
  55. * If payload length is equals 126, the following 2 bytes interpreted as a 
  56.  
  57. * 16-bit unsigned integer are the payload length 
  58.  
  59. * 
  60.  
  61. * If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the 
  62.  
  63. * most significant bit MUST be 0) are the payload length. 
  64.  
  65. */ 
  66.  
  67. if (payload_size 125) { 
  68.  
  69. // consts of ((fin + rsv1/2/3 + opcode) + payload-len bits + mask bit len + payload len) 
  70.  
  71. extend_payload_len_bits = 0; 
  72.  
  73. frame_size = 1 + 1 + MASK_BIT_LEN + payload_size; 
  74.  
  75.   
  76.  
  77. payload_len_bits = payload_size; 
  78.  
  79. } else if (payload_size > 125 && payload_size 0xffff) { 
  80.  
  81. extend_payload_len_bits = 2; 
  82.  
  83. // consts of ((fin + rsv1/2/3 + opcode) + payload-len bits + extend-payload-len bites + mask bit len + payload len) 
  84.  
  85. frame_size = 1 + 1 + extend_payload_len_bits + MASK_BIT_LEN + payload_size; 
  86.  
  87. payload_len_bits = 126; 
  88.  
  89.   
  90.  
  91. payload_bit_offset += extend_payload_len_bits; 
  92.  
  93. } else if (payload_size > 0xffff && payload_size 0xffffffffffffffffLL) { 
  94.  
  95. extend_payload_len_bits = 8; 
  96.  
  97. // consts of ((fin + rsv1/2/3 + opcode) + payload-len bits + extend-payload-len bites + mask bit len + payload len) 
  98.  
  99. frame_size = 1 + 1 + extend_payload_len_bits + MASK_BIT_LEN + payload_size; 
  100.  
  101. payload_len_bits = 127; 
  102.  
  103. payload_bit_offset += extend_payload_len_bits; 
  104.  
  105. } else { 
  106.  
  107. if (ws->error_cb) { 
  108.  
  109. ws_error_t *err = ws_new_error(WS_SEND_DATA_TOO_LARGE_ERR); 
  110.  
  111. ws->error_cb(ws, err); 
  112.  
  113. free(err); 
  114.  
  115. } 
  116.  
  117. return ; 
  118.  
  119. } 
  120.  
  121.   
  122.  
  123. *out_size = frame_size; 
  124.  
  125. char *data = (*out) = (char *)malloc(frame_size); 
  126.  
  127. char *buf_offset = data; 
  128.  
  129.   
  130.  
  131. bzero(data, frame_size); 
  132.  
  133. *data = flags & 0xff; 
  134.  
  135.   
  136.  
  137. buf_offset = data + 1; 
  138.  
  139.   
  140.  
  141. // set mask bit = 1 
  142.  
  143. *(buf_offset) = payload_len_bits | 0x80; //payload length with mask bit on 
  144.  
  145.   
  146.  
  147. buf_offset = data + 2; 
  148.  
  149. if (payload_len_bits == 126) { 
  150.  
  151. payload_size &= 0xffff; 
  152.  
  153. } else if (payload_len_bits == 127) { 
  154.  
  155. payload_size &= 0xffffffffffffffffLL; 
  156.  
  157. } 
  158.  
  159.   
  160.  
  161. for (i = 0; i 
  162.  
  163. *(buf_offset + i) = *((char *)&payload_size + (extend_payload_len_bits - i - 1)); 
  164.  
  165. } 
  166.  
  167.   
  168.  
  169.   
  170.  
  171. /** 
  172.  
  173. * according to https://tools.ietf.org/html/rfc6455#section-5.3 
  174.  
  175. * 
  176.  
  177. * buf_offset is set to mask bit 
  178.  
  179. */ 
  180.  
  181. buf_offset = data + payload_bit_offset - 4; 
  182.  
  183. for (i = 0; i 4; i++) { 
  184.  
  185. *(buf_offset + i) = mask[i] & 0xff; 
  186.  
  187. } 
  188.  
  189.   
  190.  
  191. /** 
  192.  
  193. * mask the payload data 
  194.  
  195. */ 
  196.  
  197. buf_offset = data + payload_bit_offset; 
  198.  
  199. memcpy(buf_offset, payload, payload_size); 
  200.  
  201. mask_payload(mask, buf_offset, payload_size); 
  202.  
  203. } 
  204.  
  205.   
  206.  
  207. void mask_payload(char mask[4], char *payload, unsigned long long payload_size) { 
  208.  
  209. unsigned long long i; 
  210.  
  211. for(i = 0; i 
  212.  
  213. *(payload + i) ^= mask[i % 4] & 0xff; 
  214.  
  215. } 
  216.  
  217. }  

数据解析:

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

关闭连接

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

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

服务端发起关闭的时候,会客户端发送一个关闭帧,客户端在接收到帧的时候通过解析出帧的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.  
  35. // close connection 
  36.  
  37. if (ws->close_cb) { 
  38.  
  39. ws->close_cb(ws, status_code, reason); 
  40.  
  41. } 
  42.  
  43. }  

总结

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

后记

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

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


当前题目:WebSocket实现原理
链接URL:http://www.wenjiangwz.cn/article/copsieo.html

其他资讯



Copyright © 2009-2022 www.wenjiangwz.cn 成都网站建设公司 版权所有 蜀ICP备19037934号