最近接触了IOCP服务器的编写,对IOCP有了自己的一些认识,希望能对希望正在使用IOCP 的有些建议。我对IOCP了解不多,只是用到了,所以看了一下,还没怎么熟悉。

IOCP的一大优势是高并发率,同时连接1万个用户,CPU的使用率也不会很高,只是内存稍微增大一些了。而且对CPU的利用率很好,线程的量被固定了,所以线程可以更好的处理事情。

CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);//创建完成端口
for(i=0;i<systeminfo.dwNumberOfProcessors; i++){CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);//根据CPU的数量创建线程个数,最好的是2*CPU+2}
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//监听端口
sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);//把新连入的Socket(也就是前面所谓的设备句柄),与目前的完成端口绑定在一起。CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(PER_IO_OPERATION_DATA));lpPerIOData->Buffer.len = MAX_PACKBUFFER_SIZE;lpPerIOData->Buffer.buf = lpPerIOData->szMessage;lpPerIOData->OperationType = RECV_POSTED;WSARecv(sClient,&lpPerIOData->Buffer,1,&lpPerIOData->NumberOfBytesRecvd,&lpPerIOData->Flags, &lpPerIOData->overlap,NULL);

//接收数据

static DWORD WINAPI server::WorkerThread(LPVOID CompletionPortID){HANDLE CompletionPort=(HANDLE)CompletionPortID;DWORD dwBytesTransferred;SOCKET sClient;LPPER_IO_OPERATION_DATA lpPerIOData = NULL;while (TRUE){  GetQueuedCompletionStatus(CompletionPort,&dwBytesTransferred,(PULONG_PTR)&sClient,(LPOVERLAPPED *)&lpPerIOData,INFINITE);if (dwBytesTransferred == 0xFFFFFFFF){return 0;}if (lpPerIOData->OperationType == RECV_POSTED){if (dwBytesTransferred == 0){// Connection was closed by client,将在线状态值设为0}else{//接收数据操作LPNET_PACK pack;pack=(LPNET_PACK)lpPerIOData->szMessage;//将数据重新装填memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));lpPerIOData->Buffer.len = MAX_PACKBUFFER_SIZE;lpPerIOData->Buffer.buf = lpPerIOData->szMessage;lpPerIOData->OperationType = RECV_POSTED;WSARecv(sClient,&lpPerIOData->Buffer,1,&lpPerIOData->NumberOfBytesRecvd,&lpPerIOData->Flags,&lpPerIOData->overlap,NULL);}}}return 0;}

1、静态函数问题,这一块是我个人的认识。因为线程的创建,线程函数是静态的,这样导致所有你处理的数据必须都是静态的,因为静态函数是在编译的时候就初始化了,所以无法处理一些类的非静态函数。这里可以综合考虑多种方式进行处理,动态链表,动态指针,动态存储区,这些需要new,动态申请空间的语句。还可以通过map,list这类可以动态增长的类型。

2、数据包的顺序问题。这个很重要,算是IOCP的一个缺点吧。因为多个线程从完成端口中取数据处理,难免有些线程处理过慢,过快,然后导致包的顺序处理错位,这对于服务器负责转发的时候是一个致命的问题。比如我的服务器负责客户端之间的转发数据,比如聊天数据,这时候,如果包的转发顺序错乱,则会使数据接收有问题。转发文件更称问题。我的解决方案是,服务器设置一个缓冲区,接收整个文件,接收完成后,将数据进行发出去。还有一种方法,不过会降低效率,控制数据发送的速度,比如100毫秒发送一个包,这样可以强制服务器顺序接收,不过服务器的性能如果很不好的话,这个方法还是不行。所以还是缓冲区好一些。

3、粘包的问题。所谓的粘包,我个人理解是,不同的系统在做相关的TCP发送操作的时候,会根据Nagle算法将数据包进行合并操作,所以那个时候自己做的相关操作包就会被封装在一起,这往往是我们不想看到的。这个解决方法很简单,将socket的类型进行设置一下就可以,将socket的类型设置为禁用nagle算法,即设置TCP_NODELAY这个。这个也可以解决2的问题,不需要100毫秒进行发送了。具体解释在http://blog.csdn.net/wangqing008/article/details/17403353

4、继续IOCP中的粘包和断包处理。对于第三点,其实我当时理解不多,或者说理解不够深入。其实禁用掉Nagle算法会有些改观,但是因为禁用掉Nagle算法后,程序运行比之前好很多,我以为已经解决了问题。但是还是自己考虑不周全。如果在公网上测试,特别是一些3G的网络测试,便会发现,粘包断包的问题很严重,具体原因在

http://blog.csdn.net/wangqing008/article/details/17403353后半部分。

我这里贴上解决办法,我这里的解决办法,是我自己想到的,但是我觉得并不是特别好,希望有更好的解决方案的朋友指点一下啊,因为搜了一些这种文章,最后没给自己的应用有多大帮助,最后还是按自己的想法做了。

我自己的想法是这样的,定义一个包头,包头 = 标示符+校验位+包长度,最关键的就这几个信息。在对数据进行粘包处理的时候比较简单,直接按包的结构取数据就好了。有一个比较棘手的是断包(因为TCP是流式协议),所以,最重要的是还如何处理断包,以发送“&&&&abcdefg”为一个包为例,其中“&&&&”为包头。

出现的几种情况这里简单说一下

(1)、断包分为两部分,一部分包括包头,另一部分包括数据部分。比如“&&&&abc”+“defg”,这种是最常见的,比较好解决,首先对包头进行检测,发现该断包的时候,将该断包存储到缓冲区内,作为备份。然后遇到下一个非完整数据(这里可以认为是包尾)与缓冲区内的数据进行拼包,然后对拼起来的包进行CRC校验,因为之前包头内有校验位,所以这里很好解决。这里就可以判断出一个包是否可以拼接起来。

(2)、断包分为两部分,一部分包括包头的一部分,另一部分包括“包头另一份+数据部分”,比如,“&&”+“&&abcdefg”,这种情况下,则直接将包丢弃了,因为无法对包头进行解析,将收到“&&”时直接将该断头包丢弃,然后接下来收到&&abcdefg的时候,与缓冲区内的数据进行拼包,发现拼接不了,这个时候,则是对接受到的缓冲区根据标示符查找下一个包数据,其实这里的意思则是把&&abcdefg丢弃掉。

(3)、断包分为三部分(或者四部分等等),分为三部分的这种情况对我来说,目前都是无法解决的。因为不论怎样,中间都会存在一个断头断尾包,这个是最令人讨厌的,无法完成拼包。按照2的逻辑需要丢弃。

其实我这里和TCP的处理方式差不多,不过TCP做的更好,因为TCP至少保证了不会出现丢包(这个是一个假设,当然他是可能丢包的,那个时候估计TCP的连接也会断开),但是TCP可以保证流的顺序到达,所以他的数据是顺序的。对于服务器来说,为什么要处理断包是因为,服务器是一个多线程的。对IOCP尤其如此,因为每个线程都去取数据,虽然数据到达顺序一定,但是线程处理速度不一定,导致TCP堆栈内的数据进入应用层的时间不一样。这个可以去了解IOCP的流程,我记得有一个讲的非常通俗易懂,有时间转载过来。

还有一种处理方式,我大致说一下解决方案,另外一个是对包的定义变化,额外定义了一个包尾,也就是数据包 = “包头”+“数据”+“包尾”,然后这种方式的话,有一点好处就是拼包的时候会更快,包头和包尾有相关联的信息,比如sequence是一致的,则可以直接去匹配,而不用我上面的说的方式去计算校验值。但是这种方法最终也是要计算校验值的。我个人觉得第一张方案比较好,因为有了包尾的限制,其实对数据的处理往往复杂多变,包尾的标识符,可能也会因为数据中存在相同的数据存在而被误认为是包尾。

这两种方案目前自己都实现了,因为第二种是导师提出的,但是我个人还是倾向第一种,最终还是用了第二种,原因惟“导师”2字。

下面贴出来大致流程,我把代码简化了,可能用不了了。存储断头包的时候,我用的map和list来实现的。对于第二种有包尾的方案,我用的是双层map来实现的。

             //接收数据操作LPNET_PACK pack;pack=(LPNET_PACK)lpPerIOData->szMessage;//起始读取位置int nlen=0;//检查是否是包尾,进行拼包检查if(pack->m_nSeque == PACK_SEQUE){if(!pack->VerifyCRC()){//是一个残包(只有一个有包头的前半部分半包)// 或者断包(一个后半部分的半包+若干其他包)if(dwBytesTransferred>=pack->m_nSize){//判断是否是一个断包goto pinbao;}else{//收到的数据就是一个残包,直接进行断包处理,存储断包}}//运行到这里是一个正常包}else{
pinbao://进行拼包处理//取出来listlist<OFFPACK> slist;map<SOCKET,list<OFFPACK>>::iterator listiter;listiter=socketlist.find(sClient);if(listiter!=socketlist.end()){slist = listiter->second;}else{//正常情况下不会执行,需要进行错误处理}list<OFFPACK>::iterator iter;iter=slist.begin();//申请一个缓冲区空间char * buffer =NULL;                       for(int newi=0;newi<5;newi++){try{buffer = new char[MAX_PACKBUFFER_SIZE];if(buffer != NULL){break;}                 }catch(...){buffer = NULL;printf("申请内存失败\n");}Sleep(20*(newi+1));}while(buffer!=NULL&&iter!=slist.end()){//取出offpack的包OFFPACK offpack;offpack = *(iter);//添加pack = (LPNET_PACK)offpack.buff;int bufferlength = pack->m_nSize;//将数据取出int packlength = (int)pack->m_nSize-offpack.length;if(packlength-1<0 //剩下的部分大于要拼的,也即是拼起来也不够||packlength>MAX_PACKBUFFER_SIZE){//拼起来长度过长 都略过iter++;continue;}//对缓冲区清零memset(buffer,0,MAX_PACKBUFFER_SIZE);//拼接memcpy(buffer,pack,packlength);memcpy(buffer+packlength,lpPerIOData->szMessage,offpack.length);                          pack=(LPNET_PACK)buffer;//计算CRC校验if(pack->VerifyCRC()){//拼包成功DealRecvPack();//清除掉内存delete offpack.buff;//将读取指针移位nlen=offpack.length;//将断头包去掉slist.erase(iter);//退出循环break;}else{//拼包不成功,继续尝试下一个包cout<<"出现问题"<<endl;}iter++;}//清空缓冲区if(buffer != NULL){delete buffer;}//找下一个包头数据,找标识符,packseque校验while(nlen<dwBytesTransferred){pack = (LPNET_PACK)(lpPerIOData->szMessage+nlen);if(pack->m_nSeque == PACK_SEQUE){//&&if(pack->VerifyCRC()){//下一个正常报,跳出while循环break;}else if(pack->m_nSize>(int)dwBytesTransferred-nlen){//剩余一个只有包头的包break;}else{//出现断头断尾包,忽略cout<<"出现忽略的包"<<endl;}}nlen++;cout<<"忽略包"<<endl;}  }while(nlen<(int)dwBytesTransferred){//将数据取出来pack=(LPNET_PACK)(lpPerIOData->szMessage+nlen);//校验CRC,包错误,跳过这个包if(!pack->VerifyCRC()){if((int)dwBytesTransferred-nlen>=8){nlen += pack->m_nSize;//将地址指针存入map(map 存放list)char * buffer = NULL;for(int newi=0;newi<5;newi++){try{buffer = new char[pack->m_nSize];if(buffer != NULL){break;}}catch(...){buffer = NULL;printf("申请内存失败\n");}Sleep(20*(newi+1));}if(buffer == NULL){break;}memcpy(buffer,pack,pack->m_nSize-(nlen-(int)dwBytesTransferred));//加入到断包//取出来listlist<OFFPACK> slist;//添加入断头包list,呵呵OFFPACK offpack;offpack.buff=buffer;offpack.length=nlen-(int)dwBytesTransferred;slist.push_back(offpack);map<SOCKET,list<OFFPACK>>::iterator listiter;listiter=socketlist.find(sClient);if(listiter!=socketlist.end()){listiter->second = slist;//避免下一个包不会出现断头断尾包Sleep(10);}else{//正常情况下不会执行}break;}else{//(nlen-(int)dwBytesTransferred>0)//无头包break;}}else{DealRecvPack(pack,pDbconn,sClient);nlen += pack->m_nSize;}}

这段代码需要放在上一段的31-32行之间。需要的时候进行补充。感觉还是需要根据自己的项目进行自我定制。

IOCP的一些思考(粘包,断包的处理)相关推荐

  1. 详述 Java NIO 以及 Socket 处理粘包和断包方法

    文章目录 Java NIO 通道 缓冲区 代码示例 第一部分 第二部分 选择器 Socket 处理粘包 & 断包问题 第一个问题:对于粘包问题的解决 第二个问题:对于断包问题的解决 示例代码 ...

  2. 即时通讯下数据粘包、断包处理实例(基于CocoaAsyncSocket)

    来源:涂耀辉 www.jianshu.com/p/2e16572c9ddc 如有好文章投稿,请点击 → 这里了解详情 前言 本文旨以实例的方式,使用CocoaAsyncSocket这个框架进行数据封包 ...

  3. mina处理断包和粘包

    为什么80%的码农都做不了架构师?>>>    一.  解码方法 mina中有个内置类CumulativeProtocolDecoder是专门用来处理断包和粘包的.该类的api文档中 ...

  4. 完美解决Python套接字编程时TCP断包与粘包问题

    首先,来看一个代码,使用TCP协议,发送端发送一句话,接收端接收并显示,运行完全正常. 接下来,把客户端代码稍微修改一下,连续发送多个数据, 按照正常的想法,在服务端输出的信息应该是分为多行的,这样才 ...

  5. Mina 粘包、断包、半包解决

    在使用mina框架时,很可能会出现粘包.断包和半包的情况,以下针对这些情况进行解决. 一.CumulativeProtocolDecoder详解 A. 你的doDecode()方法返回true 时,C ...

  6. TCP粘包、断包处理

    在TCP传输中,当我们使用长连接传输数据时,由于传输频率快.缓冲区不足等问题,经常会产生断包.粘包的问题,本文将基于java讲述TCP协议中这两个问题的解决. 首先,简单介绍一下粘包.断包问题产生的原 ...

  7. 三、Netty的粘包半包问题解决

    一.定义 TCP 传输中,客户端发送数据,实际是把数据写入到了 TCP 的缓存中,粘包和半包也就会在此时产生.客户端给服务端发送了两条消息ABC和DEF,服务端这边的接收会有多少种情况呢?有可能是一次 ...

  8. TCP 粘包半包 netty 编解码 三者关系

    1 何为粘包 / 半包? 对方一次性接收了多条消息这种现象,我们就称之为 粘包现象. 对方多次接收了不完整消息这种现象,我们就称之为 半包现象. 粘包的原因: 发送方发送的消息 < 缓冲区大小 ...

  9. 网络粘包解包问题杂谈

    1.如何解决粘包问题? 在设计网络协议时,可能会存在粘包.丢包或者包乱序问题,但TCP协议时可靠性协议,大多数情况不存在丢包和乱序问题,但UDP协议如果不能接受少量丢包,就必须自己设计有序和可靠性传输 ...

最新文章

  1. 投影幕布尺寸计算器_如果把投影幕布安装在家里,有哪些安装位置和方式?
  2. 新东方王强老师的感悟
  3. Oracle数据库,当DML操作时执行触发器记录日志
  4. QueryWrapper 一些常用操作 or like in 大于 小于 操作
  5. 鱼骨图分析法实际案例_8D根本原因分析——5WHY与鱼骨图培训课件(PPT64完整详细)...
  6. adb小天才_ADB工具包2020年最新版下载-支持解锁新机BL调试ROOT等各种操作
  7. Boost Graph Library
  8. 最新STM32G0系列选型表1
  9. iphone8位置无法连接服务器,iphone8无法连接到app store怎么办?苹果iphone8连接不到app store解决方法...
  10. 敏捷史诗(Epics)的定义、示例和模板
  11. 免单拼团商城小程序开发
  12. 信息与电脑杂志信息与电脑杂志社信息与电脑编辑部2022年第8期目录
  13. 绎维软件F-One获得B轮融资,华创资本领投,齐银基金跟投...
  14. 装完黑苹果怎么装windows_黑苹果安装教程,小编教你黑苹果怎么安装
  15. [易飞]客供料处理方案
  16. 黑客是如何监控你的电脑的呢?今天来了解C++远程监控系统!
  17. SpringBoot中starter场景启动器
  18. vijos1579——宿命的PSS
  19. mongodb模糊查询
  20. 似然函数的意义与极大似然估计

热门文章

  1. windows作为产品的一些设置和开发(经验之谈)
  2. 使用pymysql报错RuntimeError ‘cryptography‘ package is required for sha256_password or caching_sha2_passw
  3. Android高级控件----AdapterView与Adapter详解
  4. 关于程序猿 59 条搞笑但却真实无比的语录
  5. python创建数字列表_Python 生成一个从0到n个数字的列表4种方法小结
  6. 主管给实习生新人的一封邮件,字字珠玑,用心良苦
  7. Leetcode_35_Search Insert Position
  8. Java—String类的intern方法的学习
  9. NodeJS之搭建Web服务器
  10. opencv 手选roi区域_如何用opencv实现感兴趣区域ROI的选取