c++服务器 拆包粘包 过程(1)
来源:互联网 发布:邮件群发软件效果 编辑:程序博客网 时间:2024/06/05 01:02
做了这么长时间的服务器,也在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; //socketchar* _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 0 0
- c++服务器 拆包粘包 过程(1)
- c++服务器 拆包粘包 过程(2)
- 记录创建流媒体服务器过程(1)
- Linux(服务器)安装过程
- VSTS 安装过程(双服务器安装)
- 过程抽象----函数(C++)
- Udp分包过程(C#)
- 通信协议解包过程(C/C++)
- 特洛伊木马服务器源代码(C#)
- 特洛伊木马服务器源代码(C#)
- 服务器、客户端实例(C#)
- C语言:复杂表达式的执行过程(课时1)
- 用C一步步开发web服务器(1)
- 网吧服务器从“黑”到“白”的艰难过程(1)
- 【公开源代码】详述多用户博客程序开发过程-step by step(1)-【配置服务器】
- 阿里云服务器部署otter实现数据双A同步过程记录(1)
- 《UNIX网络编程》多线程TCP C/S服务器正常启动和正常结束过程剖析
- Linux服务器启动过程
- 让你的Git水平更上一层楼的10个小贴士
- 系统设计之代码重构
- OpenGL 红绿3D
- oc知识点总结
- 什么是“恶意代码”——————【Badboy】
- c++服务器 拆包粘包 过程(1)
- JSP中如何使浏览器点击后退按钮再点击前进按钮时,使网页失效
- UI (一) ios概述 UI概述
- http://poj.org/problem?id=1094
- GetSystemMenu AppendMenu 和 deleteMenu .
- 配置Tomcat的连接池和数据源(以oracle数据库为例)
- 强引用、软引用、弱引用、虚引用
- JQUERY获取当前页面的URL信息
- 对于目标文件系统 文件 过大