Использование нескольких сокетов, лучше неблокировать или блокировать с помощью select?

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

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

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

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

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

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


person JPhi1618    schedule 06.10.2009    source источник


Ответы (2)


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

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

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

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

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

person bdonlan    schedule 06.10.2009
comment
Спасибо. Для этой реализации я не хочу использовать Overlapped IO, но спасибо за предложение. Я посмотрю на это. Я понимаю ваше предложение использовать неблокирующий режим AND select(). Мой главный вопрос заключался в том, должен ли я вызывать recv без причины или выполнять действия вызова select(). Спасибо за ответ! - person JPhi1618; 06.10.2009
comment
немного обновил его, чтобы было понятнее - просто цикл с recv() будет привязывать ЦП к 100%, что никогда не бывает приятно. - person bdonlan; 07.10.2009

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

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

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

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

person bobah    schedule 10.05.2010