1. 程式人生 > >網路伺服器程式設計——完成埠

網路伺服器程式設計——完成埠

4.3.5完成埠模型(IOCP)

選擇模型是5種模型中效率最低的,而完成埠則是5種模型中效率最高的IO模型。

//完成埠TCP伺服器

#include <iostream>

#include <winsock2.h>

#include <windows.h>

#include <process.h>

 

#pragma comment (lib, "Ws2_32.lib")

using namespace std;

 

#define PORT 6000

#define SIZE 1024

//建立單IO結構體

typedef struct

{

WSAOVERLAPPED overlap; //每一個socket連線需要關聯一個WSAOVERLAPPED物件

WSABUF Buffer; //與WSAOVERLAPPED物件繫結的緩衝區

char szMessage[SIZE]; //初始化buffer的緩衝區

DWORD NumberOfBytesRecvd; //指定接收到的字元的數目

DWORD Flags;

}MY_WSAOVERLAPPED, *LPMY_WSAOVERLAPPED;

 

UINT WINAPI WorkerThread(LPVOID lpParameter);

 

int main(int argc,char ** argv)

{

//步驟1:當前應用程式和相應的socket庫繫結

WORD wVersionRequested;

WSADATA wsaData;

int err;

wVersionRequested = MAKEWORD

(2, 2);

err = WSAStartup(wVersionRequested, &wsaData);

if (err != 0)

{

cout << "WSAStartup Failed!" << endl;

return -1;

}

 

//步驟2:建立完成埠

HANDLE h_CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

 

//步驟3:根據系統中CPU核心的數量建立對應的Worker執行緒

SYSTEM_INFO systeminfo;

GetSystemInfo(&systeminfo);

unsigned int thread_id = 0;

for (int i = 0; i < systeminfo.dwNumberOfProcessors*2; i++)//建立CPU核心數量*2的執行緒,可以充分利用CPU資源

{

_beginthreadex(NULL, 0, WorkerThread, h_CompletionPort, 0, &thread_id);

}

 

//步驟4:建立監聽套接字和伺服器端IP/PORT

//SOCKET sockListen = WSASocket(AF_INET, SOCK_STREAM, 0,NULL,0, WSA_FLAG_OVERLAPPED);

SOCKET sockListen = socket(AF_INET, SOCK_STREAM, 0);

SOCKADDR_IN addr_server;

memset(&addr_server, 0, sizeof(addr_server));

addr_server.sin_family = AF_INET;

addr_server.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY表示繫結電腦上所有網絡卡IP

addr_server.sin_port = htons(PORT);//不能使用公認埠,即埠>= 1024

 

//步驟5:套接字繫結和監聽

bind(sockListen, (SOCKADDR*)&addr_server, sizeof(addr_server));

listen(sockListen, 5);

cout << "Start Listen..." << endl;

 

SOCKADDR_IN addr_client;

int len = sizeof(SOCKADDR);

SOCKET sockClient;

LPMY_WSAOVERLAPPED lp_OVERLAPPED;

 

while (1)

{

//步驟6:等待客戶端連線

sockClient = accept(sockListen, (struct sockaddr *)&addr_client, &len);

printf("Accepted Client IP:%s,PORT:%d\n", inet_ntoa(addr_client.sin_addr), ntohs(addr_client.sin_port));

 

//步驟7:完成埠和套接字繫結

CreateIoCompletionPort((HANDLE)sockClient, h_CompletionPort, (DWORD)sockClient, 0);

 

//步驟8:分配一個單IO資料結構

lp_OVERLAPPED = (LPMY_WSAOVERLAPPED)HeapAlloc(

GetProcessHeap(),

HEAP_ZERO_MEMORY,

sizeof(MY_WSAOVERLAPPED));

 

//步驟9:初始化單IO資料結構

lp_OVERLAPPED->Buffer.len = SIZE;//接收緩衝區的長度

lp_OVERLAPPED->Buffer.buf = lp_OVERLAPPED->szMessage;//接收緩衝區

 

//步驟10:接收資料

WSARecv(

sockClient,//接收套接字

&lp_OVERLAPPED->Buffer,//接收緩衝區

1,

&lp_OVERLAPPED->NumberOfBytesRecvd,//操作完成,接收資料的位元組數

&lp_OVERLAPPED->Flags,

&lp_OVERLAPPED->overlap,//指向WSAOVERLAPPED結構指標

NULL);

}

 

//步驟13:喚醒工作者執行緒

PostQueuedCompletionStatus(h_CompletionPort, 0xFFFFFFFF, 0, NULL);

 

//步驟14:關閉套接字和庫解綁

CloseHandle(h_CompletionPort);

closesocket(sockListen);

WSACleanup();

return 0;

}

 

