aboutsummaryrefslogtreecommitdiff
path: root/documentation/content/ru/books/developers-handbook/sockets/_index.adoc
diff options
context:
space:
mode:
Diffstat (limited to 'documentation/content/ru/books/developers-handbook/sockets/_index.adoc')
-rw-r--r--documentation/content/ru/books/developers-handbook/sockets/_index.adoc909
1 files changed, 909 insertions, 0 deletions
diff --git a/documentation/content/ru/books/developers-handbook/sockets/_index.adoc b/documentation/content/ru/books/developers-handbook/sockets/_index.adoc
new file mode 100644
index 0000000000..669a53fb60
--- /dev/null
+++ b/documentation/content/ru/books/developers-handbook/sockets/_index.adoc
@@ -0,0 +1,909 @@
+---
+authors:
+ -
+ author: 'G. Adam Stanislav'
+description: 'Сокеты FreeBSD'
+next: books/developers-handbook/ipv6
+params:
+ path: /books/developers-handbook/sockets/
+prev: books/developers-handbook/partii
+showBookMenu: true
+tags: ["Sockets", "Protocols"]
+title: 'Глава 7. Сокеты'
+weight: 9
+---
+
+[[sockets]]
+= Сокеты
+:doctype: book
+:toc: macro
+:toclevels: 1
+:icons: font
+:sectnums:
+:sectnumlevels: 6
+:sectnumoffset: 7
+:partnums:
+:source-highlighter: rouge
+:experimental:
+:images-path: books/developers-handbook/
+
+ifdef::env-beastie[]
+ifdef::backend-html5[]
+:imagesdir: ../../../../images/{images-path}
+endif::[]
+ifndef::book[]
+include::shared/authors.adoc[]
+include::shared/mirrors.adoc[]
+include::shared/releases.adoc[]
+include::shared/attributes/attributes-{{% lang %}}.adoc[]
+include::shared/{{% lang %}}/teams.adoc[]
+include::shared/{{% lang %}}/mailing-lists.adoc[]
+include::shared/{{% lang %}}/urls.adoc[]
+toc::[]
+endif::[]
+ifdef::backend-pdf,backend-epub3[]
+include::../../../../../shared/asciidoctor.adoc[]
+endif::[]
+endif::[]
+
+ifndef::env-beastie[]
+toc::[]
+include::../../../../../shared/asciidoctor.adoc[]
+endif::[]
+
+[[sockets-synopsis]]
+== Обзор
+
+Сокеты BSD выводят межпроцессное взаимодействие на новый уровень. Теперь взаимодействующие процессы не обязательно должны выполняться на одной машине. Они всё ещё _могут_, но не обязаны.
+
+Не только эти процессы не обязаны выполняться на одной машине, они также могут работать под разными операционными системами. Благодаря BSD-сокетам, ваше ПО на FreeBSD может легко взаимодействовать с программой, работающей на Macintosh(R), другой — на рабочей станции Sun(TM), и ещё одной — под Windows(R) 2000, при этом все они подключены к локальной сети на основе Ethernet.
+
+Но ваше программное обеспечение может так же эффективно взаимодействовать с процессами, работающими в другом здании, на другом континенте, внутри подводной лодки или космического челнока.
+
+Он также может взаимодействовать с процессами, которые не являются частью компьютера (по крайней мере, не в строгом смысле этого слова), а таких устройств, как принтеры, цифровые камеры, медицинское оборудование. Практически со всем, что способно к цифровой коммуникации.
+
+[[sockets-diversity]]
+== Сетевое взаимодействие и разнообразие
+
+Мы уже упоминали о _разнообразии_ сетевых технологий. Множество различных систем должны взаимодействовать друг с другом. И они должны говорить на одном языке. Также они должны _понимать_ этот язык одинаковым образом.
+
+Часто думают, что _язык тела_ универсален. Но это не так. В ранней юности отец взял меня с собой в Болгарию. Мы сидели за столиком в парке Софии, когда к нам подошел продавец, предлагая купить жареный миндаль.
+
+Я тогда еще не знал болгарского, поэтому вместо словесного отказа я покачал головой из стороны в сторону — это «универсальный» язык тела для обозначения _нет_. Продавец тут же начал угощать нас миндалем.
+
+Затем я вспомнил, что мне говорили, будто в Болгарии покачивание головой из стороны в сторону означает _да_. Быстро я начал кивать головой вверх-вниз. Продавец заметил, взял свои миндалины и ушёл. Для непосвящённого наблюдателя я не изменил язык тела: я продолжал использовать движения головой — покачивание и кивание. Изменился _смысл_ языка тела. Сначала продавец и я интерпретировали одни и те же жесты как имеющие совершенно разный смысл. Мне пришлось скорректировать свою собственную интерпретацию этих жестов, чтобы продавец меня понял.
+
+То же самое и с компьютерами: одни и те же символы могут иметь разное, даже полностью противоположное значение. Поэтому, чтобы два компьютера понимали друг друга, они должны договориться не только об одном _языке_, но и об одном _толковании_ языка.
+
+[[sockets-protocols]]
+== Протоколы
+
+В то время как различные языки программирования обычно имеют сложный синтаксис и используют множество многосимвольных зарезервированных слов (что облегчает их понимание для человека-программиста), языки передачи данных, как правило, очень лаконичны. Вместо многосимвольных слов они часто используют отдельные _биты_. Для этого есть очень убедительная причина: хотя данные внутри вашего компьютера передаются со скоростью, близкой к скорости света, между двумя компьютерами они часто передаются значительно медленнее.
+
+Поскольку языки, используемые в передаче данных, настолько лаконичны, мы обычно называем их _протоколами_, а не языками.
+
+При передаче данных от одного компьютера к другому всегда используется более одного протокола. Эти протоколы _располагаются слоями_. Данные можно сравнить с внутренней частью лука: необходимо снять несколько слоёв «кожи», чтобы добраться до данных. Это лучше всего проиллюстрировано на рисунке:
+
+.Уровни протоколов
+image::layers.png[]
+
+В этом примере мы пытаемся получить изображение с веб-страницы, к которой подключены через Ethernet.
+
+Изображение состоит из необработанных данных, которые представляют собой просто последовательность значений RGB, которые наше программное обеспечение может обработать, т.е. преобразовать в изображение и отобразить на нашем мониторе.
+
+Увы, наше программное обеспечение не может определить, как организованы сырые данные: это последовательность значений RGB, последовательность интенсивностей в градациях серого или, возможно, цвета в кодировке CMYK? Представлены ли данные 8-битными квантами, или они имеют размер 16 бит, а может быть, 4 бита? Из скольких строк и столбцов состоит изображение? Должны ли определённые пиксели быть прозрачными?
+
+Я думаю, вы поняли...
+
+Чтобы наше программное обеспечение понимало, как обрабатывать сырые данные, они кодируются в формате PNG. Это мог бы быть GIF или JPEG, но выбран PNG.
+
+И PNG — это протокол.
+
+В этот момент я слышу, как некоторые из вас кричат: _"Нет, это не так! Это формат файла!"_
+
+Ну что ж, конечно, это формат файла. Но с точки зрения передачи данных, формат файла — это протокол: структура файла — это _язык_, причем лаконичный, сообщающий нашему _процессу_, как организованы данные. Следовательно, это _протокол_.
+
+Увы, если бы мы получили только PNG-файл, наше программное обеспечение столкнулось бы с серьёзной проблемой: как ему узнать, что данные представляют изображение, а не текст, звук или что-то ещё? Во-вторых, как ему определить, что изображение сохранено в формате PNG, а не GIF, JPEG или каком-либо другом формате изображений?
+
+Для получения этой информации мы используем другой протокол: HTTP. Этот протокол может точно сообщить нам, что данные представляют изображение и используют протокол PNG. Он также может сообщить некоторые другие сведения, но давайте сосредоточимся на уровнях протоколов здесь.
+
+Итак, теперь у нас есть некоторые данные, упакованные в протокол PNG, который в свою очередь упакован в протокол HTTP. Как мы получили их с сервера?
+
+Используя TCP/IP поверх Ethernet, вот как. Действительно, это ещё три протокола. Вместо того чтобы продолжать изнутри наружу, я теперь расскажу про Ethernet, просто потому что так проще объяснить остальное.
+
+Ethernet — это интересная система соединения компьютеров в _локальной сети_ (LAN). У каждого компьютера есть _карта сетевого интерфейса_ (NIC — Network Interface Card) с уникальным 48-битным идентификатором, называемым _адресом_. Не существует двух сетевых интерфейсов Ethernet в мире с одинаковым адресом.
+
+Эти сетевые карты соединены между собой. Когда один компьютер хочет связаться с другим в той же локальной сети Ethernet, он отправляет сообщение по сети. Каждая сетевая карта видит это сообщение. Однако, согласно _протоколу_ Ethernet, данные содержат адрес сетевой карты назначения (среди прочего). Таким образом, только одна из всех сетевых карт обратит на него внимание, остальные проигнорируют его.
+
+Но не все компьютеры подключены к одной сети. Тот факт, что мы получили данные через наш Ethernet, не означает, что они возникли в нашей локальной сети. Они могли попасть к нам из другой сети (которая может быть даже не на основе Ethernet), соединённой с нашей сетью через Интернет.
+
+Все данные передаются через Интернет с использованием IP, что означает _Internet Protocol_. Его основная роль — сообщать нам, откуда в мире пришли данные и куда они должны быть направлены. Он не _гарантирует_, что мы получим данные, только что мы узнаем, откуда они пришли, _если_ мы их получим.
+
+Даже если мы получим данные, IP не гарантирует, что различные фрагменты данных придут в том же порядке, в котором их отправил другой компьютер. Например, мы можем получить центр нашего изображения до того, как получим его верхний левый угол, а после — нижний правый.
+
+Это TCP (_Transmission Control Protocol_), который запрашивает у отправителя повторную отправку потерянных данных и располагает их в правильном порядке.
+
+В итоге потребовалось _пять_ различных протоколов, чтобы один компьютер мог сообщить другому, как выглядит изображение. Мы получили данные, упакованные в протокол PNG, который был упакован в протокол HTTP, который был упакован в протокол TCP, который был упакован в протокол IP, который был упакован в протокол Ethernet.
+
+О, и кстати, вероятно, на пути были задействованы и несколько других протоколов. Например, если наша локальная сеть была подключена к Интернету через дозвон, то использовался протокол PPP над модемом, который, в свою очередь, использовал один (или несколько) из различных модемных протоколов, и так далее, и так далее, и так далее...
+
+Как разработчик, вы уже должны задаваться вопросом: _"Как я должен со всем этим справляться?"_
+
+К счастью для вас, вам _не_ нужно разбираться во всём этом. Вам _придётся_ разобраться в некоторой части, но не во всей. В частности, вам не нужно беспокоиться о физическом подключении (в нашем случае Ethernet и, возможно, PPP и т.д.). Также вам не нужно разбираться с протоколом IP или протоколом TCP.
+
+Другими словами, вам не нужно ничего делать, чтобы получить данные с другого компьютера. Ну, разве что _попросить_ их, но это почти так же просто, как открыть файл.
+
+Получив данные, вам предстоит решить, что с ними делать. В нашем случае потребуется понимание протокола HTTP и структуры файла PNG.
+
+Используя аналогию, все межсетевые протоколы становятся серой зоной: не столько потому, что мы не понимаем, как они работают, а потому, что нас это больше не беспокоит. Интерфейс сокетов берёт на себя заботу об этой серой зоне:
+
+.Уровни протоколов, покрываемые сокетами
+image::slayers.png[]
+
+Нам нужно понимать только те протоколы, которые говорят нам, как _интерпретировать данные_, а не как _получать_ их от другого процесса или как _передавать_ их другому процессу.
+
+[[sockets-model]]
+== Модель сокетов
+
+Сокеты BSD построены по базовой модели UNIX(R): _Все является файлом._ Таким образом, в нашем примере сокеты позволят нам получить, образно говоря, _HTTP-файл_. Затем нам предстоит извлечь из него _PNG-файл_.
+
+Из-за сложности межсетевого взаимодействия мы не можем просто использовать системный вызов `open` или функцию `open()` в языке C. Вместо этого необходимо выполнить несколько шагов для "открытия" сокета.
+
+Однако, как только мы это сделаем, мы можем начать обращаться с _сокетом_ так же, как и с любым _файловым дескриптором_: мы можем `читать` из него, `писать` в него, передавать его через `канал` и, в конечном итоге, `закрывать` его.
+
+[[sockets-essential-functions]]
+== Основные функции сокетов
+
+В то время как FreeBSD предлагает различные функции для работы с сокетами, нам _требуется_ только четыре, чтобы "открыть" сокет. А в некоторых случаях достаточно двух.
+
+[[sockets-client-server]]
+=== Разница между клиентом и сервером
+
+Обычно одним из концов связи на основе сокетов является _сервер_, а другой — _клиент_.
+
+[[sockets-common-elements]]
+==== Общие элементы
+
+[[sockets-socket]]
+===== `socket`
+
+Функция, используемая как клиентами, так и серверами, это man:socket[2]. Она объявляется следующим образом:
+
+[.programlisting]
+....
+int socket(int domain, int type, int protocol);
+....
+
+Возвращаемое значение имеет тот же тип, что и у `open`, целое число. FreeBSD выделяет его значение из того же пула, что и дескрипторы файлов. Это позволяет обрабатывать сокеты так же, как файлы.
+
+Аргумент `domain` указывает системе, какое _семейство протоколов_ следует использовать. Существует множество семейств, некоторые из них специфичны для определённых поставщиков, другие широко распространены. Они объявлены в [.filename]#sys/socket.h#.
+
+Используйте `PF_INET` для UDP, TCP и других интернет-протоколов (IPv4).
+
+Для аргумента `type` определено пять значений, также указанных в [.filename]#sys/socket.h#. Все они начинаются с "`SOCK_`". Наиболее распространённое — `SOCK_STREAM`, которое указывает системе, что запрашивается _надёжный сервис потоковой доставки_ (это TCP при использовании с `PF_INET`).
+
+Если бы вы запросили `SOCK_DGRAM`, вы бы запросили _сервис доставки датаграмм без установления соединения_ (в нашем случае, UDP).
+
+Если вы хотите управлять низкоуровневыми протоколами (такими как IP) или даже сетевыми интерфейсами (например, Ethernet), вам потребуется указать `SOCK_RAW`.
+
+Наконец, аргумент `protocol` зависит от двух предыдущих аргументов и не всегда имеет смысл. В таком случае используйте значение `0`.
+
+[NOTE]
+.Неподключенный сокет
+====
+Нигде в функции `socket` мы не указали, к какой другой системе должны быть подключены. Наш только что созданный сокет остаётся _неподключённым_.
+
+Это сделано намеренно: если проводить аналогию с телефоном, мы только что подключили модем к телефонной линии. Мы не сказали модему совершить звонок или ответить, если телефон зазвонит.
+====
+
+[[sockets-sockaddr]]
+===== `sockaddr`
+
+Различные функции семейства сокетов ожидают адрес (или указатель, если использовать терминологию языка C) небольшой области памяти. Различные объявления на языке C в файле [.filename]#sys/socket.h# ссылаются на неё как на `struct sockaddr`. Эта структура объявлена в том же файле:
+
+[.programlisting]
+....
+/*
+ * Structure used by kernel to store most
+ * addresses.
+ */
+struct sockaddr {
+ unsigned char sa_len; /* total length */
+ sa_family_t sa_family; /* address family */
+ char sa_data[14]; /* actually longer; address value */
+};
+#define SOCK_MAXADDRLEN 255 /* longest possible addresses */
+....
+
+Обратите внимание на _неопределённость_, с которой объявлено поле `sa_data` — просто как массив из `14` байт, с комментарием, намекающим, что их может быть больше `14`.
+
+Эта неопределенность вполне преднамеренна. Сокеты — это очень мощный интерфейс. Хотя большинство людей, возможно, считают их не более чем интерфейсом для Интернета — и большинство приложений, вероятно, используют их именно для этого в наши дни — сокеты могут быть использованы практически для _любого_ вида межпроцессного взаимодействия, из которых Интернет (или, точнее, IP) — лишь один из них.
+
+[.filename]#sys/socket.h# ссылается на различные типы протоколов, с которыми работают сокеты, как на _семейства адресов_, и перечисляет их непосредственно перед определением `sockaddr`:
+
+[.programlisting]
+....
+/*
+ * Address families.
+ */
+#define AF_UNSPEC 0 /* unspecified */
+#define AF_LOCAL 1 /* local to host (pipes, portals) */
+#define AF_UNIX AF_LOCAL /* backward compatibility */
+#define AF_INET 2 /* internetwork: UDP, TCP, etc. */
+#define AF_IMPLINK 3 /* arpanet imp addresses */
+#define AF_PUP 4 /* pup protocols: e.g. BSP */
+#define AF_CHAOS 5 /* mit CHAOS protocols */
+#define AF_NS 6 /* XEROX NS protocols */
+#define AF_ISO 7 /* ISO protocols */
+#define AF_OSI AF_ISO
+#define AF_ECMA 8 /* European computer manufacturers */
+#define AF_DATAKIT 9 /* datakit protocols */
+#define AF_CCITT 10 /* CCITT protocols, X.25 etc */
+#define AF_SNA 11 /* IBM SNA */
+#define AF_DECnet 12 /* DECnet */
+#define AF_DLI 13 /* DEC Direct data link interface */
+#define AF_LAT 14 /* LAT */
+#define AF_HYLINK 15 /* NSC Hyperchannel */
+#define AF_APPLETALK 16 /* Apple Talk */
+#define AF_ROUTE 17 /* Internal Routing Protocol */
+#define AF_LINK 18 /* Link layer interface */
+#define pseudo_AF_XTP 19 /* eXpress Transfer Protocol (no AF) */
+#define AF_COIP 20 /* connection-oriented IP, aka ST II */
+#define AF_CNT 21 /* Computer Network Technology */
+#define pseudo_AF_RTIP 22 /* Help Identify RTIP packets */
+#define AF_IPX 23 /* Novell Internet Protocol */
+#define AF_SIP 24 /* Simple Internet Protocol */
+#define pseudo_AF_PIP 25 /* Help Identify PIP packets */
+#define AF_ISDN 26 /* Integrated Services Digital Network*/
+#define AF_E164 AF_ISDN /* CCITT E.164 recommendation */
+#define pseudo_AF_KEY 27 /* Internal key-management function */
+#define AF_INET6 28 /* IPv6 */
+#define AF_NATM 29 /* native ATM access */
+#define AF_ATM 30 /* ATM */
+#define pseudo_AF_HDRCMPLT 31 /* Used by BPF to not rewrite headers
+ * in interface output routine
+ */
+#define AF_NETGRAPH 32 /* Netgraph sockets */
+#define AF_SLOW 33 /* 802.3ad slow protocol */
+#define AF_SCLUSTER 34 /* Sitara cluster protocol */
+#define AF_ARP 35
+#define AF_BLUETOOTH 36 /* Bluetooth sockets */
+#define AF_MAX 37
+....
+
+Используемый для IP — это AF_INET. Это символ для константы `2`.
+
+Это _семейство адресов_, указанное в поле `sa_family` структуры `sockaddr`, определяет, как именно будут использоваться нечетко названные байты `sa_data`.
+
+В частности, когда _семейство адресов_ — AF_INET, можно использовать `struct sockaddr_in` из [.filename]#netinet/in.h# везде, где ожидается `sockaddr`:
+
+[.programlisting]
+....
+/*
+ * Socket address, internet style.
+ */
+struct sockaddr_in {
+ uint8_t sin_len;
+ sa_family_t sin_family;
+ in_port_t sin_port;
+ struct in_addr sin_addr;
+ char sin_zero[8];
+};
+....
+
+Мы можем визуализировать его организацию следующим образом:
+
+.Структура `sockaddr_in`
+image::sain.png[]
+
+Три важных поля — это `sin_family`, которое находится в байте 1 структуры, `sin_port`, 16-битное значение, расположенное в байтах 2 и 3, и `sin_addr`, 32-битное целочисленное представление IP-адреса, хранящееся в байтах 4–7.
+
+Теперь попробуем заполнить его. Предположим, мы пытаемся написать клиент для протокола _daytime_, который просто указывает, что его сервер записывает текстовую строку с текущей датой и временем в порт 13. Мы хотим использовать TCP/IP, поэтому нам нужно указать `AF_INET` в поле семейства адресов. `AF_INET` определен как `2`. Давайте используем IP-адрес `192.43.244.18`, который является сервером времени федерального правительства США (`time.nist.gov`).
+
+.Конкретный пример sockaddr_in
+image::sainfill.png[]
+
+Кстати, поле `sin_addr` объявлено как имеющее тип `struct in_addr`, который определён в [.filename]#netinet/in.h#:
+
+[.programlisting]
+....
+/*
+ * Internet address (a structure for historical reasons)
+ */
+struct in_addr {
+ in_addr_t s_addr;
+};
+....
+
+В дополнение, `in_addr_t` является 32-битным целым числом.
+
+`192.43.244.18` — это просто удобная форма записи 32-битного целого числа, в которой перечисляются все его 8-битные байты, начиная с _старшего_.
+
+До сих пор мы рассматривали `sockaddr` как абстракцию. Наш компьютер не хранит `short` целые числа как единую 16-битную сущность, а как последовательность 2 байт. Аналогично, он хранит 32-битные целые числа как последовательность 4 байт.
+
+Предположим, мы написали что-то вроде этого:
+
+[.programlisting]
+....
+sa.sin_family = AF_INET;
+sa.sin_port = 13;
+sa.sin_addr.s_addr = (((((192 << 8) | 43) << 8) | 244) << 8) | 18;
+....
+
+Как будет выглядеть результат?
+
+Ну, это, конечно, зависит от многого. На компьютере с процессором Pentium(R) или другим на базе x86 это будет выглядеть так:
+
+.`sockaddr_in` в системе с архитектурой Intel
+image::sainlsb.png[]
+
+На другой системе это может выглядеть так:
+
+.`sockaddr_in` в системе с порядком байтов от старшего к младшему
+image::sainmsb.png[]
+
+И на PDP это может выглядеть иначе. Однако два приведённых выше варианта являются наиболее распространёнными на сегодняшний день.
+
+Обычно, стремясь писать переносимый код, программисты делают вид, что этих различий не существует. И им это сходит с рук (за исключением случаев, когда они пишут на ассемблере). Увы, при программировании сокетов так легко отделаться не получится.
+
+Почему?
+
+Потому что при обмене данными с другим компьютером вы обычно не знаете, хранит ли он данные, начиная со _старшего байта_ (MSB) или с _младшего байта_ (LSB).
+
+Вы можете задаться вопросом: _"Значит, сокеты не будут это делать за меня?"_
+
+Не будут.
+
+Хотя этот ответ может сначала вас удивить, помните, что общий интерфейс сокетов понимает только поля `sa_len` и `sa_family` структуры `sockaddr`. Вам не нужно беспокоиться о порядке байтов (конечно, в FreeBSD `sa_family` занимает всего 1 байт, но многие другие UNIX(R)-системы не имеют `sa_len` и используют 2 байта для `sa_family`, ожидая данные в том порядке, который является родным для компьютера).
+
+Но остальные данные — это просто `sa_data[14]` с точки зрения сокетов. В зависимости от _семейства адресов_ сокеты просто передают эти данные по назначению.
+
+Действительно, когда мы указываем номер порта, это делается для того, чтобы другая компьютерная система знала, какую службу мы запрашиваем. И, когда мы выступаем в роли сервера, мы считываем номер порта, чтобы понять, какую службу ожидает от нас другая система. В любом случае, сокетам нужно лишь передать номер порта в качестве данных. Они никак его не интерпретируют.
+
+Аналогично, мы указываем IP-адрес, чтобы сообщить всем на пути, куда отправлять наши данные. Сокеты, опять же, просто пересылают их как данные.
+
+Вот почему мы (программисты, а не сокеты) должны различать порядок байтов, используемый нашим компьютером, и условный порядок байтов для отправки данных на другой компьютер.
+
+Мы будем называть порядок байтов, который использует наш компьютер, _порядком байтов хоста_ или просто _хост-порядком_.
+
+Существует соглашение о передаче многобайтовых данных по IP _старшим байтом вперёд_. Это мы будем называть _порядком байтов сети_ или просто _сетевым порядком_.
+
+Вот, если бы мы скомпилировали приведённый выше код для компьютера на базе Intel, наш _порядок байтов хоста_ выдал бы:
+
+.Порядок байтов на хосте в системе Intel
+image::sainlsb.png[]
+
+Но порядок байтов в _сетевом формате_ требует, чтобы данные хранились начиная со старшего байта (MSB):
+
+.Порядок байтов в сети
+image::sainmsb.png[]
+
+К сожалению, наш _порядок хоста_ полностью противоположен _порядку сети_.
+
+У нас есть несколько способов решения этой проблемы. Один из них — _инвертировать_ значения в нашем коде:
+
+[.programlisting]
+....
+sa.sin_family = AF_INET;
+sa.sin_port = 13 << 8;
+sa.sin_addr.s_addr = (((((18 << 8) | 244) << 8) | 43) << 8) | 192;
+....
+
+Это _обманет_ наш компилятор, заставив его сохранить данные в _порядке байтов сети_. В некоторых случаях это именно тот способ, который нужен (например, при программировании на ассемблере). Однако в большинстве случаев это может вызвать проблему.
+
+Предположим, вы написали программу на C, использующую сокеты. Вы знаете, что она будет работать на Pentium(R), поэтому вводите все константы в обратном порядке и приводите их к _порядку байтов сети_. Она работает хорошо.
+
+Затем, однажды, ваш надежный старый Pentium(R) превращается в ржавый старый Pentium(R). Вы заменяете его системой, у которой _порядок байтов хоста_ совпадает с _сетевым порядком байтов_. Вам нужно перекомпилировать все ваше программное обеспечение. Все ваши программы продолжают работать хорошо, кроме той одной программы, которую вы написали.
+
+Вы уже забыли, что принудительно задали все свои константы противоположными _порядку хоста_. Вы проводите некоторое время, яростно рвя на себе волосы, взывая ко всем известным вам богам (и к некоторым, которых вы придумали), стуча нерф-битой по монитору и выполняя прочие традиционные ритуалы в попытке понять, почему то, что работало так хорошо, внезапно перестало работать вообще.
+
+В конце концов, вы разбираетесь в проблеме, произносите пару крепких словечек и начинаете переписывать свой код.
+
+К счастью, вы не первый, кто столкнулся с этой проблемой. Кто-то уже создал функции man:htons[3] и man:htonl[3] на языке C для преобразования `short` и `long` соответственно из _порядка байтов хоста_ в _порядок байтов сети_, а также функции man:ntohs[3] и man:ntohl[3] на языке C для обратного преобразования.
+
+На системах с порядком _старший байт первый_ эти функции не выполняют никаких действий. На системах с порядком _младший байт первый_ они преобразуют значения в правильный порядок.
+
+Итак, независимо от того, на какой системе компилируется ваше программное обеспечение, ваши данные будут в правильном порядке, если вы используете эти функции.
+
+[[sockets-client-functions]]
+==== Функции клиента
+
+Обычно клиент инициирует подключение к серверу. Клиент знает, к какому серверу он собирается обратиться: он знает его IP-адрес и _порт_, на котором работает сервер. Это похоже на то, как вы поднимаете трубку и набираете номер (_адрес_), а затем, когда кто-то отвечает, просите соединить со специалистом по непонятным символам (_порт_).
+
+[[sockets-connect]]
+===== `connect`
+
+Как только клиент создал сокет, ему нужно подключить его к определённому порту на удалённой системе. Для этого используется man:connect[2]:
+
+[.programlisting]
+....
+int connect(int s, const struct sockaddr *name, socklen_t namelen);
+....
+
+Аргумент `s` — это сокет, то есть значение, возвращаемое функцией `socket`. Аргумент `name` — это указатель на структуру `sockaddr`, которую мы подробно обсуждали. Наконец, `namelen` сообщает системе, сколько байт находится в нашей структуре `sockaddr`.
+
+Если `connect` завершается успешно, он возвращает `0`. В противном случае возвращается `-1`, а код ошибки сохраняется в `errno`.
+
+Существует множество причин, по которым `connect` может завершиться неудачей. Например, при попытке подключения к интернету, IP-адрес может не существовать, быть недоступен, перегружен или на указанном порту может не быть сервера. Или же подключение может быть явно _отклонено_ по определённым причинам.
+
+[[sockets-first-client]]
+===== Наш первый клиент
+
+Теперь мы знаем достаточно, чтобы написать очень простого клиента, который получит текущее время от `192.43.244.18` и выведет его в [.filename]#stdout#.
+
+[.programlisting]
+....
+/*
+ * daytime.c
+ *
+ * Programmed by G. Adam Stanislav
+ */
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+
+int main() {
+ int s, bytes;
+ struct sockaddr_in sa;
+ char buffer[BUFSIZ+1];
+
+ if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
+ perror("socket");
+ return 1;
+ }
+
+ memset(&sa, '\0', sizeof(sa));
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(13);
+ sa.sin_addr.s_addr = htonl((((((192 << 8) | 43) << 8) | 244) << 8) | 18);
+ if (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
+ perror("connect");
+ close(s);
+ return 2;
+ }
+
+ while ((bytes = read(s, buffer, BUFSIZ)) > 0)
+ write(1, buffer, bytes);
+
+ close(s);
+ return 0;
+}
+....
+
+Вперед! Введите это в вашем редакторе, сохраните как [.filename]#daytime.c#, затем скомпилируйте и запустите:
+
+[source, bash]
+....
+% cc -O3 -o daytime daytime.c
+% ./daytime
+
+52079 01-06-19 02:29:25 50 0 1 543.9 UTC(NIST) *
+%
+....
+
+В данном случае дата была 19 июня 2001 года, время — 02:29:25 UTC. Естественно, ваши результаты могут отличаться.
+
+[[sockets-server-functions]]
+==== Функции сервера
+
+Типичный сервер не инициирует соединение. Вместо этого он ожидает, когда клиент обратится к нему и запросит услуги. Он не знает, когда клиент обратится, ни сколько клиентов обратится. В один момент он может просто спокойно ожидать, а в следующий момент он может оказаться перегруженным запросами от множества клиентов, обращающихся одновременно.
+
+Интерфейс сокетов предоставляет три основные функции для обработки этого.
+
+[[sockets-bind]]
+===== `bind`
+
+Порты подобны внутренним номерам телефонной линии: после набора основного номера вы набираете внутренний номер, чтобы связаться с конкретным человеком или отделом.
+
+Существует 65535 IP-портов, но сервер обычно обрабатывает запросы, поступающие только на один из них. Это как сказать оператору телефонной комнаты, что мы сейчас на месте и готовы отвечать на звонки по определённому внутреннему номеру. Мы используем man:bind[2], чтобы указать сокетам, на каком порту мы хотим обслуживать запросы.
+
+[.programlisting]
+....
+int bind(int s, const struct sockaddr *addr, socklen_t addrlen);
+....
+
+Помимо указания порта в `addr`, сервер может включать свой IP-адрес. Однако он может просто использовать символическую константу INADDR_ANY, чтобы указать, что будет обслуживать все запросы на указанный порт, независимо от его IP-адреса. Этот символ, наряду с несколькими аналогичными, объявлен в [.filename]#netinet/in.h#
+
+[.programlisting]
+....
+#define INADDR_ANY (u_int32_t)0x00000000
+....
+
+Предположим, мы пишем сервер для протокола _daytime_ поверх TCP/IP. Напомним, что он использует порт 13. Наша структура `sockaddr_in` будет выглядеть так:
+
+.Пример sockaddr_in сервера
+image::sainserv.png[]
+
+[[sockets-listen]]
+===== `listen`
+
+Продолжая аналогию с офисным телефоном, после того как вы сообщили оператору АТС, на каком внутреннем номере вы будете находиться, вы заходите в свой офис и убеждаетесь, что ваш телефон подключен и звонок включен. Кроме того, вы активируете функцию ожидания вызова, чтобы слышать звонок даже во время разговора с кем-то.
+
+Сервер обеспечивает все это с помощью функции man:listen[2].
+
+[.programlisting]
+....
+int listen(int s, int backlog);
+....
+
+Здесь переменная `backlog` указывает сокетам, сколько входящих запросов принимать, пока вы заняты обработкой последнего запроса. Другими словами, она определяет максимальный размер очереди ожидающих соединений.
+
+[[sockets-accept]]
+===== `accept`
+
+После того как вы услышите телефонный звонок, вы принимаете вызов, отвечая на звонок. Теперь вы установили соединение с вашим клиентом. Это соединение остается активным, пока вы или ваш клиент не повесите трубку.
+
+Сервер принимает соединение, используя функцию man:accept[2].
+
+[.programlisting]
+....
+int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
+....
+
+Обратите внимание, что в этот раз `addrlen` является указателем. Это необходимо, потому что в данном случае именно сокет заполняет структуру `addr` — `sockaddr_in`.
+
+Возвращаемое значение является целым числом. Действительно, `accept` возвращает _новый сокет_. Этот новый сокет будет использоваться для обмена данными с клиентом.
+
+Что происходит со старым сокетом? Он продолжает ожидать новые запросы (помните переменную `backlog`, которую мы передали в `listen`?), пока мы не закроем его (`close`).
+
+Теперь новый сокет предназначен только для обмена данными. Он полностью подключен. Мы не можем снова передать его в `listen`, чтобы принимать дополнительные соединения.
+
+[[sockets-first-server]]
+===== Наш первый сервер
+
+Наш первый сервер будет несколько сложнее, чем первый клиент: нам нужно не только использовать больше функций сокетов, но и написать его как демон.
+
+Это лучше всего достигается созданием _дочернего процесса_ после привязки порта. Затем основной процесс завершается и возвращает управление оболочке (или любой другой программе, которая его вызвала).
+
+Дочерний процесс вызывает `listen`, затем запускает бесконечный цикл, который принимает соединение, обслуживает его и в конечном итоге закрывает свой сокет.
+
+[.programlisting]
+....
+/*
+ * daytimed - a port 13 server
+ *
+ * Programmed by G. Adam Stanislav
+ * June 19, 2001
+ */
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#define BACKLOG 4
+
+int main() {
+ int s, c;
+ socklen_t b;
+ struct sockaddr_in sa;
+ time_t t;
+ struct tm *tm;
+ FILE *client;
+
+ if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
+ perror("socket");
+ return 1;
+ }
+
+ memset(&sa, '\0', sizeof(sa));
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(13);
+
+ if (INADDR_ANY)
+ sa.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if (bind(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
+ perror("bind");
+ return 2;
+ }
+
+ switch (fork()) {
+ case -1:
+ perror("fork");
+ return 3;
+ default:
+ close(s);
+ return 0;
+ case 0:
+ break;
+ }
+
+ listen(s, BACKLOG);
+
+ for (;;) {
+ b = sizeof sa;
+
+ if ((c = accept(s, (struct sockaddr *)&sa, &b)) < 0) {
+ perror("daytimed accept");
+ return 4;
+ }
+
+ if ((client = fdopen(c, "w")) == NULL) {
+ perror("daytimed fdopen");
+ return 5;
+ }
+
+ if ((t = time(NULL)) < 0) {
+ perror("daytimed time");
+ return 6;
+ }
+
+ tm = gmtime(&t);
+ fprintf(client, "%.4i-%.2i-%.2iT%.2i:%.2i:%.2iZ\n",
+ tm->tm_year + 1900,
+ tm->tm_mon + 1,
+ tm->tm_mday,
+ tm->tm_hour,
+ tm->tm_min,
+ tm->tm_sec);
+
+ fclose(client);
+ }
+}
+....
+
+Начинаем с создания сокета. Затем заполняем структуру `sockaddr_in` в `sa`. Обратите внимание на условное использование INADDR_ANY:
+
+[.programlisting]
+....
+if (INADDR_ANY)
+ sa.sin_addr.s_addr = htonl(INADDR_ANY);
+....
+
+Его значение равно `0`. Поскольку мы только что использовали `bzero` для всей структуры, будет избыточным снова устанавливать его в `0`. Но если мы перенесем наш код на другую систему, где INADDR_ANY, возможно, не равен нулю, нам нужно будет присвоить его `sa.sin_addr.s_addr`. Большинство современных компиляторов C достаточно умны, чтобы заметить, что INADDR_ANY — это константа. Пока она равна нулю, они оптимизируют все условное выражение из кода.
+
+После успешного вызова `bind` мы готовы стать _демоном_: используем `fork` для создания дочернего процесса. В обоих процессах, родительском и дочернем, переменная `s` является нашим сокетом. Родительскому процессу он больше не нужен, поэтому он вызывает `close`, затем возвращает `0`, чтобы сообщить своему родителю об успешном завершении.
+
+Между тем, дочерний процесс продолжает работать в фоновом режиме. Он вызывает `listen` и устанавливает размер очереди ожидания (`backlog`) равным `4`. Здесь не требуется большое значение, так как _daytime_ — это не протокол, который часто запрашивают клиенты, и, кроме того, он может мгновенно обрабатывать каждый запрос.
+
+Наконец, демон запускает бесконечный цикл, который выполняет следующие шаги:
+
+[.procedure]
+. Вызовите `accept`. Он ожидает здесь, пока клиент не свяжется с ним. В этот момент он получает новый сокет, `c`, который можно использовать для обмена данными с этим конкретным клиентом.
+. Он использует функцию C `fdopen` для преобразования сокета из низкоуровневого _дескриптора файла_ в указатель типа `FILE` в стиле C. Это позволит в дальнейшем использовать `fprintf`.
+. Он проверяет время и выводит его в формате _ISO 8601_ в «файл» `client`. Затем он использует `fclose` для закрытия файла. Это также автоматически закроет сокет.
+
+Мы можем _обобщить_ это и использовать в качестве модели для многих других серверов:
+
+.Последовательный Сервер
+image::serv.png[]
+
+Эта блок-схема подходит для _последовательных серверов_, то есть серверов, которые могут обслуживать одного клиента за раз, как это было возможно с нашим _daytime_ сервером. Это возможно только в тех случаях, когда между клиентом и сервером не происходит реального "диалога": как только сервер обнаруживает подключение клиента, он отправляет некоторые данные и закрывает соединение. Вся операция может занять наносекунды, и она завершена.
+
+Преимущество этой блок-схемы в том, что, за исключением короткого момента после того, как родительский процесс выполняет ``fork`` и до его завершения, всегда активен только один _процесс_: Наш сервер не занимает много памяти и других системных ресурсов.
+
+Обратите внимание, что мы добавили _инициализацию демона_ в нашу блок-схему. Нам не нужно было инициализировать собственный демон, но это подходящее место в потоке выполнения программы для настройки обработчиков `signal`, открытия необходимых файлов и т. д.
+
+Почти все элементы блок-схемы могут быть использованы буквально на множестве различных серверов. Элемент _serve_ является исключением. Мы рассматриваем его как _"чёрный ящик"_, то есть нечто, что вы проектируете специально для своего сервера и просто "подключаете к остальной системе."
+
+Не все протоколы настолько просты. Многие получают запрос от клиента, отвечают на него, а затем получают ещё один запрос от того же клиента. В результате, они не знают заранее, как долго будут обслуживать клиента. Такие серверы обычно запускают новый процесс для каждого клиента. Пока новый процесс обслуживает своего клиента, демон может продолжать прослушивать новые подключения.
+
+Теперь сохраните приведённый исходный код в файл [.filename]#daytimed.c# (обычно имена демонов оканчиваются буквой `d`). После компиляции попробуйте запустить его:
+
+[source, bash]
+....
+% ./daytimed
+bind: Permission denied
+%
+....
+
+Что произошло? Как вы помните, протокол _daytime_ использует порт 13. Однако все порты ниже 1024 зарезервированы для суперпользователя (в противном случае любой мог бы запустить демон, притворяясь, что обслуживает часто используемый порт, создавая угрозу безопасности).
+
+Попробуйте снова, на этот раз как суперпользователь:
+
+[source, bash]
+....
+# ./daytimed
+#
+....
+
+Что... Ничего? Давайте попробуем еще раз:
+
+[source, bash]
+....
+# ./daytimed
+
+bind: Address already in use
+#
+....
+
+Каждый порт может быть связан только одной программой одновременно. Наша первая попытка действительно была успешной: она запустила дочерний демон и завершилась без ошибок. Он продолжает работать и будет работать до тех пор, пока вы его не завершите командой kill, пока какой-либо из его системных вызовов не завершится с ошибкой или пока вы не перезагрузите систему.
+
+Хорошо, мы знаем, что он работает в фоновом режиме. Но работает ли он? Как мы можем убедиться, что это настоящий сервер _daytime_? Просто:
+
+[source, bash]
+....
+% telnet localhost 13
+
+Trying ::1...
+telnet: connect to address ::1: Connection refused
+Trying 127.0.0.1...
+Connected to localhost.
+Escape character is '^]'.
+2001-06-19T21:04:42Z
+Connection closed by foreign host.
+%
+....
+
+telnet попробовал использовать новый IPv6, но не смог. Затем он повторил попытку с IPv4, и это удалось. Демон работает.
+
+Если у вас есть доступ к другой UNIX(R)-системе через telnet, вы можете использовать её для проверки удалённого доступа к серверу. Мой компьютер не имеет статического IP-адреса, поэтому я сделал следующее:
+
+[source, bash]
+....
+% who
+
+whizkid ttyp0 Jun 19 16:59 (216.127.220.143)
+xxx ttyp1 Jun 19 16:06 (xx.xx.xx.xx)
+% telnet 216.127.220.143 13
+
+Trying 216.127.220.143...
+Connected to r47.bfm.org.
+Escape character is '^]'.
+2001-06-19T21:31:11Z
+Connection closed by foreign host.
+%
+....
+
+Снова, это сработало. Сработает ли это с использованием доменного имени?
+
+[source, bash]
+....
+% telnet r47.bfm.org 13
+
+Trying 216.127.220.143...
+Connected to r47.bfm.org.
+Escape character is '^]'.
+2001-06-19T21:31:40Z
+Connection closed by foreign host.
+%
+....
+
+Кстати, telnet выводит сообщение _Connection closed by foreign host_ после того, как наш демон закрыл сокет. Это показывает, что использование `fclose(client);` в нашем коде действительно работает, как заявлено.
+
+[[sockets-helper-functions]]
+== Вспомогательные функции
+
+Библиотека C в FreeBSD содержит множество вспомогательных функций для программирования сокетов. Например, в нашем примере клиента мы жестко прописали IP-адрес `time.nist.gov`. Но мы не всегда знаем IP-адрес. Даже если знаем, наше программное обеспечение будет более гибким, если позволит пользователю ввести IP-адрес или даже доменное имя.
+
+[[sockets-gethostbyname]]
+=== `gethostbyname`
+
+Хотя нет возможности передать имя домена напрямую в какие-либо функции сокетов, стандартная библиотека C в FreeBSD предоставляет функции man:gethostbyname[3] и man:gethostbyname2[3], объявленные в [.filename]#netdb.h#.
+
+[.programlisting]
+....
+struct hostent * gethostbyname(const char *name);
+struct hostent * gethostbyname2(const char *name, int af);
+....
+
+Оба возвращают указатель на структуру `hostent`, содержащую много информации о домене. Для наших целей поле `h_addr_list[0]` структуры указывает на `h_length` байтов правильного адреса, уже сохранённого в _порядке байтов сети_.
+
+Это позволяет нам создать гораздо более гибкую — и гораздо более полезную — версию нашей программы daytime:
+
+[.programlisting]
+....
+/*
+ * daytime.c
+ *
+ * Programmed by G. Adam Stanislav
+ * 19 June 2001
+ */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+int main(int argc, char *argv[]) {
+ int s, bytes;
+ struct sockaddr_in sa;
+ struct hostent *he;
+ char buf[BUFSIZ+1];
+ char *host;
+
+ if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
+ perror("socket");
+ return 1;
+ }
+
+ memset(&sa, '\0', sizeof(sa));
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(13);
+
+ host = (argc > 1) ? argv[1] : "time.nist.gov";
+
+ if ((he = gethostbyname(host)) == NULL) {
+ herror(host);
+ return 2;
+ }
+
+ memcpy(&sa.sin_addr, he->h_addr_list[0], he->h_length);
+
+ if (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
+ perror("connect");
+ return 3;
+ }
+
+ while ((bytes = read(s, buf, BUFSIZ)) > 0)
+ write(1, buf, bytes);
+
+ close(s);
+ return 0;
+}
+....
+
+Теперь мы можем ввести доменное имя (или IP-адрес, это работает в обоих направлениях) в командной строке, и программа попытается подключиться к его серверу _daytime_. В противном случае, по умолчанию будет использоваться `time.nist.gov`. Однако даже в этом случае мы будем использовать `gethostbyname` вместо жесткого задания `192.43.244.18`. Таким образом, даже если его IP-адрес изменится в будущем, мы всё равно сможем его найти.
+
+Поскольку получение времени от локального сервера занимает практически нулевое время, вы можете запустить daytime дважды подряд: сначала для получения времени от `time.nist.gov`, а затем от вашей собственной системы. После этого вы можете сравнить результаты и увидеть, насколько точны часы вашей системы:
+
+[source, bash]
+....
+% daytime ; daytime localhost
+
+52080 01-06-20 04:02:33 50 0 0 390.2 UTC(NIST) *
+2001-06-20T04:02:35Z
+%
+....
+
+Как видно, моя система опережала время NIST на две секунды.
+
+[[sockets-getservbyname]]
+=== `getservbyname`
+
+Иногда вы можете быть не уверены, какой порт использует определённая служба. В таких случаях очень полезна функция man:getservbyname[3], также объявленная в [.filename]#netdb.h#:
+
+[.programlisting]
+....
+struct servent * getservbyname(const char *name, const char *proto);
+....
+
+Структура `servent` содержит `s_port`, в котором находится соответствующий порт, уже в _порядке байтов сети_.
+
+Если бы мы не знали правильный порт для службы _daytime_, мы могли бы найти его следующим образом:
+
+[.programlisting]
+....
+struct servent *se;
+ ...
+ if ((se = getservbyname("daytime", "tcp")) == NULL {
+ fprintf(stderr, "Cannot determine which port to use.\n");
+ return 7;
+ }
+ sa.sin_port = se->s_port;
+....
+
+Обычно порт известен. Но если вы разрабатываете новый протокол, вы можете тестировать его на неофициальном порту. Когда-нибудь вы зарегистрируете протокол и его порт (если не где-то ещё, то хотя бы в вашем [.filename]#/etc/services#, где `getservbyname` ищет). Вместо возврата ошибки в приведённом выше коде вы просто используете временный номер порта. Как только вы добавите протокол в [.filename]#/etc/services#, ваше программное обеспечение найдёт его порт без необходимости переписывать код.
+
+[[sockets-concurrent-servers]]
+== Многозадачные серверы
+
+В отличие от последовательного сервера, _многозадачный сервер_ должен иметь возможность обслуживать более одного клиента одновременно. Например, _сервер чата_ может обслуживать конкретного клиента часами — он не может ждать, пока закончит обслуживать текущего клиента, прежде чем перейти к следующему.
+
+Это требует значительных изменений в нашей блок-схеме:
+
+.Многозадачный сервер
+image::serv2.png[]
+
+Мы переместили _службу_ из _демона_ в её собственный _серверный процесс_. Однако, поскольку каждый дочерний процесс наследует все открытые файлы (а сокет обрабатывается так же, как файл), новый процесс наследует не только _"принятый дескриптор"_, т.е. сокет, возвращённый вызовом `accept`, но и _главный сокет_, т.е. тот, который был открыт главным процессом в самом начале.
+
+Однако _серверному процессу_ этот сокет не нужен, и он должен немедленно вызвать ему `close`. Аналогично, _демону_ больше не нужен _сокет, принятый вызовом accept_, и он не только должен, но и _обязан_ вызвать ему `close` — в противном случае рано или поздно закончатся доступные _файловые дескрипторы_.
+
+После завершения обслуживания _серверного процесса_ он должен закрыть _принятый сокет_. Вместо возврата к `accept`, процесс теперь завершается.
+
+В UNIX(R) процесс на самом деле не _завершается_. Вместо этого он _возвращается_ к своему родителю. Обычно родительский процесс ``ждёт`` (wait) завершения своего дочернего процесса и получает возвращаемое значение. Однако наш _демон-процесс_ не может просто остановиться и ждать. Это бы свело на нет всю цель создания дополнительных процессов. Но если он никогда не выполняет `wait`, его дочерние процессы станут _зомби_ — более не функционирующими, но всё ещё бродящими вокруг.
+
+По этой причине _демону_ необходимо установить _обработчики сигналов_ на этапе _инициализации демона_. Как минимум, должен обрабатываться сигнал SIGCHLD, чтобы демон мог удалять зомби-процессы из системы и освобождать занимаемые ими системные ресурсы.
+
+Вот почему наша блок-схема теперь содержит блок _обработки сигналов_, который не соединен с другими блоками. Кстати, многие серверы также обрабатывают SIGHUP и обычно интерпретируют его как сигнал от суперпользователя, указывающий на необходимость перечитать конфигурационные файлы. Это позволяет нам изменять настройки без необходимости завершать и перезапускать эти серверы.