Windows I/O完成連接埠

來源:互聯網
上載者:User
Windows I/O完成連接埠


Windows
2009-09-17 18:17:42
閱讀418

評論0


字型大小:大

js-fcurrent fc05">小

WINDOWS
完成連接埠編程

1、基本概念
2、WINDOWS完成連接埠的特點
3、完成連接埠(Completion Ports
)相關資料結構和建立
4、完成連接埠線程的工作原理
5、Windows完成連接埠的執行個體代碼

WINDOWS完成連接埠編程

  
摘要:開發網路程式從來都不是一件容易的事情,儘管只需要遵守很少的一些規則:建立socket,發起串連,接受串連,發送和接收資料,等等。真正的困難
在於:讓你的程式可以適應從單單一個串連到幾千個串連乃至於上萬個串連。利用Windows完成連接埠進行重疊I/O的技術,可以很方便地在Windows
平台上開發出支援大量串連的網路服務程式。本文介紹在Windows平台上使用完成連接埠模型開發的基本原理,同時給出實際的例子。本文主要關注C/S結構
的伺服器端程式,因為一般來說,開發一個大容量、具有可擴充性的winsock程式就是指服務程式。

1、基本概念

   設
備---指windows作業系統上允許通訊的任何東西,比如檔案、目錄、串列口、並行口、郵件槽、具名管道、無名管道、通訊端、控制台、邏輯磁碟、物理
磁碟等。絕大多數與裝置打交道的函數都是CreateFile/ReadFile/WriteFile等,
所以我們不能看到**File函數就只想到檔案裝置

   與裝置通訊有兩種方式,同步方式和非同步方式:同步方式

,當調用ReadFile這類函數時,函數會等待系統執行完所要求的工作,然後才返回;非同步方式

,ReadFile這類函數會直
接返回,系統自己去完成對裝置的操作,然後以某種方式通知完成操作。

   重疊I/O
----顧名思義,就是當你調用了某個函數(比如ReadFile)
就立刻返回接著做自己的其他動作的時候,系統同時也在對I/0裝置進行你所請求的操作,在這段時間內你的程式和系統的內部動作是重疊的,因此有更好的性
能。所以,重疊I/O是在非同步方式下使用I/O裝置的。重疊I/O需要使用的一個非常重要的資料結
構:OVERLAPPED


2、WINDOWS完成連接埠的特點

  
Win32重疊I/O(Overlapped
I/O)機制允許發起一個操作,並在操作完成之後接收資訊。對於那種需要很長時間才能完成的操作來說,重疊IO機制尤其有用,因為發起重疊操作的線程在重
疊請求發出後就可以自由地做別的事情了。在WinNT和Win2000上,提供的真正可擴充的I/O模型就是使用完成連接埠(Completion
Port)的重疊I/O。完成連接埠---是一種WINDOWS核心對象
。完成連接埠用於非同步方式的重疊I/0情況下,當然重疊I/O不一定非得使用完成連接埠不可,同樣裝置核心對象、事件對象、告
警I/0等也可使用。
但是完成連接埠內部提供了線程池的管理,可以避免反覆建立線程的開銷,同
時可以根據CPU的個數靈活地決定線程個數,而且可以減少線程調度的次數從而提高效能
。其實類似於WSAAsyncSelect和
select函數的機制更容易相容Unix,但是難以實現我們想要的“擴充性”。而且windows完成連接埠機制在作業系統的內部已經作了最佳化,從而具備
了更高的效率。所以,我們選擇完成連接埠開始我們的伺服器程式開發。
  
1)發起操作不一定完成:系統會在完成的時候通知你,通過使用者在完成連接埠上的等待,處理操作的結果。所以要有檢查完成連接埠和取操作結果的線程。在完成連接埠
上守候的線程系統有最佳化,除非在執行的線程發生阻塞,不會有新的線程被啟用,以此來減少線程切換造成的效能代價。所以如果程式中沒有太多的阻塞操作,就沒
有必要啟動太多的線程,使用CPU數量的兩倍,一般這麼多線程就夠了

  
2)操作與相關資料的綁定方式:在提交資料的時候使用者對資料打上相應的標記,記錄操作的類型,在使用者處理操作結果的時候,通過檢查自己打的標記和系統的操
作結果進行相應的處理。
  
