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

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

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

bzero(&act, sizeof(act));

 

 

act.sa handler = sig_handler;

 

 

sigaction(SIGURG, &act, 0);

/* регистрируем сигнал SIGURG */

/***

устанавливаем соединение

***/

 

/*—

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

SIGURG —*/

if ( fcntl(clientfd, F_SETOWN,

getpid())

!= 0 )

 

perror("Can't claim SIGURG and SIGIO");

/***

другие действия ***/

 

 

}

В этом фрагменте сервер отвечает на запросы, посылаемые клиентом. Код клиента будет немного другим:

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

/***

Обмен срочными сообщениями между клиентом и сервером

***/

/***

(клиент посылает сигналы).

***/

/***

(Взято из файла heartbeat client.с на Web узле.)

***/

int serverfd, got_reply=l; void sig_handler(int signum)

{

if ( signum == SIGURG )

 

{ char c;

 

recv(serverfd, &c, sizeof(c));

 

got_reply = ( с == 'Y' )

/* Получен ответ */

}

 

else if (signum == SIGALARM)

 

if ( got_reply )

 

{

 

 

/* Ты жив?

*/

 

send(serverfd, "?", 1, MSG_OOB);

 

alarm(DELAY);

/*

Небольшая пауза

*/

 

got reply = 0;

 

 

 

 

}

 

 

 

 

 

else

 

 

 

 

}

fprintf(stderr,

"Lost connection to

server!");

 

 

 

 

 

 

int main()

 

 

 

 

{

struct sigaction act;

 

 

 

 

bzero(&act, sizeof(act));

 

 

 

act.sa_handler = sig_handler;

 

 

 

sigaction(SIGURG,

&act,

0);

 

 

 

sigaction(SIGALRM,

&act,

0);

 

 

 

/*** устанавливаем соединение ***/

 

 

 

/*— запуск обработчика

сигналов SIGIO и SIGURG —*/

 

 

if ( fcntl(serverfd, F_SETOWN, getpid()) != 0 )

 

 

perrorj"Can't claim SIGURG and SIGIO");

 

 

 

alarm(DELAY);

 

 

 

 

}

/*** другие действия ***/

 

 

 

 

 

 

 

Глава 9. Повышение производительности

211

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

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

Резюме

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

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

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

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

212

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

www.books-shop.com

Создание устойчивых

Глава

сокетов

10

 

В этой главе...

 

Методы преобразования данных

220

Проверка возвращаемых значений

221

Обработка сигналов

223

Управление ресурсами

227

Критические серверы

230

Согласованная работа клиента и сервера

234

Отказ от обслуживания

235

Резюме: несокрушимые серверы

236

www.books-shop.com

Итак, наша задача — создание клиентских и серверных приложений коммер ческого уровня. Это достойная цель, даже если программа будет распространяться бесплатно вместе с исходными текстами на условиях открытой лицензии. Ведь никому не хочется, чтобы его критиковали за ошибки программирования. Так как же сделать хорошую программу безупречной? Хороший вопрос!

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

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

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

Методы преобразования данных

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

Как описывалось в главе 2, "Основы TCP/IP", в сети применяется обратный порядок следования байтов. Это не имеет значения, если работать за компьюте ром Alpha или 68040, где по умолчанию используется данная кодировка. В таких системах функции заменяются "заглушками", которые не выполняют никаких действий. Но если вы читаете эту книгу, то, скорее всего, ваша программа будет распространяться в среде Linux. В этом случае функции преобразования обеспе чивают правильное представление информационных структур.

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

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

214

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

www.books-shop.com

если функция завершается без ошибок, она должна возвращать значение 0;

если в процессе выполнения функции произошла ошибка, она должна возвращать отрицательное значение и записывать код ошибки в библио течную переменную errno;

пользуйтесь стандартными кодами ошибок;

передавайте структуры по ссылкам;

старайтесь определять низкоуровневые функции и строить на их основе высокоуровневые;

определяйте все параметры указатели, предназначенные только для чте ния, со спецификатором const;

лучше создавать структуры, чем typedef определения (макротипы);

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

ведите журнальный файл для регистрации событий, ошибок и нестан дартных ситуаций.

Это лишь некоторые из правил. Наилучшим решением будет просмотреть текст стандартной функции и взять его за основу.

В целом необходимо отметить, что программу, написанную стандартным и понятным способом, легче использовать, модифицировать и улучшать. Подумай те: сам Линус Торвальдс объявил о том, что не собирается владеть правами на яд ро Linux всю свою жизнь. Применение стандартных функций и методик делает ядро долговечным.

Проверка возвращаемых значений

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

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

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

connect(). Нельзя продолжить работу, если соединение не установлено. Сообщения об ошибках могут иметь вид "host not found" (узел не най ден) или "host unreachable" (узел недоступен).

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

EINTR (вызов прерван сигналом). Это не критическая ошибка. Следует

Глава10. Создание устойчивых сокетов

215

www.books-shop.com

либо вызвать функцию sigaction() с флагом SA_RESTART, либо проигно рировать ошибку и повторно вызвать функцию.

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

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

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

pthread_create(). Подобно функции fork(), необходимо убедиться в том, что дочернее задание было успешно создано.

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

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

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

