主要api
a. 创建完成端口
HANDLE WINAPI CreateIoCompletionPort(
HANDLE FileHandle,
HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey
DWORD NumberOfConcurrentThreads
);
a) 前三个参数被忽略,最后一个参数为允许执行的线程量 CPU*2,设置为0 表示数量= CPU
b) 返回完成端口句柄
b. 关联完成端口
HANDLE WINAPI CreateIoCompletionPort(
HANDLE FileHandle,
HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey
DWORD NumberOfConcurrentThreads
);
a) 第一个参数为客户端套接字
b) 第二个参数为已经存在完成端口句柄
c) 第三个参数提供客户端套接字信息的单句柄数据
c. 获取队列完成状态
BOOL WINPAI GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytes,
PILONG_PTR lpCompletionKey,
LPOVERLAPPED* lpOverlapped,
DWORD dwMilliseconds
);
a) 第一个参数为完成端口
b) 第二个参数为完成一次io操作后传输的字节数
c) 同函数2
d) 接收完成的io操作的重叠结果
e) 指定调用者希望等待一个完成数据包在完成端口上出现的时间,设置为INFINITE,会一直等待
如果函数从完成端口取出一个成功I/O操作的完成包,返回值为非0。
d. 投递一个队列完成状态
BOOL WINAPI PostQueuedCompletionStatus(
HANDLE CompletionPort,
DWORD dwNumberOfBytesTransferred,
ULONG_PTR dwCompletionKey,
LPOVERLAPPED lpOverlapped
);
a) 完成端口对象
b) 同函数3
工作流程
a. 创建完成端口,创建服务器套接字
b. 将服务器套接字进行绑定端口然后listen,accept
c. 开启工作线程查询GetQueuedCompletionStatus,获取io相关信息,通过dwCompletionKey携带套接字数据,将数据原封不动发送到客户端,然后再次触发WSARecv操作
d. 每次接受新的连接时,将新的套接字与完成端口相关联,每个io操作关联一个OVERLAPPED数据结构,触发WSARecv操作
e. 任意时刻提交异步请求WSASend,WSARecv
辅助函数
a. int WSASend{
SOCKET s,
LPWSABBUF lpBUffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
};
a) 第一个参数,标识一个已连接套接字
b) 第二个参数,一个指向WSABUF结构数组的指针,每个WSABUF包含缓冲区的指针和大小
c) 第三个参数,lpBuffers中WSABUF结构的数目
d) 第四个参数,如果发送操作立即完成,则为一个指向所发送数据字节的指针
e) 第五个参数,标志位
f) 第六个参数,指向WSAOVERLAPPED结构的指针
g) 第七个参数,指向发送操作完成后调用完成例程的指针
h) 若无错误发生且发送操作立即完成,返回0,否则返回SOCKET_ERROR,错误码为WSA_IO_PENDING,表示异步io已经提交完成
b. int WSARecv{
SOCKET s,
LPWSABBUF lpBUffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
}
a) 第一个参数,标识一个已连接套接字
b) 第二个参数,接受缓冲区,WAFBUF结构的数组
c) 第三个参数,lpBuffers中WSABUF结构的数目
d) 第四个参数,如果接收操作立即完成,则为一个指向所接收数据字节的指针
e) 第五个参数,指向标志位的指针
f) 第六个参数,绑定WSAOVERLAPPED结构
g) 第七个参数,完成例程中将会用到的参数,这里设置为NULL
h) 返回值为WSA_IO_PENDING,说明WSARecv操作成功
网络缓冲
a. 默认操作系统为每个套接字分配两个缓冲区分别用于缓冲区发送数据和接受数据
b. 应用层发送数据先拷贝到缓冲区,然后由操作系统发送出去
c. 远端发过来的数据先放在接受缓冲区,等待应用层调用读操作,把数据取走
d. 这个两个缓冲区由操作系统管理,并且属于内核地址空间,是非分页的
e. 每个连接读请求时投递一个0字节的读操作,这样做不会存在内存锁定,一旦有数据
被收到,操作系统就会投递完成通知,服务端就可以去接收缓冲区的数据了
f. 写请求时投递一个0字的写操作,表示希望发送数据,操作系统一旦判断这个连接可以
写了,会投递一个通知,此时就可以放心投递数据
乱序问题
原因:在tcp下,对于一个socket同时投递多个send,不说send端会怎么样,对于recv端将是个灾难!tcp是流式协议,会有半包、粘包,这样recv 端将有可能收到几次send出来的部分数据拼凑起来的不可解析数据... recv同时投递多个,在线程切换时,会造成数据乱序,在半包粘包时数据将不可解析
解决方法: a. tcp时,对于一个socket目标,同时只能有1次send,1次recv
b. 在包上加标记
c. 建立有个缓冲区,把要投递的数据放入缓冲区,然后交给WSASend去投递,后来的数据依次放入缓冲区中
WSAENOBUFS出错
原因: 伴随着每一次重叠发送和接收操作,其中指定的发送或接收缓冲区会被加锁。而操作系统会强行为能被锁定的内存的大小设定一个上限,当达到这个上限,重叠操作将失败,并发送WSAENOBUFS错误
解决方法:
a. 投递使用空缓冲区的 recevie操作,当操作返回后,使用非阻塞的recv来进行真实数据的读取。因此在完成端口的每一个连接中需要使用一个循环的操作来不断的来提交空缓冲区的receive操作。
b. 在投递几个普通含有缓冲区的recevie操作后,进接着开始循环投递一个空缓冲区的recevie操作。这样保证它们按照投递顺序依次返回,这样我们就总能对被锁定的内存进行解锁。
主动关闭一个关联到完成端口的连接.
不管在哪个线程里closesocket之后,GET函数都会返回,RECV失败的事件,表示这个socket断开了。 你可以在这个事件里处理释放对应的session资源,而不是closesocket之后立即释放。closesocket后,GetQueuedComplitionStatus会返回错误,你的某个worker thread会被激活,在这个线程中调用GetLastError,系统会告诉你出错的原因。不要在关闭socket的线程释放与这个socket关联的per io data,而是应该在被唤醒的线程中释放。 等到所有的 socket A有关的completion packet 处理完,其中GetQueuedCompletionStatus 返回的最后一个和 socket A 相关的 completion packet 会提示出错,也就是函数的返回值是 ERROR_SUCCESS ,同时 dwNumberOfBytesTransferred 为0。这个时候就可以放心大胆地释放相关的资源了。