#c #sockets #tcp #client
#c #сокеты #tcp #клиент
Вопрос:
Я пишу простой TCP-сервер, и, несмотря на множество поисковых запросов, я не могу найти ответ на свой вопрос.
Я хочу, чтобы мой сервер обрабатывал несколько клиентов.
По соображениям производительности и простоты я хочу, чтобы мой сервер был однопоточным, и я использую select()
для обработки всех сокетов.
Кроме того, мой протокол основан на сообщениях. Кадрирование сообщений выполняется с использованием префикса длины. Это означает, что серверу нужен буфер для восстановления фрагмента сообщений за фрагментом.
Максимальная длина сообщения составляет 64 Тыс. байт (хотя я мог бы уменьшить ее до 256 байт).
Примечание: это будет выполняться на крошечном встроенном устройстве, поэтому использование уровня обмена сообщениями, такого как ZMQ, не является вариантом (недостаточно памяти).
Я могу либо:
- иметь один буфер, но это означает, что как только я начал получать / восстанавливать сообщение из клиентского сокета, я игнорирую другие клиентские сокеты, пока текущее сообщение не будет полностью получено. Это подвержено DOS-атакам: один клиент, отправляющий огромное сообщение, байт за байтом, очень медленно, заблокирует сервер.
- имейте один буфер на клиентский сокет, и тогда мой сервер будет действительно параллельным. Но он плохо масштабируется с количеством клиентов.
Есть ли другой способ, который имеет преимущества обоих методов и не имеет недостатков?
Одна из моих идей заключалась в том, чтобы использовать буфер сокета для хранения всего сообщения. Я бы установил размер буфера на 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:
Если вы можете иметь оба, почему бы не использовать оба?
- Иметь большой единый буфер для «нескольких» клиентов.
- Сохраняйте время между чтениями для каждого клиента, если вы считаете, что имеет место DOS-атака или чтение занимает много времени (из-за медленного соединения), удалите клиента (ов). Идея здесь заключается в реализации временного окна.
- Если вы находитесь в «прайм-тайм», увеличьте свой буфер (для большего количества клиентов) или выделите другой буфер (на основе банка).
- В противном случае сократите буфер.
С небольшим управлением вы можете обслуживать несколько клиентов одновременно и не тратить драгоценную память.
Комментарии:
1. Это интересная идея, но она становится довольно сложной. Я хотел бы, чтобы сервер был как можно более простым.
2. Как пожелаете. Я предоставил вам компромисс между 1) и 2). Но, конечно, у всего есть своя цена.
Ответ №2:
Я нашел эту серию блогов, которые я нахожу очень понятными и ответил на все мои вопросы: https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction /