Книга: Основы программирования в Linux
Дейтаграммы
Дейтаграммы
В этой главе мы сосредоточились на программировании приложений, поддерживающих связь со своими клиентами с помощью TCP-соединений на базе сокетов. Существуют ситуации, в которых затраты на установку и поддержку соединения с помощью сокетов излишни.
Хорошим примером может служить сервис daytime
, использованный ранее в программе getdate.c. Вы создаете сокет, выполняете соединение, читаете единственный ответ и разрываете соединение. Столько операций для простого получения даты!
Сервис daytime
так же доступен с помощью UDP-соединений, применяющих дейтаграммы. Для того чтобы воспользоваться им, просто пошлите сервису одну дейтаграмму и получите в ответ единственную дейтаграмму, содержащую дату и время. Все просто.
Сервисы, предоставляемые по UDP-протоколу, применяются в тех случаях, когда клиенту нужно создать короткий запрос к серверу, и он ожидает единственный короткий ответ. Если стоимость времени процессора достаточно низкая, сервер способен обеспечить такой сервис, обрабатывая запросы клиентов по одному и разрешая операционной системе хранить очередь входящих запросов. Такой подход упрощает программирование сервера.
Поскольку UDP — не дающий гарантий сервис, вы можете столкнуться с потерей вашей дейтаграммы или ответа сервера. Если данные важны для вас, возможно, придется тщательно программировать ваших UDP-клиентов, проверяя ошибки и при необходимости повторяя попытки. На практике в локальных сетях UDP-дейтаграммы очень надежны.
Для доступа к сервису, обеспечиваемому UDP-протоколом, вам следует применять системные вызовы socket
и close
, но вместо использования вызовов read
и write
для сокета вы применяете два системных вызова, характерных для дейтаграмм: sendto
и recvfrom
.
Далее приведена модифицированная версия программы getdate.c, которая получает дату с помощью сервиса UDP-дейтаграмм. Изменения по сравнению с предыдущей версией выделены цветом.
/* Начните с обычных include и объявлений. */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
char *host;
int sockfd;
int len, result;
struct sockaddr_in address;
struct hostent *hostinfo;
struct servent *servinfo;
char buffer[128];
if (argc == 1) host = "localhost";
else host = argv[1];
/* Ищет адрес хоста и сообщает об ошибке, если не находит. */
hostinfo = gethostbyname(host);
if (!hostinfo) {
fprintf(stderr, "no host: %sn", host);
exit(1);
}
/* Проверяет наличие на компьютере сервиса daytime. */
servinfo = getservbyname("daytime", "udp");
if (!servinfo) {
fprintf(stderr, "no daytime servicen");
exit(1);
}
printf("daytime port is %dn", ntohs(servinfo->s_port));
/* Создает UDP-сокет. */
sockfd = socket(AF_INEТ, SOCK_DGRAM, 0);
/* Формирует адрес для использования в вызовах sendto/recvfrom... */
address.sin_family = AF_INET;
address.sin_port = servinfo->s_port;
address.sin_addr = *(struct in_addr*)*hostinfo->h_addr_list;
len = sizeof(address);
result = sendto(sockfd, buffer, 1, 0, (struct sockaddr *)&address, len);
result = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr *)&address, &len);
buffer [result] = '';
printf("read %d bytes: %s", result, buffer);
close(sockfd);
exit(0);
}
Как видите, необходимы лишь незначительные изменения. Как и раньше, вы ищете сервис daytime
с помощью вызова getservbyname
, но задаете дейтаграммный сервис, запрашивая UDP-протокол. Дейтаграммный сокет создается с помощью вызова socket
с параметром SOCK_DGRAM
. Адрес назначения задается, как и раньше, но теперь вместо чтения из сокета вы должны послать дейтаграмму.
Поскольку вы не устанавливаете явное соединение с сервисами на базе UDP, у вас должен быть способ оповещения сервера о том, что вы хотите получить ответ. В данном случае вы посылаете дейтаграмму (в нашем примере вы отправляете один байт из буфера, в который вы хотите получить ответ) сервису и он посылает в ответ дату и время.
Системный вызов sendto
отправляет дейтаграмму из буфера на сокет, используя адрес сокета и длину адреса. У этого вызова фактически следующий прототип:
int sendto(int sockfd, void *buffer, size_t len, int flags,
struct sockaddr *to, socklen_t tolen);
В случае обычного применения параметр flags
можно оставлять нулевым.
Системный вызов recvfrom ожидает дейтаграмму в соединении сокета с заданным адресом и помещает ее в буфер. У этого вызова следующий прототип:
int recvfrom(int sockfd, void *buffer, size_t len, int flags,
struct sockaddr *from, socklen_t *fromlen);
И снова в случае обычного применения параметр flags
можно оставлять нулевым.
Для упрощения примера мы пропустили обработку ошибок. Оба вызова, sendto
и recvfrom
, в случае возникновения ошибки вернут -1 и присвоят переменной errno
соответствующее значение. Возможные ошибки перечислены в табл. 15.6.
Таблица 15.6
Значение errno |
Описание |
---|---|
EBADF |
Был передан неверный файловый дескриптор |
EINTR |
Появился сигнал |
Если сокет не был определен как неблокирующийся с помощью вызова fcntl
(как вы видели ранее для TCP-соединений), вызов recvfrom
будет заблокирован на неопределенное время. Но сокет можно использовать с помощью вызова select
и времени ожидания, позволяющих определить, поступили ли данные, так же, как в случае серверов с устанавливаемыми соединениями. В противном случае можно применить сигнал тревоги для прерывания операции получения данных (см. главу 11).
- Дейтаграммные соединения и дейтаграммы
- Пример: вывод IP-адреса получателя и флага обрезки дейтаграммы
- 22.3. Обрезанные дейтаграммы
- 8.7. Потерянные дейтаграммы
- Параметры сокета SO_REUSEADDR и SO_REUSEPORT
- 20.2. Широковещательные адреса
- Глава 15 Сокеты
- 20.3. Направленная и широковещательная передачи
- ГЛАВА 12 Сетевое программирование с помощью сокетов Windows
- Использование дейтаграмм для удаленного вызова процедур
- Задание адреса следующего транзитного узла
- 25.3. Эхо-сервер UDP с использованием сигнала SIGIO