Sockets 使用GetQueuedCompletionStatus和错误\u更多\u数据的套接字
我试图将GetQueuedCompletionStatus与winsocks一起使用,但似乎无法正确使用。程序如下:Sockets 使用GetQueuedCompletionStatus和错误\u更多\u数据的套接字,sockets,winsock,io-completion-ports,Sockets,Winsock,Io Completion Ports,我试图将GetQueuedCompletionStatus与winsocks一起使用,但似乎无法正确使用。程序如下: void foo() { ... SOCKET sck = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); .... bind(sck,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
void foo() {
...
SOCKET sck = WSASocket(AF_INET, SOCK_DGRAM, 0,
NULL, 0, WSA_FLAG_OVERLAPPED);
....
bind(sck,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
HANDLE hPort = CreateIoCompletionPort((HANDLE)sck, NULL, 0, 0 );
OVERLAPPED pOverlapped = {0,};
WSARecvFrom(sck,NULL,0,NULL,NULL,(struct sockaddr *)&laddr,&lsize,&pOverlapped,0);
BOOL bReturn = GetQueuedCompletionStatus(
hPort,
&rbytes,
(LPDWORD)&lpContext,
&pOutOverlapped,
INFINITE);
...
}
然后,我从外部工具向绑定端口发送一些网络数据。GetQueuedCompletionStatus返回FALSE,GetLastError()返回ERROR\u MORE\u数据,这听起来是正确的,因为我在WSARecvFrom中没有提供缓冲区
问题是我如何提供一个缓冲区来实际从失败的I/O操作中获取数据
我试图使用原始重叠结构发出WSARecvFrom,但它只是将另一次读取排队,并且在发送更多网络数据之前,不会返回对GetQueuedCompletionStatus的后续调用
在没有重叠结构的情况下调用WSARecvFrom会阻塞它,并且在发送更多网络数据之前它也不会返回
因此,如何正确处理错误数据,而不丢失第一次操作的数据?您必须为
WSARecvFrom()
提供缓冲区,就像任何读取操作一样,无论是否使用IOCP。在IOCP操作完成之前,必须确保缓冲区在内存中保持有效。IOCP填充您提供的缓冲区,然后在完成时通知完成端口
UDP在单个数据报中传输的字节数不能超过65535个,因此可以将其用作最大缓冲区大小
在您的示例中,您的代码被编写为同步运行(完全违背了使用IOCP的目的),因此您可以使用本地缓冲区:
void foo() {
...
SOCKET sck = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
if (sck == INVALID_SOCKET)
{
// error, do something...
return;
}
....
bind(sck,(struct sockaddr *)&addr, sizeof(struct sockaddr_in));
HANDLE hPort = CreateIoCompletionPort((HANDLE)sck, NULL, 0, 0 );
if (!hPort)
{
// error, do something...
return;
}
WSAOVERLAPPED Overlapped = {0};
Overlapped.hEvent = WSACreateEvent();
BYTE buffer[0xFFFF];
DWORD dwBytesRecvd = 0;
DWORD dwFlags = 0;
sockaddr_in fromaddr = {0};
int fromaddrlen = sizeof(fromaddr);
WSABUF buf;
buf.len = sizeof(buffer);
buf.buf = buffer;
int iRet = WSARecvFrom(sck, &buf, 1, &dwBytesRecvd, &dwFlags, (sockaddr*)&fromaddr, &fromaddrlen, &Overlapped, NULL);
if (iRet == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// error, do something...
return;
}
DWORD rBytes;
ULONG_PTR key;
LPOVERLAPPED pOverlapped = NULL;
if (!GetQueuedCompletionStatus(hPort, &rbytes, &key, &pOverlapped, INFINITE))
{
if (pOverlapped)
{
// WSARecvFrom() failed...
}
else
{
// GetQueuedCompletionStatus() failed...
}
// do something...
return;
}
}
// I/O complete, use buffer, dwBytesRecvd, dwFlags, and fromaddr as needed...
}
然而,这违背了IOCP的目的。如果您真的想要同步,您可以使用recvfrom()
,让它阻塞调用线程,直到数据到达。当有一个线程池为完成端口提供服务时,IOCP工作得最好。调用WSARecvFrom()并让它在后台工作,不要等待它。让一个单独的线程调用GetQueuedCompletionPort()
,并在收到数据时处理数据,例如:
struct MyOverlapped
{
WSAOVERLAPPED overlapped;
BYTE buffer[0xFFFF];
DWORD buflen;
DWORD flags;
sockaddr_storage fromaddr;
int fromaddrLen;
};
HANDLE hPort = NULL;
void foo() {
...
SOCKET sck = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
if (sck == INVALID_SOCKET)
{
// error, do something...
return;
}
....
bind(sck,(struct sockaddr *)&addr, sizeof(struct sockaddr_in));
hPort = CreateIoCompletionPort((HANDLE)sck, NULL, 0, 0 );
if (!hPort)
{
// error, do something...
return;
}
MyOverlapped *ov = new MyOverlapped;
ZeroMemory(ov, sizeof(*ov));
ov->overlapped.hEvent = WSACreateEvent();
ov->fromaddrlen = sizeof(ov->fromaddr);
WSABUF buf;
buf.len = sizeof(ov->buffer);
buf.buf = ov->buffer;
int iRet = WSARecvFrom(sck, &buf, 1, &ov->buflen, &ov->flags, (sockaddr*)&ov->fromaddr, &ov->fromaddrlen, (WSAOVERLAPPED*)ov, NULL);
if (iRet == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// error, do something...
return;
}
// WSARecvFrom() is now operating in the background,
// the IOCP port will be signaled when finished...
}
else
{
// data is already available,
// the IOCP port will be signaled immediately...
}
...
}
...
// in another thread...
{
...
DWORD rbytes;
ULONG_PTR key;
MyOverlapped *ov = NULL;
if (!GetQueuedCompletionStatus(hPort, &rbytes, &key, (LPOVERLAPPED*)&ov, INFINITE))
{
if (ov)
{
// WSARecvFrom() failed...
// free ov, or reuse it for another operation...
}
else
{
// GetQueuedCompletionStatus() failed...
}
}
else
{
// use ov as needed...
// free ov, or reuse it for another operation...
}
...
}
为什么不在调用中提供WSABUF缓冲区/缓冲阵列?为什么要用可疑的参数挑起不可靠的数据报协议?我试图让Windows在实际读取数据之前告诉我何时有可用数据。I/O读取功能不能以这种方式工作。无论读取是同步的还是异步的,都必须为要填充的函数提供有效的缓冲区。要在不实际读取数据的情况下发现套接字上的数据何时可用,需要使用
select()
、WSAAsyncSelect()
或WSAEventSelect()
来检测套接字何时可读(数据挂起),然后可以使用缓冲区从中读取。在您的示例中,您尝试使用IOCP,它在后台执行实际读取,然后在读取完成时通知您,因此您需要一个真正的缓冲区将其读入。因此,您是说IOCP中的错误\u更多\u数据不相关(至少在套接字的情况下)。在这种情况下,如果输入缓冲区对于操作来说太小,数据是否被丢弃?难道绝对没有办法用新的缓冲区重做操作吗?您能给我指一下描述这种行为的Microsoft文档吗?ERROR\u MORE\u DATA
表示您提供的缓冲区小于接收到的数据。这是正确的,因为您根本没有提供任何缓冲区(UDP中有一个0长度数据报的概念,因此0长度缓冲区是有效的,这就是为什么WSARecvFrom()
没有提前失败的原因)。对于UDP,如果缓冲区太小,则丢弃数据报。无法恢复它,也无法使用更大的缓冲区重试读取相同的数据报。您必须在第一次正确使用它,否则将永远丢失它。由于UDP的最大缓冲区大小仅为每个数据报65535字节,这是一个非常小的分配量,请继续为每个UDP读取分配最大缓冲区大小。完成后会告诉你实际收到了多少字节。我挖掘了一点,你是对的,UDP数据将被丢弃,无法恢复,bummer。关于分配,如果有1000个套接字等待数据,我需要分配1000个64kb的缓冲区,所以这不是一个非常优雅的解决方案。我会接受你的回答,谢谢。@Andre:每次读/写操作都需要一个单独的缓冲区。当一个操作完成时,如果需要,您可以将该缓冲区重新用于另一个操作。但是,如果同时有1000个套接字读取,则需要1000个单独的缓冲区来读取。如果不想分割系统内存,请考虑为缓冲区使用专用堆或内存池。否则,请使用select()
/WSA…select()
检测数据等待的时间,并根据需要仅分配+读取。