百度了很多websocket的心跳实现办法,好像都是发个message?感觉有点怪怪的,所以我就找一下原生websocket的协议内容来看看:

 

websocket最眼熟的莫过于这张图了:

python tcp 心跳包 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];
			}
		}
	}