Что такое код socket_accept


Что такое код socket_accept

Аргумент sockfd — это сокет, который был создан с помощью socket(2), привязанный к локальному адресу с помощью bind(2), и прослушивающий соединения после listen(2).

Аргумент addr — это указатель на структуру sockaddr. В эту структуру помещается адрес ответной стороны в том виде, в каком он известен на коммуникационном уровне. Точный формат адреса, возвращаемого в параметре addr, определяется семейством адресов сокета (см. socket(2) и справочную страницу по соответствующему протоколу). Если addr равен NULL, то ничего не помещается; в этом случае addrlen не используется и также должен быть NULL.

Через аргумент addrlen осуществляется возврат результата: вызывающая сторона должна указать в нём размер (в байтах) структуры, на которую указывает addr; при возврате он будет содержать реальный размер адреса ответной стороны.

Возвращаемый адрес обрезается, если предоставленный буфер окажется слишком маленьким; в этом случае в addrlen будет возвращено значение большее чем было в вызове.

Если в очереди нет ожидающих запросов на соединение, и на сокет не помечен как неблокирующий, то accept() заблокирует вызвавшую программу до появления соединения. Если сокет помечен как неблокирующий, а в очереди нет запросов на соединение, то accept() завершится с ошибкой EAGAIN или EWOULDBLOCK.

Для того, чтобы получать уведомления о входящих соединениях на сокете, можно использовать select(2) или poll(2). В этом случае, когда придёт запрос на новое соединение, будет доставлено событие «можно читать», и после этого вы можете вызвать accept() чтобы получить сокет для этого соединения. Можно также настроить сокет так, чтобы он посылал сигнал SIGIO, когда на нём происходит какая-либо активность; см. socket(7).

Для определённых протоколов, которые требуют явного подтверждения, например, DECNet, accept() можно рассматривать просто как извлечение из очереди следующего запроса на соединение, не подразумевающее подтверждение. Подтверждение, в свою очередь, произойдет при следующем чтении или записи в новом файловом дескрипторе, а отказ от соединения может произойти при закрытии нового сокета. В настоящее время, под Linux такую семантику имеет только DECNet.

Если flags равно 0, то вызов accept4() равнозначен accept(). Следующие значения могут быть побитово сложены в flags для получения различного поведения:

SOCK_NONBLOCK Устанавливает флаг состояния файла O_NONBLOCK для нового открытого файлового дескриптора. Использование данного флага заменяет дополнительные вызовы fcntl(2) для достижения того же результата. SOCK_CLOEXEC Устанавливает флаг close-on-exec (FD_CLOEXEC) для нового открытого файлового дескриптора. Смотрите описание флага O_CLOEXEC в open(2) для того, чтобы узнать как это может пригодиться.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

Обработка ошибок

ОШИБКИ

Также, Linux accept() может завершиться с ошибкой если:

EPERM Правила межсетевого экрана запрещают соединение.

Вдобавок, могут также возвращаться сетевые ошибки на новом сокете и ошибки, могущие возникнуть в протоколе. Различные ядра Linux могут возвращать другие ошибки, например, ENOSR, ESOCKTNOSUPPORT, EPROTONOSUPPORT, ETIMEDOUT. Значение ошибки ERESTARTSYS можно увидеть при трассировке.

ВЕРСИИ

СООТВЕТСТВИЕ СТАНДАРТАМ

accept4() является нестандартным расширением Linux.

В Linux новый сокет, возвращаемый accept(), не наследует файловые флаги состояния такие как O_NONBLOCK и O_ASYNC от прослушивающего сокета. Это поведение отличается от каноническое реализации сокетов BSD. Переносимые программы не должны полагаться на наследуемость файловых флагов состояния или её отсутствия и всегда должны устанавливать на сокете, полученном от accept(), все требуемые флаги.

ЗАМЕЧАНИЯ

Возможно не всегда будет ожидание подключения после доставки SIGIO; или select(2) или poll(2) вернут событие доступности чтения, так как подключение может быть удалено из-за асинхронной сетевой ошибкой или другая нить была вызвала раньше accept(). Это это случается, то вызов блокируется, ожидая следующего прибытия подключения. Чтобы быть уверенным, что accept() никогда не заблокируется, сокету sockfd необходимо установить флаг O_NONBLOCK (см. socket(7)).

Тип socklen_t

«_Любая_ нормальная библиотека _должна_ иметь «socklen_t» размером с int. Любой другой вариант ломает реализацию BSD-сокетов. В POSIX _сначала_ls использовали size_t, но я (и, к счастью, кто-то ещё, хотя и не слишком многие) очень громко пожаловались. Такая реализация вообще не работает, так как size_t очень редко имеет тот же размер, что и «int», например, на 64-битных архитектурах. Это необходимо _только_ потому, что так сделано в интерфейсе BSD-сокетов. В любом случае, люди из POSIX наконец поняли и создали «socklen_t». Вообще, с самого начала они просто не должны были ничего трогать, но по какой-то причине они чувствовали, что должны использовать именованный тип (вероятно, они не хотели ударить в грязь лицом сделав глупость, поэтому они тихо переименовали место, в котором просчитались).»

socket_accept

socket_accept — принимает соединение с сокетом.

Описание

resource socket_accept (resource socket)

Эта функция — ЭКСПЕРИМЕНТАЛЬНАЯ. Поведение, имя и всё остальное, что задокументировано для данной функции может быть изменено в будущих релизах РНР без предупреждения. Вы можете использовать эту функцию только на свой страх и риск.

После того как сокет socket создан функцией socket_create(), связан с именем с помощью socket_bind() и начал прослушивание соединений с помощью socket_listen(), эта функция будет принимать входящие соединения по этому сокету. После создания успешного соединения возвращается новый ресурс сокета, который может использоваться при взаимодействии. Если в сокете имеется очередь из нескольких соединений, используется первое соединение. Если запущенных соединений нет, socket_accept() будет блокирована, пока соединение не появится. Если socket сделан неблокирующим с помощью функции socket_set_blocking() или socket_set_nonblock(), будет возвращено FALSE.

Ресурс сокета, возвращаемый функцией socket_accept(), не может использоваться для приёма новых соединений. Однако оригинальный прослушивающий сокет socket оста1тся открытым и может использоваться повторно.

Возвращает новый ресурс сокета успехе, FALSE при ошибке. Код ошибки можно запросить функцией socket_last_error(). Этот код ошибки можно передавать в socket_strerror() для получения текстового объяснения ошибки.

socket_accept — Принимает соединение на сокете

(PHP 4 >= 4.1.0, PHP 5, PHP 7)

socket_accept — Принимает соединение на сокете

Описание

После того, как сокет socket был создан при помощи функции socket_create() , привязан к имени при помощи функции socket_bind() , и ему было указано слушать соединения при помощи функции socket_listen() , эта функция будет принимать входящие соединения на этом сокете. Как только произошло удачное соединение, возвращается новый ресурс сокета, который может быть использован для связи. Если в очереди сокета есть несколько соединений, будет использовано первое из них. Если нету ожидающих соединений, то функция socket_accept() будет блокировать выполнение скрипта до тех пор, пока не появится соединение. Если сокет socket был сделан неблокирующим при помощи функции socket_set_blocking() или socket_set_nonblock() , будет возвращено FALSE .

Ресурс сокета, полученный при помощи функции socket_accept() не может быть использован для принятия новых соединений. Однако изначальный слушающий сокет socket , остаётся открытым и может быть использован повторно.

Список параметров

Действительный ресурс сокета, созданный при помощи функции socket_create() .

Возвращаемые значения

