GetQueuedCompletionStatus继续选择关闭套接字上的事件

时间:2018-11-20 12:33:59

标签: c++ winsock iocp

IOCP服务器可用于WebSocket连接。当浏览器发送关闭帧时,服务器delete是该客户端,closesocket函数正在调用客户端的对象析构函数。但是,即使在关闭套接字后,GetQueuedCompletionStatus函数仍继续从该套接字中选择事件。当然,结果是false和0字节被传输,但是客户端ptr和OVERLAPPED ptr不为NULL,并且GetLastError返回1236(ERROR_CONNECTION_ABORTED)...所以是的,它被中止了, closesocket被称为...但是为什么它仍在这里???以及如何停止接收此“无用”事件?我可以在thead的循环中调用continue,但是如果函数永远选择此已删除的客户端,则会浪费CPU时间。

这是工作线程循环的一部分:

while(WAIT_OBJECT_0 != WaitForSingleObject(EventShutdown, 0)){
    DWORD BytesTransfered = 0;
    OVERLAPPED *asyncinfo = nullptr;
    client *Client = nullptr;
    BOOL QCS = GetQueuedCompletionStatus(hIOCP, &BytesTransfered, (PULONG_PTR)&Client, &asyncinfo, INFINITE);
    if(!Client ) break;

    switch( QCS * (BytesTransfered > 0) * Client->OpCode() ){
        case OP_TYPE_RECV:{
        .....
            switch( recv_buf[0] &0xFF ){
            ....
                case FIN_CLOSE:
                    printf("FIN_CLOSE on client %u\n", Client->Socket());
                default:{
                    RemoveClient(Client);
                    break;
                }
            }
        }
        case OP_TYPE_SEND:{
        ...
        }
        default:{
            printf("Client %u (%lu bytes transferred, QCS is %d)\n", Client->Socket(), BytesTransfered, QCS);
            break;
        }

客户的析构函数:

client::~client(){
    while(!HasOverlappedIoCompleted(&asyncinfo)) Sleep(0);
    closesocket(socket);
    if( a_ctx ) delete a_ctx;
    if( q_ctx ) delete q_ctx;
    delete [] data_buffer;
    printf("Client %u deleted\n", socket);
}

...和服务器日志:

  

来自127.0.0.1的客户端296(代理1987)
来自127.0.0.1的客户端308   (主管)
来自324.0.1.0.1的客户端324
总数:3   客户端
向324发送33278字节
324发送完成
  发送完成308个
发送40529字节到324
发送完成   为324
向324发送41128字节
为324发送完成   将40430字节发送到324
发送完成,完成324
FIN_CLOSE   客户端324
客户端324已删除
客户端324(已传输0字节,   QCS为0)
客户端324(已传输0字节,QCS为0)
客户端   324(已传输0个字节,QCS为0)
客户端324(0个字节   已传输,QCS为0)
客户端324(已传输0字节,QCS为   0)

看到“ 324(已传输0个字节,QCS为0)”吗?插座324已关闭。为什么在析构函数的消息“ 客户端324已删除”之后发生?

1 个答案:

答案 0 :(得分:1)

我认为您没有包含足够的代码来获得完整的图片,但是此行看起来很可疑:

switch( QCS * (BytesTransfered > 0) * Client->OpCode() ){

这有两个原因。首先,不能保证GetQueuedCompletionStatus成功返回1。 MSDN仅承诺它将返回非零。因此,为成功案例依赖特定的值是有风险的。其次,您无法区分失败的调用和返回0字节的成功调用。您应该真正区分用于管理出队和分配特定I / O事件的逻辑。这将使您的代码更易于理解和维护。

您还必须记住,每个插槽都有两个侧面。在用户空间中有与之关联的结构和套接字句柄,然后还有管理低级详细信息的内核对象。仅仅因为在用户端关闭了句柄并不意味着内核对象就消失了。内核对象被引用计数,并且通常会持续存在,直到涉及这些对象的所有I / O完成为止。

这就是为什么从程序角度来看,在套接字被“破坏”之后,仍然可以获得套接字的I / O通知的原因。特别是对于套接字,关闭序列将在您关闭句柄之后 进行(因为在此之前您没有明确关闭套接字)。

代替响应特定消息而销毁Client对象,只需关闭套接字句柄并清理其他结构以响应中止通知即可。您也可以考虑进行正常的断开连接,而不是中止连接。