Asio12-HandlePacketStickingProblemSimply
Asio12-HandlePacketStickingProblemSimply
在Asio8中我们处理过粘包的问题,下面的部分的源码:
void CSession::HandleRead(const boost::system::error_code& error, size_t bytes_transferred, shared_ptr<CSession> _self_shared)
{
if (!error) {
PrintRecvData(_data, bytes_transferred);
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura);
// 已经移动的字节数:在消息体中
int copy_len = 0;
while (bytes_transferred > 0) {
// 如果还未解析头部
if (!_head_parsed) {
// 确保头部信息体+待处理的也不会溢出:收到的比头部小
if (bytes_transferred + _recv_head_node->_cur_len < HEAD_LENGTH) {
memcpy(_recv_head_node->_data + _recv_head_node->_cur_len, _data + copy_len, bytes_transferred);
_recv_head_node->_cur_len += bytes_transferred;
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));
return;
}
// 收到的比头部大,解析头部
int head_remain = HEAD_LENGTH - _recv_head_node->_cur_len;
memcpy(_recv_head_node->_data + _recv_head_node->_cur_len, _data + copy_len, head_remain);
copy_len += head_remain;
bytes_transferred -= head_remain;
short data_len = 0;
memcpy(&data_len, _recv_head_node->_data, HEAD_LENGTH);
// 网络字节序转成本地字节序
data_len = boost::asio::detail::socket_ops::network_to_host_short(data_len);
// data_len = ntohs(data_len);
if (data_len > MAX_LENGTH) {
std::cout << "data_len" << data_len << std::endl;
std::cout << "Data length is too long" << std::endl;
_server->ClearSession(_uuid);
return;
}
_recv_msg_node = make_shared<MsgNode>(data_len);
// 这时候发现消息长度小于规定长度,数据未收全,先存放到接受节点中
if (bytes_transferred < data_len) {
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, bytes_transferred);
_recv_msg_node->_cur_len += bytes_transferred;
::memset(_data, 0, MAX_LENGTH);
// 头部处理完成
_head_parsed = true;
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));
return;
}
// 这时候消息长度大于规定长度,数据收全,直接处理
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, data_len);
_recv_msg_node->_cur_len += data_len;
copy_len += data_len;
bytes_transferred -= data_len;
_recv_msg_node->_data[_recv_msg_node->_total_len] = '\0';
std::cout << "Received message: " << _recv_msg_node->_data << std::endl;
Send(_recv_msg_node->_data, _recv_msg_node->_total_len);
_head_parsed = false;
_recv_head_node->Clear();
if (bytes_transferred <= 0) {
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));
return;
}
continue;
}
std::cout << "--------------------" << std::endl;
// 已经处理完头部,继续上次未处理完的消息
int remain_msg = _recv_msg_node->_total_len - _recv_msg_node->_cur_len;
if (bytes_transferred < remain_msg) {
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, bytes_transferred);
_recv_msg_node->_cur_len += bytes_transferred;
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));
return;
}
memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, remain_msg);
_recv_msg_node->_cur_len += remain_msg;
bytes_transferred -= remain_msg;
copy_len += remain_msg;
_recv_msg_node->_data[_recv_msg_node->_total_len - 1] = '\0';
std::cout << "Received message2: " << _recv_msg_node->_data << std::endl;
Send(_recv_msg_node->_data, _recv_msg_node->_total_len);
_head_parsed = false;
_recv_head_node->Clear();
if (bytes_transferred <= 0) {
::memset(_data, 0, MAX_LENGTH);
_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, _self_shared));
return;
}
continue;
}
} else {
std::cout << "handle read failed, error is " << error.what() << endl;
_server->ClearSession(_uuid);
}
}
很明显,将所有不同的情况全部放置在一个函数处理,既复杂,同时也容易出错。原因在于我们异步读的时候使用的是async_read_some,只要接收到信息,asio框架就会触发回调,而这时候就有可能读取到支离破碎(不整体规整)的内容。
因此我们改进我们的做法,我们异步读取的时候使用的是async_read.区别在于这个读的内部实际会多次调用async_read_some,但是只有读取到指定的字节数的时候,才会触发一次回调。这样既解决了多次回调的效率问题,也解决了粘包的问题。
我们的思路是,开始,我们先读取HEAD_LENGTH的字节,用于填充_recv_head_node,解析消息体的长度,然后我们根据这个长度,async_read去接受消息体的内容。读取到指定的字节数之后,触发回调,填充_recv_msg_node,这时候内部的_data就是消息体的内容了。
接下来是代码:
// 这时候我们改动Start函数,开始先要读取两个字节用于解析头
void CSession::Start()
{
boost::asio::async_read(_socket, boost::asio::buffer(_recv_head_node->_data, HEAD_LENGTH), std::bind(&CSession::HandleHeader, this, std::placeholders::_1, std::placeholders::_2, SharedSelf()));
}
// HandleHead : 解析头部
void CSession::HandleHeader(const boost::system::error_code& error, size_t bytes_transferred, std::shared_ptr<CSession> shared_self)
{
if (error) {
if (error == boost::asio::error::eof) {
std::cout << "Connection closed by peer" << std::endl;
} else {
std::cout << "Error reading header: " << error.message()
<< ", bytes transferred: " << bytes_transferred << std::endl;
}
_server->ClearSession(_uuid);
return;
}
short data_len = 0;
memcpy(&data_len, _recv_head_node->_data, HEAD_LENGTH);
if (data_len > MAX_LENGTH) {
std::cout << "invalid data length" << std::endl;
_server->ClearSession(_uuid);
return;
}
std::cout << "data_len:" << data_len;
_recv_head_node->Clear();
_recv_msg_node = make_shared<MsgNode>(data_len);
boost::asio::async_read(_socket, boost::asio::buffer(_recv_msg_node->_data, data_len), std::bind(&CSession::HandleMsg, this, std::placeholders::_1, std::placeholders::_2, shared_self));
}
// HandleMsg:用于存储消息体
void CSession::HandleMsg(const boost::system::error_code& error, size_t bytes_transferred, std::shared_ptr<CSession> shared_self)
{
if (!error) {
std::cout << "=" << bytes_transferred << std::endl;
PrintRecvData(_recv_msg_node->_data, bytes_transferred);
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
_recv_msg_node->_data[bytes_transferred] = '\0';
std::cout << "Received message: " << _recv_msg_node->_data << std::endl;
for (int i = 0; i < bytes_transferred; i++) {
_recv_msg_node->_data[i] = toupper(_recv_msg_node->_data[i]);
}
Send(_recv_msg_node->_data, _recv_msg_node->_total_len);
_recv_head_node->Clear();
boost::asio::async_read(_socket, boost::asio::buffer(_recv_head_node->_data, HEAD_LENGTH), std::bind(&CSession::HandleHeader, this, std::placeholders::_1, std::placeholders::_2, shared_self));
}
}


浙公网安备 33010602011771号