В случае успеха возвращает новый ресурс сокета или FALSE в случае ошибки. Код ошибки может быть получен при помощи вызова функции socket_last_error() . Этот код ошибки может быть передан функции socket_strerror() для получения текстового описания ошибки.

Смотрите также

  • socket_connect() — Начинает соединение с сокетом
  • socket_listen() — Listens for a connection on a socket
  • socket_create() — Create a socket (endpoint for communication)
  • socket_bind() — Привязывает имя к сокету
  • socket_strerror() — Возвращает строку, описывающую ошибку сокета

Проблема с accept() сокеты С++

server.cpp

server.h

При запуске сервера _Client получает верное значение. 128 или 126 или 132 например. Дойдя до

честно ждет входящего соединения. Когда клиент стучится — срабатывает. Затем, когда работа с этим клиентом завершена, сервер закрывает сокет и снова вызывает

Но, дойдя во второй раз до

_Client присваивается космическое число. Типо 99895919816189 и accept() принимает соединение без входящего клиента, когда должен по идее ждать реальный вызов. Но клиент еще не коннектился. В чем дело?

1 ответ 1

Нашел ошибку и хочу поделиться. Надеюсь, поможет кому-нибудь. Дебагером нашел проблему:

Читая MSDN, нашел решение.Необходимо настроить эту функцию:

теперь bind() может связывать сокеты, по которым уже передавались данные.

Но проблема не ушла. bind(), listen() и accept() работают так как надо. accept() уходит в ожидание как положено, но при новом входящем соединении не принимает его. Ничего не происходит. Нашел в итоге ошибку: Вот тут, в собственном методе:

После назначения нового сокета клиенту я не закрываю «слушающий порт». И при повторном вызове метода

я пытаюсь переназначить уже слушающий сокет не закрыв его должным образом.

Решение — либо закрывать «слушающий порт» после каждого accept() либо не пытаться создавать его заново. Повторный вызов метода CreateListenSocket(); ошибочен.

Что такое код socket_accept

Цикл приема соединений в упрощенном виде таков:

и ждет соединения.
Когда дожидается соединения, порождает accept-ом новый сокет, с которым далее работает дитё. В родительском процессе этот сокет сервер закрывает. Дочерний процесс в свою очередь закрывает сокет $listen_socket ибо он нужен только родительскому процессу. Т.к. у меня close, а не shutdown то в родительском процессе дескриптор сокета ($listen_socket) остается жив.

Отдав обработку запроса дочернему процессу родительский опять доходит до места

и опять ждет соединений.

И вот, когда клиент наработавшись закрывает соединение (которое по идее обрабатывается сокетом $connected_socket а не $listen_socket) почему-то срабатывает метод accept, только вот возвращает не новый сокет, а undef.

Почему??
Ведь клиента обрабатывает сокет $connected_socket и при закрытии клиентом соединения именно этот сокет должен просто закрыться. почему срабатывает accept сокета $listen_socket ?

Предупреждение!
1. Harkonnen , 06.11.2006 17:17
Glanda
Дочерний процесс в свою очередь закрывает сокет $listen_socket ибо он нужен только родительскому процессу. Т.к. у меня close, а не shutdown то в родительском процессе дескриптор сокета ($listen_socket) остается жив.
У сокетов нет счётчика ссылок процесса, поэтому закрывая $listen_socket в ребёнке — начинается лажа в родителе. А ‘accept’ возвращает ‘undef’, видимо, как ошибку — «на закрытом сокете вызвали accept».

Добавление от 06.11.2006 17:19:

А вообще, странное это дело — передавать декскритор через процессы. Там что, нет потоков? Или такая парадигма для FC2 в порядке вещей?

2. Glanda , 07.11.2006 10:45
Как это нет? Отличие close от shutdown окоромя того что shutdown может закрывать отдельно чтение и запись, как раз в том, что shutdown умеет полностью закрывать сокет, а close только в своем процессе.
Да и собственно.. так оно и происходит. Я добавил банальную проверку возвращаемого accept-ом значения. Если ловою undef, то просто делаю next. В итоге опять попадаю в my $connected_socket = $listen_socket->accept; и далее все работает нормально.
Т.е. в родительском процессе сокет не закрыт.
Проблема только в странном срабатывании accept-а. но не в закрытии $listen_socket

А что странного в передаче дескриптора через процессы со времен, как perl «официально научился» это делать?

3. Harkonnen , 07.11.2006 10:53
Glanda
Как это нет? Отличие close от shutdown окоромя того что shutdown может закрывать отдельно чтение и запись, как раз в том, что shutdown умеет полностью закрывать сокет, а close только в своем процессе.
Нет. Shutdown позволяет сделать graceful обрыв соедения — принимающая сторона получит 0 от ‘recv’, что сигнализирует конец передачи данных. К примеру, HTTP так работает для ‘Connection: close’. Т.е. это — что-то вроде «send» — посылка специальной информации на уровне протокола (для SOCK_STREAM). Это можно сравнить с записью EOF в файл.

close — это уже прибить сам сокет как объект. Вроде fclose для файлов.

Если ловою undef, то просто делаю next. В итоге опять попадаю в my $connected_socket = $listen_socket->accept; и далее все работает нормально.
Весьма странно. По идее должен быть хронический ‘undef’ — т.е. вечный цикл на undef’ах. А может быть всё еще веселее — после ‘close’ другой сокет получается тот же ID — и ‘accept’ уже для него вызывается?

Т.е. в родительском процессе сокет не закрыт.
А вы уверены, что сокеты надо закрывать в каждом процессе, а не достаточно прибить его хотя бы в одном?

Проблема только в странном срабатывании accept-а. но не в закрытии $listen_socket
Дык первое — следствие второго.

А что странного в передаче дескриптора через процессы со времен, как perl «официально научился» это делать?
На уровне объектов системы проблемы. Как минимум, под Windows такое не прокатит без всяких танцев вокруг DuplicateHandle и прочего. Как-то overhead’но это выглядит.

Как выглядит сам запуск child process’а? В виде чего он получает дескриптро сокета?

4. Glanda , 07.11.2006 12:27
Harkonnen
Т.е. Вы хотите сказать, что close не посылает принимающей стороне EOF ? Откуда инфа?
Или Вы о случаях, когда close не вызывает автоматом shutdown, как он должен это делать? Ну так то вроде только на некоторых осях а-ля Red Hat наблюдаются такие глюки. На FC2 вроде все нормально. Хотя, отношение к проблеме это все равно не имеет.

Весьма странно. По идее должен быть хронический ‘undef’ — т.е. вечный цикл на undef’ах. А может быть всё еще веселее — после ‘close’ другой сокет получается тот же ID — и ‘accept’ уже для него вызывается?
Да почему? close не закрывает сокет в родительском процессе.

А вы уверены, что сокеты надо закрывать в каждом процессе, а не достаточно прибить его хотя бы в одном?
close-ом надо в каждом. shutdown — ом можно закрыть полностью из одного процесса (при соотв. параметрах вызова).
у меня close.

Дык первое — следствие второго.
Каким образом? если бы не было сокета, то ошибка бы была в вызове функции неопределенного объекта.. ибо нет у меня $listen_socket -а. А этой ошибки не возникает. Значит объект есть.. и срабатывает его метод, только вот возвращает undef.

Как минимум, под Windows такое не прокатит
Не знаю. Не пробовал. Меня windows и не интересует

Как выглядит сам запуск child process’а? В виде чего он получает дескриптро сокета?
В смысле? Обычный fork. Т.к. форкаюсь после открытия сокета, то его дескриптор в обоих процессах.

P.S. дескриптор в переменной, как это видно из кода


P.P.S.
То бишь вот в этом коде

launch_child просто форкается. со всякими сменами привилегий и т.п. Ничего сверхъестественного не делает.

