Мы потратили достаточно много времени, выясняя структуру параметров функции
select
. Теперь, наконец-то, можно перейти к описанию того, зачем она нужна и какой смысл несет каждый из ее параметров.
Функция
select
позволяет дождаться, когда хотя бы один из сокетов, переданный в одном из множеств, будет готов к выполнению той или иной операции. Какой именно операции, определяется тем, в какое из трех множеств входит сокет. Для сокетов, входящих в множество
readfds
, готовность означает, что функции
recv
или
recvfrom
будут выполнены без блокирования. В случае UDP это означает, что во входном буфере сокета есть данные, которые можно прочитать.
При использовании TCP функции
recv
и
recvfrom
могут быть выполнены без задержки еще в двух случаях: когда партнер закрыл соединение (в этом случае функции вернут 0), а также когда соединение некорректно разорвано (в этом случае функции вернут
SOCKET_ERROR
). Кроме того, если сокет, включенный в множество
readfds
, находится в состоянии ожидания соединения (в которое он переведен с помощью функции
listen
), то для него состояние готовности означает, что очередь соединений не пуста и функция
accept
будет выполнена без задержек.
Для сокетов, входящих в множество
writefds
, готовность означает, что сокет соединен, а в его выходном буфере есть свободное место. (До сих пор мы обсуждали только блокирующие сокеты, для которых успешное завершение функции connect автоматически означает, что сокет соединен. Далее мы познакомимся с неблокирующими сокетами, для которых нужно вызвать функцию
select
, чтобы понять, установлено ли соединение.) Наличие свободного места в буфере не гарантирует того, что функции
send
или
sendto
не будут блокировать вызвавшую их нить, т.к. программа может попытаться передать больший объем информации, чем размер свободного места в буфере на момент вызова функции. В этом случае функции
send
и
sendto
вернут управление вызвавшей их нити только после того, как часть данных будет отправлена, и в буфере сокета освободится достаточно места.
Следует отметить, что большинство протоколов обмена устроено таким образом, что при их реализации проблема переполнения выходного буфера практически никогда не возникает. Чаще всего клиент и сервер обмениваются небольшими пакетами, причем сервер посылает клиенту только ответы на его запросы, а клиент не посылает новый запрос до тех пор. пока не получит ответ на предыдущий. В этом случае гарантируется, что пакеты будут уходить о выходного буфера быстрее (или, по крайней мере, не медленнее), чем программа будет их туда помещать. Поэтому заботиться о том, чтобы в выходном буфере было место, приходится достаточно редко.
И наконец, последнее множество
exceptfds
. Для сокетов, входящих в это множество, состояние готовности означает либо неудачу попытки соединения для неблокирующего сокета, либо получение высокоприоритетных данных (out-of-band data). В этой книге мы не будем детально рассматривать отправку и получение высокоприоритетных данных. Те, кому это понадобится, легко разберутся с этим вопросом по MSDN.
Функция
select
возвращает общее количество сокетов, находящихся в состоянии готовности. Если функция завершила работу по тайм-ауту, возвращается 0. Множества
readfds
,
writefds
и
exceptfds
модифицируются функцией: в них остаются только те сокеты, которые находятся в состоянии готовности. При вызове функции любые два из этих трех указателей могут быть равны
nil
, если программу не интересует готовность сокетов по соответствующим критериям. Один и тот же сокет может входить в несколько множеств.
В листинге 2.23 приведен пример кода TCP-сервера, взаимодействующего с несколькими клиентами в рамках одной нити и работающего по простой схеме "запрос-ответ".
Как и в предыдущих примерах, код для краткости не содержит проверок успешности завершения функций. Еще раз напоминаем, что в реальном коде такие проверки необходимы.
Теперь разберем программу по шагам. Создание сокета, привязка к адресу и перевод в режим ожидания подключений вам уже знакомы, поэтому мы на них останавливаться не будем. Отметим только, что вместо переменной типа
TSocket
мы формируем динамический массив этого типа, длина которого сначала устанавливается равной одному элементу, и этот единственный элемент и содержит дескриптор созданного сокета. В дальнейшем мы будем добавлять в этот массив сокеты, создающиеся в результате выполнения функции
accept
. После перевода сокета в режим ожидания подключения начинается бесконечный цикл, состоящий из четырех шагов.