GetQueuedCompletionStatus продолжает выбирать события на закрытых сокетах - PullRequest
0 голосов
/ 20 ноября 2018

Сервер IOCP работает с соединениями WebSocket.Когда браузер отправляет закрытый кадр, сервер delete s этого клиента, функция closesocket вызывает в деструкторе объекта клиента.Но даже после того, как сокет был закрыт, функция GetQueuedCompletionStatus продолжает выбирать события из этого сокета.Конечно, результат false и 0 байтов переданы, но Client ptr и OVERLAPPED ptr не равны NULL, а GetLastError возвращает 1236 (ERROR_CONNECTION_ABORTED) ... так что да, он отменен, и closesocket былназывается ... Но почему это все еще здесь ???И как перестать получать эти «бесполезные» события?Я могу вызвать continue в цикле thead, но это будет тратить впустую процессорное время, если функция выберет этого удаленного клиента навсегда.

Вот часть цикла рабочего потока:

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);
}

... и журнал сервера:

Клиент 296 от 127.0.0.1 (агент 1987)
Клиент 308 от 127.0.0.1 (руководитель)
Клиент 324 из 127.0.0.1 (супервизор)
ИТОГО: 3 клиента (ей)
Отправка 33278 байтов в 324
Отправка завершена для 324
Отправка завершена для 308
Отправка 40529байт до 324
отправка завершена для 324
отправка 41128 байт для 324
отправка завершена для 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 голосов
/ 20 ноября 2018

Не думаю, что вы включили достаточно кода, чтобы получить полную картину, но эта строка выглядит подозрительно:

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

Это проблематично по нескольким причинам.Во-первых, GetQueuedCompletionStatus не гарантирует возврата 1 в случае успеха.MSDN обещает только то, что он вернет ненулевое значение.Поэтому полагаться на конкретное значение для примера успеха рискованно.Во-вторых, у вас нет возможности различить неудавшийся вызов и успешный вызов, который возвращает 0 байтов.Вы действительно должны отделить свою логику для управления очередью и для отправки определенных событий ввода / вывода.Это облегчит понимание и поддержку вашего кода.

Вы также должны помнить, что у каждого сокета есть две стороны.Существуют структуры и дескриптор сокета, которые вы связываете с ним в пространстве пользователя, а затем есть объекты ядра, которые управляют деталями низкого уровня.То, что вы закрываете свой дескриптор на стороне пользователя, не означает, что объекты ядра исчезают.На объекты ядра ссылаются, они подсчитываются и, как правило, задерживаются до тех пор, пока не завершится весь ввод-вывод, связанный с этими объектами.

Вот почему вы все еще можете получать уведомления ввода-вывода для сокета после того, как он был «уничтожен» с точки зрения вашей программы.В частности, для сокетов последовательность выключения будет иметь место после того, как вы закроете свой дескриптор (потому что ранее вы не отключили сокет явно).

Вместо уничтожения объекта Clientв ответ на конкретное сообщение просто закройте дескриптор сокета и очистите другие структуры в ответ на уведомление об отмене.Вы также можете подумать о том, чтобы сделать изящное отключение, а не прерывать соединение.

...