Сокет 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, также не является хорошим решением. Вы ввели бы высокую задержку в обработке, поскольку программа не сможет обрабатывать данные, как только данные будут доступны.