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
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 刮痧后洗了澡怎么办 刮痧后喝了啤酒怎么办 艾灸后吃水果了怎么办 刮痧后能洗澡了怎么办 膝盖筋扭了肿了怎么办 骑行之后膝盖疼怎么办 膝盖软骨磨没了怎么办 腿上膝盖长骨刺怎么办 打羽毛球后膝盖疼怎么办 打完羽毛球膝盖疼怎么办 骑动感单车后膝盖痛怎么办 踩完动感单车右膝盖痛怎么办 跑步机走步损伤膝盖怎么办 健身把膝盖伤了怎么办 膝盖后面的腿窝筋酸疼怎么办 跳绳把膝盖伤了怎么办 鼻塞嗓子肿呼吸困难怎么办 鼻子内上火肿了怎么办 鼻子又红又肿怎么办 在公司班组人员不听话怎么办 熬了一夜睡不着怎么办 有人看我脚走路怎么办 穿牛仔裤裆部有三角怎么办 我的电脑打cffps不稳定怎么办 生米煮成了熟饭该怎么办 做完下蹲腿疼该怎么办 下蹲腿的筋酸痛怎么办 躺平了 腿酸痛怎么办 500上下蹲后腿痛怎么办 蹲完马步后站不起来怎么办? 车在水里熄火了怎么办 脚磕到了很疼怎么办 破腹产4年了腰疼怎么办 蹲起之后腿疼怎么办 深蹲起跳伤腰部怎么办 蹲起膝盖有响声怎么办 腰突然不能弯了怎么办 蚂蚱吃了会过敏怎么办 孕妇能吃蚂蚱菜怎么办 孕妇吃了蚂蚁菜怎么办 白果很硬的时候怎么办