Сервер IOCP работает с соединениями WebSocket. Когда браузер отправляет закрытый кадр, сервер delete
с этим клиентом closesocket
Функция вызывается в деструкторе объекта клиента. Но даже после того, как розетка была закрыта, GetQueuedCompletionStatus
Функция продолжает выбирать события из этого сокета. Конечно, результат false
и 0 байтов передано, но клиент 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 удален«?
Я не думаю, что вы включили достаточно кода, чтобы получить полную картину, но эта строка выглядит подозрительно:
switch( QCS * (BytesTransfered > 0) * Client->OpCode() ){
Это проблематично по нескольким причинам. Первый, GetQueuedCompletionStatus
не гарантируется возвращение 1 в случае успеха. MSDN обещает только то, что он вернет ненулевое значение. Поэтому полагаться на конкретное значение для примера успеха рискованно. Во-вторых, у вас нет возможности различить неудавшийся вызов и успешный вызов, который возвращает 0 байтов. Вы действительно должны отделить свою логику для управления очередью и для отправки определенных событий ввода / вывода. Это облегчит понимание и поддержку вашего кода.
Вы также должны помнить, что у каждой розетки есть две стороны. Существуют структуры и дескриптор сокета, которые вы связываете с ним в пространстве пользователя, а затем есть объекты ядра, которые управляют деталями низкого уровня. То, что вы закрываете свой дескриптор на стороне пользователя, не означает, что объекты ядра исчезают. На объекты ядра ссылаются, они подсчитываются и обычно задерживаются до тех пор, пока не завершится весь ввод-вывод, связанный с этими объектами.
Вот почему вы все еще можете получать уведомления ввода-вывода для сокета после того, как он был «уничтожен» с точки зрения вашей программы. В частности, для сокетов будет выполняться последовательность выключения после вы закрыли свой дескриптор (потому что раньше вы явно не закрывали сокет).
Вместо того, чтобы уничтожать ваш объект Client в ответ на конкретное сообщение, просто закройте дескриптор сокета и очистите другие структуры в ответ на уведомление об отмене. Вы также можете подумать о том, чтобы сделать изящное отключение, а не прерывать соединение.
Других решений пока нет …