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
может завершиться с ошибкой если:
Параметр
EFAULTaddr
не находится в пространстве адресов с возможностью записи.
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".
Вообще, с самого начала они просто не должны были ничего трогать, но
по какой-то причине они чувствовали, что должны использовать
поименованный тип (вероятно, они не хотели ударить в грязь лицом,
сделав глупость, поэтому они тихо переименовали место, в котором
просчитались).