為什么要采用Socket模型,而不直接使用Socket?
原因源於recv()方法是堵塞式的,當多個客戶端連接服務器時,其中一個socket的recv調用時,會產生堵塞,使其他鏈接不能繼續。這樣我們又想到用多線程來實現,每個socket鏈接使用一個線程,這樣效率十分低下,根本不可能應對負荷較大的情況。於是便有了各種模型的解決方法,總之都是為了實現多個線程同時訪問時不產生堵塞。
完成端口(IOCP)模型:
首先來說為什么要使用完成端口:原因還是因為為了解決recv方法為阻塞式的問題,WinSocket封裝的WSARecv方法為非堵塞的方法。
int WSARecv(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
WSARecv為非阻塞的方法,其中第二個參數是I/O請求成功時,數據保存的地址。
Socket的觸發是屬於網卡硬件的中斷信號,只是此信號CPU不能直接獲取狀態,此時我們可以使之綁定Event事件,Event內核對象的狀態時可以監聽到的。這也就是WSAEventSelect模型的原理,當然重疊模型的最終原理也是如此。但Event的方法有着其弊病:當模型處理多線程事件時要調用WSAWaitForMultipleEvents函數,WSAWaitForMultipleEvents函數一次最多只能等待64個事件對象。所以當海量客戶端連接服務器時,服務器將沒有能力應對,於是我們使用完成端口。
完成端口:
HANDLE CreateIoCompletionPort(
HANDLE FileHandle, //要鏈接的Socket
HANDLE ExistingCompletionPort, //全局完成端口
//同完成端口關聯到一起的句柄,此處可為鏈接的socket,或是id等等(目地使接收到的socket知道是哪個socket)
DWORD CompletionKey,
DWORD NumberOfConcurrentThreads
);
此函數創建創建Socket與完成端口的鏈接,CreateIoCompletionPort函數被用於完成兩個工作:
- 用於創建—個完成端口對象。
- 將一個句柄同完成端口關聯到一起。
用函數GetQueuedCompletionStatus等待全局完成端口的完成隊列:
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytes,
PULONG_PTR lpCompletionKey, //此參數為CreateIoCompletionPort第三個參數傳過來的句柄,通過此參數獲得socket
LPOVERLAPPED* lpOverlapped,
DWORD dwMilliseconds
);
完成端口的工作原理是,把Socket和完成端口綁定,通過關聯句柄傳遞傳遞參數,使得獲取到的Socket能得知是那個socket,參數可以自定義可以是socket本身也可以是id等等。
#include "WinSock2.h" #pragma comment(lib, "ws2_32.lib") #define MESSAGESIZE 1024 SOCKET serverSocket; DWORD WINAPI SocketProcAccept(LPVOID pParam); DWORD WINAPI SocketProcMain(LPVOID pParam); enum SOCKETOPERATE { soREVC }; struct SOCKETDATA { WSAOVERLAPPED overlapped; WSABUF buf; char sMessage[MESSAGESIZE]; DWORD dwBytes; DWORD flag; SOCKETOPERATE socketType; void Clear(SOCKETOPERATE type) { ZeroMemory(this, sizeof(SOCKETDATA)); buf.buf = sMessage; buf.len = MESSAGESIZE; socketType = type; } }; SOCKET CreateServiceSocket(int Port) { int iError; WSAData data; iError = WSAStartup(0x0202, &data); SOCKET tmp = socket(AF_INET,SOCK_STREAM,0); if(tmp == INVALID_SOCKET) { return INVALID_SOCKET; } SOCKADDR_IN addr; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_family = AF_INET; addr.sin_port = htons(Port); if((bind(tmp, (sockaddr*)&addr, sizeof(addr))) != 0) { closesocket(tmp); return INVALID_SOCKET; } if((listen(tmp, INFINITE)) != 0) { closesocket(tmp); return INVALID_SOCKET; } return tmp; } int _tmain(int argc, _TCHAR* argv[]) { HANDLE CP = INVALID_HANDLE_VALUE; CP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); SYSTEM_INFO systemInfo; GetSystemInfo(&systemInfo); for (int i = 0; i<systemInfo.dwNumberOfProcessors; i++) { CreateThread(NULL, NULL, &SocketProcMain, CP, NULL, NULL); } serverSocket = CreateServiceSocket(6565); if (serverSocket == INVALID_SOCKET) { return 0; } CreateThread(NULL, NULL, &SocketProcAccept, CP, NULL, NULL); while(1) { Sleep(10000); } CloseHandle(CP); closesocket(serverSocket); WSACleanup(); return 0; } DWORD WINAPI SocketProcAccept(LPVOID pParam) { HANDLE CP = (HANDLE)pParam; SOCKADDR_IN addr; int len = sizeof(SOCKADDR_IN); SOCKET tmp; SOCKETDATA *lpSocketData; while(1) { tmp = accept(serverSocket, (sockaddr*)&addr, &len); printf("Client Accept:%s\t:%d\n", inet_ntoa(addr.sin_addr), htons(addr.sin_port)); CreateIoCompletionPort((HANDLE)tmp, CP, (DWORD)tmp, INFINITE); lpSocketData = (SOCKETDATA *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SOCKETDATA)); lpSocketData->Clear(soREVC); WSARecv(tmp, &lpSocketData->buf, 1,&lpSocketData->dwBytes, &lpSocketData->flag, &lpSocketData->overlapped, NULL); } } DWORD WINAPI SocketProcMain(LPVOID pParam) { HANDLE CP = (HANDLE)pParam; SOCKADDR_IN addr; DWORD dwBytes; SOCKETDATA *lpSocketData; SOCKET clientSocket; while(1) { GetQueuedCompletionStatus(CP, &dwBytes, (PULONG_PTR)&clientSocket, (LPOVERLAPPED*)&lpSocketData, INFINITE); if(dwBytes == 0xFFFFFFFF) { return 0; } if(lpSocketData->socketType == soREVC) { if(dwBytes == 0) { closesocket(clientSocket); HeapFree(GetProcessHeap(), 0, lpSocketData); }
else { lpSocketData->sMessage[dwBytes] = '\0'; printf("%x\t:%s\n", (DWORD)clientSocket, lpSocketData->sMessage); lpSocketData->Clear(soREVC); WSARecv(clientSocket, &lpSocketData->buf, 1, &lpSocketData->dwBytes, &lpSocketData->flag, &lpSocketData->overlapped, NULL); } } } }