Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

СПО_1 / СПО / Sozdanie.setevyh.prilojenii.v.srede.Linux

.pdf
Скачиваний:
80
Добавлен:
11.04.2015
Размер:
2.94 Mб
Скачать

Асинхронный и сигнальный ввод вывод

Алгоритмы асинхронного ввода вывода, представленные в этой главе, в действительности рабо тают на основе сигналов, которые посылаются, когда буферы готовы для чтения или записи. При истинно асинхронном вводе выводе, который определен в стандарте POSIX.1, никогда не возни кает блокирование. Например, вызов функции read() немедленно завершается. Буферы счита ются незаполненными до тех пор, пока не завершится операция чтения и программа не получит сигнал. Linux (как и многие другие операционные системы) не соответствует стандарту POSIX.1, касающемуся асинхронного ввода вывода для сокетов. Чтобы не грешить против истины, лучше употреблять термин "сигнальный ввод вывод".

Сравнение различных методик ввода$ вывода

Чтобы увеличить производительность программы и повысить ее способность к реагированию на запросы, следует творчески подойти к взаимодействию с под системой ввода вывода. Программа может получать данные из внешних источни ков. Четкий контроль над тем, когда и как следует переходить в режим блокиро вания, позволит программе оперативнее обрабатывать запросы пользователя. В Linux существуют четыре методики ведения операций ввода вывода. У каждой из них есть свои преимущества и недостатки. На рис. 8.1 изображена схема выпол нения задания, читающего пакет данных четырьмя различными способами. Про цесс начинается, когда в буфере еще нет данных.

Блокирование

Тайм аут

Опрос

Асинхронный режим

Рис. 8.1. В каждой из методик ввода!вывода у задания разные периоды простоя

Глава 8. Механизмы ввода вывода

111

Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com

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

Следует отметить, что в случае отправки данных процесс будет выглядеть по другому. Основную роль здесь играют буферы, заполнения которых вынуждена ожидать программа. В любом случае в режиме блокирования программа останав ливается, ожидая, пока ядро полностью отправит сообщение. (Это не совсем вер но, так как в действительности ядро ожидает момента, когда можно будет завер шить передачу сообщения, после чего сразу же посылает заданию сигнал "пробуждения", но в целях упрощения программирования можно предполагать, что на момент получения сигнала данные уже отправлены.) В режиме опроса и в асинхронном режиме функция send() помещает сообщение в буфер и немедленно завершается. В схеме, изображенной на рис. 8.2, предполагается, что данные слишком велики и не помещаются целиком в буфере.

Блокирование

Тайм аут

Опрос

Асинхронный режим

Рис. 8.2. В процессе записи задание выполняется быстрее благодаря на! личию внутренних буферов

В следующих разделах рассматриваются особенности каждого из трех небло кируемых режимов.

Опрос каналов ввода$вывода

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

172

Часть II. Создание серверных приложений

www.books-shop.com

интересует, как и когда сообщение покинет компьютер. С ее точки зрения под система ввода вывода отправляет сообщение целиком и за один прием. Но часто это не так. Большинство сообщений разбивается на фрагменты из за ограничен ной пропускной способности сети. Точно так же при приеме сообщения оно мо жет поступать по частям, что не всегда желательно. В обоих случаях программа останавливается и дожидается заполнения буфера.

Методика опроса позволяет избежать блокирования при осуществлении ввода вывода. Ее суть заключается в периодической проверке готовности канала. В пер вую очередь сокет должен быть переведен в неблокируемый режим. Затем необ ходимо проверить значение, возвращаемое системным вызовом send() или recv(). Если возникает ошибка EWOULDBLK или EAGAIN, следует повторить попытку позднее. Чаще всего подобные действия выполняются в цикле, например таком:

/*** Общий алгоритм опроса ***/

.*************************************************/

while ( /*** передача данных ***/ )

