使用 GetQueuedCompletionStatus 和 ERROR_MORE_DATA 的套接字
Sockets using GetQueuedCompletionStatus and ERROR_MORE_DATA
我正在尝试将 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));
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 returns FALSE,GetLastError() returns ERROR_MORE_DATA,这听起来是正确的,因为我没有在 WSARecvFrom 中提供缓冲区。
问题是我如何提供缓冲区以实际从失败的 I/O 操作中获取数据?
我试图发出一个具有原始重叠结构的 WSARecvFrom,但它只是将另一个读取排队,并且随后对 GetQueuedCompletionStatus 的调用不会 return 直到发送更多网络数据。
在没有重叠结构的情况下调用 WSARecvFrom 会阻止它,并且它也不会 return 直到发送更多网络数据。
那么,我怎样才能正确处理 ERROR_MORE_DATA,而不丢失第一次操作的数据?
您必须为 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...
}
...
}
我正在尝试将 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));
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 returns FALSE,GetLastError() returns ERROR_MORE_DATA,这听起来是正确的,因为我没有在 WSARecvFrom 中提供缓冲区。
问题是我如何提供缓冲区以实际从失败的 I/O 操作中获取数据?
我试图发出一个具有原始重叠结构的 WSARecvFrom,但它只是将另一个读取排队,并且随后对 GetQueuedCompletionStatus 的调用不会 return 直到发送更多网络数据。
在没有重叠结构的情况下调用 WSARecvFrom 会阻止它,并且它也不会 return 直到发送更多网络数据。
那么,我怎样才能正确处理 ERROR_MORE_DATA,而不丢失第一次操作的数据?
您必须为 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...
}
...
}