最近在使用IOCP的时候发现了一个问题:
MSDN中对GetQueuedCompletionStatus返回值的解释有这么一条
If a socket handle associated with a completion port is closed, GetQueuedCompletionStatus returns ERROR_SUCCESS, with *lpOverlapped non-NULL and lpNumberOfBytes equal zero.
大概意思就是说如果和完成端口相关联的socket被关闭,GetQueuedCompletionStatus 返回 ERROR_SUCCESS(即0),而且overlapped 非空, lpNumberOfBytes 为零。IOCP工作线程的代码如下:UINT WINAPI CIOCPFrame::ServerWorkerThread(void* pParam)
{
SThreadParam* pWorkerParam = (SThreadParam*)pParam;
CIOCPFrame* pIOCPFrame = pWorkerParam->pIOCPFrame;
HANDLE hCompletionPort = pWorkerParam->hCompletionPort;
DWORD BytesTransferred;
SOverlapped* pSOverlapped = NULL;
SOCKET Socket = NULL;
SOCKET AcceptSock = NULL;
int nLocalID = INVALID_CLIENT_ID;
int iError = 0;
int nRet = 0; while(TRUE)
{
nRet =  GetQueuedCompletionStatus(hCompletionPort, &BytesTransferred,
(PULONG_PTR)&Socket, (LPOVERLAPPED*)&pSOverlapped, INFINITE); nLocalID = pSOverlapped->id;
if(nRet == 0)
{
//连接断开,重用断开的套接字
if(pSOverlapped != NULL)
{
if(BytesTransferred == 0 && pSOverlapped->IoMode != IoAccept && pSOverlapped->IoMode != IoDisconnect)
{
iError = GetLastError();
pIOCPFrame->OnError(nLocalID, iError);
continue;
}
} ErrorExit("GetQueuedCompletionStatus");
return 0;
} //由PostQueuedCompletionStatus 传来的消息 lpCompletionKey == NULL
if(Socket == NULL)
{
if(pSOverlapped->IoMode == IoShutdown)
{
//退出线程
break;
}
}

switch(pSOverlapped->IoMode)
{
case IoAccept:
{ //----------------------------------------
// Associate the accept socket with the completion port
AcceptSock = pIOCPFrame->GetSocket(nLocalID); DWORD dwBytes;
struct tcp_keepalive sKA_Settings = {0} , sReturned = {0} ;  
sKA_Settings.onoff = 1 ;  
sKA_Settings.keepalivetime = 5500 ; // Keep Alive in 5.5 sec.  
sKA_Settings.keepaliveinterval = 3000 ; // Resend if No-Reply  
if(SOCKET_ERROR==WSAIoctl(AcceptSock, SIO_KEEPALIVE_VALS, &sKA_Settings, sizeof(sKA_Settings), &sReturned, sizeof(sReturned), &dwBytes, NULL, NULL))
{
iError = WSAGetLastError();
pIOCPFrame->OnError(nLocalID, iError);
} setsockopt( AcceptSock, 
SOL_SOCKET, 
SO_UPDATE_ACCEPT_CONTEXT, 
(char *)&pIOCPFrame->m_sGateSvcSocket, 
sizeof(pIOCPFrame->m_sGateSvcSocket) ); if(CreateIoCompletionPort((HANDLE)AcceptSock, hCompletionPort, (DWORD)AcceptSock, 0) == NULL)
{
iError = GetLastError();
if(iError != 87) //提示参数不正确,忽略这个错误,程序正常进行,暂时跳过
{
pIOCPFrame->OnError(nLocalID, iError);
continue;
}
} pIOCPFrame->OnAccept(nLocalID);
pIOCPFrame->PostRecv(nLocalID); pIOCPFrame->PostAccept();

}
break;
case IoDisconnect:
{
char lpOutputBuf[1024];
int outBufLen = 1024;
DWORD dwBytes; pIOCPFrame->RecvOverlapped[nLocalID].IoMode = IoAccept;
pIOCPFrame->RecvOverlapped[nLocalID].id = nLocalID;
pIOCPFrame->SplitBuff[nLocalID].init(); pIOCPFrame->OnClose(nLocalID); if(!pIOCPFrame->lpfnAcceptEx(pIOCPFrame->m_sGateSvcSocket, 
pIOCPFrame->m_sSockets[nLocalID],
lpOutputBuf, 
0,//outBufLen - ((sizeof(sockaddr_in) + 16) * 2),
sizeof(sockaddr_in) + 16, 
sizeof(sockaddr_in) + 16, 
&dwBytes, 
(LPOVERLAPPED)&pIOCPFrame->RecvOverlapped[nLocalID]))
{
iError = WSAGetLastError();
if(iError != ERROR_IO_PENDING)
{
pIOCPFrame->OnError(nLocalID, iError);
continue;
}
} }
break;
case IoRecv:
{
pIOCPFrame->OnReceive(nLocalID, pSOverlapped->WsaBuf.buf, BytesTransferred); if(nLocalID == LOGICUDP_ID)
{
pIOCPFrame->PostRecvFrom(nLocalID);
}
else
{
pIOCPFrame->PostRecv(nLocalID);
}
}
break;
case IoSend:
{
STempSendOL* pTempOL = (STempSendOL*)pSOverlapped; pIOCPFrame->OnSend(nLocalID);
delete[] pTempOL->pBuffer;
delete pTempOL;
/*pTempOL->SentBytes += BytesTransferred;
if(pTempOL->SentBytes < pTempOL->TotalBytes)
{
assert(pTempOL->SentBytes <= pTempOL->TotalBytes);
DWORD RemainBytes = pTempOL->TotalBytes - pTempOL->SentBytes;
pTempOL->WsaBuf.buf = pTempOL->pBuffer + pTempOL->SentBytes;
pTempOL->WsaBuf.len = RemainBytes; DWORD SendBytes;
if ( WSASend(Socket, &pTempOL->WsaBuf, 1, &SendBytes, 0,
(LPOVERLAPPED)pTempOL, NULL)   ==   SOCKET_ERROR )
{   
int iError = WSAGetLastError();
if ( iError != ERROR_IO_PENDING )
{   
pIOCPFrame->OnError(nLocalID, iError);
continue;
}   
}   
}
else if(pTempOL->SentBytes == pTempOL->TotalBytes)
{
pIOCPFrame->OnSend(nLocalID);
delete[] pTempOL->pBuffer;
delete pTempOL;
}
else if (pTempOL->SentBytes > pTempOL->TotalBytes)
{
printf("数据异常\n");
}*/
}
break;
}

} delete pWorkerParam;
return 0;
}
GetQueuedCompletionStatus 下面第一个判定就是关于连接断开的,其中pSOverlapped->IoMode != IoAccept && pSOverlapped->IoMode != IoDisconnect 无关紧要,是在重用socket时用到的。
关键就是这个判定,我简单的写了一个客户端的程序,去连接服务端,连上几个以后,如果我直接关闭客户端程序,IOCP工作线程就可以进入这个断开的判定。如果通过closesocket在客户端关闭socket,这个判定就不会触发,和相应客户端连接的socket也没有任何异常,仍然可以投递接收请求,只不过每次投递以后,GetQueuedCompletionStatus 返回正常,返回的overlapped为空,lpNumberOfBytes 为零。这样本来应该关闭的socket,却一直在这里投递、返回,投递、返回。呵呵~~  说了这么多,有点没头绪了, 我就是想问,怎么知道对方的socket是通过closesocket关闭的 还是不是那样。

