UNIX: разработка сетевых приложений
И функция
, и функцияrecvfromмогут использоваться с TCP, хотя обычно в этом нет необходимости.sendto8.3. Эхо-сервер UDP: функция main
Теперь мы переделаем нашу простую модель клиент-сервер из главы 5, используя UDP. Диаграмма вызовов функций в программах наших клиента и сервера UDP показана на рис. 8.1. На рис. 8.2 представлены используемые функции. В листинге 8.1 [1] показана функция сервера
.mainРис. 8.2. Простая модель клиент-сервер, использующая UDP
Листинг 8.1. Эхо-сервер UDP
//udpcliserv/udpserv01.с1 #include "unp.h"23 intmain(int argc, char **argv)4 {5 int sockfd;6 struct sockaddr_in servaddr, cliaddr;7 sockfd = Socket(AF_INET, SOCK_DGRAM, 0);8 bzero(&servaddr, sizeof(servaddr));9 servaddr.sin_family = AF_INET;10 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);11 servaddr.sin_port = htons(SERV_PORT);12 Bind(sockfd, (SA*)&servaddr, sizeof(servaddr));13 dg_echo(sodkfd, (SA*)&cliaddr, sizeof(cliaddr));14 }Создание сокета UDP, связывание с заранее известным портом при помощи функции bindМы создаем сокет UDP, задавая в качестве второго аргумента функции7-12значениеsocket(сокет дейтаграмм в протоколе IPv4). Как и в примере сервера TCP, адрес IPv4 для функции bind задается какSOCK_DGRAM, а заранее известный номер порта сервера — это константаINADDR_ANYиз заголовкаSERV_PORT.unp.hЗатем вызывается функция13для обработки клиентского запроса сервером.dg_echo8.4. Эхо-сервер UDP: функция dg_echo
В листинге 8.2 показана функция
.dg_echoЛистинг 8.2. Функция dg_echo: отражение строк на сокете дейтаграмм
//lib/dg_echo.c1 #include "unp.h"2 void3 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)4 {5 int n;6 socklen_t len;7 char mesg[MAXLINE];8 for (;;) {9 len = clilen;10 n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);11 Sendto(sockfd, mesg, n, 0, pcliaddr, len);12 }13 }Чтение дейтаграммы, отражение отправителюЭта функция является простым циклом, в котором очередная дейтаграмма, приходящая на порт сервера, читается функцией8-12и с помощью функцииrecvfromотправляется обратно.sendtoНесмотря на простоту этой функции, нужно учесть ряд важных деталей. Во- первых, эта функция никогда не завершается. Поскольку UDP — это протокол, не ориентированный на установление соединения, в нем не существует никаких аналогов признака конца файла, используемого в TCP.
Во-вторых, эта функция позволяет создать последовательный сервер, а не параллельный, который мы получали в случае TCP. Поскольку нет вызова функции
, один процесс сервера выполняет обработку всех клиентов. В общем случае большинство серверов TCP являются параллельными, а большинство серверов UDP — последовательными.forkДля сокета на уровне UDP происходит неявная буферизация дейтаграмм в виде очереди. Действительно, у каждого сокета UDP имеется буфер приема, и каждая дейтаграмма, приходящая на этот сокет, помещается в его буфер приема. Когда процесс вызывает функцию
, очередная дейтаграмма из буфера возвращается процессу в порядке FIFO (First In, First Out — первым пришел, первым обслужен). Таким образом, если множество дейтаграмм приходит на сокет до того, как процесс может прочитать данные, уже установленные в очередь для сокета, то приходящие дейтаграммы просто добавляются в буфер приема сокета. Но этот буфер имеет ограниченный размер. Мы обсуждали этот размер и способы его увеличения с помощью параметра сокетаrecvfromв разделе 7.5.SO_RCVBUFНа рис. 8.3 показано обобщение нашей модели TCP клиент-сервер из главы 5, когда два клиента устанавливают соединения с сервером.
Рис. 8.3. Обобщение модели TCP клиент-сервер с двумя клиентами
Здесь имеется два присоединенных сокета, и каждый из присоединенных сокетов на узле сервера имеет свой собственный буфер приема. На рис. 8.4 показан случай, когда два клиента отправляют дейтаграммы серверу UDP.
Рис. 8.4. Обобщение модели UDP клиент-сервер с двумя клиентами
Существует только один процесс сервера, и у него имеется один сокет, на который сервер получает все приходящие дейтаграммы и с которого отправляет все ответы. У этого сокета имеется буфер приема, в который помещаются все приходящие дейтаграммы.
Функция
в листинге 8.1 является зависящей от протокола (она создает сокет семействаmain, а затем выделяет и инициализирует структуру адреса сокета IPv4), но функцияAF_INETот протокола не зависит. Причина, по которой функцияdg_echoне зависит от протокола, заключается в том, что вызывающий процесс (в нашем случае функцияdg_echo) должен разместить в памяти структуру адреса сокета корректного размера, и указатель на эту структуру вместе с ее размером передаются в качестве аргументов функцииmain. Функцияdg_echoникогда не углубляется в эту структуру: она просто передает указатель на нее функциямdg_echoиrecvfrom. Функцияsendtoзаполняет эту структуру, вписывая в нее IP-адрес и номер порта клиента, и поскольку тот же указатель (recvfrom) затем передается функцииpcliaddrв качестве адреса получателя, таким образом дейтаграмма отражается обратно клиенту, отправившему дейтаграмму.sendto

