重疊I/O也是一種異步I/O,同樣也支持Win32的其它對(duì)象,當(dāng)然在Winsock中可以發(fā)揮很大的作用。使用Overlapped開發(fā)支持一定數(shù)量的Socket的應(yīng)用,效率是相當(dāng)很高的。但就我個(gè)人的觀點(diǎn),在Win32下做網(wǎng)絡(luò)應(yīng)用的開發(fā),如果要支持100個(gè)以上的Socket的話,還是考慮Completion Port I/O。要求支持Socket最好是100個(gè)以下,我是基于這樣考慮的:Overlapped是通過多線程支持多Socket的,如果開辟的線程太多的話,勢必影響了系統(tǒng)的性能;Completion Port I/O可以更好支持大量的客戶端。這兩種在Windows下具有高效率的I/O都不支持Windows CE及其它平臺(tái)。我在這里說一下在開發(fā)網(wǎng)絡(luò)應(yīng)用時(shí)什么時(shí)候用Overlapped I/O 模型:準(zhǔn)備在Win98和以上版本或WinNT3.1和以上版本做開發(fā),且要求支持Socket最好在100個(gè)以下。另外在串口開發(fā)中,考慮效率問題,有很多地方用到了Overlapped I/O。
Overlapped I/O主要涉及一個(gè)數(shù)據(jù)結(jié)構(gòu)Overlapped(Winsock中是WSAOverlapped)和一個(gè)函數(shù)WSAGetOverlappedResult(..)。
Overlapped I/O執(zhí)行步驟很清晰,只要下面三步:
(1) 應(yīng)用先通過WSASend或WSARecv(不知道有沒有其它的請(qǐng)求,我只用過這兩個(gè)函數(shù)),注意要向兩者轉(zhuǎn)入WSAOverlapped參數(shù),
表示,執(zhí)行的是Overlapped操作;
(2)在一個(gè)循環(huán)中,調(diào)用GetOverlappedResult(..)等待操作完成,GetOverlappedResult返回時(shí),進(jìn)行相應(yīng)的處理,如處理數(shù)據(jù);
(3) 最后,還在(2)循環(huán)中,發(fā)送另外一個(gè)請(qǐng)求(WSASend或WSARecv),重復(fù)處理(2)、(3)兩步。
第一步中執(zhí)行WSASend或WSARecv時(shí),函數(shù)立即返回,得到SOCKET_ERROR信息且此時(shí)WSAGetErrorLast返回WSA_IO_PENDING,說明調(diào)用已成功,Winsock正在處理WSASend或WSARecv的請(qǐng)求。個(gè)人認(rèn)為Winsock在內(nèi)部開辟了新的線程處理,應(yīng)用程序不用管理多線程,達(dá)到異步的目的,有利于性能的提高。WSASend或WSARecv也可能返回"0",表示立即成功,這時(shí),應(yīng)用還是可以在WSAGetOverlappedResult()處等待,處理過程與上面是一樣的;也就是說我們不須要馬上在WSASend或WSARecv進(jìn)行相關(guān)的處理。
WSAGetOverlappedResult返回FASLE且WSAGetLastError返回WSA_IO_INCOMPLETE,表示處理正在進(jìn)行中。
下面我給出支持單個(gè)Socket及支持多個(gè)Socket的Console程序代碼。先來看看支持單個(gè)Socket的程序,考慮到代碼簡潔性,只給一個(gè)框架,同時(shí)不進(jìn)行出錯(cuò)處理。
int main()
{
WSAOVERLAPPED Overlapped;
// 啟動(dòng)Winsock及建立監(jiān)聽套接字
listen(hListenSocket, 5);
hClientSocket = accept(hListenSocket, NULL, NULL);
ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
nResult = WSARecv(...); // 發(fā)出請(qǐng)求
for (; ;)
{
bResult = WSAGetOverlappedResult(...);
// 函數(shù)返回后進(jìn)行相應(yīng)的處理
nResult = WSARecv(...); // 發(fā)出另外一個(gè)請(qǐng)求
}
}
上面的程序只是想說明一下過程,程序沒有實(shí)現(xiàn)什么功能。這樣做的目的是節(jié)約字?jǐn)?shù),用來說明我下面支持多個(gè)Socket的Console應(yīng)用。請(qǐng)繼續(xù)看。
先看一個(gè)自定義的結(jié)構(gòu)體,單句柄數(shù)據(jù)結(jié)構(gòu),通過該結(jié)構(gòu),主線程與某個(gè)特定的子線程中的套接字相互聯(lián)系。
typedef struct _PER_HANDLE_DATA
{
SOCKET hSocket; // 主鍵:通信套接字
char szClientIP[16];// 自定義字段:客戶端地址
int nOperateType; // 自定義字段:操作類型
}PER_HANDLE_DATA, FAR* LPPER_HANDLE_DATA;
在上面的結(jié)構(gòu)中還可以加入自己需要的字段。在我下面的例子程序中,szClientIP是用來保存連接上來的客戶端的IP的,這樣在主線程將這個(gè)結(jié)構(gòu)體傳給子線程后,在子線程中根據(jù)客戶端IP就知道目前處理的是哪個(gè)客戶端了。下面是程序的大部分,同樣除去一些簡單的出錯(cuò)輸出。
#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));
// 為本次客戶請(qǐng)求產(chǎn)生子線程
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結(jié)構(gòu)的指針
1, // WSABUF數(shù)組的個(gè)數(shù)
&dwNumOfBytesRecved, // 存放當(dāng)WSARecv完成后所接收到的字節(jié)數(shù)
&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, // 當(dāng)一個(gè)同步I/O完成后,接收到的字節(jié)數(shù)
TRUE, // 等待I/O操作的完成
&dwFlags
);
if (bResult == FALSE && WSAGetLastError() != WSA_IO_INCOMPLETE)
{
printf("WSAGetOverlappdResult(..) failed.\n");
free(lpPerHandleData);
return 1; // 錯(cuò)誤退出
}
if (dwBytesTransferred == 0)
{
printf("客戶端已退出,將斷開與之的連接!\n");
closesocket(lpPerHandleData->hSocket);
free(lpPerHandleData);
break;
}
// 在這里將接收到的數(shù)據(jù)進(jìn)行處理
printf("Received from IP: %s.\ndata: %s\n", lpPerHandleData->szClientIP, wsaBuf.buf);
// 發(fā)送另外一個(gè)請(qǐng)求操作
ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
lpPerHandleData->nOperateType = POST_RECV;
dwFlags = 0;
nResult = WSARecv(...);
if (...){}
}
return 0;
}
程序結(jié)構(gòu)比較清晰,lpPerHandleData是主線程與子線程聯(lián)系的紐帶,子線程是通過這個(gè)結(jié)構(gòu)獲得所處理客戶端的情況的。在不同的應(yīng)用中可以將這個(gè)結(jié)構(gòu)定義成不同的形式,以符合所實(shí)現(xiàn)應(yīng)用的需要。注意結(jié)構(gòu)體的nOperateType在GetOverlappedResult返回時(shí)用到,可以根據(jù)這個(gè)字段確定我們下一步的操作。請(qǐng)朋友們多提意見了。
posted on 2007-04-10 18:06
jay 閱讀(3140)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
socket