Книги, научные публикации Pages:     | 1 | 2 | 3 | 4 | 5 |   ...   | 8 |

Создание сетевых приложений в среде Linux Руководство разработчика Шон Уолтон Москва Х Санкт Петербург Х Киев 2001 ББК 32.973.26 018.2.75 УДК 681.3.07 Издательский дом "Вильяме" По общим вопросам ...

-- [ Страница 3 ] --

Можно также узнать, кто устанавливает соединение с сервером, поскольку в функцию accept() передается информация о клиенте. Аналогичный процесс рас сматривался в главе 4, "Передача сообщений между одноранговыми компьютера ми", когда функция recvfrom() получала не только данные, но и указатель на ад рес отправителя.

#include #include int accept(int sd, sockaddr *addr, int *addr_size);

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

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

Листинг 6.4. Пример функции accept () /****************************************************************/ /*** Пример функции accept(): ожидание и принятие запросов ***/ /*** на подключение от клиентов ***/ /****************************************************************/ int sd;

struct sockaddr_in addr;

/*** Создание сокета, привязка его к порту и перевод в режим прослушивания ***/ for (;

;

) /* цикл повторяется бесконечно */ { int clientsd;

/* новый дескриптор сокета */ int size = sizeof(addr);

/* вычисление размера структуры */ 122 Часть П. Создание серверных приложений www.books-shop.com clientsd = accept(sd, &addr, &size);

/* ожидание подключения */ if { clientsd > 0 ) /* ошибок нет */ { /*** Взаимодействие с клиентом ***/ close(clientsd);

/* очистка и отключение */ } else /* произошла ошибка */ perror("Accept");

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

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

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

Листинг 6.5. Пример функции accept( ) с регистрацией подключений /****************************************************************/ /*** Расширенный пример функции accept(): информация ***/ /*** о каждом новом подключении отображается на экране ***/ /****************************************************************/ /*** (Внутри цикла) ***/ client = accept(sd, saddr, &size);

if ( client > 0 ) { if ( addr.sin_family == AF_INET) printf( "Connection [%s]: %s:%d\n", /* регистрация */ ctime(time(0) ), /* метка времени */ ntoa(addr.sin_addr), ntohs(addr.sin_port));

/* Ч взаимодействие с клиентом Ч */ Если в процессе выполнения функции accept() происходит ошибка, функция возвращает отрицательное значение. В противном случае создается новый деск риптор сокета. Ниже перечислены коды возможных ошибок.

Глава 6. Пример сервера www.books-shop.com Х EBADF. Указан неверный дескриптор сокета.

Х EOPNOTSUPP. При вызове функции accept() сокет должен иметь тип SOCK_STREAM.

Х EAGAIN. Сокет находится в режиме неблокируемого ввода вывода, а оче редь ожидания пуста. Функция accept() блокирует работу программы, если не включен данный режим.

Настало время вернуться к эхо серверу, который возвращает клиенту получен ное сообщение до тех пор, пока не поступит команда bye (листинг 6.6).

Листинг 6.6. Пример эхо сервера /*** Пример эхо сервера: возврат полученного сообщения ***/ /*** до тех пор пока не поступит команда "bye" ***/ /*** (Внутри цикла после функции accept()) ***/ Х Х Х if ( client > 0 ) { char buffer[1024];

int nbytes;

do { nbytes recv(client, buffer, sizeof(buffer), 0);

if ( nbytes > 0 ) /* если получены данные, возвращаем их */ send(client, buffer, nbytes, 0);

} while ( nbytes > 0 && strncmp("bye\r", buffer, 4) 1=0);

close(client);

} Заметьте, что признаком окончания сеанса является строка "bye\r", а не "bye\n". В общем случае это зависит от того, как выполняется обработка входного потока. Из соображений надежности следует проверять оба случая. Попробуйте протестировать данную программу, использовав в качестве клиента утилиту Telnet.

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

124 Часть //. Создание серверных приложений www.books-shop.com Какая программа должна начинать передачу первой?

БОЛЬШИНСТВО серверов первыми начинают сеанс. Но в некоторых системах с высоким уровнем безопасности предполагается, что клиент должен отправить первое сообщение. Сервер может заставить клиента идентифицировать себя (указать не только адрес узла и порт).

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

Нужно ли это клиенту? Не замедлит ли это работу?

Какая программа должна управлять диалогом?

Чаше всего диалогом с сервером управляют клиенты. Клиент подключается к серверу и посылает запросы. Сервер, в свою очередь, обрабатывает запросы и вы дает ответ.

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

Какой уровень сертификации требуется?

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

Процесс сертификации включает передачу имени пользователя и пароля.

Кроме того, может потребоваться наличие цифрового сертификата. В главе 16, "Безопасность сетевых приложений", описывается протокол SSL (Secure Sockets Layer Ч протокол защищенных сокетов), а также рассматриваются вопросы безо пасности.

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

Какой тип данных используется?

БОЛЬШИНСТВО серверов использует кодировку ASCII, а большинство Web страниц представлено в формате текст/HTML. Полезно задать себе вопросы:

"Является ли это наиболее эффективной формой представления данных?" и "Поддерживает ли клиент сжатие данных?" Подумайте, как можно уменьшить задержки на сервере, в сети и в клиентской системе.

Сжатие данных имеет существенные преимущества. Компрессированные ASCII потоки уменьшаются в размере на 50 80%.

Глава 6. Пример сервера www.books-shop.com Как следует обрабатывать двоичные данные?

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

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

Как обнаружить взаимоблокировку?

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

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

Необходима ли синхронизация по таймеру?

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

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

Одно из решений проблемы синхронизации заключается в том, чтобы прово дить все транзакции на сервере (максимально снимая ответственность с клиента).

Когда сервер завершает транзакцию, он сообщает клиенту дату и время ее окон чания.

Как и когда переустанавливать соединение?

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

126 Часть II. Создание серверных приложений www.books-shop.com В TCP/IP существует понятие приоритетного сообщения, с помощью которого можно просигнализировать об отмене. Подробная информация о приоритетных сообщениях и передаче внеполосных данных приводится в главе 9, "Повышение производительности". Но отправка приоритетного сообщения Ч это только пол дела: как сервер, так и клиент должны вернуться к некой начальной точке, что представляет собой серьезную проблему в структурном программировании.

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

Когда завершать работу?

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

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

Более сложный пример: сервер HTTP Эхо сервер представляет собой отличный отправной пункт для создания раз личных видов серверов. Одним из них является HTTP сервер. Полная его реали зация выходит за рамки данной книги, но можно создать уменьшенный вариант сервера, который отвечает на запросы любого броузера. Текст этого примера име ется на Web узле (файл html ls server.c).

Сервер генерирует HTML код динамически, а не загружает его из файла. Это упрощает программу (листинг 6.7).

Листинг 6.7. Пример простого НИР сервера /**************************************************************/ /*** Простой HTTP сервер ***/ /**************************************************************/ while(1) { int client;

int size = sizeof(addr);

client = accept(sd, &addr, &size);

if ( client > 0 ) { char buffer[1024];

/* Сообщение клиенту */ char *reply = "