UDP-сервер и подключенные сокеты - PullRequest
0 голосов
/ 12 января 2019

[править] Кажется, мой вопрос был задан почти 10 лет назад здесь ...

Эмуляция accept () для UDP (проблема синхронизации при настройке демультиплексированных сокетов UDP)

... без чистого и масштабируемого решения. Я думаю, что это может быть легко решено путем поддержки listen () и accept () для UDP, так же как и connect () сейчас. [/ Править]

В продолжение этого вопроса ...

Можете ли вы связать () и подключить () оба конца UDP-соединения

... есть ли механизм для одновременного связывания () и соединения ()?

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

Однако вызов bind () с новым дескриптором перенесет порт из дескриптора слушателя до тех пор, пока не будет выполнен вызов connect (). Это дает возможность, хотя и кратко, для ввода дейтаграмм в очередь новых дескрипторов.

Это окно также является проблемой для серверов UDP, желающих использовать DTLS. Это исправимо, если клиенты попытаются повторить попытку, но не обязательно.

Ответы [ 2 ]

0 голосов
/ 12 марта 2019

Вы описали проблему, с которой я столкнулся некоторое время назад, делая TCP-подобный механизм прослушивания / приема для UDP.

В моем случае решение (которое оказалось плохим, как я опишу позже) состояло в том, чтобы создать один UDP-сокет для приема любых входящих дейтаграмм и, когда он прибывает, подключить этот конкретный сокет к отправителю (через recvfrom() с помощью MSG_PEEK и connect()) и возвращаем его в новую ветку. Кроме того, был создан новый неподключенный сокет UDP для следующих входящих дейтаграмм. Таким образом, новый поток (и выделенный сокет) сделал recv() для сокета и с этого момента обрабатывал только этот конкретный канал, в то время как основной ожидал новых дейтаграмм, приходящих от других узлов.

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

Я не смог найти решение (например, создать новый подключенный сокет (вместо того, чтобы подключить основной) и передать полученную дейтаграмму из основного сокета в его приемный буфер для дальнейшего recv()). В итоге я получил N потоков, каждый из которых имел один «слушающий» сокет (с использованием SO_REUSEPORT) с рассеянием дейтаграмм на уровне ОС.

0 голосов
/ 12 января 2019

connect() в UDP не обеспечивает демультиплексирование соединения.

connect() делает две вещи:

  1. Устанавливает адрес по умолчанию для функций передачи, которые не принимают адрес назначения (send(), write() и т. Д.)

  2. Устанавливает фильтр для входящих дейтаграмм.

Важно отметить, что входящий фильтр просто отбрасывает датаграммы, которые не совпадают. Он не передает их в другом месте. Если к одному и тому же адресу привязано несколько сокетов UDP, некоторые ОС выберут один (может быть, случайный, может быть, последний созданный) для каждой дейтаграммы (демультиплексирование полностью нарушено), а некоторые доставят все дейтаграммы всем (демультиплексирование завершится успешно, но невероятно неэффективен). Оба из них "неправильные вещи". Даже ОС, которая позволяет выбирать между двумя вариантами поведения с помощью опции сокета, по-прежнему работает не так, как вы хотели. Время между bind() и connect() - это лишь малая часть этой загадки нежелательного поведения.

Для обработки UDP с несколькими одноранговыми узлами использовать один сокет в режиме без установления соединения . Чтобы несколько потоков обрабатывали полученные пакеты параллельно, вы можете либо

  • вызов recvfrom в нескольких потоках, которые обрабатывают данные (это работает, потому что сокеты дейтаграмм сохраняют границы сообщений, вы никогда не сделаете это с потоковым сокетом, таким как TCP), или
  • вызов recvfrom в одном потоке, который не выполняет никакой обработки, просто помещает сообщение в очередь для потока, ответственного за его обработку.

Даже если у вас была ОС, которая давала вам возможность отправлять входящий UDP на основе назначенных адресов одноранговых узлов (эмуляция соединения), выполнение этой операции внутри ОС все равно не будет более эффективным, чем в серверном приложении. и диспетчер пользовательского пространства, настроенный на ваши шаблоны трафика, вероятно, будет работать значительно лучше, чем диспетчер с универсальным размером для всех, предоставляемый ОС.

Например, сервер DNS (DHCP) будет осуществлять транзакции с большим количеством разных хостов, почти все из которых будут работать через порт 53 (67-68) на удаленном конце. Таким образом, хеширование на основе удаленного порта будет бесполезным, вам нужно хешировать на хосте. И наоборот, сервер кэширования, поддерживающий кластер серверов веб-приложений, будет работать с несколькими узлами и большим количеством различных портов. Здесь будет лучше хеширование на удаленном порту.

Выполните связывание соединений самостоятельно, не используйте эмуляцию сокетного соединения.

...