來自微軟的完成端口例子,就講解一下它的使用套路吧
反正編程這個玩意,只要用過,自然就知道什么回事,一次不會再看一次,學習這個玩意,無他,勤奮而已。
奢談效率等等,那只是孰能生巧上的功夫。
這個例子是在console下的例子,算是一個echo服務器吧,
跑起來后將在5150端口監聽,一旦有個端口連接上來,發個數據給服務端口,它就echo回數據給那個端口. 直到那個連接中斷.
完成端口,其實理解成一個通道或管子就可以了,和管道也差不了多少,不過可以實現異步處理罷了,
你這邊往管子里丟數據,通過GetQueuedCompletionStatus來查管子那頭出數據沒,出了就處理,這個管子就是通過一個自定義有點特殊的結構來寫入或讀出數據而已.
那個完成端口,其實就相當是標識那個數據塊的句柄,
//下面請看例子
#include <winsock2.h>
#include
<windows.h>
#include <stdio.h>
#define PORT 5150
#define DATA_BUFSIZE 8192
#pragma comment(lib, "Ws2_32")
typedef
struct
//這個玩意就是灌數據,取數據的一個自定義數據結構
//和那個wm_data差不了多少,不過就是老要塞一個OverLapped結構,
{
OVERLAPPED
Overlapped;
WSABUF DataBuf;
CHAR
Buffer[DATA_BUFSIZE];
DWORD
BytesSEND;
//發送字節數
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次,這是第一次調用,至于為什么?我問問你
//
if
((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) ==
NULL)
{
printf(
"CreateIoCompletionPort failed with error: %d\n",
GetLastError());
return;
}
//老套子api,不談也罷
GetSystemInfo(&SystemInfo);
//發現2個CPU,那就開個雙倍的線程跑吧
for(i = 0; i <
SystemInfo.dwNumberOfProcessors * 2; i++)
{
HANDLE
ThreadHandle;
//
//完成端口掛到線程上面來了,就像管子把灌數據的和讀數據的兩頭都連上了,
//
if
((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread,
CompletionPort,
0,
&ThreadID)) == NULL)
{
printf("CreateThread()
failed with error %d\n",
GetLastError());
return;
}
CloseHandle(ThreadHandle);
}
//
//啟動一個監聽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;
}
//
//
監聽端口打開,就開始在這里循環,一有socket連上,WSAAccept就創建一個socket,
// 這個socket
又和完成端口聯上,
//
//
嘿嘿,完成端口第二次調用那個createxxx函數,為什么,留給人思考思考可能更深刻,
//
反正這套路得來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連起來
//就像你把漏斗接到管子口上,開始要灌數據了
//
if (CreateIoCompletionPort((HANDLE) Accept,
CompletionPort, (DWORD)
PerHandleData,
0) ==
NULL)
{
printf("CreateIoCompletionPort failed with error %d\n",
GetLastError());
return;
}
//
//清管子的數據結構,準備往里面灌數據
//
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接到了數據,就放到PerIoData中,而perIoData又通過線程中的函數取出,
//
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;
}
}
}
}
//
//線程一但調用,就老在里面循環,
// 注意,傳入的可是完成端口啊,就是靠它去取出管子中的數據
//
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)
{
//
//在這里檢查完成端口部分的數據buf區,數據來了嗎?
//
這個函數參數要看說明,
// PerIoData
就是從管子流出來的數據,
//PerHandleData
也是從管子里取出的,是何時塞進來的,
//就是在建立第2次createIocompletionPort時
//
if (GetQueuedCompletionStatus(CompletionPort,
&BytesTransferred,
(LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) ==
0)
{
printf("GetQueuedCompletionStatus failed with error %d\n",
GetLastError());
return
0;
}
// 檢查數據傳送完了嗎
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;
}
//
//看看管子里面有數據來了嗎?=0,那是剛收到數據
//
if (PerIoData->BytesRECV ==
0)
{
PerIoData->BytesRECV =
BytesTransferred;
PerIoData->BytesSEND = 0;
}
else
//來了,
{
PerIoData->BytesSEND +=
BytesTransferred;
}
//
//
數據沒發完?繼續send出去
//
if
(PerIoData->BytesRECV >
PerIoData->BytesSEND)
{
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
//清0為發送準備
PerIoData->DataBuf.buf = PerIoData->Buffer +
PerIoData->BytesSEND;
PerIoData->DataBuf.len = PerIoData->BytesRECV -
PerIoData->BytesSEND;
//1個字節一個字節發送發送數據出去
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;
}
}
}
}
}