Java отбрасывает половину пакетов UDP - PullRequest
6 голосов
/ 14 марта 2010

У меня простая настройка клиент / сервер. Сервер находится на C, а клиент, запрашивающий сервер, - Java.

Моя проблема заключается в том, что при отправке данных с интенсивным использованием полосы пропускания по соединению, например видеокадров, оно падает до половины пакетов. Я проверяю, правильно ли я фрагментирую пакеты udp на стороне сервера (максимальная длина полезной нагрузки udp составляет 2 ^ 16). Я проверил, что сервер отправляет пакеты (printf результат sendto ()). Но java, похоже, не получает половину данных.

Кроме того, когда я переключаюсь на TCP, все видеокадры проходят, но задержка начинает нарастать, добавляя задержку в несколько секунд после нескольких секунд работы.

Есть что-нибудь очевидное, что я скучаю? Я просто не могу понять это.

Ответы [ 5 ]

9 голосов
/ 14 марта 2010

Получите сетевой инструмент, например Wireshark , чтобы вы могли видеть, что происходит на проводе.

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

3 голосов
/ 14 марта 2010

Любой протокол приложений на основе UDP неизбежно будет подвержен потере пакетов, переупорядочению и (в некоторых случаях) дубликатам. «U» в UDP может означать «ненадежный», как в ненадежном протоколе дейтаграмм. (Хорошо, это действительно означает «Пользователь» ... но это, безусловно, хороший способ запомнить характеристики UDP.)

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

Если вы используете UDP в приложении, приложение должно учитывать ненадежную природу UDP, реализуя свои собственные механизмы для работы с отброшенными и неупорядоченными пакетами и для управления собственным потоком. (Приложение, которое взрывает UDP-пакеты, не задумываясь о том, что это может иметь место в уже перегруженной сети, является плохим жителем сети .)

(В случае TCP пакеты, вероятно, также отбрасываются, но TCP обнаруживает и повторно отправляет отброшенные пакеты, и включается механизм управления потоком TCP, чтобы замедлить скорость передачи данных. Чистый результат " латентность».)

РЕДАКТИРОВАТЬ - исходя из комментария ОП, причиной его проблемы было то, что клиент не "прослушивал" в течение определенного периода, в результате чего пакеты (предположительно) были отброшены ОС клиента. Способ решения этой проблемы:

  1. использует выделенный поток Java, который просто читает пакеты и ставит их в очередь для обработки, а

  2. увеличить размер очереди пакетов ядра для сокета.

Но даже если вы примете эти меры, вы все равно сможете отбросить пакеты. Например, если машина перегружена, приложение может не получать временные срезы выполнения достаточно часто, чтобы прочитать и поставить в очередь все пакеты, прежде чем ядру придется их отбрасывать.

EDIT 2 - Есть некоторые споры о том, подвержена ли UDP дубликатам. Это, безусловно, правда, что UDP не имеет обнаружения или предотвращения врожденных дубликатов. Но также верно и то, что структура маршрутизации IP-пакетов, которая является Интернетом, вряд ли будет самопроизвольно дублировать пакеты. Таким образом, дубликаты, если они имеют место, могут возникать, потому что отправитель решил повторно отправить пакет UDP. Таким образом, на мой взгляд, хотя UDP подвержен проблемам с дубликатами, он не вызывает их per se ..., если только нет ошибки в стеке протоколов ОС или в структуре IP.

2 голосов
/ 14 марта 2010

Хотя UDP поддерживает пакеты длиной до 65535 байтов (, включая заголовок UDP, который составляет 8 байтов - но см. Примечание 1), базовый транспорт между вами и пунктом назначения не поддерживает пакеты IP, которые долго. Например, кадры Ethernet имеют максимальный размер 1500 байтов - с учетом издержек для заголовков IP и UDP, это означает, что любой пакет UDP с длиной полезной нагрузки данных более чем около 1450, вероятно, будет фрагментирован на несколько дейтаграмм IP.

Пакет UDP максимального размера будет фрагментирован по крайней мере на 45 отдельных дейтаграмм IP - и если какой-либо один из этих фрагментов будет потерян, весь пакет UDP будет потерян. Если ваш базовый коэффициент потери пакетов равен 1%, ваше приложение увидит уровень потерь около 36%!

Если вы хотите видеть меньше потерянных пакетов, не отправляйте огромные пакеты - ограничьте данные в каждом пакете примерно до 1400 байтов (или даже сделайте свое собственное «Определение MTU пути», чтобы определить максимальный размер, который вы можете безопасно отправлять без фрагментации).


  1. Конечно, UDP также подвержен ограничениям IP, и датаграммы IP имеют максимальный размер 65535, включая заголовок IP. Размер заголовка IP составляет от 20 до 60 байтов, поэтому максимальный объем данных приложения, переносимых в пакете UDP, может составлять всего 65467.
0 голосов
/ 14 марта 2010

IP поддерживает пакеты до 65535 байтов, включая заголовок IP-пакета 20 байт. UDP поддерживает дейтаграмм до 65507 байт, плюс 20-байтовый IP-заголовок и 8 байт UDP-заголовок. Однако сетевой MTU является практическим пределом, и не забывайте, что он включает в себя не только эти 28 байтов, но и заголовок кадра Ethernet. реальный практический предел для нефрагментированного UDP - это минимальный MTU в 576 байтов за вычетом всех служебных данных.

0 голосов
/ 14 марта 2010

Возможно, проблема связана с заполнением буфера передачи в вашем UDPSocket. Отправляйте только количество байтов за один раз, указанное UDPSocket.getSendBufferSize(). Используйте setSendBufferSize(int size), чтобы увеличить это значение.

Если #send () используется для отправки DatagramPacket, который больше, чем установка SO_SNDBUF, тогда это конкретная реализация, если пакет отправлено или отклонено.

...