短版:
使用阻塞套接字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进行验证。
套接字已启用超时,并通过以下命令保持 Activity 状态:
::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] ... 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*/)
问题:
那些发送/接收/WSARecv/WSASend阻止调用将返回带有的错误WSA_IO_PENDING 错误代码!
问题:
Q0:关于重叠属性的任何引用有阻塞的调用和超时?
它的表现如何?
如果我的套接字具有重叠的“属性” +超时功能启用,并且仅使用具有“无重叠I/O语义”的阻塞套接字API。
我找不到有关它的任何引用(例如,来自MSDN)。
问题1:这是预期的行为吗?
将代码从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:
}
}
Q2:我应该如何处理?
忽略它?还是只是关闭套接字作为常见的错误情况?
从观察结果来看,一旦我得到WSA_IO_PENDING,并且如果我只是忽略它,套接字最终将变得不再响应。
问题3:WSAGetOverlappedResult怎么样?
有什么意义吗?
我应该给哪个WSAOVERLAPPED对象?由于没有这样的程序,因此我将所有阻塞套接字调用都使用。
我已经尝试过创建一个新的空WSAOVERLAPPED并使用它来调用WSAGetOverlappedResult。最终将成功返回0字节并返回。
最佳答案
Q3: How about
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尚未完成-内核已经在随机位置堆栈中修改了数据,则当它最终完成时-这将具有不可预测的效果-从无任何 react -崩溃或非常不正常的副作用。如果send
或recv
在I/O完成之前返回-这将对进程产生致命影响。这绝对不是必须的(如果Windows中没有错误)。Q2: How should I handle it?
我如何尝试解释
WSA_IO_PENDING
或send
是否真正返回了recv
-这是系统错误。如果设备完成的I/O具有这样的结果(尽管不是必须的),则很好-只是一些未知的(对于这种情况)错误代码。像处理任何一般错误一样处理它。不需要特殊处理(例如异步io)。如果I/O尚未真正完成(在send
或recv
返回之后)-这意味着在随机时间(可能已经),您的堆栈可能会损坏。效果这无法预料。在这里什么也做不了。这是严重的系统错误。Q1: is it expected behavior?
不,绝对没有异常(exception)。
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
(打开文件对象以进行同步I/O)中的信息都将被设置或清除。接下来是
FO_SYNCHRONOUS_IO
标志的作用:I/O子系统通过IofCallDriver
调用某个驱动程序,并且如果驱动程序返回STATUS_PENDING
,以防在FO_SYNCHRONOUS_IO
中设置了FILE_OBJECT
标志的情况下,就位(因此在内核中),直到I/O未完成。否则,返回此状态-调用者的STATUS_PENDING
-它可以等待您就位,或者通过APC或IOCP进行接收者回调。当我们使用
socket
时,它会内部调用WSASocket
-The socket that is created will have the overlapped attribute as a default
这意味着文件将不具有
FO_SYNCHRONOUS_IO
属性,并且低级 I/O调用可以从内核返回STATUS_PENDING
。但让我们看看 recv
是如何工作的:在内部
WSPRecv
被称为lpOverlapped = 0
。因为这-WSPRecv
自己在堆栈中分配OVERLAPPED
作为局部变量。通过ZwDeviceIoControlFile
进行实际的I/O请求之前。因为创建的文件(套接字)没有FO_SYNCHRONOUS
标志-STATUS_PENDING
是从内核返回的。在这种情况下,WSPRecv
look-是lpOverlapped == 0
。如果是,则无法返回,直到操作完成。它通过SockWaitForSingleObject
- ZwWaitForSingleObject
开始等待事件(此套接字在用户模式下内部维护)。到位Timeout
使用了您通过SO_RCVTIMEO
与套接字关联的值,如果未设置SO_RCVTIMEO
,则使用0(无限等待)。如果ZwWaitForSingleObject
返回STATUS_TIMEOUT
(仅在通过SO_RCVTIMEO
设置超时的情况下才可以)-这意味着I/O操作不会在指定时间内完成。在这种情况下,WSPRecv
称为SockCancelIo
(与 CancelIo
效果相同)。 CancelIo
不得返回(等待),直到对文件的所有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
)?首先尝试使用不带WSASocket
的WSA_FLAG_OVERLAPPED
-在这种情况下,ZwDeviceIoControlFile
永远不会返回STATUS_PENDING
,而且您也永远不需要WSA_IO_PENDING
。检查一下-错误消失了吗?如果是,则-返回重叠的属性,并删除SO_RCVTIMEO
调用(所有用于测试-不是发行产品的解决方案),并检查此错误消失之后。如果是,则看起来像设备无效取消(使用STATUS_PENDING
?!?)IRP。所有这些的意义-找到错误更具体的地方。无论如何,有趣的是构建最小的演示exe文件,该文件可以稳定地重现这种情况并在另一个系统上进行测试-这种情况持续存在吗?仅适用于具体版本吗?如果无法在其他伴奏上重现-需要在您的混凝土上进行调试
关于sockets - 意外的WSA_IO_PENDING阻止了Winsock2调用(具有重叠的I/O属性),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52419993/