Сокет select() по сравнению с неблокированным recv

Я видел несколько рецензий, сравнивающих select() с poll() или epoll(), и я видел много руководств, обсуждающих фактическое использование select() с несколькими сокетами.

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

Ответ 1

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

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

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

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

Ответ 2

select(), и друзья позволяют создавать рабочий процесс таким образом, чтобы медлительность одного сокета не мешала скорости, с которой вы могли обслуживать другую. Представьте, что данные поступают быстро из приемного сокета, и вы хотите принять его как можно быстрее и сохранить в буферах памяти. Но отправляющий сокет медленный. Когда вы заполнили отправляющие буферы ОС и send() предоставили вам EWOULDBLOCK, вы можете выпустить select() для ожидания как для приемных, так и для отправляющих сокетов. select() провалится, если будут получены новые данные в принимающем сокете или освобождены некоторые буферы, и вы можете записать больше данных в отправляющий сокет, в зависимости от того, что произойдет раньше.

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

Фактически, select() сообщает вам, что когда следующая операция чтения или записи в соке называется успешной, поэтому, если вы только пытаетесь читать и писать, когда вы выбираете, вы можете работать, даже если вы не сделали этого, t сделать сокеты неблокирующими! Это по-прежнему неразумно, потому что существуют граничные случаи, когда следующая операция все еще может блокироваться, несмотря на то, что select() сообщила, что сокет "готов".

С другой стороны, создание блокировок, не блокирующих и не использующих select(), практически никогда не рекомендуется из-за причины объясненной @Troy.

Ответ 3

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

Спиннинг на recv() и сжигание CPU таким образом очень нежелательно; вы хотите, чтобы процесс дождался, пока данные станут доступными и не проснутся; что то, что select()/poll() и тому подобное.

И, sleep() в цикле, чтобы не записывать CPU, также не является хорошим решением. Вы ввели бы высокую задержку в обработке, поскольку программа не сможет обрабатывать данные, как только данные будут доступны.