4.6. Функция accept
Функция accept вызывается сервером TCP для возвращения следующего установленного соединения из начала очереди полностью установленных соединений (см. рис. 4.2). Если очередь полностью установленных соединений пуста, процесс переходит в состояние ожидания (по умолчанию предполагается блокируемый сокет).
#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 используются для возвращения адреса протокола подключившегося процесса (клиента). Аргумент addrlen — это аргумент типа «значение-результат» (см. раздел 3.3). Перед вызовом мы присваиваем целому числу, на которое указывает *addrlen, размер структуры адреса сокета, на которую указывает аргумент cliaddr, и по завершении функции это целое число содержит действительное число байтов, помещенных ядром в структуру адреса сокета.
Если выполнение функции accept прошло успешно, она возвращает новый дескриптор, автоматически созданный ядром. Этот дескриптор используется для обращения к соединению TCP с конкретным клиентом. При описании функции accept мы называем ее первый аргумент прослушиваемым сокетом (listening socket) (дескриптор, созданный функцией socket и затем используемый в качестве аргумента для функций bind и listen), а значение, возвращаемое этой функцией, мы называем присоединенным сокетом (connected socket). Сервер обычно создает только один прослушиваемый сокет, который существует в течение всего времени жизни сервера. Затем ядро создает по одному присоединенному сокету для каждого клиентского соединения, принятого с помощью функции accept (для которого завершено трехэтапное рукопожатие TCP). Когда сервер заканчивает предоставление сервиса данному клиенту, сокет закрывается.
Эта функция возвращает до трех значений: целое число, которое является либо дескриптором сокета, либо кодом ошибки, а также адрес протокола клиентского процесса (через указатель cliaddr) и размер адреса (через указатель addrlen). Если нам не нужно, чтобы был возвращен адрес протокола клиента, следует сделать указатели cliaddr и addrlen пустыми указателями.
В листинге 1.5 показаны эти моменты. Присоединенный сокет закрывается при каждом прохождении цикла, но прослушиваемый сокет остается открытым в течение времени жизни сервера. Мы также видим, что второй и третий аргументы функции accept являются пустыми указателями, поскольку нам не нужно идентифицировать клиент.
Пример: аргументы типа «значение-результат»
В листинге 4.2 представлен измененный код из листинга 1.5 (вывод IP-адреса и номера порта клиента), обрабатывающий аргумент типа «значение-результат» функции accept.
Листинг 4.2. Сервер определения времени и даты, сообщающий IP-адрес и номер порта клиента
//intro/daytimetcpsrv1.c
1 #include "unp.h"
2 #include <time.h>
3 int
4 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. Мы вызываем функцию inet_ntop (см. раздел 3.7) для преобразования 32-битового IP-адреса в структуре адреса сокета в строку ASCII (точечно-десятичную запись), а затем вызываем функцию ntohs (см. раздел 3.4) для преобразования сетевого порядка байтов в 16-битовом номере порта в порядок байтов узла.
ПРИМЕЧАНИЕПри вызове функции sock_ntop вместо inet_ntop наш сервер станет меньше зависеть от протокола, однако он все равно зависит от IPv4. Мы покажем версию этого сервера, не зависящего от протокола, в листинге 11.7.