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. Docker实战(五)编写Dockerfile

    一.创建Dockerfile文件 首先,需要创建一个目录来存放 Dockerfile 文件,目录名称可以任意,在目录里创建Dockerfile文件: 二.Dockerfile 基本框架 Dockerf ...

  2. ubuntu建立软ap共享无线网络

    建立ad-hoc模式共享网络 viewtopic.php?f=116&t=387194 有些android手机可能不支持ad-hoc模式,要第三方rom才行. 首先安装这些工具 代码: apt ...

  3. 安卓入门 使用android创建一个项目 从启动activity中响应按钮事件 启动另一个activity 并传递参数

    启动android studio创建一个新项目 public void sendMessage(View view){ Intent intent=new Intent(this,DispalyMes ...

  4. SeeSharpTools.JXI.DSP.Spectrum 使用

    _spectrumTask.Average.Mode = SeeSharpTools.JXI.DSP.Spectrum.SpectrumAverageMode.PeakHold; // NoAvera ...

  5. C数组逆序

    一.标准交换模式 /**** *标准交换模式 *实现数组的逆序,原理就是数组的首尾元素进行交换 ***/ #define N 5; int main(){ int array[N] = {15,20, ...

  6. CHero

    #ifndef __HERO_H__ #define __HERO_H__ #include "GameFrameHead.h" #include "GameParam. ...

  7. 【C语言】23-typedef

    一.typedef作用简介 * 我们可以使用typedef关键字为各种数据类型定义一个新名字(别名). 1 #include <stdio.h> 2 3 typedef int Integ ...

  8. C++中常函数内部的this指针也是const类型的

    代码中碰到一个奇怪的现象,在同样的函数中调用this指针,结果却有一个无法通过编译 // 读取连接信息 void ThirdWizardPage::ReadConnection() { QFile f ...

  9. oracle 函数判断字符串是否包含图片格式

    首先是写一个分割字符串的函数,返回table类型 CREATE OR REPLACE FUNCTION fn_split (p_str IN VARCHAR2, p_delimiter IN VARC ...

  10. CodeForces 558D

     Guess Your Way Out! II Time Limit:2000MS     Memory Limit:262144KB     64bit IO Format:%I64d & ...