Кстати, если бы close закрывал сокет во всех процессах, то я бы не смог работать в дочернем процессе с прожденным accept-ом сокетом. Ибо он в родительском процессе сразу закрывается. ($listen_socket->close)
А я ж работаю.. вполне успешно

5. Yaroslav Rastrigin , 07.11.2006 13:51
man 2 accept
NOTES
There may not always be a connection waiting after a SIGIO is delivered or select(2) or poll(2) return a
readability event because the connection might have been removed by an asynchronous network error or another
thread before accept is called. If this happens then the call will block waiting for the next connection to
arrive. To ensure that accept never blocks, the passed socket s needs to have the O_NONBLOCK flag set (see
socket(7)).

Кста, «метод accept» select-ом пользуется ?
ltrace perl
И посмотри, какие аргументы по результату приходят на libc’шный accept и что оно возвращает.
Версия перла какая , из любопытства ?

6. Harkonnen , 07.11.2006 18:14
Glanda
Т.е. Вы хотите сказать, что close не посылает принимающей стороне EOF ? Откуда инфа?
Из знания API BSD sockets. Если конечно, Perl ничего не наворачивает сверх их функциональности, что было бы очень странным.

Или Вы о случаях, когда close не вызывает автоматом shutdown, как он должен это делать?
Да никак — он никогда этого не делает. В аналогии с файлами:
shutdown(): ftputc(EOF, f)
close(): fclose(f)

Ну так то вроде только на некоторых осях а-ля Red Hat наблюдаются такие глюки.
Это не глюки. Слать или нет EOF — решение программиста. Принимающая сторона в этом случае получит либо 0 от ‘recv’, либо ошибку (connreset). Может еще timeout огрести.

Хотя, отношение к проблеме это все равно не имеет.
Имеет, т.к. $listen_socket->close() делает сокет невалидным.

Да почему? close не закрывает сокет в родительском процессе.
Кто это сказал?

close-ом надо в каждом. shutdown — ом можно закрыть полностью из одного процесса (при соотв. параметрах вызова).
у меня close.

shutdown вообще не имеет НИКАКОГО отношения к закрытию сокета как системного объекта.

Каким образом? если бы не было сокета, то ошибка бы была в вызове функции неопределенного объекта.. ибо нет у меня $listen_socket -а.
Объект языка ‘Perl’ $listen_socket есть. Объекта системы уже нет. Видимо, perl’овский socket ломится по закрытому ID — и будет либо получать undef в случае, если ID невалиден, либо будет вызывать ‘accept’ для чего-нибудь левого, что этот ID получит.

А этой ошибки не возникает. Значит объект есть.. и срабатывает его метод, только вот возвращает undef.
Perl и возвращает undef, т.к. этого объекта СИСТЕМЫ нет. Есть только Perl’овская обёртка — она получает от системы ошибку (wrong file descriptor) и возващает ‘undef’ уже на уровне Perl.

В смысле? Обычный fork. Т.к. форкаюсь после открытия сокета, то его дескриптор в обоих процессах.
А почему вы тогда остальные handle’ы сервера не закрываете? Ну там файлы всякие, если есть. Нафига вообще трогать $listen_socket в дите?

Кстати, если бы close закрывал сокет во всех процессах, то я бы не смог работать в дочернем процессе с прожденным accept-ом сокетом. Ибо он в родительском процессе сразу закрывается. ($listen_socket->close)
Закрытие $listen_socket не влияет на сокеты, порождённые его accept’ами.

7. rGlory , 07.11.2006 18:14
Harkonnen
А вообще, странное это дело — передавать декскритор через процессы. Там что, нет потоков? Или такая парадигма для FC2 в порядке вещей?
Такая парадигма для *nix мира вполне нормальна и очень распространенна. И весьма удобна кстати, так работает например супер демон inetd. А чтобы реализовать подобную функциональность под win нужно поплясать с бубном. Во всяком случае насколько я знаю.

Имеет, т.к. $listen_socket->close() делает сокет невалидным.
Вы, похоже, немного не в курсе. После форка разные ветки if выполняют два разных процесса. Поэтому то, что в дочернем закрывается сокет, не делает его невалидным в родительском и наоборот. Прием стандартный, в родительском процессе закрывается сокет, предназначенный для дочернего процесса и vice versa.

Glanda
Документация говорит о Socket очень мало, поэтому является ли это ожидаемым или это баг — сказать трудно. Так как, судя по отсуствию ответов здесь никто с этим не сталкивался, Вам стоит обратиться в специализированные форумы по перлу. Если Вас интересует причина. Или поставить обработку undef, хотя не понятно как отделять случаи, когда accept действительно сломался. Правда это маловероятно, если конечно, не ошибка в программе.

8. Harkonnen , 07.11.2006 19:51
rGlory
Поэтому то, что в дочернем закрывается сокет, не делает его невалидным в родительском и наоборот.
Тогда непонятно вот что:
9. rGlory , 08.11.2006 02:18
Harkonnen
// . — А ТУТ, ЧЁ — ЕЩЕ И ВСЕ log_file ЗАКРЫВАТЬ .
Если нужно, чтобы они были закрыты в клиенте, то закрывать. Кстати код некорректен, fork() может и с ошибкой завершиться (я так понимаю, это код не на перле приведен).
10. Harkonnen , 08.11.2006 04:38
rGlory
Если нужно, чтобы они были закрыты в клиенте, то закрывать
А с какой целью применяется этот «Прием стандартный, в родительском процессе закрывается сокет, предназначенный для дочернего процесса и vice versa.»? Чтобы закрытие слушающего сокета не зависело от детей или на то есть более веские причины?

Кстати код некорректен, fork() может и с ошибкой завершиться (я так понимаю, это код не на перле приведен).
Это полупсевдокод на С синтаксисе.

11. rGlory , 08.11.2006 04:50
Harkonnen
А с какой целью применяется этот «Прием стандартный, в родительском процессе закрывается сокет, предназначенный для дочернего процесса и vice versa.»? Чтобы закрытие слушающего сокета не зависело от детей или на то есть более веские причины?

Во первых количество одновременно открытых файловых дескрипторов у процесса ограничено. И если родитель сокет, созданый акцептом закрывать не будет, то через какое-то время не сможет создавать новые. Если же ребенок не закроет сокет родителя, которуму сделан bind на определенный порт, то родитель не сможет этот сокет закрыть, а потом открыть и забиндить снова — получим «address already in use», пока все клиенты не закроются. Кроме того, если родитель не закрыл сокет ребенка, а ребенок закрыл свой — то клиент этого не обнаружит, потому что сокет еще открыт. Что не есть хорошо. Возможно найдутся еще какие-то эффекты, то есть по всякому лучше закрывать.

12. Harkonnen , 08.11.2006 06:04
rGlory
Ясно. Весьма забавный геморрой получается этот fork, если server держит кучу всего открытым, актуального только для главного acceptor потока.
13. Yaroslav Rastrigin , 08.11.2006 10:02
Harkonnen
Гхм..То есть best unix practices и весьма удобный вариант развязать туеву хучу spaces (address space, fd space и т.п.) называют
«Весьма забавным геморроем»
«Куча всего» — это как раз куча fds, которые наследуются из предка в потомках. И их стОит закрывать все, кроме одного-двух нужных. Иначе, действительно, после какого-то времени начнётся fd leak и accept вернёт -1 и errno = 24 . Но это (ну ок, ещё, AFAIR, signal handlers и что-то из IPC) — всё, о чём нужно заботиться..
14. Harkonnen , 08.11.2006 11:16
Yaroslav Rastrigin
То есть best unix practices
Скорее — first unix practices. «Best» ровно в силу своей first’нутости. Есть, к примеру, pthread — пологичнее будет.

