简短版本 : 使用阻塞套接字API调用时出现WSA_IO_PENDING。我应该如何处理?套接字具有overlapped I/O attribute并设置了超时。
长版:
平台:Windows10。Visual Studio 2015
socket是用非常传统的简单方法创建的。
s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
默认情况下,套接字已启用重叠的I / O 属性。可以使用getsockop / SO_OPENTYPE进行验证。
套接字已启用超时功能,并通过...保持活动状态
::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);
我还尝试将lpOverlapped
和lpCompletionRoutine
都设置为NULL的WSARecv和WSASend。
[MSDN] ...如果lpOverlapped和lpCompletionRoutine均为NULL,则套接字在 此功能将被视为不重叠的套接字。
::WSARecv(s, &dataBuf, 1, &nBytesReceived, &flags, NULL/*lpOverlapped*/, NULL/*lpCompletionRoutine*/)
::WSASend(s, &dataBuf, 1, &nBytesSent, 0, NULL/*lpOverlapped*/, NULL/*lpCompletionRoutine*/)
问题:
那些发送/接收/ WSARecv / WSASend阻止调用将返回错误代码为 WSA_IO_PENDING 的错误!
问题:
Q0:关于重叠属性的任何引用都具有阻塞的调用和超时?
它的表现如何? 如果我的套接字具有重叠的“属性” +超时功能启用,并且仅使用具有“无重叠I / O语义”的阻塞套接字API。
我找不到有关它的任何参考(例如,来自MSDN)。
第一季度:这是预期的行为吗?
将代码从Win XP / Win 7迁移到 Win 10 后,我观察到此问题(获取WSA_IO_PENDING)。
这是客户端代码部分:(请注意:assert不在实际代码中使用,而仅在此处描述将处理相应的错误,并且有错误的套接字将停止该过程。)
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:
}
}
第二季度:我应该如何处理?
忽略它吗?还是只是在通常的错误情况下关闭套接字?
从观察结果中,一旦我得到WSA_IO_PENDING,并且如果我只是忽略它,套接字最终将变得不再响应。
第三季度:WSAGetOverlappedResult怎么样?
这有意义吗?
我应该给哪个WSAOVERLAPPED对象?由于没有这样的程序,因此我将所有阻塞套接字调用都使用。
我尝试仅创建一个新的空WSAOVERLAPPED并使用它来调用WSAGetOverlappedResult。最终将成功返回0字节并返回。
答案 0 :(得分:1)
Q3:
WSAGetOverlappedResult
怎么样?
在[WSA]GetOverlappedResult
中,我们只能使用指向传递给 I / O 请求的WSAOVERLAPPED
的指针。使用任何其他指针是没有意义的。有关 I / O 操作WSAGetOverlappedResult
的所有信息均来自lpOverlapped
(最终状态,已传输的字节数,如果需要等待-它等待与此重叠的事件)。一般而言-每个 I / O 请求都必须将OVERLAPPED
(实际上是IO_STATUS_BLOCK
)指针传递给内核。内核直接修改内存(最终状态和信息(通常是字节传输)),因为OVERLAPPED
的生存期必须有效,直到 I / O 不完整为止,并且对于每个必须是唯一的I / O 请求。[WSA]GetOverlappedResult
检查此存储器OVERLAPPED
(实际上是IO_STATUS_BLOCK
)-首先查找状态,如果它来自STATUS_PENDING
,则为-表示操作已完成-api接收传输并返回的字节数。如果此处仍为STATUS_PENDING
-I/O
尚未完成。如果我们要等待-api使用hEvent
进行重叠等待。事件句柄在 I / O 请求期间传递给内核,并在 I / O 完成时将其设置为信号状态。具体的 I / O 请求?现在想清楚为什么我们只有在传递给 I / O 完全重叠指针的情况下才能调用[WSA]GetOverlappedResult
>请求。
如果我们自己没有传递指向OVERLAPPED
的指针(例如,如果我们使用recv
或send
),则底层套接字api-您自己将OVERLAPPED
分配为堆栈并将其指针传递给 I / O 。结果-在 I / O 未完成之前,api在这种情况下无法返回。因为重叠的内存必须有效,直到 I / O 未完成(完成时内核将数据写入该内存)。但是离开函数后局部变量变得无效。因此功能必须等待到位。
因为所有这些,我们无法在[WSA]GetOverlappedResult
或send
之后调用recv
-首先,我们根本没有指向重叠的指针。在第二次重叠中,在 I / O 请求中使用的请求已被“销毁”(更确切地说,位于顶部下方的堆栈中-因此位于垃圾区域)。如果 I / O 尚未完成-内核已经在随机位置堆栈中修改了数据,那么当它最终完成时-这将具有不可预测的效果-不会发生任何事情-崩溃或非常不正常的副作用。如果send
或recv
在 I / O 完成之前返回-这将对过程产生致命影响。这绝对不是必须的(如果Windows中没有错误)。
Q2:我应该如何处理?
我如何尝试解释WSA_IO_PENDING
是send
还是recv
真正返回的-这是系统错误。如果设备完成 I / O 并具有这样的结果(尽管不一定),则很好-只是一些未知的(对于这种情况)错误代码。像处理任何一般错误一样处理它。不需要特殊处理(例如异步io)。如果 I / O 确实尚未完成(返回send
或recv
之后),这意味着在随机时间(可能已经),您的堆栈可能会损坏。效果这无法预料。在这里什么也做不了。这是严重的系统错误。
问题1:这是预期的行为吗?
不,这绝对不例外。
Q0:重叠属性上的任何引用都具有阻塞调用和 超时?
首先,当我们创建文件句柄时,我们在其上设置或不设置异步属性:在CreateFileW
-FILE_FLAG_OVERLAPPED
的情况下,在WSASocket
-WSA_FLAG_OVERLAPPED
的情况下。如果是NtOpenFile
或NtCreateFile
-FILE_SYNCHRONOUS_IO_[NO]NALERT
(反向比较FILE_FLAG_OVERLAPPED
)。所有存储在FILE_OBJECT
.Flags
-FO_SYNCHRONOUS_IO
(已为同步I / O打开文件对象。)中的信息都将被设置或清除。
FO_SYNCHRONOUS_IO
标志的效果: I / O 子系统通过IofCallDriver
调用某个驱动程序,并且如果驱动程序返回STATUS_PENDING
-在{{1}的情况下} FO_SYNCHRONOUS_IO
中设置的标志-等待就位(因此在内核中),直到 I / O 未完成。否则返回此状态-FILE_OBJECT
供呼叫者使用-它可以等待您就位,或者通过 APC 或 IOCP 进行接收者回调。
当我们使用socket
时,它内部调用STATUS_PENDING
-
创建的套接字将具有重叠的属性作为 默认
此平均文件将不具有WSASocket
属性,并且低级别 I / O 调用可以从内核返回FO_SYNCHRONOUS_IO
。但让我们看看recv
的工作方式:
内部WSPRecv
用STATUS_PENDING
调用。因为这-lpOverlapped = 0
自己在栈中分配WSPRecv
作为局部变量。之前通过OVERLAPPED
发出实际的 I / O 请求。因为创建的文件(套接字)没有ZwDeviceIoControlFile
标志-FO_SYNCHRONOUS
是从内核返回的。在这种情况下,STATUS_PENDING
的外观-为WSPRecv
。如果是,则无法返回,直到操作完成。它开始通过lpOverlapped == 0
-ZwWaitForSingleObject
等待事件(此套接字在用户模式下内部维护)。在适当的位置SockWaitForSingleObject
使用您通过Timeout
与套接字关联的值,如果未设置SO_RCVTIMEO
,则使用0(无限等待)。如果SO_RCVTIMEO
返回ZwWaitForSingleObject
(仅在通过STATUS_TIMEOUT
设置超时的情况下)-这意味着 I / O 操作不会在指定时间内完成。在这种情况下,SO_RCVTIMEO
称为WSPRecv
(与CancelIo
相同)。 CancelIo
不得返回(等待)文件(来自当前线程)的所有 I / O 请求都将完成。在此SockCancelIo
之后,从重叠状态读取最终状态。这里必须是WSPRecv
(但实际上,具体的驱动程序决定以哪个状态完成取消的STATUS_CANCELLED
)。 IRP
将WSPRecv
转换为STATUS_CANCELLED
。然后调用STATUS_IO_TIMEOUT
将ntstatus代码转换为win32错误。将NtStatusToSocketError
转换为STATUS_IO_TIMEOUT
。但是如果WSAETIMEDOUT
仍然重叠,则在STATUS_PENDING
之后-您得到了CancelIo
。仅在这种情况下。看起来像设备错误,但我无法在自己的Win 10上重制它(可能是版本扮演角色)
在这里可以做什么(如果您确定确实有WSA_IO_PENDING
)?首先尝试使用不带WSA_IO_PENDING
的{{1}}-在这种情况下,WSASocket
永远不会返回WSA_FLAG_OVERLAPPED
,并且您永远都不会得到ZwDeviceIoControlFile
。检查一下-错误消失了吗?如果是,-返回重叠的属性,然后删除STATUS_PENDING
调用(所有测试-不是发行产品的解决方案),并检查此错误是否消失。如果是,则-设备无效取消(使用WSA_IO_PENDING
?!?) IRP 。所有这些的意义-找到错误更具体的地方。无论如何,有趣的是将构建最小的演示exe,它可以稳定地重现这种情况并在另一个系统上对其进行测试-这会持续吗?仅适用于具体版本吗?如果无法在其他伴奏上重现-需要在您的混凝土上调试