Ограничение отправок TCP с помощью очереди «на отправку» и другие проблемы проектирования - PullRequest
4 голосов
/ 14 июня 2010

Этот вопрос является результатом двух других вопросов, которые я задавал в последние несколько дней.
Я создаю новый вопрос, потому что я думаю, что он связан со «следующим шагом» в моем понимании того, как контролировать поток моих отправки / получения, на который я еще не получил полного ответа.
Другие связанные вопросы:
Вопрос интерпретации документации IOCP - неопределенность владения буфером
Неблокирующие проблемы с буфером TCP

Итак, я использую порты завершения ввода / вывода Windows.
У меня есть несколько потоков, которые обрабатывают уведомления от порта завершения.
Я полагаю, что этот вопрос не зависит от платформы и будет иметь такой же ответ, как если бы он делал то же самое в системах * nix, * BSD, Solaris.

Итак, мне нужна собственная система управления потоком. Хорошо.
Так что я отправляю отправлять и отправлять, много. Как узнать, когда начинать ставить в очередь посылки, так как сторона получателя ограничена количеством X?

Давайте рассмотрим пример (наиболее близкий к моему вопросу): протокол FTP.
У меня есть два сервера; Один находится на ссылке 100 МБ, а другой на ссылке 10 МБ.
Я заказываю один 100 МБ, чтобы отправить другому (связанный 10 МБ) файл 1 ГБ. Он заканчивается со средней скоростью передачи 1,25 МБ / с.
Как отправитель (связанный со 100 МБ) знал, когда следует задержать отправку, чтобы более медленный не был затоплен? (В этом случае очередь «для отправки» является фактическим файлом на жестком диске).

Еще один способ задать вопрос:
Могу ли я получить уведомление о задержке отправки с удаленной стороны? Это встроенный в TCP или так называемый "надежный сетевой протокол", который мне нужен?

Конечно, я могу ограничить отправку фиксированным числом байтов, но это просто не подходит мне.

Опять же, у меня есть цикл с большим количеством отправок на удаленный сервер, и в какой-то момент в этом цикле я должен определить, должен ли я поставить эту очередь в очередь, или я могу передать ее на транспортный уровень (TCP) .
Как я могу это сделать? Чтобы ты делал? Конечно, когда я получу уведомление о завершении от IOCP о том, что отправка была выполнена, я отправлю другие ожидающие отправки, это ясно.

Еще один вопрос дизайна, связанный с этим:
Поскольку я должен использовать пользовательские буферы с очередью отправки, и эти буферы освобождаются для повторного использования (таким образом, не используя ключевое слово «delete»), когда получено уведомление «send-done», мне придется использовать взаимное исключение в этом пуле буферов.
Использование мьютекса замедляет работу, поэтому я подумал; Почему бы каждому потоку не иметь свой собственный пул буферов, поэтому доступ к нему, по крайней мере при получении необходимых буферов для операции отправки, не потребует мьютекса, поскольку он принадлежит только этому потоку.
Пул буферов находится на уровне локального хранилища потоков (TLS).
Отсутствие взаимного пула подразумевает отсутствие необходимости в блокировке, подразумевает более быстрые операции, НО также подразумевает больше памяти, используемой приложением, потому что даже если один поток уже выделил 1000 буферов, другому, который отправляет прямо сейчас и требуется 1000 буферов для отправки чего-либо, потребуется выделение это свое.

Другая проблема:
Скажем, у меня есть буферы A, B, C в очереди «для отправки».
Затем я получаю уведомление о завершении, в котором говорится, что получатель получил 10 из 15 байтов. Должен ли я повторно отправить из относительного смещения буфера, или TCP будет обрабатывать его для меня, т.е. завершить отправку? И если мне нужно, могу ли я быть уверен, что этот буфер является «следующим для отправки» в очереди или это может быть буфер B, например?

