SOCKET編程進階之Overlapped I\O事件通知模型

SOCKET編程進階之Overlapped I\O事件通知模型

原文地址:http://blog.csdn.net/echoff/archive/2007/09/23/1797310.aspx


WINSOCK I\O模型有六種:
一:select模型
二:WSAAsyncSelect模型
三:WSAEventSelect模型
四:Overlapped I/O 事件通知模型
五:Overlapped I/O 完成例程模型
六:完成端口IOCP模型
且一個比一個完善,一個比一個高深。最好用的莫過於完成端口,但可惜的是隻有NT、2000的系統才支持這種功能。心痛之餘,我們只能寄希望於Overlapped I/O模型。
下面,我們來詳細分析一下重疊模型:
一、重疊模型的優點
1)可以運行在支持Winsock2的所有Windows平臺 ,而不像完成端口只是支持NT系統。
2)比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,重疊I/O(Overlapped I/O)模型使應用程序能達到更佳的系統性能。
因爲它和這4種模型不同的是,使用重疊模型的應用程序通知緩衝區收發系統直接使用數據,也就是說,如果應用程序投遞了一個10KB大小的緩衝區來接收數據,且數據已經到達套接字,則該數據將直接被拷貝到投遞的緩衝區。
而這4種模型種,數據到達並拷貝到單套接字接收緩衝區中,此時應用程序會被告知可以讀入的容量。當應用程序調用接收函數之後,數據才從單套接字緩衝區拷貝到應用程序的緩衝區,差別就體現出來了。
3)從《windows網絡編程》中提供的試驗結果中可以看到,在使用了P4 1.7G Xero處理器(CPU很強啊)以及768MB的迴應服務器中,最大可以處理4萬多個SOCKET連接,在處理1萬2千個連接的時候CPU佔用率才40% 左右 ―― 非常好的性能,已經直逼完成端口了^_^
二、重疊模型的基本原理
說了這麼多的好處,你一定也躍躍欲試了吧,不過我們還是要先提一下重疊模型的基本原理。
概括一點說,重疊模型是讓應用程序使用重疊數據結構(WSAOVERLAPPED),一次投遞一個或多個Winsock I/O請求。針對這些提交的請求,在它們完成之後,應用程序會收到通知,於是就可以通過自己另外的代碼來處理這些數據了。
需要注意的是,有兩個方法可以用來管理重疊IO請求的完成情況(就是說接到重疊操作完成的通知):
1)事件對象通知(event object notification)
2)完成例程(completion routines) ,注意,這裏並不是完成端口
而本文只是講述如何來使用事件通知的的方法實現重疊IO模型,完成例程的方法準備放到下一篇講 :) (內容太多了,一篇寫不完啊) ,如沒有特殊說明,本文的重疊模型默認就是指的基於事件通知的重疊模型。
既然是基於事件通知,就要求將Windows事件對象與WSAOVERLAPPED結構關聯在一起(WSAOVERLAPPED結構中專門有對應的參數),通俗一點講,就是。。。。對了,忘了說了,既然要使用重疊結構,我們常用的send, sendto, recv, recvfrom也都要被WSASend, WSASendto, WSARecv, WSARecvFrom替換掉了, 它們的用法我後面會講到,這裏只需要注意一點,它們的參數中都有一個Overlapped參數,我們可以假設是把我們的WSARecv這樣的操作操作“綁定”到這個重疊結構上,提交一個請求,其他的事情就交給重疊結構去操心,而其中重疊結構又要與Windows的事件對象“綁定”在一起,這樣我們調用完WSARecv以後就可以“坐享其成”,等到重疊操作完成以後,自然會有與之對應的事件來通知我們操作完成,然後我們就可以來根據重疊操作的結果取得我們想要德數據了。
三、關於重疊模型的基礎知識
下面來介紹並舉例說明一下編寫重疊模型的程序中將會使用到的幾個關鍵函數。
1)WSAOVERLAPPED結構
這個結構自然是重疊模型裏的核心,它是這麼定義的
typedef struct _WSAOVERLAPPED
{  DWORD Internal;  
   DWORD InternalHigh;  
   DWORD Offset;  
   DWORD OffsetHigh;  
   WSAEVENT hEvent;      // 唯一需要關注的參數,用來關聯WSAEvent對象
} WSAOVERLAPPED, *LPWSAOVERLAPPED;
我們需要把WSARecv等操作投遞到一個重疊結構上,而我們又需要一個與重疊結構“綁定”在一起的事件對象來通知我們操作的完成,看到了和hEvent參數,不用我說你們也該知道如何來來把事件對象綁定到重疊結構上吧?大致如下:
WSAEVENT event;                   // 定義事件
WSAOVERLAPPED AcceptOverlapped ; // 定義重疊結構
event = WSACreateEvent();         // 建立一個事件對象句柄
ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED)); // 初始化重疊結構
AcceptOverlapped.hEvent = event;    // Done !!


