IOCP模型浅析

  • 什么是IOCP

IOCP即是完成端口,完成端口会充分利用Windows内核来进行I/O的调度,是用于C/S通信模式中性能最好的网络通信模型,当初微软提出完成端口的初衷是为了解决“one thread per client ”的缺点, 它充分利用内核对象的调度,只使用少量的几个线程来处理和客户端的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能,可以说在Windows中没有比它更好的通信模型了。


  • 为什么选择IOCP,优势在哪?

因为效率高呀!在完成端口模型中,我们会实现开好几个线程,一般是有多少个CPU开多少个线程(其实一般是CPU*2个),简历CPU*2个线程的好处是,在一个工作线程被Sleep()或者WaitForSingleObject()之类被停止的情况时,IOCP就能唤醒同在一个CPU上的另一个线程代替这个Sleep的线程继续执行,这样完成端口就实现了CPU的满负荷工作,效率也就高了。这样做的好处是可以避免线程的上下文切换。然后让这几个线程等待,当有用户请求来到的时候,就把这些请求添加到一个公共的消息队列中去。这个时候我们刚刚开好的那几个线程就有用了,他们会排队逐个去消息队列中提取消息,并加以处理。(其实这就是一个线程池处理消息的过程,一个线程队列,一个消息队列,线程队列不断获取消息队列中的消息。)这种方式很优雅的实现了异步通信和负载均衡的问题。并且线程在没事干的时候会被系统挂起来,不会占用CPU周期。

举个例子:

假若我这里有100万个用户同时与一个进程保持着TCP连接,而每一个时刻只有几十或几百个TCP连接,所以我们只需要处理100万连接中的一小部分连接,在使用别的模型时,只能通过select的方式对所有的连接都遍历一遍,查询出其中有事件的连接,可想而知,这种查询方式效率是多么的低下!仿佛大海捞针(虽然没这么夸张)。所以这时我们的完成端口就闪亮登场了,专治各种疑难杂症!完成端口里是这么干的:一旦一个连接上有事件发生是,它会立即将事件组成一个完成包放入到完成端口中(其实就是放入到一个队列里面),这时我们事先开启的等待线程就可以直接从该队列中取出该事件了,那么就避免了select的查询了,效率也就提高了很多,同一时间的用户量越大效率越明显!


  • 完成端口读写文件的使用流程

1、创建完成端口

HANDLE m_hIOCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );

2、创建CPU对应的线程个数

SYSTEM_INFO si;  
GetSystemInfo(&si);  
  
int m_nProcessors = si.dwNumberOfProcessors;              // 获取CPU的数量    

// 根据CPU数量,建立*2的线程  
  m_nThreads = 2 * m_nProcessors;  
 HANDLE* m_phWorkerThreads = new HANDLE[m_nThreads];  
  
 for (int i = 0; i < m_nThreads; i++)  
 {  
     m_phWorkerThreads[i] = ::CreateThread(0, 0, _WorkerThread, …);  
 }  

3、获取文件句柄

TCHAR SrcFileName[1024]; //文件路径
cin >> SrcFileName;
HANDLE hSrcFile=CreateFile(SrcFileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_FLAG_OVERLAPPED,NULL);  
if(hSrcFile==INVALID_HANDLE_VALUE)  
{  
    printf("文件打开失败!");  
}  

4、绑定完成端口

CreateIoCompletionPort(hSrcFile,hIOCP,READ_KEY,0);  

5、使用PostQueuedCompletionStatus指令来向“IO操作队列”中添加记录

PostQueuedCompletionStatus(hIOCP,0,WRITE_KEY,&ov);  

6、线程调用GetQueuedCompletionStatus命令来将自己添加到“线程等待队列”中

GetQueuedCompletionStatus(hIOCP,&nTransfer,&CompletionKey,&o,INFINITE); 

7、处理获得的CompletionKey

switch(CompletionKey)  
{  
case READ_KEY:  
    // 代表读取IO操作已经完成,进行下一步写入操作  
    // ...
    break;  
case WRITE_KEY:  
    // 代表写入IO操作已经完成,进行下一步读取操作  
    // ...
    break;  
default:  
    break;  
}  
  • 参考文献

https://blog.csdn.net/beyond_cn/article/details/9336043

https://blog.csdn.net/zssureqh/article/details/17203809

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章