Windows完成端口的理解
最近研究了一下完成端口,看了一篇奇文http://blog.csdn.net/piggyxp/article/details/6922277 , 在仔細研讀之后,調試了代碼。或許是我資歷尚淺,發現自己對作者的意圖的理解仍然欠缺。所以經過反復思考,終于有所斬獲。這里就寫一篇文章來補充一下作者的意思。
?
?1. 完成端口的實質
個人感覺完成端口就是一個“鬧鐘”,它可以被當做任何事情的提醒設備。比如說,(沒有試過)ReadFile可以異步操作,可以將這個異步操作的完成這個事件的提醒交給完成端口來完成。所以,原文中所使用的完成端口,只是將這個“鬧鐘”用在了網絡傳輸的提醒上。這也就說明了,盡管我們可以使用完成端口來完成各種提醒,但是主框架仍然是Socket那套流程。
為了更清晰地表達,請看下面的描述(從上往下)。這是作者的代碼的API調用流程。其實我不太喜歡作者的第三部分的那些流程圖,因為其中用了太多的自定義函數而掩蓋了底層的API調用。現在我把他還原。
初始化庫,WSAStart()
創建完成端口,CreateIoCompletionPort(-1,...);
創建工作線程,CreateThread()
創建listen socket,WSASocket
綁定listen socket 至完成端口,CreateIoCompletionPort(socket, …)
listen socket與本地端口綁定 ,bind()
開始監聽,listen()
向listen socket投遞accept請求,AcceptEx()
干其他事情或者等待結束命令
通信結束,通知Worker線程們退出,PostQueuedCompletionStatus()
?
至于Working 線程的做法和原先一樣。?
可以看到,盡管我們說完成端口的性能是最好的,然而它只是一個時間走得特別準的“鬧鐘”而已。 網絡編程的基本框架還是原來的那套。
?
2. 關于Accept投遞和WSARev投遞
整偏博文里我看得最頭痛的就是這個“投遞”。它到底是什么?
剛才說了,這里只是把完成端口作為“提醒”機制。具體來說,就是listen之后,通常的調用是如下面所示的
While(1)
{
?????????????????? Socket = Accept();
?????????????????? _beginthreadex(…); // 傳入socket
}
?
只不過,這樣的調用就是阻塞型調用。而使用完成端口的話,就是將這個Accept(當然,源代碼中使用更復雜的AcceptEx,其實差不多的)的調用(其實在內部,就相當于一個要求被accept的請求)委托給完成端口,然后主線程馬上返回,并且讓后者來提醒之前已經創建好的、在線程函數中調用了GetQueuedCompletionStatus函數的某個線程。當然,至于是哪個線程拿到了,那就是系統調度問題,這對我們來說是黑盒的。這就是那個AcceptEx投遞的含義了。說得通俗點,所謂的Accept投遞,就是告訴完成端口,“我——listen socket,向你提交一個請求,如果你有客戶端的連結請求了,請立即告訴我”。所以可以說,AcceptEx投遞就是listen socket的一個IO請求而已。
同樣的道理,如果我們不是讓完成端口去通知Accept完成,而是去讓它通知Receive操作完成,那就是原文中的“投遞WSARev操作”了。顯然,這是兩種操作。
?
3. 關于工作線程中的一些關鍵點?
那么作者在“詳細”那一節第六步中說的那個難點是怎么回事呢?我的理解是這樣的。負責監聽完成端口的線程(就是之前已經創建好的、在線程函數中調用了GetQueuedCompletionStatus函數的某個線程),它們是在GetQueuedCompletionStatus返回之前是不知道到底是那個操作被“鬧鐘”了,再加上之前說過的,具體是哪個線程被“鬧鐘”是我們所不知道的。所以說,你根本就無法判斷某個線程會被用做干嘛。所以就需要一種機制來分別了。作者采用的方法是,自定義一些說明性的信息,然后在投遞的時候附帶送過來,這樣在接受的時候就可以通過這些附加信息加以區別了。這就是那個PER_SOCKET_CONTEXT和PER_IO_CONTEXT的作用了。?具體來說,如果是為投遞Accept而準備的鬧鐘,那么某個線程GetQueuedCompletionStatus返回之后,將會獲得和Accept投遞有關的Socket和此請求的附加信息。和accept投遞有關的socket是哪個呢?自然是用于做listen的那個socket(GetQueuedCompletionStatus函數的第三個參數返回它的上下文——其實就是一個和它相匹配的補充性結構,那是在將socket和完成端口綁定時傳入的第三個參數)。注意到一個socket上面可以有很多個請求(類比一個客戶端/服務器連結上可以發送多次數據,每一次發送數據都需要通過一個“發送數據請求”),從而在listen socket上面,我們之前投遞了多個acceptEx請求,那么到底是哪個請求到來了呢?其實對于AcceptEx請求而言,哪一個并不那么重要,反正大家的工作都是創建一個客戶端/服務器連結socket,然后進行通信。然而如果真的重要了,那么就需要這個值了(比如說不同連結需要不同操作之類)。具體來說,GetQueuedCompletionStatus倒數第二個參數OVERLAPPED*,返回的就是和這個請求相關的信息。它其實就是我們在投遞這個請求的時候所附加給完成端口的說明性材料。注意看,這是一個OVERLAPPED結構指針,應該和我們自定義的PER_IO_CONTEXT沒有關系。實則不然,回顧CIOCPModel::_PostAccept里面AcceptEx調用的最后一個參數實際上是,&pAcceptIoContext->m_Overlapped。也就是說這里傳回來的是PER_IO_CONTEXT的第一個成員m_Overlapped的地址。通過位于_WorkThread函數中的風騷宏CONTAINING_RECORD就能夠得到這個PER_IO_CONTEXT的首地址啦。另外,如果投遞的是WSARev請求呢?這個附加信息怎么穿進去?一樣的啦,就是通過WSARev函數的OVERLAPPED結構體參數。這個結構體的實際作用,就是在異步IO操作之后,提供給你一些信息的。我們可以通過繼承這個結構(或者和這里的做法一致——采用風騷宏CONTAINING_RECORD)來拓展它,讓它包含我們需要的信息。接下去的事情就好辦了,根據自定義結構PER_IO_CONTEXT的成員m_OpType了解一些信息,知道這個回應,到底是listen socket的accept請求的回應(ACCEPT_POSTED)還是真正的客戶端/服務器連結socket的發送或者接收請求的回應(RECV_POSTED和SEND_POSTED)。如果是對accept請求的回應,那么返回的socket context和io context都是這個accept請求的,所以作者強調在_DoAccept中是一定不能夠動這兩個結構的。然而,如果是普通連結的發送/接收請求的回應,那么當然就是另外一回事情了。?
?
?
?
?
?
?
?
?
轉載于:https://www.cnblogs.com/aicro/archive/2012/08/31/2665205.html
總結
以上是生活随笔為你收集整理的Windows完成端口的理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UIAlertView的使用方法
- 下一篇: Automatic IE Testing