TTimeVal = record
tv_sec: LongInt;
tv_usec: LongInt;
end;
Эта структура служит для задания времени ожидания. Поле tv_sec
содержит число полных секунд в этом интервале, поле tv_usec
— число микросекунд. Так, чтобы задать интервал ожидания, равный 1,5 с, нужно присвоить полю tv_sec
значение 1, а полю tv_usec
— значение 500 000. Параметр timeout
функции select
должен содержать указатель на заполненную подобным образом структуру, определяющую, сколько времени функция будет ожидать, пока хотя бы один из сокетов не будет готов к требуемой операции. Если этот указатель равен nil
, ожидание будет бесконечным.
Мы потратили достаточно много времени, выясняя структуру параметров функции select
. Теперь, наконец-то, можно перейти к описанию того, зачем она нужна и какой смысл несет каждый из ее параметров.
Функция select
позволяет дождаться, когда хотя бы один из сокетов, переданный в одном из множеств, будет готов к выполнению той или иной операции. Какой именно операции, определяется тем, в какое из трех множеств входит сокет. Для сокетов, входящих в множество readfds
, готовность означает, что функции recv
или recvfrom
будут выполнены без блокирования. В случае UDP это означает, что во входном буфере сокета есть данные, которые можно прочитать. При использовании TCP функции recv
и recvfrom
могут быть выполнены без задержки еще в двух случаях: когда партнер закрыл соединение (в этом случае функции вернут 0), а также когда соединение некорректно разорвано (в этом случае функции вернут SOCKET_ERROR
). Кроме того, если сокет, включенный в множество readfds
, находится в состоянии ожидания соединения (в которое он переведен с помощью функции listen
), то для него состояние готовности означает, что очередь соединений не пуста и функция accept
будет выполнена без задержек.
Для сокетов, входящих в множество writefds
, готовность означает, что сокет соединен, а в его выходном буфере есть свободное место. (До сих пор мы обсуждали только блокирующие сокеты, для которых успешное завершение функции connect автоматически означает, что сокет соединен. Далее мы познакомимся с неблокирующими сокетами, для которых нужно вызвать функцию select
, чтобы понять, установлено ли соединение.) Наличие свободного места в буфере не гарантирует того, что функции send
или sendto
не будут блокировать вызвавшую их нить, т. к. программа может попытаться передать больший объем информации, чем размер свободного места в буфере на момент вызова функции. В этом случае функции send
и sendto
вернут управление вызвавшей их нити только после того, как часть данных будет отправлена, и в буфере сокета освободится достаточно места.
Следует отметить, что большинство протоколов обмена устроено таким образом, что при их реализации проблема переполнения выходного буфера практически никогда не возникает. Чаще всего клиент и сервер обмениваются небольшими пакетами, причем сервер посылает клиенту только ответы на его запросы, а клиент не посылает новый запрос до тех пор. пока не получит ответ на предыдущий. В этом случае гарантируется, что пакеты будут уходить о выходного буфера быстрее (или, по крайней мере, не медленнее), чем программа будет их туда помещать. Поэтому заботиться о том, чтобы в выходном буфере было место, приходится достаточно редко.
И наконец, последнее множество exceptfds
. Для сокетов, входящих в это множество, состояние готовности означает либо неудачу попытки соединения для неблокирующего сокета, либо получение
Функция select
возвращает общее количество сокетов, находящихся в состоянии готовности. Если функция завершила работу по тайм-ауту, возвращается 0. Множества readfds
, writefds
и exceptfds
модифицируются функцией: в них остаются только те сокеты, которые находятся в состоянии готовности. При вызове функции любые два из этих трех указателей могут быть равны nil
, если программу не интересует готовность сокетов по соответствующим критериям. Один и тот же сокет может входить в несколько множеств.
В листинге 2.23 приведен пример кода TCP-сервера, взаимодействующего с несколькими клиентами в рамках одной нити и работающего по простой схеме "запрос-ответ".
select
var
Sockets: array of TSocket;
Addr: TSockAddr;
Data: TWSAData;
Len, I, J: Integer;
FDSet: TFDSet;
begin
WSAStartup($101, Data);
SetLength(Sockets, 1);
Sockets[0]:= socket(AF_INET, SOCK_STREAM, 0);
Addr.sin_family:= AF_INET;