完成端口1

WinSocket模型的探討——完成端口模型(一)

開發者在線 Builder.com.cn 更新時間:2008-01-26作者:一雨田 來源:CSDN

本文關鍵詞: 端口模型 Winsocket

衆所皆知,完成端口是在WINDOWS平臺下效率最高,擴展性最好的IO模型,特別針對於WINSOCK的海量連接時,更能顯示出其威力。其實建立一個完成端口的服務器也很簡單,只要注意幾個函數,瞭解一下關鍵的步驟也就行了。

這是篇完成端口入門級的文章,分爲以下幾步來說明完成端口: 

  1. 函數
  2. 常見問題以及解答
  3. 步驟
  4. 例程

1、函數:

我們在完成端口模型下會使用到的最重要的兩個函數是:
CreateIoCompletionPort、GetQueuedCompletionStatus

CreateIoCompletionPort  的作用是創建一個完成端口和把一個IO句柄和完成端口關聯起來:

// 創建完成端口
HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

// 把一個IO句柄和完成端口關聯起來,這裏的句柄是一個socket 句柄
CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);

其中第一個參數是句柄,可以是文件句柄、SOCKET句柄。
第二個就是我們上面創建出來的完成端口,這裏就把兩個東西關聯在一起了。
第三個參數很關鍵,叫做PerHandleData,就是對應於每個句柄的數據塊。我們可以使用這個參數在後面取到與這個SOCKET對應的數據。
最後一個參數給0,意思就是根據CPU的個數,允許儘可能多的線程併發執行。

GetQueuedCompletionStatus 的作用就是取得完成端口的結果:

// 從完成端口中取得結果
GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE)

第一個參數是完成端口
第二個參數是表明這次的操作傳遞了多少個字節的數據
第三個參數是OUT類型的參數,就是前面CreateIoCompletionPort傳進去的單句柄數據,這裏就是前面的SOCKET句柄以及與之相對應的數據,這裏操作系統給我們返回,讓我們不用自己去做列表查詢等操作了。
第四個參數就是進行IO操作的結果,是我們在投遞 WSARecv / WSASend 等操作時傳遞進去的,這裏操作系統做好準備後,給我們返回了。非常省事!!

個人感覺完成端口就是操作系統爲我們包裝了很多重疊IO的不爽的地方,讓我們可以更方便的去使用,下篇我將會嘗試去講述完成端口的原理。

2、常見問題和解答

a、什麼是單句柄數據(PerHandle)和單IO數據(PerIO)

單句柄數據就是和句柄對應的數據,像socket句柄,文件句柄這種東西。

單IO數據,就是對應於每次的IO操作的數據。例如每次的WSARecv/WSASend等等

其實我覺得PER是每次的意思,翻譯成每個句柄數據和每次IO數據還比較清晰一點。

在完成端口中,單句柄數據直接通過GetQueuedCompletionStatus 返回,省去了我們自己做容器去管理。單IO數據也容許我們自己擴展OVERLAPPED結構,所以,在這裏所有與應用邏輯有關的東西都可以在此擴展。

b、如何判斷客戶端的斷開

我們要處理幾種情況

1) 如果客戶端調用了closesocket,我們就可以這樣判斷他的斷開:

if(0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, 。。。)

if(BytesTransferred == 0)
{
    // 客戶端斷開,釋放資源
}

2) 如果是客戶端直接退出,那就會出現64錯誤,指定的網絡名不可再用。這種情況我們也要處理的:

if(0 == GetQueuedCompletionStatus(。。。))
{
   if( (GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED) )
   {
        // 客戶端斷開,釋放資源
   }
}

3、步驟

編寫完成端口服務程序,無非就是以下幾個步驟:

  1、創建一個完成端口
  2、根據CPU個數創建工作者線程,把完成端口傳進去線程裏
  3、創建偵聽SOCKET,把SOCKET和完成端口關聯起來
  4、創建PerIOData,向連接進來的SOCKET投遞WSARecv操作

  5、線程裏所做的事情:
 a、GetQueuedCompletionStatus,在退出的時候就可以使用PostQueudCompletionStatus使線程退出
 b、取得數據並處理

4、例程

下面是服務端的例程,可以使用《WinSocket模型的探討——Overlapped模型(一)》中的客戶端程序來測試次服務端。稍微研究一下,也就會對完成端口模型有個大概的瞭解了。

/*

   完成端口服務器

   接收到客戶端的信息,直接顯示出來

*/

#include "winerror.h"
#include "Winsock2.h"
#pragma comment(lib, "ws2_32")

#include "windows.h"


#include <iostream>
using namespace std;


/// 宏定義
#define PORT 5050
#define DATA_BUFSIZE 8192