UINT WINAPI WorkerThread(LPVOID lpParameter)

{

HANDLE h_CompletionPort = (HANDLE)lpParameter;

DWORD NumberOfBytesTransferred;

SOCKET sockClient;

LPMY_WSAOVERLAPPED lpWSAOVERLAPPED = NULL;

 

 

while (1)

{

//步驟11:獲取完成埠的狀態。若完成端口出現已完成的IO請求,則執行緒被喚醒;否則繼續睡眠

GetQueuedCompletionStatus(

h_CompletionPort,

&NumberOfBytesTransferred,

(LPDWORD)&sockClient,

(LPOVERLAPPED*)&lpWSAOVERLAPPED,

INFINITE);

 

//步驟12:資料處理

if (NumberOfBytesTransferred == 0xFFFFFFFF)

{

return 0;

}

 

if (NumberOfBytesTransferred == 0)

{

cout << "客戶端斷開連線" << endl;

closesocket(sockClient);

HeapFree(GetProcessHeap(), 0, lpWSAOVERLAPPED);

}

else

{

//二次開發

char Buf[SIZE] = "\0";

strcpy_s(Buf, 1024, lpWSAOVERLAPPED->szMessage);

cout << Buf << endl;

strcat(Buf, ":Server Received");

send(sockClient, Buf, strlen(Buf) + 1, 0);

 

memset(lpWSAOVERLAPPED, 0, sizeof(MY_WSAOVERLAPPED));

lpWSAOVERLAPPED->Buffer.len = SIZE;

lpWSAOVERLAPPED->Buffer.buf = lpWSAOVERLAPPED->szMessage;

 

WSARecv(sockClient,

&lpWSAOVERLAPPED->Buffer,

1,

&lpWSAOVERLAPPED->NumberOfBytesRecvd,

&lpWSAOVERLAPPED->Flags,

&lpWSAOVERLAPPED->overlap,

NULL);

}

}

return 0;

}

//完成埠TCP客戶端

#include <WinSock2.h>

#include <iostream>

 

#pragma comment(lib, "ws2_32.lib")

using namespace std;

 

int main()

{

// Initialize Windows socket library

WSADATA     wsaData;

WSAStartup(0x0202, &wsaData);

 

// Create client socket

SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

 

// Connect to server

SOCKADDR_IN server;

memset(&server, 0, sizeof(SOCKADDR_IN));

server.sin_family = AF_INET;

server.sin_addr.S_un.S_addr = inet_addr("192.168.137.144");

server.sin_port = htons(6000);

 

connect(sockClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));

 

while (1)

{

cout << "send:";

char Buf[1024] = "\0";

cin.getline(Buf, 1024);

 

// Send message

send(sockClient, Buf, strlen(Buf) + 1, 0);

// Receive message

recv(sockClient, Buf, 1024, 0);

printf("Received: '%s'\n", Buf);

}

 

// Clean up

closesocket(sockClient);

WSACleanup();

return 0;

}

HANDLE CreateIoCompletionPort(HANDLE FileHandle,HANDLE ExistingCompletionPort,ULONG_PTR CompletionKey,DWORD NumberOfConcurrentThreads)建立完成埠、或完成埠和套接字繫結

引數FileHandle:開啟重疊IO完成埠的檔案控制代碼。如果設定這個引數為INVALID_HANDLE_VALUE,CreateIoCompletionPort會建立一個不關聯任何檔案的完成埠,而且ExistingCompletionPort必須設定為NULL,CompletionKey也將被忽略;

