从 IOCP 线程调用 WSAGetLastError() 将返回不正确的结果

Calling WSAGetLastError() from an IOCP thread return incorrect result

本文关键字:返回 不正确 结果 IOCP 线程 调用 WSAGetLastError      更新时间:2023-10-16

我调用了返回WSA_IO_PENDING WSARecv()。然后,我从另一端发送了一个RST数据包。存在于另一个线程中的 GetQueuedCompletionStatus() 函数按预期返回了FALSE,但是当我调用WSAGetLastError()时,我得到了64而不是WSAECONNRESET

那么WSAGetLastError()为什么没有WSAECONNRESET回来呢?


编辑:

我忘了提到,当我在WSARecv()失败后直接呼叫WSAGetLastError()时(因为收到RST数据包),返回的错误代码是WSAECONNRESET而不是64

因此,看起来返回的错误代码取决于WSARecv()是在调用它后直接失败,还是稍后在检索完成数据包时失败。

这是 IOCP 的一般性问题,您正在对 TCP/IP 驱动程序堆栈进行低级别调用。 与Windows中的所有驱动程序一样,它报告失败并带有NTSTATUS错误代码。 此处的预期错误是STATUS_CONNECTION_RESET。

这些本机错误代码需要转换为 winapi 错误代码。 此转换通常是上下文相关的,这取决于哪个 winapi 库发出了驱动程序命令。 换句话说,只有当是Winsock库执行翻译时,您才能返回WSAECONNSET错误。 但这不是程序中发生的事情,而是 GetQueuedCompletionStatus() 处理了错误。

这是一个通用帮助程序函数,用于处理任何设备驱动程序的 IOCP。 没有上下文,重叠结构不足以指示 I/O 请求是如何开始的。 转到此知识库文章,它记录了从 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() 为线程设置"最后一个错误"代码时,NTSTATUS 代码丢失了。 所以它没有,它只是返回它能返回的内容。


你所期望的是一个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()获取正确的返回值。 这不是很直观的:)