ИМЯ
accept − принять соединение на сокете
ОБЗОР
#include
<sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
ОПИСАНИЕ
Функция accept используется с сокетами, ориентированными на устанавление соединения (SOCK_STREAM, SOCK_SEQPACKET и SOCK_RDM). Эта функция извлекает первый запрос на соединение из очереди ожидающих соединений, создаёт новый подключенный сокет почти с такими же параметрами, что и у s, и выделяет для сокета новый файловый дескриптор, который и возвращается. Новый сокет более не находится в слушающем состоянии. Исходный сокет s не изменяется при этом вызове. Заметим, что флаги файловых дескрипторов (те, что можно установить с помощью параметра F_SETFL функции fcntl, типа неблокированного состояния или асинхронного ввода-вывода) не наследуются новым файловым дескриптором после accept.
Аргумент s -- это сокет, который был создан с помощью socket(2), привязан к локальному адресу с помощью bind(2), и слушает соединения после listen(2).
Аргумент addr -- это указатель на структуру sockaddr. В эту структуру помещается адрес другой стороны, в том виде, в каком он известен на коммуникационном уровне. Точный формат адреса, передаваемого в параметре addr, определяется "семейством" сокета (см. socket(2) и страницу руководства по соответствующему протоколу). Аргумент addrlen является параметром, передаваемым по ссылке: перед вызовом он содержит размер структуры, на которую ссылается addr, а после вызова -- действительную длину адреса в байтах. Если addr равен NULL, он не заполняется.
Если в очереди нет запросов на соединение, и на сокет не установлен флаг, что он является неблокирующим, accept блокирует вызвавшую программу до появления соединения. Если сокет является неблокирующим, а в очереди нет запросов на соединение, то accept возвращает EAGAIN.
Для того, чтобы получать уведомления о входящих сооединениях на сокете, можно использовать select(2) или poll(2). В этом случае, когда придёт запрос на новое соединение, будет доставлено событие "можно читать", и после этого вы можете вызвать accept, чтобы получить сокет для этого соединения. Можно также настроить сокет так, чтобы он посылал сигнал SIGIO, когда на нём происходит какая-либо активность; см. socket(7), где описаны детали.
Для определённых протоколов, которые требуют явного подтверждения, например, DECNet, системный вызов accept можно рассматривать просто как извлечение из очереди следующего запроса на соединение, не подразумевающее подтверждение. Подтверждение, в свою очередь, произойдет при следующем чтении или записи в новом файловом дескрипторе, а отказ от соединения может произойти при закрытии сокета. В настоящий момент под Linux такую семантику имеет только DECNet.
ЗАМЕЧАНИЯ
Не всегда после доставки сигнала SIGIO или после возврата из select(2) или poll(2) в очереди будет находиться соединение. Оно могло быть удалено в той же нити из-за асинхронной сетевой ошибки или же в другой нити до вызова accept. Если такое произойдет, то accept заблокирует выполнение до следующего соединения. Чтобы убедиться, что такого не произойдет в любом случае, на сокет s должен быть установлен флаг O_NONBLOCK (см. socket(7)).
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
Этот системный вызов возвращает −1 в случае ошибки. При успешном завершении возвращается неотрицательное целое, являющееся дескриптором сокета.
ОБРАБОТКА ОШИБОК
accept в реализации Linux передаёт уже ожидающие сетевые ошибки на новый сокет, как код ошибки из вызова accept. Это поведение отличается от других реализаций BSD-сокетов. Для надёжной работы приложения должны отслеживать сетевые ошибки, которые могут появиться при работе с протоколом, и обрабатывать их как EAGAIN, повторным выполнением. В случае TCP/IP такими ошибками являются ENETDOWN, EPROTO, ENOPROTOOPT, EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP, и ENETUNREACH.
ОШИБКИ
accept
завершится
с ошибкой
если:
EAGAIN или EWOULDBLOCK
сокет маркирован как неблокирующий, но нет ни одного соединения, которое можно было бы принять.
EBADF |
Дескриптор неправилен. |
ENOTSOCK
Дескриптор ссылается на файл, а не на сокет.
EOPNOTSUPP
Тип сокета, на который ссылается дескриптор, отличается от SOCK_STREAM.
EINTR |
Системный вызов был прерван сигналом, который был получен перед тем как из очереди поступило корректное соединение. |
ECONNABORTED
Соединение было прервано.
EINVAL |
Сокет не слушает соединения. | ||
EMFILE |
Было достигнуто ограничение по открытым файловым дескриптором на процесс. | ||
ENFILE |
Был достигнут системный максимум на файловые дескрипторы. |
accept
может
завершиться
с ошибкой
если:
Параметр
EFAULT addr не находится в пространстве адресов с возможностью записи.
ENOBUFS, ENOMEM
Не хватает свободной памяти. Это зачастую означает, что выделение памяти ограничено размерами буфера сокетов, а не системной памятью, но это не 100% верно.
EPROTO |
Ошибка протокола. |
В Linux accept может завешиться с ошибкой если:
EPERM |
Правила межсетевого экрана запрещают соединение. |
Вдобавок, могут также возвращаться сетевые ошибки на новом сокете и ошибки, могущие возникнуть в протоколе. Различные ядра Linux могут вернуть другие ошибки, например, EMFILE, EINVAL, ENOSR, ENOBUFS, EPERM, ECONNABORTED, ESOCKTNOSUPPORT, EPROTONOSUPPORT, ETIMEDOUT, ERESTARTSYS.
СООТВЕТСТВИЕ СТАНДАРТАМ
SVr4, 4.4BSD (функция accept впервые появилась в BSD 4.2). Страница руководства BSD документирует пять возможных кодов ошибок (EBADF, ENOTSOCK, EOPNOTSUPP, EWOULDBLOCK, EFAULT). SUSv3 документирует ошибки EAGAIN, EBADF, ECONNABORTED, EINTR, EINVAL, EMFILE, ENFILE, ENOBUFS, ENOMEM, ENOTSOCK, EOPNOTSUPP, EPROTO, EWOULDBLOCK. В дополнение, SUSv2 документирует EFAULT и ENOSR.
Реализация accept в Linux _не_ наследует флаги сокетов (типа O_NONBLOCK). Это поведение отличается от других реализаций BSD-сокетов. Переносимые программы не должны полагаться на такое поведение и всегда должны устанавливать на сокете, полученном от accept, все требуемые флаги.
ЗАМЕЧАНИЕ
Третий аргумент функции accept изначально был объявлен как int * (это именно так в libc4, libc5 и на многих других системах, включая BSD 4.*, SunOS 4 и SGI); черновик стандарта POSIX 1003.1g пытался поменять этот тип на size_t *, и в SunOS 5 это именно так. Поздние черновики POSIX содержат socklen_t *, и в Single Unix Specification и glibc2 это именно так.
По словам Линуса Торвальдса:
В _любой_ разумной библиотеке размеры "socklen_t" и int _должны_ совпадать. Любой другой вариант разламывает реализацию BSD-сокетов. В POSIX сначала использовали size_t, но я (и, к счастью, кто-то ещё, хотя и не слишком многие) очень громко пожаловались. Такая реализация полностью сломана, как раз потому, что size_t очень редко имеет тот же размер, что и "int", например, на 64-битных архитектурах. Это необходимо потому, что интерфейс BSD-сокетов таков, каков он есть.
В любом случае, люди из POSIX наконец поняли и создали "socklen_t". Вообще, с самого начала они просто не должны были ничего трогать, но по какой-то причине они чувствовали, что должны использовать поименованный тип (вероятно, они не хотели ударить в грязь лицом, сделав глупость, поэтому они тихо переименовали место, в котором просчитались).
СМОТРИ ТАКЖЕ
bind(2), connect(2), listen(2), select(2), socket(2)
ПЕРЕВОД
Copyright (C) Alexey Mahotkin <alexm [AT] hsys.ru> 1999-2001, Виктор Вислобоков <corochoone [AT] perm.ru> 2003