在 C++ 中通过 'recv' 和 'MSG_PEEK' 获取套接字中可用的字节数
Posted
技术标签:
【中文标题】在 C++ 中通过 \'recv\' 和 \'MSG_PEEK\' 获取套接字中可用的字节数【英文标题】:Get the number of bytes available in socket by 'recv' with 'MSG_PEEK' in C++在 C++ 中通过 'recv' 和 'MSG_PEEK' 获取套接字中可用的字节数 【发布时间】:2012-10-10 16:36:09 【问题描述】:C++ 具有以下从套接字接收字节的函数,它可以使用MSG_PEEK
标志检查可用的字节数。使用MSG_PEEK
,'recv' 的返回值是套接字中可用的字节数:
#include <sys/socket.h>
ssize_t recv(int socket, void *buffer, size_t length, int flags);
我需要在不创建buffer
的情况下获取套接字中可用的字节数(不为buffer
分配内存)。有可能吗?如何实现?
【问题讨论】:
【参考方案1】:你要找的是ioctl(fd,FIONREAD,&bytes_available)
,windows下是ioctlsocket(socket,FIONREAD,&bytes_available)
。
但请注意,操作系统并不一定保证它将为您缓冲多少数据,因此,如果您正在等待大量数据,最好在数据进入时读取数据并将其存储在你自己的缓冲区,直到你拥有处理某些东西所需的一切。
为此,通常所做的只是一次读取块,例如
char buf[4096];
ssize_t bytes_read;
do
bytes_read = recv(socket, buf, sizeof(buf), 0);
if (bytes_read > 0)
/* do something with buf, such as append it to a larger buffer or
* process it */
while (bytes_read > 0);
如果您不想坐在那里等待数据,您应该查看select
或epoll
以确定何时可以读取数据,并且套接字的O_NONBLOCK
标志非常如果您想确保永远不会阻塞接收,则很方便。
【讨论】:
socket 的传入数据应该是“正在进行的字节流”还是“一系列 tcp 包”? 正确,这就是为什么第一种方法更可取的原因,尽管除非您以 Gbps 速度运行,否则您永远不会注意到它.. 传入的数据会涓涓细流。如果另一端发送的数据包很小,它们很可能会同时出现,但这绝不是保证。因此,从您作为接收者的角度来看,这是一个持续的字节流,您可以知道何时收到了您期望的所有字节。 请注意,当使用UDP,即SOCK_DGRAM时,您应该立即读取发送消息,因为部分读取会丢弃多余的长度;所以在那种情况下 ioctl FIONREAD 似乎是要走的路。 @KlamerSchutte 在 SOCK_DGRAM 上,您可以使用 MSG_PEEK | MSG_TRUNK。 Peek 表示不丢弃数据,trunk 表示返回消息的实际大小,即使大于缓冲区。然后只需传入一个 1 字节的缓冲区并忽略它。【参考方案2】:在 Windows 上,您可以使用带有 FIONREAD
标志的 ioctlsocket()
函数来询问套接字有多少字节可用,而无需读取/查看实际字节本身。返回的值是recv()
可以在没有阻塞的情况下返回的最小字节数。当您实际调用 recv()
时,可能已经到达了更多字节。
【讨论】:
这个答案是完全错误的。在文档中很清楚,FIONREAD
在 Windows 上是否不返回来自recv
的可用字节数而不阻塞。在 Windows 上,recv
可以从较低层(例如具有自己的缓冲区的过滤器和队列)提取数据,而 FIONREAD
不这样做,只检查顶层。见item 12。
想象一下,当调用recv
并且过滤器没有反转功能时,数据是否通过过滤器。 FIONREAD
怎么可能告诉recv
在不调用该过滤器的情况下可以读取多少字节?如果过滤器没有反转能力,它如何调用过滤器?这不能在 Windows 上工作。 (更糟糕的是,当您尝试它时它可能起作用,因为您的系统可能碰巧没有加载这样的驱动程序。但是当您的代码在其他人的计算机上运行时......繁荣。)
@DavidSchwartz: per MSDN: "FIONREAD:用于确定网络输入缓冲区中待处理的数据量,可以从套接字s
读取... FIONREAD
返回在对recv
函数的一次调用中可以读取的数据量,这可能与套接字上排队的数据总量不同。 "
@RemyLebeau 遗憾的是,由于我在两个 cmets 中解释的原因,这是不正确的。 FIONREAD
调用不能做任何不可逆的事情,而 recv
可以。所以它可能会返回一个较小的数字。 Microsoft 对非本地套接字操作的支持在很多方面参差不齐,这就是其中之一。这就是为什么强烈建议在 Windows 上运行的软件使用 Windows API,而不是 POSIXy 的。
@DavidSchwartz:我对网络过滤器一无所知,但是如果您仍然在读取数据,那么它是否通过过滤器有什么关系?数据沿一个方向传输并最终进入套接字的输入缓冲区。我不明白为什么反转逻辑是一个限制因素,除非可能使用MSG_PEEK
调用recv()
,但这并不常见,而且我希望通过过滤器读取数据并将其存储在无论如何,缓冲区,然后只需窥视缓冲区并将数据留在那里以便以后进行非窥视读取以将其删除。【参考方案3】:
The short answer is : this cannot be done with MS-Windows WinSock2,
as I can discovered over the last week of trying.
Glad to have finally found this post, which sheds some light on the issues I've been having, using latest Windows 10 Pro, version 20H2 Build 19042.867 (x86/x86_64) :
On a bound, disconnected UDP socket 'sk' (in Listening / Server mode):
1. Any attempt to use either ioctlsocket(sk, FIONREAD, &n_bytes)
OR WsaIoctl with a shifted FIONREAD argument, though they succeed,
and retern 0, after a call to select() returns > with that
'sk' FD bit set in the read FD set,
and the ioctl call returns 0 (success), and n_bytes is > 0,
causes the socket sk to be in a state where any
subsequent call to recv(), recvfrom(), or ReadFile() returns
SOCKET_ERROR with a WSAGetLastError() of :
10045, Operation Not Supported, or ReadFile
error 87, 'Invalid Parameter'.
Moreover, even worse:
2. Any attempt to use recv or recvfrom with the 'MSG_PEEK' msg_flags
parameter returns -1 and WSAGetLastError returns :
10040 : 'A message sent on a datagram socket was larger than
the internal message buffer or some other network limit,
or the buffer used to receive a datagram into was smaller
than the datagram itself.
' .
Yet for that socket I DID successfully call:
setsockopt(s, SOL_SOCKET, SO_RCVBUF, bufsz = 4096 , sizeof(bufsz) )
and the UDP packet being received was of only 120 bytes in size.
In short, with modern windows winsock2 ( winsock2.h / Ws2_32.dll) ,
there appears to be absolutely no way to use any documented API
to determine the number of bytes received on a bound UDP socket
before calling recv() / recvfrom() in MSG_WAITALL blocking mode to
actually receive the whole packet.
If you do not call ioctlsocket() or WsaIoctl or
recv,from(...,MSG_PEEK,...)
before entering recv,from(...,MSG_WAITALL,...) ,
then the recv,from succeeds.
I am considering advising clients that they must install and run
a Linux instance with MS Services for Linux under their windows
installation , and developing some
API to communicate with it from Windows, so that reliable
asynchronous UDP communication can be achieved - or does anyone
know of a good open source replacement for WinSock2 ?
I need access to a "C" library TCP+UDP/IP implementation for
modern Windows 10 that conforms to its own documentation,
unlike WinSock2 - does anyone know of one ?
【讨论】:
【参考方案4】:使用FIONREAD
时要小心!使用ioctl(fd, FIONREAD, &available)
的问题在于它总是会返回在某些系统上的套接字缓冲区中可供读取的总字节数。
这对于 STREAM 套接字 (TCP) 没有问题,但对于 DATAGRAM 套接字 (UDP) 会产生误导。至于数据报套接字,读取请求被限制为缓冲区中第一个数据报的大小,并且当读取小于第一个数据报的大小时,该数据报的所有未读字节仍然被丢弃。所以理想情况下,您只想知道缓冲区中下一个数据报的大小。
例如在 macOS/ios it is documented 上,FIONREAD
总是返回总金额(参见 cmets 关于 SO_NREAD
)。要仅获取下一个数据报的大小(以及流套接字的总大小),您可以使用以下代码:
int available;
socklen_t optlen = sizeof(readable);
int err = getsockopt(soc, SOL_SOCKET, SO_NREAD, &available, &optlen);
在 Linux 上,FIONREAD
记录到 only return the size of the next datagram 用于 UDP 套接字。
在 Windows 上,ioctlsocket(socket, FIONREAD, &available)
被记录为始终给出总大小:
如果在 s 参数中传递的套接字是面向消息的(例如,类型 SOCK_DGRAM),FIONREAD 会返回报告可读取的总字节数,不是排队的第一个数据报(消息)的大小插座。
来源:https://docs.microsoft.com/en-us/windows/win32/api/ws2spi/nc-ws2spi-lpwspioctl
我不知道如何仅在 Windows 上获取第一个数据报的大小。
【讨论】:
以上是关于在 C++ 中通过 'recv' 和 'MSG_PEEK' 获取套接字中可用的字节数的主要内容,如果未能解决你的问题,请参考以下文章