Первый параметр nfds задает число
uses linux;
Function Select(Nfds:Longint; var readfds,writefds, errorfds:PFDset;
Var Timeout): Longint;
Первый параметр nfds задает число дескрипторов файлов, которые могут представлять интерес для сервера. Например, если дескрипторы файлов с номерами 0, 1 и 2 присвоены потокам stdin, stdout и stderr соответственно, и открыты еще два файла с дескрипторами 3 и 4, то можно присвоить параметру nfds значение 5. Программист может определять это значение самостоятельно или воспользоваться постоянной FD_SETSIZE, которая определена в файле stdio. Значение постоянной FD_SETSIZE равно максимальному числу дескрипторов файлов, которые могут быть использованы вызовом select.
Второй, третий и четвертый параметры вызова select являются указателями на битовые маски (bit mask), в которых каждый бит соответствует дескриптору файла. Если бит включен, то это обозначает интерес к соответствующему дескриптору файла. Набор readfds определяет дескрипторы, для которых сервер ожидает возможности чтения; набор writefds – дескрипторы, для которых ожидается возможность выполнить запись; набор
errorfds определяет дескрипторы, для которых сервер ожидает появление ошибки или исключительной ситуации, например, по сетевому соединению могут поступить внеочередные данные. Так как работа с битами довольно неприятна и приводит к немобильности программ, существует абстрактный тип данных fdset, а также макросы или функции (в зависимости от конкретной реализации системы) для работы с объектами этого типа. Вот эти макросы для работы с битами файловых дескрипторов:
uses linux;
(* Инициализация битовой маски, на которую указывает fds *)
Procedure FD_ZERO(var fds:fdSet);
(* Установка бита fd в маске, на которую указывает fds *)
Procedure FD_Set(fd:longint;var fds:fdSet);
(* Установлен ли бит fd в маске, на которую указывает fds? *)
Function FD_IsSet(fd:longint;var fds:fdSet):boolean;
(* Сбросить бит fd в маске, на которую указывает fds *)
Procedure FD_Clr(fd:longint;var fds:fdSet);
Следующий пример демонстрирует, как отслеживать состояние двух открытых дескрипторов файлов:
uses linux;
.
.
.
var
fd1, fd2:longint;
readset:fdset;
fd1 := fdopen('file1', Open_RDONLY);
fd2 := fdopen('file2', Open_RDONLY);
FD_ZERO(readset);
FD_SET(fd1, readset);
FD_SET(fd2, readset);
case select(5, @readset, nil, nil, nil) of
(* Обработка ввода *)
end;
Пример очевиден, если вспомнить, что переменные fd1 и fd2 представляют собой небольшие целые числа, которые можно использовать в качестве индексов битовой маски. Обратите внимание на то, что аргументам writefds и errorfds в вызове select присвоено значение nil. Это означает, что представляет интерес только чтение из fd1 и fd2.
Пятый параметр вызова select, timeout, является указателем на следующую структуру timeval:
uses linux;
TimeVal = Record
sec, (* Секунды *)
usec : Longint; (* и микросекунды *)
end;
Если указатель является нулевым, как в этом примере, то вызов select будет заблокирован до тех пор, пока не произойдет интересующее процесс событие. Если в структуре timeout задано нулевое время, то вызов select завершится немедленно (без блокирования). И, наконец, если структура timeout содержит ненулевое значение, то возврат из вызова select произойдет через заданное число секунд или микросекунд, если файловые дескрипторы неактивны.
Возвращаемое вызовом select значение равно -1 в случае ошибки, нулю – после истечения временного интервала или целому числу, равному числу «интересующих» программу дескрипторов файлов. Следует сделать предостережение: при возврате из вызова select он переустанавливает битовые маски, на которые указывают переменные readfds, writefds или errorfds, сбрасывая маску и снова задавая в ней дескрипторы файлов, содержащие искомую информацию. Поэтому необходимо сохранять копию исходных масок.[11]
Приведем более сложный пример, в котором используются три канала, связанные с тремя дочерними процессами. Родительский процесс должен также отслеживать стандартный ввод.
(* Программа server - обслуживает три дочерних процесса *)
uses linux,stdio;
const
MSGSIZE=6;
msg1:array [0..MSGSIZE-1] of char = 'hello';
msg2:array [0..MSGSIZE-1] of char = 'bye!!';
type
tp1=array [0..1] of longint;
tp3=array [0..2] of tp1;
(* Родительский процесс ожидает сигнала в трех каналах *)
procedure parent(p:tp3); (* код родительского процесса *)
var
ch:char;
buf:array [0..MSGSIZE-1] of char;
_set, master:fdset;
i:integer;
begin
(* Закрыть все ненужные дескрипторы, открытые для записи *)
for i:=0 to 2 do
fdclose (p[i][1]);
(* Задать битовые маски для системного вызова select *)
FD_ZERO (master);
FD_SET (0, master);
for i:=0 to 2 do
FD_SET (p[i][0], master);
(* Лимит времени для вызова select не задан, поэтому он
* будет заблокирован, пока не произойдет событие *)
_set := master;
while select (p[2][0] + 1, @_set, nil, nil, nil) > 0 do
begin
(* Нельзя забывать и про стандартный ввод,
* т.е. дескриптор файла fd=0. *)
if FD_ISSET (0, _set) then
begin
write('Из стандартного ввода...');
fdread (0, ch, 1);
writeln(ch);
end;
for i:=0 to 2 do
begin
if FD_ISSET (p[i][0], _set) then
begin
if fdread (p[i][0], buf, MSGSIZE) > 0 then
begin
writeln('Сообщение от потомка', i);
writeln('MSG=', buf);
end;
end;
end;
(* Если все дочерние процессы прекратили работу,
* то сервер вернется в основную программу
*)
if waitpid (-1, nil, WNOHANG) = -1 then
exit;
_set := master;
end;
end;
function child (p:tp1):integer;
var
count:integer;
begin
fdclose (p[0]);
for count:=1 to 2 do
begin
fdwrite (p[1], msg1, MSGSIZE);
(* Пауза в течение случайно выбранного времени *)
sleep (getpid mod 4);
end;
(* Послать последнее сообщение *)
fdwrite (p[1], msg2, MSGSIZE);
halt (0);
end;
var
pip:tp3;
i:integer;
begin
(* Создать три канала связи, и породить три процесса. *)
for i:=0 to 2 do
begin
if not assignpipe (pip[i][0],pip[i][1]) then
fatal ('Ошибка вызова pipe');
case fork of
-1: (* ошибка *)
fatal ('Ошибка вызова fork');
0: (* дочерний процесс *)
child (pip[i]);
end;
end;
parent (pip);
halt (0);
end.
Результат данной программы может быть таким:
Сообщение от потомка 0
MSG=hello
Сообщение от потомка 1
MSG=hello
Сообщение от потомка 2
MSG=hello
d (пользователь нажимает клавишу d, а затем клавишу Return)
Из стандартного ввода d (повторение символа d)
Из стандартного ввода (повторение символа Return)
Сообщение от потомка 0
MSG=hello
Сообщение от потомка 1
MSG=hello
Сообщение от потомка 2
MSG=hello
Сообщение от потомка 0
MSG=bye
Сообщение от потомка 1
MSG=bye
Сообщение от потомка 2
MSG=bye
Обратите внимание, что в этом примере пользователь нажимает клавишу d, а затем символ перевода строки (Enter или Return), и это отслеживается в стандартном вводе в вызове select.
Функция SelectText является модификацией Select, предназначенной для работы с текстовыми файлами:
uses linux;
Function SelectText(var T:Text; TimeOut:PTime):Longint;
SelectText выполняет системный вызов Select для файлов типа Text. Время ожидания может быть указано в параметре TimeOut. Вызов SelectText самостоятельно определяет необходимость проверки чтения и записи в зависимости от того, в каком режиме был открыт файл. При Reset выполняется проверка на чтение, при Rewrite и Append – на запись.
Пример использования SelectText:
Uses linux;
Var tv : TimeVal;
begin
Writeln ('Press the <ENTER> to continue the program.');
{ Wait until File descriptor 0 (=Input) changes }
SelectText (Input,nil);
{ Get rid of <ENTER> in buffer }
readln;
Writeln ('Press <ENTER> key in less than 2 seconds...');
tv.sec:=2;
tv.usec:=0;
if SelectText (Input,@tv)>0 then
Writeln ('Thank you !')
else
Writeln ('Too late !');
end.
Связать SelectText и Select можно с помощью функции GetFS, позволяющей из любой файловой переменной получить дескриптор файла.
uses linux;
Function GetFS(Var F:Any File Type):Longint;
Например:
Uses linux;
begin
Writeln (' File descriptor of input ',getfs(input));
Writeln ('File descriptor of output ',getfs(output));
Writeln ('File descriptor of stderr ',getfs(stderr));
end.
Пример использования SelectText:
Uses linux;
Var tv : TimeVal;
begin
Writeln ('Press the <ENTER> to continue the program.');
{ Wait until File descriptor 0 (=Input) changes }
SelectText (Input,nil);
{ Get rid of <ENTER> in buffer }
readln;
Writeln ('Press <ENTER> key in less than 2 seconds...');
tv.sec:=2;
tv.usec:=0;
if SelectText (Input,@tv)>0 then
Writeln ('Thank you !')
else
Writeln ('Too late !');
end.