意外 WSA_IO_PENDING 阻塞(具有重叠的 I/O 属性)Winsock2 调用
Unexpected WSA_IO_PENDING from blocking (with overlapped I/O attribute) Winsock2 calls
短版:
使用阻塞套接字 API 调用时,我得到 WSA_IO_PENDING。我该如何处理?套接字有 overlapped I/O attribute 并设置了超时。
长版:
平台: Windows 10. Visual Studio 2015
一个socket以非常传统的简单方式创建
s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
套接字默认启用重叠 I/O 属性 。可以用getsockop / SO_OPENTYPE.
来验证
- 我确实需要重叠属性,因为我想使用超时功能,例如SO_SNDTIMEO.
- 而且我只会以阻塞(即同步)方式使用套接字。
- 套接字读取操作仅在单个线程中运行。
- 可以从与互斥量同步的不同线程执行套接字写操作。
套接字启用超时并保持活动...
::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, ...);
::setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, ...);
::WSAIoctl(s, SIO_KEEPALIVE_VALS, ...);
套接字操作完成
::send(s, sbuffer, ssize, 0);
和
::recv(s, rbuffer, rsize, 0);
我也尝试使用 WSARecv and WSASend 并将 lpOverlapped
和 lpCompletionRoutine
都设置为 NULL。
[MSDN] ... If both lpOverlapped and lpCompletionRoutine are NULL, the socket in
this function will be treated as a non-overlapped socket.
::WSARecv(s, &dataBuf, 1, &nBytesReceived, &flags, NULL/*lpOverlapped*/, NULL/*lpCompletionRoutine*/)
::WSASend(s, &dataBuf, 1, &nBytesSent, 0, NULL/*lpOverlapped*/, NULL/*lpCompletionRoutine*/)
问题:
那些 send / recv / WSARecv / WSASend 阻塞调用会 return 错误,错误代码为 WSA_IO_PENDING!
问题:
Q0:是否有任何关于阻塞调用和超时的重叠属性的引用?
它的行为如何?
如果我有一个启用重叠 "attribute" + 超时功能的套接字,并且只使用阻塞套接字 API 和 "none-overlapped I/O semantics".
我找不到关于它的任何参考资料(例如来自 MSDN)。
Q1:这是预期的行为吗?
我在将代码从 Win XP/Win 7 迁移到 Win 10.
后发现了这个问题 (get WSA_IO_PENDING)
这里是客户端代码部分:(注:真正的代码中并没有使用断言,这里只是描述了相应的错误会被处理,一个错误的套接字会停止程序..)
auto s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
assert(s != INVALID_SOCKET);
timeval timeout;
timeout.tv_sec = (long)(1500);
timeout.tv_usec = 0;
assert(::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)) != SOCKET_ERROR);
assert(::setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout)) != SOCKET_ERROR);
struct tcp_keepalive
{
unsigned long onoff;
unsigned long keepalivetime;
unsigned long keepaliveinterval;
} heartbeat;
heartbeat.onoff = (unsigned long)true;
heartbeat.keepalivetime = (unsigned long)3000;
heartbeat.keepaliveinterval = (unsigned long)3000;
DWORD nob = 0;
assert(0 == ::WSAIoctl(s, SIO_KEEPALIVE_VALS, &heartbeat, sizeof(heartbeat), 0, 0, &nob, 0, 0));
SOCKADDR_IN connection;
connection.sin_family = AF_INET;
connection.sin_port = ::htons(port);
connection.sin_addr.s_addr = ip;
assert(::connect(s, (SOCKADDR*)&connection, sizeof(connection)) != SOCKET_ERROR);
char buffer[100];
int receivedBytes = ::recv(s, buffer, 100, 0);
if (receivedBytes > 0)
{
// process buffer
}
else if (receivedBytes == 0)
{
// peer shutdown
// we will close socket s
}
else if (receivedBytes == SOCKET_ERROR)
{
const int lastError = ::WSAGetLastError();
switch (lastError)
{
case WSA_IO_PENDING:
//.... I get the error!
default:
}
}
Q2:我该如何处理?
忽略它?或者像通常的错误情况一样关闭套接字?
根据观察,一旦我得到 WSA_IO_PENDING,如果我只是忽略它,套接字最终将不再响应..
Q3:WSAGetOverlappedResult怎么样?
有意义吗?
我应该给什么 WSAOVERLAPPED 对象?由于没有这样的一个,我将其用于所有那些阻塞套接字调用。
我试过只创建一个新的空 WSAOVERLAPPED 并用它来调用 WSAGetOverlappedResult。它最终会 return 成功传输 0 字节。
Q3: How about WSAGetOverlappedResult
?
在[WSA]GetOverlappedResult
中我们只能使用指向WSAOVERLAPPED
的指针传递给I/O请求。使用任何其他指针是没有意义的。所有关于 I/O 操作 WSAGetOverlappedResult
的信息都来自 lpOverlapped
(最终状态,传输的字节数,如果需要等待 - 它等待事件发生重叠)。一般而言 - 每个 I/O 请求都必须将 OVERLAPPED
(IO_STATUS_BLOCK
真的)指针传递给内核。内核直接修改内存(最终状态和信息(通常是传输的字节)。因为OVERLAPPED
的这个生命周期必须有效,直到I/O不完整。并且必须是唯一的对于每个 I/O 请求。[WSA]GetOverlappedResult
检查此内存 OVERLAPPED
(IO_STATUS_BLOCK
真的) - 首先查找状态。如果另一个来自 STATUS_PENDING
- 这意味着操作已完成 - api 获取传输的字节数和 return。如果这里仍然 STATUS_PENDING
- I/O
尚未完成。如果我们想要等待 - api 使用 hEvent
从重叠到等待。此事件句柄在 I/O 请求期间传递给内核,并将设置为I/O 完成时的信号状态。等待任何其他事件是没有意义的 - 它与具体的 I/O 请求有何关系?想想现在必须清楚为什么我们只能通过 exactly 重叠指针传递给 I/O 请求来调用 [WSA]GetOverlappedResult
。
如果我们自己不传递指向 OVERLAPPED
的指针(例如,如果我们使用 recv
或 send
)低级套接字 api - 你自己分配 OVERLAPPED
作为堆栈中的局部变量并将其指针传递给I/O。结果 - 在这种情况下 api 不能 return 直到 I/O 未完成。因为重叠内存必须有效,直到 I/O 未完成(完成时内核将数据写入此内存)。但是在我们离开函数后局部变量变得无效。所以函数必须原地等待。
因为所有这些我们不能在 send
或 recv
之后调用 [WSA]GetOverlappedResult
- 起初我们根本没有指向重叠的指针。在 I/O 中使用的第二个重叠请求已经 "destroyed" (更确切地说在顶部下方的堆栈中 - 所以在垃圾区)。如果 I/O 尚未完成 - 内核已经在随机位置堆栈中修改数据,当它最终完成时 - 这将产生不可预测的影响 - 从什么都没有发生 - 崩溃或非常不寻常的副作用。如果 send
或 recv
return 在 I/O 完成之前 - 这将对进程产生致命影响。这绝不是必须的(如果 windows 中没有错误)。
Q2: How should I handle it?
如果 WSA_IO_PENDING
真的 return 被 send
或 recv
编辑,我将如何尝试解释 - 这是系统错误。如果 I/O 由设备完成并具有这样的结果(尽管它不能),则很好 - 只是一些未知的(对于这种情况)错误代码。像处理任何一般错误一样处理它。不需要特殊处理(比如异步 io)。如果 I/O 确实尚未完成(在 send
或 recv
returned 之后) - 这意味着在随机时间(可能是已经)您的堆栈可能已损坏。这种效果不可预测。在这里什么也做不了。这是严重的系统错误。
Q1: is it expected behavior?
不,这绝对不例外。
Q0: any reference on overlapped attribute with blocking call and
timeout?
首先,当我们创建文件句柄时,我们设置或不设置它的异步属性:如果 CreateFileW
- FILE_FLAG_OVERLAPPED
,如果 WSASocket
- WSA_FLAG_OVERLAPPED
.如果 NtOpenFile
或 NtCreateFile
- FILE_SYNCHRONOUS_IO_[NO]NALERT
(反向效果比较 FILE_FLAG_OVERLAPPED
)。 FILE_OBJECT
.Flags
- FO_SYNCHRONOUS_IO
(The file object is opened for synchronous I/O.) 中存储的所有这些信息将被设置或清除.
接下来是 FO_SYNCHRONOUS_IO
标志的作用:I/O 子系统通过 IofCallDriver
调用某些驱动程序,如果驱动程序 return STATUS_PENDING
- 如果在 FILE_OBJECT
中设置了 FO_SYNCHRONOUS_IO
标志 - 在原地等待(因此在内核中)直到 I/O 未完成。否则 return 这个状态 - STATUS_PENDING
对于调用者 - 它可以在原地等待,或者通过 APC 或 IOCP[=164= 接收回调].
当我们使用 socket
它内部调用 WSASocket
-
The socket that is created will have the overlapped attribute as a
default
这意味着文件将没有 FO_SYNCHRONOUS_IO
属性并且 低级别 I/O 调用可以 return STATUS_PENDING
来自内核。但让我们看看 recv
是如何工作的:
内部 WSPRecv
is called with lpOverlapped = 0
. because this - WSPRecv
yourself allocate OVERLAPPED
in stack, as local variable. before do actual I/O request via ZwDeviceIoControlFile
. because file (socket) created without FO_SYNCHRONOUS
flag - the STATUS_PENDING
is returned from kernel. in this case WSPRecv
look - are lpOverlapped == 0
. if yes - it can not return, until operation not complete. it begin wait on event (internally maintain in user mode for this socket) via SockWaitForSingleObject
- ZwWaitForSingleObject
. in place Timeout
used value which you associated with socket via SO_RCVTIMEO
or 0 (infinite wait) if you not set SO_RCVTIMEO
. if ZwWaitForSingleObject
return STATUS_TIMEOUT
(this can be only in case you set timeout via SO_RCVTIMEO
) - this mean that I/O operation not finished in excepted time. in this case WSPRecv
called SockCancelIo
(same effect as CancelIo
). CancelIo
不能 return (等待)直到所有 I/O 文件请求(来自当前线程)完成。在此之后 WSPRecv
从重叠中读取最终状态。这里必须是 STATUS_CANCELLED
(但实际上具体的驱动程序决定以哪个状态完全取消 IRP
)。 WSPRecv
将 STATUS_CANCELLED
转换为 STATUS_IO_TIMEOUT
。然后调用 NtStatusToSocketError
将 ntstatus 代码转换为 win32 错误。说 STATUS_IO_TIMEOUT
转换为 WSAETIMEDOUT
。但如果仍然是 STATUS_PENDING
重叠,在 CancelIo
之后 - 你得到 WSA_IO_PENDING
。只有在这种情况下。看起来像设备错误,但我无法在自己的 win 10 上重现它(可能是版本扮演角色)
这里可以做什么(如果你确定真的得到了WSA_IO_PENDING
)?首先尝试在没有 WSA_FLAG_OVERLAPPED
的情况下使用 WSASocket
- 在这种情况下 ZwDeviceIoControlFile
永远不会 return STATUS_PENDING
并且你永远不会得到 WSA_IO_PENDING
。检查这个 - 错误消失了吗?如果是 - return 重叠属性 并删除 SO_RCVTIMEO
调用(所有这些 用于测试 - 不是发布产品的解决方案)并在此错误消失后进行检查。如果是 - 看起来设备无效取消(使用 STATUS_PENDING
?!?)IRP。所有这一切的意义 - 找出错误更具体的地方。无论如何有趣的是构建最小的演示 exe,它可以稳定地重现这种情况并在另一个系统上测试它 - 这是否持续存在?仅适用于具体版本?如果它不能在另一个 comps 上复制 - 需要在你的混凝土上调试
短版: 使用阻塞套接字 API 调用时,我得到 WSA_IO_PENDING。我该如何处理?套接字有 overlapped I/O attribute 并设置了超时。
长版:
平台: Windows 10. Visual Studio 2015
一个socket以非常传统的简单方式创建
s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
套接字默认启用重叠 I/O 属性 。可以用getsockop / SO_OPENTYPE.
来验证- 我确实需要重叠属性,因为我想使用超时功能,例如SO_SNDTIMEO.
- 而且我只会以阻塞(即同步)方式使用套接字。
- 套接字读取操作仅在单个线程中运行。
- 可以从与互斥量同步的不同线程执行套接字写操作。
套接字启用超时并保持活动...
::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, ...);
::setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, ...);
::WSAIoctl(s, SIO_KEEPALIVE_VALS, ...);
套接字操作完成
::send(s, sbuffer, ssize, 0);
和
::recv(s, rbuffer, rsize, 0);
我也尝试使用 WSARecv and WSASend 并将 lpOverlapped
和 lpCompletionRoutine
都设置为 NULL。
[MSDN] ... If both lpOverlapped and lpCompletionRoutine are NULL, the socket in this function will be treated as a non-overlapped socket.
::WSARecv(s, &dataBuf, 1, &nBytesReceived, &flags, NULL/*lpOverlapped*/, NULL/*lpCompletionRoutine*/)
::WSASend(s, &dataBuf, 1, &nBytesSent, 0, NULL/*lpOverlapped*/, NULL/*lpCompletionRoutine*/)
问题:
那些 send / recv / WSARecv / WSASend 阻塞调用会 return 错误,错误代码为 WSA_IO_PENDING!
问题:
Q0:是否有任何关于阻塞调用和超时的重叠属性的引用?
它的行为如何? 如果我有一个启用重叠 "attribute" + 超时功能的套接字,并且只使用阻塞套接字 API 和 "none-overlapped I/O semantics".
我找不到关于它的任何参考资料(例如来自 MSDN)。
Q1:这是预期的行为吗?
我在将代码从 Win XP/Win 7 迁移到 Win 10.
后发现了这个问题 (get WSA_IO_PENDING)这里是客户端代码部分:(注:真正的代码中并没有使用断言,这里只是描述了相应的错误会被处理,一个错误的套接字会停止程序..)
auto s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
assert(s != INVALID_SOCKET);
timeval timeout;
timeout.tv_sec = (long)(1500);
timeout.tv_usec = 0;
assert(::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)) != SOCKET_ERROR);
assert(::setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout)) != SOCKET_ERROR);
struct tcp_keepalive
{
unsigned long onoff;
unsigned long keepalivetime;
unsigned long keepaliveinterval;
} heartbeat;
heartbeat.onoff = (unsigned long)true;
heartbeat.keepalivetime = (unsigned long)3000;
heartbeat.keepaliveinterval = (unsigned long)3000;
DWORD nob = 0;
assert(0 == ::WSAIoctl(s, SIO_KEEPALIVE_VALS, &heartbeat, sizeof(heartbeat), 0, 0, &nob, 0, 0));
SOCKADDR_IN connection;
connection.sin_family = AF_INET;
connection.sin_port = ::htons(port);
connection.sin_addr.s_addr = ip;
assert(::connect(s, (SOCKADDR*)&connection, sizeof(connection)) != SOCKET_ERROR);
char buffer[100];
int receivedBytes = ::recv(s, buffer, 100, 0);
if (receivedBytes > 0)
{
// process buffer
}
else if (receivedBytes == 0)
{
// peer shutdown
// we will close socket s
}
else if (receivedBytes == SOCKET_ERROR)
{
const int lastError = ::WSAGetLastError();
switch (lastError)
{
case WSA_IO_PENDING:
//.... I get the error!
default:
}
}
Q2:我该如何处理?
忽略它?或者像通常的错误情况一样关闭套接字?
根据观察,一旦我得到 WSA_IO_PENDING,如果我只是忽略它,套接字最终将不再响应..
Q3:WSAGetOverlappedResult怎么样?
有意义吗?
我应该给什么 WSAOVERLAPPED 对象?由于没有这样的一个,我将其用于所有那些阻塞套接字调用。
我试过只创建一个新的空 WSAOVERLAPPED 并用它来调用 WSAGetOverlappedResult。它最终会 return 成功传输 0 字节。
Q3: How about
WSAGetOverlappedResult
?
在[WSA]GetOverlappedResult
中我们只能使用指向WSAOVERLAPPED
的指针传递给I/O请求。使用任何其他指针是没有意义的。所有关于 I/O 操作 WSAGetOverlappedResult
的信息都来自 lpOverlapped
(最终状态,传输的字节数,如果需要等待 - 它等待事件发生重叠)。一般而言 - 每个 I/O 请求都必须将 OVERLAPPED
(IO_STATUS_BLOCK
真的)指针传递给内核。内核直接修改内存(最终状态和信息(通常是传输的字节)。因为OVERLAPPED
的这个生命周期必须有效,直到I/O不完整。并且必须是唯一的对于每个 I/O 请求。[WSA]GetOverlappedResult
检查此内存 OVERLAPPED
(IO_STATUS_BLOCK
真的) - 首先查找状态。如果另一个来自 STATUS_PENDING
- 这意味着操作已完成 - api 获取传输的字节数和 return。如果这里仍然 STATUS_PENDING
- I/O
尚未完成。如果我们想要等待 - api 使用 hEvent
从重叠到等待。此事件句柄在 I/O 请求期间传递给内核,并将设置为I/O 完成时的信号状态。等待任何其他事件是没有意义的 - 它与具体的 I/O 请求有何关系?想想现在必须清楚为什么我们只能通过 exactly 重叠指针传递给 I/O 请求来调用 [WSA]GetOverlappedResult
。
如果我们自己不传递指向 OVERLAPPED
的指针(例如,如果我们使用 recv
或 send
)低级套接字 api - 你自己分配 OVERLAPPED
作为堆栈中的局部变量并将其指针传递给I/O。结果 - 在这种情况下 api 不能 return 直到 I/O 未完成。因为重叠内存必须有效,直到 I/O 未完成(完成时内核将数据写入此内存)。但是在我们离开函数后局部变量变得无效。所以函数必须原地等待。
因为所有这些我们不能在 send
或 recv
之后调用 [WSA]GetOverlappedResult
- 起初我们根本没有指向重叠的指针。在 I/O 中使用的第二个重叠请求已经 "destroyed" (更确切地说在顶部下方的堆栈中 - 所以在垃圾区)。如果 I/O 尚未完成 - 内核已经在随机位置堆栈中修改数据,当它最终完成时 - 这将产生不可预测的影响 - 从什么都没有发生 - 崩溃或非常不寻常的副作用。如果 send
或 recv
return 在 I/O 完成之前 - 这将对进程产生致命影响。这绝不是必须的(如果 windows 中没有错误)。
Q2: How should I handle it?
如果 WSA_IO_PENDING
真的 return 被 send
或 recv
编辑,我将如何尝试解释 - 这是系统错误。如果 I/O 由设备完成并具有这样的结果(尽管它不能),则很好 - 只是一些未知的(对于这种情况)错误代码。像处理任何一般错误一样处理它。不需要特殊处理(比如异步 io)。如果 I/O 确实尚未完成(在 send
或 recv
returned 之后) - 这意味着在随机时间(可能是已经)您的堆栈可能已损坏。这种效果不可预测。在这里什么也做不了。这是严重的系统错误。
Q1: is it expected behavior?
不,这绝对不例外。
Q0: any reference on overlapped attribute with blocking call and timeout?
首先,当我们创建文件句柄时,我们设置或不设置它的异步属性:如果 CreateFileW
- FILE_FLAG_OVERLAPPED
,如果 WSASocket
- WSA_FLAG_OVERLAPPED
.如果 NtOpenFile
或 NtCreateFile
- FILE_SYNCHRONOUS_IO_[NO]NALERT
(反向效果比较 FILE_FLAG_OVERLAPPED
)。 FILE_OBJECT
.Flags
- FO_SYNCHRONOUS_IO
(The file object is opened for synchronous I/O.) 中存储的所有这些信息将被设置或清除.
接下来是 FO_SYNCHRONOUS_IO
标志的作用:I/O 子系统通过 IofCallDriver
调用某些驱动程序,如果驱动程序 return STATUS_PENDING
- 如果在 FILE_OBJECT
中设置了 FO_SYNCHRONOUS_IO
标志 - 在原地等待(因此在内核中)直到 I/O 未完成。否则 return 这个状态 - STATUS_PENDING
对于调用者 - 它可以在原地等待,或者通过 APC 或 IOCP[=164= 接收回调].
当我们使用 socket
它内部调用 WSASocket
-
The socket that is created will have the overlapped attribute as a default
这意味着文件将没有 FO_SYNCHRONOUS_IO
属性并且 低级别 I/O 调用可以 return STATUS_PENDING
来自内核。但让我们看看 recv
是如何工作的:
内部 WSPRecv
is called with lpOverlapped = 0
. because this - WSPRecv
yourself allocate OVERLAPPED
in stack, as local variable. before do actual I/O request via ZwDeviceIoControlFile
. because file (socket) created without FO_SYNCHRONOUS
flag - the STATUS_PENDING
is returned from kernel. in this case WSPRecv
look - are lpOverlapped == 0
. if yes - it can not return, until operation not complete. it begin wait on event (internally maintain in user mode for this socket) via SockWaitForSingleObject
- ZwWaitForSingleObject
. in place Timeout
used value which you associated with socket via SO_RCVTIMEO
or 0 (infinite wait) if you not set SO_RCVTIMEO
. if ZwWaitForSingleObject
return STATUS_TIMEOUT
(this can be only in case you set timeout via SO_RCVTIMEO
) - this mean that I/O operation not finished in excepted time. in this case WSPRecv
called SockCancelIo
(same effect as CancelIo
). CancelIo
不能 return (等待)直到所有 I/O 文件请求(来自当前线程)完成。在此之后 WSPRecv
从重叠中读取最终状态。这里必须是 STATUS_CANCELLED
(但实际上具体的驱动程序决定以哪个状态完全取消 IRP
)。 WSPRecv
将 STATUS_CANCELLED
转换为 STATUS_IO_TIMEOUT
。然后调用 NtStatusToSocketError
将 ntstatus 代码转换为 win32 错误。说 STATUS_IO_TIMEOUT
转换为 WSAETIMEDOUT
。但如果仍然是 STATUS_PENDING
重叠,在 CancelIo
之后 - 你得到 WSA_IO_PENDING
。只有在这种情况下。看起来像设备错误,但我无法在自己的 win 10 上重现它(可能是版本扮演角色)
这里可以做什么(如果你确定真的得到了WSA_IO_PENDING
)?首先尝试在没有 WSA_FLAG_OVERLAPPED
的情况下使用 WSASocket
- 在这种情况下 ZwDeviceIoControlFile
永远不会 return STATUS_PENDING
并且你永远不会得到 WSA_IO_PENDING
。检查这个 - 错误消失了吗?如果是 - return 重叠属性 并删除 SO_RCVTIMEO
调用(所有这些 用于测试 - 不是发布产品的解决方案)并在此错误消失后进行检查。如果是 - 看起来设备无效取消(使用 STATUS_PENDING
?!?)IRP。所有这一切的意义 - 找出错误更具体的地方。无论如何有趣的是构建最小的演示 exe,它可以稳定地重现这种情况并在另一个系统上测试它 - 这是否持续存在?仅适用于具体版本?如果它不能在另一个 comps 上复制 - 需要在你的混凝土上调试