奇怪的 Winsock recv() 减速

Posted

技术标签:

【中文标题】奇怪的 Winsock recv() 减速【英文标题】:Weird Winsock recv() slowdown 【发布时间】:2012-02-21 15:33:02 【问题描述】:

我正在编写一个像 Skype 这样的小型 VOIP 应用程序,它现在运行良好,但我遇到了一个非常奇怪的问题。

在一个线程中,我在一个 while(true) 循环内调用 winsock recv() 函数,每次运行两次以从套接字获取数据。 第一次调用获得 2 个字节,这些字节将被转换为(短),而第二次调用获得消息的其余部分,如下所示:

完整消息:[2 字节标题 |消息,长度由2Byte Header决定]

这些数据包大约是 49/秒,大约是 3000 字节/秒。

这些数据包的内容是转换成波形的音频数据。

使用ioctlsocket(),我确定在收到的每个“消息”(2 字节+数据)中套接字上是否有一些数据。如果在我收到线程的while(true) loop 内的消息后立即在套接字上有东西,则将收到该消息,但会丢弃该消息以防止上行延迟。

这个概念很有效,但问题是:

当我的 VOIP 程序正在运行并且当我并行下载(例如通过浏览器)文件时,套接字上总是堆积了太多数据,因为在下载时,recv() loop 似乎实际上变慢了。除了实际的 voip up/download 之外,这在每种下载/上传情况下都会发生。

我不知道这种行为从何而来,但是当我实际上取消了除我的应用程序的 voip 流量之外的所有上传/下载时,我的应用程序再次完美运行。

如果程序完美运行,ioctlsocket() 函数会将 0 写入 bytesLeft 变量,该变量在接收函数所在的类中定义。

有人知道这是从哪里来的吗?我将在下面附上我的接收功能:

std::string D_SOCKETS::receive_message()

recv(ClientSocket,(char*)&val,sizeof(val),MSG_WAITALL);
receivedBytes = recv(ClientSocket,buffer,val,MSG_WAITALL);

if (receivedBytes != val)

    printf("SHORT: %d PAKET: %d ERROR: %d",val,receivedBytes,WSAGetLastError());
    exit(128);



ioctlsocket(ClientSocket,FIONREAD,&bytesLeft);
cout<<"Bytes left on the Socket:"<<bytesLeft<<endl;

if(bytesLeft>20)

    // message gets received, but ignored/thrown away to throw away
    return std::string();

else
    return std::string(buffer,receivedBytes);

【问题讨论】:

鉴于您的 VOIP 程序和其他程序都共享相同的带宽,难道不会期望其他程序的大量下载会降低您程序的数据接收速度吗?特别是如果您使用 TCP,并且带宽争用导致您的流的 TCP 数据包之一被丢弃......那么发送者将不得不重新发送它,同时您的应用程序将看不到任何更多数据到达。 TCP 还会自动进行发送速率限制以避免拥塞,因此这也可能是一个因素。对于 VOIP,通常最好使用 UDP... 【参考方案1】:

无需使用ioctlsocket() 来丢弃数据。这将表明您的协议设计中存在错误。假设您使用的是 TCP(您没有说),如果您的 2 字节标头始终准确,则不应有任何剩余数据。在读取 2byte 标头,然后读取指定的字节数后,您收到的下一个字节构成您的下一条消息,不应仅仅因为它存在而丢弃。

ioctlsocket() 报告更多可用字节的事实意味着您接收消息的速度比从套接字读取消息的速度要快。让你的阅读代码跑得更快,不要因为你的慢而丢掉好的数据。

您的阅读模式效率不高。您应该使用更大的缓冲区一次从套接字读取更多原始数据,而不是读取 2 个字节,然后是 X 字节,然后是 2 个字节,等等(使用ioctlsocket() 来了解有多少字节可用,并且然后一次读取至少那么多字节并将它们附加到缓冲区的末尾),然后解析缓冲区中尽可能多的完整消息,然后再次从套接字读取更多原始数据。一次可以读取的数据越多,接收数据的速度就越快。

为了进一步加快代码速度,也不要直接处理循环内的消息。而是在另一个线程中进行处理。让读取循环将完整的消息放入队列并返回读取,然后在消息可用于处理时让处理线程从队列中拉出。

【讨论】:

+1 表示尝试改变方向,错误...“不寻常”设计。 非常感谢您的帮助!我在医院待了很长时间,所以无法回信。我像 Remy Lebeau 所说的那样建造了它,它就像一个魅力。由于 Jeremy Friesner 所说的需要切换到 UDP,因为发送速率限制,因为当我上传时,我的应用程序显然看不到数据到达,例如试用我的音频程序时的文件。

以上是关于奇怪的 Winsock recv() 减速的主要内容,如果未能解决你的问题,请参考以下文章

我用winsock2在C++中做了一个简单的套接字,我把recv函数放入一个变量中,它给出了一些奇怪的符号

Winsock recv() 不阻塞

同时执行 recv() 和 send() winsock

使用 Winsock 的 send()/recv() 时是不是需要确认响应?

winsock套接字的recv函数

Winsock“recv”不会在非正常连接终止时返回