2)WSARecv系列函數
在重疊模型中,接收數據就要靠它了,它的參數也比recv要多,因爲要用刀重疊結構嘛,它是這樣定義的:
int WSARecv(SOCKET s,                      // 當然是投遞這個操作的套接字
            LPWSABUF lpBuffers,          // 接收緩衝區,與Recv函數不同
// 這裏需要一個由WSABUF結構構成的數組
            DWORD dwBufferCount,        // 數組中WSABUF結構的數量
            LPDWORD lpNumberOfBytesRecvd,  // 如果接收操作立即完成,這裏會返回函數調用
// 所接收到的字節數
            LPDWORD lpFlags,             // 說來話長了,我們這裏設置爲0 即可
            LPWSAOVERLAPPED lpOverlapped,  // “綁定”的重疊結構
            LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
                               // 完成例程中將會用到的參數,我們這裏設置爲 NULL
            );
返回值:
WSA_IO_PENDING : 最常見的返回值,這是說明我們的WSARecv操作成功了,但是I/O操作還沒有完成,所以我們就需要綁定一個事件來通知我們操作何時完成
其他的函數我這裏就不一一介紹了,因爲我們畢竟還有MSDN,而且在講後面的完成例程和完成端口的時候我還會講到一些 ^_^


3)WSAWaitForMultipleEvents函數
熟悉WSAEventSelect模型的朋友對這個函數肯定不會陌生,不對,其實大家都不應該陌生,這個函數與線程中常用的WaitForMultipleObjects函數有些地方還是比較像的,因爲都是在等待某個事件的觸發嘛。
因爲我們需要事件來通知我們重疊操作的完成,所以自然需要這個等待事件的函數與之配套。
DWORD WSAWaitForMultipleEvents(
                                  DWORD cEvents,          // 等候事件的總數量
                                  const WSAEVENT* lphEvents, // 事件數組的指針
                                  BOOL fWaitAll,          // 這個要多說兩句: 如果設置爲 TRUE,則事件數組中所有事件被傳信的時候函數纔會返回,FALSE則任何一個事件被傳信函數都要返回,我們這裏肯定是要設置爲FALSE的
                                  DWORD dwTimeout,    // 超時時間,如果超時,函數會返回WSA_WAIT_TIMEOUT,如果設置爲0,函數會立即返回,如果設置爲 WSA_INFINITE只有在某一個事件被傳信後纔會返回,在這裏不建議設置爲WSA_INFINITE
                                  BOOL fAlertable       // 在完成例程中會用到這個參數,這裏我們先設置爲FALSE
                                );
返回值:
WSA_WAIT_TIMEOUT :最常見的返回值,我們需要做的就是繼續Wait
WSA_WAIT_FAILED : 出現了錯誤,請檢查cEvents和lphEvents兩個參數是否有效
如果事件數組中有某一個事件被傳信了,函數會返回這個事件的索引值,但是這個索引值需要減去預定義值 WSA_WAIT_EVENT_0纔是這個事件在事件數組中的位置。
具體的例子就先不在這裏舉了,後面還會講到
注意:WSAWaitForMultipleEvents函數只能支持由WSA_MAXIMUM_WAIT_EVENTS對象定義的一個最大值,是 64,就是說WSAWaitForMultipleEvents只能等待64個事件,如果想同時等待多於64個事件,就要 創建額外的工作者線程,就不得不去管理一個線程池,這一點就不如下一篇要講到的完成例程模型了。