и весьма удобный вариант развязать туеву хучу spaces (address space, fd space и т.п.)
Развязать дублированием всего и вся (включая адресное пространство) — это imho, перебор. Удобно в простых скриптпоподобных программулинах, но не более того. Представьте себе сервер базы данных на fork’ах. Каждое дитё будет по 100 мегабайт памяти отжирать, дублируя адресное пространствео родителя? fork то не знает — какие переменные позже пригодятся, а какие нет

«Куча всего» — это как раз куча fds, которые наследуются из предка в потомках.
И где это может пригодиться? Всё равно их синхронизировать придётся между потоками, если оба будут в один ‘fds’ лезть. Логичней указать в командной строке нужные fds (которых 1-2) и по типу win32 DuplicateHandle сказать — какие будут наследоваться дитём.

И их стОит закрывать все, кроме одного-двух нужных
Дык — я и говорю — один «простой» fork() — а потом внезапные грабли в виде 20 close’ов.

15. Glanda , 08.11.2006 11:17
Огромное спасибо за ваши рассуждения, подчерпнул skill-а И прошу прощения, что выпал на время из беседы (на работе завал).
Сегодня постараюсь вечером проверить, что советовал Yaroslav Rastrigin и отпишусь

Harkonnen
Закрытие $listen_socket не влияет на сокеты, порождённые его accept’ами.
Перепутал. Имел ввиду конечно connected_socket. Именно он закрывается в родителе, а потом я с ним работаю в дите.

rGlory
Вам стоит обратиться в специализированные форумы по перлу.
Буду Вам признателен, если направите на такой форум. Я с такими, увы, ни разу не сталкивался. По крайней мере в рунете.
Если у вас есть ссылки на таковые форумы (на русском или англицком), то готов поменять их на пиво

Добавление от 08.11.2006 11:26:

P.S. Чтобы на будущее знать, поясните.
Таки close в перле вызывает перед закрытием сокета shutdown, или нет? Ибо насколько я помню он это делает, но вроде как не всегда (натыкался на rsdn.ru об упоминании забывчивости). При этом у себя я нормально ловлю EOF когда клиент делает close т.е. shutdown вызывается.
Где правда?

16. Harkonnen , 08.11.2006 11:38
Glanda
При этом у себя я нормально ловлю EOF когда клиент делает close т.е. shutdown вызывается.
Вполне возможно, что Perl и ошибочные, и graceful обрывы соединения преподносит как EOF. Но в этом случае невозможно, к примеру, отличить — докачалась ли страница по HTTP (без keep-alive) или нет. Лучше смотреть в сторону ‘recv’ — он вернёт 0 байт в случае shutdown, либо ошибку в случае close.
17. Glanda , 08.11.2006 11:42
Harkonnen
Так вроде нет. EOF это именно окончание соединения.
18. Harkonnen , 08.11.2006 11:58
Glanda
Чем вы сокет читаете? Если ‘recv’, то EOF (который -1) — это не EOF, а ошибка.
19. Glanda , 08.11.2006 12:11
Harkonnen
В перле для этого есть sysread.
Но речь я вел о банальном операторе <> , который нормально прекращает свою работу когда ловит EOF.
И он нормально читает данные из сокета, и нормально завершается, когда сокет закрывается close-ом.
Доки говорят что тот EOF есть именно завершение соединения.
По идее он (оператор) вообще должен вусмерть повиснуть в ожидании EOF-а, если сокет закрывается без каких-либо сообщений.

Добавление от 08.11.2006 12:11:

P.S. Как собственно и происходит при обрыве связи.

20. Harkonnen , 08.11.2006 12:19
Glanda
По идее он (оператор) вообще должен вусмерть повиснуть в ожидании EOF-а, если сокет закрывается без каких-либо сообщений.
1. Дык — есть сообщение обрыв-связи, а есть сообщение о shutdown(). Просто ‘close’ поступает «благородно» — оповещает peer’а, что можно больше не напрягаться (но в виде обрыва связи). Он мог бы этого и не делать. Тогда для peer’а эффект — как будто сетевой провод вынули. Более того, есть подозрения, что ошибки на уровне протокола так же будут отдавать EOF’ами. Как вообще проверить — ошибка или нет на сокете в perl при чтении?

2. А байт 0xFF в нормальных данных вы получить не боитесь?

21. Sioln , 08.11.2006 12:37
Harkonnen
Как вообще проверить — ошибка или нет на сокете в perl при чтении?
Думаю примерно так:
binmode($handle)
читаем $handle || warn($!)
22. Glanda , 08.11.2006 12:56
Harkonnen
Это что за сообщение «обрыв связи»? Кто его посылает, если связь оборвалась?
Короче, речь о том, что <> заканчивается когда приходит EOF (не ошибка, а именно EOF, который посылает нам shutdown и который посылает нам точно так же close). По крайней мере так говорит книга Штайна. И так говорят те, доки что я находил. Если точнее, то они говорят, что close предварительно вызывает shutdown.

Sioln
Так а sysread-ом разве нельзя? 0 — EOF, undef — ошибка.