解决方案 »

  1.   

    额~~  还有顺便问一下,重用socket,将重用的socket再次绑定到完成端口的时候,会出现87这个错误,不知道怎么回事,但是不管这个错误的话,貌似对程序也没什么影响。if(CreateIoCompletionPort((HANDLE)AcceptSock, hCompletionPort, (DWORD)AcceptSock, 0) == NULL)
                    {
                        iError = GetLastError();
                        if(iError != 87)    //提示参数不正确,忽略这个错误,程序正常进行,暂时跳过
                        {
                            pIOCPFrame->OnError(nLocalID, iError);
                            continue;
                        }            
                    }
      

  2.   

    你指的不正常离开是通过closesocket,还是没有closesocket的断开,
    没有closesocket的断开通过GetQueuedCompletionStatus是可以判定的,我上面已经说了。现在我知道对方closesocket断开,GetQueuedCompletionStatus确实收不到消息
      

  3.   

    正常断开closesocket,异常断开指的是网络故障,系统当机什么的
      

  4.   


    很奇怪,正常断开应该是能够让对方知道的呀, 为什么现在成了正常断开对方不知道,反而不正常断开,对方知道呢?
    TCP连接的断开不都是通过那几个步骤吗? closesocket的时候难道不会进行TCP断开的那几个步骤》?
      

  5.   

    如果返回值是ERROR_SUCCESS,和接收的数据长度为0则说明正常断开
      

  6.   

    和客户协议正常断开的包. 客户正常断开时发送该包.然后断开.
     server收到后.就认为是正常断开了.否则认为非正常断开.
      

  7.   


    你说的非正常断开还要分两种情况,一种是通过closesocket关闭的,一种没有通过closesocket关闭(比如直接关掉程序,而程序的关闭操作里没有关闭现有socket句柄的操作)。
    这两种情况是不一样的