4)WSAGetOverlappedResult函數
既然我們可以通過WSAWaitForMultipleEvents函數來得到重疊操作完成的通知,那麼我們自然也需要一個函數來查詢一下重疊操作的結果,定義如下
BOOL WSAGetOverlappedResult(
                          SOCKET s,                   // SOCKET,不用說了
                          LPWSAOVERLAPPED lpOverlapped,  // 這裏是我們想要查詢結果的那個重疊結構的指針
                          LPDWORD lpcbTransfer,     // 本次重疊操作的實際接收(或發送)的字節數
                          BOOL fWait,                // 設置爲TRUE,除非重疊操作完成,否則函數不會返回,設置FALSE,而且操作仍處於掛起狀態,那麼函數就會返回FALSE,錯誤爲WSA_IO_INCOMPLETE, 不過因爲我們是等待事件傳信來通知我們操作完成,所以我們這裏設,置成什麼都沒有作用…..-_-b  別仍雞蛋啊,我也想說得清楚一些…
                          LPDWORD lpdwFlags       // 指向DWORD的指針,負責接收結果標誌
                        );
這個函數沒什麼難的,這裏我們也不需要去關注它的返回值,直接把參數填好調用就可以了,這裏就先不舉例了
唯一需要注意一下的就是如果WSAGetOverlappedResult完成以後,第三個參數返回是 0 ,則說明通信對方已經關閉連接,我們這邊的SOCKET, Event之類的也就可以關閉了。


實現重疊模型的步驟作了這麼多的準備工作,費了這麼多的筆墨,我們終於可以開始着手編碼了。其實慢慢的你就會明白,要想透析重疊結構的內部原理也許是要費點功夫,但是隻是學會如何來使用它,卻是真的不難,唯一需要理清思路的地方就是和大量的客戶端交互的情況下,我們得到事件通知以後,如何得知是哪一個重疊操作完成了,繼而知道究竟該對哪一個套接字進行處理,應該去哪個緩衝區中的取得數據,everything will be OK^_^。下面我們配合代碼,來一步步的講解如何親手完成一個重疊模型。
下面是我寫的一個例子,用的標準C\C++,可以直接編譯。
由於WSAWaitForMultipleEvents最多隻能同時等待64個消息,所以兩個線程最多支持64個連接,若要更多可以在開一個線程,達到128個連接。以此類推,成線性增長。
但線程過多的話,由於CPU忙於在線程上下文之間的切換,也會影響程序的性能,所以這種模式,還是不太適合非常多的連接數,如10000多個連接就不行了,這時,我們只能用後面的完成例程或完成端口了,這也正是它的弊端所在。

 

代碼
複製代碼
#pragma comment(lib,"ws2_32.lib")
#include
<winsock2.h>
#include
<stdio.h>
#define DATA_BUFSIZE 1024 // 接收緩衝區大小
SOCKET ListenSocket,
AcceptSocket[DATA_BUFSIZE]
= {0};
WSABUF DataBuf[DATA_BUFSIZE];
WSAOVERLAPPED Overlapped[DATA_BUFSIZE];
// 重疊結構
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS]; // 用來通知重疊操作完成的事件句柄數組
DWORD dwRecvBytes = 0, // 接收到的字符長度
Flags = 0; // WSARecv的參數
DWORD volatile dwEventTotal = 0; // 程序中事件的總數