3)操作返回的方式:一般操作完成後要通知程式進行後續處理。但寫操作可以不通知使用者,此時如果使用者寫操作不能馬上完成,寫操作的相關資料會被暫存到非交
換緩衝區中,在操作完成的時候,系統會自動釋放緩衝區,此時發起完寫操作,使用的記憶體就可以釋放了。但如果佔用非交換緩衝太多會使系統停止回應。

3、
完成連接埠(Completion Ports )相關資料結構和建立

   
其實可以把完成連接埠看成系統維護的一個隊列,作業系統把重疊IO操作完成的事件通知放到該隊列裡,由於是暴露
“操作完成”的事件通知,所以命名為“完成連接埠”(Completion
Ports)。一個socket被建立後,就可以在任何時刻和一個完成連接埠聯絡起來。

OVERLAPPED資料結構

typedef struct
_OVERLAPPED {
    ULONG_PTR Internal;  //被系統內部賦值,用來表示系統狀態
   
ULONG_PTR InternalHigh;  //被系統內部賦值,表示傳輸的位元組數
    union {
       
struct {
            DWORD Offset; 
//與OffsetHigh合成一個64位的整數,用來表示從檔案頭部的多少位元組開始操作 
            DWORD
OffsetHigh;  //如果不是對檔案I/O來操作,則Offset必須設定為0 
         }; 
      
PVOID Pointer;
    }; 
   HANDLE hEvent; 
//如果不使用,就務必設為0;否則請賦一個有效Event控制代碼
} OVERLAPPED, *LPOVERLAPPED;


面是非同步方式使用ReadFile的一個例子

OVERLAPPED
Overlapped;
Overlapped.Offset=345;
Overlapped.OffsetHigh=0;
Overlapped.hEvent=0;

//假定其他參數都已經被初始化
ReadFile(hFile,buffer,sizeof(buffer),&dwNumBytesRead,&Overlapped);

這樣就完成了非同步方式讀檔案的操作,然後ReadFile函數返回,由作業系統做自己的事情。

下面介紹幾個與OVERLAPPED結構相關的函數。

等待重疊I/0操作完成的函數

BOOL GetOverlappedResult
(
HANDLE hFile,
LPOVERLAPPED
lpOverlapped, //接受返回的重疊I/0結構
LPDWORD lpcbTransfer, //成功傳輸了多少位元組數
BOOL
fWait  //TRUE只有當操作完成才返回,FALSE直接返回,如果操作沒有完成,
                   
//通過用GetLastError( )函數會返回ERROR_IO_INCOMPLETE
);



HasOverlappedIoCompleted
可以協助我們測試重疊I/0操作是否
完成,該宏對OVERLAPPED結構的Internal成員進行了測試,查看是否等於STATUS_PENDING值。

   一般來說,一個應用程式可以建立多個背景工作執行緒來處理完成連接埠上的通知事件。
工作
線程的數量依賴於程式的具體需要。但是在理想的情況下,應該對應一個CPU
建立一個線程。因為在完成連接埠理想模型中,每個線程都可以從系統獲得一個“原子”性的時間片,輪番運行並檢查完成連接埠,線程的切換是額外的開銷。但在實際
開發的時候,還要考慮這些線程是否牽涉到其他堵塞操作的情況。如果某線程進行堵塞操作,系統則將其掛起,讓別的線程獲得已耗用時間。因此,如果有這樣的情
況,可以多建立幾個線程來盡量利用時間。

建立完成連接埠的函數



連接埠是一個核心對象,使用時它總是要和至少一個有效裝置控制代碼相關聯,完成連接埠是一個複雜的核心對象,建立它的函數是:
HANDLE CreateIoCompletionPort
(
    IN HANDLE
FileHandle,
    IN HANDLE ExistingCompletionPort,
    IN
ULONG_PTR CompletionKey,
    IN DWORD NumberOfConcurrentThreads
   
);

通常建立工作分兩步:

第一步,建立一個新的完成連接埠核心對象,可以使用下面的
函數:
       HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)
      
{    
          return
CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);

       };      
第二步,將剛建立的完成連接埠和一個有效裝置控制代碼關聯起來,可以使用下面的函數:
      
bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE
hDevice,DWORD dwCompKey)
       {
          HANDLE
h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);
         
return h==hCompPort;
       };
