https://blog.csdn.net/lijia626482312/article/details/40858061

一个人从接到项目到昨天终于完成,用了差不多4个月,其中各种心酸和眼泪。我的项目是通过网络从客户端上采集数据,通讯原则是客户端有数据要上传,如果网络允许就连接服务器,首先客户端发送一个消息判断服务器是不是处于忙碌和资源空闲状态,然后发送文件等等。可以说是一个基于C/S模式的多线程socket程序。

我刚开始那到这个项目,我们经理把项目给我一看,你一个人可以做的出来吗,我一看,额。。。。。很简单吗!must,领导要求,必须做出来啊,从此在这个项目陷进去了。。。。。。
    由于以前没有做过网络方面的程序,我的开始是从传统模型开始的,就是accept一个连接就开启一个线程,这也是我那到这个项目,觉得简单的地方,很快我就实现了这个问题,我通过连接几台客户端,简单的测试一下,OK,可以正常的采集,基本的功能都可以实现。那好,我把项目一个技术经理,经理一看,小李,你效率挺快的吗,OK,你做个压力测试一下。然后我立马写了一个测试程序,写完后,我自信满满的拿给测试部门测试,我碰到了这个项目的第一个方向问题。测试部门的主管跟我说,客户端连接服务器,30、40台客户端,可以正常运行,客户端数量再往上的话,就比较容易出现乱码(也就是乱序),这个程序拿给用户不行,这是砸我们公司的招牌,我当时就懵了,怎么可能,代码没有问题啊!我回去一查,可能是粘包,我的处理方案是精简代码结构!写好后,我自己模拟用100台,测试了一晚上,没有问题,应该没有问题了吧,然后我又自信满满的拿到测试部门,我又懵了,(⊙o⊙)…,又不行,上面的问题,还是出现了,我想这是人品问题啊,怎么可能,然后在这个问题上,我纠结了差不多快两个月,代码再怎么优化都有问题,我都准备彻底放弃了!!!

一次偶然的机会在网上看到windows操作系统有六种模型:select模型,WSAAsyncSelect异步模型,WSAEventSelect事件模型,重叠I/O事件模型,重叠I/O完成例程,IOCP完成端口。我一个一个模型差不多都用了一遍,好像都不好用,前面4种,有windows平台数量限制(64台客户端),我一看这不行,那就用IOCP吧!
    
    iocp步骤是:
    1、创建工作在线程;
       SYSTEM_INFO sysinfo;
       GetSysteminfo(&sysinfo);
       int threadcount = sysinfo.sysinfo.dwNumberOfProcessors*2;
       for (DWORD i = 0;i<sysinfo.dwNumberOfProcessors*2;i++)
       {
           HANDLE m_pWorkThread;
           m_pWorkThread = CreateThread(NULL,0,ServerWorkerThread,m_ComletionPort,0,NULL);
           CloseHandle(m_pWorkThread);
       }
    2、创建socket,不同传统模型,m_listenSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    3、绑定socket,CreateIoCompletionPort((HANDLE)m_listenSock,m_ComletionPort,0,0);
    4、传统模式的bind,listen;
    5、accept(acceptex更高效),将iocp绑定客户端socket
       SOCKADDR_IN saRemote;
       int RemoteLen=sizeof(SOCKADDR_IN);
       SOCKET Accept = INVALID_SOCKET;
       Accept = WSAAccept(pDlg->m_listenSock,(SOCKADDR*)&saRemote, &RemoteLen,NULL,0));
       PerHandleData[index].Socket = Accept;
       memcpy(&PerHandleData[index].ClientAddr,&saRemote,RemoteLen);
       HANDLE m_retcompletion = CreateIoCompletionPort((HANDLE)PerHandleData[index].Socket,m_ComletionPort,(DWORD)&PerHandleData[index],0);
    6、绑定iocp成功后,投递接收请求WSARecv(PerHandleData[index].Socket, &(PerIoData[index].DataBuf), 1, &RecvBytes, &Flags,&(PerIoData[index].Overlapped), NULL);
    
    以上就是iocp的大概步骤,我使用上面的模型创建好iocp模型后,发现确实好用不少,3Mb的短连接数据传输,接收速度比传统模型几乎快了十几秒。
    
    然后我又进行了模拟100台客户端压力测试,发现运行了10几分钟系统出现崩溃,错误提示是:Debug Assertion Failed!File:Dbgheap.c Line:1011,然后我这个问题上又堵了好几天,这是什么情况呢,程序的逻辑是收完一段数据,给变一个标志位,然后通过检索这个标志位,在另外的线程处理,逻辑没有问题啊,原来windows多线程开发是线程不安全的,多线程中共享数据的访问,都要加锁。不然的话,出现内存溢出,数组越界等等奇怪的问题。
    
    还有iocp如何区分每一个socket,我是采用检索全局socket,GetQueuedCompletionStatus被激活后,将接收到的数据放到各自的全局变量区域。全局变量怎么申请呢,如果连接数量够多,申请固定大小的全局内存空间,在程序启动的时候会出现内存不足,解决办法就是在堆中申请内存(也就是malloc,注意malloc之后别忘记free,如果频繁的申请释放内存空间,容易出现磁盘碎片,不鼓励使用这种办法)。