//由於EVENT數量限制,目前最多隻能支持64個連接
DWORD WINAPI AcceptThread(LPVOID lpParameter)
{
WSADATA wsaData;
WSAStartup(MAKEWORD(
2,2),&wsaData);
ListenSocket
= WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,NULL,WSA_FLAG_OVERLAPPED);
SOCKADDR_IN ServerAddr;
ServerAddr.sin_family
= AF_INET;
ServerAddr.sin_addr.S_un.S_addr
= htonl(INADDR_ANY);
ServerAddr.sin_port
= htons(1234);
bind(ListenSocket,(LPSOCKADDR)
&ServerAddr,sizeof(ServerAddr));
listen(ListenSocket,
100);
printf(
"listenning...\n");
int i = 0;
SOCKADDR_IN ClientAddr;
int addr_length=sizeof(ClientAddr);
while (TRUE)
{
while((AcceptSocket[i] == 0) && (AcceptSocket[i] = accept(ListenSocket,(SOCKADDR*)&ClientAddr, &addr_length)) != INVALID_SOCKET)
{
printf(
"accept %d ip:%s port:%d\n",i+1,inet_ntoa(ClientAddr.sin_addr),ClientAddr.sin_port);
EventArray[i]
= WSACreateEvent();
dwEventTotal
++;
memset(
&Overlapped[i],0,sizeof(WSAOVERLAPPED));
Overlapped[i].hEvent
= EventArray[i];
char * buffer = new char[DATA_BUFSIZE];
memset(buffer,
0,DATA_BUFSIZE);
DataBuf[i].buf
= buffer;
DataBuf[i].len
= DATA_BUFSIZE;
if(WSARecv(AcceptSocket[i], &DataBuf[i], dwEventTotal, &dwRecvBytes, &Flags, &Overlapped[i], NULL) == SOCKET_ERROR)
{
int err = WSAGetLastError();
if(WSAGetLastError() != WSA_IO_PENDING)
{
printf(
"disconnect: %d\n",i+1);
closesocket(AcceptSocket[i]);
AcceptSocket[i]
= 0;
//WSACloseEvent(EventArray[i]); // 關閉事件
DataBuf[i].buf = NULL;
DataBuf[i].len
= NULL;
continue;
}
}
i
= (i+1)%WSA_MAXIMUM_WAIT_EVENTS;
}

}
return FALSE;
}

DWORD WINAPI ReceiveThread(LPVOID lpParameter)
{
DWORD dwIndex
= 0;
while (true)
{
dwIndex
= WSAWaitForMultipleEvents(dwEventTotal, EventArray, FALSE, 1000, FALSE);
if (dwIndex == WSA_WAIT_FAILED || dwIndex == WSA_WAIT_TIMEOUT)
continue;
dwIndex
= dwIndex - WSA_WAIT_EVENT_0;
WSAResetEvent(EventArray[dwIndex]);

DWORD dwBytesTransferred;
WSAGetOverlappedResult( AcceptSocket[dwIndex],
&Overlapped[dwIndex], &dwBytesTransferred, FALSE, &Flags);
if(dwBytesTransferred == 0)
{
printf(
"disconnect: %d\n",dwIndex+1);
closesocket(AcceptSocket[dwIndex]);
AcceptSocket[dwIndex]
= 0;
//WSACloseEvent(EventArray[dwIndex]); // 關閉事件
DataBuf[dwIndex].buf = NULL;
DataBuf[dwIndex].len
= NULL;
continue;
}
//使用數據
printf("%s\n",DataBuf[dwIndex].buf);
memset(DataBuf[dwIndex].buf,
0,DATA_BUFSIZE);
if(WSARecv(AcceptSocket[dwIndex], &DataBuf[dwIndex], dwEventTotal, &dwRecvBytes, &Flags, &Overlapped[dwIndex], NULL) == SOCKET_ERROR)
{
if(WSAGetLastError() != WSA_IO_PENDING)
{
printf(
"disconnect: %d\n",dwIndex+1);
closesocket(AcceptSocket[dwIndex]);
AcceptSocket[dwIndex]
= 0;
//WSACloseEvent(EventArray[dwIndex]); // 關閉事件
DataBuf[dwIndex].buf = NULL;
DataBuf[dwIndex].len
= NULL;
continue;
}
}
}

return FALSE;
}

void main()
{
HANDLE hThreads[
2];
hThreads[
0] = CreateThread(NULL, 0, AcceptThread, NULL, NULL, NULL);
hThreads[
1] = CreateThread(NULL, 0, ReceiveThread, NULL, NULL, NULL);

WaitForMultipleObjects(
2,hThreads,TRUE,INFINITE);
printf(
"exit\n");
CloseHandle(hThreads[
0]);
CloseHandle(hThreads[
1]);
}


本文來自CSDN博客,轉載請標明出處:http:
//blog.csdn.net/andylin02/archive/2008/10/12/3062717.aspx
複製代碼

 

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/andylin02/archive/2008/10/12/3062717.aspx

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