Неблокирующие сокеты при использовании мультиплексирования ввода-вывода

#c #sockets #networking #nonblocking #multiplexing

#c #сокеты #сеть #неблокирующие #мультиплексирование

Вопрос:

Должен ли я использовать неблокирующие или блокирующие TCP-сокеты при использовании API мультиплексирования ввода-вывода, такого как poll(2) или epoll(2) ?

Некоторые люди предлагают использовать здесь неблокирующие сокеты, но API мультиплексирования ввода-вывода в любом случае сообщают вам, есть ли данные для чтения, так что не так с блокирующим сокетом здесь?

Ответ №1:

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

Один из способов избежать этой проблемы — перевести все сокеты сервера в неблокирующий режим ввода-вывода; таким образом, сервер знает, что он никогда не сможет «застрять» внутри recv() или send() вызова, и, таким образом, может оставаться отзывчивым ко всем клиентам, независимо от того, хорошо ведет себя какой-либо конкретный клиент или нет. В неблокирующем дизайне единственное место, которое сервер когда-либо блокирует, находится внутри select() или poll() или аналогично, потому что эти вызовы предназначены для возврата всякий раз, когда какому-либо клиенту требуется обслуживание, а не для блокировки только для одного клиента. (компромисс заключается в том, что при неблокирующем вводе-выводе логика буферизации / организации очередей вашего сервера должна быть немного более сложной, поскольку вы больше не можете предполагать, что какое-либо конкретное фиксированное количество байтов будет отправлено или получено во время любой заданной операции отправки или получения)

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

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

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

1. Для предотвращения DDOS также необходим таймер в том же потоке ввода-вывода, чтобы иметь возможность отключать TCP-соединения, простаивающие некоторое время.

2. Это сработало бы, или просто найдите TCP-соединение с самым длительным временем простоя и отключите его, когда достигнете максимального предела одновременных подключений.

Ответ №2:

Неблокирующий ввод-вывод используется, когда вы предпочитаете ответ об ошибке ( EWOULDBLOCK / EAGAIN ) вашему потоку, ожидающему (блокирующему), пока операция ввода-вывода не станет возможной.

Это приводит к вопросу о том, как достигается мультиплексирование ввода-вывода?

Если вы используете модель потока для каждого соединения (или процесса для каждого соединения), использование блокирующего ввода-вывода может быть более удобным.

Однако, если один и тот же поток обслуживает несколько объектов ввода-вывода, блокировка ввода-вывода была бы опасной и могла бы привести к остановке всего приложения.

Лучше использовать неблокирующий ввод-вывод, когда один поток обслуживает несколько объектов ввода-вывода.

Обратите внимание, что проблема может быть незаметна сначала при опросе (с использованием select / poll или epoll / kqueue ).

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

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

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

1. Обратите внимание, что в Linux recv() может блокироваться, даже если select() ранее указано, что сокет готов к чтению. Смотрите четвертый абзац раздела «ОШИБКИ» справочной страницы Linux для select() : man7.org/linux/man-pages/man2/select . 2.html