前面几章已经分别介绍了window下socket 网络编程的几种模式,今天简单的介绍一下最后一个模型:完成端口(completionPort)模型。
关于他的一些优点网上有一堆,这边我也不再一一介绍,点而言之就是他充分利用内核对象的调度,只使用少量的几个线程来梳理和客户端所有通信,最大限度的提高了网络通信的性能。下面简单介绍一下主要涉及主要函数。
最优线程数
根据实际应用中发现,Cup核数*2+2这个数量是最优线程数(网上查询到的,我也不知道原因),获取系统内核数可调用
VOID GetSystemInfo(LPSYSTEM_INFO lpSystemInfo)
通过LPSYSTEM_INFO 结构体获取内核数
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
//获取CPU核数
int n = sys_info.dwNumberOfProcessors;
CreateIoCompletionPort函数
创建一个完成端口,函数原型为
HANDLE WINAPI CreateIoCompletionPort(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE ExistingCompletionPort,
_In_ ULONG_PTR CompletionKey,
_In_ DWORD NumberOfConcurrentThreads
);
参数:
FileHandle
打开的文件句柄或INVALID_HANDLE_VALUE。
该句柄必须指向支持重叠I / O的对象。
如果提供了句柄,则必须打开它才能完成I / O重叠。例如,在使用CreateFile函数获取句柄时,必须指定FILE_FLAG_OVERLAPPED标志 。
如果指定了INVALID_HANDLE_VALUE,那ExistingCompletionPort参数必须为NULL,并且CompletionKey参数将被忽略
ExistingCompletionPort 现有I / O完成端口或NULL的句柄。
如果此参数指定了现有的I / O完成端口,则该函数将其与FileHandle参数指定的句柄相关联。如果成功,该函数将返回现有I / O完成端口的句柄;它不会创建新的I / O完成端口。
如果此参数为NULL,则该函数将创建一个新的I / O完成端口,并且如果FileHandle参数有效,则将其与新的I / O完成端口关联。否则,不会发生文件句柄关联。如果成功,该函数将句柄返回到新的I / O完成端口。
CompletionKey
每个句柄用户定义的完成密钥,包含在指定文件句柄的每个I / O完成数据包中。
NumberOfConcurrentThreads
操作系统可以允许同时处理I / O完成端口的I / O完成数据包的最大线程数。如果ExistingCompletionPort参数不为NULL,则忽略此参数。
如果此参数为零,则系统允许的并发运行线程数与系统中的处理器数量一样。
综上:如果你要创建一个新的完成端口,第一个参数必须设置为INVALID_HANDLE_VALUE,第二个参数为NULL,第三个忽略,第四个参数是运行的最大线程数量,例如:
//创建完成端口
HANDLE g_completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
如果要在创建的端口单口上关联一个I/O操作,如sokcet 监听套接字,具体操作如下:
SOCKET listenSock=socket(AF_INET, SOCK_STREAM, 0);
//绑定监听套接字和端口
CreateIoCompletionPort((HANDLE)listenSock, g_completionPort, (DWORD)listenSock, 0);
返回值:
如果函数成功,则返回值是I / O完成端口的句柄:
如果ExistingCompletionPort参数为NULL,则返回值为新句柄。
如果ExistingCompletionPort参数是有效的I / O完成端口句柄,则返回值就是该句柄。
如果FileHandle参数是有效的句柄,则该文件句柄现在与返回的I / O完成端口关联。
如果函数失败,则返回值为NULL。若要获取扩展的错误信息,请调用GetLastError函数。
ACCEPTEX 函数
此函数是一个Microsoft特定的扩展,原型如下:
BOOL AcceptEx(
_In_ SOCKET sListenSocket,
_In_ SOCKET sAcceptSocket,
_In_ PVOID lpOutputBuffer,
_In_ DWORD dwReceiveDataLength,
_In_ DWORD dwLocalAddressLength,
_In_ DWORD dwRemoteAddressLength,
_Out_ LPDWORD lpdwBytesReceived,
_In_ LPOVERLAPPED lpOverlapped
);
但是平不是所有的系统都支持使用这个API,并且获取的开销很大,不建议直接使用(好多资料上都是这么说的,具体未研究)
微软给了俩个特定指针来解析这个动作,固定的格式:
LPFN_ACCEPTEX lpfnAcceptEx;
LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs;
GUID guidAcceptEx = WSAID_ACCEPTEX;
GUID guidGetAddr = WSAID_GETACCEPTEXSOCKADDRS;
//两个扩展函数,成功都是返回0
DWORD bytes;
//加载AccpetEx函数指针
int rc = WSAIoctl(
listenSock,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&guidAcceptEx,
sizeof(guidAcceptEx),
&lpfnAcceptEx,
sizeof(lpfnAcceptEx),
&bytes,
NULL,
NULL
);
//加载GetAcceptExSockaddrs函数指针
rc = WSAIoctl(
listenSock,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&guidGetAddr,
sizeof(guidGetAddr),
&lpfnGetAcceptExSockaddrs,
sizeof(lpfnGetAcceptExSockaddrs),
&bytes,
NULL,
NULL
);
上面的写法是固定的,不明白的可以查询关资料。
GetQueuedCompletionStatus
获取完成端口的状态,当有重叠任务完成时,在多个调用该函数的线程中挑选一个线程返回,并返回相应的结构用于Accept,Recv,Send等操作。
函数原型:
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytes,
PULONG_PTR lpCompletionKey,
LPOVERLAPPED *lpOverlapped,
DWORD dwMilliseconds
);
参数:
CompletionPort:指定的IOCP,该值由CreateIoCompletionPort函数创建的完成端口句柄
lpnumberofbytes:一次完成后的I/O操作所传送数据的字节数。
lpcompletionkey:当文件I/O操作完成后,用于存放与之关联的CK,如socket 等
lpoverlapped:为调用IOCP机制所引用的OVERLAPPED结构。
dwmilliseconds:用于指定调用者等待CP的时间,如果设置为 INFINITE,表示会一直等待下去
返回值:
调用成功,则返回非零数值,相关数据存于lpNumberOfBytes、lpCompletionKey、lpCompletionKey变量中。失败则返回零值。
DWORD dwBytesTransfered = 0