假设您有一个从套接字读取的程序。您如何将下载速率保持在某个给定阈值以下?
8 回答
在应用层(使用 Berkeley 套接字样式 API),您只需查看时钟,并以您想要限制的速率读取或写入数据。
如果您平均只读取 10kbps,但源发送的速度超过了 10kbps,那么最终它和您之间的所有缓冲区都会填满。TCP/IP 允许这样做,并且协议会安排发送方减速(在应用层,您可能只需要知道在另一端,阻塞写入调用会阻塞,非阻塞写入会失败,并且异步在您读取足够的数据以允许写入之前,写入不会完成)。
在应用层,你只能是近似的——你不能保证硬限制,例如“在任何一秒钟内,不超过 10 kb 将通过网络中的给定点”。但是,如果您跟踪收到的内容,从长远来看,您可以获得正确的平均值。
假设网络传输,基于 TCP/IP 的传输,数据包被发送以响应 ACK/NACK 数据包以另一种方式发送。
通过限制确认接收传入数据包的数据包速率,您将反过来降低发送新数据包的速率。
它可能有点不精确,因此它可能是监控下游速率并自适应调整响应速率直到它落在舒适阈值内的最佳选择。(这会很快发生,但是,您会每秒发送大量确认)
如果您正在从套接字读取,则无法控制所使用的带宽 - 您正在读取该套接字的操作系统缓冲区,并且您所说的任何内容都不会使写入套接字的人写入更少的数据(除非,当然,您已经为此制定了协议)。
缓慢的阅读只会填满缓冲区,并最终导致网络端停止 - 但您无法控制这种情况发生的方式或时间。
如果你真的想一次只读取这么多数据,你可以这样做:
ReadFixedRate() {
while(Data_Exists()) {
t = GetTime();
ReadBlock();
while(t + delay > GetTime()) {
Delay()'
}
}
}
就像将游戏限制在一定数量的 FPS 时一样。
extern int FPS;
....
timePerFrameinMS = 1000/FPS;
while(1) {
time = getMilliseconds();
DrawScene();
time = getMilliseconds()-time;
if (time < timePerFrameinMS) {
sleep(timePerFrameinMS - time);
}
}
这样可以确保游戏刷新率最高为 FPS。以同样的方式,DrawScene 可以是用于将字节泵入套接字流的函数。
wget 似乎使用 --limit-rate 选项来管理它。这是手册页的内容:
请注意,Wget 通过在网络读取花费的时间少于速率指定的时间后休眠适当的时间来实现限制。最终,此策略会导致 TCP 传输减慢到大约指定的速率。但是,实现这种平衡可能需要一些时间,因此如果限制速率不适用于非常小的文件,请不要感到惊讶。
正如其他人所说,操作系统内核正在管理流量,您只是从内核内存中读取数据的副本。要粗略地限制一个应用程序的速率,您需要延迟读取数据并允许传入数据包在内核中缓冲,这最终会减慢对传入数据包的确认并降低该套接字的速率。
如果您想减慢机器的所有流量,您需要调整传入 TCP 缓冲区的大小。在 Linux 中,您可以通过更改 /proc/sys/net/ipv4/tcp_rmem(读取内存缓冲区大小)和其他 tcp_* 文件中的值来影响此更改。
要添加到 Branan 的答案:
如果您自愿限制接收端的读取速度,最终队列将在两端填满。然后,发送方要么阻塞其 send() 调用,要么从 send() 调用返回,其 sent_length 小于传递给 send() 调用的预期长度。
如果发件人没有准备好通过休眠并尝试重新发送不适合操作系统缓冲区的内容来处理这种情况,您最终将遇到连接问题(发件人可能会将其检测为错误)或丢失数据(发件人可能在不知不觉中丢弃不适合操作系统缓冲区的数据)。
设置小的套接字发送和接收缓冲区,比如 1k 或 2k,这样带宽 * 延迟乘积 = 缓冲区大小。您可能无法通过快速链接将其变得足够小。