众所周知,为了应对人们日益强烈的
RealTime 交互需求
,模拟 TCP socket 全双工 的特性,产生了 websocket 这一双向通信协议
,它借用 HTTP协议的101 switch protocal
协议转换,所以在一定程度跟HTTP协议传输有很大程度的相似,但是请放心,ws是一个独立的协议。本次我们从 websocket client 的角度实现协议功能
以及对 ws协议进行解析
。
环境说明:
- 完整代码: websocket_client.c
- RFC6455协议
- gcc version 4.4.7 x86_64-redhat-linux
- websocket服务端:Workerman 3.5.3 (
websocket://0.0.0.0:2346
)
websocket client 功能流程
首先我们先对 websocket 客户端的功能实现的流程进行一个将要的说明,后面结合协议进行解析的时候可能仅仅解释一些比较重要的功能模块(有网络编程功底的同学可以先将例子clone下来查看,简单例子,见谅!^-^)
- 配置地址以及服务端口
往服务端发送 握手信息的header
验证客户端返回的握手信息(验证成功,完成握手)
掩码加密 客户端实体数据信息 “aaaaa”
掩码解密 服务端返回的数据信息
- close 连接
协议概况
ws是一个基于 TCP 的独立的协议,和HTTP的关系就是握手请求可以作为一个升级请求(Upgrade)经由HTTP服务器进行解释。协议主体主要分为两部分 握手
,数据帧
。
握手
私以为握手部分就是为了兼容现有的 基于HTTP的服务端组件(web服务器软件) 或者 代理服务器软件类型的中间件等。这样一个端口既可以接收普通的web请求,又可以对 websocket 进行横向兼容。所以 websocket client 的握手是一个 HTTP升级版请求(HTTP Upgrade request),ws协议规定,握手中的header字段并没有顺序要求。
Request header
GET
请求标识符 Request-URI,用于识别 websocket 链接到不同的服务终结点。Connection
告诉服务端对协议进行升级,具体升级内容取决于 Upgrade部分Host
服务器地址以及service端口Sec-WebSocket-Key
为了保证握手一致性,由客户端生成随机字符串并base64编码,发送给服务端,后面在服务端返回内容方面会针对这个进行讲解Sec-WebSocket-Version
ws协议版本,常用13Upgrade
: 升级至*协议
Response header
状态码101
代表协议升级成功后的状态码Connection
和Upgrade
内容代表协议成功升级为wsSec-WebSocket-Version
和Server
内容代表 ws协议版本 以及 服务端服务软件信息Sec-WebSocket-Accept
重点在此,证明服务端接受了客户端的请求,然后客户端对accept值进行验证,任何 为空或者不符合验证规则的都视为服务器拒绝了请求。服务端生成 Sec-WebSocket-Accept 规则
:服务端将客户端传递的key进行去除首尾空白,然后和一段固定的GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)进行连接,连接后的结果使用 SHA-1(160数位)进行哈希操作,对哈希后的字符串进行base64编码,即为 Accept 内容。客户端验证规则
仿上述规则对 Sec-WebSocket-Key 进行加密,跟服务端返回 Sec-WebSocket-Accept 值进行对比即可。
上述只是基础的 header 字段,当然还会根据具体需求添加一些 cookie 或者 其他衍生字段。
握手过程的实现代码片段如下(请着重注意代码注释,注意事项过程
)(代码中使用的函数均会存在于完整代码中
)
“数据帧”
“数据帧”
之所以加引号,是因为它包含 控制帧 + 数据帧 两个基本组成部分,基础帧协议如下【或者点此查看高清协议图片】
常规套路上我们先解释一下帧协议字段内容,为了更加详细一点儿,我们提前打印demo(发送字符串为 aaaaa)中的控制帧:
FIN
:1 bit表示这是不是消息的最后一帧。第一帧也有可能是最后一帧。
%x0
: 还有后续帧%x1
:最后一帧RSV1
、RSV2
、RSV3
:1 bit扩展字段,除非一个扩展经过协商赋予了非零值的某种含义,否则必须为0
opcode
:4 bit解释 payload data 的类型,如果收到识别不了的opcode,直接断开。分类值如下:
%x0
:连续的帧%x1
:text帧%x2
:binary帧%x3 - 7
:为非控制帧而预留的%x8
:关闭握手帧%x9
:ping帧%xA
:pong帧%xB - F
:为非控制帧而预留的
|
|
MASK
:1 bit标识 Payload data 是否经过掩码处理,如果是 1,Masking-key域的数据即为掩码密钥,用于解码Payload data。协议规定客户端数据需要进行掩码处理,所以此位为1
Payload len
:7 bit | 7+16 bit | 7+64 bit表示了 “有效负荷数据 Payload data”,
以字节为单位
:- 如果是 0~125,那么就直接表示了 payload 长度
- 如果是 126,那么接下来的两个字节表示的 16位无符号整型数的值就是 payload 长度
- 如果是 127,那么接下来的八个字节表示的 64位无符号整型数的值就是 payload 长度
|
|
Masking-key
:0 | 4 bytes掩码密钥,所有从客户端发送到服务端的帧都包含一个 32bits 的掩码(如果mask被设置成1),否则为0。一旦掩码被设置,所有接收到的 payload data 都必须与该值以一种算法做异或运算来获取真实值。
|
|
Payload data
:(x+y) bytes它是
Extension data
和Application data
数据的总和,但是一般扩展数据为空。Extension data
:x bytes除非扩展被定义,否则就是0
Application data
:y bytes占据
Extension data
后面的所有空间
|
|
数据帧数据的生成 实现代码片段如下(请着重注意代码注释,注意事项过程
)(代码中使用的函数均会存在于完整代码中
)
解密数据帧
|
|
|
|
|
|
|
|
服务端返回的数据帧的解析 实现代码片段如下(请着重注意代码注释,注意事项过程
)(代码中使用的函数均会存在于完整代码中
)
本文作者: wettper
本文链接: http://www.web-lovers.com/c-websocket-client.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!