C++:如何测量非阻塞套接字的实际上传速率

Posted

技术标签:

【中文标题】C++:如何测量非阻塞套接字的实际上传速率【英文标题】:C++: How to measure real upload rate on non blocking sockets 【发布时间】:2013-07-26 14:01:53 【问题描述】:

我正在使用带有 epoll 的非阻塞套接字在 linux C++ 上编写一个程序,等待 EPOLLOUT 以便对某些数据执行 send()。

我的问题是:我已经读过,在非阻塞模式下,数据被复制到内核的缓冲区,因此 send() 调用可能会立即返回,表明所有数据都已发送,而实际上它只是复制到内核的缓冲区。

我如何知道远程对等方实际发送和接收数据的时间,以了解实际传输速率?

【问题讨论】:

你可能需要另一端发回确认... 【参考方案1】:

无论是否处于非阻塞模式,只要将数据复制到内核缓冲区,send 就会返回。阻塞和非阻塞模式的区别在于缓冲区满的时候。在缓冲区满的情况下,阻塞模式将暂停当前线程,直到写入发生,而非阻塞模式将立即返回 EAGAIN 或 EWOULDBLOCK。

在 TCP 连接中,内核缓冲区通常等于窗口大小,因此一旦有太多数据仍未得到确认,连接就会阻塞。这意味着发送方知道远程端接收数据的速度。

对于 UDP,它有点复杂,因为没有确认。这里只有接收端能够测量真实速度,因为发送的数据可能会在途中丢失。

在 TCP 和 UDP 两种情况下,内核都不会尝试发送链路层无法处理的数据。如果网络拥塞,链路层也可以流出数据。

回到您的案例,当使用非阻塞套接字时,只要您正确处理 EAGAIN 或 EWOULDBLOCK 错误,您就可以测量网络速度。这对于 TCP 来说当然是正确的,在这种情况下,您发送的数据比当前窗口大小(可能是 64K 左右)更多,您也可以了解使用 UDP 套接字的链路层速度。

【讨论】:

第二段不正确。 (1) 套接字接收缓冲区是固定大小的:窗口大小是动态的。 (2) 发送缓冲区的大小与窗口大小完全无关。 (3) 发送时连接阻塞当且仅当发送缓冲区已满。 @EJP - 你确定。您确实需要缓冲窗口中的数据,因为您可能需要重新发送,但您不想缓冲更多数据,因为这会使您的连接由于缓冲区膨胀而无响应。我有待纠正,但我不明白为什么要从窗口实现一个单独的发送缓冲区。它会产生比它解决的问题更多的问题。 这是我一直在寻找的答案。所以基本上我明白了,当发送缓冲区已满时,发送会阻塞(或返回EAGAIN),只有在网络上实际发送数据并被远程端接收后才能清理发送缓冲区? 对于 TCP 是这样,对于 UDP,数据一传输到链路层就会被删除。 我不知道你在说什么,我也不确定你是否知道。我没有说“从窗口实现[ing]一个单独的发送缓冲区”。 TCP 实现 (1) 发送缓冲区,(2) 接收缓冲区,以及 (3) 接收窗口。你没有任何选择。您只是混淆了问题,并将不同的事物混为一谈。套接字发送缓冲区未确认的数据。时期。套接字接收缓冲区决定了接收窗口的最大尺寸,随着接收缓冲区的填满而缩小。【参考方案2】:

您可以使用 IOCTL 获取内核套接字缓冲区中的当前数据量。这将允许您检查实际发送的内容。不过,我不确定这是否重要,除非您有 MASSIVE 缓冲区和要发送的少量数据,否则可能不感兴趣。

调查您的套接字 fd 上的 TIOCOUTQ/TIOCINQ ioctl。

【讨论】:

这并不能告诉你它是否已被另一端接收,对吗?只是它离开了当前的机器。它可能位于路由器中或“正在路上”的其他地方。 没有。这样做的唯一方法是从另一端发回该数据。 我收回,@EJP 有一个狡猾的计划!【参考方案3】:

我的问题是:我已经读过在非阻塞模式下数据被复制到内核的缓冲区

这发生在所有模式中,而不仅仅是非阻塞模式。我建议你复习你的阅读材料。

因此,send() 调用可能会立即返回,表明所有数据都已发送,而实际上它只是复制到内核的缓冲区。

在所有模式下都是如此。

我如何知道远程对等方实际发送和接收数据的时间,以了解实际传输速率?

当您发送完所有数据后,关闭套接字以进行输出,然后设置阻塞模式并读取,或者继续选择“可读”;然后在任何一种情况下都读取应该产生的 EOS。这起到了对收盘的对等确认的作用。然后停止计时器。

【讨论】:

我不明白您所说的“阅读应该产生的 EOS”和“作为对收盘的同行确认”是什么意思。我不是在寻找关闭的确认,我在寻找数据已发送到另一端的确认。 那么当你知道数据已经发送了,你打算怎么做呢? 当你关闭输出的套接字时,对端将读取一个 EOS:recv() 将返回零。那时,它唯一明智的做法就是关闭套接字。这将导致 your recv() 返回零。所有这些都作为对关闭的确认,它在最终发送之后立即发生,因此它也确认了这一点,因此您知道何时停止时钟。这样您就不必在带内发送任何明确的内容,从而简化了您的应用程序协议。 但这并没有真正衡量EOS之前的发送速度,也不允许socket在发送完成后被复用做其他事情。 @RemyLebeau 当然,但是(a)在问题中没有将套接字用于其他事情,并且(b)不会为 FIN 进行一次额外的数据包交换显着改变测量速率。【参考方案4】:

send() 只是将数据放入内核的缓冲区然后退出,让内核在后台执行实际的传输,所以你真正能做的就是测量内核接受你传出数据的速度。您无法真正测量实际传输速度,除非对等方为收到的每个缓冲区发送确认(并且无法检测何时收到 TCP 自己的确认)。但是使用send() 可以在仍有太多数据传输时阻塞的事实可以帮助您了解您的代码将传出数据传递给send() 的事实。

send() 告诉您接受了多少字节。因此,很容易计算出一个近似的接受速度——将接受的字节数除以自上次调用 send() 以来经过的时间。所以当你调用send()发送X字节得到Y返回字节时,记录时间为time1,再次调用send()发送X字节得到Y返回字节,记录时间为time2,您将看到您的代码以大约每毫秒Y / ((time2-time1) in ms) 字节的速度发送数据,然后您可以根据需要使用它来计算B/KB/MB/GB per ms/sec/min/hr。在数据传输的整个生命周期内,您可以很好地了解应用的一般传输速度。

【讨论】:

以上是关于C++:如何测量非阻塞套接字的实际上传速率的主要内容,如果未能解决你的问题,请参考以下文章

C++ 中的 Bittorrent 客户端,在非阻塞套接字上连接到对等点总是超时

C++非阻塞键盘缓冲区输入检测或读取函数是啥?

如何将 TCP 套接字更改为非阻塞?

阻塞与非阻塞winsock的速度/性能特征

同步异步阻塞非阻塞的概念理解

如何将套接字重置为阻塞模式(在我将其设置为非阻塞模式之后)?