Вызов WSAGetLastError() из потока IOCP возвращает неверный результат

Я вызвал WSARecv(), который вернул WSA_IO_PENDING. Затем я отправил пакет RST с другого конца. Функция GetQueuedCompletionStatus(), существующая в другом потоке, вернула FALSE, как и ожидалось, но когда я вызвал WSAGetLastError(), я получил 64 вместо WSAECONNRESET.

Так почему же WSAGetLastError() не вернул WSAECONNRESET?


Изменить:

Я забыл упомянуть, что когда я вызываю WSAGetLastError() сразу после отказа WSARecv() (из-за полученного пакета RST), возвращается код ошибки WSAECONNRESET, а не 64.

Таким образом, похоже, что возвращаемый код ошибки зависит от того, произошел ли сбой WSARecv() сразу после его вызова или позже, при получении пакета завершения.


person Tom    schedule 08.03.2015    source источник
comment
64 это ERROR_NETNAME_DELETED. Именно так это и работает. Вам придется иметь дело с этим. По моему опыту использования WinSock IOCP, он обычно сообщает ERROR_NETNAME_DELETED, а не WSAECONNRESET, поэтому просто обрабатывайте обе ошибки, как если бы они были одинаковыми.   -  person Remy Lebeau    schedule 08.03.2015
comment
@Remy Lebeau Будут ли все коды ошибок, возвращаемые WSAGetLastError() внутри потока IOCP, быть ERROR_NETNAME_DELETED или просто WSAECONNRESET?   -  person Tom    schedule 08.03.2015
comment
Не все ошибки, но я не знаю, какие именно сопоставляются с ERROR_NETNAME_DELETED. Я всегда отношусь к этому как к неожиданному разрыву связи. Действительно ли имеет значение, было ли это вызвано RST или нет? Это не изящное отключение в ту или иную сторону.   -  person Remy Lebeau    schedule 08.03.2015
comment
@Remy Lebeau В моем случае это не имеет значения, однако знание причины отключения может иметь значение в других случаях.   -  person Tom    schedule 08.03.2015


Ответы (1)


Это общая проблема с IOCP, вы делаете низкоуровневый вызов стека драйверов TCP/IP. Которые, как и все драйверы в Windows, сообщают о сбое с кодами ошибок NTSTATUS. Здесь ожидается ошибка STATUS_CONNECTION_RESET.

Эти собственные коды ошибок необходимо преобразовать в код ошибки winapi. Этот перевод обычно зависит от контекста, это зависит от того, какая библиотека winapi выдала команду драйвера. Другими словами, вы можете вернуть ошибку WSAECONNRESET только в том случае, если это была библиотека Winsock, которая выполнила перевод. Но это не то, что произошло в вашей программе, это была GetQueuedCompletionStatus(), которая обработала ошибку.

Это общая вспомогательная функция, которая обрабатывает IOCP для любого драйвера устройства. Здесь нет контекста, структуры OVERLAPPED недостаточно, чтобы указать, как был запущен запрос ввода-вывода. Перейдите к этой статье базы знаний, в ней описано сопоставление по умолчанию кодов ошибок NTSTATUS с кодами ошибок winapi. Отображение, которое использует GetQueuedCompletionStatus(). Соответствующие записи в списке:

STATUS_NETWORK_NAME_DELETED          ERROR_NETNAME_DELETED
STATUS_LOCAL_DISCONNECT              ERROR_NETNAME_DELETED
STATUS_REMOTE_DISCONNECT             ERROR_NETNAME_DELETED
STATUS_ADDRESS_CLOSED                ERROR_NETNAME_DELETED
STATUS_CONNECTION_DISCONNECTED       ERROR_NETNAME_DELETED
STATUS_CONNECTION_RESET              ERROR_NETNAME_DELETED 

Это был, гм, не фантастический выбор. Вероятно, восходит к очень ранней Windows, когда Lanman был предпочтительным сетевым уровнем. WSAGetLastError() довольно бессилен для сопоставления ERROR_NETNAME_DELETED обратно с конкретной ошибкой WSA, код NTSTATUS был потерян, когда GetQueuedCompletionStatus() установил код "последней ошибки" для потока. Так что это не так, он просто возвращает то, что может.


Что вы ожидаете, так это функцию WSAGetQueuedCompletionStatus(), чтобы этот перевод ошибок мог происходить правильно, используя правила Winsock. Нет ни одного. В настоящее время я предпочитаю пользоваться высшим авторитетом в том, как правильно писать код для Windows, исходным кодом .NET Framework, доступным по адресу Источник ссылок. Я связался с источником метода SocketAsyncEventArgs.CompletionCallback(). Который содержит ключ:

// The Async IO completed with a failure.
// here we need to call WSAGetOverlappedResult() just so Marshal.GetLastWin32Error() will return the correct error.
bool success = UnsafeNclNativeMethods.OSSOCK.WSAGetOverlappedResult(
    m_CurrentSocket.SafeHandle,
    m_PtrNativeOverlapped,
    out numBytes,
    false,
    out socketFlags);
socketError = (SocketError)Marshal.GetLastWin32Error();

Или, другими словами, вам нужно сделать дополнительный вызов WSAGetOverlappedResult(), чтобы получить правильное возвращаемое значение из GetLastError(). Это не очень интуитивно :)

person Community    schedule 10.03.2015