网络服务器编程——完成端口

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

扫描二维码关注公众号,回复: 4267512 查看本文章

#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章。

猜你喜欢

转载自blog.csdn.net/Bing_bing_bing_/article/details/83685968