当前位置: 首页 > news >正文

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));}
}

image-20250922171235517

http://www.jsqmd.com/news/135661/

相关文章:

  • 第三章 SQL Server函数
  • Chap22-DistributedLock_MultiServer
  • Asio09-SendQueueAndEndian
  • 第四章 SQL Server备份和还原
  • 5分钟使用modelengine打造儿童数字人,小白也能快速上手以低代码的方式快速搭建智能应用,从而大幅降低开发难度
  • 基于springboot在线课程管理系统的设计与实现毕业论文+PPT(附源代码+演示视频)
  • LLM - 用 SpecKit 和 AICode 改造遗留系统 完整实践指南
  • Elasticsearch数据膨胀?调优部署全攻略
  • 【计算机毕业设计案例】基于Java的停车场管理系统、预订车位系统、停车缴费(程序+文档+讲解+定制)
  • Router_路由的基本使用
  • 计算机Java毕设实战-基于Java的停车场管理系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 快速检查Ubuntu进程是否运行的3种方法
  • Java毕设项目:基于springboot的公司财务预算管理系统(源码+文档,讲解、调试运行,定制等)
  • 贝叶斯优化Transformer-LSTM的模型结构图
  • 番茄小说下载器 2025.12.21 | 现代化、高效的番茄小说下载器,支持批量下载和多种格式导出
  • 计算机Java毕设实战-基于SpringBoot的植物知识管理与分享平台的设计与实现家庭园艺种植分享平台设计与实现【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • Router_路由重定向和其他小细节问题
  • Java毕设项目:基于SpringBoot+Vue技术的医疗器械管理系统设计与实现(源码+文档,讲解、调试运行,定制等)
  • Redis 数据结构底层与 Hash 优于 JSON 的工程实践
  • STM32平衡车工具-匿名助手+虚拟串口如何使用。
  • 编码器测速思路,以及如何进行测速,速度调整
  • 从零开始学C++:STL简介
  • 【计算机毕业设计案例】基于springboot+vue技术的二手车交易管理系统的设计与实现(程序+文档+讲解+定制)
  • 别再“+”到天亮!String.format 一键拯救Java字符串拼接,高可读+可维护神操作
  • Router_编程式路由
  • 重装数次arch_linux有感
  • Java毕设选题推荐:基于springboot+vue技术的二手车交易管理系统的设计与实现汽车管理汽车品牌管理,公告类型管理,论坛管理【附源码、mysql、文档、调试+代码讲解+全bao等】
  • Java五种文件拷贝方式
  • 2-[(2-叠氮乙酰基)氨基]-2-脱氧-D-吡喃甘露糖—糖生物学与代谢标记的关键化学探针 1971934-97-0
  • 萤石开放平台 国标设备接入 |常见问题