23. Yaroslav Rastrigin , 08.11.2006 13:28
«Holy war ! Holy war !» — запело в душе unix zealot’а (т.е. меня

цитата: Harkonnen:
Yaroslav Rastrigin
То есть best unix practices
Скорее — first unix practices. «Best» ровно в силу своей first’нутости. Есть, к примеру, pthread — пологичнее будет.

цитата:
и весьма удобный вариант развязать туеву хучу spaces (address space, fd space и т.п.)
Развязать дублированием всего и вся (включая адресное пространство) — это imho, перебор. Удобно в простых скриптпоподобных программулинах, но не более того. Представьте себе сервер базы данных на fork’ах. Каждое дитё будет по 100 мегабайт памяти отжирать, дублируя адресное пространствео родителя? fork то не знает — какие переменные позже пригодятся, а какие нет

цитата:
«Куча всего» — это как раз куча fds, которые наследуются из предка в потомках.
И где это может пригодиться? Всё равно их синхронизировать придётся между потоками, если оба будут в один ‘fds’ лезть. Логичней указать в командной строке нужные fds (которых 1-2) и по типу win32 DuplicateHandle сказать — какие будут наследоваться дитём.

цитата:
И их стОит закрывать все, кроме одного-двух нужных
Дык — я и говорю — один «простой» fork() — а потом внезапные грабли в виде 20 close’ов.

Ну не всё так уж плохо. Просто разные парадигмы

24. Harkonnen , 08.11.2006 18:39
Glanda
По крайней мере так говорит книга Штайна.
Может быть. ‘man close / man shutdown’ обычно говорит другое. Возможно, это особенность Perl’а такая.

Yaroslav Rastrigin
«Holy war ! Holy war !» — запело в душе unix zealot’а (т.е. меня
Yeah, buddy

Ну, конкретно в linux’е copy on write — в потомке дублируются только страницы, в которые предок или потомок пишет
Дык — fork() не знает заранее, какие переменные потом будут использоваться — а предок может во всё писать,
соответственно оно в итоге копируется для предка, составляя оригинал для потомков. Дай бог, что не для КАЖДОГО потомка. — тогда хотя бы x2 overhead, а не xN overhead.

А вообще решений для таких моментов много — например, если требуется общая между процессами память — берётся файл и делается его mmap . Или IPC, но менее удобно. Или unix domain sockets, обмен по которым дёшев, поскольку zero copy.
Дык — речь о банальных malloc’ах. Или всё большое и pre-fork’нутое из-за этого приходится на mmap пересаживать?

Ммм.. Два потока в один fd ? Я пытаюсь представить себе, зачем — и не могу
Вот и я не могу. (разве что log file какой-нибудь — но всё равно общая синхронизация потребуется). А ‘fork’ поступает именно таким образом — дублирует все ‘fd’ как будто все они нужны потомку

а список есть, из него select/poll набивается
А heap walker есть, чтобы то же самое сделать с addres space?

Ну не всё так уж плохо. Просто разные парадигмы

25. rGlory , 08.11.2006 19:34
Harkonnen

цитата: Скорее — first unix practices. «Best» ровно в силу своей first’нутости. Есть, к примеру, pthread — пологичнее будет.

Вы пытаетесь «натянуть» чужую парадигму, и поэтому Ваша логичность здесь не к месту. В unix вообще принято не складывать все яйца в одну корзину, то есть работа сервера это именно делать accept на сокете. И ничего более. При таком подходе много открытых файлов там просто не будет. У многопоточного приложения же есть свои недостатки — надежность например. В данном случае ошибка в клиенстком коде не приведет к прекращению работы всего сервера и остальных клиентов. Удобнее ли более тесная взаимосвязь или обособленность и надежность, тут нужно решать в каждом конкретном случае. И на тех же нитях можно написать под Linux, и не нужно будет сокеты закрывать. Правда лучше не на перле.

Glanda
Почему Вы не воспользовались (x)inetd? Что-то важное делается в сервере помимо этого?

26. Harkonnen , 08.11.2006 20:24
rGlory
Вы пытаетесь «натянуть» чужую парадигму, и поэтому Ваша логичность здесь не к месту.
Дык — я доводы привёл. Мера логичности простая — удобство. Закрывать кучу лишнего сразу после fork’а — это IMHO не есть удобство. (in case that’s the case)

У многопоточного приложения же есть свои недостатки — надежность например.
Только в силу потребности в синхронизации. fork()-like процессы от этого страдают не меньше, когда появляются общие данные.

В данном случае ошибка в клиенстком коде не приведет к прекращению работы всего сервера и остальных клиентов.
Я не против вынесения клиента в отдельный процесс. Я не понимаю — нафига при этом дублировать ВЕСЬ контекст процесса-батьки, когда от него требуется всего-то пару fds’ов.

27. Glanda , 08.11.2006 21:35
Harkonnen
Я не понимаю — нафига при этом дублировать ВЕСЬ контекст процесса-батьки, когда от него требуется всего-то пару fds’ов.
Если речь о конкретном примере (да и о многих других), то не ВЕСЬ, а весь. Причем только то, что было до fork-а. А до него там почти ничего нет, как правило.

rGlory
Почему Вы не воспользовались (x)inetd? Что-то важное делается в сервере помимо этого?
Да. Там есть некоторая «обвязка» для статистики, управления детьми и т.п.
Сорри за настырность, но у вас нету случаем пары ссылок на спец. форумы по перлу? Ибо очень хочется

28. Harkonnen , 08.11.2006 21:46
Glanda
Причем только то, что было до fork-а. А до него там почти ничего нет, как правило.
Разве что так.

цитата: Harkonnen:
Я не против вынесения клиента в отдельный процесс. Я не понимаю — нафига при этом дублировать ВЕСЬ контекст процесса-батьки, когда от него требуется всего-то пару fds’ов.

Так в том то и дело, что если принять парадигму, принятую в *nix world, то в контексте батьки практически ничего не будет. Кроме того, можно еще сделать vfork() и exec() — при этом в итоге просто получим inetd.

29. rGlory , 08.11.2006 22:05
30. Glanda , 09.11.2006 00:04
Хм.. что-то я не совладал с ltrace-ом (я в нем не силен).
Последнее, что он выводит перед тем, как скрипт заходит в бесконечный цикл в ожидании accept-ов это:

Последняя строка — это вывод самого скрипта.

После этого — пусто. Если к серверу стучится клиент (срабатывает accept, затем форк и закрытие сокета возвращенного accept-ом) то ничего кроме стандартного вывода скрипта нет. не уж-то никакие библиотеки не юзаются

man пока не помог. сча еще поковыряюсь.

Добавление от 09.11.2006 00:05:

P.S.
А версия perl — 5.0.8

Добавление от 09.11.2006 00:06:

P.P.S.
Или может вот тут (Perl_newXS) и вызывается сишная библиотека с accept-ом.

31. rGlory , 09.11.2006 00:12
Glanda
Сорри за настырность, но у вас нету случаем пары ссылок на спец. форумы по перлу?
Я думаю стоит попробовать написать в news группу по перлу, но я не спец могу ошибаться, по с++, например, на сложные вопросы стоит искать ответа именно там. Или поискать на cpan. Не думаю, что в рунете Вы найдете что-то путное по этому поводу.
32. Sioln , 09.11.2006 09:46
Glanda
http://perlmonks.com/
В интерфейсе разобраться довольно трудно, но там РЕАЛЬНЫЕ ЧЕРЕПА сидят по перлу
33. Glanda , 13.11.2006 14:59
Люди, а подскажите по еще одному вопросу. В man-ах это как-то не объясняется.

Если я читаю из сокета с помощью sysread. При этом мы знаем, что эта функция возвращает сразу же результат.. при этом возвращает столько сколько может. Т.е. если я «попросил» прочитать 200 байт, но столько нет, то она прочтет сколько есть и вернет эти данные.
Так вот. а что если у нас случай с тормознутым клиентом?
Т.е. я в цикле запускаю sysread . читаю по 100 байт.. и нарываюсь на такую ситуацию, когда в буфере пусто, но клиент еще пишет мне в сокет? sysread вернет 0. А 0 у sysread это есть EOF.
Т.е. закрытие сокета клиентом я не смогу отличить от его тормозов? В обоих случаях получу 0 ?

34. Harkonnen , 13.11.2006 15:07
Glanda
Если сокет в blocking I/O (по умолчанию именно так) — sysread не вернётся, пока хоть что-нибудь не придёт.
35. Glanda , 13.11.2006 15:14
Harkonnen
Т.е. хотя бы один байт но он будет ждать?
При этом если придет нулевой (клиент закрыл соединение), то он вернет 0?


36. Harkonnen , 13.11.2006 15:25
Glanda
В оригинале есть ‘recv(buf, size)’ — он возвращает КОЛИЧЕСТВО БАЙТ, которые прочитаны в буфер. Либо что-то отрицательное в случае ошибки, либо 0 в случае shutdown() на другой стороне. Как ведёт себя sysread, я не знаю.
37. Glanda , 13.11.2006 15:47
Harkonnen
хм. в перле recv позиционируется для UDP
Почитаю что про него пишут..

Добавление от 13.11.2006 15:48:

стоп. а что он вернет, если в буфере 0 ?

38. Harkonnen , 13.11.2006 16:52
Glanda
Если в буфере содержится байт 0, то он вернёт 1 и в ‘buf[0]’ запишет 0. Если в буфере приём shutdown’а — то он вернёт 0. Если в буфере нифига нет — то функция не вернёт управление пока там что-нибудь не появится (хоть shutdown, хоть данные). Если ‘sysread’ возвращает прочитанный байт, тогда в неё невозможно отличить байт 0 от приёма shutdown’а и, возможно, ошибку она тоже выдаёт на shutdown. Но если это так — то это особенности ‘sysread’ — ради упрощения синтаксиса (не надо передавать буфера) некоторые ситуации стали неразличимы на уровне sysread интерфейса.
39. Glanda , 13.11.2006 16:56
Harkonnen
Не-не, она возвращает кол-во.
Мы друг друга немного не поняли
В общем понял. Спасибо за наводку на правильное решение
40. Harkonnen , 13.11.2006 17:13
Glanda
Ok
One last word. На уровне протокола TCP байт 0 и shutdown — это совершенно разные вещи. Коллизии между 0 (или 0xFF) и shutdown() могут возникнуть только на уровне языкового интерфейса, если кто-то пойдёт на упрощение в виде ‘getс’ для сокетов. Т.е. пространство возвращаемых значений через ‘recv’ (и, видимо, sysread) — это 0. Байты ноль принадлежат подклассу ‘>0’ — они просто будут записаны в буфер.

Если в буфере приёмя лежит 123 байта, а следом за ними идёт признак shutdown’а — то, вызывая ‘recv’ для 50 байт в буфере, — мы получим следующие возвращённые значения от ‘recv’:
50 // bytes 0..49
50 // bytes 50..99
23 // bytes 100..122
0 // shutdown marker

Добавление от 13.11.2006 17:16:

(я неправильно понял коммент)

Как работает функция socket API accept ()?

API сокета является де-факто стандартом для TCP/IP и UDP / IP связи (то есть, сетевой код, как мы его знаем). Однако, одна из его основных функций, accept() Это немного волшебно.

заимствовать полуформальное определение:

accept () используется на стороне сервера. Он принимает полученную входящую попытку чтобы создать новое TCP-соединение из удаленный клиент, и создает новый гнездо, связанное с гнездом адрес этой соединение.

другими словами, accept возвращает новый сокет, через который сервер может взаимодействовать с вновь подключенным клиентом. Старая розетка (на которой accept был вызван) остается открытым, на том же порту, прослушивая новые соединения.

как accept работы? Как это реализуется? Существует много путаницы на эту тему. Многие люди утверждают, что принимают открывает новый порт и вы общаетесь с клиентом через него. Но это, очевидно, не так, как нет открывается новый порт. Вы действительно можете общаться через один и тот же порт с разными клиентами, но как? Когда несколько потоков вызывают recv на том же порту, как данные знают, куда идти?

Я думаю, что это что-то вроде адреса клиента, связанного с дескриптором сокета, и всякий раз, когда данные поступают через recv он направляется в правильный сокет, но я не уверен.

было бы здорово получить подробное объяснение внутренней работы этот механизм.

4 ответа:

ваша путаница заключается в том, что сокет идентифицируется по IP сервера : порт сервера. Когда в действительности сокеты однозначно идентифицируются квартетом информации:

Client IP : Client Port и Server IP : Server Port

таким образом, хотя IP-адрес сервера и порт сервера являются постоянными во всех принятых подключениях, информация на стороне клиента-это то, что позволяет ему отслеживать, где все происходит.

пример для уточнения вещей:

скажем, у нас есть сервер в 192.168.1.1: 80 и два клиента, 10.0.0.1 и 10.0.0.2.

10.0.0.1 открывает соединение на локальном порту 1234 и подключается к серверу. Теперь у сервера есть один сокет, идентифицированный следующим образом:

теперь 10.0.0.2 открывает соединение на локальном порту 5678 и подключается к серверу. Теперь сервер имеет два сокета, идентифицированных следующим образом:

10.0.0.1:1234 — 192.168.1.1:80
10.0.0.2:5678 — 192.168.1.1:80

просто добавить к ответу, данному пользователем «17 из 26»

гнездо на самом деле состоит из 5 кортежей — (IP-адрес источника, порт источника, IP назначения, порт назначения, протокол). Здесь протокол может быть TCP или UDP или любой протокол транспортного уровня. Этот протокол идентифицируется в пакете из поля «протокол» в датаграмме IP.

таким образом, можно иметь разные приложения на сервере, связывающиеся с одним и тем же клиентом на точно таких же 4-кортежах но по-разному в поле протокола. Например

Apache на стороне сервера говорить дальше (server1. com:880-client1:1234 на TCP) и World of Warcraft talking on (server1.com:880-client1:1234 on UDP)

и клиент, и сервер будут обрабатывать это как поле протокола в IP-пакете в обоих случаях отличается, даже если все остальные 4 поля одинаковы.

что смутило меня, когда я учился этому, было то, что термины socket и port предположим, что они являются чем-то физическим, когда на самом деле это просто структуры данных, которые ядро использует для абстрагирования деталей сети.

таким образом, структуры данных реализованы, чтобы иметь возможность поддерживать отдельные соединения от разных клиентов. Что касается как они реализованы, ответ либо a.) это не имеет значения, цель API сокетов именно в этом реализация не должна иметь значения или b.) просто посмотрите. Помимо настоятельно рекомендуемых книг Стивенса, содержащих подробное описание одной реализации, проверьте источник в Linux или Solaris или один из BSD.

