Как вы ограничиваете скорость операции ввода-вывода?
-
01-07-2019 - |
Вопрос
Предположим, у вас есть программа, которая считывает данные из сокета.Как вам удается поддерживать скорость загрузки ниже определенного заданного порога?
Решение
На прикладном уровне (используя API в стиле сокетов Berkeley) вы просто смотрите на часы и считываете или записываете данные со скоростью, которую вы хотите ограничить.
Если вы читаете в среднем всего 10 Кбит / с, но источник отправляет больше, то в конечном итоге все буферы между ним и вами заполнятся.TCP / IP допускает это, и протокол обеспечит замедление работы отправителя (на прикладном уровне, вероятно, все, что вам нужно знать, это то, что на другом конце блокирующие вызовы записи будут блокироваться, неблокирующая запись завершится ошибкой, а асинхронная запись не завершится, пока вы не прочитаете достаточно данных, чтобы разрешить это).
На прикладном уровне вы можете быть только приблизительными - вы не можете гарантировать жестких ограничений, таких как "не более 10 кб будет проходить через данную точку сети в любую секунду".Но если вы будете отслеживать то, что получили, то сможете получить правильное среднее значение в долгосрочной перспективе.
Другие советы
Предполагая сетевой транспорт, основанный на TCP / IP, пакеты отправляются в ответ на пакеты ACK / NACK, идущие другим путем.
Ограничивая скорость передачи пакетов, подтверждающих получение входящих пакетов, вы, в свою очередь, снизите скорость отправки новых пакетов.
Это может быть немного неточно, поэтому, возможно, оптимальным будет отслеживать скорость нисходящего потока и адаптивно регулировать скорость отклика до тех пор, пока она не опустится ниже комфортного порога.(Однако это произойдет очень быстро, вы отправляете количество подтверждений в секунду)
Это похоже на ограничение игры определенным количеством кадров в секунду.
extern int FPS;
....
timePerFrameinMS = 1000/FPS;
while(1) {
time = getMilliseconds();
DrawScene();
time = getMilliseconds()-time;
if (time < timePerFrameinMS) {
sleep(timePerFrameinMS - time);
}
}
Таким образом, вы убедитесь, что частота обновления игры будет не более нескольких кадров в секунду.Точно так же drawScene может быть функцией, используемой для перекачки байтов в поток сокета.
Если вы читаете из сокета, у вас нет контроля над используемой пропускной способностью - вы читаете буфер операционной системы этого сокета, и что бы вы ни сказали, это не заставит человека, пишущего в сокет, записывать меньше данных (если, конечно, вы не разработали протокол для этого).
Все, что могло бы сделать такое медленное чтение, - это заполнить буфер и вызвать возможную остановку на сетевом конце, но вы не можете контролировать, как и когда это произойдет.
Если вы действительно хотите читать только такое количество данных за раз, вы можете сделать что-то вроде этого:
ReadFixedRate() {
while(Data_Exists()) {
t = GetTime();
ReadBlock();
while(t + delay > GetTime()) {
Delay()'
}
}
}
wget, похоже, управляет этим с помощью опции --limit-rate.Вот со справочной страницы:
Обратите внимание, что Wget реализует ограничение путем перехода в режим ожидания на соответствующее количество времени после сетевого чтения, которое заняло меньше времени, чем указано в скорости.В конечном итоге эта стратегия приводит к тому, что передача TCP замедляется примерно до указанной скорости.Однако для достижения этого баланса может потребоваться некоторое время, поэтому не удивляйтесь, если ограничение скорости плохо работает с очень маленькими файлами.
Как уже говорили другие, ядро операционной системы управляет трафиком, а вы просто считываете копию данных из памяти ядра.Чтобы грубо ограничить скорость работы только одного приложения, вам нужно отложить чтение данных и позволить входящим пакетам буферизоваться в ядре, что в конечном итоге замедлит подтверждение входящих пакетов и снизит скорость работы этого одного сокета.
Если вы хотите замедлить весь трафик, поступающий на компьютер, вам нужно пойти и настроить размеры ваших входящих буферов TCP.В Linux вы могли бы повлиять на это изменение, изменив значения в /proc/sys/net/ipv4/tcp_rmem (размеры буфера чтения памяти) и других файлах tcp_ *.
Чтобы добавить к ответу Бранана:
Если вы добровольно ограничите скорость чтения на стороне получателя, в конечном итоге очереди заполнятся на обоих концах.Затем отправитель либо заблокирует свой вызов send(), либо вернется из вызова send() с значением sent_length, меньшим ожидаемой длины, переданной вызову send().
Если отправитель не готов справиться с этим случаем, перейдя в спящий режим и пытаясь повторно отправить то, что не поместилось в буферы ОС, у вас в конечном итоге возникнут проблемы с подключением (отправитель может определить это как ошибку) или потеря данных (отправитель может неосознанно отбросить данные, которые не поместились в буферы ОС).
Установите небольшие буферы отправки и приема сокетов, скажем, 1k или 2k, таким образом, чтобы произведение пропускной способности * задержки = размеру буфера.Возможно, вам не удастся сделать его достаточно маленьким по быстрым ссылкам.