#define OutErr(a) cout << (a) << endl
      << "出錯代碼:" << WSAGetLastError() << endl
      << "出錯文件:" << __FILE__ << endl  
      << "出錯行數:" << __LINE__ << endl

#define OutMsg(a) cout << (a) << endl;


/// 全局函數定義


///////////////////////////////////////////////////////////////////////
//
// 函數名       : InitWinsock
// 功能描述     : 初始化WINSOCK
// 返回值       : void
//
///////////////////////////////////////////////////////////////////////
void InitWinsock()
{
 // 初始化WINSOCK
 WSADATA wsd;
 if( WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
 
}

///////////////////////////////////////////////////////////////////////
//
// 函數名       : BindServerOverlapped
// 功能描述     : 綁定端口,並返回一個 Overlapped 的Listen Socket
// 參數         : int nPort
// 返回值       : SOCKET
//
///////////////////////////////////////////////////////////////////////
SOCKET BindServerOverlapped(int nPort)
{
 // 創建socket
 SOCKET sServer = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

 // 綁定端口
 struct sockaddr_in servAddr;
 servAddr.sin_family = AF_INET;
 servAddr.sin_port = htons(nPort);
 servAddr.sin_addr.s_addr = htonl(INADDR_ANY);

 if(bind(sServer, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0)
 {
  OutErr("bind Failed!");
  return NULL;
 }

 // 設置監聽隊列爲200
 if(listen(sServer, 200) != 0)
 {
  OutErr("listen Failed!");
  return NULL;
 }
 return sServer;
}


/// 結構體定義
typedef struct
{
   OVERLAPPED Overlapped;
   WSABUF DataBuf;
   CHAR Buffer[DATA_BUFSIZE];
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;


typedef struct
{
   SOCKET Socket;
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;


DWORD WINAPI ProcessIO(LPVOID lpParam)
{
 HANDLE CompletionPort = (HANDLE)lpParam;
    DWORD BytesTransferred;
    LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_OPERATION_DATA PerIoData;

 while(true)
 {
 
  if(0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE))
  {
   if( (GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED) )
   {
    cout << "closing socket" << PerHandleData->Socket << endl;
    
    closesocket(PerHandleData->Socket);
    
    delete PerIoData;
    delete PerHandleData;
    continue;
   }
   else
   {
    OutErr("GetQueuedCompletionStatus failed!");
   }
   return 0;
  }
  
  // 說明客戶端已經退出
  if(BytesTransferred == 0)
  {
   cout << "closing socket" << PerHandleData->Socket << endl;
   closesocket(PerHandleData->Socket);
   delete PerIoData;
   delete PerHandleData;
   continue;
  }

  // 取得數據並處理
  cout << PerHandleData->Socket << "發送過來的消息:" << PerIoData->Buffer << endl;

  // 繼續向 socket 投遞WSARecv操作
  DWORD Flags = 0;
  DWORD dwRecv = 0;
  ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
  PerIoData->DataBuf.buf = PerIoData->Buffer;
  PerIoData->DataBuf.len = DATA_BUFSIZE;
  WSARecv(PerHandleData->Socket, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL); 
 }

 return 0;
}

void main()
{
 InitWinsock();

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

 // 根據系統的CPU來創建工作者線程
 SYSTEM_INFO SystemInfo;
 GetSystemInfo(&SystemInfo);

 for(int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
 {
  HANDLE hProcessIO = CreateThread(NULL, 0, ProcessIO, CompletionPort, 0, NULL);
  if(hProcessIO)
  
 }

 // 創建偵聽SOCKET
 SOCKET sListen = BindServerOverlapped(PORT);


 SOCKET sClient;
 LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_OPERATION_DATA PerIoData;
 while(true)
 {
  // 等待客戶端接入
  //sClient = WSAAccept(sListen, NULL, NULL, NULL, 0);
  sClient = accept(sListen, 0, 0);
  
  cout << "Socket " << sClient << "連接進來" << endl;
  
  PerHandleData = new PER_HANDLE_DATA();
  PerHandleData->Socket = sClient;

  // 將接入的客戶端和完成端口聯繫起來
  CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);

  // 建立一個Overlapped,並使用這個Overlapped結構對socket投遞操作
  PerIoData = new PER_IO_OPERATION_DATA();
  
  ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
  PerIoData->DataBuf.buf = PerIoData->Buffer;
  PerIoData->DataBuf.len = DATA_BUFSIZE;

  // 投遞一個WSARecv操作
  DWORD Flags = 0;
  DWORD dwRecv = 0;
  WSARecv(sClient, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
 }

 DWORD dwByteTrans;
 PostQueuedCompletionStatus(CompletionPort, dwByteTrans, 0, 0);
 closesocket(sListen);
}

發佈了29 篇原創文章 · 獲贊 4 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章