常见的网络通讯分为两种:同步和异步。
在同步通讯中,每一次接受数据都会致使主线程的挂起,从而阻塞住了其余操做。为了解决这一问题,咱们一般会采起同步通讯+多线程的策略,即为每个连入的Socket分配一个线程。然而随着连入的Socket的数量的增长,线程的数量也在增长,这样CPU则须要不停地进行线程的切换,所以难以成为高性能的服务器程序。
异步通讯则能够把接收数据这一操做交给内核,即在内核接收数据的时候,主线程能够不用被阻塞而且继续执行其余操做,而一旦接收数据完成之后,再由内核通知主线程。而如何通知主线程是一个关键,不一样的异步通讯策略有着不一样的通知方式。
在这样的状况下,完成端口这一I/O模型被提出,成为目前Windows下性能最好的I/O模型之一。
编程
首先根据CPU数量开好线程,当有用户请求的时候,把这些请求加入一个特定的消息队列中,而事先开好的线程则会排队从这个消息队列中获取请求并做出处理。完成端口正是指这一消息队列.
api
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );
HANDLE WINAPI CreateIoCompletionPort( _in_ HANDLE FileHandle, // Socket的句柄,置为INVALID_HANDLE_VALUE表示建立一个没有和任何HANDLE有关系的完成端口 _in_opt HANDLE ExistingCompletionPort, // NULL表示新建一个完成端口 _in_ ULONG_PTR CompletionKey, // 完成键,建立完成端口时置为0 _in_ DWORD NumberOfConcurrentThreads // 完成端口并发线程的数量,置0表示有多少个CPU就开多少个线程 );
初始化Socket库... ... listenSoc = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); ... 绑定端口,并监听...
HANDLE WINAPI CreateIoCompletionPort(...)
这一API.CreateIoCompletionPort(listenSoc, iocp, CompKey, 0);
HANDLE WINAPI CreateIoCompletionPort( _in_ HANDLE FileHandle, // 监听Socket的句柄 _in_opt HANDLE ExistingCompletionPort, // 刚才建立的完成端口 _in_ ULONG_PTR CompletionKey, // 完成键,咱们在绑定的同时为其分配一段内存空间,以存储与这一Socket相关的信息,当网络操做完成的时候,咱们能够根据这段内存空间里面的信息分辨这是哪个Socket _in_ DWORD NumberOfConcurrentThreads // 完成端口并发线程的数量,置0表示有多少个CPU就开多少个线程 );
BOOL AcceptEx ( SOCKET sListenSocket, // 监听Socket SOCKET sAcceptSocket, // 事先准备好给新用户的Socket PVOID lpOutputBuffer, // 接受缓冲区 DWORD dwReceiveDataLength, // 用于存放用户第一组数据的空间大小 DWORD dwLocalAddressLength, // 本地地址的空间大小 DWORD dwRemoteAddressLength, // 客户端地址的空间大小 LPDWORD lpdwBytesReceived, LPOVERLAPPED lpOverlapped // 重叠结构,每个网络操做都会对应一个重叠结构,至关于网络操做的ID );
int WSARecv( SOCKET s, // 接受数据的Socket LPWSABUF lpBuffers, // 接收缓冲区 DWORD dwBufferCount, // 置为1 LPDWORD lpNumberOfBytesRecvd, // 所接收到的字节数 LPDWORD lpFlags, // 置为0 LPWSAOVERLAPPED lpOverlapped, // 这个Socket对应的重叠结构 NULL );
GetAcceptExSockAddrs()
来解析这些数据.void GetAcceptExSockaddrs( _In_ PVOID lpOutputBuffer, // AcceptEX中的缓冲区 _In_ DWORD dwReceiveDataLength, // 用户第一组数据的空间大小 _In_ DWORD dwLocalAddressLength, // 本地地址的空间大小 _In_ DWORD dwRemoteAddressLength, // 客户端地址的空间大小 _Out_ LPSOCKADDR *LocalSockaddr, // 本地地址 _Out_ LPINT LocalSockaddrLength, // 实际本地地址的空间大小 _Out_ LPSOCKADDR *RemoteSockaddr, // 客户端地址 _Out_ LPINT RemoteSockaddrLength // 实际客户端地址的大小 );
参考: 1. 完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三
2. Overlapped模型深刻分析服务器