从 IOCP 线程调用 WSAGetLastError() return 不正确的结果
Calling WSAGetLastError() from an IOCP thread return incorrect result
我调用了 WSARecv()
,return 调用了 WSA_IO_PENDING
。然后我从另一端发送了一个 RST
数据包。存在于另一个线程中的 GetQueuedCompletionStatus()
函数按预期具有 returned FALSE
,但是当我调用 WSAGetLastError()
时,我得到 64
而不是 WSAECONNRESET
.
那为什么WSAGetLastError()
没有returnWSAECONNRESET
?
编辑:
我忘了说,当我在 WSARecv()
失败后直接调用 WSAGetLastError()
时(因为收到了 RST
数据包),错误代码 returned是 WSAECONNRESET
而不是 64
.
所以看起来错误代码returned取决于WSARecv()
是在调用之后直接失败,还是稍后在检索完成数据包时失败。
这是 IOCP 的一般问题,您正在对 TCP/IP 驱动程序堆栈进行低级调用。其中,正如所有驱动程序在 Windows 中所做的那样,使用 NTSTATUS 错误代码报告失败。这里的预期错误是 STATUS_CONNECTION_RESET.
这些本机错误代码需要转换为 winapi 错误代码。这个翻译通常是 context-sensitive,它取决于哪个 winapi 库发出了驱动程序命令。换句话说,如果是 Winsock 库进行了翻译,您只能返回 WSAECONNRESET 错误。但这不是您的程序中发生的情况,是 GetQueuedCompletionStatus() 处理了错误。
这是处理任何设备驱动程序的 IOCP 的通用辅助函数。没有上下文,OVERLAPPED 结构不足以表明 I/O 请求是如何开始的。转到this KB article,它记录了从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 特定错误,当 GetQueuedCompletionStatus() 为线程设置 "last error" 代码时,NTSTATUS 代码丢失。所以它没有,它只是 return 它能做到的。
您 期望 的是一个 WSAGetQueuedCompletionStatus() 函数,因此可以使用 Winsock 规则正确地进行错误转换。没有一个。这些天来,我更喜欢使用关于如何正确编写 Windows 代码的最终权威,Reference Source 提供的 .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() 中获取正确的 return 值。这不是很直观:)
我调用了 WSARecv()
,return 调用了 WSA_IO_PENDING
。然后我从另一端发送了一个 RST
数据包。存在于另一个线程中的 GetQueuedCompletionStatus()
函数按预期具有 returned FALSE
,但是当我调用 WSAGetLastError()
时,我得到 64
而不是 WSAECONNRESET
.
那为什么WSAGetLastError()
没有returnWSAECONNRESET
?
编辑:
我忘了说,当我在 WSARecv()
失败后直接调用 WSAGetLastError()
时(因为收到了 RST
数据包),错误代码 returned是 WSAECONNRESET
而不是 64
.
所以看起来错误代码returned取决于WSARecv()
是在调用之后直接失败,还是稍后在检索完成数据包时失败。
这是 IOCP 的一般问题,您正在对 TCP/IP 驱动程序堆栈进行低级调用。其中,正如所有驱动程序在 Windows 中所做的那样,使用 NTSTATUS 错误代码报告失败。这里的预期错误是 STATUS_CONNECTION_RESET.
这些本机错误代码需要转换为 winapi 错误代码。这个翻译通常是 context-sensitive,它取决于哪个 winapi 库发出了驱动程序命令。换句话说,如果是 Winsock 库进行了翻译,您只能返回 WSAECONNRESET 错误。但这不是您的程序中发生的情况,是 GetQueuedCompletionStatus() 处理了错误。
这是处理任何设备驱动程序的 IOCP 的通用辅助函数。没有上下文,OVERLAPPED 结构不足以表明 I/O 请求是如何开始的。转到this KB article,它记录了从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 特定错误,当 GetQueuedCompletionStatus() 为线程设置 "last error" 代码时,NTSTATUS 代码丢失。所以它没有,它只是 return 它能做到的。
您 期望 的是一个 WSAGetQueuedCompletionStatus() 函数,因此可以使用 Winsock 规则正确地进行错误转换。没有一个。这些天来,我更喜欢使用关于如何正确编写 Windows 代码的最终权威,Reference Source 提供的 .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() 中获取正确的 return 值。这不是很直观:)