說明如下:

1)CreateIoCompletionPort函數也可以一次性的既建立完成連接埠對象,又關聯到一個有效裝置控制代碼。
2)CompletionKey是一個可以自己定義的參數,我們可以把一個結構的地址賦給它,然後在合適的時候取出來使用

最好要保證結構裡面的記憶體不是分配在棧上,除非你有十分的把握記憶體會保留到你要使用的那一刻。
3)NumberOfConcurrentThreads
用來指定要允許同時啟動並執行的線程的最大個數,通常我們指定為0,這樣系統會根據CPU的個數來自動確定。
4)建立和關聯的動作完成後,系統會將完
成連接埠關聯的裝置控制代碼、完成鍵作為一條紀錄加入到這個完成連接埠的裝置列表中。如果你有多個完成連接埠,就會有多個對應的裝置列表。如果裝置控制代碼被關閉,則表
中該紀錄會被自動刪除。

4、完成連接埠線程的工作原理

1)完成連接埠管理線程池

  
完成連接埠可以協助我們管理線程池,但是線程池中的線程需要我們自己使用_beginthreadex來建立,憑
什麼通知完成連接埠管理我們的新線程呢?
答案在函數GetQueuedCompletionStatus。該函數原型:
BOOL GetQueuedCompletionStatus
(
    IN HANDLE
CompletionPort,
    OUT LPDWORD lpNumberOfBytesTransferred,
   
OUT PULONG_PTR lpCompletionKey,
    OUT LPOVERLAPPED *lpOverlapped,
   
IN DWORD dwMilliseconds
); 

   這個函數試圖從指定的完成連接埠的I/0完成隊列中提取紀錄。只有當重疊I/O動作完成的時候,完成隊列中才有紀錄。凡是調用這個函數的線程將會被放入到完成連接埠的等待線程隊列中,因此完成連接埠就可以在自己的線程池中協助我們維護這個線

。完成連接埠的I/0完成隊列中存放了當重疊I/0完成的結果----
一條紀錄,該紀錄擁有四個欄位,前三項就對應GetQueuedCompletionStatus函數的2、3、4參數,最後一個欄位是錯誤資訊
dwError。我們
也可以通過調用
PostQueudCompletionStatus類比完成一個重疊I/0操作
。 

   當I/0完成隊列中出現了紀錄,完成連接埠將會檢查等待線程隊列
,該隊列中的線程都是通過調用GetQueuedCompletionStatus函數使自己排入佇列的。

待線程隊列很簡單,只是儲存了這些線程的ID。完成連接埠按照後進先出的原則將一個線程隊列的ID放入到釋放
線程列表中,同時該線程將從等待GetQueuedCompletionStatus函數返回的睡眠狀態中變為可調度狀態等待CPU的調度

所以我們的線程要想成為完成連接埠管理的線程,就必須要調用GetQueuedCompletionStatus函數。出於效能的最佳化,實際上完成連接埠還維
護了一個暫停線程列表,具體細節可以參考《Windows進階編程指南》,我們現在知道的知識,已經足夠了。

2)線程間資料傳遞

  
完成連接埠線程間傳遞資料最常用的辦法是在_beginthreadex函數中將參數傳遞給線程函數,或者使用全域變數。但完成連接埠也有自己的傳遞資料的方法,答案就在於CompletionKey和OVERLAPPED參數

CompletionKey
被儲存在完成連接埠的裝置表中,是和裝置控制代碼一一對應的,我們可以將與裝置控制代碼相關的資料儲存到CompletionKey中,或者將
CompletionKey表示為結構指標,這樣就可以傳遞更加豐富的內容。這些內容只能在一開始關聯完成連接埠和裝置控制代碼的時候做,因此不能在以後動態改
變。

   OVERLAPPED參數是在每次調用ReadFile這樣的支援重疊I/0的函數時傳遞給完
