UNIX: разработка сетевых приложений
4.6. Функция accept
Функция
вызывается сервером TCP для возвращения следующего установленного соединения из начала очереди полностью установленных соединений (см. рис. 4.2). Если очередь полностью установленных соединений пуста, процесс переходит в состояние ожидания (по умолчанию предполагается блокируемый сокет).accept#include <sys/socket.h>int accept(int <i>sockfd</i>, struct sockaddr *<i>cliaddr</i>, socklen_t *<i>addrlen</i>);<i>Возвращает: неотрицательный дескриптор в случае успешного выполнения функции, -1 в случае ошибки</i>Аргументы
иcliaddrиспользуются для возвращения адреса протокола подключившегося процесса (клиента). Аргументaddrlen— это аргумент типа «значение-результат» (см. раздел 3.3). Перед вызовом мы присваиваем целому числу, на которое указываетaddrlen, размер структуры адреса сокета, на которую указывает аргумент*addrlen, и по завершении функции это целое число содержит действительное число байтов, помещенных ядром в структуру адреса сокета.cliaddrЕсли выполнение функции
прошло успешно, она возвращает новый дескриптор, автоматически созданный ядром. Этот дескриптор используется для обращения к соединению TCP с конкретным клиентом. При описании функцииacceptмы называем ее первый аргумент прослушиваемым сокетом (listening socket) (дескриптор, созданный функциейacceptи затем используемый в качестве аргумента для функцийsocketиbind), а значение, возвращаемое этой функцией, мы называем присоединенным сокетом (connected socket). Сервер обычно создает только один прослушиваемый сокет, который существует в течение всего времени жизни сервера. Затем ядро создает по одному присоединенному сокету для каждого клиентского соединения, принятого с помощью функцииlisten(для которого завершено трехэтапное рукопожатие TCP). Когда сервер заканчивает предоставление сервиса данному клиенту, сокет закрывается.acceptЭта функция возвращает до трех значений: целое число, которое является либо дескриптором сокета, либо кодом ошибки, а также адрес протокола клиентского процесса (через указатель
) и размер адреса (через указательcliaddr). Если нам не нужно, чтобы был возвращен адрес протокола клиента, следует сделать указателиaddrlenиcliaddrпустыми указателями.addrlenВ листинге 1.5 показаны эти моменты. Присоединенный сокет закрывается при каждом прохождении цикла, но прослушиваемый сокет остается открытым в течение времени жизни сервера. Мы также видим, что второй и третий аргументы функции
являются пустыми указателями, поскольку нам не нужно идентифицировать клиент.acceptПример: аргументы типа «значение-результат»
В листинге 4.2 представлен измененный код из листинга 1.5 (вывод IP-адреса и номера порта клиента), обрабатывающий аргумент типа «значение-результат» функции accept.
Листинг 4.2. Сервер определения времени и даты, сообщающий IP-адрес и номер порта клиента
//intro/daytimetcpsrv1.c1 #include "unp.h"2 #include <time.h>3 int4 main(int argc, char **argv)5 {6 int listenfd, connfd;7 socklen_t len;8 struct sockaddr_in servaddr, cliaddr;9 char buff[MAXLINE];10 time_t ticks;11 listenfd = Socket(AF_INET, SOCK_STREAM, 0);12 bzero(&servaddr, sizeof(servaddr));13 servaddr.sin_family = AF_INET;14 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);15 servaddr.sin_port = htons(13); /* сервер времени и даты */16 Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));17 Listen(listenfd, LISTENQ);18 for (;;) {19 len = sizeof(cliaddr);20 connfd = Accept(listenfd, (SA*)&cliaddr, &len);21 printf("connection from %s, port %d\n",22 Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff));23 ntohs(cliaddr.sin_port));24 ticks = time(NULL);25 snprintf(buff, sizeof(buff), "% 24s\r\n", ctime(&ticks));26 Write(connfd, buff, strlen(buff));27 Close(connfd);28 }29 }Новые объявленияМы определяем две новых переменных:7-8, которая будет переменной типа «значение-результат», иlen, которая будет содержать адрес протокола клиента.cliaddrПринятие соединения и вывод адреса клиентаМы инициализируем переменную19-23, присвоив ей значение, равное размеру структуры адреса сокета, и передаем указатель на структуруlenи указатель наcliaddrв качестве второго и третьего аргументов функцииlen. Мы вызываем функциюaccept(см. раздел 3.7) для преобразования 32-битового IP-адреса в структуре адреса сокета в строку ASCII (точечно-десятичную запись), а затем вызываем функциюinet_ntop(см. раздел 3.4) для преобразования сетевого порядка байтов в 16-битовом номере порта в порядок байтов узла.ntohsПРИМЕЧАНИЕПри вызове функции sock_ntop вместо inet_ntop наш сервер станет меньше зависеть от протокола, однако он все равно зависит от IPv4. Мы покажем версию этого сервера, не зависящего от протокола, в листинге 11.7.