Как сказал другой парень, сокет однозначно идентифицируется 4-кортежем (клиентский IP, клиентский порт, серверный IP, серверный порт).

серверный процесс, запущенный на IP-сервере, поддерживает базу данных (то есть мне все равно, какую таблицу/список/дерево/массив/магическую структуру данных он использует) активных сокетов и прослушивает порт сервера. Когда он получает сообщение (через стек TCP/IP сервера), он проверяет IP-адрес и порт клиента в базе данных. Если клиентский IP и порт клиента находятся в записи базы данных, сообщение передается существующему обработчику, иначе создается новая запись базы данных и новый обработчик, созданный для обработки этого сокета.

в первые дни ARPAnet некоторые протоколы (FTP для одного) будут прослушивать указанный порт для запросов на подключение и отвечать портом передачи обслуживания. Дальнейшие коммуникации для этого соединения будут проходить через порт передачи. Это было сделано для повышения производительности каждого пакета: компьютеры были несколько порядков магнитуда в те дни была меньше.

Веб-сервер на C++ и сокетах

Создадим HTTP-сервер, который обрабатывает запросы браузера и возвращает ответ в виде HTML-страницы.

Введение в HTTP

Для начала разберемся, что из себя представляет HTTP. Это текстовый протокол для обмена данными между браузером и веб-сервером.

Пример HTTP-запроса:

Первая строка передает метод запроса, идентификатор ресурса (URI) и версию HTTP-протокола. Затем перечисляются заголовки запроса, в которых браузер передает имя хоста, поддерживаемые кодировки, cookie и другие служебные параметры. После каждого заголовка ставится символ переноса строки \r\n .

У некоторых запросов есть тело. Когда отправляется форма методом POST, в теле запроса передаются значения полей этой формы.

Тело запроса отделяется от заголовков одной пустой строкой. Заголовок «Content-Type» говорит серверу, в каком формате закодировано тело запроса. По умолчанию, в HTML-форме данные кодируются методом «application/x-www-form-urlencoded».

Иногда необходимо передать данные в другом формате. Например, при загрузке файлов на сервер, бинарные данные кодируются методом «multipart/form-data».

Сервер обрабатывает запрос клиента и возвращает ответ.

Пример ответа сервера:

В первой строке ответа передается версия протокола и статус ответа. Для успешных запросов обычно используется статус «200 OK». Если ресурс не найден на сервере, возвращается «404 Not Found».

Тело ответа так же, как и у запроса, отделяется от заголовков одной пустой строкой.

Полная спецификации протокола HTTP описывается в стандарте rfc-2068. По понятным причинам, мы не будем реализовывать все возможности протокола в рамках этого материала. Достаточно реализовать поддержку работы с заголовками запроса и ответа, получение метода запроса, версии протокола и URL-адреса.

Что будет делать сервер?

Сервер будет принимать запросы клиентов, парсить заголовки и тело запроса, и возвращать тестовую HTML-страничку, на которой отображены данные запроса клиента (запрошенный URL, метод запроса, cookie и другие заголовки).

О сокетах

Для работы с сетью на низком уровне традиционно используют сокеты. Сокет — это абстракция, которая позволяет работать с сетевыми ресурсами, как с файлами. Мы можем писать и читать данные из сокета почти так же, как из обычного файла.

