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

做了这么长时间的服务器,也在CSDN上发过不少帖子,谢谢那些热心的朋友!!!

在做服务器的时候,感觉这个 拆包粘包 有一点难度,现在呢,我就把我的拆包粘包过程分享,这个过程是经过测试的,在4个工作线程下连续发送大量消息,持续发送几分钟,每个包1000bytes左右,都是小包,测试了很多次,没有处理异常和错误崩溃。

下面代码我一段段的上,附上说明, 同时也会附上文件:http://download.csdn.net/detail/oiooooio/7774413

第一段代码,先上数据结构(单数据句柄):

typedef struct sPerHandleData
	{
		friend class::ReceiveManager;
		friend class::SendManager;
		friend class::SendOrRecvMngBase;

	private:
		boost::asio::ip::tcp::socket* _socket; //socket

		char* _buffer; //消息缓冲区

		unsigned __int32 _buffer_size; //缓冲区 【总】 大小

		unsigned __int32 _offset;//消息长度
		 
	public:
		boost::asio::ip::tcp::socket* Socket() const { return _socket; }
		unsigned __int32 Buffer_size() const { return _buffer_size; }
		unsigned __int32 Offset() const { return _offset; }
		char* Buffer() const { return _buffer; }

	public:
		void Offset(unsigned __int32 val) { _offset = val; }
		void Socket(boost::asio::ip::tcp::socket* val) { _socket = val; }

	//还有一些方法这里就不在做介绍了

	}PerHandleData, *PPerHandleData;

以上大家应该一看就懂,下来上包头结构:


	typedef struct sPacket
	{
		unsigned __int8		_packet_type; //不介绍
		unsigned __int8		_device_type; //不介绍
		unsigned __int16	_the_packet_header_size; //包头大小
		unsigned __int32	_the_whole_packet_size;  //整包大小(包头+消息体)
	}Packet, *PPacket;

看完以上结构,我就再附上真正的东西:

void ReceiveManager::AsynReceive( mc_system_msg::PPerHandleData handledata, const boost::system::error_code& error, std::size_t bytes_transferred )
{
	//在拆包的时候,这个变量是有用的,因为拆包需要移动handledata->_buffer指针,使之不断指向下一个包
	//当处理完毕后,需要对handledata->_buffer缓冲区指针还原,以免释放缓冲区时出现问题
	char* tmpPtr = handledata->_buffer;

	//设置总共收到的消息的长度,必须要用+=,而不是=
	handledata->_offset += bytes_transferred;

	if(Receive(handledata, bytes_transferred))
	{
		//投递接收操作
		PostReceive(handledata->Socket());
	}

	//防止handledata->_buffer指针在Receive中被改变,还原指针值,希望指针被正确的释放
	handledata->_buffer = tmpPtr;
	ReleaseMemory(handledata);
}

/// <summary>
/// 此过程主要处理TCP消息的拆包和粘包过程,把处理完整的消息加入到消息队列
/// </summary>
/// <param name="handledata">The handledata.</param>
/// <param name="bytes_transferred">The bytes_transferred.</param>
/// <returns>int.</returns>
bool ReceiveManager::Receive( mc_system_msg::PPerHandleData handledata, std::size_t bytes_transferred )
{
	if(bytes_transferred == 0){
		//DoData(handledata, 0);
		_msg_queue->Add(handledata);
		return false;
	}

	using namespace mc_system_msg;

	do
	{
		PPacket tmpPacket = (PPacket)handledata->_buffer;

		if(handledata->_offset < PACKET_SIZE){
			//消息不全,跳转到[2]继续读取消息
			goto FALG_4;
		}
		else if(tmpPacket->_the_packet_header_size != PACKET_SIZE){
			return false;
		}

		/*[1]*/if(tmpPacket->_the_whole_packet_size == handledata->_offset){
			//DoData(handledata, bytes_transferred);
			_msg_queue->Add(handledata);
			return true;
		}
		/*[2]*/else if(handledata->_offset < tmpPacket->_the_whole_packet_size){
			/*
			 *	代码执行到这里,说明已收到的数据不是一个完整的包,需要再次接收剩下的数据,这里或许有一个疑问,为什么重新获取数据句柄?
			 *	原因在于下面一个判断[3]:
			 *	else if(handledata->_offset > tmpPacket->_the_whole_packet_size && tmpPacket->_the_whole_packet_size != 0)
			 *	这个判断的意思是,收到的数据是几个消息包的集合,需要拆分处理,那么可能正好是3个包的集合,也可能是3个半的消息包的集合,这样,剩下的1/2
			 *	数据包的处理将会走到这个if里,这时,并不能保证handledata->_buffer指针,还指在handledata->_buffer的头部,或许已经移动到了中部,
			 *	尾部等,基于这个原因,所以再次申请一个数据句柄,用于投递接收剩下的数据。
			 *
			 *	看了以上的话,那为什么不定义一个临时指针?这里可以不用重新申请数据句柄吗?
			 *	答:就算定义一个临时指针,那么在【3个半的消息包的集合,这样,剩下的1/2数据包的处理将会走到这个if里】这种情况下,还要把剩下的数据移动到
			 *	handledata->_buffer头部,然后再去投递接收剩下的数据,这样的一个操作过程不比重新申请一个数据句柄简单
			 */
		FALG_4:
			mc_system_msg::PPerHandleData tmp_perHandledata = GetData(handledata->_socket);

			if(tmp_perHandledata == NULL){
				return false;
			}
			else{
				memcpy((tmp_perHandledata->_buffer), (handledata->_buffer), handledata->_offset);
				tmp_perHandledata->_offset = handledata->_offset;
				PostReceive(tmp_perHandledata);
			}

			//这里不需要投递接收消息
			return false;
		}
		/*[3]*/else if(handledata->_offset > tmpPacket->_the_whole_packet_size && tmpPacket->_the_whole_packet_size != 0){
			DoData(handledata, tmpPacket->_the_whole_packet_size);
			//_msg_queue->Add(handledata);

			char* tmpMovePtr	=  handledata->_buffer + tmpPacket->_the_whole_packet_size;
			handledata->_buffer =  tmpMovePtr;
			bytes_transferred	-= tmpPacket->_the_whole_packet_size;
			handledata->_offset -= tmpPacket->_the_whole_packet_size;
			
			continue;
		}
		/*[4]*/else{
			//出现这种情况呢,一般来说,正常的情况下是收到的消息/或者可能是拆包后剩下的消息长度达不到包头的长度,导致消息解析失败
			//跳转到[2]再次去读取消息
			goto FALG_4;
		}
	} while (true);
	
	assert(false);
	throw std::exception("bool ReceiveManager::Receive(...)error");
	return false;
}


代码已经上完了,废话不多...

在拆包粘包过程中,注释写的很清楚,整个项目依赖于boost库,这段代码其他跟boost库也没啥大关系,读者顶多就是看到有几个boost字眼,哪有怎么样...

我在处理完整的消息的时候,是把消息加入到了消息队列(加入的时候,是做了消息的一个副本),加入完毕后,把消息句柄释放。读者应该会看到有DoData函数,用一个子类继承这个函数,就可以直接对消息进行处理。

拆包粘包过程,2个函数,这2个函数是配合使用的,请仔细看...

---

好了,最后呢,欢迎大家交流讨论,有问题和疑问的尽管说,如果我知道,那么言无不尽,如有错误,欢迎指出,谢谢!

打个广告,我的群,欢迎各位朋友加入!

c/c++_发烧友_ 80416665

群名称是 c/c++_发烧友_
  80416665

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值