TCP数据解包处理

本文介绍了TCP数据传输中可能出现的粘包和断包问题,以及如何通过预先设定的包头长度来解析和处理这些问题。通过定义报文头结构体,创建了一个模板类`recv_msg`来实现解包。类中包含了获取报文头、解析报文头、获取报文体等方法。在实际应用中,先读取报文头,然后根据报文头长度读取报文体,确保了数据的完整性和准确性。最后,文章提到了其他处理方式,并指出可以使用`boost::asio`库进行异步读取。
摘要由CSDN通过智能技术生成

TCP数据解包处理

  1. 组包解包
    组包:根据协议格式化将数据序列化的过程。
    解包:根据协议格式化将数据反序列化的过程。

    我们假定要处理的包协议格式是由包头、包体组成。(暂不考虑包尾);如下:
    		data包含:| head | body |
    
  2. 粘包断包
    在TCP传输数据过程中,由于传输频率快、缓冲区不足等问题会导致粘包、断包的问题出现;通俗来说粘包是多个数据协议包粘在了一起,缓存上是尾首相连的;而断包则是一个数据协议包被分成了若干份。

    粘包和断包并不独立存在,在处理的过程中往往是你中有我,我中有你的存在;你可能遇到: 粘包+断包、断包+粘包、一直粘、一直断、还有即不粘也不断的情况。

  3. 处理粘包断包
    以下处理根据报文头字段length去解析包体数据;来解决粘包和断包问题。

报文头定义

/// 报文头定义
typedef struct _MsgHeader
{
	......
	uint32_t  length;	/* 报文体长度 */
} MsgHeader;

类封装处理:

#pragma once
#define INIT_BODYLEN 1024 * 20

template<class T>
class recv_msg
{
public:
	recv_msg()
		: buffer_(nullptr)
		, body_len_(0)
	{
		memset(&head, 0, sizeof(T));
		/// 预先开辟空间,防止频繁的new|delete的存在
		buffer_ = new (std::nothrow) char[sizeof(head) + INIT_BODYLEN];
		/// 包体最大长度赋值
		max_bodysize_ = INIT_BODYLEN;
	}

	~recv_msg() 
	{
		if (buffer_)
		{
			delete[] buffer_;
			buffer_ = nullptr;
		}		
		max_bodysize_ = 0;
	}

	char* get_buffer()
	{
		return buffer_;
	}
	int get_headlen()
	{
		if (!buffer_)
			return 0;

		return sizeof(T);
	}
	/// 解析报文头
	bool decode_header()
	{
		T head;
		memcpy(&head, get_buffer(), sizeof(head));
		head.length -= sizeof(head);
		if (head.length < 0)
			return false;
		else if (head.length > max_bodysize_)
		{
			/// 若接收数据大于最大预设的20KB,则释放重新开辟空间。
			if (buffer_)
			{
				delete[] buffer_;
				buffer_ = nullptr;
				max_bodysize_ = 0;
			}
			/// 重新开启
			buffer_ = new char[sizeof(head) + head.length];
			if (buffer_) {
				memcpy(buffer_, (char*)&head, sizeof(head));
				max_bodysize_ = head.length; /// 记录最大包体长度
			}
			else
				return false;
		}
		body_len_ = head.length; /// 记录本次报文包体长度

		return true;
	}

	char* get_body()
	{
		if (!buffer_)
			return nullptr;

		return buffer_ + sizeof(T);
	}

	int get_bodylen()
	{
		if (!buffer_)
			return 0;

		return body_len_;
	}
private:
	T	head;			///	数据包头
	char* buffer_;		/// 接收数据存放在buffer
	int body_len_;		/// 报文体长度
	int max_bodysize_;  /// 最大报文体长度
};

代码使用部分

class Client
{
/// ......
private:
	/// 读取报文头部数据
	void do_read_header()
	{
		boost::asio::async_read(socket_, boost::asio::buffer(recv_msg_.get_buffer(), recv_msg_.get_headlen()),
			[this](boost::system::error_code ec, std::size_t /*length*/)
		{
			if (!ec && recv_msg_.decode_header()) 
			{
				do_read_body();
			}
			else
			{
				std::cout<<"do_read_header is fail."<<std::endl;
				async_reconnect(); /// 重连处理
			}
		});
	
	}
	/// 读取报文体数据
	void Client::do_read_body()
	{	
		boost::asio::async_read(socket_, boost::asio::buffer(recv_msg_.get_body(), recv_msg_.get_bodylen()),
			[this](boost::system::error_code ec, std::size_t /*length*/)
		{
			if (!ec )
			{
			 	/// proccess_package 用于解析完整的包。。
				proccess_package();
				do_read_header();
			}
			else
			{
				std::cout << "do_read_body is fail " << std::endl;
				async_reconnect();
			}
		});
	}

private:
	recv_msg<MsgHeader> recv_msg_;
	/// .... 其他变量定义
}

以上具体流程是1、先获取报文头,2、再根据报文头内容获取报文体 获取完整报文后,最后对整个报文通过具体协议在函数proccess_package做解析处理。

当然,还有其他方法,比如:也可以通过boost::asio::async_read_some获取对端数据到本地buffer,然后再对buffer根据报文协议进行粘包断包处理,处理后的完整报文在函数proccess_package做解析,这里就不做细究。

若有错误烦请指出,有地方不理解欢迎讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值