В этом материале мы будем работать с виндовой реализацией сокетов, которая находится в заголовочном файле . В Unix-подобных ОС принцип работы с сокетами такой же, только отличается API. Вы можете подробнее почитать о сокетах Беркли, которые используются в GNU/Linux.

Создание сокета

Создадим сокет с помощью функции socket , которая находится в заголовочном файле . Для работы с IP-адресами нам понадобится заголовочный файл .

Мы подготовили все данные, которые необходимо для создания сокета и создали сам сокет. Функция socket возвращает целочисленное значение файлового дескриптора, который выделен операционной системой под сокет.

Привязка сокета к адресу (bind)

Следующим шагом, нам необходимо привязать IP-адрес к сокету, чтобы он мог принимать входящие соединения. Для привязки конкретного адреса к сокету используется фукнция bind . Она принимает целочисленный идентификатор файлового дескриптора сокета, адрес (поле ai_addr из структуры addrinfo ) и размер адреса в байтах (используется для поддержки IPv6).

Подготовка сокета к принятию входящих соединений (listen)

Подготовим сокет к принятию входящих соединений от клиентов. Это делается с помощью функции listen . Она принимает дескриптор слушающего сокета и максимальное количество одновременных соединений.

В случае ошибки, функция listen возращает значение константы SOCKET_ERROR . При успешном выполнении она вернет 0.

В константе SOMAXCONN хранится максимально возможное число одновременных TCP-соединений. Это ограничение работает на уровне ядра ОС.

Ожидание входящего соединения (accept)

Функция accept ожидает запрос на установку TCP-соединения от удаленного хоста. В качестве аргумента ей передается дескриптор слушающего сокета.

При успешной установке TCP-соединения, для него создается новый сокет. Функция accept возвращает дескриптор этого сокета. Если произошла ошибка соединения, то возвращается значение INVALID_SOCKET .

Получение запроса и отправка ответа

После установки соединение с сервером, браузер отправляет HTTP-запрос. Мы получаем содержимое запроса через функцию recv . Она принимает дескриптор TCP-соединения (в нашем случае это client_socket ), указатель на буфер для сохранения полученных данных, размер буфера в байтах и дополнительные флаги (которые сейчас нас не интересуют).

При успешном выполнении функция recv вернет размер полученных данных. В случае ошибки возвращается значение SOCKET_ERROR . Если соединение было закрыто клиентом, то возвращается 0.

Мы создадим буфер размером 1024 байта для сохранения HTTP-запроса.

После получения запроса мы сразу же отправили ответ клиенту с помощью функции send . Она принимает дескриптор сокета, строку с данными ответа и размер ответа в байтах.

В случае ошибки, функция возвращает значение SOCKET_ERROR . В случае успеха — количество переданных байт.

Попробуем скомпилировать программу, не забыв предварительно завершить функцию main .

Если скомпилировать и запустить программу, то окно консоли «подвиснет» в ожидании запроса на установление TCP-соединения. Откройте в браузере адрес http://127.0.0.1:8000/. Сервер вернет ответ, как на рисунке ниже и завершит работу.

Последовательная обработка запросов

Чтобы сервер не завершал работу после обработки первого запроса, а продолжал обрабатывать новые соединения, нужно зациклить ту часть кода, которая принимает запрос на установку соединения и возвращает ответ.

Когда сервер закончит обработку запроса одного клиента, он закроет соединение с ним и будет ожидать нового запроса.

Во второй части этой статьи мы напишем парсер HTTP-заголовков и создадим нормальное API для управления HTTP-запросами и ответами.

Примечание: если вы используете MinGW в Windows, то библиотеку Ws2_32.lib нужно вручную прописать в настройках линковщика.

Сокеты¶