{

if ( /*** проверка готовности канала ***/ ) /*** обработка данных ***/

/*** другие вычисления ***/

Сутью приведенного фрагмента является наличие секции вычислений. Напом ним, что в Linux применяется алгоритм приоритетного кругового обслуживания заданий. Если программа больше занята обращением к процессору, чем к кана лам ввода вывода, ее эффективность возрастает. Но если не задать секцию вы числений, возникнет поглощающий цикл, в котором приоритет программы увели чивается, хотя она, по сути, ничего не делает. Поглощающие циклы представля ют собой серьезную проблему с точки зрения планирования заданий. Программа может занимать ресурсы процессора, выполняя слишком малый объем работы.

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

Чтение данных по методике опроса

Методика опроса позволяет собирать блоки поступающих данных и обрабаты вать их по частям, пока передаются остальные данные. Поскольку процессы чте ния и обработки обычно связаны между собой, программа должна перевести со кет в режим неблокируемого ввода вывода. Это делается с помощью функции fcntl():

linclude <fcntl.h>

in fcntl(int fd, int command, int option);

Подобно системному вызову read(), эта функция принимает в первом пара метре либо дескриптор файла, либо дескриптор сокета. Параметр command должен

Глава 8. Механизмы ввода вывода

173

www.books-shop.com

быть равен F_SETFL, а параметр option — O_NONBLOCK. (У этих параметров очень много возможных значений, которые перечислены в приложении В, "API функции ядра") Формат использования функции таков:

if ( fcntl(sd, F_SETFL, 0_NONBLOCK) != О ) perror("Fcntl — could not set nonblocking");

Для примера предположим, что создается модуль музыкального проигрывате ля. Вместо того чтобы ждать, пока весь аудиоклип будет загружен из сети Internet, можно воспроизводить поступающие данные. Естественно, следует так же учитывать, что данные могут приходить в сжатом виде. Общий алгоритм про граммы представлен в листинге 8.1.

Листинг 8.1. Пример чтения данных по методике опроса

/***************************************************************/

/***

Пример алгоритма опроса: чтение аудиопотока,

***/

/***

обработка данных и их воспроизведение

***/

/***************************************************************/

if ( fcntl(sd, F SETFL, 0 NONBLOCK) != О ) perror("FcntI could" not set nonblocking");

done = 0 ; while ( !done )

{int bytes;

Queue ProcessingQueue;

Queue OutputQueue;

/* —

Получаем данные из буфера и помещаем их

— */

/* —

в

очередь обработки — */

 

 

if (

(bytes = recv(sd, buffer,

sizeof (buffer),

0)) > 0 )

 

QueueData(ProcessingQueue,OutputQueue);

 

 

/* —

Преобразуем определенное число байтов из

— */

/* —

очереди обработки в аудиоформат (обычно

 

— */

/* —

это

выполняется быстрее,

чем прием данных

— */

/* —

с помощью функции recv())

 

— */

ConvertAudio( ProcessingQueue ,

OutputQueue ) ;

 

 

if ( /*** в выходной очереди накоплено достаточно данных ***/ ) PlayAudio( OutputQueue) ;

/* —

Если входной поток

закончился — */

/* —

и выходная очередь

пуста — */

if ( bytes == 0 && /*— выходная очередь пуста —*/ )

done = 1;

}

Программа запрашивает данные, помещает их в очередь и обрабатывает. Но есть одна проблема: если обе очереди пусты, цикл while станет поглощающим. Чтобы решить эту проблему, можно добавить в программу небольшой цикл за держки, так как она должна работать в реальном времени.

174

Часть П. Создание серверных приложений

www.books-shop.com

Запись данных по методике опроса

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

Алгоритм записи данных по методике опроса напоминает рассмотренный вы ше алгоритм чтения, так как подготовка данных, их обработка и отправка осуще ствляются в одном цикле. Подготовительные действия здесь такие же: вызывается функция fcntl() с аналогичными аргументами.

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

Для примера рассмотрим программу, представленную в листинге 8.2. Она посы лает фотографии, получаемые от цифровой камеры, сразу нескольким клиентам.

Листинг 8.2. Пример записи данных по методике опроса

/**************************************************************/

/***

Пример алгоритма опроса: отправка изображения

***/

/***

нескольким клиентам

***/

/***************************************************************/

int pos[MAXCLIENTS]; bzero(pos, sizeof(pos));

for ( i = 0; i < ClientCount; i++ )

if ( fcntl(client[i], F_SETFL, 0_NONBLOCK) != 0 ) perrorf'Fcntl — could not set nonblocking");

•••

done = 0;

 

 

 

 

 

 

 

/*—

повторяем

до тех пор, пока все

клиенты

—*/

 

/*— не получат сообщение целиком —*/

 

 

while( !done )

 

 

 

 

 

 

 

{

int bytes;

 

 

 

 

 

 

 

 

done = 0;

 

 

 

 

 

 

 

 

/*— для всех клиентов —*/

 

 

 

 

 

for { i = 0; i < ClientCount; i++ )

 

 

 

 

/*— если

имеются

неотправленные данные... —*/

 

 

if ( pos[i] < size )

 

 

 

 

 

{

 

 

 

 

 

 

 

 

/*—

отправляем сообщение,

отслеживая, —*/

 

 

/*— сколько байтов послано —*/

 

 

 

bytes = send(client[i], buffer+pos[i], size pos[i],

0);

 

if ( bytes > 0 )

 

 

 

 

 

{

 

 

 

 

 

 

 

 

pos[i] += bytes;

благополучно отправлено

—*/

 

/*

если

сообщение

 

/*—

всем

клиентам,

завершаем

работу —*/

 

Глава 8. Механизмы ввода вывода

 

175

www.books-shop.com

if ( pos[i] < size ) done = 0;

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

Еще одна возможная проблема заключается в том, что программа может полу чить следующее изображение от камеры, в то время как отправка предыдущей фотографии еще не была завершена. Пути решения этой проблемы могут быть такими:

присоединить новое сообщение к старому;

отменить последнее сообщение и начать передачу нового;

изменить частоту работы камеры.

Влюбом случае выбор зависит от того, насколько критичными являются дан ные, сколько данных может быть потеряно, насколько неустойчивым является соединение и т.д. Общие соображения по этому поводу приводились в главе 4, "Передача сообщений между одноранговыми компьютерами".

Установление соединений по методике опроса

Одним из редко применяемых алгоритмов опроса является прием запросов на подключение по разным портам. Как правило, серверная программа работает только с одним портом. Но ничто не мешает открыть в программе столько пор тов, сколько необходимо. Рассмотрим пример:

/*** Пример алгоритма опроса: проверка поступления запросов

***/

/*** на подключение по нескольким портам и создание для

***/

/*** каждого запроса нового задания

***/

•••

 

 

/*—

Установка неблокируемого

режима для каждого сокета —*/

for ( i = 0; i < numports; i++ )

 

 

if ( fcntl(client[i], F_SETFL, O_NONBLOCK) != 0 )

 

 

perror("Fcntl can't set nonblocking on port #%d", i);

for

(;;) /* повторяем бесконечно */

 

{

int client;

 

 

 

for ( i = 0; i < numports; i++ )

 

 

if ( (client = accept(sd[i], &addr, &size)) > 0 )

 

 

SpawnServer(sd[i],

i);

 

/*** служебные действия ***/

176

Часть II. Создание серверных приложений

www.books-shop.com

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

Асинхронный ввод вывод

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

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

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

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

Программа переходит в режим сигнального ввода вывода, уведомляя ядро о том, что она готова обрабатывать сигнал SIGIO. Когда поступает сигнал, его обра ботчик устанавливает специальный флаг, понятный программе. В это время в те ле программы выполняется цикл, в котором периодически проверяется состояние этого флага. Обработчик сигнала может сам управлять вводом выводом или пре доставлять эту задачу программе.

Чем данный режим отличается от методики опроса? В обоих случаях периоди чески выполняется проверка готовности канала ввода вывода. В режиме опроса системные вызовы не блокируются, то же самое имеет место и в асинхронном режиме.

Основное отличие заключается в том, сколько времени программа тратит на выполнение системных вызовов. В асинхронном режиме время работы функций send() и recv() очень невелико, так как ядро само уведомляет программу об их завершении. С другой стороны, в режиме опроса программа вынуждена периоди чески вызывать их.

В листинге 8.3 представлен общий алгоритм асинхронного ввода вывода.

Глава 8. Механизмы ввода вывода

177

www.books-shop.com

Листинг 8.3. Алгоритм асинхронного ввода вывода

/***************************************************************/

/***

Общий алгоритм асинхронного, или сигнального,

***/

/***

ввода вывода

***/

/***************************************************************/ int ready=0;

void sig_io(int sig)

{

/*** функция recv(): получаем все данные из буфера ***/

/*** функция send(): отправляем все обработанные данные ***/

ready =1; /* сообщаем программе о завершении транзакции */

}

for (;;)

{

if ( ready > 0 )

{

/*** Временно блокируем сигнал SIGIO ***/

ready = 0;

/*** функция recv(): копируем данные в буферы для обработки ***/

/*** функция send(): заполняем выходной буфер из очереди обработанных данных ***/

/*** Разблокируем сигнал SIGIO ***/

}

/*** Обработка поступающих данных ***/

/*** ИЛИ ***/

/*** Подготовка новых данных для отправки ***/

}

Блокирование сигнала SIGIO может показаться несколько необычным. Оно не обходимо из за того, что обработчик сигнала и основная программа имеют доступ к одной и той же переменной, поэтому фактически в данном месте программы при сутствует критическая секция (см. главу 7, "Распределение нагрузки: многозадач ность"). Отключение обработчика подобно применению исключающего семафора.

Сообщения, помещенныевочередь

Для обслуживания всех отложенных сообщений может потребоваться несколько раз вызвать в обработчике функцию send( ) или recv(). Ядро может послать программе несколько сигналов одновременно, но сама программа в конкретный момент времени получает сигнал только одного типа. Если два сигнала поступают с очень маленьким интервалом, программа распознает их как один сигнал.

Программа заявляет о том, что она готова принимать сигналы SIGIO, вызывая функцию fcntl(). Эта функция не только переводит сокет в асинхронный режим, но также просит систему направлять сигналы заданному процессу. Например, в следующем фрагменте включается обработка сигналов SIGIO, и система получает указание направлять сигналы текущему заданию.

178

Часть П. Создание серверных приложений

www.books-shop.com

/*** Запуск обработчика сигналов SIGIO ***/

/*****************************************************/

if ( fcntl(sd, F_SETFL, O_ASYNC | O_NONBLOCK) < 0 ) PANIC("Can't make socket asynch & nonblocking");

if ( fcntl(sd, F_SETOWN, getpid()) < 0 ) PANIC("Can't own SIGIO");

Когда программа запрашивает получение сигналов ввода вывода, это касается всех сигналов данной группы, а не только SIGIO. Другим возможным сигналом является SIGURG, посылаемый при передаче внеполосных данных.(Дополнительная информация приводится в главе 9, "Повышение производительности".)

Чтение данных по запросу

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

Лучше всего применять чтение по запросу в тех программах, которые выпол няют много вычислений и ожидают поступления данных из одного источника. (Помните: можно открыть несколько каналов ввода вывода, но придется вручную проверять, для какого из каналов был послан сигнал.) В качестве примера рас смотрим процедуру обработки VRML документа (Virtual Reality Modeling Language — язык моделирования виртуальной реальности), представленную в листинге 8.4.

Листинг8.4.Алгоритмасинхронногочтения

/***************************************************************/

/*** Пример асинхронного чтения VRML документа: обработка

***/

/*** порции данных во время ожидания следующей порции

***/

int

ready=0, bytes;

 

void sig_io(int sig)

 

{

/*— чтение отложенных сообщений —*/

 

 

 

 

bytes = recv(server, buffer, sizeof(buffer), 0);

 

 

if ( bytes < 0 )

 

 

perror("SIGIO");

 

 

}ready =1;

/* сообщаем программе о завершении транзакции

*/

/*—

Разрешаем

асинхронный, неблокируемый ввод вывод —*/

 

if ( fcntl(sd, F_SETFL, 0_ASYNC | O_NTONBLOCK) < 0 )

 

 

PANIC("Can't make socket asynch & nonblocking");

 

/*— Заявляем о готовности обрабатывать —*/

 

Глава 8. Механизмы ввода вывода

179

www.books-shop.com

/*— сигналы SIGIO и SIGURG —*/

if ( fcntl(sd, F_SETOWN, getpid()) < 0 ) PANIC("Can't own SIGIO");

while ( Idone )

if ( ready > 0 )

/*** Временно блокируем сигнал SIGIO ***/

ready = 0;

FillQueue(Queue, buffer, bytes); /*** Разблокируем сигнал SIGIO ***/

/*** Обработка поступающих данных в модуле растрирования ***/

/***

в течение короткого промежутка времени или до тех

***/

/***

пор, пока переменная ready не изменится

***/

}

Избегайте выполнения большого количества действий в обработчике сигналов. В нашем случае обработчик мог бы просто уведомить программу о том, что она должна загрузить данные, но поскольку менеджер очереди может выполнять свои собственные операции ввода вывода, функции recv() и FillQueue() следует раз нести между собой.

Когдабуферсчитаетсяпустым?

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

личить с помощью функции setsockopt{) (описана в

главе 9, "Повышение производительно

сти").

Но похоже,

что в Linux данная

возможность не поддерживается.

Асинхронная запись данных

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

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

Листинг 8.5. Алгоритм асинхронной записи

/***

Пример асинхронной записи: генерирование ответа

***/

/***

в процессе обработки запроса

 

***/

180

Часть II.

Создание серверных приложений

www.books-shop.com

Соседние файлы в папке СПО
  • #
    11.04.201527.19 Mб69Cpp4Unix.pdf
  • #
    11.04.201516.44 Mб52IP Arhitektura, protokoly, realizatsiya (vklyuchaya IP versii s IP Security).djvu
  • #
  • #
    11.04.201510.72 Mб51Стивенс. UNIX. Разработка сетевых приложений.djvu