为什么特定的UDP消息总是低于特定的缓冲区大小?

3个不同的消息以不同的速率发送到同一个端口:

消息 大小(字节) 发送每个 传输速度
232 10 ms 100Hz
中等 148 20ms 50Hz
20 60 ms 16.6Hz

我每隔约6毫秒只能处理一条消息。
单线程。 阻止阅读。


一种奇怪的情况正在发生,我没有解释。
当我将接收缓冲区设置为4,799字节时,我的所有低速消息都会被丢弃。
我看到可能有一两个被处理,然后什么都没有。

当我将接收缓冲区设置为4,800 (或更高!)时,似乎所有低速消息都开始被处理。 我看到大约每秒16/17。


这一点一直被观察到。 发送数据包的应用程序始终在接收应用程序之前启动。 创建套接字后,在开始处理之前,接收应用程序总是有很长的延迟。 因此,处理开始时缓冲区始终为满,并且每次测试发生时它都不是相同的起始缓冲区。 这是因为套接字是在发送方已经发送消息之后创建的,因此接收方可能会在发送周期的中间开始监听。

为什么增加单个字节的接收缓冲区大小会导致低速消息处理发生巨大变化?

我构建了一个表来更好地可视化预期的处理:
在此处输入图像描述

由于其中一些消息得到处理,因此可能会将更多消息放入队列而不是被丢弃。

尽管如此,我希望4,799字节缓冲区的行为与4,800字节相同。

然而,这不是我所观察到的。


我认为这个问题与低速消息与其他两条消息同时发送的事实有关。 它总是在高/中速信息之后接收。 (已通过wireshark确认)。

例如,假设缓冲区开始时为空,很明显低速消息需要比其他消息排队更长。
*每6ms 1条消息大约每30ms发送5条消息。 在此处输入图像描述

这仍然不能解释缓冲区大小。

我们正在运行VxWorks,并使用他们的sockLib,它是Berkeley套接字的一个实现。 这是我们的套接字创建的代码片段:
我正在改变SOCKET_BUFFER_SIZE

 struct sockaddr_in tSocketAddress; // Socket address int nSocketAddressSize = sizeof(struct sockaddr_in); // Size of socket address structure int nSocketOption = 0; // Already created if (*ptParameters->m_pnIDReference != 0) return FALSE; // Create UDP socket if ((*ptParameters->m_pnIDReference = socket(AF_INET, SOCK_DGRAM, 0)) == ERROR) { // Error CreateSocketMessage(ptParameters, "CreateSocket: Socket create failed with error."); // Not successful return FALSE; } // Valid local address if (ptParameters->m_szLocalIPAddress != SOCKET_ADDRESS_NONE_STRING && ptParameters->m_usLocalPort != 0) { // Set up the local parameters/port bzero((char*)&tSocketAddress, nSocketAddressSize); tSocketAddress.sin_len = (u_char)nSocketAddressSize; tSocketAddress.sin_family = AF_INET; tSocketAddress.sin_port = htons(ptParameters->m_usLocalPort); // Check for any address if (strcmp(ptParameters->m_szLocalIPAddress, SOCKET_ADDRESS_ANY_STRING) == 0) tSocketAddress.sin_addr.s_addr = htonl(INADDR_ANY); else { // Convert IP address for binding if ((tSocketAddress.sin_addr.s_addr = inet_addr(ptParameters->m_szLocalIPAddress)) == ERROR) { // Error CreateSocketMessage(ptParameters, "Unknown IP address."); // Cleanup socket close(*ptParameters->m_pnIDReference); *ptParameters->m_pnIDReference = ERROR; // Not successful return FALSE; } } // Bind the socket to the local address if (bind(*ptParameters->m_pnIDReference, (struct sockaddr *)&tSocketAddress, nSocketAddressSize) == ERROR) { // Error CreateSocketMessage(ptParameters, "Socket bind failed."); // Cleanup socket close(*ptParameters->m_pnIDReference); *ptParameters->m_pnIDReference = ERROR; // Not successful return FALSE; } } // Receive socket if (ptParameters->m_eType == SOCKTYPE_RECEIVE || ptParameters->m_eType == SOCKTYPE_RECEIVE_AND_TRANSMIT) { // Set the receive buffer size nSocketOption = SOCKET_BUFFER_SIZE; if (setsockopt(*ptParameters->m_pnIDReference, SOL_SOCKET, SO_RCVBUF, (char *)&nSocketOption, sizeof(nSocketOption)) == ERROR) { // Error CreateSocketMessage(ptParameters, "Socket buffer size set failed."); // Cleanup socket close(*ptParameters->m_pnIDReference); *ptParameters->m_pnIDReference = ERROR; // Not successful return FALSE; } } 