Сокеты (англ. socket — разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.

Принципы сокетов¶

Каждый процесс может создать слушающий сокет (серверный сокет) и привязать его к какому-нибудь порту операционной системы (в UNIX непривилегированные процессы не могут использовать порты меньше 1024). Слушающий процесс обычно находится в цикле ожидания, то есть просыпается при появлении нового соединения. При этом сохраняется возможность проверить наличие соединений на данный момент, установить тайм-аут для операции и т.д.

Каждый сокет имеет свой адрес. ОС семейства UNIX могут поддерживать много типов адресов, но обязательными являются INET-адрес и UNIX-адрес. Если привязать сокет к UNIX-адресу, то будет создан специальный файл (файл сокета) по заданному пути, через который смогут сообщаться любые локальные процессы путём чтения/записи из него (см. Доменный сокет Unix). Сокеты типа INET доступны из сети и требуют выделения номера порта.


Обычно клиент явно подсоединяется к слушателю, после чего любое чтение или запись через его файловый дескриптор будут передавать данные между ним и сервером.

Основные функции¶

Общие
Socket Создать новый сокет и вернуть файловый дескриптор
Send Отправить данные по сети
Receive Получить данные из сети
Close Закрыть соединение
Серверные
Bind Связать сокет с IP-адресом и портом
Listen Объявить о желании принимать соединения. Слушает порт и ждет когда будет установлено соединение
Accept Принять запрос на установку соединения
Клиентские
Connect Установить соединение

socket()¶

Создаёт конечную точку соединения и возвращает файловый дескриптор. Принимает три аргумента:

domain указывающий семейство протоколов создаваемого сокета

  • AF_INET для сетевого протокола IPv4
  • AF_INET6 для IPv6
  • AF_UNIX для локальных сокетов (используя файл)

type

  • SOCK_STREAM (надёжная потокоориентированная служба (сервис) или потоковый сокет)
  • SOCK_DGRAM (служба датаграмм или датаграммный сокет)
  • SOCK_RAW (Сырой сокет — сырой протокол поверх сетевого уровня).

protocol

Протоколы обозначаются символьными константами с префиксом IPPROTO_* (например, IPPROTO_TCP или IPPROTO_UDP). Допускается значение protocol=0 (протокол не указан), в этом случае используется значение по умолчанию для данного вида соединений.

Функция возвращает −1 в случае ошибки. Иначе, она возвращает целое число, представляющее присвоенный дескриптор.

Пример на Python

Связывает сокет с конкретным адресом. Когда сокет создается при помощи socket(), он ассоциируется с некоторым семейством адресов, но не с конкретным адресом. До того как сокет сможет принять входящие соединения, он должен быть связан с адресом. bind() принимает три аргумента:

  1. sockfd — дескриптор, представляющий сокет при привязке
  2. serv_addr — указатель на структуру sockaddr, представляющую адрес, к которому привязываем.
  3. addrlen — поле socklen_t, представляющее длину структуры sockaddr.

Возвращает 0 при успехе и −1 при возникновении ошибки.

Пример на Python

Автоматическое получение имени хоста.

listen()¶

Подготавливает привязываемый сокет к принятию входящих соединений. Данная функция применима только к типам сокетов SOCK_STREAM и SOCK_SEQPACKET. Принимает два аргумента:

  1. sockfd — корректный дескриптор сокета.
  2. backlog — целое число, означающее число установленных соединений, которые могут быть обработаны в любой момент времени. Операционная система обычно ставит его равным максимальному значению.

После принятия соединения оно выводится из очереди. В случае успеха возвращается 0, в случае возникновения ошибки возвращается −1.

Пример на Python

accept()¶

Используется для принятия запроса на установление соединения от удаленного хоста. Принимает следующие аргументы:

  1. sockfd — дескриптор слушающего сокета на принятие соединения.
  2. cliaddr — указатель на структуру sockaddr, для принятия информации об адресе клиента.
  3. addrlen — указатель на socklen_t, определяющее размер структуры, содержащей клиентский адрес и переданной в accept(). Когда accept() возвращает некоторое значение, socklen_t указывает сколько байт структуры cliaddr использовано в данный момент.

Функция возвращает дескриптор сокета, связанный с принятым соединением, или −1 в случае возникновения ошибки.

Пример на Python

connect()¶

Устанавливает соединение с сервером.

Некоторые типы сокетов работают без установления соединения, это в основном касается UDP-сокетов. Для них соединение приобретает особое значение: цель по умолчанию для посылки и получения данных присваивается переданному адресу, позволяя использовать такие функции как send() и recv() на сокетах без установления соединения.

Загруженный сервер может отвергнуть попытку соединения, поэтому в некоторых видах программ необходимо предусмотреть повторные попытки соединения.

Возвращает целое число, представляющее код ошибки: 0 означает успешное выполнение, а −1 свидетельствует об ошибке.

Пример на Python

Передача данных¶

Для передачи данных можно пользоваться стандартными функциями чтения/записи файлов read и write, но есть специальные функции для передачи данных через сокеты:

Нужно обратить внимание, что при использовании протокола TCP (сокеты типа SOCK_STREAM) есть вероятность получить меньше данных, чем было передано, так как ещё не все данные были переданы, поэтому нужно либо дождаться, когда функция recv возвратит 0 байт, либо выставить флаг MSG_WAITALL для функции recv, что заставит её дождаться окончания передачи. Для остальных типов сокетов флаг MSG_WAITALL ничего не меняет (например, в UDP весь пакет = целое сообщение).

send, sendto — отправка данных.

Что происходит, когда серверный сокет принимает клиентские сокеты?

Я занимаюсь программированием сокетов, а серверный сокет accept() меня смущает. Я написал два сценария для серверного сокета accept() , пожалуйста, посмотрите:

  • Когда серверный сокет делает accept() , он создает новый (клиентский) сокет, который привязан к порту, который отличается от сокета сервера порта, является связующим. Таким образом, связь сокетов выполняется через новый порт связывания, а серверный сокет (только для accept() ) ожидает другого клиентского соединения на первоначально порту связи.

Я думаю, что это не совсем правильный ответ, потому что (1) порт соответствует одному процессу и (2) прием сокетов — это вопросы внутри процесса, а один процесс может иметь несколько сокетов. Поэтому я сделал второй сценарий, основанный на некоторых ответах stackoverflow:

  1. Когда серверный сокет делает accept() , он создает новый (клиентский) сокет, который не связывается с каким-либо конкретным портом, а когда клиент взаимодействует с сервером, он использует порт, который связан с сокетом сервера (кто accept() соединения) и , который клиентский сокет для фактического общения разрешен корнем (sourceIP, sourcePort, destIP, destPort) из заголовка TCP (?) на уровне передачи (это также подозрительно, потому что я думал, что сокет — это объект уровня приложения)

Этот сценарий также вызывает некоторые вопросы. Если связь сокета по-прежнему использует порт сокета сервера, т.е. Клиент отправляет некоторые сообщения на порт сокета сервера, не использует ли он серверную очередь на сервере? Я имею в виду, как можно отличать сообщения от клиента между connect() и read() or write() ? И как они могут быть разрешены для каждого клиентского сокета на сервере, БЕЗ привязки к порту?

Если один из моих сценариев правильный, ответьте на следующие вопросы? Или, возможно, я делаю два неправильных сценария, поэтому было бы очень благодарно вам за предоставление правильных ответов или, по крайней мере, некоторых соответствующих текстов для изучения.

Когда серверный сокет принимает accept(), он создает новый (клиентский) сокет, который привязан к порту, который отличается от сокета сервера порта, является связующим. Таким образом, сокет-связь выполняется через новый порт связывания, а серверный сокет (только для accept()) ожидает другого клиентского соединения на первоначально подключенном порту.

Я думаю, что это не совсем правильный ответ

Это неправильный ответ.

потому что (1) порт соответствует одному процессу

Это ничего не значит.

и (2) прием сокетов — это вопросы внутри процесса

Не так. Это вообще не означает ничего вообще.

и один процесс может иметь несколько сокетов.

Это правда, но это не влияет на то, почему ваш ответ неправильный. Причина, по которой ваш ответ неверен, заключается в том, что не используется второй порт.

Когда серверный сокет принимает accept(), он создает новый (клиентский) сокет, который не привязан к какому-либо определенному порту

Нет. Он создает второй сокет, который наследует все от серверного сокета: номер порта, размеры буфера, параметры сокета. все, кроме дескриптора файла и состояния LISTENING, и, возможно, я забыл что-то еще. Затем он устанавливает удаленный IP-порт сокета на порт клиента и помещает сокет в состояние ESTABLISHED.

и когда клиент связывается с сервером

Клиент уже общался с сервером. Вот почему мы создаем этот сокет.

он использует порт, который привязан к сокету сервера (который принимает() s-соединения), и какой клиентский сокет для фактической связи разрешен корнем (sourceIP, sourcePort, destIP, destPort) из заголовка TCP (?) на уровне передачи

Это уже произошло.

Это также подозрительно, потому что я думал, что сокет — это объект уровня приложения)

Нет, это не так. Сокет — это объект уровня ядра с дескриптором файла на уровне приложения, чтобы идентифицировать его.

Если связь сокета по-прежнему использует порт сокета-порта, то есть клиент отправляет некоторые сообщения на порт сокета-сервера, не использует ли он серверную очередь на сервере?

Нет. Очередь отставания предназначена для входящих запросов на подключение, а не для данных. Входящие данные поступают в буфер приема сокета.

Я имею в виду, как можно различать сообщения от клиента между connect() и read() или write()?

Поскольку запрос connect() устанавливает специальные биты в заголовке TCP. Последняя часть может быть объединена с данными.

И как они могут быть разрешены для каждого клиентского сокета на сервере, БЕЗ привязки к порту?

Связывание портов происходит в момент создания сокета при вызове accept() . Вы сами это изобрели. Это не реально.

Если один из моих сценариев правильный, ответьте на следующие вопросы:

Ни один из них не является правильным.

Или, возможно, я делаю два неправильных сценария, поэтому было бы очень благодарно вам предоставить правильные ответы или, по крайней мере, некоторые соответствующие тексты для изучения.

socket_accept

socket_accept — принимает соединение с сокетом.

Описание

resource socket_accept (resource socket)

Эта функция — ЭКСПЕРИМЕНТАЛЬНАЯ. Поведение, имя и всё остальное, что задокументировано для данной функции может быть изменено в будущих релизах РНР без предупреждения. Вы можете использовать эту функцию только на свой страх и риск.

После того как сокет socket создан функцией socket_create(), связан с именем с помощью socket_bind() и начал прослушивание соединений с помощью socket_listen(), эта функция будет принимать входящие соединения по этому сокету. После создания успешного соединения возвращается новый ресурс сокета, который может использоваться при взаимодействии. Если в сокете имеется очередь из нескольких соединений, используется первое соединение. Если запущенных соединений нет, socket_accept() будет блокирована, пока соединение не появится. Если socket сделан неблокирующим с помощью функции socket_set_blocking() или socket_set_nonblock(), будет возвращено FALSE.

Ресурс сокета, возвращаемый функцией socket_accept(), не может использоваться для приёма новых соединений. Однако оригинальный прослушивающий сокет socket оста1тся открытым и может использоваться повторно.

Возвращает новый ресурс сокета успехе, FALSE при ошибке. Код ошибки можно запросить функцией socket_last_error(). Этот код ошибки можно передавать в socket_strerror() для получения текстового объяснения ошибки.

Илон Маск рекомендует:  Тег figcaption
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL
Предупреждение!