完成端口是真正意义上的异步模型,如果硬要程序需要同时与成百上千个客户端同时通信,使用完成端口模型可以大大提高应用程序的性能,提升并发处理能力。完成端口会充分利用Windows内核来进行I/O的调度,是用于C/S通信模式中性能最好的网络通信模型,没有之一;甚至连和它性能接近的通信模型都没有。

1、创建完成端口
调用CreateIoCompletionPort()函数创建一个完成端口,而且在一般情况下,我们需要且只需要建立这一个完成端口,把它的句柄保存好,我们今后会经常用到它。

HANDLE CreateIoCompletionPort(
    HANDLE FileHandle,
    HANDLE ExistingCompletionPort,
    ULONG_PTR CompletionKey,
    DWORD NumberOfConcurrentThreads
);

CreateIoCompletionPort可以将一个I/O完成端口(IOCP)关联到一个或多个文件句柄(Socket),或与文件句柄无关的I/O完成端口。将Socket与完成端口关联后,应用程序就可以接收到该Socket上执行异步I/O操作完成后发送的通知。

(1)FileHandle:重叠I/O对应的文件句柄(Socket),如果FileHandle指定为INVALID_HANDLE_VALUE,则CreateIoCompletionPort创建与文件句柄无关的I/O完成端口。此时,ExistingCompletionPort必须为NULL,且CompletionKey会被忽略。
(2)ExistingCompletionPort:完成端口句柄,如果指定一个已存在的完成端口句柄,则函数会关联到FileHandle参数指定的文件句柄上;如果参数为NULL,则创建一个与FileHandle参数指定的文件句柄关联的新完成端口。
(3)CompletionKey:每个I/O完成数据包中用于指定文件句柄(Socket)的完成键。
(4)NumberOfConcurrentThreads:指定I/O完成端口上操作系统允许的并发处理I/O完成数据包的最大线程数。如果ExistingCompletionPort为空,则该参数会忽略。
如果函数执行成功,则返回与指定文件(Socket)相关联的I/O完成端口句柄,否则返回NULL,可以调用GetLastError获取错误信息。

首先要注意该函数实际用于两个明显有别的目的:

  • 用于创建一个完成端口对象
IOCP=CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

NumberOfConcurrentThreads:指定I/O完成端口上操作系统允许的并发处理I/O完成数据包的最大线程数,如果指定为0,系统根据CPU数量自动选择。

  • 将一个句柄同完成端口关联到一起
IOCP=CreateIoCompletionPort(hDevice, hComPort, dwComKey, 0);

参数hDevice已经创建的文件句柄,hComPort已经创建的完成端口内核对象。

2、建立工作线程

SYSTEM_INFO si;  
GetSystemInfo(&si);  
int m_nProcessors = si.dwNumberOfProcessors;

Tips:我们最好是建立CPU核心数量*2那么多的线程,这样更可以充分利用CPU资源,因为完成端口的调度是非常智能的,比如我们的Worker线程有的时候可能会有Sleep()或者WaitForSingleObject()之类的情况,这样同一个CPU核心上的另一个线程就可以代替这个Sleep的线程执行了;因为完成端口的目标是要使得CPU满负荷的工作。

for(int i=0;i<(int)systemInfo.dwNumberOfProcessors*2;++i)
{
    HANDLE ThreadHandle;
    ThreadHandle=(HANDLE)_beginthreadex(NULL,0,ServerWorkerThread,CompletionPort,0,&ThreadID);
    CloseHandle(ThreadHandle);
}

3、创建监听Socket、完成端口

server=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
saddr.sin_family=AF_INET;
saddr.sin_port=htons(PORT);
saddr.sin_addr.s_addr=htonl(ADDR_ANY);
bind(server,(sockaddr*)&saddr,sizeof(saddr));
listen(server,5);
CompletionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);

4、监听Socket上投递Accept请求

client=accept(server,NULL,NULL);

5、工作线程
(1)使用GetQueuedCompletionStatus()监控完成端口

BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,           // 建立的完成端口
LPDWORD lpNumberOfBytes,         // 操作完成后返回的字节数
PULONG_PTR lpCompletionKey,      // 建立完成端口的时候绑定的自定义结构体参数  
LPOVERLAPPED *lpOverlapped,      // 连入Socket的时候一起建立的那个重叠结构
DWORD dwMilliseconds             // 等待完成端口的超时时间
);

未出现已完成的I/O请求时,会让工作线程进入不占用CPU的睡眠状态,直到完成端口上出现了需要处理的网络操作或者超出了等待的时间限制为止;一旦完成端口上出现了已完成的I/O请求,那么等待的线程会被立刻唤醒,然后继续执行后续的代码。

6、接收/发送数据

WSARecv(PerHandleData->socket,&(PerIOData->DataBuf),1,&RecvBytes,&Flags,&(PerIOData->overlapped),NULL);

WSASend(PerHandleData->socket,&(PerIOData->DataBuf),1,&SendBytes,0,&(PerIOData->overlapped),NULL);

标签: none

评论已关闭