重疊I/O也是一種異步I/O,同樣也支持Win32的其它對象,當然在Winsock中可以發揮很大的作用。使用Overlapped開發支持一定數量的Socket的應用,效率是相當很高的。但就我個人的觀點,在Win32下做網絡應用的開發,如果要支持100個以上的Socket的話,還是考慮Completion Port I/O。要求支持Socket最好是100個以下,我是基于這樣考慮的:Overlapped是通過多線程支持多Socket的,如果開辟的線程太多的話,勢必影響了系統的性能;Completion Port I/O可以更好支持大量的客戶端。這兩種在Windows下具有高效率的I/O都不支持Windows CE及其它平臺。我在這里說一下在開發網絡應用時什么時候用Overlapped I/O 模型:準備在Win98和以上版本或WinNT3.1和以上版本做開發,且要求支持Socket最好在100個以下。另外在串口開發中,考慮效率問題,有很多地方用到了Overlapped I/O。
Overlapped I/O主要涉及一個數據結構Overlapped(Winsock中是WSAOverlapped)和一個函數WSAGetOverlappedResult(..)。
Overlapped I/O執行步驟很清晰,只要下面三步:
(1) 應用先通過WSASend或WSARecv(不知道有沒有其它的請求,我只用過這兩個函數),注意要向兩者轉入WSAOverlapped參數,
表示,執行的是Overlapped操作;
(2)在一個循環中,調用GetOverlappedResult(..)等待操作完成,GetOverlappedResult返回時,進行相應的處理,如處理數據;
(3) 最后,還在(2)循環中,發送另外一個請求(WSASend或WSARecv),重復處理(2)、(3)兩步。
第一步中執行WSASend或WSARecv時,函數立即返回,得到SOCKET_ERROR信息且此時WSAGetErrorLast返回WSA_IO_PENDING,說明調用已成功,Winsock正在處理WSASend或WSARecv的請求。個人認為Winsock在內部開辟了新的線程處理,應用程序不用管理多線程,達到異步的目的,有利于性能的提高。WSASend或WSARecv也可能返回"0",表示立即成功,這時,應用還是可以在WSAGetOverlappedResult()處等待,處理過程與上面是一樣的;也就是說我們不須要馬上在WSASend或WSARecv進行相關的處理。
WSAGetOverlappedResult返回FASLE且WSAGetLastError返回WSA_IO_INCOMPLETE,表示處理正在進行中。
下面我給出支持單個Socket及支持多個Socket的Console程序代碼。先來看看支持單個Socket的程序,考慮到代碼簡潔性,只給一個框架,同時不進行出錯處理。
int main()
{
WSAOVERLAPPED Overlapped;
// 啟動Winsock及建立監聽套接字
listen(hListenSocket, 5);
hClientSocket = accept(hListenSocket, NULL, NULL);
ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
nResult = WSARecv(...); // 發出請求
for (; ;)
{
bResult = WSAGetOverlappedResult(...);
// 函數返回后進行相應的處理
nResult = WSARecv(...); // 發出另外一個請求
}
}
上面的程序只是想說明一下過程,程序沒有實現什么功能。這樣做的目的是節約字數,用來說明我下面支持多個Socket的Console應用。請繼續看。
先看一個自定義的結構體,單句柄數據結構,通過該結構,主線程與某個特定的子線程中的套接字相互聯系。
typedef struct _PER_HANDLE_DATA
{
SOCKET hSocket; // 主鍵:通信套接字
char szClientIP[16];// 自定義字段:客戶端地址
int nOperateType; // 自定義字段:操作類型
}PER_HANDLE_DATA, FAR* LPPER_HANDLE_DATA;
在上面的結構中還可以加入自己需要的字段。在我下面的例子程序中,szClientIP是用來保存連接上來的客戶端的IP的,這樣在主線程將這個結構體傳給子線程后,在子線程中根據客戶端IP就知道目前處理的是哪個客戶端了。下面是程序的大部分,同樣除去一些簡單的出錯輸出。
#define LISTEN_PORT 5000
#define DATA_BUFSIZE 8192
#define POST_RECV 0X01
#define POST_SEND 0X02
int main( )
{
LPPER_HANDLE_DATA lpPerHandleData;
SOCKET hListenSocket;
SOCKET hClientSocket;
SOCKADDR_IN ClientAddr;
int nAddrLen;
HANDLE hThread;
// Start WinSock and create a listen socket.
listen(hListenSocket, 5);
for (; ;)
{
nAddrLen = sizeof(SOCKADDR);
hClientSocket = accept(hListenSocket, (LPSOCKADDR)&ClientAddr, &nAddrLen);
lpPerHandleData = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
lpPerHandleData->hSocket = hClientSocket;
// 注意這里將連接的客戶端的IP地址,保存到了lpPerHandleData字段中了
strcpy(lpPerHandleData->szClientIP, inet_ntoa(ClientAddr.sin_addr));
// 為本次客戶請求產生子線程
hThread = CreateThread(
NULL,
0,
OverlappedThread,
lpPerHandleData, // 將lpPerHandleData傳給子線程
0,
NULL
);
CloseHandle(hThread);
}
return 0;
}
DWORD WINAPI OverlappedThread(LPVOID lpParam)
{
LPPER_HANDLE_DATA lpPerHandleData = (LPPER_HANDLE_DATA)lpParam;
WSAOVERLAPPED Overlapped;
WSABUF wsaBuf;
char Buffer[DATA_BUFSIZE];
BOOL bResult;
int nResult;
ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
wsaBuf.buf = Buffer;
wsaBuf.len = sizeof(Buffer);
lpPerHandleData->nOperateType = POST_RECV; // 記錄本次操作是Recv(..)
dwFlags = 0;
nResult = WSARecv(
lpPerHandleData->hSocket, // Receive socket
&wsaBuf, // 指向WSABUF結構的指針
1, // WSABUF數組的個數
&dwNumOfBytesRecved, // 存放當WSARecv完成后所接收到的字節數
&dwFlags, // A pointer to flags
&Overlapped, // A pointer to a WSAOVERLAPPED structure
NULL // A pointer to the completion routine,this is NULL
);
if ( nResult == SOCKET_ERROR && GetLastError() != WSA_IO_PENDING)
{
printf("WSARecv(..) failed.\n");
free(lpPerHandleData);
return 1;
}
while (TRUE)
{
bResult = WSAGetOverlappedResult(
lpPerHandleData->hSocket,
&Overlapped,
&dwBytesTransferred, // 當一個同步I/O完成后,接收到的字節數
TRUE, // 等待I/O操作的完成
&dwFlags
);
if (bResult == FALSE && WSAGetLastError() != WSA_IO_INCOMPLETE)
{
printf("WSAGetOverlappdResult(..) failed.\n");
free(lpPerHandleData);
return 1; // 錯誤退出
}
if (dwBytesTransferred == 0)
{
printf("客戶端已退出,將斷開與之的連接!\n");
closesocket(lpPerHandleData->hSocket);
free(lpPerHandleData);
break;
}
// 在這里將接收到的數據進行處理
printf("Received from IP: %s.\ndata: %s\n", lpPerHandleData->szClientIP, wsaBuf.buf);
// 發送另外一個請求操作
ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
lpPerHandleData->nOperateType = POST_RECV;
dwFlags = 0;
nResult = WSARecv(...);
if (...){}
}
return 0;
}
程序結構比較清晰,lpPerHandleData是主線程與子線程聯系的紐帶,子線程是通過這個結構獲得所處理客戶端的情況的。在不同的應用中可以將這個結構定義成不同的形式,以符合所實現應用的需要。注意結構體的nOperateType在GetOverlappedResult返回時用到,可以根據這個字段確定我們下一步的操作。請朋友們多提意見了。
posted on 2007-04-10 18:06
jay 閱讀(3141)
評論(0) 編輯 收藏 引用 所屬分類:
socket