百度了很多websocket的心跳实现办法,好像都是发个message?感觉有点怪怪的,所以我就找一下原生websocket的协议内容来看看:
websocket最眼熟的莫过于这张图了:
由于我当时对TCP/IP不了解,第一次接触看的我是一脸懵逼
后来本着解决实际问题的态度去找“轮子”,翻阅了很多资料之后,才发现其实一个常规的websocket连接不需要完全搞懂数据帧的每一个字节是什么东西,但是首先需要关注opcode,opcode表明websocket的数据帧是什么类型的:
struct wsheader_type {
unsigned header_size;
bool fin;
bool mask;
enum opcode_type {
CONTINUATION = 0x0,
TEXT_FRAME = 0x1,
BINARY_FRAME = 0x2,
CLOSE = 8,
PING = 9,
PONG = 0xa,
} opcode;
int N0;
uint64_t N;
uint8_t masking_key[4];
};
- %x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
- %x1:表示这是一个文本帧(frame)
- %x2:表示这是一个二进制帧(frame)
- %x3-7:保留的操作代码,用于后续定义的非控制帧。
- %x8:表示连接断开。
- %x9:表示这是一个ping操作。
- %xA:表示这是一个pong操作。
- %xB-F:保留的操作代码,用于后续定义的控制帧。
其中比较重要的是文本帧、二进制帧、断开、ping和pong。
文本帧:
这玩意就是字符串,没什么好说的,哪怕你们交互使用json载体,也是字符串罢了,只是要自己提出来做解析json和后续的业务逻辑。
二进制帧:
这玩意,好像也没啥好说的,传递文件?图片?用不到就不重要。
断开:
这个东西就是表示关闭通道,一般后面会接close,如果是发送方就表示要主动断开连接,通知一些接收方,如果是接收方,收到这个类型,就表示你被甩了。
ping、pong:
心跳机制,websocket作为长连接,不一定一直都有数据交互。
心跳的存在就是双方彼此感受彼此的存在,让彼此都有安全感的这么一个机制。这个东西一般根据具体项目和实际情况来定频率
我看过很多文章,他们发心跳就是send一个message,message里一个文本:“ping”或者“heartbeat”就表示心跳了。
但其实这本质上只是字符串,必须携带一定数量级的字符串(哪怕只是一个单词)
而规范的心跳应该是在opcode里定义type:ping(9)才对,也就是在sendData里写入opcode的Type,表示这是一个ping消息,而次消息的内容是null的,什么都没有,这才是最轻量级最规范的websocket心跳机制吧。见代码:
std::string empty;
sendData(wsheader_type::PING,empty.size(),empty.begin(),empty.end());
void sendData(wsheader_type::opcode_type type, uint64_t message_size,
Iterator message_begin, Iterator message_end) {
const uint8_t masking_key[4] = { 0x12, 0x34, 0x56, 0x78 };
if (readyState == CLOSING || readyState == CLOSED) {
return;
}
std::vector<uint8_t> header;
header.assign(
2 + (message_size >= 126 ? 2 : 0)
+ (message_size >= 65536 ? 6 : 0) + (useMask ? 4 : 0),
0);
header[0] = 0x80 | type;
if (false) {
} else if (message_size < 126) {
header[1] = (message_size & 0xff) | (useMask ? 0x80 : 0);
if (useMask) {
header[2] = masking_key[0];
header[3] = masking_key[1];
header[4] = masking_key[2];
header[5] = masking_key[3];
}
} else if (message_size < 65536) {
header[1] = 126 | (useMask ? 0x80 : 0);
header[2] = (message_size >> 8) & 0xff;
header[3] = (message_size >> 0) & 0xff;
if (useMask) {
header[4] = masking_key[0];
header[5] = masking_key[1];
header[6] = masking_key[2];
header[7] = masking_key[3];
}
} else {
header[1] = 127 | (useMask ? 0x80 : 0);
header[2] = (message_size >> 56) & 0xff;
header[3] = (message_size >> 48) & 0xff;
header[4] = (message_size >> 40) & 0xff;
header[5] = (message_size >> 32) & 0xff;
header[6] = (message_size >> 24) & 0xff;
header[7] = (message_size >> 16) & 0xff;
header[8] = (message_size >> 8) & 0xff;
header[9] = (message_size >> 0) & 0xff;
if (useMask) {
header[10] = masking_key[0];
header[11] = masking_key[1];
header[12] = masking_key[2];
header[13] = masking_key[3];
}
}
txbuf.insert(txbuf.end(), header.begin(), header.end());
txbuf.insert(txbuf.end(), message_begin, message_end);
if (useMask) {
for (size_t i = 0; i != message_size; ++i) {
*(txbuf.end() - message_size + i) ^= masking_key[i & 0x3];
}
}
}