IOCP 是5种socket 编程模型里最复杂的一种,只使用几个api,就能使程序支持成百上千个连接,而GetQueuedCompletionStatus函数又是IOCP里最重要的api,这个函数返回值含有各种信息。
一般的处理顺序为:是先判断函数GetQueuedCompletionStatus的返回值,如果失败,看看WSAGetLastError()的值。否则检查OVERLAPP结构体是否为NULL,不为NULL,就判断OVERLAPPED相关的东西和传输的字节数等等。
先看看GetQueuedCompletionStatus函数的完整声明:
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytes,
PULONG_PTR lpCompletionKey,
LPOVERLAPPED *lpOverlapped,
DWORD dwMilliseconds
);
再看看MSDN上对其返回值的说明:
If the function dequeues a completion packet for a successful I/O operation from the completion port, the return value is nonzero. The function stores information in the variables pointed to by the lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped parameters.
如果函数从完成端口取出一个成功I/O操作的完成包,返回值为非0。函数在指向lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped的参数中存储相关信息。
------------------第1种情况
If *lpOverlapped is NULL and the function does not dequeue a completion packet from the completion port, the return value is zero. The function does not store information in the variables pointed to by the lpNumberOfBytesTransferred and lpCompletionKey parameters. To get extended error information, call GetLastError. If the function did not dequeue a completion packet because the wait timed out, GetLastError returns WAIT_TIMEOUT.
如果 *lpOverlapped为空并且函数没有从完成端口取出完成包,返回值则为0。函数则不会在lpNumberOfBytes and lpCompletionKey所指向的参数中存储信息。调用GetLastError可以得到一个扩展错误信息。如果函数由于等待超时而未能出列完成包,GetLastError返回WAIT_TIMEOUT.
------------------第2种情况
If *lpOverlapped is not NULL and the function dequeues a completion packet for a failed I/O operation from the completion port, the return value is zero. The function stores information in the variables pointed to by lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped. To get extended error information, call GetLastError
如果 *lpOverlapped不为空并且函数从完成端口出列一个失败I/O操作的完成包,返回值为0。函数在指向lpNumberOfBytesTransferred, lpCompletionKey, and lpOverlapped的参数指针中存储相关信息。调用GetLastError可以得到扩展错误信息 。
------------------第3种情况
If a socket handle associated with a completion port is closed, GetQueuedCompletionStatus returns ERROR_SUCCESS, with lpNumberOfBytes equal zero.
如果关联到一个完成端口的一个socket句柄被关闭了,则GetQueuedCompletionStatus返回ERROR_SUCCESS(也是0),并且lpNumberOfBytes等于0
-----------------第4种情况
结合各种情况,我自己实现的IOCP 的处理顺序如下:
DWORD WINAPI CIOCPModel::WorkerThread(LPVOID lpParam)
{
THREADPARAMS_WORKER* pParam = (THREADPARAMS_WORKER*)lpParam;
CIOCPModel* pIOCPModel = (CIOCPModel*)pParam->pIOCPModel;
int nThreadNo = (int)pParam->nThreadNo;
Write_Log("Work thread start,ID: %d.",nThreadNo);
OVERLAPPED *pOverlapped = NULL;
PER_SOCKET_CONTEXT *pSocketContext = NULL;
DWORD dwBytesTransfered = 0;
// 循环处理请求,知道接收到Shutdown信息为止
while (WAIT_OBJECT_0 != WaitForSingleObject(pIOCPModel->m_hShutdownEvent, 0))
{
BOOL bReturn = GetQueuedCompletionStatus(
pIOCPModel->m_hIOCompletionPort,
&dwBytesTransfered,
(PULONG_PTR)&pSocketContext,
&pOverlapped,
INFINITE);
// 判断是否出现了错误
if( bReturn == FALSE )
{
if(NULL == pOverlapped) //处理第2种情况
continue;
//pOverlapped !=NULL
DWORD dwErr = GetLastError();
if(pSocketContext == NULL)
{
continue;
}
else
{
pIOCPModel->HandleError(pSocketContext,dwErr); //处理第3和第4种情况
continue;
}
}
else
{
// 读取传入的参数
PER_IO_CONTEXT* pIoContext = CONTAINING_RECORD(pOverlapped, PER_IO_CONTEXT, m_Overlapped);
// 判断是否有客户端断开了
if((0 == dwBytesTransfered) && ( RECV_POSTEDHEAD == pIoContext->m_OpType || SEND_POSTED == pIoContext->m_OpType||RECV_POSTEDBODY == pIoContext->m_OpType))
{
// 释放掉对应的资源
pIOCPModel->RemoveSocketContext( pSocketContext );
continue;
}
else
{
//处理第1种情况
switch( pIoContext->m_OpType )
{
case ACCEPT_POSTED:
{
}
break;
case RECV_POSTEDHEAD:
{
pIOCPModel->DoRecvHead( pSocketContext,pIoContext,dwBytesTransfered );
}
break;
case RECV_POSTEDBODY:
pIOCPModel->DoRecvData( pSocketContext,pIoContext,dwBytesTransfered );
break;
case SEND_POSTED:
{
pIOCPModel->DoSendData(pSocketContext,pIoContext,dwBytesTransfered);
}
break;
default:
// 不应该执行到这里
WriteSingleLog("_WorkThread's pIoContext->m_OpType Param invalid.\n");
break;
} //switch
}//if
}//if
}//while
Write_Log("Work Thread %d exit.\n",nThreadNo);
// 释放线程参数
RELEASE(lpParam);
return 0;
}
// 显示并处理完成端口上的错误
bool CIOCPModel::HandleError( PER_SOCKET_CONTEXT *pContext,const DWORD& dwErr )
{
if( NULL == pContext )
return false;
// 如果是超时了,就再继续等吧
if(WAIT_TIMEOUT == dwErr)
{
// 确认客户端是否还活着...
if( !IsSocketAlive( pContext->GetSocket()) )
{
WriteSingleLog( "Client disconnect!" );
this->RemoveSocketContext( pContext );
return true;
}
else
{
WriteSingleLog( "Network operate time out!" );
return true;
}
}
else
{
Write_Log( "Shutdown Client,Error Code is :%d!",dwErr);
this->RemoveSocketContext( pContext );
return true;
}
}
/////////////////////////////////////////////////////////////////////
// 判断客户端Socket是否已经断开,否则在一个无效的Socket上投递WSARecv操作会出现异常
// 使用的方法是尝试向这个socket发送数据,判断这个socket调用的返回值
// 因为如果客户端网络异常断开(例如客户端崩溃或者拔掉网线等)的时候,服务器端是无法收到客户端断开的通知的
bool CIOCPModel::IsSocketAlive(SOCKET s)
{
int nByteSent=send(s,"",0,0);
if (-1 == nByteSent)
return false;
return true;
}