1.IOCP自动维护worker线程,没必要将worker组织为线程池,即不采用IOCP+线程池
2.
lpCompletionKey与File Handle关联,每当File Handle上的请求完成时lpCompletionKey都会随IO完成包出现在完成队列。
File Handle出现问题,之前投递的所有请求都会返回,如何释放lpCompletionKey堆空间?
有做法如下,使用引用计数,外带一把锁,投递请求计数加1,请求完成计数减一,检查计数为0,则删除lpCompletionKey,删除引用计数,销毁锁。
问题:设线程A于SOCKET s上已投递WSARecv,则s对象引用计数为1;某时刻线程B于s投递WSASend或WSARecv,投递成功、请求未完成,则B主动申请s锁以递增引用计数。若恰在此时连接被Client关闭,则A和B投递的请求均从GetQueuedCompletionStatus返回,则两者都将申请s锁以递减引用计数。假设A投递的请求返回后由线程D处理,D先与B获取到s锁,递减引用计数后值为0,则执行清理操作。D释放lpCompletionKey堆空间、释放引用计数、销毁锁,那么B将面临锁被销毁的不确定因素和之后访问引用计数带来的内存崩溃。若D仅释放lpCompletionKey,则程序长时间运行必将导致引用计数和锁资源的泄露问题。
AlarmServer面对此问题的解决方案:
不使用任何lpCompletionKey:CreateIoCompletionPort((HANDLE)s, hCompletionPort, NULL, 0);
或仅传入普通变量SOCKET:CreateIoCompletionPort((HANDLE)s, hCompletionPort, s, 0);
一切记录工作由PER_IO_DATA完成。
切忌传入堆地址CreateIoCompletionPort((HANDLE)s, hCompletionPort, pphd, 0);势必将引入引用计数、临界区、堆释放的邪恶深渊。
该问题的解决方法:
对于每条连接,同时投递CPU个数的接收,设CPU个数为n。
连接出现异常,如主动关闭或对方关闭时,处理PER_HANDLE_DATA的线程原子递增该结构中的计数器iReleaseCount。
若iReleaseCount == n,则对此结构进行清理操作。
清理完成,此结构加入空闲列表表尾,空闲列表长度超过限定,从表头delete释放空间。
3.
BOOL WINAPI GetQueuedCompletionStatus(
_In_ HANDLE CompletionPort,
_Out_ LPDWORD lpNumberOfBytes,
_Out_ PULONG_PTR lpCompletionKey,
_Out_ LPOVERLAPPED *lpOverlapped,
_In_ DWORD dwMilliseconds
);
Client关闭连接的判断条件是:bRet == true && lpNumberOfBytes == 0 && OP_ACCEPT != iTypeOpt
4. 把所有的closesocket(s)放在一个函数里面:在连接异常断开时,在该函数中添加断点,方便定位。