c++服务器 拆包粘包 过程(2)

本文分享了一种TCP拆粘包处理的优化方法,通过智能指针管理资源并简化包头设计,有效解决了网络通信中常见的拆包和粘包问题。文章详细介绍了优化后的代码实现细节,包括关键函数的逻辑流程及包结构设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这篇短文之前,有一篇:

 

c++服务器 拆包粘包 过程(1)


这是我第一篇发的关于TCP socket 服务器拆粘包的代码,这个代码有些繁琐。下面我再传我修改后的第二版的这个例子,这个例子也是经过了几万次的消息收发的运行以及调试后的代码,尽管不是最好,也希望能帮助一些朋友。等我修改完毕第三次的时候,会再次更新。

	void TCPReceiveManager::AsynReceive(__tcp_phd_weak_ptr phd,
		Common::__cr_boost_ec error, std::size_t bytes_transferred)
	{
		if (_func_add == NULL){
			/* 上层没有设置消息接收接口,那么这个消息无法传递,所以释放 */
			Release(phd);
			return;
		}

		__tcp_phd *tcpphd = static_cast<__tcp_phd*>(phd.lock().get());
		Common::__tcp_skt* skt = tcpphd->_socket;

		// 设置总共收到的消息的长度,必须要用+=,而不是=
		tcpphd->_offset += bytes_transferred;
		if (Receive(tcpphd->_buffer, bytes_transferred, phd)){
			// 投递接收操作
			PostReceive(skt);
			return;
		}

		// 这里不需要再次释放,因为不bytes_transferred是不是0,收到的这条消息
		// 都会被加入到消息队列,通知上层,这里释放的话,那么上层在释放的时候会造
		// 成多次释放以至于崩溃的结果
		// Release(phd);
	}

	/*
	 *	t:表明还要投递接收包
	 *	f:表明不用再次投递接收包,socket已经断开连接
	 */
	bool TCPReceiveManager::Receive(char* buffer, 
		std::size_t bytes_transferred, Common::__tcp_phd_weak_ptr phd)
	{
		if (bytes_transferred == 0){
			_func_add(phd);
			// NETLOG(__type_ec::ec_general, "user offline.", LOG_TAG);
			return false;
		}

		
		typedef MessageInterface::TCPMessageHeader* header;
#if _IOOOOIO_SVR_CORE_LEVEL_2_DEBUG
		int loopCnt = 100;
#endif

		do
		{
#if _IOOOOIO_SVR_CORE_LEVEL_2_DEBUG
			--loopCnt;

			if (!loopCnt){
				break;
			}
#endif
			header tmpPacket = (header)buffer;
			__tcp_phd *tcpphd = static_cast<__tcp_phd*>(phd.lock().get());


			if (tmpPacket->_packet_size > tcpphd->_buffer_size){
				NETLOG(__type_ec::ec_error, 
					"the header size is wrong when recv a packet.close the socket.", LOG_TAG);
				/* 通知上层,这个客户端要被关闭! */
				tcpphd->_offset = 0;
				_func_add(phd);
				return false;
			}

			if (tcpphd->_offset < HEADSIZE){
				/* 消息不全,跳转到[2]继续读取消息 */
				goto FALG_2;
			}

			/*[1]*/if (tmpPacket->_packet_size == tcpphd->_offset){
				/* 消息包完整 */
				_func_add(phd);
				return true;
			}
			/*[2]*/else if (tcpphd->_offset < tmpPacket->_packet_size){
				/*
				 *	收到的消息不全,投递消息句柄继续接收
				 */
			FALG_2:
				PostReceive(phd);
				/* 这里不需要投递接收消息 */
				return false;
			}
			/*[3]*/else if (tcpphd->_offset > tmpPacket->_packet_size && tmpPacket->_packet_size != 0){
				__tcp_phd_weak_ptr tmpPtr = Get();
				__tcp_phd *tmptcpphd = static_cast<__tcp_phd*>(tmpPtr.lock().get());
				if (!tmptcpphd){
					/* 如果分配内存失败,关闭客户端连接 */
					NETLOG(__type_ec::ec_error, "get a chunk failed.", LOG_TAG);
					tcpphd->_offset = 0;
					_func_add(phd);
					return false;
				}
				/* 创建一个新的数据包,这个数据包肯定是完整的数据包 */
				tmptcpphd->_socket = tcpphd->_socket;
				tmptcpphd->_offset = tmpPacket->_packet_size;
				/* 创建一个新的完整的数据包 */
				memcpy(tmptcpphd->_buffer, buffer, bytes_transferred);

				/* 剩余的数据部分,把buffer指针指向剩余的数据的部分的开头 */
				/* 以tmptcpphd->_buffer作为交换区域,把buffer中尾部的数据移动到开头 */
				memcpy(buffer, tmptcpphd->_buffer + tmpPacket->_packet_size, bytes_transferred - tmpPacket->_packet_size);
				bytes_transferred -= tmptcpphd->_offset;
				tcpphd->_offset -= tmptcpphd->_offset;
				/* 消息处理 */
				_func_add(tmpPtr);
				/* 继续循环解析消息 */
				continue;
			}
			/*[4]*/else{
				/*
				 *	出现这种情况呢,一般来说,正常的情况下是收到的消息/或者可能是拆包后剩下的消息长度达
				 *	不到包头的长度,导致消息解析失败.跳转到[2]再次去读取消息
				 */
				goto FALG_2;
			}
		} while (true);

#if _IOOOOIO_SVR_CORE_LEVEL_2_DEBUG
		if (!loopCnt){
			NETLOG(__type_ec::ec_error, 
				"the loop performs 100 times that it unpacks and splices tcp message packet."
				"Generally, it is impossible because no packets so much big there are."
				"it maybe occur a error to cause infinite loop.", LOG_TAG);
			assert(false);
		}
#endif

		NETLOG(__type_ec::ec_error, 
			"it should return before this line if anything correctly.", LOG_TAG);
		assert(false);
		return false;
	}

上面的代码依然是2个函数,不同于第一个版本,这个版本的(数据句柄)用的是智能指针,并且数据封装的包头也是简化了,第一版的太烦。

针对这一期的代码:

使用的智能指针,因为一开始使用smt-ptr,所以呢,用的不太熟而且这个smt-ptr是后来加上去的,所以自我感觉这个使用的都是乱七八糟的。

而且在c++11中,对于复制和移动都有优化,我这里还没有使用到。

-----

下面对于包的数据结构说明下:

本次的包的数据结构是:

	/*
	 *	TCP包头定义
	 *	这个包头只含一个int成员,用来标志整个包的大小,负责TCP包的拆包/粘包过程。
	 *	后面定义的所有的TCP消息包,都必须继承此对象
	 *	4bytes
	 */
	struct TCPMessageHeader
	{
		std::size_t	_packet_size : 32/*bit*/;
	};

#define HEADSIZE /*sizeof(TCPMessageHeader)*/4

就像上面的代码,包头的前4个字节定义了包的总大小,后面的数据为内容。包头是可以强转为int*的。这样可以直观的判断。

------

ok!就这样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值