并且套接字接收在无限循环中被调用:
*缓冲区大小肯定足够大

 int SocketReceive(int nSocketIndex, char *pBuffer, int nBufferLength) { int nBytesReceived = 0; char szError[256]; // Invalid index or socket if (nSocketIndex = SOCKET_COUNT || g_pnSocketIDs[nSocketIndex] == 0) { sprintf(szError,"SocketReceive: Invalid socket (%d) or ID (%d)", nSocketIndex, g_pnSocketIDs[nSocketIndex]); perror(szError); return -1; } // Invalid buffer length if (nBufferLength == 0) { perror("SocketReceive: zero buffer length"); return 0; } // Send data nBytesReceived = recv(g_pnSocketIDs[nSocketIndex], pBuffer, nBufferLength, 0); // Error in receiving if (nBytesReceived == ERROR) { // Create error string sprintf(szError, "SocketReceive: Data Receive Failure:  ", errno); // Set error message perror(szError); // Return error return ERROR; } // Bytes received return nBytesReceived; } 

有关将缓冲区大小增加到4,800的原因的任何线索都可以成功一致地读取低速消息?

SO_RCVBUF大小为4799导致低速消息丢失和4800大小正常的问题的基本答案是UDP数据包的混合,它们进入的速率,速率您正在处理传入的数据包,并且您的vxWorks内核中的mbuff和簇编号的大小允许足够的网络堆栈吞吐量,以便不会丢弃较大的低速消息。

上面评论中提到的URL http://www.vxdev.com/docs/vx55man/vxworks/ref/sockLib.html#setsockopt的setsockopt()手册页中的SO_SNDBUF选项说明可以说明指定的大小和对mbuff使用的影响:

设置缓冲区的最大大小(对于SO_SNDBUF和SO_RCVBUF,如下所述)的效果实际上不是从mbuf池分配mbuf。 相反,效果是在协议数据结构中设置高水位标记,稍后用于限制mbuf分配的数量。

UDP数据包是离散单元。 如果发送10个大小为232的数据包,则不会将其视为连续内存中的2320字节数据。 相反,它是网络堆栈中的10个内存缓冲区,因为UDP是离散数据包,而TCP是连续的字节流。

请参阅如何在VxWorks 5.4中调整网络缓冲? 在DDS社区网站上,讨论了mbuff大小和网络集群混合的相互依赖性。

请参阅如何解决VxWorks缓冲区的问题? 在DDS社区网站上。

请参阅幻灯片演示文稿的PDF,这是一个研究 2004年VxWorks中网络堆栈耗尽的新工具,它讨论了使用mBufShowinetStatShow等各种工具来查看网络堆栈中发生的情况。

如果不对发送UDP消息的路径中的每个网络堆栈实现进行详细分析,则几乎不可能说明结果行为。

UDP实现允许自行决定丢弃任何数据包。 通常,当堆栈得出结论它需要丢弃数据包以便能够接收新数据包时,就会发生这种情况。 没有正式要求丢弃的数据包是最旧的或最新的。 也可能是由于内部存储器管理策略,特定大小的类更受影响。

从涉及的IP堆栈中,最有趣的一个是接收机器上的那个。

如果您将接收方改为接收缓冲区需要几秒钟的预期消息,那么肯定会获得更好的接收体验。 我从至少10k开始。

从4,799到4,800时观察到的行为“变化”可能是由于后者只是允许在需要再次丢弃之前接收到一条小消息,而较小的大小只会导致它稍早丢弃。 如果接收应用程序足够快以读取待处理消息,则在一种情况下将收到小消息,而在另一种情况下则不会收到小消息。