Скачиваний:
56
Добавлен:
08.01.2014
Размер:
2.6 Mб
Скачать

7.1.6. Использование системного вызова select для работы с несколькими каналами

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

Представьте ситуацию, когда родительский процесс выступает в качестве серверного процесса и может иметь произвольное число связанных с ним клиентских (дочерних) процессов, как показано на рис. 7.4.

В конечном итоге получится однонаправленный поток данных от дочернего процесса к родительскому. Эта упрощенная ситуация показана на рис. 7.3.

Дочерний процесс 1

Родительский процесс

fdwrite()

fdw1 → →

→ → fdr1

fdread()

Дочерний процесс 2

fdwrite()

fdw2 → →

→ → fdr2

fdread()

Рис. 7.4. Клиент/сервер с использованием каналов

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

Это можно сделать при помощи системного вызова select (существует также аналогичный вызов poll). Системный вызов select используется не только для каналов, но и для обычных файлов, терминальных устройств, именованных каналов (которые будут рассмотрены в разделе 7.2) и сокетов (им посвящена глава 10). Системный вызов select показывает, какие дескрипторы файлов из заданных наборов готовы для чтения, записи или ожидают обработки ошибок. Иногда серверный процесс не должен совсем прекращать работу, даже если не происходит никаких co6ытий, поэтому в вызове select также можно задать предельное время ожидания.

Описание

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, сбрасывая маску и снова задавая в ней дескрипторы файлов, содержащие искомую информацию. Поэтому необходимо сохранять копию исходных масок.1

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

(* Программа 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, предназначенной для работы с текстовыми файлами:

Соседние файлы в папке Полищук, Семериков. Системное программирование в UNIX средствами Free Pascal