投递多个wsarecv有必要吗,投递多个能充分的使用cpu,这个不可否认,但是投递之后我们需要花更多的内存处理时间去组包和处理数据,如何数据还没有收完的话,还需要进一步的投递多个包,然后再组包和处理数据。。。。。。这样的效率更慢,我觉得一来一回(接收数据先判断自己的数据是否收满,没有收满再投递一个wsarecv)的方式已经可以满足自己的需要了,但是一来一回的处理方式,需要注意,GetQueuedCompletionStatus所在的工作着线程最好是不要做过多的数据处理工作,以免影响客户端发送的太快,服务器接收的太慢(如果出现这个问题,容易引起远程主机主动关闭socket错误)。

iocp中是如何进行超时判断的?服务器资源一定,客户端如果一直连接而不发送数据或者出现各种意外客户端断开连接服务器而没有及时发现,服务器会出现资源不够用的情况,这时就要超时判断。工作者线程中记录wsarecv和WSASend操作的时间,然后再另外开一个线程判断上一次的处理wsarecv和wsasend操作跟当前时间是否超时(可以根据自己需求设定,mfc中可以使用CTimespan::GetTotalSeconds()取得时间差),如果超时就关闭socket,释放相应的内存等等。

与IO打交道,估计是所有iocp的难题,据相关资料记载(我也不知道是在哪里看到的,IO外设的任何操作速度相对cpu处理速度来说,cpu开了1000多倍),而iocp工作者线程池设计目的为了充分发挥cpu的性能,所以工作者线程中最好不要处理IO(这就出现一个问题,空间换时间,内存不够使用)。

工作者不处理IO,那就带来了内存的使用问题,IO处理慢,未处理的IO比较多,未处理内存积压越来越多,内存不够用。32位操作系统,内存使用机制,内核默认2G内存,程序员自己可用2G。可以通过设置c盘的boot.ini隐藏文件设置3GB程序员可用内存(具体方法可自己上网搜索)。

昨天终于写好iocp服务器程序,在内存大小限制情况,短连接,每个连接都只上传6MB以内的数据,能跑500台端机,不过IO处理是关键,如果服务器忙碌的时候,在服务器上做其他操作如刷屏、打印、键盘输入数据等,会出现写文件很慢,内存很容易出现不够用的情况,不过,这已经能满足我的性能需求了。

终于能长长的松一口气。。。。。。好开心啊!

