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 编码的字符串。

GET /chat HTTP/1.1  Host: server.example.com  Upgrade: websocket  Connection: Upgrade  Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==  Origin: http://example.com  Sec-WebSocket-Protocol: chat, superchat  Sec-WebSocket-Version: 13 

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

const char * fmt = "GET %s HTTP/1.1"  "Upgrade: websocket"  "Connection: Upgrade"  "Host: %s"  "Sec-WebSocket-Key: %s"  "Sec-WebSocket-Version: 13"  "";  size = strlen(fmt) + strlen(path) + strlen(host) + strlen(ws->key);  buf = (char *)malloc(size);  sprintf(buf, fmt, path, host, ws->key);  size = strlen(buf);  nbytes = ws->io_send(ws, ws->context, buf, size); 收到请求后,服务端也会做一次响应:HTTP/1.1 101 Switching Protocols  Upgrade: websocket  Connection: Upgrade  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个字节。

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 

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

char *rev = (rev *)malloc(4);  rev[0] = (char)(0x81 & 0xff);  rev[1] = 126 & 0x7f;  rev[2] = 1;  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 的和

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

数据发送:void ws__wrap_packet(_WS_IN websocket_t *ws,  _WS_IN const char *payload,  _WS_IN unsigned long long payload_size,  _WS_IN int flags,  _WS_OUT char** out,  _WS_OUT uint64_t *out_size) {    struct timeval tv;  char mask[4];  unsigned int mask_int;  unsigned int payload_len_bits;  unsigned int payload_bit_offset = 6;  unsigned int extend_payload_len_bits, i;  unsigned long long frame_size;    const int MASK_BIT_LEN = 4;    gettimeofday(&tv, NULL);  srand(tv.tv_usec * tv.tv_sec);  mask_int = rand();  memcpy(mask, &mask_int, 4);    /**  * payload_len bits  * ref to https://tools.ietf.org/html/rfc6455#section-5.2  * If 0-125, that is the payload length  *  * If payload length is equals 126, the following 2 bytes interpreted as a  * 16-bit unsigned integer are the payload length  *  * If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the  * most significant bit MUST be 0) are the payload length.  */  if (payload_size 125) {  // consts of ((fin + rsv1/2/3 + opcode) + payload-len bits + mask bit len + payload len)  extend_payload_len_bits = 0;  frame_size = 1 + 1 + MASK_BIT_LEN + payload_size;    payload_len_bits = payload_size;  } else if (payload_size > 125 && payload_size 0xffff) {  extend_payload_len_bits = 2;  // consts of ((fin + rsv1/2/3 + opcode) + payload-len bits + extend-payload-len bites + mask bit len + payload len)  frame_size = 1 + 1 + extend_payload_len_bits + MASK_BIT_LEN + payload_size;  payload_len_bits = 126;    payload_bit_offset += extend_payload_len_bits;  } else if (payload_size > 0xffff && payload_size 0xffffffffffffffffLL) {  extend_payload_len_bits = 8;  // consts of ((fin + rsv1/2/3 + opcode) + payload-len bits + extend-payload-len bites + mask bit len + payload len)  frame_size = 1 + 1 + extend_payload_len_bits + MASK_BIT_LEN + payload_size;  payload_len_bits = 127;  payload_bit_offset += extend_payload_len_bits;  } else {  if (ws->error_cb) {  ws_error_t *err = ws_new_error(WS_SEND_DATA_TOO_LARGE_ERR);  ws->error_cb(ws, err);  free(err);  }  return ;  }    *out_size = frame_size;  char *data = (*out) = (char *)malloc(frame_size);  char *buf_offset = data;    bzero(data, frame_size);  *data = flags & 0xff;    buf_offset = data + 1;    // set mask bit = 1  *(buf_offset) = payload_len_bits | 0x80; //payload length with mask bit on    buf_offset = data + 2;  if (payload_len_bits == 126) {  payload_size &= 0xffff;  } else if (payload_len_bits == 127) {  payload_size &= 0xffffffffffffffffLL;  }    for (i = 0; i  *(buf_offset + i) = *((char *)&payload_size + (extend_payload_len_bits - i - 1));  }      /**  * according to https://tools.ietf.org/html/rfc6455#section-5.3  *  * buf_offset is set to mask bit  */  buf_offset = data + payload_bit_offset - 4;  for (i = 0; i 4; i++) {  *(buf_offset + i) = mask[i] & 0xff;  }    /**  * mask the payload data  */  buf_offset = data + payload_bit_offset;  memcpy(buf_offset, payload, payload_size);  mask_payload(mask, buf_offset, payload_size);  }    void mask_payload(char mask[4], char *payload, unsigned long long payload_size) {  unsigned long long i;  for(i = 0; i  *(payload + i) ^= mask[i % 4] & 0xff;  }  } 数据解析:int ws_recv(websocket_t *ws) {  if (ws->state  return ws_do_handshake(ws);  }    int ret;  while(true) {  ret = ws__recv(ws);  if (ret != OK) {  break;  }  }    return ret;  }    int ws__recv(websocket_t *ws) {  if (ws->state  return ws_do_handshake(ws);  }    int ret = OK, i;  int state = ws->rd_state;  char *rd_buf;    switch(state) {  case WS_READ_IDLE: {  ret = ws__make_up(ws, 2);  if (ret != OK) {  return ret;  }    ws_frame_t * frame;  if (ws->c_frame == NULL) {  ws__append_frame(ws);  }  frame = ws->c_frame;  rd_buf = ws->buf;  frame->fin = (*(rd_buf) & 0x80) == 0x80 ? 1 : 0;  frame->op_code = *(rd_buf) & 0x0fu;  frame->payload_len = *(rd_buf + 1) & 0x7fu;    if (frame->payload_len 126) {  frame->payload_bit_offset = 2;  ws->rd_state = WS_READ_PAYLOAD;  } else if (frame -> payload_len == 126) {  frame->payload_bit_offset = 4;  ws->rd_state = WS_READ_EXTEND_PAYLOAD_2_WORDS;  } else {  frame->payload_bit_offset = 8;  ws->rd_state = WS_READ_EXTEND_PAYLOAD_8_WORDS;  }    ws__reset_buf(ws, 2);  break;  }  case WS_READ_EXTEND_PAYLOAD_2_WORDS: {  #define PAYLOAD_LEN_BITS 2  ret = ws__make_up(ws, PAYLOAD_LEN_BITS);  if (ret != OK) {  return ret;  }  rd_buf = ws->buf;  ws_frame_t * frame = ws->c_frame;    char *payload_len_bytes = (char *)&frame->payload_len;  for (i = 0; i  *(payload_len_bytes + i) = rd_buf[PAYLOAD_LEN_BITS - 1 - i];  }    ws__reset_buf(ws, PAYLOAD_LEN_BITS);  ws->rd_state = WS_READ_PAYLOAD;  #undef PAYLOAD_LEN_BITS  break;  }  case WS_READ_EXTEND_PAYLOAD_8_WORDS: {  #define PAYLOAD_LEN_BITS 8  ret = ws__make_up(ws, PAYLOAD_LEN_BITS);  if (ret != OK) {  return ret;  }    rd_buf = ws->buf;  ws_frame_t * frame = ws->c_frame;  char *payload_len_bytes = (char *)&frame->payload_len;  for (i = 0; i  *(payload_len_bytes + i) = rd_buf[PAYLOAD_LEN_BITS - 1 - i];  }    ws__reset_buf(ws, PAYLOAD_LEN_BITS);  ws->rd_state = WS_READ_PAYLOAD;  #undef PAYLOAD_LEN_BITS  break;  }  case WS_READ_PAYLOAD: {  ws_frame_t * frame = ws->c_frame;  uint64_t payload_len = frame->payload_len;  ret = ws__make_up(ws, payload_len);  if (ret != OK) {  return ret;  }      rd_buf = ws->buf;  frame->payload = malloc(payload_len);  memcpy(frame->payload, rd_buf, payload_len);    ws__reset_buf(ws, payload_len);    if (frame->fin == 1) {  // is control frame  ws__dispatch_msg(ws, frame);  ws__clean_frame(ws);  } else {  ws__append_frame(ws);  }    ws->rd_state = WS_READ_IDLE;    break;  }  }    return ret;  } 

关闭连接

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

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

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

if (op_code == OP_CLOSE) {  int status_code;  char *reason;  char *status_code_buf = (char *)&status_code;  status_code_buf[0] = payload[1];  status_code_buf[1] = payload[0];  reason = payload + 2;    if (ws->state != WS_STATE_CLOSED) {  /**  * should send response to remote server  */    ws_send(ws, NULL, 0, OP_CLOSE | FLAG_FIN);  ws->state = WS_STATE_CLOSED;  }    // close connection  if (ws->close_cb) {  ws->close_cb(ws, status_code, reason);  }  } 

总结

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

websocket binary 数据解析_WebSocket实现原理相关知识点相关推荐

  1. 大数据各框架的原理与知识点

    以下是根据自己的总结和梳理,建议入过门大数据的人看.写的很片面,没有图片,我仅仅想把握学的东西梳理下. Hadoop篇 数据仓库 数据仓库,简写DW,是为企业级所有级别的决策制定过程,提供所有类型数据 ...

  2. 玩日志的你不了解 Filebeat ,就像搞结拜不认识关二爷!深度解析 Filebeat 工作原理,轻松玩转大数据!

    文章目录 深度解析 Filebeat 工作原理,轻松玩转大数据! 什么是 Filebeat Filebeat 工作原理 Filebeat 工作流图 Filebeat 组件图 Filebeat Harv ...

  3. websocket传输数据大小限制_websocket 发送字符串数据上限是多少

    匿名用户 1级 2017-06-07 回答 网上很多关于websocket发送数据大于大于0xFFFF(65535)的处理都是一句话"暂不处理"!!!!!所以特写此文. int W ...

  4. Redis数据持久化机制AOF原理分析一---转

    http://blog.csdn.net/acceptedxukai/article/details/18136903 http://blog.csdn.net/acceptedxukai/artic ...

  5. Python爬虫之旅_(数据解析)_bs4

    前言: 这次来学习数据解析这方面的知识! 0x00:了解数据解析 在ONE那一篇中,就提到过聚焦爬虫(爬取页面中指定内容),大部分的爬虫都是聚焦爬虫,但我们刚开始爬取的肯定都是整个页面的数据,如何定位 ...

  6. 数据解析学习笔记(正则解析、bs4解析、xpath解析)

    聚焦爬虫:爬取页面中指定的页面内容. - 编码流程: - 指定url - 发起请求 - 获取响应数据 - 数据解析 - 持久化存储 数据解析分类: 正则 bs4 xpath(***) 数据解析原理概述 ...

  7. pymavlink 源码剖析(一)之XML文件的数据解析

    文章目录 1 引言 2 pymavlink 的代码自动生成方法 3 XML 文件的数据解析 3.1 XML 文件预处理 3.2 解析 XML 的数据 3.2.1 依据协议版本初始化一些版本特征变量 3 ...

  8. Android中XML数据解析

    转载请注明出处:http://blog.csdn.net/yegongheng/article/details/38296207 XML初步 今天我们来学习另一种非常重要的数据交换格式-XML.XML ...

  9. 基于TencentOS-tiny实现PM2.5传感器(攀藤PMSA003)数据解析思路及实现

    说明:此文章提供了一种基于TencentOS-tiny的串口数据解析思路及实现,感谢戴大神最初写的源码,这种思路同样可以实现AT框架.基于串口的GPS数据解析等等. 1. PM2.5传感器 本文使用的 ...

最新文章

  1. 'avpicture_fill': 被声明为已否决
  2. 人工智能应用实战系列-如何在新闻推荐中使用迁移学习
  3. Michael-Scott非阻塞队列(lock-free)算法的C实现
  4. linux I/O 栈 预习(上)
  5. linux驱动编写(声卡驱动之asoc移植)
  6. java quickfix_QuickFix Java 讲解(三)客户端的搭建与解析
  7. 张亚勤称云计算进入2.0时代,百度首推端云一体化芯片,开放110+AI能力
  8. vagrant box磁盘扩容 亲测有效
  9. bootstrap.min.css和bootstrap.min.js以及bootstrap.bundle.min.js下载,jquery各版本下载
  10. 速魔与图马思特优缺点对比
  11. 这绝对是目前最好用的电脑桌面便签,免费的,墙裂推荐
  12. XSS靶场练习 https://xss.haozi.me
  13. 飞塔防火墙手动升级UTM库
  14. 微软2019 Windows 10更新五月版:10个不可错过的新功能
  15. 中微SC8F5771模拟IIC通信——指令运行速度的探索(附编译软件与烧录软件)
  16. html5 xdwlnjs cn,最近需要调用一个网站的js,但是发现是加密的,有大佬来解密下吗?...
  17. 特征工程-幅度调整-无量纲化(二):归一化和标准化案例,看不懂你打我
  18. 在 F1 Delta Time 大奖赛中找到对速度的热爱
  19. JavaScript进阶必会的手写功能(二)
  20. 埃夫特机器人离线编程软件_埃夫特ER-Factory数字化工厂软件重磅升级

热门文章

  1. Docker 部署不香吗?
  2. Cocos2d-x Win7环境编译apk
  3. 音视频技术开发周刊 | 159
  4. 基于Open WebRTC Toolkit(OWT)的8K全景视频低延时直播系统
  5. RealNetworks CTO:我们追求低复杂度的软解码
  6. Java多线程之Callable、Future和FutureTask
  7. 别琢磨了,七夕礼物都给你想好了
  8. 巧用 Protobuf 反射来优化代码,拒做 PB Boy
  9. WebRtc音视频实时通信--基本术语
  10. 每日两SQL(10),欢迎交流~