Использование нескольких сокетов, неблокирование или блокировка с выбором лучше? - PullRequest
0 голосов
/ 06 октября 2009

Допустим, у меня есть серверная программа, которая может принимать соединения от 10 (или более) разных клиентов. Клиенты отправляют данные случайным образом, которые принимаются сервером, но есть уверенность, что по крайней мере один клиент будет отправлять данные каждое обновление Сервер не может дождаться поступления информации, потому что ему нужно выполнить другую обработку. Помимо использования асинхронных сокетов, я вижу два варианта:

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

  2. Оставьте розетки как блокирующие. Добавьте все сокеты к FD_SET и позвоните select(). Если возвращаемое значение ненулевое (что будет происходить большую часть времени), переберите все сокеты, чтобы найти подходящее число читаемых сокетов с FD_ISSET() и вызовите recv() только для читаемых сокетов.

Первая опция создаст намного больше вызовов функции recv(). Второй метод - больший боль с точки зрения программирования из-за всех циклов FD_SET и FD_ISSET.

Какой метод (или другой метод) является предпочтительным? Избегает ли издержек при сбое recv() в неблокирующем сокете, что стоит хлопот с вызовом select()?

Я думаю, что понимаю оба метода, и я попробовал оба с успехом, но я не знаю, считается ли один из способов лучшим или оптимальным.

Ответы [ 2 ]

1 голос
/ 10 мая 2010

IO должен либо полностью блокироваться с одним потоком на соединение, и в этом случае цикл событий по сути является планировщиком ОС, либо IO должен быть полностью неблокирующим, и в этом случае цикл событий на основе select / waitformultipleobjects будет в вашем приложении

Все промежуточные варианты не очень удобны в обслуживании и подвержены ошибкам

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

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

1 голос
/ 06 октября 2009

Я бы рекомендовал вместо этого использовать перекрывающийся IO . Затем вы можете запустить WSARecv() и предоставить функцию обратного вызова, которая будет вызываться после завершения операции. Более того, поскольку она будет вызываться только тогда, когда ваша программа находится в состоянии ожидания с оповещением, вам не нужно беспокоиться о блокировках, как если бы вы работали в многопоточном приложении (при условии, что вы запускаете их в своем основном потоке).

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

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

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

Возможно, вы захотите использовать библиотеку для абстрагирования от привередливых деталей. Например, libevent или boost :: asio .

...