引數ExistingCompletionPort:完成埠控制代碼。如果指定一個已經存在的完成埠,函式將關聯FileHandle指定的檔案;

引數CompletionKey:單檔案控制代碼,包含指定檔案每次IO完成資料包資訊;

引數NumberOfConcurrentThreads:系統允許在完成埠上併發處理IO完成包的最大執行緒數量。

BOOL GetQueuedCompletionStatus(HANDLE CompletionPort,LPDWORD lpNumberOfBytesTransferred,PULONG_PTR lpCompletionKey,LPOVERLAPPED* lpOverlapped,DWORD dwMilliseconds)查詢完成埠狀態

引數CompletionPort:指定的完成埠(IOCP),由CreateIoCompletionPort函式建立; 

引數lpNumberOfBytesTransferred:一次I/O操作完成後,所傳送資料的位元組數;  

引數lpCompletionKey:當檔案I/O操作完成後,用於存放與完成埠關聯的socket;

引數lpOverlapped:為呼叫IOCP機制所引用的OVERLAPPED結構;   

引數dwMilliseconds: 等待完成埠的超時時間,INFINITE表示一直等待。

BOOL PostQueuedCompletionStatus(HANDLE CompletionPort,DWORD dwNumberOfBytesTransferred,ULONG_PTR dwCompletionKey,LPOVERLAPPED lpOverlapped)喚醒工作者執行緒

引數CompletionPort:指定想向其傳送一個完成資料包的完成埠物件;

引數dwNumberOfBytesTransferred:指定—個值,直接傳遞給GetQueuedCompletionStatus函式中對應的引數 ;

引數dwCompletionKey:指定—個值,直接傳遞給GetQueuedCompletionStatus函式中對應的引數;

引數lpOverlapped:指定—個值,直接傳遞給GetQueuedCompletionStatus函式中對應的引數。

當呼叫GetQueuedCompletionStatus,若完成埠無IO請求,則執行緒睡眠。如果完成埠上一直都沒有已經完成的I/O請求,那麼這些執行緒將無法被喚醒,這也意味著執行緒沒法正常退出。PostQueuedCompletionStatus函式讓我們手動的新增一個完成埠I/O操作,這樣處於睡眠等待的狀態的執行緒就會有一個被喚醒,如果為我們每一個工作執行緒都呼叫一次PostQueuedCompletionStatus(),那麼所有的執行緒也就會因此而被喚醒了。

總結:完成埠是C/S通訊模式中最好的I/O模型,也是最複雜的I/O模型。

重點1:一般情況下,我們只需要建立一個完成埠,儲存好它的控制代碼,後面會經常用到。

重點2:系統中有多少個處理器,可以建立處理器數量*2個執行緒。

重點3:可以用accept來接收客戶端的連線請求,也可以使用效能更好的AcceptEx來接收客戶端連線請求。accept阻塞;AcceptEx非阻塞。

重點4:客戶端連線後,就可以在socket上提交網路請求,如WSARecv。

推薦小豬的部落格:https://blog.csdn.net/piggyxp/article/details/6922277,這是國內對完成埠解釋最詳細的部落格。

4.3.6總結

該如何挑選最適合自己應用程式的 I / O模型?

1. 客戶機的開發

若打算開發一個客戶機應用,令其同時管理一個或多個套接字,那麼建議採用重疊I/O或WSAEventSelect模型,以便在一定程度上提升效能。然而,假如開發的是一個以Windows為基礎的應用程式,要進行視窗訊息的管理,那麼WSAAsyncSelect模型恐怕是一種最好的選擇,因為WSAAsyncSelect本身便是從Windows訊息模型借鑑來的。若採用這種模型,我們的程式一開始便具備了處理訊息的能力。

2. 伺服器的開發

若開發的是一個伺服器應用,要在一個給定的時間,同時控制幾個套接字,建議大家採用重疊I/O模型,這同樣是從效能出發點考慮的。但是,如果預計到自己的伺服器在任何給定的時間,都會為大量I/O請求提供服務,便應考慮使用I/O完成埠模型,從而獲得更好的效能。

節選自《網路程式設計》第8章。