TCP数据解包处理
-
组包解包:
组包:根据协议格式化将数据序列化的过程。
解包:根据协议格式化将数据反序列化的过程。我们假定要处理的包协议格式是由包头、包体组成。(暂不考虑包尾);如下: data包含:| head | body |
-
粘包断包
在TCP传输数据过程中,由于传输频率快、缓冲区不足等问题会导致粘包、断包的问题出现;通俗来说粘包是多个数据协议包粘在了一起,缓存上是尾首相连的;而断包则是一个数据协议包被分成了若干份。粘包和断包并不独立存在,在处理的过程中往往是你中有我,我中有你的存在;你可能遇到: 粘包+断包、断包+粘包、一直粘、一直断、还有即不粘也不断的情况。
-
处理粘包断包
以下处理根据报文头字段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做解析,这里就不做细究。
若有错误烦请指出,有地方不理解欢迎讨论。