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

Posted

技术标签:

【中文标题】GetQueuedCompletionStatus 继续选择关闭套接字上的事件【英文标题】:GetQueuedCompletionStatus continues to select events on closed sockets 【发布时间】:2018-11-20 12:33:58 【问题描述】:

IOCP 服务器使用 WebSocket 连接。当浏览器发送关闭帧时,服务器deletes 这个客户端,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 (主管) 来自 127.0.0.1 的客户 324(主管) 总计:3 客户端 正在向 324 发送 33278 个字节 324 发送完成 308 发送完成 将 40529 字节发送到 324 发送完成 对于 324 发送 41128 字节到 324 发送完成对于 324 向 324 发送 40430 字节 324 发送完成 FIN_CLOSE on 客户端 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 已关闭。为什么它在析构函数的消息“Client 324 deleted”之后发生?

【问题讨论】:

【参考方案1】:

我认为您包含的代码不足以获得完整的图片,但这条线看起来很可疑:

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

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

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

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

无需销毁您的客户端对象以响应特定消息,只需关闭套接字句柄并清理您的其他结构以响应中止通知。您也可以考虑优雅地断开连接,而不是中止连接。

【讨论】:

当我关闭 C++ 客户端上的连接时,我在服务器上收到错误代码 64 (ERROR_NETNAME_DELETED) 并且所有操作都结束了。但是浏览器不会关闭它的套接字,所以连接描述符留在内核中。这很奇怪......我需要以某种方式清除这个描述符。如果它发生在单线程服务器中,我不敢想当我添加一些线程时会发生什么)) 确实!我阅读了有关 closesocket 功能的信息——这并不意味着与之相关的事件将停止。所以我只需要忽略这些事件? 可能,但您显然会在一次关闭时获得多个事件这一事实令人担忧。就像我说的那样,我认为这个问题缺少一些细节。 我也从两边找到了连接不关闭的原因。根据 WebSocket 规范,浏览器等待来自服务器的 echo-close 并且不会立即终止连接。 )

以上是关于GetQueuedCompletionStatus 继续选择关闭套接字上的事件的主要内容,如果未能解决你的问题,请参考以下文章

如何强制 GetQueuedCompletionStatus() 立即返回?

一些使用 WSASend 的 OVERLAPS 没有使用 GetQueuedCompletionStatus 及时返回?

当 GetQueuedCompletionStatus() 返回 FALSE 时,这些参数的值是多少?

GetQueuedCompletionStatus 返回后发生错误,错误号为 ERROR_INVALID_NETNAME

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

io 完成端口问题,每个 GetQueuedCompletionStatus 调用多个 wsarecv 或 wsasend