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

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