socket(). Ошибка в данной функции возникает только тогда, когда де скриптор сокета нельзя получить (нет привилегий или ядро не поддер живает эту функцию), указан неправильный параметр или таблица деск рипторов переполнена. В любом случае функции bind(), connect() и др. вернут ошибку вида "not a socket" (дескриптор не относится к сокету).

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

close() или shutdown(). Если дескриптор файла неправильный, файл не был открыт. Так или иначе, после вызова любой из этих функций мож но считать файл закрытым и продолжать работу.

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

216

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

www.books-shop.com

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

Можно также перехватывать ошибки, возникающие не в системных или биб лиотечных функциях. Они связаны с динамичной природой сетей, в которых клиенты и серверы могут периодически "уходить в себя". Сетевая подсистема от слеживает некоторые ошибки в протоколах TCP/IP, постоянно проверяя готов ность канала к двунаправленному обмену сообщениями.

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

int error;

socklen_t size = sizeof(error);

if ( getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &size) == 0 )

if ( error != 0 )

fprintf(stderr, "socket error: %s(%d)\n", stderror(error), error);

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

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

Обработка сигналов

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

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

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

но в целом чем меньше команд вызывается в обработчике, тем лучше.

 

Глава 10. Создание устойчивых сонетов

217

www.books-shop.com

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

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

Порядок обработки каждого сигнала зависит от типа сигнала. Из всех 32 х сигналов (информацию о них можно получить в приложении А, "Информационные таблицы", и разделе 7 интерактивной документации) чаще всего обрабатываются такие: SIGPIPE, SIGURG, SIGCHLD, SIGHUP, SIGIO и SIGALRM.

SIGPIPE

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

Ошибка канала возникает, когда узел адресат закрывает соединение до окон чания сеанса передачи данных (см. файлы sigpipe client.c и sigpipe server.c на Web узле). То же самое произойдет, если направить длинный список файлов программе постраничной разбивки, например less, а затем завершить ее работу, не дойдя до конца списка, — будет выдано сообщение "broken pipe" (разрыв ка нала). Избежать получения сигнала SIGPIPE можно, задав опцию MSG_NOSIGNAL в функции send(). Но это не лучший подход.

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

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

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

218

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

www.books-shop.com

SIGURG

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

Если программа получает несколько срочных сообщений подряд и не успевает их все обработать, лишние сообщения будут потеряны. Это объясняется тем, что во входящей очереди сокета зарезервирован только один байт для срочных сооб щений.

SIGCHLD

Сигнал SIGCHLD возникает в многозадачной среде, когда дочернее задание (в частности, процесс) завершается. Ядро сохраняет контекст задания, чтобы роди тельская программа могла проверить, как завершился дочерний процесс. Если программа проигнорирует этот Сигнал, ссылка на контекст останется в таблице процессов в виде процесса зомби (см. главу 7, "Распределение нагрузки: много задачность").

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

Поскольку функция wait() блокирует работу программы (а в обработчике сиг налов это крайне нежелательно), воспользуйтесь вместо нее функцией waitpid():

#include <sys/types.h> #include<sys/wait.h>

int waitpid(int pid, int *status, int options);

Параметр pid может принимать разные значения; если он равен — 1, функция будет вести себя так же, как и обычная функция wait(). Параметр status анало гичен одноименному параметру функции wait() и содержит код завершения по томка. Чтобы предотвратить блокирование функции, следует задать параметр options равным WNOHANG. Когда все процессы зомби, ожидающие обработки, будут обслужены, функция вернет значение 0. Ниже показан типичный пример обра ботчика сигнала SIGCHLD.

/*** Улучшенный пример уничтожения зомби ***/

/*******************************************/ void sig_child(int signum)

i

while ( waitpid( l, 0, WNOHANG) > 0 );

}

Это единственный случай, когда в обработчике сигналов следует применять цикл. Такова особенность работы функции waitpid(). Предположим, обработчик

Глава 10. Создание устойчивых сокетов

219

www.books-shop.com

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

SIGHUP

Что произойдет с дочерним процессом, если завершится родительская про грамма? Он получит сигнал SIGHUP. По умолчанию процесс прекращает свою ра боту. Обычно этого вполне достаточно.

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

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

SIGIO

Ускорить работу сетевой программы можно, поручив обработку событий вво да вывода ядру. Правильно написанная программа получает сигнал SIGIO всякий раз, когда буфер ввода вывода становится доступен для очередной операции (обращение к нему не вызовет блокирования программы). В случае записи дан ных это происходит, когда буфер готов принять хотя бы один байт (пороговое значение устанавливается с помощью параметра SO_SNDLOWAT сокета). В случае чтения данных сигнал поступает, если в буфере есть хотя бы один байт (пороговое значение устанавливается с помощью параметра SO_RCVLOWAT сокета). О реализации обработчика этого сигнала рассказывалось в главе 8, "Механизмы ввода вывода", а о способах повышения производительности подсистемы ввода вывода — в главе 9, "Повышение производительности".

SIGALRM

Подобно сигналу SIGIO, программа получает сигнал SIGALRM, только если явно его запрашивает. Обычно он генерируется функцией alarm(), которая "будит" программу после небольшой паузы. Этот сигнал часто используется демонами, которые проверяют, работает ли та или иная программа.

220

$

Часть 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