Обрабатывать несколько TCP-клиентов

#c #sockets #tcp #client

#c #сокеты #tcp #клиент

Вопрос:

Я пишу простой TCP-сервер, и, несмотря на множество поисковых запросов, я не могу найти ответ на свой вопрос.

Я хочу, чтобы мой сервер обрабатывал несколько клиентов.

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

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

Максимальная длина сообщения составляет 64 Тыс. байт (хотя я мог бы уменьшить ее до 256 байт).

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

Я могу либо:

  1. иметь один буфер, но это означает, что как только я начал получать / восстанавливать сообщение из клиентского сокета, я игнорирую другие клиентские сокеты, пока текущее сообщение не будет полностью получено. Это подвержено DOS-атакам: один клиент, отправляющий огромное сообщение, байт за байтом, очень медленно, заблокирует сервер.
  2. имейте один буфер на клиентский сокет, и тогда мой сервер будет действительно параллельным. Но он плохо масштабируется с количеством клиентов.

Есть ли другой способ, который имеет преимущества обоих методов и не имеет недостатков?

Одна из моих идей заключалась в том, чтобы использовать буфер сокета для хранения всего сообщения. Я бы установил размер буфера на 64 КБ, используя setsockopt() и SO_RCVBUFSIZ .

Но мне нужно было бы выполнить a recv() с обоими MSG_WAITALL и MSG_DONTWAIT , чтобы либо сообщение было полностью доступно в буфере сокета, и я его получаю, либо оно еще не полностью получено, а затем recv() не блокируется. Однако эти 2 варианта не работают вместе.

Может быть, я могу сделать a recv() с MSG_PEEK , чтобы прочитать размер, затем другой recv() с MSG_PEEK , чтобы проверить, доступны ли все байты, и если да, то повторно выполните a recv() без MSG_PEEK , чтобы фактически прочитать байты из сокета?

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

Комментарии:

1. Сколько клиентов, как вы ожидаете, будут подключены к вашему устройству одновременно ? Сколько памяти доступно на вашем устройстве? Такие ответы помогут определить, какие стратегии буферизации вы можете использовать. Использование буфера 256B для каждого клиента не займет много памяти даже при работе с тысячами клиентов. Даже 64-килобайтный буфер для каждого клиента, вероятно, будет выполним на современном оборудовании.

2. У вас нет другого выбора, кроме как выполнить (2), но буфер не должен быть достаточно большим, чтобы вместить полное сообщение, если ваш синтаксический анализ сообщений достаточно умен.

3. Моя идея с MSG_PEEK не работает. Во-первых, как только фрагмент сообщения доступен в сокете, но сообщение еще не получено полностью, функция select() немедленно вернется, и сервер будет использовать 100% процессора. Во-вторых, я не могу обнаружить соединение, закрытое с помощью recv(), возвращающего 0.

Ответ №1:

Если вы можете иметь оба, почему бы не использовать оба?

  1. Иметь большой единый буфер для «нескольких» клиентов.
  2. Сохраняйте время между чтениями для каждого клиента, если вы считаете, что имеет место DOS-атака или чтение занимает много времени (из-за медленного соединения), удалите клиента (ов). Идея здесь заключается в реализации временного окна.
  3. Если вы находитесь в «прайм-тайм», увеличьте свой буфер (для большего количества клиентов) или выделите другой буфер (на основе банка).
  4. В противном случае сократите буфер.

С небольшим управлением вы можете обслуживать несколько клиентов одновременно и не тратить драгоценную память.

Комментарии:

1. Это интересная идея, но она становится довольно сложной. Я хотел бы, чтобы сервер был как можно более простым.

2. Как пожелаете. Я предоставил вам компромисс между 1) и 2). Но, конечно, у всего есть своя цена.

Ответ №2:

Я нашел эту серию блогов, которые я нахожу очень понятными и ответил на все мои вопросы: https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction /