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

GetQueuedCompletionStatus continues to select events on closed sockets

IOCP 服务器使用 WebSocket 连接。当浏览器发送关闭帧时,服务器deletes 这个客户端,closesocket 函数正在调用客户端的对象析构函数。但即使在套接字关闭后,GetQueuedCompletionStatus 函数仍继续处理来自该套接字的 select 事件。结果当然是false和0字节传输,但是Client ptr和OVERLAPPED ptr不为NULL,而且GetLastError returns 1236(ERROR_CONNECTION_ABORTED).. .所以是的,它被中止了,closesocket 被调用了……但为什么它还在这里???以及如何停止接收此 "useless" 事件?我可以在 thead 的循环中调用 continue,但是如果该函数将永远 select 这个已删除的客户端,它将浪费 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);
}

...和服务器的日志:

Client 296 from 127.0.0.1 (agent 1987)
Client 308 from 127.0.0.1 (supervisor)
Client 324 from 127.0.0.1 (supervisor)
TOTAL: 3 client(s)
Sending 33278 bytes to 324
Send finished for 324
Send finished for 308
Sending 40529 bytes to 324
Send finished for 324
Sending 41128 bytes to 324
Send finished for 324
Sending 40430 bytes to 324
Send finished for 324
FIN_CLOSE on client 324
Client 324 deleted
Client 324 (0 bytes transferred, QCS is 0)
Client 324 (0 bytes transferred, QCS is 0)
Client 324 (0 bytes transferred, QCS is 0)
Client 324 (0 bytes transferred, QCS is 0)
Client 324 (0 bytes transferred, QCS is 0)

看到“324(传输 0 个字节,QCS 为 0)”?套接字 324 已关闭。为什么它发生在析构函数消息“Client 324 deleted”之后?

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

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

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

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

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

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