多线程下的神奇的IOCP的更多相关文章

  1. 多线程下NSOperation、NSBlockOperation、NSInvocationOperation、NSOperationQueue的使用

    本篇文章主要介绍下多线程下NSOperation.NSBlockOperation.NSInvocationOperation.NSOperationQueue的使用,列举几个简单的例子. 默认情况下 ...

  2. python 类变量 在多线程下的共享与释放问题

    最近被多线程给坑了下,没意识到类变量在多线程下是共享的,还有一个就是没意识到 内存释放问题,导致越累越大 1.python 类变量 在多线程情况 下的 是共享的 2.python 类变量 在多线程情况 ...

  3. Java多线程21:多线程下的其他组件之CyclicBarrier、Callable、Future和FutureTask

    CyclicBarrier 接着讲多线程下的其他组件,第一个要讲的就是CyclicBarrier.CyclicBarrier从字面理解是指循环屏障,它可以协同多个线程,让多个线程在这个屏障前等待,直到 ...

  4. Java多线程20:多线程下的其他组件之CountDownLatch、Semaphore、Exchanger

    前言 在多线程环境下,JDK给开发者提供了许多的组件供用户使用(主要在java.util.concurrent下),使得用户不需要再去关心在具体场景下要如何写出同时兼顾线程安全性与高效率的代码.之前讲 ...

  5. 多线程下C#如何保证线程安全?

    多线程编程相对于单线程会出现一个特有的问题,就是线程安全的问题.所谓的线程安全,就是如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是 ...

  6. 多线程下HashMap的死循环问题

    多线程下[HashMap]的问题: 1.多线程put操作后,get操作导致死循环.2.多线程put非NULL元素后,get操作得到NULL值.3.多线程put操作,导致元素丢失. 本次主要关注[Has ...

  7. ASP.NET多线程下使用HttpContext.Current为null解决方案 2015-01-22 15:23 349人阅读 评论(0) 收藏

    问题一:多线程下获取文件绝对路径 当我们使用HttpContext.Current.Server.MapPath(strPath)获取绝对路径时HttpContext.Current为null,解决办 ...

  8. ASP.NET多线程下使用HttpContext.Current为null解决方案 2015-01-22 15:23 350人阅读 评论(0) 收藏

    问题一:多线程下获取文件绝对路径 当我们使用HttpContext.Current.Server.MapPath(strPath)获取绝对路径时HttpContext.Current为null,解决办 ...

  9. ASP.NET多线程下使用HttpContext.Current

    本来要实现asp.net下使用tcp通讯方式向服务器获取数据,开始采用的方式是 参考: ASP.NET多线程下使用HttpContext.Current为null解决方案 http://www.cnb ...

随机推荐

  1. Android开发之Shortcuts, LiveFolder, Widget

    2013-07-05 桌面组件包括:快捷方式(Shortcuts),实时文件夹(Live Folder),桌面插件(Widget).   快捷方式用于启动应用程序的某个组件,例如Activity, S ...

  2. eval函数处理JSON数据需要加括号

    在将服务器端构建好的JSON数据转化为可用的JavaScript对象时常常使用eval函数.如下: var dataJson = eval('(' + data + ')'); 在转化的时候需要将JS ...

  3. 函数适配器bind2nd 、mem_fun_ref 源码分析、函数适配器应用举例

    一.适配器 三种类型的适配器: 容器适配器:用来扩展7种基本容器,利用基本容器扩展形成了栈.队列和优先级队列 迭代器适配器:(反向迭代器.插入迭代器.IO流迭代器) 函数适配器:函数适配器能够将仿函数 ...

  4. IOS基于XMPP协议开发--XMPPFramewok框架(三):用户注册

    接着上面说 用户注册是比较简单的,成功连接上服务器后,设置好JID,即可调用 [_xmppStream registerWithPassword:pwd error:&err] 进行注册 -( ...

  5. 安卓-APP安装后多个图标的解决

    原因是在不同Activity的intent中配置了多个LAUNCHER. <intent-filter> <action android:name="android.int ...

  6. Struts2源码阅读(一)_Struts2框架流程概述

    1. Struts2架构图  当外部的httpservletrequest到来时 ,初始到了servlet容器(所以虽然Servlet和Action是解耦合的,但是Action依旧能够通过httpse ...

  7. linux查看匹配内容的前后几行(转)

    linux系统中,利用grep打印匹配的上下几行   如果在只是想匹配模式的上下几行,grep可以实现.   $grep -5 'parttern' inputfile //打印匹配行的前后5行   ...

  8. love2d--glsl02变量和语句

    Shader分为顶点着色器和片段着色器,GPU先处理顶点再处理片段,大概可以这么理解, 顶点着色器处理模型里的点,输出处理后的数据,这些数据经过GPU其它模块处理后传入 片段着色器,经片段着色器综合后 ...

  9. 解决Access denied for user &#39;&#39;@&#39;localhost&#39; to database &#39;mysql&#39;问题

    在改动mysql的root用户password后,再登陆,提示如标题的错误,找了一番答案之后,最终解决,过程例如以下: 1.停掉mysql:      service mysqld stop 2.使用 ...

  10. Unix系统编程()在堆上分配内存

    在堆上分配内存:malloc和free 一般情况下,C程序使用malloc函数族在堆上分配和释放内存.较之brk和sbrk,这些函数具备不少优点: 属于C语言标准的一部分 更易于在多线程程序中使用 接 ...