成連接埠的
。我們可以看到,如果我們不是對檔案裝置做操作,該結構的成員變數就對我們幾乎毫無作用。我們需要附加資訊,可以建立自己的結構,
然後將OVERLAPPED結構變數作為我們結構變數的第一個成員,然後傳遞第一個成員變數的地址給ReadFile這樣的函數。因為類型匹配,當然可以
通過編譯。當GetQueuedCompletionStatus函數返回時,我們可以擷取到第一個成員變數的地址,然後一個簡單的強制轉換,我們就可以
把它當作完整的自訂結構的指標使用,這樣就可以傳遞很多附加的資料了。太好了!只有一點要注意,如果跨線程傳遞,請注意將資料分配到堆上,並且接收端應
該將資料用完後釋放。我們通常需要將ReadFile這樣的非同步函數的所需要的緩衝區放到我們自訂的結構中,這樣當
GetQueuedCompletionStatus被返回時,我們的自訂結構的緩衝區變數中就存放了I/0操作的資料。CompletionKey和OVERLAPPED參數,都可以通過
GetQueuedCompletionStatus函數獲得

3)線程的安全退出

   很多線程為了不止一次地執行非同步資料處理,需要使用如下語句
while
(true)
{
       ......
       GetQueuedCompletionStatus
(...); 
       ......
}

麼線程如何退出
呢,答案就在於上面曾提到過的PostQueudCompletionStatus

數,我們可以向它發送一個自訂的包含了OVERLAPPED成員變數的結構地址,裡面含一個狀態變數,當狀態變數為退出標誌時,線程就執行清除動作然後
退出。

5、Windows完成連接埠的執行個體代碼

DWORD WINAPI
WorkerThread(LPVOID lpParam)
{
ULONG_PTR *PerHandleKey;
OVERLAPPED
*Overlap;
OVERLAPPEDPLUS *OverlapPlus;
OVERLAPPEDPLUS *newolp;
DWORD
dwBytesXfered;
while (1)
{
  ret = GetQueuedCompletionStatus
(hIocp,
&dwBytesXfered, (PULONG_PTR)&PerHandleKey, &Overlap,
INFINITE);
  if (ret == 0)
   {
    // Operation failed
   
continue;
   }
 OverlapPlus = CONTAINING_RECORD(Overlap,
OVERLAPPEDPLUS, ol);
 switch (OverlapPlus->OpCode)
 {
 case
OP_ACCEPT:
   // Client socket is contained in OverlapPlus.sclient
 
 // Add client to completion port
  CreateIoCompletionPort
((HANDLE)OverlapPlus->sclient,
hIocp, (ULONG_PTR)0, 0);
  // Need a new OVERLAPPEDPLUS structure
 
// for the newly accepted socket. Perhaps
  // keep a look aside
list of free structures.
  newolp = AllocateOverlappedPlus();
  if
(!newolp)
   {
    // Error
   }
  newolp->s =
OverlapPlus->sclient;
  newolp->OpCode = OP_READ;
  // This
function divpares the data to be sent
 
PrepareSendBuffer(&newolp->wbuf);
  ret = WSASend
(newolp->s, &newolp->wbuf, 1,
&newolp->dwBytes, 0, &newolp.ol, NULL);
  if (ret ==
SOCKET_ERROR)
   {
    if (WSAGetLastError() != WSA_IO_PENDING)
    
{
       // Error
     }
   }
  // Put structure in look
aside list for later use
  FreeOverlappedPlus(OverlapPlus);
  //
Signal accept thread to issue another AcceptEx
  SetEvent
(hAcceptThread);
  break;
case
OP_READ:
  // Process the data read
  // Repost the read if
necessary, reusing the same
  // receive buffer as before
 
memset(&OverlapPlus->ol, 0, sizeof(OVERLAPPED));
  ret = WSARecv
(OverlapPlus->s,
&OverlapPlus->wbuf, 1, &OverlapPlus->dwBytes,
&OverlapPlus->dwFlags, &OverlapPlus->ol, NULL);
  if
(ret == SOCKET_ERROR)
  {
    if (WSAGetLastError() !=
WSA_IO_PENDING)
     {
      // Error
     }
  }
  break;
case
OP_WRITE:
  // Process the data sent, etc.
  break;
} //
switch
} // while
} // WorkerThread

查看以上代碼,注意如果Overlapped操作立刻失敗(比如,返回SOCKET_ERROR或其他非WSA_IO_PENDING的錯誤),則
沒有任何完成通知時間會被放到完成連接埠隊列裡。反之,則一定有相應的通知時間被放到完成連接埠隊列。更完善的關於Winsock的完成連接埠機制,可以參考
MSDN的Microsoft PlatForm SDK,那裡有完成連接埠的例子。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.