当应用程序必须一次管理多个套接字的时候,完成端口模型提供了最好的系统性能。这个模型也提供了最好的伸缩性,它非常适合用来处理上百、上千个套接字。IOCP广泛应用于各种类型的高性能服务器,如Apach等。
1 什么是完成端口对象
I/O完成端口是应用程序使用线程池处理异步I/O请求的一种机制。处理多个并发异步I/O请求时,使用I/O完成端口比在I/O请求时创建线程更快更有效。
I/O完成端口最初的设计师应用程序发出一些异步I/O请求,当这些请求完成时,设备驱动将吧这些工作项目排序到完成端口。这样,在完成端口上等待的线程池便可以处理这些完成端口。完成端口实际上市一个Windows I/O结构,它可以接收多种对象的句柄,如文件对象,套接字对象等。
2 创建完成端口对象
使用完成端口模型,首先要调用CreateIOCompletionPort函数创建一个完成端口对象,Winsock将使用这个对象为任意数量的套接字句柄管理I/O请求,函数定义如下:
Associates an input/output (I/O) completion port with one or more file handles, or it can create an I/O completion port that is not associated with a file handle.
Associating an instance of an opened file with an I/O completion port lets an application receive notification of the completion of asynchronous I/O operations involving that file.
HANDLE WINAPI CreateIoCompletionPort( __in HANDLE FileHandle, __in HANDLE ExistingCompletionPort, __in ULONG_PTR CompletionKey, __in DWORD NumberOfConcurrentThreads );
A handle to a file opened for overlapped I/O completion. You must specify the FILE_FLAG_OVERLAPPED flag when using theCreateFile function to obtain the handle.
If FileHandle specifies INVALID_HANDLE_VALUE, CreateIoCompletionPort creates an I/O completion port without associating it with a file. In this case, theExistingCompletionPort parameter must be NULL and the CompletionKey parameter is ignored.
A handle to the I/O completion port.
If this parameter specifies an existing completion port, the function associates it with the file specified by theFileHandle parameter. The function returns the handle of the existing completion port; it does not create a new I/O completion port.
If this parameter is NULL, the function creates a new I/O completion port and associates it with the file specified byFileHandle. The function returns the handle to the new I/O completion port.
The per-file completion key that is included in every I/O completion packet for the specified file.
The maximum number of threads that the operating system can allow to concurrently process I/O completion packets for the I/O completion port. This parameter is ignored if theExistingCompletionPort parameter is not NULL.
If this parameter is zero, the system allows as many concurrently running threads as there are processors in the system.
If the function succeeds, the return value is the handle to the I/O completion port that is associated with the specified file.
If the function fails, the return value is NULL. To get extended error information, callGetLastError.
2 I/O服务线程和完成端口成功创建完成端口对象后,便可以向这个对象关联套接字句柄了,在关联套接字之前,需要先创建一个或多个工作线程(称为I/O服务线程),在完成端口上执行并处理投递到完成端口上的I/O请求。这里的关键问题是要创建多少个工作线程。要注意,创建完成端口时指定的线程数量和这里要创建的线程数量不是一回事。前面我们推荐线程数量为处理器的数量以避免上下文切换。CreateIoCompletionPort函数的NUmberOfConcurrentThreads参数明确告诉系统允许在完成端口上同时运行的线程数量。
有了足够多的工作线程来处理完成端口上的I/O请求之后,就该为完成端口关联套接字句柄了,这用到了函数的前三个参数。
向完成端口关联套接字句柄之后,便可以通过在套接字上投递重叠发送和接收请求处理I/O了,在这些I/O操作完成时,I/O系统会向完成端口对象发送一个完成通知封包。I/O完成端口以先进先出的方式为这些封包排队。应用程序使用GetQueuedCompletionStatus函数以取得这些队列中的封包.这个函数应该在处理完成对象I/O的服务线程中调用。
Attempts to dequeue an I/O completion packet from the specified I/O completion port. If there is no completion packet queued, the function waits for a pending I/O operation associated with the completion port to complete.
BOOL WINAPI GetQueuedCompletionStatus( __in HANDLE CompletionPort, __out LPDWORD lpNumberOfBytes, __out PULONG_PTR lpCompletionKey, __out LPOVERLAPPED* lpOverlapped, __in DWORD dwMilliseconds );
Attempts to dequeue an I/O completion packet from the specified I/O completion port. If there is no completion packet queued, the function waits for a pending I/O operation associated with the completion port to complete.
BOOL WINAPI GetQueuedCompletionStatus( __in HANDLE CompletionPort, __out LPDWORD lpNumberOfBytes, __out PULONG_PTR lpCompletionKey, __out LPOVERLAPPED* lpOverlapped, __in DWORD dwMilliseconds );
A handle to the completion port. To create a completion port, use the CreateIoCompletionPort function.
A pointer to a variable that receives the number of bytes transferred during an I/O operation that has completed.
A pointer to a variable that receives the completion key value associated with the file handle whose I/O operation has completed. A completion key is a per-file key that is specified in a call toCreateIoCompletionPort.
A pointer to a variable that receives the address of the OVERLAPPED structure that was specified when the completed I/O operation was started.
The following functions can be used to start I/O operations that complete using completion ports. You must pass the function anOVERLAPPED structure and a file handle associated with an completion port (by a call toCreateIoCompletionPort) to invoke the I/O completion port mechanism:
Even if you have passed the function a file handle associated with a completion port and a validOVERLAPPED structure, an application can prevent completion port notification. This is done by specifying a valid event handle for thehEvent member of the OVERLAPPED structure, and setting its low-order bit. A valid event handle whose low-order bit is set keeps I/O completion from being queued to the completion port.
The number of milliseconds that the caller is willing to wait for a completion packet to appear at the completion port. If a completion packet does not appear within the specified time, the function times out, returns FALSE, and sets *lpOverlapped to NULL.
If dwMilliseconds is INFINITE, the function will never time out. If dwMilliseconds is zero and there is no I/O operation to dequeue, the function will time out immediately.
此时需要先创建per-handle数据和per-IO操作数据的结构类型
//初始化Winsock库
CInitSock theSock;
#define BUFFER_SIZE 1024
typedef struct _PER_HANDLE_DATA
{
SOCKET s;
sockaddr_in addr;
}PER_HANDLE_DATA,*PPER_HANDLE_DATA;
typedef struct _PER_IO_DATA
{
OVERLAPPED ol;
char buf[BUFFER_SIZE];
int nOperationType;
#define OP_READ 1;
#define OP_WRITE 2;
#define OP_ACCEPT 3;
}PER_IO_DATA,*PPER_IO_DATA;