UNIX: разработка сетевых приложений, стр. 43

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 &quot;unp.h&quot;

 2 #include &lt;time.h&gt;

 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(&amp;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*)&amp;servaddr, sizeof(servaddr));

17  Listen(listenfd, LISTENQ);

18  for (;;) {

19   len = sizeof(cliaddr);

20   connfd = Accept(listenfd, (SA*)&amp;cliaddr, &amp;len);

21   printf(&quot;connection from %s, port %d\n&quot;,

22    Inet_ntop(AF_INET, &amp;cliaddr.sin_addr, buff, sizeof(buff));

23   ntohs(cliaddr.sin_port));

24   ticks = time(NULL);

25   snprintf(buff, sizeof(buff), &quot;% 24s\r\n&quot;, ctime(&amp;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.