Это длинный вопрос, и я надеюсь, что никто не пострадал (:

Мне бы очень хотелось, чтобы кто-то нашел время, чтобы ответить здесь. Я обещаю, что дважды проголосую за него! (:
Спасибо всем!

Ответы [ 3 ]

3 голосов
/ 14 июня 2010

Во-первых: я бы задал это как отдельные вопросы. Скорее всего, вы получите ответы таким образом.

Я говорил о большей части этого в своем блоге: http://www.lenholgate.com, но так как вы уже написали мне письмо, чтобы сказать, что вы читаете мой блог, вы знаете, что ...

Проблема управления потоком TCP такова, что, поскольку вы публикуете асинхронные записи, и каждый из них использует ресурсы до завершения (см. здесь ). Во время ожидания записи существуют различные проблемы, связанные с использованием ресурсов, и использование вашего буфера данных является наименее важным из них; вы также будете использовать некоторый пейджинг невыгружаемого пула, который является ограниченным ресурсом (хотя в Vista и более поздних версиях его гораздо больше, чем в предыдущих операционных системах), вы также будете блокировать страницы в памяти на время записи, и есть ограничение на общее количество страниц, которые ОС может заблокировать. Обратите внимание, что как проблемы с использованием невыгружаемого пула, так и проблемы с блокировкой страниц не очень хорошо документированы в любом месте, но вы увидите сбой записи с ENOBUFS, как только вы нажмете на них.

Из-за этих проблем нецелесообразно иметь бесконтрольное количество ожидающих записей. Если вы отправляете большой объем данных, и у вас нет управления потоком на уровне приложения, вам нужно знать, что если вы отправляете данные быстрее, чем они могут быть обработаны другим концом соединения, или быстрее, чем скорость соединения, затем вы начнете использовать много и много вышеперечисленных ресурсов, поскольку ваши записи занимают больше времени из-за проблем управления потоком TCP и окон. Вы не получите этих проблем с блокировкой кода сокета, поскольку вызовы записи просто блокируются, когда стек TCP больше не может записывать из-за проблем управления потоком; с async пишет записи завершены и затем ожидают. С блокирующим кодом блокировка касается вашего управления потоком для вас; с асинхронной записью вы можете продолжать цикл и все больше и больше данных, которые просто ждут отправки стека TCP ...

В любом случае, из-за этого с асинхронным вводом-выводом в Windows вы ВСЕГДА должны иметь явную форму управления потоком. Таким образом, вы либо добавляете управление потоком на уровне приложения к своему протоколу, возможно, используя ACK, чтобы вы знали, когда данные достигли другой стороны, и позволяете только определенному количеству оставаться невыполненным в любой момент времени ИЛИ, если вы не можете добавить к протокол уровня приложения, вы можете управлять вещами, используя ваши завершения записи. Хитрость заключается в том, чтобы разрешить определенное количество ожидающих завершений записи для каждого соединения и ставить в очередь данные (или просто не генерировать их), как только вы достигнете своего предела. Затем, когда каждая запись завершается, вы можете сгенерировать новую запись ....

Ваш вопрос о пуле буферов данных, IMHO, является преждевременной оптимизацией с вашей стороны прямо сейчас. Достигните точки, когда ваша система работает должным образом, и вы профилировали свою систему и обнаружили, что конфликт в вашем буферном пуле является самой важной горячей точкой, и ТО затем решите ее. Я обнаружил, что пулы буферов для каждого потока работают не так хорошо, так как распределение распределений и освобождений между потоками имеет тенденцию быть не столь сбалансированным, как это необходимо для работы. Я говорил об этом больше в моем блоге: http://www.lenholgate.com/blog/2010/05/performance-comparisons-for-recent-code-changes.html

Ваш вопрос о частичных завершениях записи (вы отправляете 100 байтов, а завершение возвращается и говорит, что вы отправили только 95) на самом деле не является проблемой ИМХО. Если вы дойдете до этой позиции и у вас будет более одной ожидающей записи, то вы ничего не сможете сделать, последующие записи вполне могут сработать, и у вас не будет байтов в том, что вы ожидали отправить; НО а) Я никогда не видел, чтобы это произошло, если вы уже не столкнулись с проблемами с ресурсами, которые я подробно описал выше, и б) вы ничего не можете сделать, если вы уже опубликовали больше записей по этому соединению, поэтому просто прервите соединение - обратите внимание, что почему я всегда профилирую свои сетевые системы на оборудовании, на котором они будут работать, и я стараюсь устанавливать ограничения в МОЕМ коде, чтобы не допустить когда-либо ограничения ресурсов ОС (плохие драйверы в операционных системах до Vista часто показывают синий экран, если могут) Вы не получите пейджинговый пул, так что вы можете убрать ящик, если не будете внимательно относиться к этим деталям.

Отдельные вопросы в следующий раз, пожалуйста.

1 голос
/ 14 июня 2010

Чтобы ответить на ваш вопрос о том, когда это замедлилось, вам, похоже, не хватает понимания механизмов перегрузки TCP.«Медленный старт» - это то, о чем вы говорите, но не совсем так, как вы это сформулировали.Медленный старт - это именно то, что начинается медленно и становится быстрее, до той скорости, с которой готов идти другой конец, скорость проводной линии, что угодно.ответа должно быть достаточно.

1 голос
/ 14 июня 2010

Q1.Большинство API выдадут вам событие «запись возможна» после того, как вы в последний раз написали и запись снова стала доступной (может произойти немедленно, если вам не удалось заполнить большую часть буфера отправки последней отправкой)., он прибудет как событие «новые данные».Думайте о новых данных как о «прочитано нормально», так что есть также событие «хорошо написано».Имена API-интерфейсов различаются.

Q2.Если переход в режим ядра для получения мьютекса для каждой части данных причиняет вам боль, я рекомендую переосмыслить то, что вы делаете.Это занимает не более 3 микросекунд, в то время как размер окна вашего планировщика потоков может составлять до 60 миллисекунд.

В крайних случаях это может повредить.Если вы думаете, что программируете экстремальные коммуникации, пожалуйста, спросите еще раз, и я обещаю рассказать вам все об этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...