來自微軟的完成端口例子,就講解一下它的使用套路吧
反正編程這個玩意,只要用過,自然就知道什么回事,一次不會再看一次,學(xué)習(xí)這個玩意,無他,勤奮而已。
奢談效率等等,那只是孰能生巧上的功夫。
這個例子是在console下的例子,算是一個echo服務(wù)器吧,
跑起來后將在5150端口監(jiān)聽,一旦有個端口連接上來,發(fā)個數(shù)據(jù)給服務(wù)端口,它就echo回數(shù)據(jù)給那個端口. 直到那個連接中斷.
完成端口,其實理解成一個通道或管子就可以了,和管道也差不了多少,不過可以實現(xiàn)異步處理罷了,
你這邊往管子里丟數(shù)據(jù),通過GetQueuedCompletionStatus來查管子那頭出數(shù)據(jù)沒,出了就處理,這個管子就是通過一個自定義有點特殊的結(jié)構(gòu)來寫入或讀出數(shù)據(jù)而已.
那個完成端口,其實就相當(dāng)是標(biāo)識那個數(shù)據(jù)塊的句柄,
//下面請看例子
#include <winsock2.h>
#include
<windows.h>
#include <stdio.h>
#define PORT 5150
#define DATA_BUFSIZE 8192
#pragma comment(lib, "Ws2_32")
typedef
struct
//這個玩意就是灌數(shù)據(jù),取數(shù)據(jù)的一個自定義數(shù)據(jù)結(jié)構(gòu)
//和那個wm_data差不了多少,不過就是老要塞一個OverLapped結(jié)構(gòu),
{
OVERLAPPED
Overlapped;
WSABUF DataBuf;
CHAR
Buffer[DATA_BUFSIZE];
DWORD
BytesSEND;
//發(fā)送字節(jié)數(shù)
DWORD
BytesRECV;
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
typedef struct
{
SOCKET Socket;
} PER_HANDLE_DATA,
* LPPER_HANDLE_DATA;
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);
void main(void)
{
SOCKADDR_IN
InternetAddr;
SOCKET Listen;
SOCKET
Accept;
HANDLE CompletionPort;
SYSTEM_INFO
SystemInfo;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerIoData;
int i;
DWORD
RecvBytes;
DWORD Flags;
DWORD
ThreadID;
WSADATA wsaData;
DWORD Ret;
if ((Ret = WSAStartup(0x0202, &wsaData)) !=
0)
{
printf("WSAStartup failed
with error %d\n", Ret);
return;
}
//
//完成端口的建立得搞2次,這是第一次調(diào)用,至于為什么?我問問你
//
if
((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) ==
NULL)
{
printf(
"CreateIoCompletionPort failed with error: %d\n",
GetLastError());
return;
}
//老套子api,不談也罷
GetSystemInfo(&SystemInfo);
//發(fā)現(xiàn)2個CPU,那就開個雙倍的線程跑吧
for(i = 0; i <
SystemInfo.dwNumberOfProcessors * 2; i++)
{
HANDLE
ThreadHandle;
//
//完成端口掛到線程上面來了,就像管子把灌數(shù)據(jù)的和讀數(shù)據(jù)的兩頭都連上了,
//
if
((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread,
CompletionPort,
0,
&ThreadID)) == NULL)
{
printf("CreateThread()
failed with error %d\n",
GetLastError());
return;
}
CloseHandle(ThreadHandle);
}
//
//啟動一個監(jiān)聽socket ,以下都是長長的交代
//
if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL,
0,
WSA_FLAG_OVERLAPPED)) ==
INVALID_SOCKET)
{
printf("WSASocket() failed with error %d\n",
WSAGetLastError());
return;
}
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(PORT);
if (bind(Listen, (PSOCKADDR) &InternetAddr,
sizeof(InternetAddr)) == SOCKET_ERROR)
{
printf("bind() failed with error %d\n",
WSAGetLastError());
return;
}
if (listen(Listen, 5) == SOCKET_ERROR)
{
printf("listen() failed with error %d\n",
WSAGetLastError());
return;
}
//
//
監(jiān)聽端口打開,就開始在這里循環(huán),一有socket連上,WSAAccept就創(chuàng)建一個socket,
// 這個socket
又和完成端口聯(lián)上,
//
//
嘿嘿,完成端口第二次調(diào)用那個createxxx函數(shù),為什么,留給人思考思考可能更深刻,
//
反正這套路得來2次,
// 完成端口completionport和accept
socket掛起來了,
//
while(TRUE)
{
//主線程跑到這里就等啊等啊,但是線程卻開工了,
if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) ==
SOCKET_ERROR)
{
printf("WSAAccept() failed
with error %d\n",
WSAGetLastError());
return;
}
if ((PerHandleData = (LPPER_HANDLE_DATA)
GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA))) ==
NULL)
{
printf("GlobalAlloc()
failed with error %d\n",
GetLastError());
return;
}
PerHandleData->Socket = Accept;
//
//把這頭和完成端口completionPort連起來
//就像你把漏斗接到管子口上,開始要灌數(shù)據(jù)了
//
if (CreateIoCompletionPort((HANDLE) Accept,
CompletionPort, (DWORD)
PerHandleData,
0) ==
NULL)
{
printf("CreateIoCompletionPort failed with error %d\n",
GetLastError());
return;
}
//
//清管子的數(shù)據(jù)結(jié)構(gòu),準(zhǔn)備往里面灌數(shù)據(jù)
//
if ((PerIoData = (LPPER_IO_OPERATION_DATA)
GlobalAlloc(GPTR,sizeof(PER_IO_OPERATION_DATA))) ==
NULL)
{
printf("GlobalAlloc()
failed with error %d\n",
GetLastError());
return;
}
ZeroMemory(&(PerIoData->Overlapped),
sizeof(OVERLAPPED));
PerIoData->BytesSEND =
0;
PerIoData->BytesRECV =
0;
PerIoData->DataBuf.len =
DATA_BUFSIZE;
PerIoData->DataBuf.buf =
PerIoData->Buffer;
Flags = 0;
//
//
accept接到了數(shù)據(jù),就放到PerIoData中,而perIoData又通過線程中的函數(shù)取出,
//
if (WSARecv(Accept,
&(PerIoData->DataBuf), 1, &RecvBytes,
&Flags,
&(PerIoData->Overlapped), NULL) ==
SOCKET_ERROR)
{
if (WSAGetLastError() !=
ERROR_IO_PENDING)
{
printf("WSARecv() failed with error %d\n",
WSAGetLastError());
return;
}
}
}
}
//
//線程一但調(diào)用,就老在里面循環(huán),
// 注意,傳入的可是完成端口啊,就是靠它去取出管子中的數(shù)據(jù)
//
DWORD
WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
HANDLE
CompletionPort = (HANDLE) CompletionPortID;
DWORD BytesTransferred;
LPOVERLAPPED Overlapped;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA
PerIoData;
DWORD SendBytes, RecvBytes;
DWORD Flags;
while(TRUE)
{
//
//在這里檢查完成端口部分的數(shù)據(jù)buf區(qū),數(shù)據(jù)來了嗎?
//
這個函數(shù)參數(shù)要看說明,
// PerIoData
就是從管子流出來的數(shù)據(jù),
//PerHandleData
也是從管子里取出的,是何時塞進來的,
//就是在建立第2次createIocompletionPort時
//
if (GetQueuedCompletionStatus(CompletionPort,
&BytesTransferred,
(LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) ==
0)
{
printf("GetQueuedCompletionStatus failed with error %d\n",
GetLastError());
return
0;
}
// 檢查數(shù)據(jù)傳送完了嗎
if (BytesTransferred == 0)
{
printf("Closing socket
%d\n", PerHandleData->Socket);
if
(closesocket(PerHandleData->Socket) ==
SOCKET_ERROR)
{
printf("closesocket() failed with error %d\n",
WSAGetLastError());
return 0;
}
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
//
//看看管子里面有數(shù)據(jù)來了嗎?=0,那是剛收到數(shù)據(jù)
//
if (PerIoData->BytesRECV ==
0)
{
PerIoData->BytesRECV =
BytesTransferred;
PerIoData->BytesSEND = 0;
}
else
//來了,
{
PerIoData->BytesSEND +=
BytesTransferred;
}
//
//
數(shù)據(jù)沒發(fā)完?繼續(xù)send出去
//
if
(PerIoData->BytesRECV >
PerIoData->BytesSEND)
{
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
//清0為發(fā)送準(zhǔn)備
PerIoData->DataBuf.buf = PerIoData->Buffer +
PerIoData->BytesSEND;
PerIoData->DataBuf.len = PerIoData->BytesRECV -
PerIoData->BytesSEND;
//1個字節(jié)一個字節(jié)發(fā)送發(fā)送數(shù)據(jù)出去
if
(WSASend(PerHandleData->Socket, &(PerIoData->DataBuf), 1,
&SendBytes,
0,
&(PerIoData->Overlapped), NULL) ==
SOCKET_ERROR)
{
if
(WSAGetLastError() !=
ERROR_IO_PENDING)
{
printf("WSASend() failed with error %d\n",
WSAGetLastError());
return 0;
}
}
}
else
{
PerIoData->BytesRECV =
0;
Flags =
0;
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->DataBuf.len =
DATA_BUFSIZE;
PerIoData->DataBuf.buf = PerIoData->Buffer;
if
(WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1,
&RecvBytes,
&Flags,
&(PerIoData->Overlapped), NULL) ==
SOCKET_ERROR)
{
if
(WSAGetLastError() !=
ERROR_IO_PENDING)
{
printf("WSARecv() failed with error %d\n",
WSAGetLastError());
return 0;
}
}
}
}
}