完成端口是套接字的一种模型。利用套接字的完成端口模型,可以在套接字上实现重叠I/O操作。
1 完成端口简介
1.1 传统C/S模式
在传统的C/S模式中,一旦有客户端连接服务端,服务端将创建一个线程来处理与该客户端的I/O操作。如果有多个客户端连接服务端时,服务端将会创建多个线程。多个线程间的切换会占据CPU的大量处理时间,而线程的创建和销毁也会消耗大量的CPU时间。
1.2 完成端口模型基本原理
可以通过完成端口模型避免以上问题。完成端口模型并没有为每一个连接服务端的客户端都创建一个对应的线程。在该模型中,当有客户端的I/O请求完成之后,会向完成端口发送消息。完成端口模型利用线程池中的线程在完成端口中取出该消息进行处理,在消息处理完之后,线程池中的线程变为休眠状态继续等待完成端口中的消息。
1.3 完成端口模型流程
图1 完成端口模型基本流程
从图1可以看出,服务端仅使用线程池中少量的线程来处理客户端的I/O请求。这样就避免了多个线程切换以及创建销毁线程带来的时间消耗。
2 相关API函数
从“1完成端口简介”中分析可知,完成端口模型主要实现了完成端口的创建、完成端口与套接字I/O的关联以及从完成端口队列中取出消息等功能。以上功能主要涉及到两个函数,一个是CreateIoCompletionPort()函数,另外一个是GetQueuedCompletionStatus()函数。
2.1 CreateIoCompletionPort()函数
该函数主要实现完成端口的创建和完成端口与套接字I/O的关联等功能。该函数的格式为
HANDLE WINAPI CreateIoCompletionPort(
HANDLE FileHandle
, HANDLE ExistingCompletionPort
, ULONG_PTR CompletionKey
, DWORD NumberOfConcurrentThreads
);
该函数的第一个参数FileHandle表示打开文件的句柄,该文件用于重叠I/O的完成,这里的“文件”指的是Windows的内核对象,套接字也属于“文件”;第二个参数ExistingCompletionPort指定了I/O完成端口的句柄;第三个参数CompletionKey指定了文件的完成键,在完成键中包含了指定文件的每个I/O完成包;NumberOfConcurrentThreads指定了同时处理I/O完成包的线程的最大数量。
相关连接:
1使用CreateIoCompletionPort()函数创建完成端口时,参数FileHandle设置为INVALID_HANDLE_VALUE,ExistingCompletionPort和CompletionKey设置为NULL,NumberOfConcurrentThreads设置为0即可;
2完成键可以看作是用户自定义数值的指针,这个自定义的数值会交给“1.2 完成端口模型基本原理”中提到的线程池中的服务线程,服务线程得到该指针后,可以使用该指针指向的内存中的数据,完成了主线程与服务线程之间的通信。
2.2 GetQueuedCompletionStatus()函数
该函数的作用是从I/O完成端口队列中取出完成包消息。GetQueuedCompletionStatus()函数一般在“1.2 完成端口模型基本原理”中提到的线程池中的服务线程中调用。
BOOL WINAPI GetQueuedCompletionPort(
HANDLE CompletionPort
, LPDWORD lpNumberOfBytes
, PULONG_PTR lpComletionKey
, LPOVERLAPPED* lpOverlapped
, DWORD dwMilliseconds
);
其中,第一个参数CompletionPort指定了完成端口的句柄;第二个参数lpNumberOfBytes用于保存在已经完成的I/O操作中,传输了多少比特的数据;lpComletionKey用于保存完成键的地址,该完成键即为“2.1 CreateIoCompletionPort()函数”中提到的在创建完成端口时CreateIoCompletionPort()函数的参数,通过该参数可以判断从完成端口中获取到的消息类型;第四个参数lpOverlapped是OVERLAPPED结构的指针,该结构的变量在I/O操作完成时被指定;第五个参数指定了GetQueuedCompletionStatus()函数在完成端口上等待的时间,如果到了指定的时间仍然没有完成包消息到来,则函数返回。