<?xml version="1.0" encoding="windows-1251"?>
<FictionBook xmlns="http://www.gribuser.ru/xml/fictionbook/2.0" xmlns:l="http://www.w3.org/1999/xlink">
 <stylesheet type="text/css">
                                      section section section title { page-break-before: auto }
 image + p { page-break-before: avoid; margin-bottom: 1em; }
 p &gt; code { font-size: 83% }
</stylesheet>
 <description>
  <title-info>
   <genre>comp_osnet</genre>
   <author>
    <first-name>Уильям</first-name>
    <middle-name>Ричард</middle-name>
    <last-name>Стивенс</last-name>
   </author>
   <author>
    <first-name>Билл</first-name>
    <last-name>Феннер</last-name>
   </author>
   <author>
    <first-name>Эндрю</first-name>
    <middle-name>М.</middle-name>
    <last-name>Рудофф</last-name>
   </author>
   <book-title>UNIX: разработка сетевых приложений</book-title>
   <annotation>
    <p>Новое издание книги, посвященной созданию веб-серверов, клиент-серверных приложений или любого другого сетевого программного обеспечения в операционной системе UNIX, — классическое руководство по сетевым программным интерфейсам, в частности сокетам. Оно основано на трудах Уильяма Стивенса и полностью переработано и обновлено двумя ведущими экспертами по сетевому программированию. В книгу включено описание ключевых современных стандартов, реализаций и методов, она содержит большое количество иллюстрирующих примеров и может использоваться как учебник по программированию в сетях, так и в качестве справочника для опытных программистов.</p>
   </annotation>
   <date>2007</date>
   <coverpage>
    <image l:href="#img_0.jpeg"/></coverpage>
   <lang>ru</lang>
   <src-lang>en</src-lang>
   <translator>
    <first-name>Д.</first-name>
    <last-name>Солнышков</last-name>
   </translator>
  </title-info>
  <src-title-info>
   <genre>comp_osnet</genre>
   <author>
    <first-name>William</first-name>
    <middle-name>Richard</middle-name>
    <last-name>Stevens</last-name>
   </author>
   <author>
    <first-name>Bill</first-name>
    <last-name>Fenner</last-name>
   </author>
   <author>
    <first-name>Andrew</first-name>
    <middle-name>M.</middle-name>
    <last-name>Rudoff</last-name>
   </author>
   <book-title>UNIX Network Programming</book-title>
   <date>2004</date>
   <lang>en</lang>
  </src-title-info>
  <document-info>
   <author>
    <nickname>honorato bonafe</nickname>
   </author>
   <program-used>OOoFBTools-2.5 (ExportToFB21), FictionBook Editor Release 2.6.6</program-used>
   <date value="2014-11-03">03.11.2014</date>
   <id>OOoFBTools-2014-8-2-15-23-53-1359</id>
   <version>1.0</version>
   <history>
    <p>1.0 djvu-&gt;fb2</p>
   </history>
  </document-info>
  <publish-info>
   <book-name>UNIX: разработка сетевых приложений. 3-е издание</book-name>
   <publisher>Питер</publisher>
   <city>Санкт-Петербург</city>
   <year>2007</year>
   <isbn>5-94723-991-4</isbn>
   <sequence name="Мастер-класс"/>
  </publish-info>
 </description>
 <body>
  <title>
   <p>У. Р. Стивенс, Б. Феннер, Э. М. Рудофф</p>
   <p>UNIX</p>
   <p>Разработка сетевых приложений</p>
   <p>3-е издание</p>
  </title>
  <epigraph>
   <p>Рику</p>
   <p>Aloha nui loa</p>
  </epigraph>
  <section>
   <title>
    <p>Вступительное слово</p>
   </title>
   <p>Вышедшее в 1990 году первое издание этой книги было признано лучшим учебником для программистов, изучающих технологии сетевого программирования. С тех пор сеть претерпела серьезнейшие изменения. Достаточно взглянуть на адрес автора, указанный в том издании: «uunet!hsi!netbook». Вряд ли любой читатель сможет сказать, что это адрес в сети UUCP, которая была популярна в 1980-х.</p>
   <p>Сейчас сети UUCP стали раритетом, а новые технологии, подобные беспроводным сетям, получают повсеместное распространение. Разрабатываются новые протоколы и парадигмы программирования. И программисты начинают ощущать потребность в учебнике, который помог бы им освоить все тонкости новых методик.</p>
   <p>Книга, которую вы держите в руках, заполняет этот пробел. Тем, у кого на книжной полке стоит истрепанное первое издание, она даст возможность узнать о новых технологиях программирования и о протоколах следующего поколения, таких как IPv6. Эта книга нужна всем, потому что она представляет собой соединение практического опыта, исторической перспективы и глубины понимания.</p>
   <p>Я уже получил удовольствие и новые знания благодаря этой книге, и не сомневаюсь, что вы сможете сделать то же самое.</p>
   <cite>
    <text-author>Сэм Леффлер</text-author>
   </cite>
  </section>
  <section>
   <title>
    <p>Предисловие</p>
   </title>
   <subtitle>Введение</subtitle>
   <p>Эта книга предназначена для тех, кто хочет писать программы, взаимодействующие друг с другом посредством интерфейса сокетов. Некоторые читатели, возможно, уже достаточно хорошо знакомы с сокетами, потому что сейчас сетевое программирование фактически немыслимо без них. Другим придется начинать с самых азов. Цель этой книги — предоставить руководство по сетевому программированию для начинающих и для профессионалов, тех, кто разрабатывает новые сетевые приложения и тех, кто поддерживает существующий код. Будет полезна она и тем, кто хочет понимать, как работают сетевые компоненты их систем.</p>
   <p>Все примеры в этой книге относятся к операционной системе Unix, хотя основные понятия и концепции сетевого программирования практически не зависят от операционной системы. Почти все операционные системы предоставляют набор сетевых приложений, таких как браузеры, почтовые клиенты и файловые серверы. Мы обсудим обычное деление таких приложений на клиентскую и серверную части и напишем множество примеров собственных клиентов и серверов.</p>
   <p>Ориентируясь на Unix, мы не могли не рассказать о самой этой системе и о TCP/IP. В тех случаях, когда читателю могут оказаться интересными более подробные сведения, мы отсылаем его к другим книгам:</p>
   <p>&#9632; <emphasis>Advanced Programming in the Unix Environment</emphasis> [110];</p>
   <p>&#9632; <emphasis>TCP/IP Illustrated</emphasis>, том 1 [111];</p>
   <p>&#9632; <emphasis>TCP/IP Illustrated</emphasis>, том 2 [128]; </p>
   <p>&#9632; <emphasis>TCP/IP Illustrated</emphasis>, том 3 [112].</p>
   <p>В первую очередь читателю следует обращаться к книге [128], в которой представлена реализация 4.4BSD функций сетевого программирования для API сокетов (<code>socket</code>, <code>bind</code>, <code>connect</code> и т.д.). При понимании того, как реализована та или иная функциональная возможность, ее применение в приложениях становится более осмысленным.</p>
   <subtitle>Изменения по сравнению со вторым изданием</subtitle>
   <p>Сокеты в нынешней их форме существовали с 1980-х годов. Благодаря совершенству архитектуры этого интерфейса, он продолжает оставаться оптимальным для большинства приложений. Возможно, вы будете удивлены, узнав, как много изменилось в этом интерфейсе с 1998 года, когда было опубликовано второе издание этой книги. Эти изменения были отражены в новом издании. Их можно сгруппировать следующим образом:</p>
   <p>&#9632; Новое издание содержит обновленные сведения об IPv6, который на момент публикации второго издания существовал только в черновом варианте, и за прошедшие годы был усовершенствован.</p>
   <p>&#9632; Определения функций и примеры их использования были изменены в соответствии с последней спецификацией POSIX (POSIX 1003.1–2001), которая известна под названием «Единая спецификация UNIX версии 3».</p>
   <p>&#9632; Описание транспортного интерфейса X/Open было исключено из книги, потому что этот интерфейс вышел из широкого употребления и последняя спецификация POSIX не описывает его.</p>
   <p>&#9632; Также исключено было описание протокола TCP для транзакций (T/TCP).</p>
   <p>&#9632; Были добавлены три новые главы, посвященные относительно новому транспортному протоколу SCRIPT. Этот надежный протокол, ориентированный на передачу сообщений, предоставляет поддержку многопоточной передачи и обеспечивает работу с несколькими интерфейсами. Изначально он был предназначен для Интернет-телефонии, но его функции могут оказаться полезными многим другим приложениям.</p>
   <p>&#9632; Также была добавлена глава, посвященная сокетам управления ключами, которые могут использоваться с протоколом IPSec и другими сервисами сетевой безопасности.</p>
   <p>&#9632; Все примеры тестировались на новых компьютерах с новыми версиями операционных систем. Во многих случаях это оказывалось необходимым по той причине, что производители устраняли ошибки и добавляли новые функции, правда, время от времени, нам удавалось обнаруживать новые ошибки. Для тестирования использовались следующие компьютеры (см. рис. 1.17):</p>
   <p> &#9633; Apple Power PC с MacOS/X 10.2.6</p>
   <p> &#9633; HP PA-RISC с HP-UX 11i</p>
   <p> &#9633; IBM Power PC с AIX 5.1</p>
   <p> &#9633; Intel x86 с FreeBSD 4.8</p>
   <p> &#9633; Intel x86 с Linux 2.4.7</p>
   <p> &#9633; Sun SPARC с FreeBSD 5.1</p>
   <p> &#9633; Sun SPARC с Solaris 9</p>
   <p>Второй том под названием «Взаимодействие процессов» рассказывает о передаче сообщений, синхронизации, разделяемой памяти и удаленном вызове процедур.</p>
   <subtitle>Кому адресована эта книга</subtitle>
   <p>Эту книгу можно использовать и как учебное пособие по сетевому программированию, и как справочник для более опытных программистов. При использовании его как учебника или для ознакомления с сетевым программированием следует уделить особое внимание второй части («Элементарные сокеты», главы 3–11), после чего можно переходить к чтению тех глав, которые представляют наибольший интерес. Во второй части рассказывается об основных функциях сокетов как для TCP, так и для UDP; кроме того, рассматривается мультиплексирование ввода-вывода, параметры сокетов и основные преобразования имен и адресов. Всем читателям следует прочесть главу 1, в особенности раздел 1.4, так как в нем описаны некоторые функции-обертки, используемые далее во всей книге. Глава 2 и, возможно, приложение А могут быть использованы по мере необходимости для получения справочных сведений в зависимости от уровня подготовки читателя. Большинство глав в третьей части («Дополнительные возможности сокетов») могут быть прочитаны независимо от других, содержащихся в этой же части.</p>
   <p>Для тех, кто собирается использовать эту книгу в качестве справочного пособия, имеется подробный предметный указатель. Для тех, кто будет читать только выборочные главы в произвольном порядке, в книге имеются ссылки на те места, где обсуждаются близкие темы.</p>
   <subtitle>Исходный код и замеченные опечатки</subtitle>
   <p>Исходный код для всех примеров расположен на моей домашней странице<a l:href="#n1" type="note">[1]</a>, адрес которой указан в конце предисловия. Чтобы научиться сетевому программированию, лучше всего будет взять эти программы, изменить их и расширить. На самом деле написание программ таким образом является <emphasis>единственным</emphasis> способом овладеть изученными технологиями. В конце каждой главы приводятся упражнения, а ответы на большинство из них содержатся в приложении Г.</p>
   <p>Список найденных опечаток по этой книге также находится на моей домашней странице.</p>
   <subtitle>Благодарности</subtitle>
   <p>Первое и второе издания этой книги были написаны У. Ричардом Стивенсом, который скончался 1 сентября 1999 г. Его книги стали образцом учебной литературы по сетевому программированию и считаются яркими, тщательно проработанными и необычайно популярными произведениями искусства. Авторы новой редакции постарались сохранить качество книги на прежнем уровне.</p>
   <p>Без поддержки семьи и друзей написать книгу невозможно. Билл Феннер хотел бы поблагодарить свою жену Пегги (чемпионку в беге на четверть мили) и соседа по дому Кристофера Бойда за то, что они взяли на себя все тяготы домашнего труда на время его работы над этим проектом. Нужно поблагодарить и Джерри Виннера, чья поддержка была незаменима. Энди Рудофф благодарен своей жене Эллен и дочерям Джо и Кэти за понимание и поощрение. Без вашей помощи мы бы не справились с этим.</p>
   <p>Рэндолл Стюарт из Cisco Systems предоставил большую часть материала по SCRIPT и заслуживает отдельной благодарности за свой бесценный вклад. Без помощи Рэндолла мы не смогли бы рассказать ничего на эту новую интересную тему.</p>
   <p>Многочисленные рецензенты помогли ценными замечаниями и указаниями, обращая внимание на многочисленные ошибки и те области, которые требовали более подробного изложения, а также предложили альтернативные варианты формулировок, изложения материала и самих программ. Авторы хотели бы поблагодарить Джеймса Карлсона, Ву-Чана Фена, Рика Джонса, Брайана Кернигана, Сэма Леффлера, Джона МакКанна, Крейга Метца, Яна Ланса Тейлора, Дэвида Шварца и Гари Райта.</p>
   <p>Многие люди и те организации, в которых они работали, шли мне навстречу, предоставляя программное обеспечение или доступ к системе, необходимые для тестирования некоторых примеров к книге.</p>
   <p>&#9632; Джесси Хог из IBM Austin предоставила систему AIX и компиляторы.</p>
   <p>&#9632; Рик Джонс и Уильям Гиллэм из Hewlett-Packard предоставили доступ ко множеству систем под управлением HP-UX.</p>
   <p>Истинным удовольствием было работать с персоналом Addison Wesley: Норин Региной, Кэтлин Кэрен, Дэном де Паскуале, Энтони Гемелларо и Мэри Франц, нашим редактором, которая заслуживает отдельных благодарностей.</p>
   <p>Продолжая традиции Рика Стивенса (но в противоположность общепринятым технологиям), мы подготовили оригинал-макет книги, используя замечательный пакет <code>groff</code>, написанный Джеймсом Кларком (James Clark), создали иллюстрации с помощью программы <code>gpic</code> (используя многие из макросов Гари Райта), сделали таблицы с помощью программы <code>gtbl</code>, составили предметный указатель и подготовили окончательный макет страниц. Программа Дейва Хансона (Dave Hanson) <code>loom</code> и некоторые сценарии Гари Райта (Gary Wright) использовались для включения кода программ в книгу. Набор сценариев на языке <code>awk</code>, написанный Джоном Бентли (Jon Bentley) и Брайаном Керниганом (Brian Kernighan), помогал в создании предметного указателя.</p>
   <p>Авторы с нетерпением ждут комментарии, предложения и сообщения о замеченных опечатках.</p>
   <p><code>authors@unpbook.com</code></p>
   <p><code>http://www.unpbook.com</code></p>
   <subtitle>От издательства</subtitle>
   <p>Ваши замечания, предложения, вопросы отправляйте по адресу электронной почты <code>comp@piter.com</code> (издательство «Питер», компьютерная редакция).</p>
   <p>Мы будем рады узнать ваше мнение!</p>
   <p>Исходные коды всех программ, приведенных в книге, вы можете найти по адресу <code>http://www.piter.com</code>.</p>
   <p>На веб-сайте издательства <code>http://www.piter.com</code> вы найдете подробную информацию о наших книгах.</p>
  </section>
  <section>
   <title>
    <p>Часть 1</p>
    <p>Введение. TCP/IP</p>
   </title>
   <section>
    <title>
     <p>Глава 1</p>
     <p>Введение в сетевое программирование</p>
    </title>
    <section>
     <title>
      <p>1.1. Введение</p>
     </title>
     <p>Чтобы писать программы, рассчитанные на взаимодействие в компьютерных сетях, необходимо сначала изобрести протокол — соглашение о порядке взаимодействия таких программ. Прежде чем углубляться в детальное проектирование протокола, нужно принять некоторые высокоуровневые решения о том, какая программа будет инициировать передачу данных и в каких случаях можно ожидать ответной передачи. Например, веб-сервер обычно рассматривается как долгоживущая программа (или <emphasis>демон</emphasis> — <emphasis>daemon</emphasis>), которая отправляет сообщения исключительно в ответ на запросы, поступающие по сети. Другой стороной является веб-клиент, например браузер, который всегда начинает взаимодействие с сервером первым. Деление на клиенты и серверы характерно для большинства сетевых приложений. И протокол, и программы обычно упрощаются, если возможность отправки запросов предоставляется только клиенту. Конечно, некоторые сетевые приложения более сложной структуры требуют поддержки <emphasis>асинхронного обратного вызова</emphasis> (<emphasis>asynchronous callback</emphasis>), то есть инициации передачи сообщений сервером, а не клиентом. Однако гораздо чаще приложения реализуются в базовой модели клиент-сервер, изображенной на рис. 1.1.</p>
     <image l:href="#img_1.png"/>
     <p><strong>Рис. 1.1</strong>. Сетевое приложение: клиент и сервер</p>
     <p>Клиенты обычно устанавливают соединение с одним сервером за один раз, хотя, если в качестве примера говорить о веб-браузере, мы можем соединиться со множеством различных веб-серверов, скажем, в течение 10 минут. Сервер, напротив, в любой момент времени может быть соединен со множеством клиентов. Это отражено на рис. 1.2. Далее в этой главе будут рассмотрены различные возможности взаимодействия сервера одновременно со множеством клиентов.</p>
     <image l:href="#img_2.png"/>
     <p><strong>Рис. 1.2</strong>. Сервер, который одновременно обслуживает множество клиентов</p>
     <p>Не будет большой ошибкой сказать, что клиентское и серверное приложения взаимодействуют по сетевому протоколу, однако фактически в большинстве случаев используется несколько протоколов различных уровней. В этой книге мы сосредоточимся на наборе (стеке) протоколов TCP/IP, также называемом набором протоколов Интернета. Так, например, клиенты и веб-серверы устанавливают соединения, используя протокол управления передачей (Transmission Control Protocol, TCP). TCP, в свою очередь, использует протокол Интернета (Internet Protocol, IP), а протокол IP устанавливает соединение с тем или иным протоколом канального уровня. Если и клиент, и сервер находятся в одной сети Ethernet, взаимодействие между ними будет осуществляться по схеме, изображенной на рис. 1.3.</p>
     <image l:href="#img_3.png"/>
     <p><strong>Рис. 1.3</strong>. Клиент и сервер в одной сети Ethernet, соединенные по протоколу TCP</p>
     <p>Хотя клиент и сервер устанавливают соединение с использованием протокола уровня приложений, транспортные уровни устанавливают соединение, используя TCP. Обратите внимание, что действительный поток информации между клиентом и сервером идет вниз по стеку протоколов на стороне клиента, затем по сети и, наконец, вверх по стеку протоколов на стороне сервера.</p>
     <p>Заметьте, что клиент и сервер являются типичными пользовательскими процессами, в то время как TCP и протоколы IP обычно являются частью стека протоколов внутри ядра. Четыре уровня протоколов обозначены на рис. 1.3 справа.</p>
     <p>Мы будем обсуждать не только протоколы TCP и IP. Некоторые клиенты и серверы используют протокол пользовательских дейтаграмм (User Datagram Protocol, UDP) вместо TCP; оба эти протокола более подробно обсуждаются в главе 2. Мы часто пользуемся термином «IP», но на самом деле протокол, который мы при этом подразумеваем, называется «IP версии 4» (IP version 4, IPv4). Новая версия этого протокола, IP версии 6 (IPv6), была разработана в середине 90-х и, возможно, со временем заменит протокол IPv4. В этой книге описана разработка сетевых приложений как под IPv4, так и под IPv6. В приложении А приводится сравнение протоколов IPv4 и IPv6 наряду с другими протоколами, с которыми мы встретимся.</p>
     <p>Клиент и сервер не обязательно должны быть присоединены к одной и той же <emphasis>локальной сети</emphasis> (<emphasis>local area network</emphasis>, <emphasis>LAN</emphasis>), как в примере на рис. 1.3. Вместо этого, как показано на рис. 1.4, клиент и сервер могут относиться к разным локальным сетям, при этом обе локальных сети должны быть соединены в <emphasis>глобальную сеть</emphasis> (<emphasis>wide area network</emphasis>, <emphasis>WAN</emphasis>) с использованием маршрутизаторов.</p>
     <image l:href="#img_4.png"/>
     <p><strong>Рис. 1.4</strong>. Клиент и сервер в различных локальных сетях, соединенных через глобальную сеть</p>
     <p>Маршрутизаторы — это «кирпичи», из которых строится глобальная сеть. На сегодня наибольшей глобальной сетью является Интернет, хотя многие компании создают свои собственные глобальные сети, и эти частные сети могут быть, а могут и не быть подключены к Интернету.</p>
     <p>Оставшаяся часть этой главы представляет собой введение и обзор различных тем, которые более подробно раскрываются далее по тексту книги. Мы начнем с полного, хотя и простого, примера клиента TCP, на котором демонстрируются вызовы многих функций и понятия, с которыми мы встретимся далее. Клиент работает только с протоколом IPv4, и мы покажем изменения, необходимые для работы с протоколом IPv6. Разумнее всего создавать независимые от протокола клиенты и серверы, и такое решение будет рассмотрено нами в главе 11. Мы приводим также код полнофункционального сервера TCP, работающего с нашим клиентом.</p>
     <p>Чтобы упростить написанный нами код, мы определяем наши собственные <emphasis>функции-обертки</emphasis> (<emphasis>wrapper functions</emphasis>) для большинства вызываемых системных функций. Функции-обертки в большинстве случаев служат для проверки кода возврата. В случае ошибки функция-обертка печатает соответствующее сообщение и завершает работу программы.</p>
     <p>В этой же главе мы подробно расскажем о сети, в которой тестировались все примеры этой книги, приведем имена узлов, их IP-адреса и названия операционных систем, под управлением которых они работают.</p>
     <p>В разговорах о Unix широко используется термин «X», обозначающий стандарт, принятый большинством производителей. Мы опишем историю стандарта POSIX и то, каким образом он определяет интерфейсы программирования приложений (Application Programming Interfaces, API), рассматриваемые в этой книге, наряду с другими конкурирующими стандартами.</p>
    </section>
    <section>
     <title>
      <p>1.2. Простой клиент времени и даты</p>
     </title>
     <p>Рассмотрим конкретный пример, на котором мы введем многие понятия и термины, используемые в этой книге. В листинге 1.1<a l:href="#n1" type="note">[1]</a> представлена реализация TCP-клиента времени и даты. Этот клиент устанавливает TCP-соединение с сервером, а сервер просто посылает клиенту время и дату в текстовом формате.</p>
     <p><strong>Листинг 1.1</strong>. Клиент TCP для определения времени и даты</p>
     <p><code>//intro/daytimetcpcli.с</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd, n;</code></p>
     <p><code> 6  char recvline[MAXLINE + 1];</code></p>
     <p><code> 7  struct sockaddr_in servaddr;</code></p>
     <empty-line/>
     <p><code> 8  if (argc != 2)</code></p>
     <p><code> 9   err_quit("usage: a.out &lt;Ipaddress&gt;");</code></p>
     <empty-line/>
     <p><code>10  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) &lt; 0)</code></p>
     <p><code>11   err_sys("socket error");</code></p>
     <empty-line/>
     <p><code>12  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>13  servaddr.sin_family = AF_INET;</code></p>
     <p><code>14  servaddr.sin_port = htons(13); /* сервер времени и даты */</code></p>
     <p><code>15  if (inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr) &lt;= 0)</code></p>
     <p><code>16   err_quit("inet_pton error for %s", argv[1]);</code></p>
     <empty-line/>
     <p><code>17  if (connect(sockfd, (SA*)&amp;servaddr, sizeof(servaddr)) &lt; 0)</code></p>
     <p><code>18   err_sys("connect error");</code></p>
     <empty-line/>
     <p><code>19  while ((n = read(sockfd, recvline, MAXLINE)) &gt; 0) {</code></p>
     <p><code>20   recvline[n] = 0; /* завершающий нуль */</code></p>
     <p><code>21   if (fputs(recvline, stdout) == EOF)</code></p>
     <p><code>22    err_sys("fputs error");</code></p>
     <p><code>23  }</code></p>
     <p><code>24  if (n &lt; 0)</code></p>
     <p><code>25  err_sys("read error");</code></p>
     <empty-line/>
     <p><code>26  exit(0);</code></p>
     <p><code>27 }</code></p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Это формат, который мы используем для всего исходного кода в тексте. Каждая непустая строка пронумерована. Абзац, описывающий некоторую часть кода, начинается с двух номеров — начального и конечного номеров тех строк, о которых идет речь в данном абзаце. Как правило, абзацу предшествует короткий заголовок, в котором кратко резюмируется содержание описываемого кода.</p>
      <p>В начале фрагмента кода указано имя файла исходного кода: в данном примере это файл daytimetcpcli.c в каталоге intro. Поскольку исходный код всех примеров этой книги можно свободно скачать из Сети (см. предисловие), вы можете найти соответствующие исходные файлы по их названиям. Наилучший способ изучить концепции сетевого программирования — компилировать, запускать и особенно модифицировать эти программы в ходе изучения книги.</p>
     </cite>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Мы будем использовать примечания наподобие этого для описания особенностей реализации и исторических справок.</p>
     </cite>
     <p>Если мы откомпилируем эту программу в определенный по умолчанию файл <code>a.out</code> и выполним ее, на выходе мы получим следующее:</p>
     <p><code>solaris % <strong>a.out 206.168.112.96</strong> <emphasis>наш ввод</emphasis></code></p>
     <p><code>Mon May 26 20:58:40 2003 <emphasis>вывод программы</emphasis></code></p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Отображая интерактивный ввод и вывод, мы показываем то, что мы вводим, полужирным шрифтом; вывод же компьютера показываем моноширинным шрифтом. Мы всегда указываем название системы как часть приглашения интерпретатора (в данном примере solaris), чтобы показать, на каком узле выполняется команда. Системы, используемые для выполнения большинства примеров этой книги, показаны на рис. 1.7. Имена узлов обычно соответствуют операционным системам.</p>
     </cite>
     <p>В этой программе, состоящей из 27 строк, есть много важных особенностей, нуждающихся в обсуждении. Мы кратко рассмотрим их на тот случай, если это первая сетевая программа, с которой вы встретились, а более подробные сведения по соответствующим вопросам вы сможете получить в других главах.</p>
     <subtitle>Подключение собственного заголовочного файла</subtitle>
     <p><code>1</code> Мы подключаем наш собственный заголовочный файл, <code>unp.h</code>, текст которого приведен в разделе Г.1. Этот заголовочный файл, в свою очередь, подключает различные системные заголовочные файлы, которые необходимы большинству сетевых программ, и определяет используемые нами константы (например, <code>MAXLINE</code>).</p>
     <subtitle>Аргументы командной строки</subtitle>
     <p><code>2-3</code> Определение функции <code>main</code> вместе с аргументами командной строки. Везде в данной книге при написании кода подразумевалось, что для его компиляции должен использоваться компилятор ANSI С (American National Standards Institute — Национальный институт стандартизации США), который также называют ISO С.</p>
     <subtitle>Создание сокета TCP</subtitle>
     <p><code>10-11</code> Функция <code>socket</code> создает потоковый сокет (<code>SOCK_STREAM</code>) Интернета (AF_INET) — это красивое название для обычного TCP-сокета). Функция возвращает дескриптор (небольшое целое число), который мы используем для идентификации сокета во всех последующих вызовах (например, <code>connect</code> и <code>read</code>).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Оператор if содержит вызов функции socket, присваивание возвращаемого значения переменной sockfd и последующую проверку, является ли это присвоенное значение меньшим нуля. Мы могли разбить этот оператор на два оператора С следующим образом:</p>
      <p><code>sockfd = socket(AF_INET, SOCK_STREAM, 0);</code></p>
      <p><code> if (sockfd &lt; 0)</code></p>
      <p>но использованная в листинге 1.1 запись является типичным для языка С способом объединения двух строк. Поскольку в языке С оператор «меньше» (&lt;) имеет более высокий приоритет, чем оператор присваивания, необходимо заключить в скобки операции присваивания и вызова функции (как это и сделано в листинге 1.1, в строке 10). Между двумя открывающими скобками мы всегда вставляем пробел как указание на то, что левая часть операции сравнения содержит также операцию присваивания. (Этот стиль позаимствован из исходного кода Minix [120].) Мы используем этот же прием в операторе while дальше в нашей программе.</p>
     </cite>
     <p>Мы будем встречать множество различных вариантов использования термина <emphasis>сокет</emphasis> (<emphasis>socket</emphasis>). Во-первых, используемый нами API называется <emphasis>API сокетов</emphasis>. Во-вторых, в предыдущем абзаце мы упоминали функцию <code>socket</code>, которая входит в API сокетов. В-третьих, там же мы ссылались и на «сокет TCP», который является синонимом <emphasis>конечной точки TCP</emphasis> (<emphasis>TCP endpoint</emphasis>).</p>
     <p>Если вызов функции socket оказывается неудачным, мы прерываем выполнение программы с помощью вызова функции <code>err_sys.</code> Она выдает сообщение об ошибке с ее описанием (например, «Протокол не поддерживается» — одна из возможных ошибок функции <code>socket</code>) и прерывает выполнение процесса. Эта функция создана нами, как и некоторые другие, начинающиеся с <code>err_</code>. Мы будем широко использовать их в примерах в последующих главах. Описание функций приводится в разделе Г.4.</p>
     <subtitle>Задание IP-адреса и порта сервера</subtitle>
     <p><code>12-16</code> Мы заполняем структуру адреса сокета Интернета (структура типа <code>sockaddr_in</code> с именем <code>servaddr</code>) IP-адресом и номером порта сервера. Сначала мы инициализируем всю структуру нулями, используя функцию <code>bzero</code>, затем устанавливаем номер порта в 13 (который является номером <emphasis>заранее известного порта</emphasis> (<emphasis>well-known port</emphasis>) сервера времени и даты на любом узле TCP/IP, поддерживающем соответствующую службу — см. табл. 2.1), после чего устанавливаем IP-адрес равным значению, определенному первым аргументом командной строки (<code>argv[1]</code>). В этой структуре поля IP-адреса и номера порта должны иметь определенный формат: мы вызываем библиотечную функцию <code>htons</code> (host to network short), чтобы преобразовать двоичный номер порта в требуемый формат, и вызываем библиотечную функцию <code>inet_pton</code> (presentation to numeric), чтобы преобразовать аргумент командной строки в символах ASCII (например, <code>206.168.112.96</code> при выполнении данного примера) в двоичный формат.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Функция bzero не является функцией ANSI С. Она происходит от более раннего кода сетевого программирования Беркли. Тем не менее мы используем именно ее, а не функцию ANSI С memset, потому что с функцией bzero работать проще: она вызывается с двумя аргументами, a memset — с тремя. Почти каждый производитель, поддерживающий API сокетов, также реализует и функцию bzero, а если и не реализует, мы определяем ее через макрос в нашем заголовочном файле unp.h.</p>
      <p>Автор [112] в первом издании сделал десять ошибок, поменяв местами аргументы memset. Компилятор С не может распознать эту ошибку, поскольку оба аргумента принадлежат одному типу. В действительности второй аргумент принадлежит типу int, а третий — size_t — обычно имеет тип unsigned int (то есть целое без знака), но заданные значения, соответственно, 0 и 16, являются допустимыми для обоих типов аргумента. Вызов функции memset все равно осуществлялся, но реально функция ничего не делала, поскольку задавалось нулевое число инициализируемых байтов. Программа работала, потому что только некоторые функции сокетов действительно требуют, чтобы последние 8 байт структуры адреса сокета Интернета были установлены в 0. Тем не менее это ошибка, и ее можно избежать при использовании функции bzero, поскольку перестановка двух аргументов функции bzero всегда будет выявлена компилятором С, если используются прототипы функций.</p>
      <p>Возможно, вы впервые встречаете функцию inet_pton. Она появилась вместе с протоколом IPv6 (о котором более подробно мы поговорим в приложении А). В старых программах для преобразования точечно-десятичной записи (dotted-decimal string) ASCII в необходимый формат использовалась функция inet_addr, но у нее есть ряд ограничений, которых не имеет функция inet_pton. Не беспокойтесь, если ваша система (еще) не поддерживает эту функцию; реализация ее приведена в разделе 3.7.</p>
     </cite>
     <subtitle>Установка соединения с сервером</subtitle>
     <p><code>17-18</code> Функция <code>connect</code>, применяемая к сокету TCP, устанавливает соединение по протоколу TCP с сервером, адрес сокета которого содержится в структуре, на которую указывает второй аргумент. Мы также должны задать длину структуры адреса сокета в качестве третьего аргумента функции <code>connect</code>, а для структур адреса интернет-сокета мы всегда предоставляем вычисление длины компилятору, используя оператор С <code>sizeof</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В заголовочном файле unp.h мы используем директиву #define SA, чтобы определить SA как struct sockaddr, что соответствует общей структуре адреса сокета. Каждый раз, когда одна из функций сокетов требует указателя на структуру адреса сокета, этот указатель должен быть преобразован к указателю на общую структуру адреса сокета. Это происходит потому, что функции сокетов появились раньше, чем стандарт ANSI С. Соответственно, тип указателя void* не был доступен в начале 80-х, когда эти функции были разработаны. Проблема состоит в том, что "struct sockaddr" занимает 15 символов и часто заставляет выходить строку исходного кода за правую границу экрана (или за страницу книги), поэтому мы сократили ее до SA. Более подробно мы исследуем общие структуры адресов сокетов на примере листинга 3.2.</p>
     </cite>
     <subtitle>Чтение и отображение ответа сервера</subtitle>
     <p><code>19-25</code> Мы читаем ответ сервера и отображаем результат, используя стандартную функцию ввода-вывода <code>fputs</code>. Нужно быть внимательным при использовании TCP, поскольку это <emphasis>потоковый</emphasis> (<emphasis>byte-stream</emphasis>) протокол без границ записей. Обычно ответом сервера является 26-байтовая строка следующей формы:</p>
     <p><code>Fri Jan 12 14:27:52 1996\r\n</code></p>
     <p>где <code>\r</code> — это возврат каретки, а <code>\n</code> — перевод строки (в символах ASCII). В случае потокового протокола эти 26 байт можно получить в нескольких вариантах: в виде отдельного сегмента TCP, содержащего все 26 байт данных, либо в виде 26 сегментов, каждый из которых содержит по одному байту данных, или в виде любой другой комбинации, в сумме дающей 26 байт. Обычно возвращается один сегмент, содержащий все 26 байт, но при больших объемах данных нельзя рассчитывать, что ответ сервера будет получен с помощью одного вызова <code>read</code>. Следовательно, при чтении из сокета TCP нужно всегда вызывать функцию <code>read</code> циклически и прерывать цикл либо когда функция возвращает 0 (например, соединение было разорвано другой стороной), либо когда возвращенное значение оказывается меньше нуля (ошибка).</p>
     <p>В приведенном примере конец записи обозначается сервером, закрывающим соединение. Эта технология используется также версией 1.0 протокола передачи гипертекста (Hypertext Transfer Protocol, HTTP). Существуют и другие способы обозначения конца записи. Например, протокол передачи файлов (File Transfer Protocol, FTP) и простой протокол передачи почты (Simple Mail Transfer Protocol, SMTP) обозначают конец записи 2-байтовой последовательностью, состоящей из символов ASCII возврата каретки и перевода строки. Служба вызова удаленных процедур (Remote Procedure Call, RPC) и система именования доменов (Domain Name System, DNS) помещают перед каждой записью, отсылаемой по протоколу TCP, двоичное число, соответствующее длине этой записи. Здесь важно осознать, что протокол TCP сам по себе не предоставляет никаких меток записей: если приложение хочет отделять записи одну от другой, оно должно делать это самостоятельно, и для этого имеются стандартные методы.</p>
     <subtitle>Завершение программы</subtitle>
     <p><code>26</code> Функция <code>exit</code> завершает программу. Unix всегда закрывает все открытые дескрипторы при завершении процесса, поэтому теперь наш сокет TCP закрыт.</p>
     <p>Как уже говорилось, пока мы лишь выделили наиболее важные моменты, детальным исследованием которых займемся в дальнейшем.</p>
    </section>
    <section>
     <title>
      <p>1.3. Независимость от протокола</p>
     </title>
     <p>Наша программа, представленная в листинге 1.1, является <emphasis>зависимой от протокола</emphasis> (<emphasis>protocol dependent</emphasis>) IPv4. Мы выделяем и инициализируем структуру <code>sockaddr_in</code>, определяем адрес как относящийся к семейству AF_INET и устанавливаем первый аргумент функции <code>socket</code> равным <code>AF_INET</code>.</p>
     <p>Если мы хотим изменить программу так, чтобы она работала по протоколу IPv6, мы должны изменить код. В листинге 1.2 показана новая версия программы с соответствующими изменениями, отмеченными полужирным шрифтом.</p>
     <p><strong>Листинг 1.2</strong>. Версия листинга 1.1 для IPv6</p>
     <p><code>//intro/daytimetcpcliv6.с</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd, n;</code></p>
     <p><code> 6  char recvline[MAXLINE + 1];</code></p>
     <p><code> 7  struct <strong>sockaddr_in6</strong> servaddr;</code></p>
     <empty-line/>
     <p><code> 8  if (argc != 2)</code></p>
     <p><code> 9   err_quit("usage: a.out &lt;Ipaddress&gt;");</code></p>
     <empty-line/>
     <p><code>10  if ((sockfd = socket(<strong>AF_INET6</strong>, SOCK_STREAM, 0)) &lt; 0)</code></p>
     <p><code>11   err_sys("socket error");</code></p>
     <empty-line/>
     <p><code>12  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>13  servaddr.<strong>sin6_family</strong> = AF_INET6;</code></p>
     <p><code>14  servaddr.<strong>sin6_port</strong> = htons(13); /* сервер времени и даты */</code></p>
     <p><code>15  if (inet_pton(<strong>AF_INET6</strong>, argv[1], &amp;servaddr.<strong>sin6_addr</strong>) &lt;= 0)</code></p>
     <p><code>16   err_quit("inet_pton error for %s", argv[1]);</code></p>
     <empty-line/>
     <p><code>17  if (connect(sockfd, (SA*)&amp;servaddr, sizeof(servaddr)) &lt; 0)</code></p>
     <p><code>18   err_sys("connect error");</code></p>
     <empty-line/>
     <p><code>19  while ((n = read(sockfd, recvline, MAXLINE)) &gt; 0) {</code></p>
     <p><code>20   recvline[n] = 0; /* символ конца строки */</code></p>
     <p><code>21   if (fputs(recvline, stdout) == EOF)</code></p>
     <p><code>22    err_sys("fputs error");</code></p>
     <p><code>23  }</code></p>
     <p><code>24  if (n &lt; 0)</code></p>
     <p><code>25   err_sys("read error");</code></p>
     <empty-line/>
     <p><code>26  exit(0);</code></p>
     <p><code>27 }</code></p>
     <p>Изменились только пять строк, но в результате мы все равно получили программу, зависимую от протокола, в данном случае — от протокола IPv6. Лучше сделать программу <emphasis>независимой от протокола</emphasis> (<emphasis>protocol independent</emphasis>). В листинге 11.3 представлена независимая от протокола версия этого клиента, основанная на вызове <code>getaddrinfo</code> из <code>tcp_connect</code>.</p>
     <p>Другим недостатком наших программ является то, что пользователь должен вводить IP-адрес сервера в точечно-десятичной записи (например, 206.168.112.219 для версии IPv4). Людям проще работать с именами, чем с числами (например, <code>www.unpbook.com</code>). В главе 11 мы обсудим функции, обеспечивающие преобразование имен узлов в IP-адреса и имен служб в порты. Мы специально откладываем описание этих функций, продолжая использовать IP-адреса и номера портов, чтобы иметь ясное представление о том, что именно входит в структуры адресов сокетов, которые мы должны заполнить и проверить. Это также упрощает наши объяснения сетевого программирования, снимая необходимость описывать в подробностях еще один набор функций.</p>
    </section>
    <section>
     <title>
      <p>1.4. Обработка ошибок: функции-обертки</p>
     </title>
     <section>
      <p>В любой реальной программе существенным моментом является проверка <emphasis>каждого</emphasis> вызова функции на предмет возвращаемой ошибки. В листинге 1.1 мы проводим поиск ошибок в вызовах функций <code>socket</code>, <code>inet_pton</code>, <code>connect</code>, <code>read</code> и <code>fputs</code>, и когда ошибка случается, мы вызываем свои собственные функции <code>err_quit</code> и <code>err_sys</code> для печати сообщения об ошибке и для прерывания выполнения программы. В отдельных случаях, когда функция возвращает ошибку, бывает нужно сделать еще что-либо помимо прерывания программы, как показано в листинге 5.9, когда мы должны проверить прерванный системный вызов.</p>
      <p>Поскольку прерывание программы из-за ошибки — типичное явление, мы сократим наши программы, определив <emphasis>функции-обертки</emphasis>, которые будут вызывать соответствующие рабочие функции, проверять возвращаемые значения и прерывать программу при возникновении ошибки. Соглашение, используемое нами, заключается в том, что название функции-обертки пишется с заглавной буквы, например:</p>
      <p><code>sockfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
      <p>Наша функция-обертка для функции socket показана в листинге 1.3.</p>
      <p><strong>Листинг 1.3</strong>. Наша функция-обертка для функции socket</p>
      <p><code>//lib/wrapsock.c</code></p>
      <p><code>172 int</code></p>
      <p><code>173 Socket(int family, int type, int protocol)</code></p>
      <p><code>174 {</code></p>
      <p><code>175  int n;</code></p>
      <empty-line/>
      <p><code>176  if ((n = socket(family, type, protocol)) &lt; 0)</code></p>
      <p><code>177   err_sys("socket error");</code></p>
      <p><code>178  return (n);</code></p>
      <p><code>179 }</code></p>
      <p>Хотя вы можете решить, что использование этих функций-оберток не обеспечивает большой экономии, на самом деле это не так. Обсуждая потоки (threads) в главе 26, мы обнаружим, что, когда происходит какая-либо ошибка, функции потоков не устанавливают значение стандартной переменной Unix <code>errno</code> равным определенной константе, специфической для произошедшей ошибки. Вместо этого значение переменной <code>errno</code> просто возвращается функцией. Это значит, что каждый раз, когда мы вызываем одну из функций <code>pthread</code>, мы должны разместить в памяти переменную, сохранить возвращаемое значение в этой переменной и установить <code>errno</code> равной этому значению перед вызовом <code>err_sys</code>. Чтобы избежать загромождения кода скобками, мы можем использовать оператор языка С <emphasis>запятая</emphasis> для объединения присваивания значения переменной <code>errno</code> и вызова <code>err_sys</code> в отдельное выражение следующим образом:</p>
      <p><code>int n;</code></p>
      <p><code>if ((n = pthread_mutex_lock(&amp;ndone_mutex)) != 0)</code></p>
      <p><code> errno = n, err_sys("pthread_mutex_lock error");</code></p>
      <cite>
       <subtitle>ВНИМАНИЕ</subtitle>
       <p>В тексте книги вам будут встречаться функции, имена которые начинаются с заглавной буквы. Это наши собственные функции-обертки. Функция-обертка вызывает функцию, имеющую такое же имя, но начинающееся со строчной буквы.</p>
       <p>При описании исходного кода, представленного в тексте книги, мы всегда ссылаемся на вызываемую функцию низшего уровня (например, socket), но не на функцию-обертку (например, Socket).</p>
      </cite>
      <p>В качестве альтернативы мы можем определить новую функцию выдачи сообщений об ошибках, которая в качестве аргумента получает системный код ошибки. Однако проще всего текст будет выглядеть с использованием функции-обертки, определенной в листинге 1.4:</p>
      <p><code>Pthread_mutex_lock(&amp;ndone_mutex);</code></p>
      <p><strong>Листинг 1.4</strong>. Наша собственная функция-обертка для функции pthread_mutex_lock</p>
      <p><code>//lib/wrappthread.c</code></p>
      <p><code>72 void</code></p>
      <p><code>73 Pthread_mutex_lock(pthread_mutex_t *mptr)</code></p>
      <p><code>74 {</code></p>
      <p><code>75  int n;</code></p>
      <empty-line/>
      <p><code>76  if ((n = pthread_mutex_lock(mptr)) == 0)</code></p>
      <p><code>77   return;</code></p>
      <p><code>78  errno = n;</code></p>
      <p><code>79  err_sys("pthread_mutex_lock error");</code></p>
      <p><code>80 }</code></p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Если аккуратно программировать на С, можно использовать макросы вместо функций, что обеспечивает небольшой выигрыш в производительности, однако функции- обертки редко, если вообще когда-нибудь бывают причиной недостаточной производительности программ.</p>
       <p>Наш выбор — первая заглавная буква в названии функции — является компромиссом. Было предложено множество других стилей: подстановка префикса e перед названием функции (как сделано в [67, с. 182]), добавление _е к имени функции и т.д. Наш вариант кажется наименее отвлекающим внимание и одновременно дающим визуальное указание на то, что вызывается какая-то другая функция.</p>
       <p>Эта технология имеет, кроме того, полезный побочный эффект: она позволяет проверять возникновение ошибок при выполнении таких функций, ошибки в которых часто остаются незамеченными, например close и listen.</p>
      </cite>
      <p>На протяжении всей книги мы будем использовать эти функции-обертки, кроме тех случаев, когда нам нужно проверить ошибку явно и обрабатывать ее другим, отличным от прерывания программы, способом. Мы не приводим исходный код для всех наших собственных функций-оберток, но он свободно доступен в Интернете (см. предисловие).</p>
     </section>
     <section>
      <title>
       <p>Значение системной переменной Unix errno</p>
      </title>
      <p>Когда при выполнении функции Unix (например, одной из функций сокетов) происходит ошибка, глобальной переменной <code>errno</code> присваивается положительное значение, указывающее на тип ошибки, а возвращаемое значение функции обычно равно -1. Наша функция <code>err_sys</code> проверяет значение переменной <code>errno</code> и печатает строку с соответствующим сообщением об ошибке (например, «Время соединения истекло», если значение переменной errno равно <code>ETIMEDOUT</code>).</p>
      <p>Переменная errno устанавливается равной определенному значению, только если при выполнении функции произошла какая-либо ошибка. Ее значение не определено, если функция не возвращает ошибки. Все положительные значения ошибок являются константами с именами в верхнем регистре, начинающимися на «E», и обычно определяются в заголовке <code>&lt;sys/errno.h&gt;</code>. Ни одна ошибка не имеет кода 0.</p>
      <p>Переменную errno нельзя хранить как глобальную переменную в случае множества потоков, у которых все глобальные переменные являются общими. О решении этой проблемы мы расскажем в главе 23.</p>
      <p>На протяжении всего текста книги мы использовали фразы типа «функция connect возвращает <code>ECONNREFUSED</code>» для сокращенного обозначения того, что при выполнении функции произошла ошибка (обычно при этом возвращаемое значение функции равно -1), и значение переменной <code>errno</code> стало равным указанной константе.</p>
     </section>
    </section>
    <section>
     <title>
      <p>1.5. Простой сервер времени и даты</p>
     </title>
     <p>Мы можем написать простую версию сервера TCP для определения времени и даты, который будет работать с клиентом, описанным в разделе 1.2. Мы используем функции-обертки, описанные в предыдущем разделе. Код сервера приведен в листинге 1.5.</p>
     <p><strong>Листинг 1.5</strong>. TCP-сервер времени и даты</p>
     <p><code>//intro/daytimetcpsrv.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <p><code> 2 #include &lt;time.h&gt;</code></p>
     <empty-line/>
     <p><code> 3 int</code></p>
     <p><code> 4 main(int argc, char **argv)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  int listenfd, connfd;</code></p>
     <p><code> 7  struct sockaddr_in servaddr;</code></p>
     <p><code> 8  char buff[MAXLINE];</code></p>
     <p><code> 9  time_t ticks;</code></p>
     <empty-line/>
     <p><code>10  listenfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <p><code>11  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>12  servaddr.sin_family = AF_INET;</code></p>
     <p><code>13  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>14  servaddr.sin_port = htons(13); /* сервер времени и даты */</code></p>
     <empty-line/>
     <p><code>15  Bind(listenfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>16  Listen(listenfd, LISTENQ);</code></p>
     <empty-line/>
     <p><code>17  for (;;) {</code></p>
     <p><code>18   connfd = Accept(listenfd, (SA*)NULL, NULL);</code></p>
     <empty-line/>
     <p><code>19   ticks = time(NULL);</code></p>
     <p><code>20   snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&amp;ticks));</code></p>
     <p><code>21   Write(connfd. buff, strlen(buff));</code></p>
     <empty-line/>
     <p><code>22   Close(connfd);</code></p>
     <p><code>23  }</code></p>
     <p><code>24 }</code></p>
     <subtitle>Создание сокета TCP</subtitle>
     <p><code>10</code> Создание сокета TCP выполняется так же, как и в клиентском коде.</p>
     <subtitle>Связывание заранее известного порта сервера с сокетом</subtitle>
     <p><code>11-15</code> Заранее известный порт сервера (13 в случае сервера времени и даты) связывается с сокетом путем заполнения структуры адреса интернет-сокета и вызова функции <code>bind</code>. Мы задаем IP-адрес как <code>INADDR_ANY</code>, что позволяет серверу принимать соединение клиента на любом интерфейсе в том случае, если узел сервера имеет несколько интерфейсов. Далее мы рассмотрим, как можно ограничить прием соединений одним-единственным интерфейсом.</p>
     <subtitle>Преобразование сокета в прослушиваемый сокет</subtitle>
     <p><code>16</code> С помощью вызова функции <code>listen</code> сокет преобразуется в прослушиваемый, то есть такой, на котором ядро принимает входящие соединения от клиентов. Эти три этапа, <code>socket</code>, <code>bind</code> и <code>listen</code>, обычны для любого сервера TCP при создании того, что мы называем <emphasis>прослушиваемым дескриптором</emphasis> (<emphasis>listening descriptor</emphasis>) (в нашем примере это переменная <code>listenfd</code>).</p>
     <p>Константа <code>LISTENQ</code> взята из нашего заголовочного файла <code>unp.h</code>. Она задает максимальное количество клиентских соединений, которые ядро ставит в очередь на прослушиваемом сокете. Более подробно мы расскажем о таких очередях в разделе 4.5.</p>
     <subtitle>Прием клиентского соединения, отправка ответа</subtitle>
     <p><code>17-21</code> Обычно процесс сервера блокируется при вызове функции <code>accept</code>, ожидая принятия подключения клиента. Для установки TCP-соединения используется <emphasis>трехэтапное рукопожатие</emphasis> (<emphasis>three-way handshake</emphasis>). Когда рукопожатие состоялось, функция accept возвращает значение, и это значение является новым дескриптором (<code>connfd</code>), который называется <emphasis>присоединенным дескриптором</emphasis> (<emphasis>connected descriptor</emphasis>). Этот новый дескриптор используется для связи с новым клиентом. Новый дескриптор возвращается функцией <code>accept</code> для каждого клиента, соединяющегося с нашим сервером.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Стиль, используемый в книге для обозначения бесконечного цикла, выглядит так:</p>
      <p><code>for (;;) {</code></p>
      <p><code> ...</code></p>
      <p><code>}</code></p>
     </cite>
     <p>Библиотечная функция <code>time</code> возвращает количество секунд с начала эпохи Unix: 00:00:00 1 января 1970 года UTC (Universal Time Coordinated — универсальное синхронизированное время, среднее время по Гринвичу). Следующая библиотечная функция, <code>ctime</code>, преобразует целочисленное значение секунд в строку следующего формата, удобного для человеческого восприятия:</p>
     <p><code>Fri Jan 12 14:27:52 1996</code></p>
     <p>Возврат каретки и пустая строка добавляются к строке функцией <code>snprintf</code>, а результат передается клиенту функцией <code>write</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Если вы еще не выработали у себя привычку пользоваться функцией snprintf вместо устаревшей sprintf, сейчас самое время заняться этим. Функция sprintf не в состоянии обеспечить проверку переполнения буфера получателя. Функция snprintf, наоборот, требует, чтобы в качестве второго аргумента указывался размер буфера получателя, переполнение которого таким образом предотвращается.</p>
      <p>Функция snprintf была добавлена в стандарт ANSI С относительно нравно, в версии ISO C99. Практически все поставщики программного обеспечения уже сейчас включают эту функцию в стандартную библиотеку языка С. Существуют и свободно распространяемые реализации. В нашей книге мы используем функцию snprintf и рекомендуем вам пользоваться ею в своих программах для повышения их надежности.</p>
      <p>Удивительно много сетевых атак было реализовано хакерами с использованием незащищенности sprintf от переполнения буфера. Есть еще несколько функций, с которыми нужно быть аккуратными: gets, strcat и strcpy. Вместо них лучше использовать fgets, strncat и strncpy. Еще лучше работают более современные функции strlcat и strlcpy, возвращающие в качестве результата правильно завершенную строку. Полезные советы, касающиеся написания надежных сетевых программ, можно найти в главе 23 книги [32].</p>
     </cite>
     <subtitle>Завершение соединения</subtitle>
     <p><code>22</code> Сервер закрывает соединение с клиентом, вызывая функцию <code>close</code>. Это инициирует обычную последовательность прерывания соединения TCP: пакет FIN посылается в обоих направлениях, и каждый пакет FIN распознается на другом конце соединения. Более подробно трехэтапное рукопожатие и четыре пакета TCP, используемые для прерывания соединения, будут описаны в разделе 2.6.</p>
     <p>Сервер времени и даты был рассмотрен нами достаточно кратко, как и клиент из предыдущего раздела. Запомните следующие моменты.</p>
     <p>&#9632; Сервер, как и клиент, зависим от протокола IPv4. В листинге 11.7 мы покажем версию, не зависящую от протокола, которая использует функцию <code>getaddrinfo</code>.</p>
     <p>&#9632; Наш сервер обрабатывает только один запрос клиента за один раз. Если приблизительно в одно время происходит множество клиентских соединений, ядро ставит их в очередь, максимальная длина которой регламентирована, и передает эти соединения функции accept по одному за один раз. Наш сервер времени и даты, который требует вызова двух библиотечных функций, time и ctime, является достаточно быстрым. Но если у сервера обслуживание каждого клиента занимает больше времени (допустим, несколько секунд или минуту), нам будет необходимо некоторым образом организовать одновременное обслуживание нескольких клиентов.</p>
     <p>Сервер, показанный в листинге 1.5, называется <emphasis>последовательным сервером</emphasis> (<emphasis>iterative server</emphasis>), поскольку он обслуживает клиентов последовательно, по одному клиенту за один раз. Существует несколько технологий написания <emphasis>параллельного сервера</emphasis> (<emphasis>concurrent server</emphasis>), который обслуживает множество клиентов одновременно. Самой простой технологией является вызов функции Unix <code>fork</code> (раздел 4.7), когда создается по одному дочернему процессу для каждого клиента. Другой способ — использование программных потоков (threads) вместо функции <code>fork</code> (раздел 26.4) или предварительное порождение фиксированного количества дочерних процессов с помощью функции fork в начале работы (раздел 30.6).</p>
     <p>&#9632; Запуская такой сервер из командной строки, мы обычно рассчитываем, что он будет работать достаточно долго, поскольку часто серверы работают, пока работает система. Поэтому мы должны модифицировать код сервера таким образом, чтобы он корректно работал как <emphasis>демон</emphasis> (<emphasis>daemon</emphasis>) Unix, то есть процесс, функционирующий в фоновом режиме без подключения к терминалу. Это решение подробно описано в разделе 13.4.</p>
    </section>
    <section>
     <title>
      <p>1.6. Таблица соответствия примеров технологии клиент-сервер</p>
     </title>
     <p>Технологии сетевого программирования иллюстрируются в этой книге на двух основных примерах:</p>
     <p>&#9632; клиент-сервер времени и даты (описание которого мы начали в листингах 1.1, 1.2 и 1.5), и</p>
     <p>&#9632; эхо-клиент-сервер (который появится в главе 5).</p>
     <p>Чтобы обеспечить удобный поиск различных тем, которых мы касаемся в этой книге, мы объединили разработанные нами программы и сопроводили их номерами листингов, в которых приведен исходный код. В табл. 1.1 перечислены версии клиента времени и даты (две из них мы уже видели). В табл. 1.2 перечисляются версии сервера времени и даты. В табл. 1.3 представлены версии эхо-клиента, а в табл. 1.4 — версии эхо-сервера.</p>
     <empty-line/>
     <p><strong>Таблица 1.1</strong>. Различные версии клиента времени и даты</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Листинг</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1.1</td>
       <td align="left" valign="top">TCP/Ipv4, зависимый от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1.2</td>
       <td align="left" valign="top">TCP/Ipv6, зависимый от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">11.2</td>
       <td align="left" valign="top">TCP/Ipv4, зависимый от протокола, вызывает функции gethostbyname и getservbyname</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">11.5</td>
       <td align="left" valign="top">TCP, независимый от протокола, вызывает функции getaddrinfo и tcp_connect</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">11.10</td>
       <td align="left" valign="top">UDP, независимый от протокола, вызывает функции getaddrinfo и udp_connect</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">16.7</td>
       <td align="left" valign="top">TCP, использует неблокирующую функцию connect</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">31.2</td>
       <td align="left" valign="top">TCP/IPv4, зависимый от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Д.1</td>
       <td align="left" valign="top">TCP, зависимый от протокола, генерирует SIGPIPE</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Д.2</td>
       <td align="left" valign="top">TCP, зависимый от протокола, печатает размер буфера сокета и MSS</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Д.5</td>
       <td align="left" valign="top">TCP, зависимый от протокола, допускает использование имени узла (функция gethostbyname) или IP-адреса</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Д.6</td>
       <td align="left" valign="top">TCP, независимый от протокола, допускает использование имени узла (функция gethostbyname).</td>
      </tr>
     </table>
     <empty-line/>
     <p><strong>Таблица 1.2</strong>. Различные версии сервера времени и даты, рассматриваемые в данной книге</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Листинг</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1.5</td>
       <td align="left" valign="top">TCP/IPv4, зависимый от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">11.7</td>
       <td align="left" valign="top">TCP, независимый от протокола, вызывает getaddrinfo и tcp_listen</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">11.8</td>
       <td align="left" valign="top">TCP, независимый от протокола, вызывает getaddrinfo и tcp_listen</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">11.13</td>
       <td align="left" valign="top">UDP, независимый от протокола, вызывает getaddrinfo и udp_server</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">13.2</td>
       <td align="left" valign="top">TCP, независимый от протокола, выполняется как автономный демон</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">13.4</td>
       <td align="left" valign="top">TCP, независимый от протокола, порожденный демоном inetd</td>
      </tr>
     </table>
     <empty-line/>
     <p><strong>Таблица 1.3</strong>. Различные версии эхо-клиента, рассматриваемые в данной книге</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Листинг</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">5.3</td>
       <td align="left" valign="top">TCP/IPv4, зависимый от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">6.1</td>
       <td align="left" valign="top">TCP, использует функцию select</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">6.2</td>
       <td align="left" valign="top">TCP, использует функцию select и работает в пакетном режиме</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">8.3</td>
       <td align="left" valign="top">UDP/IPv4, зависимый от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">8.5</td>
       <td align="left" valign="top">UDP, проверяет адрес сервера</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">8.7</td>
       <td align="left" valign="top">UDP, вызывает функцию connect для получения асинхронных ошибок</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">14.2</td>
       <td align="left" valign="top">UDP, тайм-аут при чтении ответа сервера с использованием сигнала SIGALRM</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">14.4</td>
       <td align="left" valign="top">UDP, тайм-аут при чтении ответа сервера с использованием функции select</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">14.5</td>
       <td align="left" valign="top">UDP, тайм-аут при чтении ответа сервера с использованием опции сокета SO_RCVTIMEO</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">14.7</td>
       <td align="left" valign="top">TCP, использует интерфейс /dev/poll</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">14.8</td>
       <td align="left" valign="top">TCP, использует интерфейс kqueue</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">15.4</td>
       <td align="left" valign="top">Поток домена Unix, зависит от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">15.6</td>
       <td align="left" valign="top">Дейтаграмма домена Unix, зависит от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">16.1</td>
       <td align="left" valign="top">TCP, использует неблокируемый ввод-вывод</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">16.6</td>
       <td align="left" valign="top">TCP, использует два процесса (функцию fork)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">16.14</td>
       <td align="left" valign="top">TCP, устанавливает соединение, затем посылает пакет RST</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">20.1</td>
       <td align="left" valign="top">UDP, широковещательный, ситуация гонок</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">20.2</td>
       <td align="left" valign="top">UDP, широковещательный, ситуация гонок</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">20.3</td>
       <td align="left" valign="top">UDP, широковещательный, для устранения ситуации гонок используется функция pselect</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">20.5</td>
       <td align="left" valign="top">UDP, широковещательный, для устранения ситуации гонок используются функции sigsetjmp и siglongmp</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">20.6</td>
       <td align="left" valign="top">UDP, широковещательный, для устранения ситуации гонок в обработчике сигнала используется IPC</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">22.4</td>
       <td align="left" valign="top">UDP, увеличение надежности протокола за счет применения повторной передачи, тайм-аутов и порядковых номеров</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">26.1</td>
       <td align="left" valign="top">TCP, использование двух потоков</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">27.4</td>
       <td align="left" valign="top">TCP/IPv4, задание маршрута от отправителя</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">27.5</td>
       <td align="left" valign="top">UDP/IPv6, задание маршрута от отправителя</td>
      </tr>
     </table>
     <empty-line/>
     <p><strong>Таблица 1.4</strong>. Различные версии эхо-сервера, рассматриваемые в данной книге</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Листинг</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">5.1</td>
       <td align="left" valign="top">TCP/IPv4, зависимый от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">5.9</td>
       <td align="left" valign="top">TCP/IPv4, зависимый от протокола, корректно обрабатывает завершение всех дочерних процессов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">6.3</td>
       <td align="left" valign="top">TCP/IPv4, зависимый от протокола, использует функцию select, один процесс обрабатывает всех клиентов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">6.5</td>
       <td align="left" valign="top">TCP/IPv4, зависимый от протокола, использует функцию poll, один процесс обрабатывает всех клиентов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">8.1</td>
       <td align="left" valign="top">UDP/IPv4, зависимый от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">8.14</td>
       <td align="left" valign="top">TCP и UDP/IPv4, зависимый от протокола, использует функцию select</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">14.6</td>
       <td align="left" valign="top">TCP, использует стандартный ввод-вывод</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">15.3</td>
       <td align="left" valign="top">Доменный сокет Unix, зависимый от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">15.5</td>
       <td align="left" valign="top">Дейтаграмма домена Unix, зависит от протокола</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">15.13</td>
       <td align="left" valign="top">Доменный сокет Unix, с передачей данных, идентифицирующих клиента</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">22.3</td>
       <td align="left" valign="top">UDP, печатает полученный IP-адрес назначения и имя полученного интерфейса, обрезает дейтаграммы</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">22.13</td>
       <td align="left" valign="top">UDP, связывает все адреса интерфейсов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">25.2</td>
       <td align="left" valign="top">UDP, использование модели ввода-вывода, управляемого сигналом</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">26.2</td>
       <td align="left" valign="top">TCP, один поток на каждого клиента</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">26.3</td>
       <td align="left" valign="top">TCP, один поток на каждого клиента, машинонезависимая (переносимая) передача аргумента</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">27.4</td>
       <td align="left" valign="top">TCP/IPv4, печатает полученный маршрут от отправителя</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">27.6</td>
       <td align="left" valign="top">UDP/IPv4, печатает полученный маршрут от отправителя и обращает его</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">28.21</td>
       <td align="left" valign="top">UDP, использует функцию icmpd для получения асинхронных ошибок</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Д.9</td>
       <td align="left" valign="top">UDP, связывает все адреса интерфейсов</td>
      </tr>
     </table>
    </section>
    <section>
     <title>
      <p>1.7. Модель OSI</p>
     </title>
     <p>Распространенным способом описания уровней сети является предложенная Международной организацией по стандартизации (International Standards Organization, ISO) <emphasis>модель взаимодействия открытых систем</emphasis> (<emphasis>open systems interconnection</emphasis>, <emphasis>OSI</emphasis>). Эта семиуровневая модель показана на рис. 1.5, где она сравнивается со стеком протоколов Интернета.</p>
     <image l:href="#img_5.png"/>
     <p><strong>Рис. 1.5</strong>. Уровни модели OSI и набор протоколов Интернета</p>
     <p>Мы считаем, что два нижних уровня модели OSI соответствуют драйверу устройства и сетевому оборудованию, которые имеются в системе. Обычно нам не приходится беспокоиться об этих уровнях, за исключением того, что мы должны знать некоторые свойства канального уровня — например, что MTU (максимальная единица передачи) Ethernet, которая описывается в разделе 2.11, имеет размер 1500 байт.</p>
     <p>Сетевой уровень управляется протоколами IPv4 и IPv6, оба они описываются в приложении А. Из протоколов транспортного уровня мы можем выбирать TCP, UDP и SCRIPT, они описываются в главе 2. На рис. 1.5 изображен разрыв между TCP и UDP; это означает, что приложение может обойти транспортный уровень и использовать IPv4 или IPv6 непосредственно. В таких случаях речь идет о <emphasis>символьных сокетах</emphasis> (<emphasis>raw socket</emphasis>), которые будут описаны в главе 28.</p>
     <p>Три верхних уровня модели OSI соответствуют уровню приложений. Приложением может быть веб-клиент (браузер), клиент Telnet, веб-сервер, сервер FTP или любое другое используемое нами приложение. В случае протоколов Интернета три верхние уровня модели OSI разделяются очень редко.</p>
     <p>Описанный в этой книге API сокетов является интерфейсом между верхними тремя уровнями («приложением») и транспортным уровнем. Это один из важнейших вопросов книги: как создавать приложения, используя сокеты TCP и UDP. Мы уже упоминали о символьных сокетах, и в главе 29 мы увидим, что можем даже полностью обойти уровень IP, чтобы читать и записывать свои собственные кадры канального уровня.</p>
     <p>Почему сокеты предоставляют интерфейс между верхними тремя уровнями модели OSI и транспортным уровнем? Для подобной организации модели OSI имеются две причины, которые мы отобразили на правой стороне рис. 1.5. Прежде всего, три верхних уровня отвечают за все детали, имеющие отношение к приложению (например, FTP, Telnet, HTTP), но знают мало об особенностях взаимодействия по сети. Четыре же нижних уровня знают мало о приложении, но отвечают за все, что связано с коммуникацией: отправку данных, ожидание подтверждения, упорядочивание данных, приходящих не в должном порядке, расчет и проверку контрольных сумм и т.д. Второй же причиной является то, что верхние три уровня часто формируют так называемый <emphasis>пользовательский процесс</emphasis> (<emphasis>user process</emphasis>), в то время как четыре нижних уровня обычно поставляются как часть ядра операционной системы. Unix, как и многие современные операционные системы, обеспечивает разделение пользовательского процесса и ядра. Следовательно, интерфейс между уровнями 4 и 5 является естественным местом для создания API.</p>
    </section>
    <section>
     <title>
      <p>1.8. История сетевого обеспечения BSD</p>
     </title>
     <p>API сокетов происходит от системы 4.2BSD (Berkeley Software Distribution — программное изделие Калифорнийского университета, в данном случае — адаптированная для Интернета реализация операционной системы Unix, разрабатываемая и распространяемая этим университетом), выпущенной в 1983 году. На рис. 1.6 показано развитие различных реализаций BSD и отмечены главные этапы развития TCP/IP. Некоторые изменения API сокетов также имели место в 1990 году в реализации 4.3BSD Reno, когда протоколы OSI были включены в ядро BSD.</p>
     <p>Вертикальная цепочка систем на рис. 1.6 от 4.2BSD до 4.4BSD включает версии, созданные группой исследования компьютерных систем (Computer System Research Group, CSRG) университета Беркли. Для использования этих реализаций требовалось, чтобы у получателя уже была лицензия на исходный код для Unix. Однако весь код сетевых программ — и поддержка на уровне ядра (например, стек протоколов TCP/IP и доменные сокеты Unix, а также интерфейс сокетов), и приложения (такие, как клиенты и серверы Telnet и FTP), были разработаны независимо от кода Unix, созданного AT&amp;T. Поэтому начиная с 1989 года университет Беркли начал выпускать реализации системы BSD, не ограниченные лицензией на исходный код Unix. Эти реализации распространялись свободно и, в конечном итоге, стали доступны через анонимные FTP-серверы фактически любому пользователю Интернета.</p>
     <p>Последними реализациями Беркли стали 4.4BSD-Lite в 1994 году и 4.4BSD-Lite2 в 1995 году. Нужно отметить, что эти две реализации были затем использованы в качестве основы для других систем: BSD/OS, FreeBSD, NetBSD и OpenBSD, и все четыре до сих пор активно развиваются и совершенствуются. Более подробную информацию о различных реализациях BSD, а также общую историю развития различных систем Unix можно найти в главе 1 книги [74].</p>
     <image l:href="#img_6.png"/>
     <p><strong>Рис. 1.6</strong>. История различных реализаций BSD</p>
     <p>Многие системы Unix начинались с некоторой версии сетевого кода BSD, включавшей API сокетов, и мы называем их <emphasis>реализациями, происходящими от Беркли</emphasis>, или <emphasis>Беркли-реализациями</emphasis> (<emphasis>Berkeley-derived implementations</emphasis>). Многие коммерческие версии Unix основаны на Unix System V Release 4 (SVR4). Некоторые из них включают сетевой код из Беркли-реализаций (например, UnixWare 2.x), в то время как сетевой код других систем, основанных на SVR4, был разработан независимо (например, Solaris 2.x). Мы также должны отметить, что система Linux, популярная и свободно доступная реализация Unix, <emphasis>не</emphasis> относится к классу происходящих от Беркли: ее сетевой код и API сокетов были разработаны «с нуля».</p>
    </section>
    <section>
     <title>
      <p>1.9. Сети и узлы, используемые в примерах</p>
     </title>
     <section>
      <p>На рис. 1.7 показаны различные сети и узлы, используемые нами в примерах. Для каждого узла мы указываем операционную систему и тип компьютера (потому, что некоторые операционные системы могут работать на компьютерах разных типов). Внутри прямоугольников приведены имена узлов, появляющиеся в тексте.</p>
      <image l:href="#img_7.png"/>
      <p><strong>Рис. 1.7</strong>. Сети и узлы, используемые в примерах</p>
      <p>Топология, приведенная на рис. 1.7, интересна для наших примеров, но на практике физическая топология сети оказывается не столь важной, поскольку взаимодействующие компьютеры обычно связываются через Интернет. Виртуальные частные сети (virtual private network, VPN) и защищенные подключения интерпретатора (secure shell connections, SSH) обеспечивают соединение, не зависящее от физического размещения компьютеров.</p>
      <p>Обозначение «/24» указывает количество последовательных битов адреса начиная с крайнего левого, задающих сеть и подсеть. В разделе А.4 об этом формате рассказывается более подробно.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Хотим подчеркнуть, что настоящее имя операционной системы Sun — SunOS 5.x, а не Solaris 2.x, однако все называют ее Solaris.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Определение топологии сети</p>
      </title>
      <p>На рис. 1.7 мы показываем топологию сети, состоящей из улов, используемых в качестве примеров в этой книге, но вам нужно знать топологию вашей собственной сети, чтобы запускать в ней примеры и выполнять упражнения. Хотя в настоящее время не существует стандартов Unix в отношении сетевой конфигурации и администрирования, большинство Unix-систем предоставляют две основные команды, которые можно использовать для определения подробностей строения сети: <code>netstat</code> и <code>ifconfig</code>. Мы приводим примеры в различных системах, представленных на рис. 1.7. Изучите руководство, где описаны эти команды для ваших систем, чтобы понять различия в той информации, которую вы получите на выходе. Также имейте в виду, что некоторые производители помещают эти команды в административный каталог, например <code>/sbin</code> или <code>/usr/sbin</code>, вместо обычного <code>/usr/bin</code>, и эти каталоги могут не принадлежать обычному пути поиска (<code>PATH</code>).</p>
      <p>1. <code>netstat - i</code> предоставляет информацию об интерфейсах. Мы также задаем флаг <code>-n</code> для печати численных адресов, а не имен сетей. При этом показываются интерфейсы с их именами.</p>
      <p><code>linux % <strong>netstat -ni</strong></code></p>
      <p><code>Kernel Interface table</code></p>
      <p><code>Iface  MTU Met    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg</code></p>
      <p><code>eth0  1500   0 49211085      0      0      0 40540958      0      0      0 BMRU</code></p>
      <p><code>lo   16436   0 98613572      0      0      0 98613572      0      0      0 LRU</code></p>
      <p>Интерфейс закольцовки называется <code>lo</code>, a Ethernet называется <code>eth0</code>. В следующем примере показан узел с поддержкой Ipv6.</p>
      <p><code>freebsd % <strong>netstat -ni</strong></code></p>
      <p><code>Name  Mtu Network      Address              Ipkts Ierrs    Opkts Oerrs Coll</code></p>
      <p><code>hme0 1500 &lt;Link#1&gt;     08:00:20:a7:68:6b 29100435    35 46561488     0    0</code></p>
      <p><code>hme0 1500 12.106.32/24 12.106.32.254     28746630     - 46617260     -    -</code></p>
      <p><code>hme0 1500 fe80:1::a00:20ff:fea7 686b/64</code></p>
      <p><code>                       fe80:1::a00:20ff:fea7:68b</code></p>
      <p><code>                                                0      -       0     -    -</code></p>
      <p><code>hme0 1500 3ffe:b80:1f8d:1::1/64</code></p>
      <p><code>                       3ffe:b80:1f8d:1::1       0      -        0    –    -</code></p>
      <p><code>hme1 1500 &lt;Link#2&gt;     08:00:20:a7:68:6b    51092      0    31537    0    0</code></p>
      <p><code>hme1 1500 fe80:2::a00:20ff:fea7:686b/64</code></p>
      <p><code>                       fe80:2::a00:20ff:fea7:686b</code></p>
      <p><code>                                                0      -       90    -    -</code></p>
      <p><code>hme1 1500 192.168.42   192.168.42.1         43584      -    24173    -    -</code></p>
      <p><code>hme1 1500 3ffe:b80:1f8d:2::1/64</code></p>
      <p><code>                       3ffe:b80:1f8d:2::1      78      -        8    -    -</code></p>
      <p><code>lo0 16384 &lt;Link#6&gt;                          10198      0    10198    0    0</code></p>
      <p><code>lo0 16384 ::1/128      ::1                     10      -       10    -    -</code></p>
      <p><code>lo0 16384 fe80:6::1/64 fe80:6::1                0      -        0    -    -</code></p>
      <p><code>lo0 16384 127          127.0.0.1            10167      -    10167    -    -</code></p>
      <p><code>gif0 1280 &lt;Link#8&gt;                              6      0        5    0    0</code></p>
      <p><code>gif0 1280 3ffe:b80:3:9ad1::2/128</code></p>
      <p><code>                       3ffe:b80:3:9ad1::2       0      -        0    -    -</code></p>
      <p><code>gif0 1280 fe80:8::a00:20ff:fea7:686b/64</code></p>
      <p><code>                       fe80:8::a00:20ff:fea7:686b</code></p>
      <p><code>                                                0      -        0    -    -</code></p>
      <p>Мы разбили некоторые длинные строки на несколько частей, чтобы сохранить ясность представления.</p>
      <p>2. <code>netstat -r</code> показывает таблицу маршрутизации, которая тоже позволяет определить интерфейсы. Обычно мы задаем флаг <code>-n</code> для печати численных адресов. При этом также приводится IP-адрес маршрутизатора, заданного по умолчанию:</p>
      <p><code>freebsd % <strong>netstat -nr</strong></code></p>
      <p><code>Routing tables</code></p>
      <empty-line/>
      <p><code>Internet:</code></p>
      <p><code>Destination   Gateway           Flags Refs   Use Netif Expire</code></p>
      <p><code>default       12.106.32.1       UGSc    10  6877 hme0</code></p>
      <p><code>12.106.32/24  link#1            UC       3     0 hme0</code></p>
      <p><code>12.106.32.1   00:b0:8e:92:2c:00 UHLW     9     7 hme0  1187</code></p>
      <p><code>12.106.32.253 08:00:20:b8:f7:e0 UHLW     0     1 hme0   140</code></p>
      <p><code>12.106.32.254 08:00:20:a7:68:b6 UHLW     0     2 lo0</code></p>
      <p><code>127.0.0.1     127.0.0.1         UH       1 10167 lo0</code></p>
      <p><code>192.168.42    link#2            UC       2     0 hme1</code></p>
      <p><code>192.168.42.1  08:00:20:a7:68:6b UHLW     0    11 lo0</code></p>
      <p><code>192.168.42.2  00:04:ac:17:bf:38 UHLW     2 24108 hme1   210</code></p>
      <empty-line/>
      <p><code>Internet6:</code></p>
      <p><code>Destination                        Gateway            Flags Netif Expire</code></p>
      <p><code>::/96                              ::1                UGRSc lo0   =&gt;</code></p>
      <p><code>default                            3ffe:b80:3:9ad1::1 UGSc  gif0</code></p>
      <p><code>::1                                ::1                UH    lo0</code></p>
      <p><code>::ffff:0.0.0.0/96                  ::1                UGRSc lo0</code></p>
      <p><code>3ffe:b80:3:9ad1::1                 3ffe:b80:3:9ad1::2 UH    gif0</code></p>
      <p><code>3ffe:b80:3:9ad1::2                 link#8             UHL   lo0</code></p>
      <p><code>3ffe:b80:1f8d::/48                 lo0                USc   lo0</code></p>
      <p><code>3ffe:b80:1f8d:1::/64               link#1             UC    hme0</code></p>
      <p><code>3ffe:b80:1f8d:1::1                 08:00:20:a7:68:6b  UHL   lo0</code></p>
      <p><code>3ffe:b80:1f8d:2::/64               link#2             UC    hme1</code></p>
      <p><code>3ffe:b80:1f8d:2::1                 08:00:20:a7:68:6b  UHL   lo0</code></p>
      <p><code>3ffe:b80:1f8d:2:204:acff:fe17:bf38 00:04.ac:17:bf:38  UHLW  hme1</code></p>
      <p><code>fe80::/10                          ::1                UGRSc lo0</code></p>
      <p><code>fe80::%hme0/64                     link#1             UC    hme0</code></p>
      <p><code>fe80::a00:20ff:fea7:686b%hme0      08:00:20:a7:68:6b  UHL   lo0</code></p>
      <p><code>fe80::%hme1/64                     link#2             UC    hme1</code></p>
      <p><code>fe80::a00:20ff:fea7:686b%hme1      08:00:20:a7:68:6b  UHL   lo0</code></p>
      <p><code>fe80::%lo0/64                      fe80::1%lo0        Uc    lo0</code></p>
      <p><code>fe80::1%lo0                        link#6             UHL   lo0</code></p>
      <p><code>fe80::%gif0/64                     link#8             UC    gif0</code></p>
      <p><code>fe80::a00:20ff:fea7:686b%gif0      link#8             UHL   lo0</code></p>
      <p><code>ff01::/32                          ::1                U     lo0</code></p>
      <p><code>ff02::/16                          ::1                UGRS  lo0</code></p>
      <p><code>ff02::%hme0/32                     link#1             UC    hme0</code></p>
      <p><code>ff02::%hem1/32                     link#2             UC    hme1</code></p>
      <p><code>ff02::%lo0/32                      ::1                UC    lo0</code></p>
      <p><code>ff02::%gif0/32                     link#8             UC    gif0</code></p>
      <p>3. Имея имена интерфейсов, мы выполняем команду <code>ifconfig</code>, чтобы получить подробную информацию для каждого интерфейса:</p>
      <p><code>linux % <strong>ifconfig eth0</strong></code></p>
      <p><code>eth0 Link encap:Ethernet HWaddr 00:C0:9F:06:B0:E1</code></p>
      <p><code>     inet addr:206.168.112.96 Bcast:206.168.112.127 Mask:255.255.255.128</code></p>
      <p><code>     UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1</code></p>
      <p><code>     RX packets:49214397 errors:0 dropped:0 overruns:0 frame:0</code></p>
      <p><code>     TX packets:40543799 errors:0 dropped:0 overruns:0 carrier:0</code></p>
      <p><code>     collisions:0 txqueuelen:100</code></p>
      <p><code>     RX bytes:1098069974 (1047.2 Mb) TX bytes:3360546472 (3204.8 Mb)</code></p>
      <p><code>     Interrupt:11 Base address:0x6000</code></p>
      <p>При этом мы получаем IP-адрес, маску подсети и широковещательный адрес. Флаг <code>MULTICAST</code> указывает на то, что узел поддерживает широковещательную передачу. В некоторых реализациях поддерживается флаг <code>-a</code>, при указании которого печатается информация обо всех сконфигурированных интерфейсах.</p>
      <p>4. Одним из способов определить IP-адрес нескольких узлов локальной сети является проверка широковещательного адреса (найденного нами на предыдущем шаге) с помощью утилиты <code>ping</code>.</p>
      <p><code>linux % <strong>ping -b 206.168.112.127</strong></code></p>
      <p><code>WARNING: pinging broadcast address</code></p>
      <p><code>PING 206.168.112.127 (206.168.112.127) from 206.168.112.96 : 56 (84) bytes of data.</code></p>
      <p><code>64 bytes from 206.168.112.96: icmp_seq=0 ttl=255 time=241 usec</code></p>
      <p><code>64 bytes from 206.168.112.40: icmp_seq=0 ttl=255 time=2 566 msec (DUP!)</code></p>
      <p><code>64 bytes from 206.168.112.118: icmp_seq=0 ttl=255 time=2.973 msec (DUP!)</code></p>
      <p><code>64 bytes from 206.168.112.14: icmp_seq=0 ttl=255 time=3.089 msec (DUP!)</code></p>
      <p><code>64 bytes from 206.168.112.126: icmp_seq=0 ttl=255 time=3.200 msec (DUP!)</code></p>
      <p><code>64 bytes from 206.168.112.71: icmp_seq=0 ttl=255 time=3.311 msec (DUP!)</code></p>
      <p><code>64 bytes from 206.168.112.31: icmp_seq=0 ttl=255 time=3.541 msec (DUP!)</code></p>
      <p><code>64 bytes from 206.168.112.7: icmp_seq=0 ttl=255 time=3.636 msec (DUP!)</code></p>
      <p><code>...</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>1.10. Стандарты Unix</p>
     </title>
     <section>
      <p>Когда писалась эта книга, наибольший интерес в сфере стандартизации Unix вызывала деятельность группы Остина по пересмотру общих стандартов (The Austin Common Standards Revision Group, CSRG). Ими было написано в общей сложности около 4000 страниц спецификаций, описывающих более 1700 интерфейсов программирования. Эти спецификации являются одновременно стандартами IEEE POSIX и The Open Group. Поэтому один и тот же стандарт может встретиться вам под разными названиями, например ISO/IEC 9945:2002, IEEE Std 1003.1-2001 и Single Unix Specification Version 3. В нашей книге мы будем называть этот стандарт просто: спецификация POSIX, за исключением разделов, подобных этому, где обсуждаются особенности различных более старых стандартов.</p>
      <p>Проще всего получить копию этого консолидированного стандарта, заказав ее на компакт-диске или скачав из Сети (бесплатно). В любом случае начинать следует с <code>http://www.UNIX.org/version3</code>.</p>
     </section>
     <section>
      <title>
       <p>История POSIX</p>
      </title>
      <p>Слово «POSIX» представляет собой сокращение от «Portable Operating System Interface» (интерфейс переносимой операционной системы). POSIX — целое семейство стандартов, разрабатываемых организацией IEEE (Institute of Electrical and Electronics Engineers — Институт инженеров по электротехнике и радиоэлектронике). Стандарты POSIX также приняты в качестве международных стандартов ISO (International Standards Organization — Международная организация по стандартизации) и IEC (International Electrotechnical Commission — Международная комиссия по электротехнике), называемых ISO/IEC. История стандартов POSIX достаточно интересна, но мы рассмотрим ее кратко.</p>
      <p>&#9632; Первым из стандартов POSIX был IEEE Std 1003.1-1988 (317 страниц), и он определял интерфейс между языком С и оболочкой ядра типа Unix в следующих областях: примитивы процесса (<code>fork</code>, <code>exec</code>, сигналы, таймеры), среда процесса (идентификаторы пользователя, группы процессов), файлы и каталоги (все функции ввода-вывода), ввод-вывод на терминал, системные базы данных (файлы паролей и групп) и архивные форматы <code>tar</code> и <code>cpio</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Первый стандарт POSIX был пробной версией, выпущенной в 1986 году и известной как IEEE-IX. Название «POSIX» было предложено Ричардом Столлмэном (Richard Stallman).</p>
      </cite>
      <p>&#9632; Следующим был IEEE Std 1003.2-1990 (356 страниц), который стал международным стандартом (ISO/IEC 9945-1:1990). По сравнению с версией 1988 году в версии 1990 года были внесены минимальные изменения. К названию было добавлено «Часть 1: Системный программный интерфейс приложений [язык С]», что указывало, что этот стандарт являлся интерфейсом API, написанным на языке С.</p>
      <p>&#9632; Затем был выпущен двухтомный стандарт IEEE Std 1003.2-1992 (около 1300 страниц). Второй том был озаглавлен «Часть 2: интерпретатор и утилиты» и описывал интерпретатор команд (Основанный на интерпретаторе System V Bourne Shell) и порядка сотни утилит (программ, запускаемых из интерпретатора, от <code>awk</code> и <code>basename</code> до <code>vi</code> и <code>yacc</code>). В тексте мы будем называть этот стандарт <emphasis>POSIX.2</emphasis>.</p>
      <p>&#9632; IEEE Std 1003.1b-1993 (590 страниц) изначально именовался IEEE 1003.4. Он стал дополнением стандарта 1003.1-1990 и включал расширения реального времени, разработанные группой P1003.4. Стандарт 1003.1b-1993 добавил к стандарту 1990 года следующие пункты: синхронизацию файлов, асинхронный ввод-вывод, семафоры, управление памятью (вызов <code>mmap</code> и разделяемая память), планирование выполнения, часы, таймеры и очереди сообщений.</p>
      <p>&#9632; Следующий стандарт POSIX — IEEE Std 1003.1, редакция 1996 года [50], включил в себя 1003.1-1990 (базовый API), 1003.1b-1993 (расширения реального времени), 1003.1с-1995 (функции управления потоками) и 1003.1i-1995 (технические исправления 1003.1b). Этот стандарт также называется ISO/IEC 9945-1:1996. Были добавлены три главы, посвященные программным потокам, и общий объем стандарта составил 743 страницы. В тексте мы будем называть его <emphasis>POSIX.1</emphasis>. В стандарт включено предисловие, где говорится, что стандарт ISO/IEC 9945 состоит из следующих частей:</p>
      <p> &#9633; Часть 1. Системный API [язык С].</p>
      <p> &#9633; Часть 2. Оболочка и утилиты.</p>
      <p> &#9633; Часть 3. Системное администрирование (в стадии разработки).</p>
      <p>Части 1 и 2 — это именно то, что мы называем POSIX.1 и POSIX.2.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Более четверти из 743 страниц отводится приложению, названному «Обоснование и замечания» («Rationale and Notes»). Это обоснование содержит историческую информацию и причины, по которым те или иные функции были включены или опущены. Часто обоснование бывает столь же информативным, как и официальный стандарт.</p>
      </cite>
      <p>&#9632; Стандарт IEEE Std 1003.1g: Protocol Independent Interfaces (PII) (интерфейсы, не зависящие от протокола) был принят в 2000 году. До появления единой спецификации Unix версии 3 этот стандарт имел наибольшее отношение к тематике данной книги, потому что он определяет сетевые API (называя их DNI — Detailed Network Interfaces, подробные сетевые интерфейсы):</p>
      <p> 1) DNI/Socket, основанный на API сокетов 4.4BSD;</p>
      <p> 2) DNI/XTI, основанный на спецификации X/Open XPG4.</p>
      <p>Работа над этим стандартом началась в 80-х (рабочая группа P1003.12, позже переименованная в P1003.1g). В тексте мы будем называть его <emphasis>POSIX.1g</emphasis>.</p>
      <p>Текущее состояние различных стандартов POSIX можно получить в Интернете по адресу <code>http://www.pasc.org/standing/sd11.html</code>.</p>
     </section>
     <section>
      <title>
       <p>История Open Group</p>
      </title>
      <p>The Open Group (Открытая группа) была сформирована в 1996 году объединением организаций X/Open Company (основана в 1984 году) и Open Software Foundation (OSF, основан в 1988 году). Эта группа представляет собой международный консорциум производителей и потребителей из промышленности, правительства и образовательных учреждений. Их стандарты тоже выходили в нескольких версиях.</p>
      <p>&#9632; В 1989 году X/Open опубликовала третий выпуск X/Open Portability Guide (Руководство по разработке переносимых программ) — XPG3.</p>
      <p>&#9632; В 1992 году был опубликован четвертый выпуск (Issue 4), а в 1994 — вторая его версия (Issue 4, Version 2). Последняя известна также под названием Spec 1170, где магическое число 1170 представляет собой сумму количества интерфейсов системы (926), заголовков (70) и команд (174). Есть и еще два названия: X/Open Single Unix Specification (Единая спецификация Unix) и Unix 95.</p>
      <p>&#9632; В марте 1997 года было объявлено о выходе второй версии Единой спецификации Unix. Этот стандарт программного обеспечения называется также Unix 98, и именно так мы называем эту спецификацию далее в тексте книги. Количество интерфейсов в Unix 98 возросло с 1170 до 1434, хотя для рабочей станции это количество достигает 3030, поскольку сюда входит CDE (Common Desktop Environment — общее окружение рабочего стола), которое, в свою очередь, требует системы X Window System и пользовательского интерфейса Motif. Подробно об этом написано в книге [55]. Полезную информацию можно также найти по адресу <code>http://www.UNIX.org/version2</code>. Сетевые службы, входящие в Unix 98, определяются как для API сокетов, так и для XTI. Эта спецификация практически идентична POSIX.1g.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>К сожалению, X/Open обозначает свои сетевые стандарты с помощью аббревиатуры «XNS» — X/Open Networking Services. Например, версия этого документа, в которой определяются сокеты и технологии XTI для Unix 98 [86], называется «XNS Issue 5*. Дело в том, что в мире сетевых технологий аббревиатура «XNS» всегда служила акронимом для «Xerox Network Systems» (сетевые системы Xerox). Поэтому мы избегаем использования акронима «XNS» и называем соответствующий документ X/Open просто стандартом сетевого API Unix 98.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Объединение стандартов</p>
      </title>
      <p>Краткую историю POSIX и The Open Group продолжает опубликованная CSRG третья версия единой спецификации Unix (The Single Unix Specification Version 3), о которой уже шла речь в начале раздела. Добиться принятия единого стандарта пятьюдесятью производителями — заметная веха в истории Unix. Большинство сегодняшних Unix-систем отвечают требованиям какой-либо версии POSIX.1 и POSIX.2, а многие уже соответствуют третьей единой спецификации Unix.</p>
      <p>Исторически для большинства Unix-систем четко прослеживалось родство либо с BSD, либо с SVR4, но различия между современными системами постепенно стираются по мере того, как производители принимают новые стандарты. Наиболее существенные из оставшихся отличий связаны с администрированием систем, которое пока не охватывается никакими стандартами.</p>
      <p>Эта книга основана на третьей версии единой спецификации Unix, причем основное внимание уделяется API сокетов. Везде, где это возможно, мы используем исключительно стандартные функции.</p>
     </section>
     <section>
      <title>
       <p>Internet Engineering Task Force</p>
      </title>
      <p>IETF (Internet Engineering Task Force — группа, отвечающая за решение сетевых инженерных задач) — это большое открытое международное сообщество сетевых разработчиков, операторов, производителей и исследователей, работающих в области развития архитектуры Интернета и более стабильной его работы. Это сообщество открыто для всех желающих.</p>
      <p>Стандарты Интернета документированы в RFC 2026 [13]. Обычно стандарты Интернета описывают протоколы, а не интерфейсы API. Тем не менее два документа RFC (RFC 3493 [36] и RFC 3542 [114]) определяют API сокетов для протокола IP версии 6. Это информационные документы RFC, а не стандарты, и они были выпущены для того, чтобы ускорить применение переносимых приложений различными производителями, работающими с более ранними реализациями IPv6. Разработка текстов стандартов занимает много времени, но в третьей версии единой спецификации многие API были успешно стандартизованы.</p>
     </section>
    </section>
    <section>
     <title>
      <p>1.11. 64-разрядные архитектуры</p>
     </title>
     <p>С середины до конца 90-х годов развивается тенденция к переходу на 64-разрядные архитектуры и 64-разрядное программное обеспечение. Одной из причин является более значительная по размеру адресация внутри процесса (например, 64-разрядные указатели), которая необходима в случае использования больших объемов памяти (более 2<sup>32</sup> байт). Обычная модель программирования для существующих 32-разрядных систем Unix называется <emphasis>ILP32</emphasis>. Ее название указывает на то, что целые числа (I), длинные целые числа (L) и указатели (P) занимают 32 бита. Модель, которая получает все большее распространение для 64-разрядных систем Unix, называется <emphasis>LP64</emphasis>. Ее название говорит о том, что 64 бита требуется только для длинных целых чисел (L) и указателей (P). В табл. 1.5 приводится сравнение этих двух моделей.</p>
     <empty-line/>
     <p><strong>Таблица 1.5</strong>. Сравнение количества битов для хранения различных типов, данных в моделях ILP32 и LP64</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Тип данных</th>
       <th align="left" valign="top">Модель ILP32</th>
       <th align="left" valign="top">Модель LP64</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Char</td>
       <td align="left" valign="top">8</td>
       <td align="left" valign="top">8</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Short</td>
       <td align="left" valign="top">16</td>
       <td align="left" valign="top">16</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Int</td>
       <td align="left" valign="top">32</td>
       <td align="left" valign="top">32</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Long</td>
       <td align="left" valign="top">32</td>
       <td align="left" valign="top">64</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Указатель</td>
       <td align="left" valign="top">32</td>
       <td align="left" valign="top">64</td>
      </tr>
     </table>
     <p>С точки зрения программирования модель LP64 означает, что мы не можем рассматривать указатель как целое число. Мы также должны учитывать влияние модели LP64 на существующие API.</p>
     <p>В ANSI С введен тип данных <code>size_t</code>, который используется, например в качестве аргумента функции <code>malloc</code> (количество байтов, которое данная функция выделяет в памяти для размещения какого-либо объекта), а также как третий аргумент для функций <code>read</code> и <code>write</code> (число считываемых или записываемых байтов). В 32-разрядной системе значение типа <code>size_t</code> является 32-разрядным, но в 64-разрядной системе оно должно быть 64-разрядным, чтобы использовать преимущество большей модели адресации. Это означает, что в 64-разрядной системе, возможно, <code>size_t</code> будет иметь тип <code>unsigned long</code> (целое число без знака, занимающее 32 разряда). Проблемой сетевого интерфейса API является то, что в некоторых проектах по POSIX.1g было определено, что аргументы функции, содержащие размер структур адресов сокета, должны иметь тип <code>size_t</code> (например, третий аргумент в функциях <code>bind</code> и <code>connect</code>). Некоторые поля структуры XTI также имели тип данных <code>long</code> (например, структуры <code>t_info</code> и <code>t_opthdr</code>). Если бы стандарты остались неизменными, в обоих случаях 32-разрядные значения должны были бы смениться 64-разрядными при переходе с модели ILP32 на LP64. В обоих случаях нет никакой необходимости в 64-разрядных типах данных: длина структуры адресов сокета занимает максимум несколько сотен байтов, а использование типа данных <code>long</code> для полей структуры XTI было просто ошибкой.</p>
     <p>Решение состоит в том, чтобы использовать типы данных, разработанные специально для борьбы с подобными проблемами. Интерфейс API сокетов использует тип данных <code>socklen_t</code> для записи длины структур адресов сокетов, a XTI использует типы данных <code>t_scalar_t</code> и <code>t_uscalar_t</code>. Причина, по которой эти 32-разрядные значения не заменяются на 64-разрядные, заключается в том, что таким образом упрощается двоичная совместимость с новыми 64-разрядными системами для приложений, скомпилированных под 32-разрядные системы.</p>
    </section>
    <section>
     <title>
      <p>1.12. Резюме</p>
     </title>
     <p>В листинге 1.1 показан полностью рабочий, хотя и простой, клиент TCP, который получает текущее время и дату с заданного сервера. В листинге 1.5 представлена полная версия сервера. На этих примерах вводятся многие термины и понятия, которые далее рассматриваются более подробно. Наш клиент был зависим от протокола, и мы изменили его, чтобы он использовал IPv6. Но при этом мы получили всего лишь еще одну зависимую от протокола программу. В главе 11 мы разработаем некоторые функции, которые позволят нам написать код, не зависимый от протокола. Это важно, поскольку в Интернете начинает использоваться протокол IPv6. По ходу книги мы будем использовать функции-обертки, созданные в разделе 1.4, для уменьшения размера нашего кода, хотя по-прежнему каждый вызов функции будет проходить проверку на предмет возвращения ошибки. Все имена наших функций-оберток начинаются с заглавной буквы.</p>
     <p>Третья версия единой спецификации Unix, известная также под несколькими другими названиями (мы называем ее просто «Спецификация POSIX»), представляет собой результат слияния двух стандартов, осуществленного The Austin Group.</p>
     <p>Читатели, которых интересует история сетевого программирования в Unix, должны изучить прежде всего историю развития Unix, а история TCP/IP и Интернета представлена в книге [106].</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Проделайте все шаги, описанные в конце раздела 1.9, чтобы получить информацию о топологии вашей сети.</p>
     <p>2. Отыщите исходный код для примеров в тексте (см. предисловие). Откомпилируйте и протестируйте клиент времени и даты, представленный в листинге 1.1. Запустите программу несколько раз, задавая каждый раз различные IP- адреса в командной строке.</p>
     <p>3. Замените первый аргумент функции <code>socket</code>, представленной в листинге 1.1, на 9999. Откомпилируйте и запустите программу. Что происходит? Найдите значение <code>errno</code>, соответствующее выданной ошибке. Как вы можете получить дополнительную информацию по этой ошибке?</p>
     <p>4. Измените листинг 1.1: поместите в цикл <code>while</code> счетчик, который будет считать, сколько раз функция <code>read</code> возвращает значение, большее нуля. Выведите значение счетчика перед завершением. Откомпилируйте и запустите новую программу-клиент.</p>
     <p>5. Измените листинг 1.5 следующим образом. Сначала поменяйте номер порта, заданный функции <code>sin_port</code>, с 13 на 9999. Затем замените один вызов функции <code>write</code> на циклический, при котором функция <code>write</code> вызывается для каждого байта результирующей строки. Откомпилируйте полученный сервер и запустите его в фоновом режиме. Затем измените клиент из предыдущего упражнения (в котором выводится счетчик перед завершением программы), изменив номер порта, заданный функции <code>sin_port</code>, с 13 на 9999. Запустите этот клиент, задав в качестве аргумента командной строки IP-адрес узла, на котором работает измененный сервер. Какое значение клиентского счетчика будет напечатано? Если это возможно, попробуйте также запустить клиент и сервер на разных узлах.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 2</p>
     <p>Транспортный уровень: TCP, UDP и SCRIPT</p>
    </title>
    <section>
     <title>
      <p>2.1. Введение</p>
     </title>
     <p>В этой главе приводится обзор протоколов семейства TCP/IP, которые используются в примерах на всем протяжении книги. Наша цель — как можно подробнее описать эти протоколы с точки зрения сетевого программирования, чтобы понять, как их использовать, а также дать ссылки на более подробные описания фактического устройства, реализации и истории протоколов.</p>
     <p>В данной главе речь пойдет о транспортном уровне: протоколах TCP, UDP и протоколе управления передачей потоков (Stream Control Transmission Protocol, SCRIPT). Большинство приложений, построенных по архитектуре клиент-сервер, используют либо TCP, либо UDP. Протоколы транспортного уровня, в свою очередь, используют протокол сетевого уровня IP — либо IPv4, либо IPv6. Хотя и возможно использовать IPv4 или IPv6 непосредственно, минуя транспортный уровень, эта технология (символьные сокеты) используется гораздо реже. Поэтому мы даем более подробное описание IPv4 и IPv6 наряду с ICMPv4 и ICMPv6 в приложении А.</p>
     <p>UDP представляет собой простой и ненадежный протокол передачи дейтаграмм, в то время как TCP является сложным и надежным потоковым протоколом. SCRIPT тоже обеспечивает надежность передачи, как и TCP, но помимо этого он позволяет задавать границы сообщений, обеспечивает поддержку множественной адресации на транспортном уровне, а также минимизирует блокирование линии в начале передачи. Нужно понимать, какие сервисы предоставляют приложениям транспортные протоколы, какие задачи решаются протоколами, а что необходимо реализовывать в приложении.</p>
     <p>Есть ряд свойств TCP, которые при должном понимании упрощают написание надежных клиентов и серверов. Знание этих особенностей облегчит нам отладку наших клиентов и серверов с использованием общеупотребительных средств, таких как <code>netstat</code>. В этой главе мы коснемся различных тем, попадающих в эту категорию: трехэтапное рукопожатие TCP, последовательность прерывания соединения TCP, состояние TCP TIME_WAIT, четырехэтапное рукопожатие и завершение соединения SCRIPT, буферизация TCP, UDP и SCRIPT уровнем сокетов и так далее.</p>
    </section>
    <section>
     <title>
      <p>2.2. Обзор протоколов TCP/IP</p>
     </title>
     <p>Хотя набор протоколов и называется «TCP/IP», это семейство состоит не только из собственно протоколов TCP и IP. На рис. 2.1 представлен обзор этих протоколов.</p>
     <image l:href="#img_8.png"/>
     <p><strong>Рис. 2.1</strong>. Обзор протоколов семейства TCP/IP</p>
     <p>На этом рисунке представлены и IPv4, и IPv6. Если рассматривать этот рисунок справа налево, то пять приложений справа используют IPv6. О константе <code>AF_INET6</code> и структуре <code>sockaddr_in6</code> мы говорим в главе 3. Следующие шесть приложений используют IPv4.</p>
     <p>Приложение, находящееся в самой левой части рисунка, <code>tcpdump</code>, соединяется непосредственно с канальным уровнем, используя либо BPF (BSD Packet Filter — фильтр пакетов BSD), либо DLPI (Data Link Provider Interface — интерфейс канального уровня). Мы обозначили штриховую горизонтальную линию под девятью приложениями (интерфейс) как <emphasis>API</emphasis>, что обычно соответствует сокетам или XTI. Интерфейс и к BPF, и к DLPI не использует сокетов или XTI.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Здесь существует исключение, описанное нами в главе 25: Linux предоставляет доступ к канальному уровню при помощи специального типа сокета, называемого SOCK PACKET.</p>
     </cite>
     <p>На рис. 2.1 мы также отмечаем, что программа <code>traceroute</code> использует два сокета: один для IP, другой для ICMP. В главе 25 мы создадим версии IPv4 и IPv6 утилит <code>ping</code> и <code>traceroute</code>.</p>
     <p>А сейчас мы опишем каждый из протоколов, представленных на рисунке.</p>
     <p>&#9632; <emphasis>Протокол Интернета версии 4</emphasis>. IPv4 (Internet Protocol, version 4), который мы часто обозначаем просто как IP, был «рабочей лошадкой» набора протоколов Интернета с начала 80-х. Он использует 32-разрядную адресацию (см. раздел А.4). IPv4 предоставляет сервис доставки пакетов для протоколов TCP, UDP, SCRIPT, ICMP и IGMP.</p>
     <p>&#9632; <emphasis>Протокол Интернета версии 6</emphasis>. IPv6 (Internet Protocol, version 6) был разработан в середине 90-х как замена протокола IPv4. Главным изменением является увеличение размера адреса, в случае IPv6 равного 128 бит (см. раздел А.5) для работы с бурно развивавшимся в 90-е годы Интернетом. IPv6 предоставляет сервис доставки пакетов для протоколов TCP, UDP, SCRIPT и ICMPv6.</p>
     <p>Мы часто используем аббревиатуру «IP» в словосочетаниях типа «IP-адрес», «IP-уровень», когда нет необходимости различать IPv4 и IPv6.</p>
     <p>&#9632; <emphasis>Протокол управления передачей</emphasis>. TCP (Transmission Control Protocol) является протоколом, ориентированным на установление соединения и предоставляющим надежный двусторонний байтовый поток использующим его приложениям. Сокеты TCP — типичный пример <emphasis>потоковых сокетов</emphasis> (<emphasis>stream sockets</emphasis>). TCP обеспечивает отправку и прием подтверждений, обработку тайм-аутов, повторную передачу и тому подобные возможности. Большинство прикладных программ в Интернете используют TCP. Заметим, что TCP может использовать как IPv4, так и Ipv6.</p>
     <p>&#9632; <emphasis>Протокол пользовательских дейтаграмм</emphasis>. UDP (User Datagram Protocol) — это протокол, не ориентированный на установление соединения. Сокеты UDP служат примером дейтаграммных сокетов (datagram sockets). В отличие от TCP, который является надежным протоколом, в данном случае отнюдь не гарантируется, что дейтаграммы UDP когда-нибудь достигнут заданного места назначения. Как и в случае TCP, протокол UDP может использовать как IPv4, так и IPv6.</p>
     <p>&#9632; <emphasis>Протокол управления передачей потоков</emphasis>. SCRIPT (Stream Control Transmission Protocol) — ориентированный на установление соединения протокол, предоставляющий надежную двустороннюю ассоциацию. Соединение по протоколу SCRIPT называется <emphasis>ассоциацией</emphasis> (<emphasis>association</emphasis>), потому что это многоканальный протокол, позволяющий задать несколько IP-адресов и один порт для каждой стороны соединения. SCRIPT предоставляет также сервис сообщений, то есть разграничение отдельных записей в передаваемом потоке. Как и другие транспортные протоколы, SCRIPT может использовать IPv4 и IPv6, но он отличается тем, что может работать с обеими версиями IP на одной и той же ассоциации.</p>
     <p>&#9632; <emphasis>Протокол управляющих сообщений Интернета</emphasis>. ICMP (Internet Control Message Protocol) обеспечивает передачу управляющей информации и сведений об ошибках между маршрутизаторами и узлами. Эти сообщения обычно генерируются и обрабатываются самостоятельно сетевым программным обеспечением TCP/IP, а не пользовательскими процессами, хотя мы и приводим в качестве примера программы <code>ping</code> и <code>traceroute</code>, использующие ICMP. Иногда мы называем этот протокол «ICMPv4», чтобы отличать его от ICMPv6.</p>
     <p>&#9632; <emphasis>Протокол управления группами Интернета</emphasis>. IGMP (Internet Group Management Protocol) используется для многоадресной передачи (см. главу 21), поддержка которой не является обязательной для IPv4.</p>
     <p>&#9632; <emphasis>Протокол разрешения адресов</emphasis>. ARP (Address Resolution Protocol) ставит в соответствие аппаратному адресу (например, адресу Ethernet) адрес IPv4. ARP обычно используется в широковещательных сетях, таких как Ethernet, Token-ring и FDDI, но не нужен в сетях типа «точка-точка» (point-to-point).</p>
     <p>&#9632; <emphasis>Протокол обратного разрешения адресов</emphasis>. RARP (Reverse Address Resolution Protocol) ставит в соответствие адресу IPv4 аппаратный адрес. Он иногда используется, когда загружается бездисковый узел.</p>
     <p>&#9632; <emphasis>Протокол управляющих сообщений Интернета, версия 6</emphasis>. ICMPv6 (Internet Control Message Protocol, version 6) объединяет возможности протоколов ICMPv4, IGMP и ARP.</p>
     <p>&#9632; <emphasis>Фильтр пакетов BSD</emphasis>. Этот интерфейс предоставляет доступ к канальному уровню для процесса. Обычно он поддерживается ядрами, произошедшими от BSD.</p>
     <p>&#9632; <emphasis>Интерфейс провайдера канального уровня</emphasis>. DLPI (Datalink Provider Interface) предоставляет доступ к канальному уровню и обычно предоставляется SVR4 (System V Release 4).</p>
     <p>Все протоколы Интернета определяются в документах <emphasis>RFC</emphasis> (<emphasis>Request For Comments</emphasis>), которые играют роль формальной спецификации. Решение к упражнению 2.1 показывает, как можно получить документы RFC.</p>
     <p>Мы используем термины узел <emphasis>IPv4/IPv6</emphasis> (<emphasis>IPv4/IPv6 host</emphasis>) и <emphasis>узел с двойным стеком</emphasis> (<emphasis>dual-stack host</emphasis>) для определения узла, поддерживающего как IPv4, так и IPv6.</p>
     <p>Дополнительные подробности собственно по протоколам TCP/IP можно найти в [111]. Реализация TCP/IP в 4.4BSD описывается в [128].</p>
    </section>
    <section>
     <title>
      <p>2.3. UDP: протокол пользовательских дейтаграмм</p>
     </title>
     <p>UDP — это простой протокол транспортного уровня. Он описывается в документе RFC 768 [93]. Приложение записывает в сокет UDP <emphasis>дейтаграмму</emphasis> (<emphasis>datagram</emphasis>), которая <emphasis>инкапсулируется</emphasis> (<emphasis>encapsulate</emphasis>) или, иначе говоря, упаковывается либо в дейтаграмму IPv4, либо в дейтаграмму IPv6, и затем посылается к пункту назначения. При этом не гарантируется, что дейтаграмма UDP когда-нибудь дойдет до указанного пункта назначения.</p>
     <p>Проблема, с которой мы сталкиваемся в процессе сетевого программирования с использованием UDP, заключается в его недостаточной надежности. Если мы хотим быть уверены в том, что дейтаграмма дошла до места назначения, мы должны встроить в наше приложение множество функций: подтверждение приема, тайм-ауты, повторные передачи и т.п.</p>
     <p>Каждая дейтаграмма UDP имеет конкретную длину, и мы можем рассматривать дейтаграмму как <emphasis>запись</emphasis> (<emphasis>record</emphasis>). Если дейтаграмма корректно доходит до места назначения (то есть пакет приходит без ошибки контрольной суммы), длина дейтаграммы передается принимающему приложению. Мы уже отмечали, что TCP является <emphasis>потоковым</emphasis> (<emphasis>byte-stream</emphasis>) протоколом, без каких бы то ни было границ записей (см. раздел 1.2), что отличает его от UDP.</p>
     <p>Мы также отметили, что UDP предоставляет сервис, не ориентированный на <emphasis>установление соединения</emphasis> (<emphasis>connectionless</emphasis>), поскольку нет необходимости в установлении долгосрочной связи между клиентом и сервером UDP. Например, клиент UDP может создать сокет и послать дейтаграмму данному серверу, а затем срезу же послать через тот же сокет дейтаграмму другому серверу. Аналогично, сервер UDP может получить пять дейтаграмм подряд через один и тот же сокет UDP от пяти различных клиентов.</p>
    </section>
    <section>
     <title>
      <p>2.4. TCP: протокол контроля передачи</p>
     </title>
     <p>Сервис, предоставляемый приложению протоколом TCP, отличается от сервиса, предоставляемого протоколом UDP. TCP описывается в документах RFC 793 [96], RFC 1323 [53], RFC 2581 [4], RFC 2988 [91] и RFC 3390 [2]. Прежде всего, TCP обеспечивает установление <emphasis>соединений</emphasis> (<emphasis>connections</emphasis>) между клиентами и серверами. Клиент TCP устанавливает соединение с выбранным сервером, обменивается с ним данными по этому соединению и затем разрывает соединение.</p>
     <p>TCP также обеспечивает <emphasis>надежность</emphasis> (<emphasis>reliability</emphasis>). Когда TCP отправляет данные на другой конец соединения, он требует, чтобы ему было выслано подтверждение получения. Если подтверждение не приходит, TCP автоматически передает данные повторно и ждет в течение большего количества времени. После некоторого числа повторных передач TCP оставляет эти попытки. В среднем суммарное время попыток отправки данных занимает от 4 до 10 минут (в зависимости от реализации).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>TCP не гарантирует получение данных адресатом, поскольку это в принципе невозможно. Если доставка оказывается невозможной, TCP уведомляет об этом пользователя, прекращая повторную передачу и разрывая соединение. Следовательно, TCP нельзя считать протоколом, надежным на 100%: он обеспечивает надежную доставку данных или надежное уведомление о неудаче.</p>
     </cite>
     <p>TCP содержит алгоритмы, позволяющие динамически прогнозировать <emphasis>время (период) обращения</emphasis> (<emphasis>round-trip time</emphasis>, RTT) между клиентом и сервером, и таким образом определять, сколько времени необходимо для получения подтверждения. Например, RTT в локальной сети может иметь значение порядка миллисекунд, в то время как для глобальной сети (WAN) эта величина может достигать нескольких секунд. Более того, TCP постоянно пересчитывает величину RTT, поскольку она зависит от сетевого трафика.</p>
     <p>TCP также <emphasis>упорядочивает</emphasis> (<emphasis>sequences</emphasis>) данные, связывая некоторый порядковый номер с каждым отправляемым им байтом. Предположим, например, что приложение записывает 2048 байт в сокет TCP, что приводит к отправке двух сегментов TCP. Первый из них содержит данные с порядковыми номерами 1-1024, второй — с номерами 1025-2048. (<emphasis>Сегмент</emphasis> (<emphasis>segment</emphasis>) — это блок данных, передаваемых протоколом TCP протоколу IP.) Если какой-либо сегмент приходит вне очереди (то есть если нарушается последовательность сегментов), принимающий TCP заново упорядочит сегменты, основываясь на их порядковых номерах, перед тем как отправить данные принимающему приложению. Если TCP получает дублированные данные (допустим, компьютер на другом конце ошибочно решил, что сегмент был потерян, и передал его заново, когда на самом деле он потерян не был, просто сеть была перегружена), он может определить, что данные были дублированы (исходя из порядковых номеров), и дублированные данные будут проигнорированы.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Протокол UDP не обеспечивает надежности. UDP сам по себе не имеет ничего похожего на описанные подтверждения передачи, порядковые номера, определение RTT, тайм-ауты или повторные передачи. Если дейтаграмма UDP дублируется в сети, на принимающий узел могут быть доставлены два экземпляра. Также, если клиент UDP отправляет две дейтаграммы в одно и то же место назначения, их порядок может быть изменен сетью, и они будут доставлены с нарушением исходного порядка. Приложения UDP должны самостоятельно обрабатывать все подобные случаи, как это показано в разделе 22.5.</p>
     </cite>
     <p>TCP обеспечивает <emphasis>управление потоком</emphasis> (<emphasis>flow control</emphasis>). TCP всегда сообщает своему собеседнику, сколько именно байтов он хочет получить от него. Это называется объявлением <emphasis>окна</emphasis> (<emphasis>window</emphasis>). В любой момент времени окно соответствует свободному пространству в буфере получателя. Управление потоком гарантирует, что отправитель не переполнит этот буфер. Окно изменяется динамически с течением времени: по мере того как приходят данные от отправителя, размер окна уменьшается, но по мере считывания принимающим приложением данных из буфера окно увеличивается. Возможно, что окно станет нулевым: если принимающий буфер TCP для данного сокета заполнен, отправитель должен подождать, когда приложение считает данные из буфера.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>UDP не обеспечивает управления потоком. Быстрый отправитель UDP может передавать дейтаграммы с такой скоростью, с которой не может работать получатель UDP, как это показано в разделе 8.13.</p>
     </cite>
     <p>Наконец, соединение TCP также является <emphasis>двусторонним</emphasis> (<emphasis>full-duplex</emphasis>). Это значит, что приложение может отправлять и принимать данные в обоих направлениях на заданном соединении в любой момент времени. Иначе говоря, TCP должен отслеживать состояние таких характеристик, как порядковые номера и размеры окна, для каждого направления потока данных: отправки и приема. После установления двустороннее соединение может быть преобразовано в одностороннее (см. раздел 6.6).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>UDP может быть (а может и не быть) двусторонним.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>2.5. SCRIPT: протокол управления передачей потоков</p>
     </title>
     <p>Сервисы, предоставляемые SCRIPT, имеют много общего с сервисами TCP и UDP. Протокол SCRIPT описывается в RFC 2960 [118] и RFC 3309 [119]. Введение в SCRIPT приводится в RFC 3286 [85]. SCRIPT ориентирован на создание ассоциаций между клиентами и серверами. Кроме того, SCRIPT предоставляет приложениям надежность, упорядочение данных, управление передачей и двустороннюю связь, подобно TCP. Слово «ассоциация» используется вместо слова «соединение» намеренно, потому что соединение всегда устанавливалось между двумя IP-адресами. Ассоциация означает взаимодействие двух систем, которые могут иметь по несколько адресов (это называется multihoming — множественная адресация).</p>
     <p>В отличие от TCP, протокол SCRIPT ориентирован не на поток байтов, а на сообщения. Он обеспечивает упорядоченную доставку отдельных записей. Как и в UDP, длина сообщения, записанная отправителем, передается приложению-получателю.</p>
     <p>SCRIPT может поддерживать несколько потоков между конечными точками ассоциации, для каждого из которых надежность и порядок сообщений контролируются отдельно. Утрата сообщения в одном из потоков не блокирует доставку сообщений по другим потокам. Этот подход прямо противоположен тому, что имеется в TCP, где потеря единственного байта блокирует доставку всех последующих байтов по соединению до тех пор, пока ситуация не будет исправлена.</p>
     <p>Кроме того, SCRIPT поддерживает множественную адресацию, что позволяет единственной конечной точке SCRIPT иметь несколько IP-адресов. Эта функция обеспечивает дополнительную устойчивость в случае отказов сети. Конечная точка может иметь избыточные IP-адреса, каждый из которых может соответствовать собственному соединению с инфраструктурой Интернета. В такой конфигурации SCRIPT позволит обойти проблему, возникшую на одном из адресов, благодаря переключению на другой адрес, заранее связанный с соответствующей ассоциацией SCRIPT.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Подобной устойчивости можно достичь и с TCP, если воспользоваться протоколами маршрутизации. Например, BGP-соединения внутри домена (iBGP) часто используют адреса, назначаемые виртуальному интерфейсу маршрутизатора в качестве конечных точек соединения TCP. Протокол маршрутизации домена гарантирует, что если между двумя маршрутизаторами будет хоть какой-то доступный путь, он будет использован, что было бы невозможно, если бы используемые адреса принадлежали интерфейсу в сети, где возникли проблемы. Функция множественной адресации SCRIPT позволяет узлам (а не только маршрутизаторам) использовать аналогичный подход, причем даже с подключениями через разных провайдеров, что невозможно при использовании TCP с маршрутизацией.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>2.6. Установление и завершение соединения TCP</p>
     </title>
     <section>
      <p>Чтобы облегчить понимание функций <code>connect</code>, <code>accept</code> и <code>close</code> и чтобы нам было легче отлаживать приложения TCP с помощью программы <code>netstat</code>, мы должны понимать, как устанавливаются и завершаются соединения TCP. Мы также должны понимать диаграмму перехода состояний TCP.</p>
     </section>
     <section>
      <title>
       <p>Трехэтапное рукопожатие</p>
      </title>
      <p>При установлении соединения TCP действия развиваются по следующему сценарию.</p>
      <p>1. Сервер должен быть подготовлен для того, чтобы принять входящее соединение. Обычно это достигается вызовом функций <code>socket</code>, <code>bind</code> и <code>listen</code> и называется <emphasis>пассивным открытием</emphasis> (<emphasis>passive open</emphasis>).</p>
      <p>2. Клиент выполняет <emphasis>активное открытие</emphasis> (<emphasis>active open</emphasis>), вызывая функцию <code>connect</code>. Это заставляет клиента TCP послать сегмент SYN (от слова synchronize — синхронизировать), чтобы сообщить серверу начальный порядковый номер данных, которые клиент будет посылать по соединению. Обычно с сегментом SYN не посылается никаких данных: он содержит только заголовок IP, заголовок TCP и, возможно, параметры TCP (о которых мы вскоре поговорим).</p>
      <p>3. Сервер должен подтвердить получение клиентского сегмента SYN, а также должен послать свой собственный сегмент SYN, содержащий начальный порядковый номер для данных, которые сервер будет посылать по соединению. Сервер посылает SYN и ACK — подтверждение приема (от слова acknowledgment) клиентского SYN — в виде единого сегмента.</p>
      <p>4. Клиент должен подтвердить получение сегмента SYN сервера.</p>
      <p>Для подобного обмена нужно как минимум три пакета, поэтому он называется <emphasis>трехэтапным рукопожатием TCP</emphasis> (<emphasis>TCP three-way handshake</emphasis>). На рис. 2.2 представлена схема такого обмена.</p>
      <image l:href="#img_9.png"/>
      <p><strong>Рис. 2.2</strong>. Трехэтапное рукопожатие TCP</p>
      <p>Мы обозначаем начальный порядковый номер клиента как <emphasis>J</emphasis>, а начальный порядковый номер сервера как <emphasis>K</emphasis>. Номер подтверждения в сегменте ACK — это следующий предполагаемый порядковый номер на том конце связи, который отправил сегмент ACK. Поскольку сегмент SYN занимает 1 байт пространства порядковых номеров, номер подтверждения в сегменте ACK каждого сегмента SYN — это начальный порядковый номер плюс один. Аналогично сегмент ACK каждого сегмента FIN — это порядковый номер сегмента FIN плюс один.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Повседневной аналогией установления соединения TCP может служить система телефонной связи [81]. Функция socket эквивалентна включению используемого телефона. Функция bind дает возможность другим узнать ваш телефонный номер, чтобы они могли позвонить вам. Функция listen включает звонок, и вы можете услышать, когда происходит входящий звонок. Функция connect требует, чтобы мы знали чей-то номер телефона и могли до него дозвониться. Функция accept — аналогия ответа на входящий звонок. Получение идентифицирующих данных, возвращаемых функцией accept (где идентифицирующие данные — это IP-адрес и номер порта клиента), аналогично получению информации, идентифицирующей вызывающего по телефону — его телефонного номера. Однако имеется отличие, и состоит оно в том, что функция accept возвращает идентифицирующие данные клиента только после того, как соединение установлено, тогда как во время телефонного звонка после указания номера телефона звонящего мы можем выбрать, отвечать на звонок или нет. Служба DNS (см. главу 11) предоставляет сервис, аналогичный телефонной книге. Вызов getaddrinfo — поиск телефонного номера в книге; getnameinfo — поиск имени по телефонному номеру (правда, такая книга должна быть отсортирована по номерам, а не по именам).</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Параметры TCP</p>
      </title>
      <p>Каждый сегмент SYN может содержать параметры TCP. Ниже перечислены наиболее общеупотребительные параметры TCP.</p>
      <p>&#9632; <emphasis>Параметр MSS</emphasis>. Этот параметр TCP позволяет узлу, отправляющему сегмент SYN, объявить свой максимальный размер сегмента (maximum segment size, MSS) — максимальное количество данных, которое он будет принимать в каждом сегменте TCP на этом соединении. Мы покажем, как получить и установить этот параметр TCP с помощью параметра сокета <code>TCP_MAXSEG</code> (см. раздел 7.9).</p>
      <p>&#9632; <emphasis>Параметр масштабирования окна</emphasis> (<emphasis>Window scale option</emphasis>). Максимальный размер окна, который может быть установлен в заголовке TCP, равен 65 535, поскольку соответствующее поле занимает 16 бит. Но высокоскоростные соединения (45 Мбит/с и больше, как описано в документе RFC 1323 [53]) или линии с большой задержкой (спутниковые сети) требуют большего размера окна для получения максимально возможной пропускной способности. Этот параметр, появившийся не так давно, определяет, что объявленная в заголовке TCP величина окна должна быть отмасштабирована — сдвинута влево на 0-14 разрядов, предоставляя максимально возможное окно размером почти гигабайт (65 535 &#215; 2<sup>14</sup>). Для использования параметра масштабирования окна в соединении необходима его поддержка обоими связывающимися узлами. Мы увидим, как задействовать этот параметр с помощью параметра сокета <code>SO_RCVBUF</code> (см. раздел 7.5).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Чтобы обеспечить совместимость с более ранними реализациями, в которых не поддерживается этот параметр, применяются следующие правила. TCP может отправить параметр со своим сегментом SYN в процессе активного открытия сокета. Но он может масштабировать свое окно, только если другой конец связи также отправит соответствующий параметр со своим сегментом SYN. Эта логика предполагает, что недоступные в данной реализации параметры просто игнорируются. Это общее и необходимое требование, но, к сожалению, его выполнение не гарантировано для всех реализаций.</p>
      </cite>
      <p>&#9632; <emphasis>Временная метка</emphasis> (<emphasis>Timestamp option</emphasis>). Этот параметр необходим для высокоскоростных соединений, чтобы предотвратить возможное повреждение данных, вызванное приходом устаревших, задержавшихся и дублированных пакетов. Поскольку это один из недавно появившихся параметров, его обработка производится аналогично параметру масштабирования окна. С точки зрения сетевого программиста, этот параметр не должен вызывать беспокойства.</p>
      <p>Перечисленные выше параметры поддерживаются большинством реализаций. Последние два параметра иногда называются «параметрами RFC 1323», они были описаны именно этим стандартом [53]. Они также часто именуются параметрами для «канала с повышенной пропускной способностью», поскольку сеть с широкой полосой пропускания или с большой задержкой называется <emphasis>каналом с повышенной пропускной способностью</emphasis>, или, если перевести дословно, <emphasis>длинной толстой трубой</emphasis> (<emphasis>long fat pipe</emphasis>). В главе 24 [111] эти новые параметры описаны более подробно.</p>
     </section>
     <section>
      <title>
       <p>Завершение соединения TCP</p>
      </title>
      <p>В то время как для установления соединения необходимо три сегмента, для его завершения требуется четыре сегмента.</p>
      <p>1. Одно из приложений первым вызывает функцию <code>close</code>, и мы в этом случае говорим, что конечная точка TCP выполняет <emphasis>активное закрытие</emphasis> (<emphasis>active close</emphasis>). TCP этого узла отправляет сегмент FIN, обозначающий прекращение передачи данных.</p>
      <p>2. Другой узел, получающий сегмент FIN, выполняет <emphasis>пассивное закрытие</emphasis> (<emphasis>passive close</emphasis>). Полученный сегмент FIN подтверждается TCP. Получение сегмента FIN также передается приложению как признак конца файла (после любых данных, которые уже стоят в очереди, ожидая приема приложением), поскольку получение приложением сегмента FIN означает, что оно уже не получит никаких дополнительных данных по этому соединению.</p>
      <p>3. Через некоторое время после того как приложение получило признак конца файла, оно вызывает функцию <code>close</code> для закрытия своего сокета. При этом его TCP отправляет сегмент FIN.</p>
      <p>4. TCP системы, получающей окончательный сегмент FIN (то есть того узла, на котором произошло активное закрытие), подтверждает получение сегмента FIN.</p>
      <p>Поскольку сегменты FIN и ACK передаются в обоих направлениях, обычно требуется четыре сегмента. Мы используем слово «обычно», поскольку в ряде сценариев сегмент FIN на первом шаге отправляется вместе с данными. Кроме того, сегменты, отправляемые на шаге 2 и 3, исходят с узла, выполняющего пассивное закрытие, и могут быть объединены. Соответствующие пакеты изображены на рис. 2.3.</p>
      <image l:href="#img_10.png"/>
      <p><strong>Рис. 2.3</strong>. Обмен пакетами при завершении соединения TCP</p>
      <p>Сегмент FIN занимает 1 байт пространства порядковых номеров аналогично SYN. Следовательно, сегмент ACK каждого сегмента FIN — это порядковый номер FIN плюс один.</p>
      <p>Возможно, что между шагами 2 и 3 какие-то данные будут переданы от узла, выполняющего пассивное закрытие, к узлу, выполняющему активное закрытие. Это состояние называется <emphasis>частичным закрытием</emphasis> (<emphasis>half-close</emphasis>), и мы рассмотрим его во всех подробностях вместе с функцией <code>shutdown</code> в разделе 6.6.</p>
      <p>Отправка каждого сегмента FIN происходит при закрытии сокета. Мы говорили, что для этого приложение вызывает функцию <code>close</code>, но нужно понимать, что когда процесс Unix прерывается либо произвольно (при вызове функции <code>exit</code> или при возврате из функции <code>main</code>), либо непроизвольно (при получении сигнала, прерывающего процесс), все его открытые дескрипторы закрываются, что также вызывает отправку сегмента FIN любому соединению TCP, которое все еще открыто.</p>
      <p>Хотя на рис. 2.3 мы продемонстрировали, что активное закрытие выполняет клиент, на практике активное закрытие может выполнять любой узел: и клиент, и сервер. Часто активное закрытие выполняет клиент, но с некоторыми протоколами (особенно HTTP) активное закрытие выполняет сервер.</p>
     </section>
     <section>
      <title>
       <p>Диаграмма состояний TCP</p>
      </title>
      <p>Последовательность действий TCP во время установления и завершения соединения можно определить с помощью <emphasis>диаграммы состояний TCP</emphasis> (<emphasis>state transition diagram</emphasis>). Ее мы изобразили на рис. 2.4.</p>
      <image l:href="#img_11.png"/>
      <p><strong>Рис. 2.4</strong>. Диаграмма состояний TCP</p>
      <p>Для соединения определено 11 различных состояний, а правила TCP предписывают переходы от одного состояния к другому в зависимости от текущего состояния и сегмента, полученного в этом состоянии. Например, если приложение выполняет активное открытие в состоянии CLOSED (Закрыло), TCP отправляет сегмент SYN, и новым состоянием становится SYN_SENT (Отправлен SYN). Если затем TCP получает сегмент SYN с сегментом ACK, он отправляет сегмент ACK, и следующим состоянием становится ESTABLISHED (Соединение установлено). В этом последнем состоянии проходит большая часть обмена данными.</p>
      <p>Две стрелки, идущие от состояния ESTABLISHED, относятся к разрыву соединения. Если приложение вызывает функцию close перед получением признака конца файла (активное закрытие), происходит переход к состоянию FIN_WAIT_1 (Ожидание FIN 1). Но если приложение получает сегмент FIN в состоянии ESTABLISHED (пассивное закрытие), происходит переход в состояние CLOSE_WAIT (Ожидание закрытия).</p>
      <p>Мы отмечаем нормальные переходы клиента с помощью более толстой сплошной линии, а нормальные переходы сервера — с помощью штриховой линии. Мы также должны отметить, что существуют два перехода, о которых мы не говорили: одновременное открытие (когда оба конца связи отправляют сегменты SYN приблизительно в одно время, и эти сегменты пересекаются в сети) и одновременное закрытие (когда оба конца связи отправляют сегменты FIN). В главе 18 [111] содержатся примеры и описания обоих этих сценариев, которые хотя и возможны, но встречаются достаточно редко.</p>
      <p>Одна из причин, по которым мы приводим здесь диаграмму перехода состояний, — мы хотим показать все 11 состояний TCP и их названия. Эти состояния отображаются программой <code>netstat</code>, которая является полезным средством отладки клиент-серверных приложений. Мы будем использовать программу <code>netstat</code> для отслеживания изменений состояния в главе 5.</p>
     </section>
     <section>
      <title>
       <p>Обмен пакетами</p>
      </title>
      <p>На рис. 2.5 представлен реальный обмен пакетами, происходящий во время соединения TCP: установление соединения, передача данных и завершение соединения. Мы также показываем состояния TCP, через которые проходит каждый узел.</p>
      <image l:href="#img_12.png"/>
      <p><strong>Рис. 2.5</strong>. Обмен пакетами для соединения TCP</p>
      <p>В этом примере клиент объявляет размер сегмента (MSS) равным 536 байт (это означает, что его реализация работает с минимальным размером буфера сборки пакетов), а сервер — 1460 байт (типичное значение для IPv4 в Ethernet). Как видно, MSS в каждом направлении передачи вполне могут отличаться (см. также упражнение 2.5).</p>
      <p>Как только соединение установлено, клиент формирует запрос и посылает его серверу. Мы считаем, что этот запрос соответствует одиночному сегменту TCP (то есть его размер меньше 1460 байт — анонсированного размера MSS сервера). Сервер обрабатывает запрос и отправляет ответ, и мы также считаем, что ответ соответствует одиночному сегменту (в данном примере меньше 536 байт). Оба сегмента данных мы отобразили более жирными линиями. Заметьте, что подтверждение запроса клиента отправляется с ответом сервера. Это называется <emphasis>вложенным подтверждением</emphasis> (<emphasis>piggybacking</emphasis>) и обычно происходит, когда сервер успевает обработать запрос и подготовить ответ меньше, чем за 200 мс или около того. Если серверу требуется больше времени, скажем, 1 с, ответ будет приходить после подтверждения. (Динамика потока данных TCP подробно описана в главах 19 и 20 [111].)</p>
      <p>Затем мы показываем четыре сегмента, закрывающих соединение. Заметьте, что узел, выполняющий активное закрытие (в данном сценарии клиент), входит в состояние TIME_WAIT. Мы рассмотрим это в следующем разделе.</p>
      <p>На рис. 2.5 важно отметить, что если целью данного соединения было отправить запрос, занимающий один сегмент, и получить ответ, также занимающий один сегмент, то при использовании TCP всего будет задействовано восемь сегментов. Если же используется UDP, произойдет обмен только двумя сегментами: запрос и ответ. Но при переходе от TCP к UDP теряется надежность, которую TCP предоставляет приложению, и множество задач по обеспечению надежности транспортировки данных переходит с транспортного уровня (TCP) на уровень приложения. Другое важное свойство, предоставляемое TCP, — это управление в условиях перегрузки, которое в случае использования протокола UDP должно принимать на себя приложение. Тем не менее важно понимать, что многие приложения используют именно UDP, потому что они обмениваются небольшими объемами данных, a UDP позволяет избежать накладных расходов, возникающих при установлении и разрыве соединения TCP.</p>
     </section>
    </section>
    <section>
     <title>
      <p>2.7. Состояние TIME_WAIT</p>
     </title>
     <p>Без сомнений, самым сложным для понимания аспектом TCP в отношении сетевого программирования является состояние TIME_WAIT (<emphasis>время ожидания</emphasis>). На рис. 2.4 мы видим, что узел, выполняющий активное закрытие, проходит это состояние. Продолжительность этого состояния равна двум MSL (<emphasis>maximum segment lifetime — максимальное время жизни сегмента</emphasis>), иногда этот период называется 2MSL.</p>
     <p>В каждой реализации TCP выбирается какое-то значение MSL. Рекомендуемое значение, приведенное в документе RFC 1122 [10], равно 2 мин, хотя Беркли-реализации традиционно использовали значение 30 с. Это означает, что продолжительность состояния TIME_WAIT — от 1 до 4 мин. MSL — это максимальное количество времени, в течение которого дейтаграмма IP может оставаться в сети. Это время ограничено, поскольку каждая дейтаграмма содержит 8-разрядное поле <emphasis>предельного количества прыжков</emphasis> (<emphasis>hop limit</emphasis>) (поле TTL IPv4 на рис. А.1 и поле «Предельное количество транзитных узлов» IPv6 на рис. А.2), максимальное значение которого равно 255. Хотя этот предел ограничивает количество транзитных узлов, а не время пребывания пакета в сети, считается, что пакет с максимальным значением этого предела (которое равно 255) не может существовать в сети более MSL секунд.</p>
     <p>Пакеты в объединенных сетях обычно теряются в результате различных аномалий. Маршрутизатор отключается, или нарушается связь между двумя маршрутизаторами, и им требуются секунды или минуты для стабилизации и нахождения альтернативного пути. В течение этого периода времени могут возникать петли маршрутизации (маршрутизатор А отправляет пакеты маршрутизатору В, а маршрутизатор В отправляет их обратно маршрутизатору А), и пакеты теряются в этих петлях. В этот момент, если потерянный пакет — это сегмент TCP, истекает установленное время ожидания отправляющего узла, и он снова передает пакет, и этот заново переданный пакет доходит до конечного места назначения по некоему альтернативному пути. Но если спустя некоторое время (не превосходящее количества секунд MSL после начала передачи потерянного пакета) петля маршрутизации исправляется, пакет, потерянный в петле, отправляется к конечному месту назначения. Начальный пакет называется <emphasis>потерянной копией или дубликатом</emphasis> (<emphasis>lost duplicate</emphasis>), а также <emphasis>блуждающей копией или дубликатом</emphasis> (<emphasis>wandering duplicate</emphasis>). TCP должен обрабатывать эти дублированные пакеты.</p>
     <p>Есть две причины существования состояния TIME_WAIT:</p>
     <p>&#9632; необходимо обеспечить надежность разрыва двустороннего соединения TCP;</p>
     <p>&#9632; необходимо подождать, когда истечет время жизни в сети старых дублированных сегментов.</p>
     <p>Первую причину можно объяснить, рассматривая рис. 2.5 в предположении, что последний сегмент ACK потерян. Сервер еще раз отправит свой последний сегмент FIN, поэтому клиент должен сохранять информацию о своем состоянии, чтобы отправить завершающее подтверждение ACK повторно. Если бы клиент не сохранял информацию о состоянии, он ответил бы серверу сегментом RST (еще один вид сегмента TCP), что сервер интерпретировал бы как ошибку. Если ответственность за корректное завершение двустороннего соединения в обоих направлениях ложится на TCP, он должен правильно обрабатывать потерю любого из четырех сегментов. Этот пример объясняет, почему в состоянии TIME_WAIT остается узел, выполняющий активное закрытие: именно этому узлу может потребоваться повторно передать подтверждение.</p>
     <p>Чтобы понять вторую причину, по которой необходимо состояние TIME_WAIT, давайте считать, что у нас имеется соединение между IP-адресом 12.106.32.254, порт 1500 и IP-адресом 206.168.112.219, порт 21. Это соединение закрывается, и спустя некоторое время мы устанавливаем другое соединение между теми же IP-адресами и портами: 12.106.32.254, порт 1500 и 206.168.112.219, порт 21. Последнее соединение называется новым <emphasis>воплощением</emphasis> (<emphasis>incarnation</emphasis>) предыдущего соединения, поскольку IP-адреса и порты те же. TCP должен предотвратить появление старых дубликатов, относящихся к данному соединению, в новом воплощении этого соединения. Чтобы гарантировать это, TCP запрещает установление нового воплощения соединения, которое в данный момент находится в состоянии TIME_WAIT. Поскольку продолжительность состояния TIME_WAIT равна двум MSL, это позволяет удостовериться, что истечет и время жизни пакетов, посланных в одном направлении, и время жизни пакетов, посланных в ответ. Используя это правило, мы гарантируем, что в момент успешного установления соединения TCP время жизни в сети всех старых дубликатов от предыдущих воплощений этого соединения уже истекло.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Из этого правила существует исключение. Реализации, происходящие от Беркли, инициируют новое воплощение соединения, которое в настоящий момент находится в состоянии TIME WAIT, если приходящий сегмент SYN имеет порядковый номер «больше» конечного номера из предыдущего воплощения. На с. 958-959 [128] об этом рассказано более подробно. Для этого требуется, чтобы сервер выполнил активное закрытие, поскольку состояние TIME_WAIT должно существовать на узле, получающем следующий сегмент SYN. Эта возможность используется командой rsh. В документе RFC 1185 [54] рассказывается о некоторых ловушках, которые могут вас подстерегать при этом.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>2.8. Установление и завершение ассоциации SCRIPT</p>
     </title>
     <section>
      <p>Протокол SCRIPT ориентирован на установление соединения, подобно TCP, поэтому он также имеет собственные процедуры рукопожатия и завершения. Однако рукопожатия SCRIPT отличаются от рукопожатий TCP, поэтому мы описываем их отдельно.</p>
     </section>
     <section>
      <title>
       <p>Четырехэтапное рукопожатие</p>
      </title>
      <p>При установлении ассоциации SCRIPT выполняется приведенная далее последовательность действий, подобная трехэтапному рукопожатию TCP.</p>
      <p>1. Сервер должен быть готов к приему входящего соединения. Подготовка обычно осуществляется последовательным вызовом функций <code>socket</code>, <code>bind</code> и <code>listen</code> и называется <emphasis>пассивным открытием</emphasis> (<emphasis>passive open</emphasis>).</p>
      <p>2. Клиент начинает <emphasis>активное открытие</emphasis> (<emphasis>active open</emphasis>), вызывая функцию <code>connect</code> или сразу отправляя сообщение, что также приводит к установлению ассоциации. При этом клиент SCRIPT передает сообщение INIT (от слова «инициализация»), в котором серверу отправляется список IP-адресов клиента, начальный порядковый номер, идентификационная метка, позволяющая отличать пакеты данной ассоциации от всех прочих, количество исходящих потоков, запрашиваемых клиентом, и количество входящих потоков, поддерживаемых клиентом.</p>
      <p>3. Сервер подтверждает получение сообщения INIT от клиента сообщением INIT-ACK, которое содержит список IP-адресов сервера, начальный порядковый номер, идентификационную метку, количество исходящих потоков, запрашиваемых сервером, количество входящих потоков, поддерживаемых сервером, и cookie с данными о состоянии. Cookie содержит все сведения о состоянии, которые нужны серверу для того, чтобы гарантировать действительность ассоциации. В cookie включается цифровая подпись, подтверждающая аутентичность.</p>
      <p>4. Клиент отсылает cookie обратно серверу сообщением COOKIE-ECHO. Это сообщение уже может содержать пользовательские данные.</p>
      <p>5. Сервер подтверждает правильность приема cookie и установление ассоциации сообщением COOKIE-ACK. Это сообщение также может включать полезные данные.</p>
      <p>Минимальное количество пакетов для установления ассоциации SCRIPT равно четырем, поэтому описанная процедура называется <emphasis>четырехэтажным рукопожатием SCRIPT</emphasis>. Эти четыре пакета, передаваемые между клиентом и сервером, показаны на рис. 2.6.</p>
      <image l:href="#img_13.png"/>
      <p><strong>Рис. 2.6</strong>. Четырехэтапное рукопожатие SCRIPT</p>
      <p>Во многих отношениях четырехэтапное рукопожатие SCRIPT подобно трехэтапному рукопожатию TCP, за исключением всего, что связано с cookie. Сообщение INIT включает (помимо множества параметров) контрольную метку <emphasis>Та</emphasis> (<emphasis>verification tag</emphasis>) и начальный порядковый номер <emphasis>J</emphasis>. Метка <emphasis>Та</emphasis> должна присутствовать во всех пакетах, отправляемых собеседнику по данной ассоциации. Начальный порядковый номер используется для нумерации сообщений DATA (порций данных — DATA chunks). Собеседник тоже выбирает собственную метку <emphasis>Tz</emphasis>, которая должна присутствовать во всех его пакетах. Помимо контрольной метки и начального порядкового номера <emphasis>K</emphasis> получатель сообщения INIT отправляет cookie <emphasis>С</emphasis>. Пакет cookie содержит все сведения о состоянии, необходимые для установления ассоциации SCRIPT, так что стеку SCRIPT сервера не приходится хранить сведения о клиенте, с которым устанавливается ассоциация. Более подробные сведения о настройке ассоциаций SCRIPT вы можете найти в главе 4 книги [117].</p>
      <p>В заключение рукопожатия каждая сторона выбирает основной адрес назначения. На этот адрес передаются все данные в отсутствие неполадок в сети.</p>
      <p>Четырехэтапное рукопожатие используется в SCRIPT для того, чтобы сделать невозможной одну из атак типа «отказ в обслуживании» (см. раздел 4.5).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Четырехэтапное рукопожатие SCRIPT с использованием cookie формализует метод защиты от атак типа «отказ в обслуживании». Многие реализации TCP используют аналогичный метод. Отличие в том, что при работе с TCP данные cookie приходится кодировать в начальный порядковый номер, длина которого составляет всего 32 разряда. В SCRIPT используется поле произвольной длины и криптографическая защита.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Завершение ассоциации</p>
      </title>
      <p>В отличие от TCP, SCRIPT не имеет состояния, соответствующего частично закрытой ассоциации. Когда один узел закрывает ассоциацию, второй узел должен перестать отправлять новые данные. Получатель запроса на закрытие ассоциации отправляет те данные, которые уже были помещены в очередь, после чего завершает процедуру закрытия. Обмен пакетами изображен на рис. 2.7.</p>
      <image l:href="#img_14.png"/>
      <p><strong>Рис. 2.7</strong>. Обмен пакетами при завершении ассоциации SCRIPT</p>
      <p>SCTP не нуждается в состоянии TIME_WAIT благодаря контрольным меткам. Все порции данных помечаются так, как было оговорено при обмене сегментами INIT. Задержавшаяся порция от предыдущего соединения будет иметь неправильную метку. Вместо того, чтобы поддерживать в состоянии ожидания TIME_WAIT целое соединение, SCRIPT помещает в это состояние значения контрольных меток.</p>
     </section>
     <section>
      <title>
       <p>Диаграмма состояний SCRIPT</p>
      </title>
      <p>Порядок работы SCRIPT при установлении и завершении ассоциаций может быть проиллюстрирован диаграммой состояний (рис. 2.8).</p>
      <image l:href="#img_15.png"/>
      <p><strong>Рис. 2.8</strong>. Диаграмма состояний SCRIPT</p>
      <p>Как и на рис. 2.4, переходы из одного состояния в другое регулируются правилами SCRIPT и определяются текущим состоянием и порцией данных, полученной в этом состоянии. Например, если приложение выполняет активное открытие в состоянии CLOSED (Закрыто), SCRIPT отправляет пакет INIT и переходит в состояние COOKIE-WAIT (Ожидание cookie). Если затем SCRIPT получает пакет INIT-ACK, он отправляет пакет COOKIE-ECHO и новым состоянием становится COOKIE-ECHOED (Cookie отправлен обратно). Если после этого SCRIPT принимает COOKIE ACK, он переходит в состояние ESTABLISHED (Соединение установлено). В этом состоянии осуществляется передача основного объема данных. Порции данных могут передаваться совместно с пакетами COOKIE ECHO и COOKIE ACK.</p>
      <p>Две стрелки из состояния ESTABLISHED на рис. 2.8 соответствуют двум сценариям завершения ассоциации. Если приложение вызывает функцию close до получения пакета SHUTDOWN (активное закрытие), переход осуществляется в состояние SHUTDOWN-PENDING (Ожидание завершения). Если же приложение получает пакет SHUTDOWN, находясь в состоянии ESTABLISHED (пассивное закрытие), переход осуществляется в состояние SHUTDOWN-RECEIVED (Получен сигнал о завершении).</p>
     </section>
     <section>
      <title>
       <p>Обмен пакетами</p>
      </title>
      <p>На рис. 2.9 показан реальный обмен пакетами для ассоциации SCRIPT. Рисунок включает установление ассоциации, передачу данных и завершение ассоциации. Мы также показываем состояния SCRIPT, через которые проходит каждый из узлов.</p>
      <image l:href="#img_16.png"/>
      <p><strong>Рис. 2.9</strong>. Обмен пакетами для ассоциации SCRIPT</p>
      <p>В этом примере первая порция данных включается клиентом в COOKIE ECHO, а сервер включает данные в порцию COOKIE ACK. В общем случае в пакет COOKIE ECHO может включаться и несколько порций данных, если приложение использует интерфейс типа «один-ко-многим» (о разных типах интерфейсов речь пойдет в разделе 9.2).</p>
      <p>Блок информации, передаваемый в пакете SCRIPT, называется <emphasis>порцией</emphasis> (<emphasis>chunk</emphasis>). Порция информации самодостаточна, она включает сведения о типе данных, флаги и поле длины. Этот подход облегчает упаковку нескольких порций в один исходящий пакет (подробнее об упаковке порций и нормальном режиме передачи данных рассказывается в главе 5 [117]).</p>
     </section>
     <section>
      <title>
       <p>Параметры SCRIPT</p>
      </title>
      <p>SCTP использует параметры для облегчения использования дополнительных возможностей. Функции SCRIPT могут расширяться добавлением новых типов порций или новых параметров. При этом стандартные реализации SCRIPT имеют возможность сообщать о неизвестных параметрах и порциях данных. Старшие два бита пространства параметров и пространства порций определяют, что именно должен сделать получатель SCRIPT с неизвестным параметром или порцией (подробнее см. в разделе 3.1 [117]).</p>
      <p>В настоящий момент разрабатываются два расширения SCRIPT:</p>
      <p>1. Динамическое расширение адресов, позволяющее взаимодействующим узлам добавлять и удалять IP-адреса из существующей ассоциации.</p>
      <p>2. Поддержка частичной надежности, позволяющая взаимодействующим узлам по указанию от приложения ограничивать повторную передачу данных. Если сообщение становится слишком старым (это решает приложение), оно пропускается, и никаких попыток отправить его еще раз не делается. Это означает, что доставка всех данных адресату уже не гарантируется.</p>
     </section>
    </section>
    <section>
     <title>
      <p>2.9. Номера портов</p>
     </title>
     <section>
      <p>В любой момент времени каждый транспортный протокол (UDP, TCP, SCRIPT) может использоваться несколькими процессами. Все три протокола различают эти процессы при помощи 16-разрядных целых чисел — <emphasis>номеров портов</emphasis> (<emphasis>port numbers</emphasis>).</p>
      <p>Когда клиент хочет соединиться с сервером, клиент должен идентифицировать этот сервер. Для TCP, UDP и SCRIPT определена группа <emphasis>заранее известных портов</emphasis> (<emphasis>well-known ports</emphasis>) для идентификации известных служб. Например, каждая реализация TCP/IP, поддерживающая FTP, присваивает заранее известный порт 21 (десятичный) серверу FTP. Серверам TFTP (Trivial File Transfer Protocol — упрощенный протокол передачи файлов) присваивается порт UDP 69.</p>
      <p>С другой стороны, клиенты используют <emphasis>динамически назначаемые</emphasis>, или <emphasis>эфемерные</emphasis> (<emphasis>ephemeral</emphasis>) порты, то есть порты с непродолжительным временем жизни. Эти номера портов обычно присваиваются клиенту автоматически протоколами UDP или TCP. Клиенту обычно не важно фактическое значение динамически назначаемого порта; клиент лишь должен быть уверен, что динамически назначаемый порт является уникальным на клиентском узле. Реализации транспортного уровня гарантируют такую уникальность.</p>
      <p>IANA (Internet Assigned Numbers Authority — агентство по выделению имен и уникальных параметров протоколов Интернета) ведет список назначенных номеров портов. Раньше они публиковались в документах RFC; последним в этой серии был RFC 1700 [103]. В документе RFC 3232 [102] указан адрес базы данных, заменившей RFC 1700: <code>http://www.iana.org/</code>. Номера портов делятся на три диапазона.</p>
      <p>1. <emphasis>Заранее известные порты</emphasis>: от 0 до 1023. Эти номера портов управляются и присваиваются агентством IANA. Когда это возможно, один и тот же номер порта присваивается данному сервису и для TCP, и для UDP. Например, порт 80 присваивается веб-серверу для обоих протоколов, хотя в настоящее время все реализации используют только TCP.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Когда веб-серверу был назначен порт 80, протокол SCRIPT еще не существовал. Новые порты назначаются всем трем протоколам, и в RFC 2960 отмечено, что все существующие номера портов TCP могут использоваться теми же службами, работающими по протоколу SCRIPT.</p>
      </cite>
      <p>2. <emphasis>Зарегистрированные порты</emphasis>: от 1024 до 49 151. Они не управляются IANA, но IANA регистрирует и составляет списки использования этих портов для удобства потребителей. Когда это возможно, один и тот же порт выделяется одной и той же службе и для TCP, и для UDP. Например, порты с номерами от 6000 до 6063 присвоены серверу X Window для обоих протоколов, хотя в настоящее время все реализации используют только TCP. Верхний предел 49 151 для этих портов был установлен для того, чтобы оставить часть диапазона адресов для динамических портов. В документе RFC 1700 [103] верхний предел был 65 535.</p>
      <p>3. Динамические, или частные порты: от 49 152 до 65 535. IANA ничего не говорит об этих портах. Эти порты мы иногда называем <emphasis>эфемерными</emphasis>. (Магическое число 49 152 составляет три четверти от 65 536.)</p>
      <p>Разделение портов на диапазоны и общее распределение номеров портов показано на рис. 2.10.</p>
      <image l:href="#img_17.png"/>
      <p><strong>Рис. 2.10</strong>. Распределение номеров портов</p>
      <p>На этом рисунке мы отмечаем следующие моменты:</p>
      <p>&#9632; В системах Unix имеется понятие <emphasis>зарезервированного</emphasis> порта (<emphasis>reserved</emphasis> port), и это порт с номером меньше 1024. Эти порты может присвоить сокету только процесс, обладающий соответствующими привилегиями. Все заранее известные порты IANA являются зарезервированными портами; следовательно, сервер, желающий использовать этот порт (такой, как сервер FTP), должен обладать правами привилегированного пользователя.</p>
      <p>&#9632; Исторически сложилось так, что Беркли-реализации (начиная с 4.3BSD) позволяют динамически выделять порты в диапазоне от 1024 до 5000. Это было хорошо в начале 80-х, когда серверы не могли обрабатывать много клиентов одновременно, но сегодня можно легко найти сервер, поддерживающий более 3977 клиентов в любой момент времени. Поэтому некоторые системы выделяют динамически назначаемые порты по-другому, либо из диапазона, определенного IANA, либо из еще более широкого диапазона (например, Solaris, как показано на рис. 2.6), чтобы предоставить больше динамически назначаемых портов.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Как выяснилось, значение 5000 для верхнего предела динамически назначаемых портов, реализованное в настоящее время во многих системах, было типографской ошибкой [7]. Этот предел должен был быть равен 50 000.</p>
      </cite>
      <p>&#9632; Существуют несколько клиентов (не серверов), которые запрашивают зарезервированный порт для аутентификации в режиме клиент-сервер: типичным примером могут служить клиенты <code>rlogin</code> и <code>rsh</code>. Эти клиенты вызывают библиотечную функцию <code>rresvport</code> для создания сокета TCP и присваивают сокету неиспользованный номер порта из диапазона от 513 до 1023. Эта функция обычно пытается связаться с портом 1023, если попытка оказывается неудачной — с портом 1022, и так далее, пока не будет достигнут желаемый результат или пока не будут перебраны все порты вплоть до порта 513.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>И зарезервированные порты BSD, и порты функции rresvport частично перекрывают верхнюю половину заранее известных портов IANA. Это происходит потому, что известные порты IANA когда-то заканчивались на 255. В документе RFC 1340 под названием «Assigned numbers» в 1992 году началось присваивание заранее известных портов в диапазоне от 256 до 1023. В предыдущем документе RFC под названием «Assigned numbers» за номером 1060 от 1990 году эти порты назывались стандартными службами Unix (Unix Standard Services). Существует множество Беркли-серверов, номера портов которых были заданы в 80-х годах и начинались с 512 (таким образом, номера с 256 по 511 были пропущены). Функция rresvport начинает выбор с верхней границы диапазона 512-1023 и направляется вниз.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Пара сокетов</p>
      </title>
      <p><emphasis>Пара сокетов</emphasis> (<emphasis>socket pair</emphasis>) для соединения TCP — это кортеж (группа взаимосвязанных элементов данных или записей) из четырех элементов, определяющий две конечных точки соединения: локальный IP-адрес, локальный порт TCP, удаленный IP-адрес и удаленный порт TCP. В SCRIPT ассоциация определяется набором локальных IP-адресов, локальным портом, набором удаленных IP-адресов и удаленным портом. В простейшем варианте без множественной адресации получается точно такой же четырехэлементный кортеж, как и для TCP. Однако если хотя бы один из узлов, составляющих ассоциацию, используем множественную адресацию, одной и той же ассоциации может сопоставляться несколько четырехэлементных кортежей (с разными IP-адресами, но одинаковыми номерами портов).</p>
      <p>Два значения, идентифицирующих конечную точку, — IP-адрес и номер порта — часто называют <emphasis>сокетом</emphasis>.</p>
      <p>Мы можем распространить понятие пары сокетов на UDP, даже учитывая то, что этот протокол не ориентирован на установление соединения. Когда мы будем говорить о функциях сокетов (<code>bind</code>, <code>connect</code>, <code>getpeername</code> и т.д.), мы увидим, какими функциями задаются конкретные элементы пары сокетов. Например, функция bind позволяет приложению задавать локальный IP-адрес и локальный порт для сокетов TCP, UDP и SCRIPT.</p>
     </section>
    </section>
    <section>
     <title>
      <p>2.10. Номера портов TCP и параллельные серверы</p>
     </title>
     <p>Представим себе параллельный сервер, основной цикл которого порождает дочерний процесс для обработки каждого нового соединения. Что случится, если дочерний процесс будет продолжать использовать заранее известный номер порта при обслуживании длительного запроса? Давайте проанализируем типичную последовательность. Пусть сервер запускается на узле freebsd, поддерживающем множественную адресацию (IP-адреса 12.106.32.254 и 192.168.42.1), и выполняет пассивное открытие, используя свой заранее известный номер порта (в данном примере 21). Теперь он ожидает запрос клиента. Эта ситуация изображена на рис. 2.11.</p>
     <image l:href="#img_18.png"/>
     <p><strong>Рис. 2.11</strong>. Сервер TCP с пассивным открытием на порте 21</p>
     <p>Мы используем обозначение (<code>*:21,*:*</code>) для указания пары сокетов сервера. Сервер ожидает запроса соединения на любом локальном интерфейсе (первая звездочка) на порт 21. Удаленный IP-адрес и удаленный порт не определены, поэтому мы обозначаем их как <code>*.*</code>. Такая структура называется <emphasis>прослушиваемым сокетом</emphasis> (<emphasis>listening socket</emphasis>).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Мы отделяем IP-адрес от номера порта символом «:», потому что это обозначение используется в HTTP и часто встречается в других местах. Программа netstat отделяет номер порта от IP-адреса точкой, но иногда это приводит к затруднениям, потому что точки используются как в доменных именах (freebsd.unpbook.com.21), так и в записи IPv4 (12.106.32.254.21).</p>
     </cite>
     <p>Когда мы обозначаем звездочкой локальный IP-адрес, такое обозначение называется <emphasis>универсальным адресом</emphasis>, а звездочка — <emphasis>символом подстановки</emphasis> (<emphasis>wildcard</emphasis>). Если узел, на котором запущен сервер, поддерживает множественную адресацию (как в нашем примере), сервер может указать, что он хочет принимать входящие соединения, которые приходят только для одного определенного локального интерфейса. Сервер должен выбрать либо один определенный интерфейс, либо принимать запросы от всех интерфейсов, то есть сервер не может задать список, состоящий из нескольких адресов. Локальный адрес, заданный с помощью символа подстановки, соответствует выбору произвольного адреса из определенного множества. В листинге 1.5 перед вызовом функции bind произвольный IP-адрес в структуре адреса сокета задан с помощью константы <code>INADDR_ANY</code>.</p>
     <p>Через некоторое время на узле с IP-адресом 206.168.112.219 запускается клиент и выполняет активное открытие соединения с IP-адресом сервера 12.106.32.254. В этом примере мы считаем, что динамически назначаемый порт, выбранный клиентом TCP, — это порт 1500, что отражено на рис. 2.12. Под клиентом мы показываем его пару сокетов.</p>
     <image l:href="#img_19.png"/>
     <p><strong>Рис. 2.12</strong>. Запрос на соединение от клиента к серверу</p>
     <p>Когда сервер получает и принимает соединение клиента, он с помощью функции <code>fork</code> создает свою копию, давая возможность дочернему процессу обработать запрос клиента, как показано на рис. 2.13 (функцию <code>fork</code> мы описываем в разделе 4.7).</p>
     <image l:href="#img_20.png"/>
     <p><strong>Рис. 2.13</strong>. Параллельный сервер, дочерний процесс которого обрабатывает запрос клиента</p>
     <p>На этом этапе мы должны провести различие между прослушиваемым сокетом и присоединенным сокетом на сервере. Заметьте, что присоединенный сокет использует тот же локальный порт (21), что и прослушиваемый сокет. Также заметьте, что на многоадресном сервере локальный адрес заполняется для присоединенного сокета (206.62.226.35), как только устанавливается соединение.</p>
     <p>При выполнении следующего шага предполагается, что другой клиентский процесс на клиентском узле запрашивает соединение с тем же сервером. Код TCP клиента задает новому сокету клиента неиспользованный номер динамически назначаемого порта, скажем 1501. Мы получаем сценарий, представленный на рис. 2.14. На сервере различаются два соединения: пара сокетов для первого соединения отличается от пары сокетов для второго соединения, поскольку TCP клиента выбирает неиспользованный порт (1501) для второго соединения.</p>
     <image l:href="#img_21.png"/>
     <p><strong>Рис. 2.14</strong>. Второе соединение клиента с тем же сервером</p>
     <p>Из этого примера видно, что TCP не может демультиплексировать входящие сегменты, просматривая только номера портов назначения. TCP должен обращать внимание на все четыре элемента в паре сокетов, чтобы определить, какая конечная точка получает приходящий сегмент. На рис. 2.14 представлены три сокета с одним и тем же локальным портом (21). Если сегмент приходит с IP- адреса 206.168.112.219, порт 1500 и предназначен для IP-адреса 12.106.32.254, порт 21, он доставляется первому дочернему процессу. Если сегмент приходит с IP- адреса 206.168.112.219, порт 1501 и предназначен для IP-адреса 12.106.32.254, порт 21, он доставляется второму дочернему процессу. Все другие сегменты TCP, предназначенные для порта 21, доставляются исходному серверу с прослушиваемым сокетом.</p>
    </section>
    <section>
     <title>
      <p>2.11. Размеры буфера и ограничения</p>
     </title>
     <section>
      <p>Существуют несколько ограничений, устанавливающих максимальный размер дейтаграмм IP. Сначала мы опишем эти ограничения, а затем свяжем их вместе, чтобы показать, как они влияют на данные, которые может передавать приложение.</p>
      <p>&#9632; Максимальный размер дейтаграммы IPv4 — 65 535 байт, включая заголовок IPv4. Это связано с тем, что размер дейтаграммы ограничен 16-разрядным полем общей длины (см. рис. А.1).</p>
      <p>&#9632; Максимальный размер дейтаграммы IPv6 — 65 575 байт, включая 40-байтовый заголовок IPv6. Это ограничение связано с 16-разрядным полем длины полезных данных на рис. А.2. Заметьте, что поле длины IPv6 не включает размер заголовка IPv6, в то время как в случае IPv4 длина заголовка включается.</p>
      <p>IPv6 поддерживает возможность п<emphasis>ередачи полезных данных увеличенного объема</emphasis> (<emphasis>jumbo payload</emphasis>), при этом поле длины полезных данных расширяется до 32 бит, но эта функция поддерживается только на тех канальных уровнях, на которых максимальная единица передачи (MTU) превышает 65 535. Это свойство разработано для соединений между двумя узлами, таких как HIPPI (High-Performance Parallel Interface — высокоскоростной параллельный интерфейс), у которых часто нет собственных ограничений на MTU.</p>
      <p>&#9632; Во многих сетях определена MTU (maximum transmission unit — максимальная единица передачи), величина которой диктуется возможностями оборудования. Например, размер MTU для Ethernet равен 1500 байт. Другие канальные уровни, такие как соединения «точка-точка» с использованием протокола PPP, имеют конфигурируемую MTU. Более ранние соединения по протоколу SLIP (Serial Line Internet Protocol — межсетевой протокол для последовательного канала) часто использовали MTU, равную 296 или 1006 байт.</p>
      <p>Минимальная величина <emphasis>канальной MTU</emphasis> (<emphasis>link MTU</emphasis>) для IPv4 — 68 байт. Это сумма размера заголовка IPv4 максимальной длины (20 байт фиксированных полей и 30 байт параметров) и фрагмента минимального размера (сдвиг фрагмента должен быть кратен 8 байтам). Минимальная величина MTU для IPv6 — 1280 байт. IPv6 может работать и в сетях с меньшей MTU, но при условии фрагментации и последующей сборки на канальном уровне, чтобы извне сеть казалась имеющей большую MTU (RFC 2460 [27]).</p>
      <p>&#9632; Наименьшая величина MTU в пути между двумя узлами называется <emphasis>транспортной MTU</emphasis> (<emphasis>path MTU</emphasis>). В настоящее время MTU Ethernet, равная 1500 байт, часто является и транспортной MTU. Величина транспортной MTU между любыми двумя узлами не обязательно должна быть одинаковой в обоих направлениях, поскольку маршрутизация в Интернете часто асимметрична [90]. То есть маршрут от А к В может отличаться от маршрута от В к А.</p>
      <p>&#9632; Если размер дейтаграммы превышает канальную MTU, и IPv4 и IPv6 выполняют <emphasis>фрагментацию</emphasis> (<emphasis>fragmentation</emphasis>). <emphasis>Сборка</emphasis> (<emphasis>reassemble</emphasis>) фрагментов обычно не выполняется, пока они не достигнут конечного места назначения. Узлы IPv4 выполняют фрагментацию дейтаграмм, которые они генерируют, а маршрутизаторы IPv4 выполняют фрагментацию передаваемых ими дейтаграмм. Но в случае IPv6 дейтаграммы фрагментируются только узлами, а маршрутизаторы IPv6 фрагментацией не занимаются.</p>
      <p>&#9632; Если в заголовке IPv4 (см. рис. А.1) установлен бит DF (don't fragment — не фрагментировать), это означает, что данная дейтаграмма не должна быть фрагментирована ни отправляющим узлом, ни любым маршрутизатором на ее пути. Маршрутизатор, получающий дейтаграмму IPv4 с установленным битом DF, размер которой превышает MTU исходящей линии, генерирует сообщение об ошибке ICMPv4 «Необходима фрагментация, но установлен бит DF» (см. табл. А.5).</p>
      <p>Поскольку маршрутизаторы IPv6 не выполняют фрагментации, можно считать, что во всех дейтаграммах IPv6 установлен бит DF. Когда маршрутизатор IPv6 получает дейтаграмму, размер которой превышает MTU исходящей линии, он генерирует сообщение об ошибке ICMPv6 «Слишком большой пакет» (см. табл. А.6).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Будьте внимательны при использовании данной терминологии. Узел, помеченный как маршрутизатор IPv6, может все равно выполнять фрагментацию, но только для дейтаграмм, которые этот маршрутизатор генерирует сам. Он никогда не фрагментирует передаваемые им дейтаграммы. Когда этот узел генерирует дейтаграммы IPv6, он на самом деле выступает в роли узла (а не маршрутизатора). Например, большинство маршрутизаторов поддерживают протокол Telnet, используемый администраторами для настройки. Дейтаграммы IP, генерируемые сервером Telnet маршрутизатора, считаются порождаемыми маршрутизатором, поэтому он может выполнять их фрагментацию.</p>
       <p>Вы можете заметить, что в заголовке IPv4 (см. рис. А.1) существуют поля для выполнения IPv4-фрагментации, но в заголовке IPv6 (см. рис. А.2) полей для фрагментации нет. Поскольку фрагментация скорее исключение, чем правило, IPv6 может содержать дополнительный заголовок с информацией о фрагментации.</p>
       <p>Некоторые межсетевые экраны, обычно выполняющие по совместительству функции маршрутизаторов, могут собирать фрагментированные пакеты, чтобы проверять их содержимое целиком. Это позволяет предотвратить атаки определенного рода за счет дополнительного усложнения устройства экрана. Кроме того, для этого требуется, чтобы конкретный экран был единственной точкой соединения сети с внешней сетью, что сокращает возможности по обеспечению избыточности.</p>
      </cite>
      <p>Бит DF протокола IPv4 и его аналог в IPv6 могут использоваться для <emphasis>обнаружения транспортной MTU</emphasis> (<emphasis>path MTU discovery</emphasis>) (RFC 1191 [78] для IPv4 и RFC 1981 [71] для IPv6). Например, если TCP использует этот прием с IPv4, он отправляет все дейтаграммы с установленным битом DF. Если какой-нибудь промежуточный маршрутизатор возвращает сообщение об ошибке ICMP «Место назначения недоступно, необходима фрагментация, но установлен бит DF», TCP уменьшает количество данных, которые он отправляет в каждой дейтаграмме, и передает их повторно. Обнаружение транспортной MTU не обязательно для IPv4, тогда как реализации IPv6 должны либо поддерживать обнаружение транспортной MTU, либо отсылать пакеты только с минимальной MTU.</p>
      <p>&#9632; IPv4 и IPv6 определяют <emphasis>минимальный размер буфера сборки</emphasis> (<emphasis>minimum reassembly buffer size</emphasis>) — максимальный размер дейтаграммы, который гарантированно поддерживает любая реализация. Для IPv4 этот размер равен 576 байт, для IPv6 он увеличен до 1500 байт. Например, в случае IPv4 мы не знаем, может ли данный пункт назначения принять дейтаграмму в 577 байт. Поэтому многие приложения IPv4, использующие UDP (DNS, RIP, TFTP, BOOTP, SNMP) предотвращают возможность генерирования приложением IP-дейтаграмм, превышающих этот размер.</p>
      <p>&#9632; Для протокола TCP определен <emphasis>максимальный размер сегмента</emphasis> (<emphasis>MSS</emphasis>, <emphasis>maximum segment size</emphasis>). MSS указывает собеседнику максимальный объем данных TCP, которые собеседник может отправлять в каждом сегменте. Параметр MSS мы видели в сегментах SYN на рис. 2.5. Цель параметра MSS — сообщить собеседнику действительный размер буфера сборки и попытаться предотвратить фрагментацию. Размер MSS часто устанавливается равным значению MTU интерфейса минус фиксированные размеры заголовков IP и TCP. В Ethernet при использовании IPv4 это будет 1460, а в Ethernet при использовании IPv6 — 1440 (заголовок TCP для обоих протоколов имеет длину 20 байт, но заголовок IPv4 имеет длину 20 байт, а заголовок IPv6 — 40 байт).</p>
      <p>16-разрядное поле MSS ограничивает величину соответствующего параметра на уровне 65 536. Это хорошо для IPv4, поскольку максимальное количество данных TCP в дейтаграмме IPv4 равно 65 495 (65 535 минус 20-байтовый заголовок IPv4 и 20-байтовый заголовок TCP). Но в случае увеличенного объема полезных данных дейтаграммы IPv6 используется другая технология (см. документ RFC 2675 [9]). Прежде всего, максимальное количество данных TCP в дейтаграмме IPv6 без увеличения объема полезных данных равно 65 515 байт (65 535 минус 20-байтовый заголовок IPv6). Следовательно, значение MSS, равное 65 535, считается особым случаем, обозначающим «бесконечность». Это значение используется только вместе с параметром увеличения объема полезных данных, что требует размера MTU, превышающего 65 535. Если TCP использует параметр увеличения объема полезных данных и получает от собеседника объявление размера MSS, равного 65 535 байт, предельный размер дейтаграммы, посылаемой им, будет равен просто величине MTU интерфейса. Если оказывается, что этот размер слишком велик (например, в пути существует канал с меньшим размером MTU), при обнаружении транспортной MTU будет установлено меньшее значение MSS.</p>
      <p>&#9632; SCRIPT устанавливает параметр фрагментации равным наименьшей транспортной MTU для всех адресов собеседника. Сообщения, объем которых превышает эту величину, разбиваются на более мелкие, которые могут быть отправлены в одной IP-дейтаграмме. Параметр сокета <code>SCRIPT_MAXSEG</code> дает пользователю возможность установить меньший предел фрагментации.</p>
     </section>
     <section>
      <title>
       <p>Отправка по TCP</p>
      </title>
      <p>Приняв все вышеизложенные термины и определения, посмотрим на рис. 2.15, где показано, что происходит, когда приложение записывает данные в сокет TCP.</p>
      <image l:href="#img_22.png"/>
      <p><strong>Рис. 2.15</strong>. Этапы записи данных в сокет TCP и буферы, используемые при этой записи</p>
      <p>У каждого сокета TCP есть буфер отправки, и мы можем изменять размер этого буфера с помощью параметра сокета <code>SO_SNDBUF</code> (см. раздел 7.5). Когда приложение вызывает функцию <code>write</code>, ядро копирует данные из буфера приложения в буфер отправки сокета. Если для всех данных приложения недостаточно места в буфере сокета (либо буфер приложения больше буфера отправки сокета, либо в буфере отправки сокета уже имеются данные), процесс приостанавливается (переходит в состояние ожидания). Подразумевается, что мы используем обычный блокируемый сокет (о неблокируемых сокетах мы поговорим в главе 15). Ядро возвращает управление из функции <code>write</code> только после того, как последний байт в буфере приложения будет скопирован в буфер отправки сокета. Следовательно, успешное возвращение управления из функции <code>write</code> в сокет TCP говорит нам лишь о том, что мы можем снова использовать наш буфер приложения. Оно не говорит о том, получил ли собеседник отправленные данные или получило ли их приложение-адресат (более подробно мы рассмотрим это при описании параметра сокета <code>SO_LINGER</code> в разделе 7.5).</p>
      <p>TCP помещает данные в буфер отправки сокета и отправляет их собеседнику TCP, основываясь на всех правилах передачи данных TCP (главы 19 и 20 [111]). Собеседник TCP должен подтвердить данные, и только когда от него придет сегмент ACK, подтверждающий прием данных, наш TCP сможет удалить подтвержденные данные из буфера отправки сокета. TCP должен хранить копию данных, пока их прием не будет подтвержден адресатом.</p>
      <p>TCP отправляет данные IP порциями размером MSS или меньше, добавляя свой заголовок TCP к каждому сегменту. Здесь MSS — это значение, анонсированное собеседником, или 536, если собеседник не указал значения для MSS. IP добавляет свой заголовок, ищет в таблице маршрутизации IP-адрес назначения (соответствующая запись в таблице маршрутизации задает исходящий интерфейс, то есть интерфейс для исходящих пакетов) и передает дейтаграмму на соответствующий канальный уровень. IP может выполнить фрагментацию перед передачей дейтаграммы, но, как мы отмечали выше, одна из целей параметра MSS — не допустить фрагментации; а более новые реализации также используют обнаружение транспортной MTU. У каждого канального соединения имеется очередь вывода, и если она заполнена, пакет игнорируется, и вверх по стеку протоколов возвращается ошибка: от канального уровня к IP и затем от IP к TCP. TCP учтет эту ошибку и попытается отправить сегмент позже. Приложение не информируется об этом временном состоянии.</p>
     </section>
     <section>
      <title>
       <p>Отправка по UDP</p>
      </title>
      <p>На рис. 2.16 показано, что происходит, когда приложение записывает данные в сокет UDP.</p>
      <image l:href="#img_23.png"/>
      <p><strong>Рис. 2.16</strong>. Отправка данных через сокет UDP</p>
      <p>На этот раз буфер отправки сокета изображен пунктирными линиями, поскольку он (буфер) на самом деле не существует. У сокета UDP есть размер буфера отправки (который мы можем изменить с помощью параметра сокета <code>SO_SNDBUF</code>, см. раздел 7.5), но это просто верхнее ограничение на размер дейтаграммы UDP, которая может быть записана в сокет. Если приложение записывает дейтаграмму размером больше буфера отправки сокета, возвращается ошибка <code>EMSGSIZE</code>. Поскольку протокол UDP не является надежным, ему не нужно хранить копию данных приложения. Ему также не нужно иметь настоящий буфер отправки (данные приложения обычно копируются в буфер ядра по мере их движения вниз по стеку протоколов, но эта копия сбрасывается канальным уровнем после передачи данных).</p>
      <p>UDP просто добавляет свой 8-байтовый заголовок и передает дейтаграмму протоколу IP. IPv4 или IPv6 добавляет свой заголовок, определяет исходящий интерфейс, выполняя функцию маршрутизации, и затем либо добавляет дейтаграмму в очередь вывода канального уровня (если размер дейтаграммы не превосходит MTU), либо фрагментирует дейтаграмму и добавляет каждый фрагмент в очередь вывода канального уровня.</p>
      <p>Если приложение UDP отправляет большие дейтаграммы (например, 2000-байтовые), существует гораздо большая вероятность фрагментации, чем в случае TCP, поскольку TCP разбивает данные приложения на порции, равные по размеру MSS, а этому параметру нет аналога в UDP.</p>
      <p>Успешное возвращение из функции записи в сокет UDP говорит о том, что либо дейтаграмма, либо фрагменты дейтаграммы были добавлены к очереди вывода канального уровня. Если для дейтаграммы или одного из ее фрагментов недостаточно места, приложению в большинстве случаев возвращается сообщение <code>ENOBUFS</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>К сожалению, некоторые реализации не возвращают этой ошибки, не предоставляя приложению никаких указаний на то, что дейтаграмма была проигнорирована еще до начала передачи.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Отправка по SCRIPT</p>
      </title>
      <p>На рис. 2.17 показан процесс записи данных в сокет SCRIPT.</p>
      <image l:href="#img_24.png"/>
      <p><strong>Рис. 2.17</strong>. Отправка данных через сокет SCRIPT</p>
      <p>Для обеспечения надежности в SCRIPT предусмотрен буфер отправки. Приложение может менять размер этого буфера при помощи параметра сокета <code>SO_SNDBUF</code> (см. раздел 7.5), как и при работе с TCP. Когда приложение вызывает функцию <code>write</code>, ядро копирует все данные из буфера приложения в буфер отправки сокета. Если в буфере сокета недостаточно места для размещения всего объема данных приложения (то есть буфер приложения больше буфера сокета или в последнем уже имелись данные), пользовательский процесс приостанавливается. Приостановка производится для блокируемых сокетов. По умолчанию сокеты SCRIPT являются блокируемыми (о неблокируемых сокетах речь пойдет в главе 16). Ядро не возвращает управление процессу до тех пор, пока все байты буфера приложения не будут скопированы в буфер отправки сокета. Успешное возвращение из вызова <code>write</code> для сокета SCRIPT означает лишь, что приложение снова может воспользоваться своим буфером. Оно вовсе не означает, что SCRIPT адресата или приложение-адресат получили отправленные данные.</p>
      <p>SCRIPT обрабатывает данные, которые находятся в буфере отправки на основании правил передачи SCRIPT (подробнее см. главу 5 [117]). Передающий SCRIPT должен дождаться получения порции SACK, в которой передается кумулятивное уведомление о приеме, чтобы удалить данные из буфера отправки сокета.</p>
     </section>
    </section>
    <section>
     <title>
      <p>2.12. Стандартные службы Интернета</p>
     </title>
     <p>В табл. 2.1 перечислены некоторые стандартные службы, предоставляемые большинством реализаций TCP/IP. Заметьте, что все они поддерживают и TCP, и UDP, и номер порта для обоих протоколов один и тот же.</p>
     <empty-line/>
     <p><strong>Таблица 2.1</strong>. Стандартные службы TCP/IP, предоставляемые в большинстве реализаций</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Имя</th>
       <th align="left" valign="top">Порт TCP</th>
       <th align="left" valign="top">Порт UDP</th>
       <th align="left" valign="top">RFC</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">echo</td>
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">862</td>
       <td align="left" valign="top">Сервер возвращает то, что посылает клиент</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">discard</td>
       <td align="left" valign="top">9</td>
       <td align="left" valign="top">9</td>
       <td align="left" valign="top">863</td>
       <td align="left" valign="top">Сервер игнорирует все данные, присланные клиентом</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">daytime</td>
       <td align="left" valign="top">13</td>
       <td align="left" valign="top">13</td>
       <td align="left" valign="top">867</td>
       <td align="left" valign="top">Сервер возвращает время и дату в формате, удобном для восприятия человеком</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">chargen</td>
       <td align="left" valign="top">19</td>
       <td align="left" valign="top">19</td>
       <td align="left" valign="top">864</td>
       <td align="left" valign="top">TCP-сервер посылает непрерывный поток символов, пока соединение не будет разорвано клиентом. UDP-сервер посылает дейтаграмму со случайным количеством символов каждый раз, когда клиент посылает дейтаграмму</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">time</td>
       <td align="left" valign="top">37</td>
       <td align="left" valign="top">37</td>
       <td align="left" valign="top">868</td>
       <td align="left" valign="top">Сервер возвращает текущее время в виде двоичного 32-разрядного числа. Это число представляет собой количество секунд, прошедших с полуночи 1 января 1900 года (UTC)</td>
      </tr>
     </table>
     <p>Часто эти службы предоставляются демоном inetd на узлах Unix (см. раздел 13.5). Стандартные службы делают возможным простейшее тестирование при помощи стандартного клиента Telnet.</p>
     <p>Вот, например, тесты для сервера, определяющего время и дату, и для эхо-сервера.</p>
     <p><code>aix % <strong>telnet freebsd daytime</strong></code></p>
     <p><code>Trying 12.106.32.254...            вывод клиента Telnet</code></p>
     <p><code>Connected to freebsd.unpbook.com   вывод клиента Telnet</code></p>
     <p><code>Escape character is '^]'.          вывод клиента Telnet</code></p>
     <p><code>Mon Jul 28 11:56:22 2003           вывод сервера времени и даты</code></p>
     <p><code>Connection closed by foreign host. вывод клиента Telnet (сервер закрыл</code></p>
     <p><code>                                   соединение)</code></p>
     <empty-line/>
     <p><code>aix % <strong>telnet freebsd echo</strong></code></p>
     <p><code>Trying 12.106.32.254...          вывод клиента Telnet</code></p>
     <p><code>Connected to freebsd.unpbook.com вывод клиента Telnet</code></p>
     <p><code>Escape character is '^]'.        вывод клиента Telnet</code></p>
     <p><code><strong>hello, world</strong>                     ввод с клавиатуры</code></p>
     <p><code>hello, world                     эхо-ответ сервера</code></p>
     <p><code>^]                 ввод с клавиатуры для обращения к клиенту Telnet</code></p>
     <p><code>telnet&gt; <strong>quit</strong>       команда клиенту на завершение соединения</code></p>
     <p><code>Connection closed. на этот раз соединение завершает клиент</code></p>
     <p>В этих двух примерах мы вводим имя узла и название службы (<code>daytime</code> и <code>echo</code>). Соответствие названий служб и номеров портов (см. табл. 2.1) устанавливается в файле <code>/etc/services</code> (см. раздел 11.5).</p>
     <p>Заметьте, что когда мы соединяемся с сервером <code>daytime</code>, сервер выполняет активное закрытие. В случае эхо-сервера активное закрытие выполняет клиент. Вспомним рис. 2.4, где показано, что узел, выполняющий активное закрытие, — это узел, проходящий состояние TIME_WAIT.</p>
     <p>В современных системах стандартные службы чаще всего отключены по умолчанию, потому что через них могут быть проведены атаки типа «отказ в обслуживании» и другие, связанные с чрезмерным потреблением ресурсов.</p>
    </section>
    <section>
     <title>
      <p>2.13. Использование протоколов типичными приложениями Интернета</p>
     </title>
     <p>Таблица 2.2 иллюстрирует использование протоколов типичными приложениями Интернета.</p>
     <empty-line/>
     <p><strong>Таблица 2.2</strong>. Использование протоколов типичными приложениями Интернета</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Приложение</th>
       <th align="left" valign="top">IP</th>
       <th align="left" valign="top">ICMP</th>
       <th align="left" valign="top">UDP</th>
       <th align="left" valign="top">TCP</th>
       <th align="left" valign="top">SCRIPT</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">ping</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">traceroute</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">OSPF (протокол маршрутизации)</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">RIP (протокол маршрутизации)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">BGP (протокол маршрутизации)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">BOOTP (протокол bootstrap — протокол дистанционной загрузки и запуска устройств в сети)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">DHCP (протокол bootstrap)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">NTP (синхронизирующий сетевой протокол)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">TFTP (упрощенный протокол передачи файлов)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SNMP (управление сетью)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SMTP (электронная почта)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Telnet (удаленный вход в систему)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">FTP (передача файлов)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">HTTP (протокол передачи HTML-файлов по сети WWW)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">NNTP (сетевой протокол передачи новостей)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">DNS (система доменных имен)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">NFS (сетевая файловая система)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Sun RPC (удаленный вызов процедур)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">DCE RPC (удаленный вызов процедур)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IUA (ISDN поверх IP)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">M2UA, M3UA (телефонная связь SS7)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">H.248 (управление шлюзом)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">H.323 (IP-телефония)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIP (IP-телефония)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
      </tr>
     </table>
     <p>Первые два приложения, <code>ping</code> и <code>traceroute</code>, являются диагностическими и используют протокол ICMP, <code>traceroute</code> создает свои собственные пакеты UDP и считывает ответы ICMP.</p>
     <p>Три популярных протокола маршрутизации демонстрируют многообразие транспортных протоколов, которые используются протоколами маршрутизации. Алгоритм OSPF (Open Shortest Path First — первоочередное открытие кратчайших маршрутов) использует IP непосредственно через символьный сокет, в то время как RIP (Routing Information Protocol — протокол информации о маршрутизации) использует UDP, a BGP (Border Gateway Protocol — протокол граничных шлюзов) использует TCP.</p>
     <p>Далее идут пять приложений, основанные на UDP, за ними следуют семь приложений TCP и четыре приложения UDP/TCP. Последние пять приложений относятся к IP-телефонии. Они могут использовать либо только SCRIPT, либо UDP, TCP и SCRIPT по выбору.</p>
    </section>
    <section>
     <title>
      <p>2.14. Резюме</p>
     </title>
     <p>UDP является простым, ненадежным протоколом, не ориентированным на установление соединения, в то время как TCP — это сложный, надежный, ориентированный на установление соединения протокол. SCRIPT сочетает особенности обоих протоколов, расширяя возможности TCP. Хотя большинство приложений в Интернете используют протокол TCP (веб-сервисы, Telnet, FTP, электронная почта), существует потребность во всех трех транспортных протоколах. В разделе 22.4 мы рассматриваем причины, по которым иногда вместо TCP выбирается UDP. В разделе 23.12 будут проанализированы ситуации, в которых SCRIPT предпочтительнее TCP.</p>
     <p>TCP устанавливает соединения, используя трехэтапное рукопожатие, и разрывает соединение, используя обмен четырьмя пакетами. Когда соединение TCP установлено, оно переходит из состояния CLOSED в состояние ESTABLISHED. При разрыве соединения оно переходит обратно в состояние CLOSED. Всего существует 11 состояний, в которых может находиться соединение TCP, и диаграмма переходов состояний определяет правила перемещения между этими состояниями. Понимание этой диаграммы существенно для диагностики проблем при использовании программы <code>netstat</code> и для понимания того, что происходит, когда мы вызываем такие функции, как <code>connect</code>, <code>accept</code> и <code>close</code>.</p>
     <p>Состояние TCP TIME_WAIT — неиссякаемый источник путаницы, возникающей у сетевых программистов. Это состояние существует для того, чтобы реализовать разрыв двустороннего соединения TCP (то есть для решения проблем, возникающих в случае потери последнего сегмента ACK), а также чтобы дождаться, когда истечет время жизни в сети старых дублированных сегментов.</p>
     <p>SCRIPT устанавливает ассоциацию, выполняя четырехэтапное рукопожатие, и завершает соединение обменом тремя пакетами. При установлении ассоциации SCRIPT происходит переход из состояния CLOSED в состояние ESTABLISHED, а при завершении ассоциации — возврат к состоянию CLOSED. Ассоциация SCRIPT может находиться в восьми состояниях, правила перехода между которыми описываются диаграммой состояний. Благодаря использованию контрольных меток SCRIPT не нуждается в состоянии TIME_WAIT.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Мы говорили об IPv4 и IPv6. А что произошло с версией 5 и каковы были версии 0, 1, 2 и 3? (<emphasis>Подсказка</emphasis>: найдите журнал IANA «Internet Protocol». Можете сразу переходить к решению, если вы не можете подключиться к <code>http://www.iana.org/</code>.)</p>
     <p>2. Где вы будете искать дополнительную информацию о протоколе, которому присвоено название «IP версия 5»?</p>
     <p>3. Описывая рис. 2.15, мы отметили, что TCP считает MSS равным 536, если не получает величину параметра MSS от собеседника. Почему используется это значение?</p>
     <p>4. Нарисуйте рисунок, аналогичный рис. 2.5, для клиент-серверного приложения времени и даты из главы 1, предполагая, что сервер возвращает 26 байт данных в отдельном сегменте TCP.</p>
     <p>5. Допустим, что установлено соединение между узлом в Ethernet, чей TCP объявляет MSS, равный 1460, и узлом в Token-ring, чей TCP объявляет MSS, равный 4096. Ни один из узлов не пытается обнаружить, чему равна транспортная MTU. При просмотре пакетов мы никогда не видим более 1460 байт данных в любом направлении. Почему?</p>
     <p>6. Описывая табл. 2.2, мы отметили, что OSPF использует IP непосредственно. Каково значение поля протокола в заголовке IPv4 (см. рис. А.1) для дейтаграмм OSPF?</p>
     <p>7. Обсуждая отправку данных по SCRIPT, мы отметили, что отправителю приходится ждать получения кумулятивного уведомления, чтобы удалить данные из буфера сокета. Если еще до получения кумулятивного уведомления принято выборочное уведомление, указывающее, что данные уже доставлены, почему буфер все равно не может быть освобожден?</p>
    </section>
   </section>
  </section>
  <section>
   <title>
    <p>Часть 2</p>
    <p>Элементарные сокеты</p>
   </title>
   <section>
    <title>
     <p>Глава 3</p>
     <p>Введение в сокеты</p>
    </title>
    <section>
     <title>
      <p>3.1. Введение</p>
     </title>
     <p>Эта глава начинается с описания программного интерфейса приложения (API) сокетов. Мы начнем со структур адресов сокетов, которые будут встречаться почти в каждом примере на протяжении всей книги. Эти структуры можно передавать в двух направлениях: от процесса к ядру и от ядра к процессу. Последний случай — пример аргумента, через который передается возвращаемое значение, и далее в книге мы встретимся с другими примерами таких аргументов.</p>
     <p>Перевод текстового представления адреса в двоичное значение, входящее в структуру адреса сокета, осуществляется функциями преобразования адресов. В большей части существующего кода IPv4 используются функции <code>inet_addr</code> и <code>inet_ntoa</code>, но две новых функции <code>inet_pton</code> и <code>inet_ntop</code> работают и с IPv4, и с IPv6.</p>
     <p>Одной из проблем этих функций является то, что они зависят от протокола, так как для них имеет значение тип преобразуемого адреса — IPv4 или IPv6. Мы разработали набор функций, названия которых начинаются с <code>sock_</code>, работающих со структурами адресов сокетов независимо от протокола. Эти функции мы и будем использовать, чтобы сделать наш код не зависящим от протокола.</p>
    </section>
    <section>
     <title>
      <p>3.2. Структуры адреса сокетов</p>
     </title>
     <section>
      <p>Большинство функций сокетов используют в качестве аргумента указатель на структуру адреса сокета. Каждый набор протоколов определяет свою собственную структуру адреса сокетов. Имена этих структур начинаются с <code>sockaddr_</code> и заканчиваются уникальным суффиксом для каждого набора протоколов.</p>
     </section>
     <section>
      <title>
       <p>Структура адреса сокета IPv4</p>
      </title>
      <p>Структура адреса сокета IPv4, обычно называемая структурой адреса сокета Интернета, именуется <code>sockaddr_in</code> и определяется в заголовочном файле <code>&lt;netinet/in.h&gt;</code>. В листинге 3.1<a l:href="#n1" type="note">[1]</a> представлено определение POSIX.</p>
      <p><strong>Листинг 3.1</strong>. Структура адреса сокета Интернета (IPv4): sockaddr_in</p>
      <p><code>struct in_addr {</code></p>
      <p><code> in_addr_t s_addr; /* 32-разрядный адрес IPv4 */</code></p>
      <p><code>                   /* сетевой порядок байтов */</code></p>
      <p><code>};</code></p>
      <empty-line/>
      <p><code>struct sockaddr_in {</code></p>
      <p><code> uint8_t sin_len;         /* длина структуры (16) */</code></p>
      <p><code> sa_family_t sin_family;  /* AF_INET */</code></p>
      <p><code> in_port_t sin_port;      /* 16-разрядный номер порта TCP или UDP */</code></p>
      <p><code>                          /* сетевой порядок байтов */</code></p>
      <p><code> struct in_addr sin_addr; /* 32-разрядный адрес IPv4 */</code></p>
      <p><code>                          /* сетевой порядок байтов */</code></p>
      <p><code> char sin_zero[8];        /* не используется */</code></p>
      <p><code>};</code></p>
      <p>Есть несколько моментов, касающихся структур адреса сокета в целом, которые мы покажем на примере.</p>
      <p>&#9632; Элемент длины <code>sin_len</code> появился в версии 4.3BSD-Reno, когда была добавлена поддержка протоколов OSI (см. рис. 1.6). До этой реализации первым элементом был <code>sin_family</code>, который исторически имел тип <code>unsigned short</code> (целое без знака). Не все производители поддерживают поле длины для структур адреса сокета, и в POSIX, например, не требуется наличия этого элемента. Типы данных, подобные <code>uint8_t</code>, введены в POSIX (см. табл. 3.1). Наличие поля длины упрощает обработку структур адреса сокета с переменной длиной.</p>
      <p>&#9632; Даже если поле длины присутствует, нам не придется устанавливать и проверять его значение, пока мы не имеем дела с маршрутизирующими сокетами (см. главу 18). Оно используется внутри ядра процедурами, работающими со структурами адресов сокетов из различных семейств протоколов (например, код таблицы маршрутизации).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Четыре функции, передающие структуру адреса сокета от процесса к ядру, — bind, connect, sendto и sendmsg — используют функцию sockargs в реализациях, ведущих происхождение от Беркли [128, с. 452]. Эта функция копирует структуру адреса сокета из процесса и затем явно присваивает элементу sin_len значение размера структуры, переданной в качестве аргумента этим четырем функциям. Пять функций, передающих структуру адреса сокета от ядра к процессу, — accept, recvfrom, recvmsg, getpeername и getsockname — устанавливают элемент sin_len перед возвращением управления процессу.</p>
       <p>К сожалению, обычно не существует простого теста, выполняемого в процессе компиляции и определяющего, задает ли реализация поле длины для своих структур адреса сокета. В нашем коде мы тестируем собственную константу HAVE_SOCKADDR_SA_LEN (см. листинг Г.2), но для того чтобы определить, задавать эту константу или нет, требуется откомпилировать простую тестовую программу, использующую необязательный элемент структуры, и проверить, успешно ли выполнена компиляция. В листинге 3.3 мы увидим, что от реализаций IPv6 требуется задавать SIN6_LEN, если структура адреса сокета имеет поле длины. В некоторых реализациях IPv4 (например, Digital Unix) поле длины предоставляется для приложений, основанных на параметре времени компиляции (например, _SOCKADDR_LEN). Это свойство обеспечивает совместимость с другими, более ранними программами.</p>
      </cite>
      <p>&#9632; POSIX требует наличия только трех элементов структуры: <code>sin_family</code>, <code>sin_addr</code> и <code>sin_port</code>. POSIX-совместимая реализация может определять дополнительные элементы структуры, и это норма для структуры адреса сокета Интернета. Почти все реализации добавляют элемент sin_zero, так что все структуры адреса сокета имеют размер как минимум 16 байт.</p>
      <p>&#9632; Типы элементов <code>s_addr</code>, <code>sin_family</code> и <code>sin_port</code> мы указываем согласно POSIX. Тип данных <code>in_addr_t</code> соответствует целому числу без знака длиной как минимум 32 бита, <code>in_port_t</code> — целому числу без знака длиной как минимум 16 бит, a <code>sa_family_t</code> — это произвольное целое число без знака. Последнее обычно представляет собой 8-разрядное целое без знака, если реализация поддерживает поле длины, либо 16-разрядное целое без знака, если поле длины не поддерживается. В табл. 3.1 перечислены эти три типа данных POSIX вместе с некоторыми другими типами данных POSIX, с которыми мы встретимся.</p>
      <empty-line/>
      <p><strong>Таблица 3.1</strong>. Типы данных, требуемые POSIX</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Тип данных</th>
        <th align="left" valign="top">Описание</th>
        <th align="left" valign="top">Заголовочный файл</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">int8_t</td>
        <td align="left" valign="top">8-разрядное целое со знаком</td>
        <td align="left" valign="top">&lt;sys/types.h&gt;</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">uint8_t</td>
        <td align="left" valign="top">8-разрядное целое без знака</td>
        <td align="left" valign="top">&lt;sys/types.h&gt;</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">int16_t</td>
        <td align="left" valign="top">16-разрядное целое со знаком</td>
        <td align="left" valign="top">&lt;sys/types.h&gt;</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">uint16_t</td>
        <td align="left" valign="top">16-разрядное целое без знака</td>
        <td align="left" valign="top">&lt;sys/types.h&gt;</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">int32_t</td>
        <td align="left" valign="top">32-разрядное целое со знаком</td>
        <td align="left" valign="top">&lt;sys/types.h&gt;</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">uint32_t</td>
        <td align="left" valign="top">32-разрядное целое без знака</td>
        <td align="left" valign="top">&lt;sys/types.h&gt;</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">sa_family_t</td>
        <td align="left" valign="top">семейство адресов структуры адреса сокета</td>
        <td align="left" valign="top">&lt;sys/socket.h&gt;</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">socklen_t</td>
        <td align="left" valign="top">длина структуры адреса сокета, обычно типа uint32_t</td>
        <td align="left" valign="top">&lt;sys/socket.h&gt;</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">in_addr_t</td>
        <td align="left" valign="top">IPv4-адрес, обычно типа uint32_t</td>
        <td align="left" valign="top">&lt;netinet/in.h&gt;</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">in_port_t</td>
        <td align="left" valign="top">порт TCP или UDP, обычно типа uint16_t</td>
        <td align="left" valign="top">&lt;netinet/in.h&gt;</td>
       </tr>
      </table>
      <p>&#9632; Вы также встретите типы данных <code>u_char</code>, <code>u_short</code>, <code>u_int</code> и <code>u_long</code>, которые не имеют знака. POSIX определяет их с замечанием, что они устарели. Они предоставляются в целях обратной совместимости.</p>
      <p>&#9632; И адрес IPv4, и номер порта TCP и UDP всегда хранятся в структуре в соответствии с порядком байтов, определенным в сети (<emphasis>сетевой порядок байтов</emphasis> — <emphasis>network byte order</emphasis>). Об этом нужно помнить при использовании этих элементов (более подробно о разнице между порядком байтов узла и порядком байтов в сети мы поговорим в разделе 3.4).</p>
      <p>&#9632; К 32-разрядному адресу IPv4 можно обратиться двумя путями. Например, если <code>serv</code> — это структура адреса сокета Интернета, то <code>serv.sin_addr</code> указывает на 32-разрядный адрес IPv4 как на структуру <code>in_addr</code>, в то время как <code>serv.sin_addr.s_addr</code> указывает на тот же 32-разрядный адрес IPv4 как на значение типа <code>in_addr_t</code> (обычно это 32-разрядное целое число без знака). Нужно следить за корректностью обращения к адресам IPv4, особенно при использовании их в качестве аргументов различных функций, потому что компиляторы часто передают структуры не так, как целочисленные переменные.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Причина того, что sin_addr является структурой, а не просто целым числом без знака, носит исторический характер. В более ранних реализациях (например, 4.2BSD) структура in_addr определялась как объединение (union) различных структур, чтобы сделать возможным доступ к каждому из четырех байтов 32-разрядного IPv4-адреса, а также к обоим входящим в него 16-разрядным значениям. Эта возможность использовалась в адресах классов А, В и С для выборки соответствующих байтов адреса. Но с появлением подсетей и последующим исчезновением различных классов адресов (см. раздел А.4) и введением бесклассовой адресации (classless addressing) необходимость в объединении структур отпала. В настоящее время большинство систем отказались от использования объединения и просто определяют in_addr как структуру, содержащую один элемент типа in_addr_t.</p>
      </cite>
      <p>&#9632; Элемент <code>sin_zero</code> не используется, но мы <emphasis>всегда</emphasis> устанавливаем его в нуль при заполнении одной из этих структур. Перед заполнением структуры мы всегда обнуляем все ее элементы, а не только <code>sin_zero</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В большинстве случаев при использовании этой структуры не требуется, чтобы элемент sin_zero был равен нулю, но, например, при привязке конкретного адреса IPv4 (а не произвольного интерфейса) этот элемент обязательно должен быть нулевым [128, с. 731-732].</p>
      </cite>
      <p>&#9632; Структуры адреса сокета используются только на данном узле: сама структура не передается между узлами, хотя определенные поля (например, поля IP-адреса и порта) используются для соединения.</p>
     </section>
     <section>
      <title>
       <p>Универсальная структура адреса сокета</p>
      </title>
      <p>Структуры адреса сокета <emphasis>всегда</emphasis> передаются по ссылке при передаче в качестве аргумента для любой функции сокета. Но функции сокета, принимающие один из этих указателей в качестве аргумента, должны работать со структурами адреса сокета из <emphasis>любого</emphasis> поддерживаемого семейства протоколов.</p>
      <p>Проблема в том, как объявить тип передаваемого указателя. Для ANSI С решение простое: <code>void*</code> является указателем на неопределенный (универсальный) тип (generic pointer type). Но функции сокетов существовали до появления ANSI С, и в 1982 году было принято решение определить <emphasis>универсальную</emphasis> структуру адреса сокета (generic socket address structure) в заголовочном файле <code>&lt;sys/socket.h&gt;</code>, которая показана в листинге 3.2.</p>
      <p><strong>Листинг 3.2</strong>. Универсальная структура адреса сокета: sockaddr</p>
      <p><code>struct sockaddr {</code></p>
      <p><code> uint8_t sa_len;</code></p>
      <p><code> sa_family_t sa_family; /* семейство адресов: константа AF_xxx */</code></p>
      <p><code> char sa_data[14];      /* адрес, специфичный для протокола */</code></p>
      <p><code>};</code></p>
      <p>Функции сокетов определяются таким образом, что их аргументом является указатель на общую структуру адреса сокета, как показано в прототипе функции <code>bind</code> (ANSI С):</p>
      <p><code>int bind(int, struct sockaddr*, socklen_t);</code></p>
      <p>При этом требуется, чтобы для любых вызовов этих функций указатель на структуру адреса сокета, специфичную для протокола, был преобразован в указатель на универсальную структуру адреса сокета. Например:</p>
      <p><code>struct sockaddr_in serv; /* структура адреса сокета IPv4 */</code></p>
      <empty-line/>
      <p><code>/* заполняем serv{} */</code></p>
      <p><code>bind(sockfd, (struct sockaddr*)&amp;serv, sizeof(serv));</code></p>
      <p>Если мы не выполним преобразование (<code>struct sockaddr*</code>), компилятор С сгенерирует предупреждение в форме <code>"Warning: passing arg 2 of 'bind' from incompatible pointer type"</code> (Передается указатель несовместимого типа). Здесь мы предполагаем, что в системных заголовочных файлах имеется прототип ANSI С для функции bind.</p>
      <p>С точки зрения разработчика приложений, универсальная структура адреса сокета используется только для преобразования указателей на структуры адресов конкретных протоколов.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Вспомните, что в нашем заголовочном файле unp.h (см. раздел 1.2) мы определили SA как строку "struct sockaddr", чтобы сократить код, который мы написали для преобразования этих указателей.</p>
       <p>С точки зрения ядра основанием использовать в качестве аргументов указатели на универсальные структуры адреса сокетов является то, что ядро должно получать указатель вызывающей функции, преобразовывать его в struct sockaddr, а затем по значению элемента sa_family определять тип структуры. Но разработчику приложений было бы проще работать с указателем void*, поскольку это избавило бы его от необходимости выполнять явное преобразование указателя.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Структура адреса сокета IPv6</p>
      </title>
      <p>Структура адреса сокета IPv6 задается при помощи включения заголовочного файла <code>&lt;netinet/in.h&gt;</code>, как показано в листинге 3.3.</p>
      <p><strong>Листинг 3.3</strong>. Структура адреса сокета IPv6: sockaddr_in6</p>
      <p><code>struct in6_addr {</code></p>
      <p><code> uint8_t s6_addr[16]; /* 128-разрядный адрес IPv6 */</code></p>
      <p><code>                      /* сетевой порядок байтов */</code></p>
      <p><code>};</code></p>
      <empty-line/>
      <p><code>#define SIN6_LEN /* требуется для проверки во время компиляции */</code></p>
      <empty-line/>
      <p><code>struct sockaddr_in6 {</code></p>
      <p><code> uint8_t sin_len;           /* длина этой структуры (24) */</code></p>
      <p><code> sa_family_t sin6_family;   /* AF_INET6 */</code></p>
      <p><code> in_port_t sin6_port;       /* номер порта транспортного уровня */</code></p>
      <p><code>                            /* сетевой порядок байтов */</code></p>
      <p><code> uint32_t sin6_flowinfo;    /* приоритет и метка потока */</code></p>
      <p><code>                            /* сетевой порядок байтов */</code></p>
      <p><code> struct in6_addr sin6_addr; /* IPv6-адрес */</code></p>
      <p><code>                            /* сетевой порядок байтов */</code></p>
      <p><code> uint32_t sin6_scope_id;    /* набор интерфейсов */</code></p>
      <p><code>};</code></p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Расширения API сокетов для IPv6 описаны в RFC 3493 [36].</p>
      </cite>
      <p>Отметим следующие моменты относительно листинга 3.3:</p>
      <p>&#9632; Константа <code>SIN6_LEN</code> должна быть задана, если система поддерживает поле длины для структур адреса сокета.</p>
      <p>&#9632; Семейством IPv6 является <code>AF_INET6</code>, в то время как семейство IPv4 — <code>AF_INET</code>.</p>
      <p>&#9632; Элементы в структуре упорядочены таким образом, что если структура <code>sockaddr_in6</code> выровнена по 64 битам, то так же выровнен и 128-разрядный элемент <code>sin6_addr</code>. На некоторых 64-разрядных процессорах доступ к данным с 64-разрядными значениями оптимизирован, если данные выровнены так, что их адрес кратен 64.</p>
      <p>&#9632; Элемент <code>sin6_flowinfo</code> разделен на три поля:</p>
      <p> &#9633; 20 бит младшего порядка — это метка потока;</p>
      <p> &#9633; следующие 12 бит зарезервированы.</p>
      <p>Поле метки потока и поле приоритета рассматриваются в описании рис. А.2. Отметим, что использование поля приоритета еще не определено.</p>
      <p>&#9632; Элемент <code>sin6_scope_id</code> определяет контекст, в котором действует контекстный адрес (scoped address). Чаще всего это бывает индекс интерфейса для локальных адресов (см. раздел А.5).</p>
     </section>
     <section>
      <title>
       <p>Новая универсальная структура адреса сокета</p>
      </title>
      <p>Новая универсальная структура адреса сокета была определена как часть API сокетов IPv6 с целью преодолеть некоторые недостатки существующей структуры <code>sockaddr</code>. В отличие от структуры <code>sockaddr</code>, новая структура <code>sockaddr_storage</code> достаточно велика для хранения адреса сокета любого типа, поддерживаемого системой. Новая структура задается подключением заголовочного файла <code>&lt;netinet/in.h&gt;</code>, часть которого показана в листинге 3.4.</p>
      <p><strong>Листинг 3.4</strong>. Структура хранения адреса сокета sockaddr_storage</p>
      <p><code>struct sockaddr_storage {</code></p>
      <p><code> uint8_t ss_len;        /* длина этой структуры (зависит от реализации) */</code></p>
      <p><code> sa_family_t ss_family; /* семейство адреса. AF_xxx */</code></p>
      <p><code> /* зависящие от реализации элементы, обеспечивающие:</code></p>
      <p><code>  а) выравнивание, достаточное для выполнения требований по выравниванию всех</code></p>
      <p><code>     типов адресов сокетов, поддерживаемых системой;</code></p>
      <p><code>  б) достаточный объем для хранения адреса сокета любого типа,</code></p>
      <p><code>     поддерживаемого системой. */</code></p>
      <p><code>};</code></p>
      <p>Тип <code>sockaddr_storage</code> — это универсальная структура адреса сокета, отличающаяся от <code>struct sockaddr</code> по следующим параметрам:</p>
      <p>1. Если к структурам адресов сокетов, поддерживаемым системой, предъявляются требования по выравниванию, структура <code>sockaddr_storage</code> выполняет самое жесткое из них.</p>
      <p>2. Структура <code>sockaddr_storage</code> достаточно велика для размещения любой структуры адреса сокета, поддерживаемой системой.</p>
      <p>Заметьте, что поля структуры <code>sockaddr_storage</code> непрозрачны для пользователя, за исключением <code>ss_family</code> и <code>ss_len</code> (если таковые заданы). Структура <code>sockaddr_storage</code> должна преобразовываться в структуру адреса соответствующего типа для обращения к содержимому остальных полей.</p>
     </section>
     <section>
      <title>
       <p>Сравнение структур адреса сокетов</p>
      </title>
      <p>На рис. 3.1 показано сравнение пяти структур адресов сокетов, с которыми мы встретимся в тексте, предназначенных для IPv4, IPv6, доменного сокета Unix (см. листинг 15.1), канального уровня (см. листинг 18.1) и хранения. Подразумевается, что все структуры адреса сокета содержат 1-байтовое поле длины, поле семейства также занимает 1 байт и длина любого поля, размер которого ограничен снизу, в точности равна этому ограничению.</p>
      <image l:href="#img_25.png"/>
      <p><strong>Рис. 3.1</strong>. Сравнение различных структур адресов сокетов</p>
      <p>Две структуры адреса сокета имеют фиксированную длину, а структура доменного сокета Unix и структура канального уровня — переменную. При обработке структур переменной длины мы передаем функциям сокетов указатель на структуру адреса сокета, а в другом аргументе передаем длину этой структуры. Под каждой структурой фиксированной длины мы показываем ее размер в байтах (для реализации 4.4BSD).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Сама структура sockaddr_un имеет фиксированную длину, но объем информации в ней — длина полного имени (pathname) — может быть переменным. Передавая указатели на эти структуры, следует соблюдать аккуратность при обработке поля длины — как длины в структуре адреса сокета (если поле длины поддерживается данной реализацией), так и длины данных, передаваемых ядру и принимаемых от него.</p>
       <p>Этот рисунок служит также иллюстрацией стиля, которого мы придерживаемся в этой книге: названия структур на рисунках всегда выделяются полужирным шрифтом, а за ними следуют фигурные скобки.</p>
       <p>Ранее отмечалось, что в реализации 4.3BSD Reno ко всем структурам адресов сокетов было добавлено поле длины. Если бы поле длины присутствовало в оригинальной реализации сокетов, то не возникло бы необходимости передавать аргумент длины функциям сокетов (третий аргумент функций bind и connect). Вместо этого размер структуры мог бы храниться в поле длины структуры.</p>
      </cite>
     </section>
    </section>
    <section>
     <title>
      <p>3.3. Аргументы типа «значение-результат»</p>
     </title>
     <p>Мы отмечали, что когда структура адреса сокета передается какой-либо из функций сокетов, она всегда передается по ссылке, то есть в качестве аргумента передается указатель на структуру. Длина структуры также передается в качестве аргумента. Но способ, которым передается длина, зависит от того, в каком направлении передается структура: от процесса к ядру или наоборот.</p>
     <p>1. Три функции <code>bind</code>, <code>connect</code> и <code>sendto</code> передают структуру адреса сокета от процесса к ядру. Один из аргументов этих функций — указатель на структуру адреса сокета, другой аргумент — это целочисленный размер структуры, как показано в следующем примере:</p>
     <p><code>struct sockaddr_in serv;</code></p>
     <empty-line/>
     <p><code>/* заполняем serv{} */</code></p>
     <p><code>connect(sockfd, (SA*)&amp;serv, sizeof(serv));</code></p>
     <p>Поскольку ядру передается и указатель, и размер структуры, на которую он указывает, становится точно известно, какое количество данных нужно скопировать из процесса в ядро. На рис. 3.2 показан этот сценарий.</p>
     <image l:href="#img_26.png"/>
     <p><strong>Рис. 3.2</strong>. Структура адреса сокета, передаваемая от процесса к ядру</p>
     <p>В следующей главе мы увидим, что размер структуры адреса сокета в действительности имеет тип <code>socklen_t</code>, а не <code>int</code>, но POSIX рекомендует определять <code>socklen</code>_t как <code>uint32_t</code>.</p>
     <p>2. Четыре функции <code>accept</code>, <code>recvfrom</code>, <code>getsockname</code> и <code>getpeername</code> передают структуру адреса сокета от ядра к процессу, то есть в направлении, противоположном предыдущему случаю. Этим функциям передается указатель на структуру адреса сокета и указатель на целое число, содержащее размер структуры, как показано в следующем примере:</p>
     <p><code>struct sockaddr_un cli; /* домен Unix */</code></p>
     <p><code>socklen_t len;</code></p>
     <p><code>len = sizeof(cli);      /* len - это значение */</code></p>
     <p><code>getpeername(unixfd, (SA*)&amp;cli, &amp;len);</code></p>
     <p><code>/* значение len могло измениться */</code></p>
     <p>Причина замены типа для аргумента «длина» с целочисленного на указатель состоит в том, что «длина» эта является и <emphasis>значением</emphasis> при вызове функции (сообщает ядру размер структуры, так что ядро при заполнении структуры знает, где нужно остановиться), и <emphasis>результатом</emphasis>, когда функция возвращает значение (сообщает процессу, какой объем информации ядро действительно сохранило в этой структуре). Такой тип аргумента называется <emphasis>аргументом типа «значение-результат»</emphasis> (<emphasis>value-result argument</emphasis>). На рис. 3.3 представлен этот сценарий.</p>
     <image l:href="#img_27.png"/>
     <p><strong>Рис. 3.3</strong>. Структура адреса сокета, передаваемая от ядра к процессу</p>
     <p>Пример аргументов типа «значение-результат» вы увидите в листинге 4.2.</p>
     <p>Если при использовании аргумента типа «значение-результат» для длины структуры структура адреса сокета имеет фиксированную длину (см. рис. 3.1), то значение, возвращаемое ядром, будет всегда равно этому фиксированному размеру: 16 для <code>sockaddr_in</code> IPv4 и 24 для <code>sockaddr_in6</code> IPv6. Для структуры адреса сокета переменной длины (например, <code>sockaddr_un</code> домена Unix) возвращаемое значение может быть меньше максимального размера структуры (вы увидите это в листинге 15.2).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Мы говорили о структурах адресов сокетов, передаваемых между процессом и ядром. Для такой реализации, как 4.4BSD, где все функции сокетов являются системными вызовами внутри ядра, это верно. Но в некоторых реализациях, особенно в System V, функции сокетов являются лишь библиотечными функциями, которые выполняются как часть обычного пользовательского процесса. То, как эти функции взаимодействуют со стеком протоколов в ядре, относится к деталям реализации, которые обычно нас не волнуют. Тем не менее для простоты изложения мы будем продолжать говорить об этих структурах как о передаваемых между процессом и ядром такими функциями, как bind и connect. (В разделе В.1 вы увидите, что реализации System V действительно передают пользовательские структуры адресов сокетов между процессом и ядром, но как часть сообщений потоков STREAMS.)</p>
      <p>Существует еще две функции, передающие структуры адресов сокетов: это recvmsg и sendmsg (см. раздел 14.5). Однако при их вызове поле длины не является отдельным аргументом функции, а передается как одно из полей структуры.</p>
     </cite>
     <p>В сетевом программировании наиболее общим примером аргумента типа «значение-результат» может служить длина возвращаемой структуры адреса сокета. Вы встретите и другие аргументы типа «значение-результат»:</p>
     <p>&#9632; Три средних аргумента функции <code>select</code> (раздел 6.3).</p>
     <p>&#9632; Аргумент «длина» для функции <code>getsockopt</code> (см. раздел 7.2).</p>
     <p>&#9632; Элементы <code>msg_namelen</code> и <code>msg_controllen</code> структуры <code>msghdr</code> при использовании с функцией <code>recvmsg</code> (см. раздел 14.5).</p>
     <p>&#9632; Элемент <code>ifc_len</code> структуры <code>ifconf</code> (см. листинг 17.1).</p>
     <p>&#9632; Первый из двух аргументов длины в функции <code>sysctl</code> (см. раздел 18.4).</p>
    </section>
    <section>
     <title>
      <p>3.4. Функции определения порядка байтов</p>
     </title>
     <p>Рассмотрим 16-разрядное целое число, состоящее из двух байтов. Возможно два способа хранения этих байтов в памяти. Такое расположение, когда первым идет младший байт, называется <emphasis>прямым порядком байтов</emphasis> (<emphasis>little-endian</emphasis>), а когда первым расположен старший байт — <emphasis>обратным порядком байтов</emphasis> (<emphasis>big-endian</emphasis>). На рис. 3.4 показаны оба варианта.</p>
     <image l:href="#img_28.png"/>
     <p><strong>Рис. 3.4</strong>. Прямой и обратный порядок байтов для 16-разрядного целого числа</p>
     <p>Сверху на этом рисунке изображены адреса, возрастающие справа налево, а снизу — слева направо. <emphasis>Старший бит</emphasis> (<emphasis>most significant bit</emphasis>, <emphasis>MSB</emphasis>) является в 16-разрядном числе крайним слева, а <emphasis>младший бит</emphasis> (<emphasis>least significant bit</emphasis>, <emphasis>LSB</emphasis>) — крайним справа.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Термины «прямой порядок байтов» и «обратный порядок байтов» указывают, какой конец многобайтового значения — младший байт или старший — хранится в качестве начального адреса значения.</p>
     </cite>
     <empty-line/>
     <p>К сожалению, не существует единого стандарта порядка байтов, и можно встретить системы, использующие оба формата. Способ упорядочивания байтов, используемый в конкретной системе, мы называем <emphasis>порядком байтов узла</emphasis> (<emphasis>host byte order</emphasis>). Программа, представленная в листинге 3.5, выдает порядок байтов узла.</p>
     <p><strong>Листинг 3.5</strong>. Программа для определения порядка байтов узла</p>
     <p><code>//intro/byteorder.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  union {</code></p>
     <p><code> 6   short s;</code></p>
     <p><code> 7   char c[sizeof(short)];</code></p>
     <p><code> 8  } un;</code></p>
     <empty-line/>
     <p><code> 9  un.s = 0x0102;</code></p>
     <p><code>10  printf("%s: ", CPU_VENDOR_OS);</code></p>
     <p><code>11  if (sizeof(short) == 2) {</code></p>
     <p><code>12   if (un.c[0] == 1 &amp;&amp; un.c[1] == 2)</code></p>
     <p><code>13    printf("big-endian\n");</code></p>
     <p><code>14   else if (un.c[0] == 2 &amp;&amp; un.c[1] == 1)</code></p>
     <p><code>15    printf("little-endian\n");</code></p>
     <p><code>16   else</code></p>
     <p><code>17    printf("unknown\n");</code></p>
     <p><code>18  } else</code></p>
     <p><code>19   printf('sizeof(short) = %d\n", sizeof(short));</code></p>
     <p><code>20  exit(0);</code></p>
     <p><code>21 }</code></p>
     <p>Мы помещаем двухбайтовое значение <code>0x0102</code> в переменную типа <code>short</code> (короткое целое) и проверяем значения двух байтов этой переменной: <code>с[0]</code> (адрес А на рис. 3.4) и <code>c[1]</code> (адрес А + 1 на рис. 3.4), чтобы определить порядок байтов.</p>
     <p>Константа <code>CPU_VENDOR_OS</code> определяется программой GNU (аббревиатура «GNU» раскрывается рекурсивно — GNU's Not Unix) <code>autoconf</code> в процессе конфигурации, необходимой для выполнения программ из этой книги. В этой константе хранится тип центрального процессора, а также сведения о производителе и реализации операционной системы. Ниже представлены некоторые примеры вывода этой программы при запуске ее в различных системах (см. рис. 1.7).</p>
     <p><code>freebsd4 % <strong>byteorder</strong></code></p>
     <p><code>i386-unknown-freebsd4.8: little-endian</code></p>
     <empty-line/>
     <p><code>macosx % <strong>byteorder</strong></code></p>
     <p><code>powerpc-apple-darwin6.6: big-endian</code></p>
     <empty-line/>
     <p><code>freebsd5 % <strong>byteorder</strong></code></p>
     <p><code>sparc64-unknown-freebsd5.1: big-endian</code></p>
     <empty-line/>
     <p><code>aix % <strong>byteorder</strong></code></p>
     <p><code>powerpc-ibm-aix5.1.0.0: big-endian</code></p>
     <empty-line/>
     <p><code>hpux % <strong>byteorder</strong></code></p>
     <p><code>hppa1.1-hp-ux11 11: big-endian</code></p>
     <empty-line/>
     <p><code>linux % <strong>byteorder</strong></code></p>
     <p><code>i586-pc-linux-gnu: little-endian</code></p>
     <empty-line/>
     <p><code>solaris % <strong>byteorder</strong></code></p>
     <p><code>sparc-sun-solaris2.9: big-endian</code></p>
     <p>Все, что было сказано об определении порядка байтов 16-разрядного целого числа, конечно, справедливо и в отношении 32-разрядного целого.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Существуют системы, в которых возможен переход от прямого к обратному порядку байтов либо при перезапуске системы (MIPS 2000), либо в любой момент выполнения программы (Intel i860).</p>
     </cite>
     <p>Разработчикам сетевых приложений приходится обрабатывать различия в определении порядка байтов, поскольку в сетевых протоколах используется <emphasis>сетевой порядок байтов</emphasis> (<emphasis>network byte order</emphasis>). Например, в сегменте TCP есть 16- разрядный номер порта и 32-разрядный адрес IPv4. Стеки отправляющего и принимающего протоколов должны согласовывать порядок, в котором передаются байты этих многобайтовых полей. Протоколы Интернета используют обратный порядок байтов.</p>
     <p>Теоретически реализация Unix могла бы хранить поля структуры адреса сокета в порядке байтов узла, а затем выполнять необходимые преобразования при перемещении полей в заголовки протоколов и обратно, позволяя нам не беспокоиться об этом. Но исторически и с точки зрения POSIX определяется, что для некоторых полей в структуре адреса сокета порядок байтов всегда должен быть сетевым. Поэтому наша задача — выполнить преобразование из порядка байтов узла в сетевой порядок и обратно. Для этого мы используем следующие четыре функции:</p>
     <p><code>#include &lt;netinet/in.h&gt;</code></p>
     <empty-line/>
     <p><code>uint16_t htons(uint16_t <emphasis>host16bitvalue</emphasis>);</code></p>
     <p><code>uint32_t htonl(uint32_t <emphasis>host32bitvalue</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают значение, записанное в сетевом порядке байтов</emphasis></code></p>
     <empty-line/>
     <p><code>uint16_t ntohs(uint16_t <emphasis>net16bitvalue</emphasis>);</code></p>
     <p><code>uint32_t ntohl(uint32_t <emphasis>net32bitvalue</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают значение, записанное в порядке байтов узла</emphasis></code></p>
     <p>В названиях этих функций <code>h</code> обозначает <emphasis>узел</emphasis>, <code>n</code> обозначает <emphasis>сеть</emphasis>, <code>s</code> — тип <emphasis>short</emphasis>, <code>l</code> — тип <emphasis>long</emphasis>. Термины <emphasis>short</emphasis> и <emphasis>long</emphasis> являются наследием времен реализации 4.2BSD Digital VAX. Следует воспринимать <code>s</code> как 16-разрядное значение (например, номер порта TCP или UDP), а <code>l</code> — как 32-разрядное значение (например, адрес IPv4). В самом деле, в 64-разрядной системе Digital Alpha длинное целое занимает 64 разряда, а функции <code>htonl</code> и <code>ntohl</code> оперируют 32-разрядными значениями (несмотря на то, что используют тип <code>long</code>).</p>
     <p>Используя эти функции, мы можем не беспокоиться о реальном порядке байтов на узле и в сети. Для преобразования порядка байтов в конкретном значении следует вызвать соответствующую функцию. В системах с таким же порядком байтов, как в протоколах Интернета (обратным), эти четыре функции обычно определяются как пустой макрос.</p>
     <p>Мы еще вернемся к проблеме определения порядка байтов, обсуждая данные, содержащиеся в сетевом пакете, и сравнивая их с полями в заголовках протокола, в разделе 5.18 и упражнении 5.8.</p>
     <p>Мы до сих пор не определили термин байт. Его мы будем использовать для обозначения 8 бит, поскольку практически все современные компьютерные системы используют 8-битовые байты. Однако в большинстве стандартов Интернета для обозначения 8 бит используется термин <emphasis>октет</emphasis>. Началось это на заре TCP/IP, поскольку большая часть работы выполнялась в системах типа DEC-10, в которых не применялись 8-битовые байты. Еще одно важное соглашение, принятое в стандартах Интернета, связано с порядком битов. Во многих стандартах вы можете увидеть «изображения» пакетов, подобные приведенному ниже (это первые 32 разряда заголовка IPv4 из RFC 791):</p>
     <p><code>0                   1                   2                   3</code></p>
     <p><code>0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1</code></p>
     <p><code>+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</code></p>
     <p><code>|Version| IHL |Type of Service|           Total Length          |</code></p>
     <p><code>+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</code></p>
     <p>В этом примере приведены четыре байта в том порядке, в котором они передаются по проводам. Крайний слева бит является наиболее значимым. Однако нумерация начинается с нуля, который соответствует как раз наиболее значимому биту. Вам необходимо получше ознакомиться с этой записью, чтобы не испытывать трудностей при чтении описаний протоколов в RFC.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Типичной ошибкой среди программистов сетевых приложений начала 80-х, разрабатывающих код на рабочих станциях Sun (Motorola 68000 с обратным порядком байтов), было забыть вызвать одну из указанных четырех функций. На этих рабочих станциях программы работали нормально, но при переходе на машины с прямым порядком байтов они переставали работать.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>3.5. Функции управления байтами</p>
     </title>
     <p>Существует две группы функций, работающих с многобайтовыми полями без преобразования данных и без интерпретации их в качестве строк языка С с завершающим нулем. Они необходимы нам при обработке структур адресов сокетов, поскольку такие поля этих структур, как IP-адреса, могут содержать нулевые байты, но при этом не являются строками С. Строки с завершающим нулем обрабатываются функциями языка С, имена которых начинаются с аббревиатуры <code>str</code>. Эти функции подключаются с помощью файла <code>&lt;string.h&gt;</code>.</p>
     <p>Первая группа функций, названия которых начинаются с <code>b</code> (от слова «byte» — «байт»), взяты из реализации 4.2BSD и все еще предоставляются практически любой системой, поддерживающей функции сокетов. Вторая группа функций, названия которых начинаются с <code>mem</code> (от слова «memory» — память), взяты из стандарта ANSI С и доступны в любой системе, обеспечивающей поддержку библиотеки ANSI С.</p>
     <p>Сначала мы представим функции, которые берут начало от реализации Беркли, хотя в книге мы будем использовать только одну из них — <code>bzero</code>. (Дело в том, что она имеет только два аргумента и ее проще запомнить, чем функцию <code>memset</code> с тремя аргументами, как объяснялось в разделе 1.2.) Две другие функции, <code>bcopy</code> и <code>bcmp</code>, могут встретиться вам в существующих приложениях.</p>
     <p><code>#include &lt;strings.h&gt;</code></p>
     <empty-line/>
     <p><code>void bzero(void *<emphasis>dest</emphasis>, size_t <emphasis>nbytes</emphasis>);</code></p>
     <empty-line/>
     <p><code>void bcopy(const void *<emphasis>src</emphasis>, void *<emphasis>dest</emphasis>, size_t <emphasis>nbytes</emphasis>);</code></p>
     <empty-line/>
     <p><code>int bcmp(const void *<emphasis>ptr1</emphasis>, const void *<emphasis>ptr2</emphasis>, size_t <emphasis>nbytes</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае равенства, ненулевое значение в случае неравенства</emphasis></code></p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Мы впервые встречаемся со спецификатором const. В приведенном примере он служит признаком того, что значения, на которые указывает указатель, то есть src, ptr1 и ptr2, не изменяются функцией. Другими словами, область памяти, на которую указывает указатель со спецификатором const, считывается функцией, но не изменяется.</p>
     </cite>
     <p>Функция <code>bzero</code> обнуляет заданное число байтов в указанной области памяти. Мы часто используем эту функцию для инициализации структуры адреса сокета нулевым значением. Функция <code>bcopy</code> копирует заданное число байтов из источника в место назначения. Функция <code>bcmp</code> сравнивает две произвольных последовательности байтов и возвращает нулевое значение, если две байтовых строки идентичны, и ненулевое — в противном случае.</p>
     <p>Следующие функции являются функциями ANSI С:</p>
     <p><code>#include &lt;string.h&gt;</code></p>
     <empty-line/>
     <p><code>void *memset(void *<emphasis>dest</emphasis>, int <emphasis>c</emphasis>, size_t <emphasis>len</emphasis>);</code></p>
     <empty-line/>
     <p><code>void *memcpy(void *<emphasis>dest</emphasis>, const void *<emphasis>src</emphasis>, size_t <emphasis>nbytes</emphasis>);</code></p>
     <empty-line/>
     <p><code>int memcmp(const void *<emphasis>ptr1</emphasis>, const void *<emphasis>ptr2</emphasis>, size_t <emphasis>nbytes</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае равенства, значение &lt;0 или &gt;0 в случае неравенства (см. текст)</emphasis></code></p>
     <p>Функция <code>memset</code> присваивает заданному числу байтов значение с. Функция <code>memcpy</code> аналогична функции <code>bcopy</code>, но имеет другой порядок двух аргументов. Функция <code>bcopy</code> корректно обрабатывает перекрывающиеся поля, в то время как поведение функции <code>memcpy</code> не определено, если источник и место назначения перекрываются. В случае перекрывания полей должна использоваться функция ANSI С <code>memmove</code> (упражнение 30.3).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Чтобы запомнить порядок аргументов функции memcpy, подумайте о том, что он совпадает с порядком аргументов в операторе присваивания (справа — оригинал, слева — копия).</p>
      <p><code>dest = src;</code></p>
      <p>Последним аргументом этой функции (как и всех ANSI-функций memXXX) всегда является длина области памяти.</p>
     </cite>
     <p>Функция <code>memcmp</code> сравнивает две произвольных последовательности байтов и возвращает нуль, если они идентичны. В противном случае знак возвращаемого значения определяется знаком разности между первыми несовпадающими байтами, на которые указывают <emphasis>ptr1</emphasis> и <emphasis>ptr2</emphasis>. Предполагается, что сравниваемые байты принадлежат к типу <code>unsigned char</code>.</p>
    </section>
    <section>
     <title>
      <p>3.6. Функции inet_aton, inet_addr и inet_ntoa</p>
     </title>
     <p>Существует две группы функций преобразования адресов, которые мы рассматриваем в этом и следующем разделах. Они выполняют преобразование адресов Интернета из строк ASCII (удобных для человеческого восприятия) в двоичные значения с сетевым порядком байтов (эти значения хранятся в структурах адресов сокетов).</p>
     <p>1. Функции <code>inet_aton</code>, <code>inet_ntoa</code> и <code>inet_addr</code> преобразуют адрес IPv4 из точечно-десятичной записи (например, 206.168.112.96) в 32-разрядное двоичное значение в сетевом порядке байтов. Возможно, вы встретите эти функции в многочисленных существующих программах.</p>
     <p>2. Более новые функции <code>inet_pton</code> и <code>inet_ntop</code> работают и с адресами IPv4, и с адресами IPv6. Эти функции, описываемые в следующем разделе, мы используем в книге.</p>
     <p><code>#include &lt;arpa/inet.h&gt;</code></p>
     <empty-line/>
     <p><code>int inet_aton(const char *<emphasis>strptr</emphasis>, struct in_addr *<emphasis>addrptr</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 1, если строка преобразована успешно, 0 в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>in_addr_t inet_addr(const char *<emphasis>strptr</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 32-разрядный адрес IPv4 в сетевом порядке байтов: INADDR_NONE в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>char *inet_ntoa(struct in_addr <emphasis>inaddr</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: указатель на строку с адресом в точечно-десятичной записи</emphasis></code></p>
     <p>Первая из названных функций, <code>inet_aton</code>, преобразует строку, на которую указывает <code>strptr</code>, в 32-разрядное двоичное число, записанное в сетевом порядке байтов, передаваемое через указатель <code>addrptr</code>. При успешном выполнении возвращаемое значение равно 1, иначе возвращается нуль.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Функция inet_aton обладает одним недокументированным свойством: если addrptr — пустой указатель (null pointer), функция все равно выполняет проверку допустимости адреса, содержащегося во входной строке, но не сохраняет результата.</p>
     </cite>
     <p>Функция <code>inet_addr</code> выполняет то же преобразование, возвращая в качестве значения 32-разрядное двоичное число в сетевом порядке байтов. Проблема при использовании этой функции состоит в том, что все 2<sup>32</sup> возможных двоичных значений являются действительными IP-адресами (от 0.0.0.0 до 255.255.255.255), но в случае возникновения ошибки функция возвращает константу <code>INADDR_NONE</code> (обычно представленную двоичным числом, состоящим из 32 бит, установленных в единицу). Это означает, что точечно-десятичная запись 255.255.255.255 (ограниченный адрес для широковещательной передачи IPv4, см. раздел 18.2) не может быть обработана этой функцией, поскольку ее двоичное значение выглядит как указание на сбой при выполнении функции.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Характерной проблемой, сопровождающей выполнение функции inet_addr, может стать то, что, как утверждается в некоторых руководствах, в случае ошибки она возвращает значение -1 вместо INADDR_NONE. С некоторыми компиляторами это может вызвать проблемы при сравнении возвращаемого значения функции (значение без знака) с отрицательной константой.</p>
     </cite>
     <p>На сегодняшний день функция <code>inet_addr</code> является нерекомендуемой, или устаревшей, и в создаваемом коде вместо нее должна использоваться функция <code>inet_aton</code>. Еще лучше использовать более новые функции, описанные в следующем разделе, работающие и с IPv4, и с IPv6.</p>
     <p>Функция <code>inet_ntoa</code> преобразует 32-разрядный двоичный адрес IPv4, хранящийся в сетевом порядке байтов, в точечно-десятичную строку. Строка, на которую указывает возвращаемый функцией указатель, находится в статической памяти. Это означает, что функция не допускает повторного вхождения, то есть не является повторно входимой (reentrant), что мы обсудим в разделе 11.14. Наконец, отметим, что эта функция принимает в качестве аргумента структуру, а не указатель на структуру.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Функции, принимающие структуры в качестве аргументов, встречаются редко. Более общим способом является передача указателя на структуру.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>3.7. Функции inet_pton и inet_ntop</p>
     </title>
     <section>
      <p>Эти функции появились с IPv6 и работают как с адресами IPv4, так и с адресами IPv6. Их мы и будем использовать в книге. Символы <code>p</code> и <code>n</code> обозначают соответственно формат <emphasis>представления</emphasis> и <emphasis>численный</emphasis> формат. Формат представления адреса часто является строкой ASCII, а численный формат — это двоичное значение, входящее в структуру адреса сокета. #include &lt;arpa/inet.h&gt;</p>
      <p><code>int inet_pton(int <emphasis>family</emphasis>, const char *<emphasis>strptr</emphasis>, void *<emphasis>addrptr</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: 1 в случае успешного выполнения функции: 0, если входная строка имела неверный формат представления; -1 в случае ошибки</emphasis></code></p>
      <empty-line/>
      <p><code>const char *inet_ntop(int <emphasis>family</emphasis>, const void *<emphasis>addrptr</emphasis>,</code></p>
      <p><code> char *<emphasis>strptr</emphasis>, size_t <emphasis>len</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: указатель на результат, если выполнение функции прошло успешно. NULL в случае ошибки</emphasis></code></p>
      <p>Значением аргумента <code>family</code> для обеих функций может быть либо <code>AF_INET</code>, либо <code>AF_INET6</code>. Если <code>family</code> не поддерживается, обе функции возвращают ошибку со значением переменной <code>errno</code>, равным <code>EAFNOSUPPORT</code>.</p>
      <p>Первая функция пытается преобразовать строку, на которую указывает <code>strptr</code>, сохраняя двоичный результат с помощью указателя <code>addrptr</code>. При успешном выполнении ее возвращаемое значение равно 1. Если входная строка находится в неверном формате представления для заданного семейства (<code>family</code>), возвращается нуль.</p>
      <p>Функция <code>inet_ntop</code> выполняет обратное преобразование: из численного формата (<code>addrptr</code>) в формат представления (<code>strptr</code>). Аргумент <code>len</code> — это размер принимающей строки, который передается, чтобы функция не переполнила буфер вызывающего процесса. Чтобы облегчить задание этого размера, в заголовочный файл <code>&lt;netinet/in.h&gt;</code> включаются следующие определения:</p>
      <p><code>#define INET_ADDRSTRLEN  16 /* для точечно-десятичной записи IPv4-адреса */</code></p>
      <p><code>#define INET6_ADDRSTRLEN 46 /* для шестнадцатеричной записи IPv6-адреса */</code></p>
      <p>Если аргумент <code>len</code> слишком мал для хранения результирующего формата представления вместе с символом конца строки (terminating null), возвращается пустой указатель и переменной <code>errno</code> присваивается значение <code>ENOSPC</code>.</p>
      <p>Аргумент <code>strptr</code> функции <code>inet_ntop</code> не может быть пустым указателем. Вызывающий процесс должен выделить память для хранения преобразованного значения и задать ее размер. При успешном выполнении функции возвращаемым значением является этот указатель.</p>
      <p>На рис. 3.5 приведена схема действия пяти функций, описанных в этом и предыдущем разделах.</p>
      <image l:href="#img_29.png"/>
      <p><strong>Рис. 3.5</strong>. Функции преобразования адресов</p>
     </section>
     <section>
      <title>
       <p>Пример</p>
      </title>
      <p>Даже если ваша система еще не поддерживает IPv6, вы можете использовать новые функции, заменив вызовы вида</p>
      <p><code>foo.sin_addr.s_addr = inet_addr(cp);</code></p>
      <p>на</p>
      <p><code>inet_pton(AF_INET, cp, &amp;foo.sin_addr);</code></p>
      <p>а также заменив вызовы вида</p>
      <p><code>ptr = inet_ntoa(foo.sin_addr);</code></p>
      <p>на</p>
      <p><code>char str[INET_ADDRSTRLEN];</code></p>
      <p><code>ptr = inet_ntop(AF_INET, &amp;foo.sin_addr, str, sizeof(str));</code></p>
      <p>В листинге 3.6 представлено простое определение функции <code>inet_pton</code>, поддерживающее только IPv4, а в листинге 3.7 — версия <code>inet_ntop</code>, поддерживающая только IPv4.</p>
      <p><strong>Листинг 3.6</strong>. Простая версия функции inet_pton, поддерживающая только IPv4</p>
      <p><code>//libfree/inet_pton_ipv4.c</code></p>
      <p><code>10 int</code></p>
      <p><code>11 inet_pton(int family, const char *strptr, void *addrptr)</code></p>
      <p><code>12 {</code></p>
      <p><code>13  if (family == AF_INET) {</code></p>
      <p><code>14   struct in_addr in_val;</code></p>
      <empty-line/>
      <p><code>15   if (inet_aton(strptr, &amp;in_val)) {</code></p>
      <p><code>16    memcpy(addrptr, &amp;in_val, sizeof(struct in_addr));</code></p>
      <p><code>17    return (1);</code></p>
      <p><code>18   }</code></p>
      <p><code>19   return (0);</code></p>
      <p><code>20  }</code></p>
      <p><code>21  errno = EAFNOSUPPORT;</code></p>
      <p><code>22  return (-1);</code></p>
      <p><code>23 }</code></p>
      <p><strong>Листинг 3.7</strong>. Простая версия функции inet_ntop, поддерживающая только IPv4</p>
      <p><code>//libfree/inet_ntop_ipv4.c</code></p>
      <p><code> 8 const char *</code></p>
      <p><code> 9 inet_ntop(int family, const void *addrptr, char *strptr, size_t len)</code></p>
      <p><code>10 {</code></p>
      <p><code>11  const u_char *p = (const u_char*)addrptr;</code></p>
      <empty-line/>
      <p><code>12  if (family == AF_INET) {</code></p>
      <p><code>13   char temp[INET_ADDRSTRLEN];</code></p>
      <empty-line/>
      <p><code>14   snprintf(temp, sizeof(temp), "%d.%d.%d.%d",</code></p>
      <p><code>15    p[0], p[1], p[2], p[3]);</code></p>
      <p><code>16   if (strlen(temp) &gt;= len) {</code></p>
      <p><code>17    errno = ENOSPC;</code></p>
      <p><code>18    return (NULL);</code></p>
      <p><code>19   }</code></p>
      <p><code>20   strcpy(strptr, temp);</code></p>
      <p><code>21   return (strptr);</code></p>
      <p><code>22  }</code></p>
      <p><code>23  errno = EAFNOSUPPORT;</code></p>
      <p><code>24  return (NULL);</code></p>
      <p><code>25 }</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>3.8. Функция sock_ntop и связанные с ней функции</p>
     </title>
     <p>Основная проблема, связанная с функцией <code>inet_ntop</code>, состоит в том, что вызывающий процесс должен передать ей указатель на двоичный адрес. Этот адрес обычно содержится в структуре адреса сокета, поэтому вызывающему процессу необходимо знать формат структуры и семейство адресов. Следовательно, чтобы использовать эту функцию, для IPv4 нужно написать код следующего вида:</p>
     <p><code>struct sockaddr_in addr;</code></p>
     <p><code>inet_ntop(AF_INET, &amp;addr.sin_addr, str, sizeof(str));</code></p>
     <p>или для IPv6 такого вида:</p>
     <p><code>struct sockaddr_in6 addr6:</code></p>
     <p><code>inet_ntop(AF_INET6, &amp;addr6.sin6_addr, str, sizeof(str));</code></p>
     <p>Как видите, код становится зависящим от протокола.</p>
     <p>Чтобы решить эту проблему, напишем собственную функцию и назовем ее <code>sock_ntop</code>. Она получает указатель на структуру адреса сокета, исследует эту структуру и вызывает соответствующую функцию для того, чтобы возвратить формат представления адреса.</p>
     <p><code>#include "unp.h"</code></p>
     <empty-line/>
     <p><code>char *sock_ntop(const struct sockaddr *<emphasis>sockaddr</emphasis>, socklen_t <emphasis>addrlen</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: непустой указатель, если функция выполнена успешно, NULL в случае ошибки</emphasis></code></p>
     <p><code>sockaddr</code> указывает на структуру адреса сокета, длина которой равна значению <code>addrlen</code>. Функция <code>sock_ntop</code> использует свой собственный статический буфер для хранения результата и возвращает указатель на этот буфер.</p>
     <p>Формат представления — либо точечно-десятичная форма записи адреса IPv4, либо шестнадцатеричная форма записи адреса IPv6, за которой следует завершающий символ (мы используем точку, как в программе <code>netstat</code>), затем десятичный номер порта, а затем завершающий нуль. Следовательно, размер буфера должен быть равен как минимум <code>INET_ADDRSTRLEN</code> плюс 6 байт для IPv4 (16 + 6 - 22) либо <code>INET6_ADDRSTRLEN</code> плюс 6 байт для IPv6 (46 + 6 - 52).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Обратите внимание, что при статическом хранении результата функция не допускает повторного вхождения (не является повторно входимой) и не может быть использована несколькими программными потоками (не является безопасной в многопоточной среде — thread-safe). Более подробно мы поговорим об этом в разделе 11.18. Мы допустили такое решение для этой функции, чтобы ее было легче вызывать из простых программ, приведенных в книге.</p>
     </cite>
     <p>В листинге 3.8 представлена часть исходного кода, обрабатывающая семейство <code>AF_INET</code>.</p>
     <p><strong>Листинг 3.8</strong>. Наша функция sock_ntop</p>
     <p><code>//lib/sock_ntop.c</code></p>
     <p><code> 5 char *</code></p>
     <p><code> 6 sock_ntop(const struct sockaddr *sa, socklen_t salen)</code></p>
     <p><code> 7 {</code></p>
     <p><code> 8  char portstr[7];</code></p>
     <p><code> 9  static char str[128]; /* макс. длина для доменного сокета Unix */</code></p>
     <empty-line/>
     <p><code>10  switch (sa-&gt;sa_family) {</code></p>
     <p><code>11  case AF_INET: {</code></p>
     <p><code>12    struct sockaddr_in *sin = (struct sockaddr_in*)sa;</code></p>
     <empty-line/>
     <p><code>13    if (inet_ntop(AF_INET, &amp;sin-&gt;sin_addr. str, sizeof(str)) == NULL)</code></p>
     <p><code>14     return (NULL);</code></p>
     <p><code>15    if (ntohs(sin-&gt;sin_port) != 0) {</code></p>
     <p><code>16     snprintf(portstr, sizeof(portstr), ntohs(sin-&gt;sin_port));</code></p>
     <p><code>17     strcat(str, portstr);</code></p>
     <p><code>18    }</code></p>
     <p><code>19    return (str);</code></p>
     <p><code>20   }</code></p>
     <p>Для работы со структурами адресов сокетов мы определяем еще несколько функций, которые упростят переносимость нашего кода между IPv4 и IPv6.</p>
     <p><code>#include "unp.h"</code></p>
     <empty-line/>
     <p><code>int sock_bind_wild(int <emphasis>sockfd</emphasis>, int <emphasis>family</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного выполнения функции, -1 в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>int sock_cmp_addr(const struct sockaddr *<emphasis>sockaddr1</emphasis>,</code></p>
     <p><code> const struct sockaddr *<emphasis>sockaddr2</emphasis>, socklen_t <emphasis>addrlen</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0, если адреса относятся к одному семейству и совпадают, ненулевое значение в противном случае</emphasis></code></p>
     <empty-line/>
     <p><code>int sock_cmp_port(const struct sockaddr *<emphasis>sockaddr1</emphasis>,</code></p>
     <p><code> const struct sockaddr *<emphasis>sockaddr2</emphasis>, socklen_t <emphasis>addrlen</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0, если адреса относятся к одному семейству и порты совпадают, ненулевое значение в противном случае</emphasis></code></p>
     <empty-line/>
     <p><code>int sock_get_port(const struct sockaddr *<emphasis>sockaddr</emphasis>, socklen_t <emphasis>addrlen</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: неотрицательный номер порта для адресов IPv4 или IPv6, иначе -1</emphasis></code></p>
     <empty-line/>
     <p><code>char *sock_ntop_host(const struct sockaddr *<emphasis>sockaddr</emphasis>, socklen_t <emphasis>addrlen</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: непустой указатель в случае успешного выполнения функции, NULL в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>void sock_set_addr(const struct sockaddr *<emphasis>sockaddr</emphasis>,</code></p>
     <p><code> socklen_t <emphasis>addrlen</emphasis>, void *<emphasis>ptr</emphasis>);</code></p>
     <p><code>void sock_set_port(const struct sockaddr *<emphasis>sockaddr</emphasis>,</code></p>
     <p><code> socklen_t <emphasis>addrlen</emphasis>, int <emphasis>port</emphasis>);</code></p>
     <p><code>void sock_set_wild(struct sockaddr *<emphasis>sockaddr</emphasis>, socklen_t <emphasis>addrlen</emphasis>);</code></p>
     <p>Функция <code>sock_bind_wild</code> связывает универсальный адрес и динамически назначаемый порт с сокетом. Функция <code>sock_cmp_addr</code> сравнивает адресные части двух структур адреса сокета, а функция <code>sock_cmp_port</code> сравнивает номера их портов. Функция <code>sock_get_port</code> возвращает только номер порта, а функция <code>sock_ntop_host</code> преобразует к формату представления только ту часть структуры адреса сокета, которая относится к узлу (все, кроме порта, то есть IP-адрес узла). Функция <code>sock_set_addr</code> присваивает адресной части структуры значение, указанное аргументом <code>ptr</code>, а функция <code>sock_set_port</code> задает в структуре адреса сокета только номер порта. Функция <code>sock_set_wild</code> задает адресную часть структуры через символы подстановки. Как обычно, мы предоставляем для всех этих функций функции- обертки, которые возвращают значение, отличное от типа void, и в наших программах обычно вызываем именно обертки. Мы не приводим в данной книге исходный код для этих функций, так как он свободно доступен (см. предисловие).</p>
    </section>
    <section>
     <title>
      <p>3.9. Функции readn, writen и readline</p>
     </title>
     <p>Потоковые сокеты (например, сокеты TCP) демонстрируют с функциями <code>read</code> и <code>write</code> поведение, отличное от обычного ввода-вывода файлов. Функция <code>read</code> или <code>write</code> на потоковом сокете может ввести или вывести немного меньше байтов, чем запрашивалось, но это не будет ошибкой. Причиной может быть достижение границ буфера для сокета в ядре. Все, что требуется в этой ситуации — чтобы процесс повторил вызов функции <code>read</code> или <code>write</code> для ввода или вывода оставшихся байтов. (Некоторые версии Unix ведут себя аналогично при записи в канал (pipe) более 4096 байт.) Этот сценарий всегда возможен на потоковом сокете при выполнении функции <code>read</code>, но с функцией <code>write</code> он обычно наблюдается, только если сокет неблокируемый. Тем не менее вместо <code>write</code> мы всегда вызываем функцию <code>writen</code> на тот случай, если в данной реализации возможно возвращение меньшего количества данных, чем мы запрашиваем.</p>
     <p>Введем три функции для чтения и записи в потоковый сокет.</p>
     <p><code>#include "unp.h"</code></p>
     <empty-line/>
     <p><code>ssize_t readn(int <emphasis>filedes</emphasis>, void *<emphasis>buff</emphasis>, size_t <emphasis>nbytes</emphasis>);</code></p>
     <p><code>ssize_t writen(int <emphasis>filedes</emphasis>, const void *<emphasis>buff</emphasis>, size_t <emphasis>nbytes</emphasis>);</code></p>
     <p><code>ssize_t readline(int <emphasis>filedes</emphasis>, void *<emphasis>buff</emphasis>, size_t <emphasis>maxlen</emphasis>);</code></p>
     <p><code><emphasis>Все функции возвращают: количество считанных или записанных байтов, -1 в случае ошибки</emphasis></code></p>
     <p>В листинге 3.9 представлена функция <code>readn</code>, в листинге 3.10 — функция <code>writen</code>, а в листинге 3.11 — функция <code>readline</code>.</p>
     <p><strong>Листинг 3.9</strong>. Функция readn: считывание n байт из дескриптора</p>
     <p><code>//lib/readn.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 ssize_t /* Считывает n байт из дескриптора */</code></p>
     <p><code> 3 readn(int fd, void *vptr, size_t n)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  size_t nleft;</code></p>
     <p><code> 6  ssize_t nread;</code></p>
     <p><code> 7  char *ptr;</code></p>
     <empty-line/>
     <p><code> 8  ptr = vptr;</code></p>
     <p><code> 9  nleft = n;</code></p>
     <p><code>10  while (nleft &gt; 0) {</code></p>
     <p><code>11   if ((nread = read(fd, ptr, nleft)) &lt; 0) {</code></p>
     <p><code>12    if (errno == EINTR)</code></p>
     <p><code>13     nread = 0; /* и вызывает снова функцию read() */</code></p>
     <p><code>14    else</code></p>
     <p><code>15     return (-1);</code></p>
     <p><code>16   } else if (nread == 0)</code></p>
     <p><code>17   break; /* EOF */</code></p>
     <empty-line/>
     <p><code>18   nleft -= nread;</code></p>
     <p><code>19   ptr += nread;</code></p>
     <p><code>20  }</code></p>
     <p><code>21  return (n - nleft); /* возвращает значение &gt;= 0 */</code></p>
     <p><code>22 }</code></p>
     <p><strong>Листинг 3.10</strong>. Функция writen: запись n байт в дескриптор</p>
     <p><code>//lib/writen.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 ssize_t /* Записывает n байт в дескриптор */</code></p>
     <p><code> 3 writen(int fd, const void *vptr, size_t n)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  size_t nleft;</code></p>
     <p><code> 6  ssize_t nwritten;</code></p>
     <p><code> 7  const char *ptr;</code></p>
     <empty-line/>
     <p><code> 8  ptr = vptr;</code></p>
     <p><code> 9  nleft = n;</code></p>
     <p><code>10  while (nleft &gt; 0) {</code></p>
     <p><code>11   if ((nwritten = write(fd, ptr, nleft)) &lt;= 0) {</code></p>
     <p><code>12    if (errno == EINTR)</code></p>
     <p><code>13     nwritten = 0; /* и снова вызывает функцию write() */</code></p>
     <p><code>14    else</code></p>
     <p><code>15     return (-1); /* ошибка */</code></p>
     <p><code>16   }</code></p>
     <p><code>17   nleft -= nwritten;</code></p>
     <p><code>18   ptr += nwritten;</code></p>
     <p><code>19  }</code></p>
     <p><code>20  return (n);</code></p>
     <p><code>21 }</code></p>
     <p><strong>Листинг 3.11</strong>. Функция readline: считывание следующей строки из дескриптора, по одному байту за один раз</p>
     <p><code>//test/readline1.с</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <p><code>   /* Ужасно медленная версия, приводится только для примера */</code></p>
     <empty-line/>
     <p><code> 2 ssize_t</code></p>
     <p><code> 3 readline(int fd, void *vptr, size_t maxlen)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  ssize_t n, rc;</code></p>
     <p><code> 6  char c, *ptr;</code></p>
     <empty-line/>
     <p><code> 7  ptr = vptr;</code></p>
     <p><code> 8  for (n = 1; n &lt; maxlen; n++) {</code></p>
     <p><code> 9   again:</code></p>
     <p><code>10   if ((rc = read(fd, &amp;c, 1)) == 1) {</code></p>
     <p><code>11    *ptr++ = c;</code></p>
     <p><code>12    if (c == '\n')</code></p>
     <p><code>13     break; /* записан символ новой строки, как в fgets() */</code></p>
     <p><code>14   } else if (rc == 0) {</code></p>
     <p><code>15    if (n == 1)</code></p>
     <p><code>16     return (0); /* EOF, данные не считаны */</code></p>
     <p><code>17    else</code></p>
     <p><code>18     break; /* EOF, некоторые данные были считаны */</code></p>
     <p><code>19   } else {</code></p>
     <p><code>20    if (errno == EINTR)</code></p>
     <p><code>21     goto again;</code></p>
     <p><code>22    return (-1); /* ошибка, errno задается функцией read() */</code></p>
     <p><code>23   }</code></p>
     <p><code>24  }</code></p>
     <empty-line/>
     <p><code>25  *ptr = 0; /* завершаем нулем, как в fgets() */</code></p>
     <p><code>26  return (n);</code></p>
     <p><code>27 }</code></p>
     <p>Если функция чтения или записи (<code>read</code> или <code>write</code>) возвращает ошибку, то наши функции проверяют, не совпадает ли код ошибки с EINTR (прерывание системного вызова сигналом, см. раздел 5.9). В этом случае прерванная функция вызывается повторно. Мы обрабатываем ошибку в этой функции, чтобы не заставлять процесс снова вызвать <code>read</code> или <code>write</code>, поскольку целью наших функций является предотвращение обработки нехватки данных вызывающим процессом.</p>
     <p>В разделе 14.3 мы покажем, что вызов функции <code>recv</code> с флагом <code>MSG_WAITALL</code> позволяет обойтись без использования отдельной функции <code>readn</code>.</p>
     <p>Заметим, что наша функция <code>readline</code> вызывает системную функцию <code>read</code> один раз для каждого байта данных. Это очень неэффективно, поэтому мы и написали в примечании «Ужасно медленно!». Возникает соблазн обратиться к стандартной библиотеке ввода-вывода (<code>stdio</code>). Об этом мы поговорим через некоторое время в разделе 14.8, но учтите, что это может привести к определенным проблемам. Буферизация, предоставляемая <code>stdio</code>, решает проблемы с производительностью, но при этом создает множество логистических сложностей, которые в свою очередь порождают скрытые ошибки в приложении. Дело в том, что состояние буферов <code>stdio</code> недоступно процессу. Рассмотрим, например, строчный протокол взаимодействия клиента и сервера, причем такой, что могут существовать разные независимые реализации клиентов и серверов (достаточно типичное явление; например, множество веб-браузеров и веб-серверов были разработаны независимо в соответствии со спецификацией HTTP). Хороший стиль программирования заключается в том, что эти программы должны не только ожидать от своих собеседников соблюдения того же протокола, но и контролировать трафик на возможность получения непредвиденного трафика. Подобные нарушения протокола должны рассматриваться как ошибки, чтобы программисты имели возможность находить и устранять неполадки в коде, а также обнаруживать попытки взлома систем. Обработка некорректного трафика должна давать приложению возможность продолжать работу. Буферизация <code>stdio</code> мешает достижению перечисленных целей, поскольку приложение не может проверить наличие непредвиденных (некорректных) данных в буферах <code>stdio</code> в любой конкретный момент.</p>
     <p>Существует множество сетевых протоколов, основанных на использовании строк текста: SMTP, HTTP, FTP, finger. Поэтому соблазн работать со строками будет терзать вас достаточно часто. Наш совет: мыслить в терминах буферов, а не строк. Пишите код таким образом, чтобы считывать содержимое буфера, а не отдельные строки. Если же ожидается получение строки, ее всегда можно поискать в считанном буфере.</p>
     <p>В листинге 3.12 приведена более быстрая версия функции <code>readline</code>, использующая свой собственный буфер (а не буферизацию <code>stdio</code>). Основное достоинство этого буфера состоит в его открытости, благодаря чему вызывающий процесс всегда знает, какие именно данные уже приняты. Несмотря на это, использование <code>readline</code> все равно может вызвать проблемы, как мы увидим в разделе 6.3. Системные функции типа <code>select</code> ничего не знают о внутреннем буфере <code>readline</code>, поэтому неаккуратно написанная программа с легкостью может очутиться в состоянии ожидания в вызове <code>select</code>, при том, что данные уже будут находиться в буферах <code>readline</code>. По этой причине сочетание вызовов <code>readn</code> и <code>readline</code> не будет работать так, как этого хотелось бы, пока функция <code>readn</code> не будет модифицирована с учетом наличия внутреннего буфера.</p>
     <p><strong>Листинг 3.12</strong>. Улучшенная версия функции readline</p>
     <p><code>//lib/readline.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 static int read_cnt;</code></p>
     <p><code> 3 static char *read_ptr;</code></p>
     <p><code> 4 static char read_buf[MAXLINE];</code></p>
     <empty-line/>
     <p><code> 5 static ssize_t</code></p>
     <p><code> 6 my_read(int fd, char *ptr)</code></p>
     <p><code> 7 {</code></p>
     <empty-line/>
     <p><code> 8  if (read_cnt &lt;= 0) {</code></p>
     <p><code> 9   again:</code></p>
     <p><code>10   if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) &lt; 0) {</code></p>
     <p><code>11    if (errno == EINTR)</code></p>
     <p><code>12     goto again;</code></p>
     <p><code>13    return(-1);</code></p>
     <p><code>14   } else if (read_cnt == 0)</code></p>
     <p><code>15   return(0);</code></p>
     <p><code>16   read_ptr = read_buf;</code></p>
     <p><code>17  }</code></p>
     <p><code>18  read_cnt--;</code></p>
     <p><code>19  *ptr = *read_ptr++;</code></p>
     <p><code>20  return(1);</code></p>
     <p><code>21 }</code></p>
     <empty-line/>
     <p><code>22 ssize_t</code></p>
     <p><code>23 readline(int fd, void *vptr, size_t maxlen)</code></p>
     <p><code>24 {</code></p>
     <p><code>25  ssize_t n, rc;</code></p>
     <p><code>26  char c, *ptr;</code></p>
     <empty-line/>
     <p><code>27  ptr = vptr;</code></p>
     <p><code>28  for (n = 1; n &lt; maxlen; n++) {</code></p>
     <p><code>29   if ((rc = my_read(fd, &amp;c)) == 1) {</code></p>
     <p><code>30    *ptr++ = c;</code></p>
     <p><code>31    if (c== '\n')</code></p>
     <p><code>32     break; /* Записан символ новой строки, как в fgets() */</code></p>
     <p><code>33   } else if (rc == 0) {</code></p>
     <p><code>34    *ptr = 0;</code></p>
     <p><code>35    return(n - 1); /* EOF, считано n-1 байт данных */</code></p>
     <p><code>36   } else</code></p>
     <p><code>37    return(-1); /* ошибка, read() задает значение errno */</code></p>
     <p><code>38  }</code></p>
     <empty-line/>
     <p><code>39  *ptr = 0; /* завершающий нуль, как в fgets() */</code></p>
     <p><code>40  return(n);</code></p>
     <p><code>41 }</code></p>
     <empty-line/>
     <p><code>42 ssize_t</code></p>
     <p><code>43 readlinebuf(void **vptrptr)</code></p>
     <p><code>44 {</code></p>
     <p><code>45  if (read_cnt)</code></p>
     <p><code>46   *vptrptr = read_ptr;</code></p>
     <p><code>47  return(read_cnt);</code></p>
     <p><code>48 }</code></p>
     <p><code>2-21</code> Внутренняя функция <code>my_read</code> считывает до <code>MAXLINE</code> символов за один вызов и затем возвращает их по одному.</p>
     <p><code>29</code> Единственное изменение самой функции <code>readline</code> заключается в том, что теперь она вызывает функцию <code>my_read</code> вместо <code>read</code>.</p>
     <p><code>42-48</code> Новая функция <code>readlinebuf</code> выдает сведения о состоянии внутреннего буфера, что позволяет вызывающим функциям проверить, нет ли в нем других данных, помимо уже принятой строки.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>К сожалению, использование переменных типа static в коде readline.c для поддержки информации о состоянии при последовательных вызовах приводит к тому, что функция больше не является безопасной в многопоточной системе (thread-safe) и повторно входимой (reentrant). Мы обсуждаем это в разделах 11.18 и 26.5. Мы предлагаем версию, безопасную в многопоточной системе, основанную на собственных данных программных потоков, в листинге 26.5.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>3.10. Резюме</p>
     </title>
     <p>Структуры адресов сокетов являются неотъемлемой частью каждой сетевой программы. Мы выделяем для них место в памяти, заполняем их и передаем указатели на них различным функциям сокетов. Иногда мы передаем указатель на одну из этих структур функции сокета, и она сама заполняет поля структуры. Мы всегда передаем эти структуры по ссылке (то есть передаем указатель на структуру, а не саму структуру) и всегда передаем размер структуры в качестве дополнительного аргумента. Когда функция сокета заполняет структуру, длина также передается по ссылке, и ее значение может быть изменено функцией, поэтому мы называем такой аргумент «значение-результат» (value-result).</p>
     <p>Структуры адресов сокетов являются самоопределяющимися, поскольку они всегда начинаются с поля <code>family</code>, которое идентифицирует семейство адресов, содержащихся в структуре. Более новые реализации, поддерживающие структуры адресов сокетов переменной длины, также содержат поле, которое определяет длину всей структуры.</p>
     <p>Две функции, преобразующие IP-адрес из формата представления (который мы записываем в виде последовательности символов ASCII) в численный формат (который входит в структуру адреса сокета) и обратно, называются <code>inet_pton</code> и <code>inet_ntop</code>. Эти функции являются зависящими от протокола. Более совершенной методикой является работа со структурами адресов сокетов как с непрозрачными (opaque) объектами, когда известны лишь указатель на структуру и ее размер. Мы разработали набор функций <code>sock_</code>, которые помогут сделать наши программы не зависящими от протокола. Создание наших не зависящих от протокола средств мы завершим в главе 11 функциями <code>getaddrinfo</code> и <code>getnameinfo</code>.</p>
     <p>Сокеты TCP предоставляют приложению поток байтов, лишенный маркеров записей. Возвращаемое значение функции read может быть меньше запрашиваемого, но это не обязательно является ошибкой. Чтобы упростить считывание и запись потока байтов, мы разработали три функции <code>readn</code>, <code>writen</code> и <code>readline</code>, которые и используем в книге. Однако сетевые программы должны быть написаны в расчете на работу с буферами, а не со строками.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Почему аргументы типа «значение-результат», такие как длина структуры адреса сокета, должны передаваться по ссылке?</p>
     <p>2. Почему и функция <code>readn</code>, и функция <code>writen</code> копируют указатель <code>void*</code> в указатель <code>char*</code>?</p>
     <p>3. Функции <code>inet_aton</code> и <code>inet_addr</code> характеризуются традиционно нестрогим отношением к тому, что они принимают в качестве точечно-десятичной записи адреса IPv4: допускаются от одного до четырех десятичных чисел, разделенных точками; также допускается задавать шестнадцатеричное число с помощью начального <code>0x</code> или восьмеричное число с помощью начального 0 (выполните команду <code>telnet 0xe</code>, чтобы увидеть поведение этих функций). Функция <code>inet_pton</code> намного более строга в отношении адреса IPv4 и требует наличия именно четырех чисел, разделенных точками, каждое из которых является десятичным числом от 0 до 255. Функция <code>inet_pton</code> не разрешает задавать точечно- десятичный формат записи адреса, если семейство адресов — <code>AF_INET6</code>, хотя существует мнение, что это можно было бы разрешить, и тогда возвращаемое значение было бы адресом IPv4, преобразованным к виду IPv6 (см. рис. А.6). Напишите новую функцию <code>inet_pton_loose</code>, реализующую такой сценарий: если используется семейство адресов <code>AF_INET</code> и функция <code>inet_pton</code> возвращает нуль, вызовите функцию <code>inet_aton</code> и посмотрите, успешно ли она выполнится. Аналогично, если используется семейство адресов <code>AF_INET6</code> и функция <code>inet_pton</code> возвращает нуль, вызовите функцию <code>inet_aton</code>, и если она выполнится успешно, возвратите адрес IPv4, преобразованный к виду IPv6.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 4</p>
     <p>Элементарные сокеты TCP</p>
    </title>
    <section>
     <title>
      <p>4.1. Введение</p>
     </title>
     <p>В этой главе описываются элементарные функции сокетов, необходимые для написания полностью работоспособного клиента и сервера TCP. Сначала мы опишем все элементарные функции сокетов, которые будем использовать, а затем в следующей главе создадим клиент и сервер. С этими приложениями мы будем работать на протяжении всей книги, постоянно их совершенствуя (см. табл. 1.3 и 1.4).</p>
     <p>Мы также опишем параллельные (concurrent) серверы — типичную технологию Unix для обеспечения параллельной обработки множества клиентов одним сервером. Подключение очередного клиента заставляет сервер выполнить функцию <code>fork</code>, порождающую новый серверный процесс для обслуживания этого клиента. Здесь применительно к использованию функции <code>fork</code> мы будем рассматривать модель «каждому клиенту — один <emphasis>процесс</emphasis>», а в главе 26 при обсуждении программных потоков расскажем о модели «каждому клиенту — один <emphasis>поток</emphasis>».</p>
     <p>На рис. 4.1 представлен типичный сценарий взаимодействия, происходящего между клиентом и сервером. Сначала запускается сервер, затем, спустя некоторое время, запускается клиент, который соединяется с сервером. Предполагается, что клиент посылает серверу запрос, сервер этот запрос обрабатывает и посылает клиенту ответ. Так продолжается, пока клиентская сторона не закроет соединение, посылая при этом серверу признак конца файла. Затем сервер закрывает свой конец соединения и либо завершает работу, либо ждет подключения нового клиента.</p>
     <image l:href="#img_30.png"/>
     <p><strong>Рис. 4.1</strong>. Функции сокетов для элементарного клиент-серверного соединения TCP</p>
    </section>
    <section>
     <title>
      <p>4.2. Функция socket</p>
     </title>
     <section>
      <p>Чтобы обеспечить сетевой ввод-вывод, процесс должен начать работу с вызова функции <code>socket</code>, задав тип желаемого протокола (TCP с использованием IPv4, UDP с использованием IPv6, доменный сокет Unix и т.д.).</p>
      <p><code>#include &lt;sys/socket.h&gt;</code></p>
      <empty-line/>
      <p><code>int socket(int <emphasis>family</emphasis>, int <emphasis>type</emphasis>, int <emphasis>protocol</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: неотрицательный дескриптор, если функция выполнена успешно, -1 в случае ошибки</emphasis></code></p>
      <p>Константа <code>family</code> задает семейство протоколов. Ее возможные значения приведены в табл. 4.1. Часто этот параметр функции <code>socket</code> называют «областью» или «доменом» (<emphasis>domain</emphasis>), а не семейством. Значения константы <code>type</code> (тип) перечислены в табл. 4.2. Аргумент <code>protocol</code> должен быть установлен в соответствии с используемым протоколом (табл. 4.3) или должен быть равен нулю для выбора протокола, по умолчанию соответствующего заданному семейству и типу.</p>
      <empty-line/>
      <p><strong>Таблица 4.1</strong>. Константы протокола (family) для функции socket</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Семейство сокетов (family)</th>
        <th align="left" valign="top">Описание</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">AF_INET</td>
        <td align="left" valign="top">Протоколы IPv4</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">AF_INET6</td>
        <td align="left" valign="top">Протоколы IPv6</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">AF_LOCAL</td>
        <td align="left" valign="top">Протоколы доменных сокетов Unix (см. главу 14)</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">AF_ROUTE</td>
        <td align="left" valign="top">Маршрутизирующие сокеты (см. главу 17)</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">AF_KEY</td>
        <td align="left" valign="top">Сокет управления ключами</td>
       </tr>
      </table>
      <empty-line/>
      <p><strong>Таблица 4.2</strong>. Тип сокета для функции socket</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Тип (type)</th>
        <th align="left" valign="top">Описание</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SOCK STREAM</td>
        <td align="left" valign="top">Потоковый сокет</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SOCK_DGRAM</td>
        <td align="left" valign="top">Сокет дейтаграмм</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SOCK_SEQPACKET</td>
        <td align="left" valign="top">Сокет последовательных пакетов</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SOCK_RAW</td>
        <td align="left" valign="top">Символьный (неструктурированный) сокет</td>
       </tr>
      </table>
      <empty-line/>
      <p><strong>Таблица 4.3</strong>. Возможные значения параметра protocol</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Protocol</th>
        <th align="left" valign="top">Значение</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">IPPROTO_TCP</td>
        <td align="left" valign="top">Транспортный протокол TCP</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">IPPROTO_UDP</td>
        <td align="left" valign="top">Транспортный протокол UDP</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">IPPROTO_SCTP</td>
        <td align="left" valign="top">Транспортный протокол SCTP</td>
       </tr>
      </table>
      <p>Не все сочетания констант <code>family</code> и <code>type</code> допустимы. В табл. 4.4 показаны допустимые сочетания, а также протокол, соответствующий каждой паре. Клетки таблицы, содержащие «Да», соответствуют допустимым комбинациям, для которых нет удобных сокращений. Пустая клетка означает, что данное сочетание не поддерживается.</p>
      <empty-line/>
      <p><strong>Таблица 4.4</strong>. Сочетания констант family и type для функции socket</p>
      <table>
       <tr align="left">
        <th align="left" valign="top"/>
        <th align="left" valign="top">AF_INET</th>
        <th align="left" valign="top">AF_INET6</th>
        <th align="left" valign="top">AF_LOCAL</th>
        <th align="left" valign="top">AF_ROUTE</th>
        <th align="left" valign="top">AF_KEY</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SOCK_STREAM</td>
        <td align="left" valign="top">TCP/SCTP</td>
        <td align="left" valign="top">TCP/SCTP</td>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SOCK_DGRAM</td>
        <td align="left" valign="top">UDP</td>
        <td align="left" valign="top">UDP</td>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SOCK_SEQPACKET</td>
        <td align="left" valign="top">SCTP</td>
        <td align="left" valign="top">SCTP</td>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SOCK RAW</td>
        <td align="left" valign="top">IPv4</td>
        <td align="left" valign="top">IPv6</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top">Да</td>
       </tr>
      </table>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В качестве первого аргумента функции socket вы также можете встретить константу PF_xxx. Подробнее об этом мы расскажем в конце данного раздела.</p>
       <p>Кроме того, вам может встретиться название AF_UNIX (исторически сложившееся в Unix) вместо AF_LOCAL (название из POSIX), и более подробно мы поговорим об этом в главе 14.</p>
       <p>Для аргументов family и type существуют и другие значения. Например, 4.4BSD поддерживает и AF_NS (протоколы Xerox NS, часто называемые XNS), и AF_ISO (протоколы OSI). Но сегодня очень немногие используют какой-либо из этих протоколов. Аналогично, значение type для SOCK_SEQPACKET, сокета последовательных пакетов, реализуется и протоколами Xerox NS, и протоколами OSI. Но протокол TCP является потоковым и поддерживает только сокеты SOCK_STREAM.</p>
       <p>Linux поддерживает новый тип сокетов, SOCK_PACKET, предоставляющий доступ к канальному уровню, аналогично BPF и DLPI на рис. 2.1. Об этом более подробно рассказывается в главе 29.</p>
       <p>Сокет управления ключами AF_KEY является новшеством. Аналогично тому, как маршрутизирующий сокет (AF_ROUTE) является интерфейсом к таблице маршрутизации ядра, сокет управления ключами — это интерфейс к таблице ключей ядра. Подробнее об этом рассказывается в главе 19.</p>
      </cite>
      <p>При успешном выполнении функция <code>socket</code> возвращает неотрицательное целое число, аналогичное дескриптору файла. Мы называем это число <emphasis>дескриптором сокета</emphasis> (<emphasis>socket descriptor</emphasis>), или <code>sockfd</code>. Чтобы получить дескриптор сокета, достаточно указать лишь семейство протоколов (IPv4, IPv6 или Unix) и тип сокета (потоковый, символьный или дейтаграммный). Мы еще не задали ни локальный адрес протокола, ни удаленный адрес протокола.</p>
     </section>
     <section>
      <title>
       <p>AF_xxx и PF_xxx</p>
      </title>
      <p>Префикс <code>AF_</code> обозначает <emphasis>семейство адресов</emphasis> (<emphasis>address family</emphasis>), a <code>PF_</code> — <emphasis>семейство протоколов</emphasis> (<emphasis>protocol family</emphasis>). Исторически ставилась такая цель, чтобы отдельно взятое семейство протоколов могло поддерживать множество семейств адресов и значение <code>PF_</code> использовалось для создания сокета, а значение <code>AF_</code> — в структурах адресов сокетов. Но в действительности семейства протоколов, поддерживающего множество семейств адресов, никогда не существовало, и поэтому в заголовочном файле <code>&lt;sys/socket.h&gt;</code> значение <code>PF_</code> для протокола задается равным значению <code>AF_</code>. Хотя не гарантируется, что это равенство будет всегда справедливо, но при попытке изменить ситуацию для существующих протоколов большая часть написанного кода потеряет работоспособность.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Просмотр 137 программ с вызовами функции socket в реализации BSD/OS 2.1 показывает, что в 143 случаях вызова задается значение AF_, и только в 8 случаях — значение PF_.</p>
       <p>Причина создания аналогичных наборов констант с префиксами AF_ и PF_ восходит к 4.1cBSD [69] и к версии функции socket, предшествующей описываемой нами версии (которая появилась с 4.2BSD). Версия функции socket в 4.1cBSD получала четыре аргумента, одним из которых был указатель на структуру sockproto. Первый элемент этой структуры назывался sp_family, и его значение было одним из значений PF_. Второй элемент, sp_protocol, был номером протокола, аналогично третьему аргументу нынешней функции socket. Единственный способ задать семейство протоколов заключался в том, чтобы задать эту структуру. Следовательно, в этой системе значения PF_ использовались как элементы для задания семейства протоколов в структуре sockproto. Значения AF_ играли роль элементов для задания семейства адресов в структурах адресов сокетов. Структура sockproto еще присутствует в 4.4BSD [128, с. 626-627], но служит только для внутреннего использования ядром. Начальное определение содержало для элемента sp_family комментарий «семейство протоколов», но в исходном коде 4.4BSD он был изменен на «семейство адресов».</p>
       <p>Еще большую путаницу в эту ситуацию вносит то, что в Беркли-реализации структура данных ядра, содержащая значение, которое сравнивается с первым аргументом функции socket (элемент dom_family структуры domain [128, с. 187]), сопровождается комментарием, где сказано, что в этой структуре содержится значение AF_. Но некоторые структуры domain внутри ядра инициализированы с помощью константы AF_ [128, с. 192], в то время как другие — с помощью PF_ [128, с. 646], [112, с. 229].</p>
       <p>Еще одно историческое замечание. Страница руководства по 4.2BSD от июля 1983 года, посвященная функции socket, называет ее первый аргумент af и перечисляет его возможные значения как константы AF_.</p>
       <p>Наконец, отметим, что POSIX задает первый аргумент функции socket как значение PF_, а значение AF_ использует для структуры адреса сокета. Но далее в структуре addrinfo определяется только одно значение семейства (см. раздел 11.2), предназначенное для использования либо в вызове функции socket, либо в структуре адреса сокета!</p>
      </cite>
      <p>В целях согласования с существующей практикой программирования мы используем в тексте только константы <code>AF_</code>, хотя вы можете встретить и значение <code>PF_</code>, в основном в вызовах функции <code>socket</code>.</p>
     </section>
    </section>
    <section>
     <title>
      <p>4.3. Функция connect</p>
     </title>
     <p>Функция <code>connect</code> используется клиентом TCP для установления соединения с сервером TCP.</p>
     <p><code>#include &lt;sys/socket.h&gt;</code></p>
     <empty-line/>
     <p><code>int connect(int <emphasis>sockfd</emphasis>, const struct sockaddr *<emphasis>servaddr</emphasis>,</code></p>
     <p><code> socklen_t <emphasis>addrlen</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного выполнения функции, -1 в случае ошибки</emphasis></code></p>
     <p>Аргумент <code>sockfd</code> — это дескриптор сокета, возвращенный функцией <code>socket</code>. Второй и третий аргументы — это указатель на структуру адреса сокета и ее размер (см. раздел 3.3). Структура адреса сокета должна содержать IP-адрес и номер порта сервера. Пример применения этой функции был представлен в листинге 1.1.</p>
     <p>Клиенту нет необходимости вызывать функцию <code>bind</code> (которую мы описываем в следующем разделе) до вызова функции <code>connect</code>: при необходимости ядро само выберет и динамически назначаемый порт, и IP-адрес отправителя.</p>
     <p>В случае сокета TCP функция <code>connect</code> инициирует трехэтапное рукопожатие TCP (см. раздел 2.6). Функция возвращает значение, только если установлено соединение или произошла ошибка. Возможно несколько ошибок:</p>
     <p>1. Если клиент TCP не получает ответа на свой сегмент SYN, возвращается сообщение <code>ETIMEDOUT</code>. 4.4BSD, например, отправляет один сегмент SYN, когда вызывается функция <code>connect</code>, второй — 6 с спустя, и еще один — через 24 с [128, с. 828]. Если ответ не получен в течение 75 с, возвращается ошибка.</p>
     <p>Некоторые системы позволяют администратору устанавливать значение времени ожидания; см. приложение Е [111].</p>
     <p>2. Если на сегмент SYN сервер отвечает сегментом RST, это означает, что ни один процесс на узле сервера не находится в ожидании подключения к указанному нами порту (например, нужный процесс может быть не запущен). Это <emphasis>устойчивая неисправность</emphasis> (<emphasis>hard error</emphasis>), и клиенту возвращается сообщение <code>ECONNREFUSED</code> сразу же по получении им сегмента RST.</p>
     <p>RST (от «reset» — сброс) — это сегмент TCP, отправляемый собеседнику при возникновении ошибок. Вот три условия, при которых генерируется RST: сегмент SYN приходит для порта, не имеющего прослушивающего сервера (что мы только что описали); TCP хочет разорвать существующее соединение; TCP получает сегмент для несуществующего соединения (дополнительная информация содержится на с. 246–250 [111]).</p>
     <p>3. Если сегмент SYN клиента приводит к получению сообщения ICMP о недоступности получателя от какого-либо промежуточного маршрутизатора, это считается <emphasis>случайным сбоем</emphasis> (<emphasis>soft error</emphasis>). Клиентское ядро сохраняет сообщение об ошибке, но продолжает отправлять сегменты SYN с теми же временными интервалами, что и в первом сценарии. Если же но истечении определенного фиксированного времени (75 с для 4.4BSD) ответ не получен, сохраненная ошибка ICMP возвращается процессу либо как <code>EHOSTUNREACH</code>, либо как <code>ENETUNREACH</code>. Может случиться, что удаленная система будет недоступна по любому маршруту из таблицы маршрутизации локального узла, или что возврат из <code>connect</code> произойдет без всякого ожидания.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Многие более ранние системы, такие как 4.2BSD, некорректно прерывали попытки установления соединения при получении сообщения ICMP о недоступности получателя. Это было неверно, поскольку данная ошибка ICMP может указывать на временную неисправность. Например, может быть так, что эта ошибка вызвана проблемой маршрутизации, которая исправляется в течение 15 с.</p>
      <p>Обратите внимание, что мы не включили ENETUNREACH в табл. А.5 несмотря на то, что сеть получателя действительно может быть недоступна. Недоступность сети считается устаревшей ошибкой, и даже если 4.4BSD получает такое сообщение, приложению возвращается EHOSTUNREACH.</p>
     </cite>
     <p>Эти ошибки мы можем наблюдать на примере нашего простого клиента, созданного в листинге 1.1. Сначала мы указываем адрес нашего собственного узла (127.0.0.1), на котором работает сервер времени и даты, и видим обычный вывод:</p>
     <p><code>solaris % <strong>daytimetcpcli 127.0.0.1</strong></code></p>
     <p><code>Sun Jul 27 22:01:51 2003</code></p>
     <p>Укажем IP-адрес другого компьютера (HP-UX):</p>
     <p><code>solaris % <strong>daytimecpcli 192.6.38.100</strong></code></p>
     <p><code>Sun Jul 27 22:04:59 PDT 2003</code></p>
     <p>Затем мы задаем IP-адрес в локальной подсети (192.168.1/24) с несуществующим адресом узла (100). Когда клиент посылает запросы ARP (запрашивая аппаратный адрес узла), он не получает никакого ответа:</p>
     <p><code>solaris % <strong>daytimetcpcli 192.168.1.100</strong></code></p>
     <p><code>connect error: Connection timed out</code></p>
     <p>Мы получаем сообщение об ошибке только по истечении времени выполнения функции <code>connect</code> (которое, как мы говорили, для Solaris 9 составляет 3 мин). Обратите внимание, что наша функция <code>err_sys</code> выдает текстовое сообщение, соответствующее коду ошибки <code>ETIMEDOUT</code>.</p>
     <p>В следующем примере мы пытаемся обратиться к локальному маршрутизатору, на котором не запущен сервер времени и даты:</p>
     <p><code>solaris % <strong>daytimetcpcli 192.168.1.5</strong></code></p>
     <p><code>connect error: Connection refused</code></p>
     <p>Сервер отвечает немедленно, отправляя сегмент RST.</p>
     <p>В последнем примере мы пытаемся обратиться к недоступному адресу из сети Интернет. Просмотрев пакеты с помощью программы <code>tcpdump</code>, мы увидим, что маршрутизатор, находящийся на расстоянии шести прыжков от нас, возвращает сообщение ICMP о недоступности узла:</p>
     <p><code>solaris % <strong>daytimetcpcli 192.3.4.5</strong></code></p>
     <p><code>connect error: No route to host</code></p>
     <p>Как и в случае ошибки <code>ETIMEDOUT</code>, в этом примере функция <code>connect</code> возвращает ошибку <code>EHOSTUNREACH</code> только после ожидания в течение определенного времени.</p>
     <p>В терминах диаграммы перехода состояний TCP (см. рис. 2.4) функция <code>connect</code> переходит из состояния <code>CLOSED</code> (состояния, в котором сокет начинает работать при создании с помощью функции <code>socket</code>) в состояние <code>SYN_SENT</code>, а затем, при успешном выполнении, в состояние <code>ESTABLISHED</code>. Если выполнение функции <code>connect</code> окажется неудачным, сокет больше не используется и должен быть закрыт. Мы не можем снова вызвать функцию <code>connect</code> для сокета. В листинге 11.4 вы увидите, что если функция <code>connect</code> выполняется в цикле, проверяя каждый IP-адрес данного узла, пока он не заработает, то каждый раз, когда выполнение функции оказывается неудачным, мы должны закрыть дескриптор сокета с помощью функции <code>close</code> и снова вызвать функцию <code>socket</code>.</p>
    </section>
    <section>
     <title>
      <p>4.4. Функция bind</p>
     </title>
     <p>Функция <code>bind</code> связывает сокет с локальным адресом протокола. В случае протоколов Интернета адрес протокола — это комбинация 32-разрядного адреса IPv4 или 128-разрядного адреса IPv6 с 16-разрядным номером порта TCP или UDP.</p>
     <p><code>#include &lt;sys/socket.h&gt;</code></p>
     <empty-line/>
     <p><code>int bind(int <emphasis>sockfd</emphasis>, const struct sockaddr *<emphasis>myaddr</emphasis>, socklen_t <emphasis>addrlen</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В руководстве при описании функции bind говорилось: «функция bind присваивает имя неименованному сокету». Использование термина «имя» спорно, обычно оно вызывает ассоциацию с доменными именами (см. главу 11), такими как foo.bar.com. Функция bind не имеет ничего общего с именами. Она задает сокету адрес протокола, а что означает этот адрес — зависит от самого протокола.</p>
     </cite>
     <p>Вторым аргументом является указатель на специфичный для протокола адрес, а третий аргумент — это размер структуры адреса. В случае TCP вызов функции <code>bind</code> позволяет нам задать номер порта или IP-адрес, а также задать оба эти параметра или вообще не указывать ничего.</p>
     <p>&#9632; Серверы связываются со своим заранее известным портом при запуске. Мы видели это в листинге 1.5. Если клиент или сервер TCP не делает этого, ядро выбирает динамически назначаемый порт для сокета либо при вызове функции <code>connect</code>, либо при вызове функции <code>listen</code>. Клиент TCP обычно позволяет ядру выбирать динамически назначаемый порт, если приложение не требует зарезервированного порта (см. рис. 2.10), но сервер TCP достаточно редко предоставляет ядру право выбора, так как обращение к серверам производится через заранее известные порты.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Исключением из этого правила являются серверы удаленного вызова процедур RPC (Remote Procedure Call). Обычно они позволяют ядру выбирать динамически назначаемый порт для их прослушиваемого сокета, поскольку затем этот порт регистрируется программой отображения портов RPC. Клиенты должны соединиться с этой программой, чтобы получить номер динамически назначаемого порта до того, как они смогут соединиться с сервером с помощью функции connect. Это также относится к серверам RPC, использующим протокол UDP.</p>
     </cite>
     <p>&#9632; С помощью функции <code>bind</code> процесс может связать конкретный IP-адрес с сокетом. IP-адрес должен соответствовать одному из интерфейсов узла. Так определяется IP-адрес, который будет использоваться для отправляемых через сокет IP-дейтаграмм. При этом для сервера TCP на сокет накладывается ограничение: он может принимать только такие входящие соединения клиента, которые предназначены именно для этого IP-адреса.</p>
     <p>Обычно клиент TCP не связывает IP-адрес с сокетом при помощи функции <code>bind</code>. Ядро выбирает IP-адрес отправителя в момент подключения клиента к сокету, основываясь на используемом исходящем интерфейсе, который, в свою очередь, зависит от маршрута, требуемого для обращения к серверу [128, с. 737].</p>
     <p>Если сервер TCP не связывает IP-адрес с сокетом, ядро назначает ему IP-адрес (указываемый в исходящих пакетах), который совпадает с адресом получателя сегмента SYN клиента [128, с. 943].</p>
     <p>Как мы уже говорили, вызов функции <code>bind</code> позволяет нам задать IP-адрес и порт (вместе или по отдельности) либо не задавать никаких аргументов. В табл. 4.5 приведены все возможные значения, которые присваиваются аргументам <code>sin_addr</code> и <code>sin_port</code> либо <code>sin6_addr</code> и <code>sin6_port</code> в зависимости от желаемого результата.</p>
     <empty-line/>
     <p><strong>Таблица 4.5</strong>. Результаты задания IP-адреса и (или) номера порта в функции bind</p>
     <table>
      <tr align="left">
       <th colspan="2" align="left" valign="top">Процесс задает</th>
       <th rowspan="2" align="left" valign="top">Результат</th>
      </tr>
      <tr align="left">
       <th align="left" valign="top">IP-адрес</th>
       <th align="left" valign="top">Порт</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Универсальный</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Ядро выбирает IP-адрес и порт</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Универсальный</td>
       <td align="left" valign="top">Ненулевое значение</td>
       <td align="left" valign="top">Ядро выбирает IP-адрес, процесс задает порт</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Локальный</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Процесс задает IP-адрес, ядро выбирает порт</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Локальный</td>
       <td align="left" valign="top">Ненулевое значение</td>
       <td align="left" valign="top">Процесс задает IP-адрес и порт</td>
      </tr>
     </table>
     <p>Если мы зададим нулевой номер порта, то при вызове функции <code>bind</code> ядро выберет динамически назначаемый порт. Но если мы зададим IP-адрес с помощью символов подстановки, ядро не выберет локальный IP-адрес, пока к сокету не присоединится клиент (TCP) либо на сокет не будет отправлена дейтаграмма (UDP).</p>
     <p>В случае IPv4 <emphasis>универсальный</emphasis> адрес, состоящий из символов подстановки (wildcard), задается константой <code>INADDR_ANY</code>, значение которой обычно нулевое. Это указывает ядру на необходимость выбора IP-адреса. Пример вы видели в листинге 1.5:</p>
     <p><code>struct sockaddr_in servaddr;</code></p>
     <p><code>servaddr sin_addr s_addr = htonl(INADDR_ANY); /* универсальный */</code></p>
     <p>Этот прием работает с IPv4, где IP-адрес является 32-разрядным значением, которое можно представить как простую численную константу (в данном случае 0), но воспользоваться им при работе с IPv6 мы не можем, поскольку 128-разрядный адрес IPv6 хранится в структуре. (В языке С мы не можем поместить структуру в правой части оператора присваивания.) Эта проблема решается следующим образом:</p>
     <p><code>struct sockaddr_in6 serv;</code></p>
     <p><code>serv sin6_addr = in6addr_any; /* универсальный */</code></p>
     <p>Система выделяет место в памяти и инициализирует переменную <code>in6addr_any</code>, присваивая ей значение константы <code>IN6ADDR_ANY_INIT</code>. Объявление внешней константы <code>in6addr_any</code> содержится в заголовочном файле <code>&lt;netinet/in.h&gt;</code>.</p>
     <p>Значение <code>INADDR_ANY</code> (0) не зависит от порядка байтов, поэтому использование функции <code>htonl</code> в действительности не требуется. Но поскольку все константы <code>INADDR_</code>, определенные в заголовочном файле <code>&lt;netinet/in.h&gt;</code>, задаются в порядке байтов узла, с любой из этих констант следует использовать функцию <code>htonl</code>.</p>
     <p>Если мы поручаем ядру выбрать для нашего сокета номер динамически назначаемого порта, то функция <code>bind</code> не возвращает выбранное значение. В самом деле, она не может возвратить это значение, поскольку второй аргумент функции <code>bind</code> имеет спецификатор <code>const</code>. Чтобы получить значение динамически назначаемого порта, заданного ядром, потребуется вызвать функцию <code>getsockname</code>, которая возвращает локальный адрес протокола.</p>
     <p>Типичным примером процесса, связывающего с сокетом конкретный IP-адрес, служит узел, на котором работают веб-серверы нескольких организаций (см. раздел 14.2 [112]). Прежде всего, у каждой организации есть свое собственное доменное имя, например <code>www.organization.com</code>. Доменному имени каждой организации сопоставляется некоторый IP-адрес; различным организациям сопоставляются различные адреса, но обычно из одной и той же подсети. Например, если маска подсети 198.69.10, то IP-адресом первой организации может быть 198. 69.10.128, следующей — 198.69.10.129, и т.д. Все эти IP-адреса затем становятся псевдонимами, или альтернативными именами (alias), одного сетевого интерфейса (например, при использовании параметра <code>alias</code> команды <code>ifconfig</code> в 4.4BSD). В результате уровень IP будет принимать входящие дейтаграммы, предназначенные для любого из адресов, являющихся псевдонимами. Наконец, для каждой организации запускается по одной копии сервера HTTP, и каждая копия связывается с помощью функции <code>bind</code> только с IP-адресом определенной организации.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В качестве альтернативы можно запустить одиночный сервер, связанный с универсальным адресом. Когда происходит соединение, сервер вызывает функцию getsockname, чтобы получить от клиента IP-адрес получателя, который (см. наше обсуждение ранее) может быть равен 198.69.10.128,198.69.10.129 и т.д. Затем сервер обрабатывает запрос клиента па основе именно того IP-адреса, к которому было направлено это соединение.</p>
      <p>Одним из преимуществ связывания с конкретным IP-адресом является то, что демультиплексирование данного IP-адреса с процессом сервера выполняется ядром.</p>
      <p>Следует внимательно относиться к различию интерфейса, на который приходит пакет, и IP-адреса получателя этого пакета. В разделе 8.8 мы поговорим о моделях систем с гибкой привязкой (weak end system) и с жесткой привязкой (strong end system). Большинство реализаций используют первую модель, то есть считают обычным явлением принятие пакета на интерфейсе, отличном от указанного в IP-адресе получателя. (При этом подразумевается узел с несколькими сетевыми интерфейсами.) При связывании с сокетом конкретного IP-адреса на этом сокете будут приниматься дейтаграммы с заданным IP-адресом получателя, и только они. Никаких ограничений на принимающий интерфейс не накладывается — эти ограничения возникают только в случае, если используется модель системы с жесткой привязкой.</p>
     </cite>
     <p>Общей ошибкой выполнения функции <code>bind</code> является <code>EADDRINUSE</code>, указывающая на то, что адрес уже используется. Более подробно мы поговорим об этом в разделе 7.5, когда будем рассматривать параметры сокетов <code>SO_REUSEADDR</code> и <code>SO_REUSEPORT</code>.</p>
    </section>
    <section>
     <title>
      <p>4.5. Функция listen</p>
     </title>
     <p>Функция <code>listen</code> вызывается только сервером TCP и выполняет два действия.</p>
     <p>1. Когда сокет создается с помощью функции <code>socket</code>, считается, что это активный сокет, то есть клиентский сокет, который запустит функцию <code>connect</code>. Функция <code>listen</code> преобразует неприсоединенный сокет в пассивный сокет, запросы на подключение к которому начинают приниматься ядром. В терминах диаграммы перехода между состояниями TCP (см. рис. 2.4) вызов функции <code>listen</code> переводит сокет из состояния CLOSED в состояние LISTEN.</p>
     <p>2. Второй аргумент этой функции задает максимальное число соединений, которые ядро может помещать в очередь этого сокета.</p>
     <p><code>#include &lt;sys/socket.h&gt;</code></p>
     <empty-line/>
     <p><code>int listen(int <emphasis>sockfd</emphasis>, int <emphasis>backlog</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Эта функция обычно вызывается после функций <code>socket</code> и <code>bind</code>. Она должна вызываться перед вызовом функции <code>accept</code>.</p>
     <p>Чтобы уяснить смысл аргумента <code>backlog</code>, необходимо понять, что для данного прослушиваемого сокета ядро поддерживает две очереди:</p>
     <p>1. <emphasis>Очередь не полностью установленных соединений</emphasis> (<emphasis>incomplete connection queue</emphasis>), содержащую запись для каждого сегмента SYN, пришедшего от клиента, для которого сервер ждет завершения трехэтапного рукопожатия TCP. Эти сокеты находятся в состоянии SYN_RCVD (см. рис. 2.4).</p>
     <p>2. <emphasis>Очередь полностью установленных соединений</emphasis> (<emphasis>complete connection queue</emphasis>), содержащую запись для каждого клиента, с которым завершилось трехэтапное рукопожатие TCP. Эти сокеты находятся в состоянии ESTABLISHED (см. рис. 2.4).</p>
     <p>На рис. 4.2 представлены обе эти очереди для прослушиваемого сокета.</p>
     <image l:href="#img_31.png"/>
     <p><strong>Рис. 4.2</strong>. Две очереди, поддерживаемые прослушиваемым сокетом TCP</p>
     <p>Когда в очередь не полностью установленных соединений добавляется новая запись, параметры прослушиваемого сокета копируются на создаваемое соединение. Механизм создания соединения полностью автоматизирован, и процесс сервера в нем не участвует. На рис. 4.3 показан обмен пакетами во время установления соединения с использованием этих очередей.</p>
     <image l:href="#img_32.png"/>
     <p><strong>Рис. 4.3</strong>. Обмен пакетами в процессе установления соединения с применением очередей</p>
     <p>Когда от клиента приходит сегмент SYN, TCP создает новую запись в очереди не полностью установленных соединений, а затем отвечает вторым сегментом трехэтапного рукопожатия, посылая сегмент SYN вместе с сегментом ACK, подтверждающим прием клиентского сегмента SYN (см. раздел 2.6). Эта запись останется в очереди не полностью установленных соединений, пока не придет третий сегмент трехэтапного рукопожатия (клиентский сегмент ACK для сегмента сервера SYN) или пока не истечет время жизни этой записи. (В реализациях, происходящих от Беркли, время ожидания (тайм-аут) для элементов очереди не полностью установленных соединений равно 75 с.) Если трехэтапное рукопожатие завершается нормально, запись переходит из очереди не полностью установленных соединений в конец очереди полностью установленных соединений. Когда процесс вызывает функцию <code>accept</code> (о которой мы поговорим в следующем разделе), ему возвращается первая запись из очереди полностью установленных соединений, а если очередь пуста, процесс переходит в состояние ожидания до появления записи в ней.</p>
     <p>Есть несколько важных моментов, которые нужно учитывать при работе с этими очередями.</p>
     <p>&#9632; Аргумент <emphasis>backlog</emphasis> функции <code>listen</code> исторически задавал максимальное суммарное значение для обеих очередей.</p>
     <p>&#9632; Беркли-реализации включают поправочный множитель для аргумента <code>backlog</code>, равный 1,5 [111, с. 257], [128, с. 462]. Например, при типичном значении аргумента <code>backlog</code> = 5 в таких системах допускается до восьми записей в очередях, как показано в табл. 4.6.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Формального определения аргумента backlog никогда не существовало. В руководстве 4.2BSD сказано, что «он определяет максимальную длину, до которой может вырасти очередь не полностью установленных соединений». Многие руководства и даже POSIX копируют это определение дословно, но в нем не говорится, в каком состоянии должно находится соединение — в состоянии SYN_RCVD, ESTABLISHED (до вызова accept), или же в любом из них. Определение, приведенное выше, относится к реализации Беркли 4.2BSD, и копируется многими другими реализациями.</p>
     </cite>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Причина возникновения этого множителя теряется в истории [57]. Но если мы рассматриваем backlog как способ задания максимального числа установленных соединений, которые ядро помещает в очередь прослушиваемого сокета (об этом вскоре будет рассказано), этот множитель нужен для учета не полностью установленных соединений, находящихся в очереди [8].</p>
     </cite>
     <p>&#9632; Не следует задавать нулевое значение аргументу <code>backlog</code>, поскольку различные реализации интерпретируют это по-разному (см. табл. 4.6). Некоторые реализации допускают помещение в очередь одного соединения, в то время как в других вообще невозможно помещать соединения в очередь. Если вы не хотите, чтобы клиенты соединялись с вашим прослушиваемым сокетом, просто закройте прослушиваемый сокет.</p>
     <p>&#9632; Если трехэтапное рукопожатие завершается нормально (то есть без потерянных сегментов и повторных передач), запись остается в очереди не полностью установленных соединений на время одного периода обращения (round-trip time, RTT), какое бы значение ни имел этот параметр для конкретного соединения между клиентом и сервером. В разделе 14.4 [112] показано, что для одного веб-сервера средний период RTT оказался равен 187 мс. (Чтобы редкие большие числа не искажали картину, здесь использована медиана, а не обычное среднее арифметическое по всем клиентам.)</p>
     <p>&#9632; Традиционно в примерах кода всегда используется значение <code>backlog</code>, равное 5, поскольку это было максимальное значение, которое поддерживалось в системе 4.2BSD. Это было актуально в 80-х, когда загруженные серверы могли обрабатывать только несколько сотен соединений в день. Но с ростом Сети (WWW), когда серверы обрабатывают миллионы соединений в день, столь малое число стало абсолютно неприемлемым [112, с. 187–192]. Серверам HTTP необходимо намного большее значение аргумента <code>backlog</code>, и новые ядра должны поддерживать такие значения.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В настоящее время многие системы позволяют администраторам изменять максимальное значение аргумента backlog.</p>
     </cite>
     <p>&#9632; Возникает вопрос: какое значение аргумента <code>backlog</code> должно задавать приложение, если значение 5 часто является неадекватным? На этот вопрос нет простого ответа. Серверы HTTP сейчас задают большее значение, но если заданное значение является в исходном коде константой, то для увеличения константы требуется перекомпиляция сервера. Другой способ — принять некоторое значение по умолчанию и предоставить возможность изменять его с помощью параметра командной строки или переменной окружения. Всегда можно задавать значение больше того, которое поддерживается ядром, так как ядро должно обрезать значение до максимального, не возвращая при этом ошибку [128, с. 456].</p>
     <p>Мы приводим простое решение этой проблемы, изменив нашу функцию-обертку для функции <code>listen</code>. В листинге 4.1<a l:href="#n1" type="note">[1]</a> представлен действующий код. Переменная окружения <code>LISTENQ</code> позволяет переопределить значение по умолчанию.</p>
     <p><strong>Листинг 4.1</strong>. Функция-обертка для функции listen, позволяющая переменной окружения переопределить аргумент backlog</p>
     <p><code>//lib/wrapsock.c</code></p>
     <p><code>137 void</code></p>
     <p><code>138 Listen(int fd, int backlog)</code></p>
     <p><code>139 {</code></p>
     <p><code>140  char *ptr;</code></p>
     <empty-line/>
     <p><code>141  /* может заменить второй аргумент на переменную окружения */</code></p>
     <p><code>142  if ((ptr = getenv("LISTENQ")) != NULL)</code></p>
     <p><code>143   backlog = atoi(ptr);</code></p>
     <empty-line/>
     <p><code>144  if (listen(fd, backlog) &lt; 0)</code></p>
     <p><code>145   err_sys("listen error");</code></p>
     <p><code>146 }</code></p>
     <p>&#9632; Традиционно в руководствах и книгах утверждалось, что помещение фиксированного числа соединений в очередь позволяет обрабатывать случай загруженного серверного процесса между последовательными вызовами функции <code>accept</code>. При этом подразумевается, что из двух очередей больше записей будет содержаться, вероятнее всего, в очереди полностью установленных соединений. Но оказалось, что для действительно загруженных веб-серверов это не так. Причина задания большего значения <code>backlog</code> в том, что очередь не полностью установленных соединений растет по мере поступления сегментов SYN от клиентов; элементы очереди находятся в состоянии ожидания завершения трехэтапного рукопожатия.</p>
     <p>&#9632; Если очереди заполнены, когда приходит клиентский сегмент SYN, то TCP игнорирует приходящий сегмент SYN [128, с. 930–931] и не посылает RST. Это происходит потому, что состояние считается временным, и TCP клиента должен еще раз передать свой сегмент SYN, для которого в ближайшее время, вероятно, найдется место в очереди. Если бы TCP сервера послал RST, функция <code>connect</code> клиента сразу же возвратила бы ошибку, заставив приложение обработать это условие, вместо того чтобы позволить TCP выполнить повторную передачу. Кроме того, клиент не может увидеть разницу между сегментами RST в ответе на сегмент SYN, означающими, что на данном порте нет сервера либо на данном порте есть сервер, но его очереди заполнены.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Некоторые реализации отправляют сегмент RST в описанной выше ситуации, что некорректно по изложенным выше причинам. Если вы не пишете клиент специально для работы с подобным сервером, лучше всего игнорировать такую возможность. Ее учет при кодировании клиента снизит его устойчивость и увеличит нагрузку на сеть, если окажется, что порт действительно не прослушивается сервером.</p>
     </cite>
     <p>&#9632; Данные, которые приходят после завершения трехэтапного рукопожатия, но до того, как сервер вызывает функцию <code>accept</code>, должны помещаться в очередь TCP-сервера, пока не будет заполнен приемный буфер.</p>
     <p>В табл. 4.6 показано действительное число установленных в очередь соединений для различных значений аргумента <code>backlog</code> в операционных системах, показанных на рис. 1.7. Семь операционных систем помещены в пять колонок, что иллюстрирует многообразие значений аргумента <code>backlog</code>.</p>
     <empty-line/>
     <p><strong>Таблица 4.6</strong>. Действительное количество соединений в очереди для различных значений аргумента backlog</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">backlog</th>
       <th align="left" valign="top">MacOS 10.2.6 AIX 5.1</th>
       <th align="left" valign="top">Linux 2.4.7</th>
       <th align="left" valign="top">HP-UX 11.11</th>
       <th align="left" valign="top">FreeBSD 4.8 FreeBSD 5.1</th>
       <th align="left" valign="top">Solaris 2.9</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">1</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">2</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">5</td>
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">4</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">5</td>
       <td align="left" valign="top">6</td>
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">5</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">6</td>
       <td align="left" valign="top">5</td>
       <td align="left" valign="top">6</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">5</td>
       <td align="left" valign="top">8</td>
       <td align="left" valign="top">8</td>
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">6</td>
       <td align="left" valign="top">8</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">6</td>
       <td align="left" valign="top">10</td>
       <td align="left" valign="top">9</td>
       <td align="left" valign="top">9</td>
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">10</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">И</td>
       <td align="left" valign="top">10</td>
       <td align="left" valign="top">10</td>
       <td align="left" valign="top">8</td>
       <td align="left" valign="top">11</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">8</td>
       <td align="left" valign="top">13</td>
       <td align="left" valign="top">11</td>
       <td align="left" valign="top">12</td>
       <td align="left" valign="top">9</td>
       <td align="left" valign="top">13</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">9</td>
       <td align="left" valign="top">14</td>
       <td align="left" valign="top">12</td>
       <td align="left" valign="top">13</td>
       <td align="left" valign="top">10</td>
       <td align="left" valign="top">14</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">10</td>
       <td align="left" valign="top">16</td>
       <td align="left" valign="top">13</td>
       <td align="left" valign="top">15</td>
       <td align="left" valign="top">11</td>
       <td align="left" valign="top">16</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">11</td>
       <td align="left" valign="top">17</td>
       <td align="left" valign="top">14</td>
       <td align="left" valign="top">16</td>
       <td align="left" valign="top">12</td>
       <td align="left" valign="top">17</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">12</td>
       <td align="left" valign="top">19</td>
       <td align="left" valign="top">15</td>
       <td align="left" valign="top">18</td>
       <td align="left" valign="top">13</td>
       <td align="left" valign="top">19</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">13</td>
       <td align="left" valign="top">20</td>
       <td align="left" valign="top">16</td>
       <td align="left" valign="top">19</td>
       <td align="left" valign="top">14</td>
       <td align="left" valign="top">20</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">14</td>
       <td align="left" valign="top">22</td>
       <td align="left" valign="top">17</td>
       <td align="left" valign="top">21</td>
       <td align="left" valign="top">15</td>
       <td align="left" valign="top">22</td>
      </tr>
     </table>
     <p>Системы AIX, BSD/ОХ и SunOS реализуют традиционный алгоритм Беркли, хотя последний не допускает значения аргумента <emphasis>backlog</emphasis> больше пяти. В системах HP-UX и Solaris 2.6 используется другой поправочный множитель к аргументу <code>backlog</code>. Системы Digital Unix, Linux и UnixWare воспринимают этот аргумент буквально, то есть не используют поправочный множитель, а в Solaris 2.5.1 к аргументу <code>backlog</code> просто добавляется единица.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Программа для измерения этих значений представлена в решении упражнения 15.4.</p>
      <p>Как мы отмечали, традиционно аргумент backlog задавал максимальное значение для суммы обеих очередей. В 1996 году была предпринята новая атака через Интернет, названная SYN flooding (лавинная адресация сегмента SYN). Написанная хакером программа отправляет жертве сегменты SYN с высокой частотой, заполняя очередь не полностью установленных соединений для одного или нескольких портов TCP. (Хакером мы называем атакующего, как сказано в предисловии к [20].) Кроме того, IP-адрес отправителя каждого сегмента SYN задается случайным числом — формируются вымышленные IP-адреса (IP spoofing), что ведет к получению доступа обманным путем. Таким образом, сегмент сервера SYN/ACK уходит в никуда. Это не позволяет серверу узнать реальный IP-адрес хакера. Очередь не полностью установленных соединений заполняется ложными сегментами SYN, в результате чего для подлинных сегментов SYN в ней не хватает места — происходит отказ в обслуживании (denial of service) нормальных клиентов. Существует два типичных способа противостояния этим атакам [8]. Но самое интересное в этом примечании — это еще одно обращение к вопросу о том, что на самом деле означает аргумент backlog функции listen. Он должен задавать максимальное число установленных соединений для данного сокета, которые ядро помещает в очередь. Ограничение количества установленных соединений имеет целью приостановить получение ядром новых запросов на соединение для данного сокета, когда их не принимает приложение (по любой причине). Если система реализует именно такую интерпретацию, как, например, BSD/OS 3.0, то приложению не нужно задавать большие значения аргумента backlog только потому, что сервер обрабатывает множество клиентских запросов (например, занятый веб-сервер), или для защиты от «наводнения» SYN (лавинной адресации сегмента SYN). Ядро обрабатывает множество не полностью установленных соединений вне зависимости от того, являются ли они законными или приходят от хакера. Но даже в такой интерпретации мы видим (см. табл. 4.6), что значения 5 тут явно недостаточно.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>4.6. Функция accept</p>
     </title>
     <section>
      <p>Функция <code>accept</code> вызывается сервером TCP для возвращения следующего установленного соединения из начала очереди полностью установленных соединений (см. рис. 4.2). Если очередь полностью установленных соединений пуста, процесс переходит в состояние ожидания (по умолчанию предполагается блокируемый сокет).</p>
      <p><code>#include &lt;sys/socket.h&gt;</code></p>
      <empty-line/>
      <p><code>int accept(int <emphasis>sockfd</emphasis>, struct sockaddr *<emphasis>cliaddr</emphasis>, socklen_t *<emphasis>addrlen</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: неотрицательный дескриптор в случае успешного выполнения функции, -1 в случае ошибки</emphasis></code></p>
      <p>Аргументы <code>cliaddr</code> и <code>addrlen</code> используются для возвращения адреса протокола подключившегося процесса (клиента). Аргумент <code>addrlen</code> — это аргумент типа «значение-результат» (см. раздел 3.3). Перед вызовом мы присваиваем целому числу, на которое указывает <code>*addrlen</code>, размер структуры адреса сокета, на которую указывает аргумент <code>cliaddr</code>, и по завершении функции это целое число содержит действительное число байтов, помещенных ядром в структуру адреса сокета.</p>
      <p>Если выполнение функции <code>accept</code> прошло успешно, она возвращает новый дескриптор, автоматически созданный ядром. Этот дескриптор используется для обращения к соединению TCP с конкретным клиентом. При описании функции <code>accept</code> мы называем ее первый аргумент <emphasis>прослушиваемым сокетом</emphasis> (<emphasis>listening socket</emphasis>) (дескриптор, созданный функцией <code>socket</code> и затем используемый в качестве аргумента для функций <code>bind</code> и <code>listen</code>), а значение, возвращаемое этой функцией, мы называем <emphasis>присоединенным сокетом</emphasis> (<emphasis>connected socket</emphasis>). Сервер обычно создает только один прослушиваемый сокет, который существует в течение всего времени жизни сервера. Затем ядро создает по одному присоединенному сокету для каждого клиентского соединения, принятого с помощью функции <code>accept</code> (для которого завершено трехэтапное рукопожатие TCP). Когда сервер заканчивает предоставление сервиса данному клиенту, сокет закрывается.</p>
      <p>Эта функция возвращает до трех значений: целое число, которое является либо дескриптором сокета, либо кодом ошибки, а также адрес протокола клиентского процесса (через указатель <code>cliaddr</code>) и размер адреса (через указатель <code>addrlen</code>). Если нам не нужно, чтобы был возвращен адрес протокола клиента, следует сделать указатели <code>cliaddr</code> и <code>addrlen</code> пустыми указателями.</p>
      <p>В листинге 1.5 показаны эти моменты. Присоединенный сокет закрывается при каждом прохождении цикла, но прослушиваемый сокет остается открытым в течение времени жизни сервера. Мы также видим, что второй и третий аргументы функции <code>accept</code> являются пустыми указателями, поскольку нам не нужно идентифицировать клиент.</p>
     </section>
     <section>
      <title>
       <p>Пример: аргументы типа «значение-результат»</p>
      </title>
      <p>В листинге 4.2 представлен измененный код из листинга 1.5 (вывод IP-адреса и номера порта клиента), обрабатывающий аргумент типа «значение-результат» функции accept.</p>
      <p><strong>Листинг 4.2</strong>. Сервер определения времени и даты, сообщающий IP-адрес и номер порта клиента</p>
      <p><code>//intro/daytimetcpsrv1.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;time.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 main(int argc, char **argv)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int listenfd, connfd;</code></p>
      <p><code> 7  socklen_t len;</code></p>
      <p><code> 8  struct sockaddr_in servaddr, cliaddr;</code></p>
      <p><code> 9  char buff[MAXLINE];</code></p>
      <p><code>10  time_t ticks;</code></p>
      <p><code>11  listenfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
      <p><code>12  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
      <p><code>13  servaddr.sin_family = AF_INET;</code></p>
      <p><code>14  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
      <p><code>15  servaddr.sin_port = htons(13); /* сервер времени и даты */</code></p>
      <p><code>16  Bind(listenfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
      <p><code>17  Listen(listenfd, LISTENQ);</code></p>
      <empty-line/>
      <p><code>18  for (;;) {</code></p>
      <p><code>19   len = sizeof(cliaddr);</code></p>
      <p><code>20   connfd = Accept(listenfd, (SA*)&amp;cliaddr, &amp;len);</code></p>
      <p><code>21   printf("connection from %s, port %d\n",</code></p>
      <p><code>22    Inet_ntop(AF_INET, &amp;cliaddr.sin_addr, buff, sizeof(buff));</code></p>
      <p><code>23   ntohs(cliaddr.sin_port));</code></p>
      <empty-line/>
      <p><code>24   ticks = time(NULL);</code></p>
      <p><code>25   snprintf(buff, sizeof(buff), "% 24s\r\n", ctime(&amp;ticks));</code></p>
      <p><code>26   Write(connfd, buff, strlen(buff));</code></p>
      <empty-line/>
      <p><code>27   Close(connfd);</code></p>
      <p><code>28  }</code></p>
      <p><code>29 }</code></p>
      <subtitle>Новые объявления</subtitle>
      <p><code>7-8</code> Мы определяем две новых переменных: <code>len</code>, которая будет переменной типа «значение-результат», и <code>cliaddr</code>, которая будет содержать адрес протокола клиента.</p>
      <subtitle>Принятие соединения и вывод адреса клиента</subtitle>
      <p><code>19-23</code> Мы инициализируем переменную <code>len</code>, присвоив ей значение, равное размеру структуры адреса сокета, и передаем указатель на структуру <code>cliaddr</code> и указатель на <code>len</code> в качестве второго и третьего аргументов функции <code>accept</code>. Мы вызываем функцию <code>inet_ntop</code> (см. раздел 3.7) для преобразования 32-битового IP-адреса в структуре адреса сокета в строку ASCII (точечно-десятичную запись), а затем вызываем функцию <code>ntohs</code> (см. раздел 3.4) для преобразования сетевого порядка байтов в 16-битовом номере порта в порядок байтов узла.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>При вызове функции sock_ntop вместо inet_ntop наш сервер станет меньше зависеть от протокола, однако он все равно зависит от IPv4. Мы покажем версию этого сервера, не зависящего от протокола, в листинге 11.7.</p>
      </cite>
      <p>Если мы запустим наш новый сервер, а затем запустим клиент на том же узле, то дважды соединившись с сервером, мы получим от клиента следующий вывод:</p>
      <p><code>solaris % <strong>daytimetcpcli 127.0.0.1</strong></code></p>
      <p><code>Thu Sep 11 12:44:00 2003</code></p>
      <p><code>solaris % <strong>daytimetcpcli 192.168.1.20</strong></code></p>
      <p><code>Thu Sep 11 12:44:09 2003</code></p>
      <p>Сначала мы задаем IP-адрес сервера как адрес закольцовки на себя (loopback address) (127.0.0.1), а затем как его собственный IP-адрес (192.168.1.20). Вот соответствующий вывод сервера:</p>
      <p><code>solaris # <strong>daytimetcpsrv1</strong></code></p>
      <p><code>connection from 127.0.0.1, port 43388</code></p>
      <p><code>connection from 192.168.1.20, port 43389</code></p>
      <p>Обратите внимание на то, что происходит с IP-адресом клиента. Поскольку наш клиент времени и даты (см. листинг 1.1) не вызывает функцию <code>bind</code>, как сказано в разделе 4.4, ядро выбирает IP-адрес отправителя, основанный на используемом исходящем интерфейсе. В первом случае ядро задает IP-адрес равным адресу закольцовки, во втором случае — равным IP-адресу интерфейса Ethernet. Кроме того, мы видим, что динамически назначаемый порт, выбранный ядром Solaris, — это 33 188, а затем 33 189 (см. рис. 2.10).</p>
      <p>Наконец, заметьте, что приглашение интерпретатора команд изменилось на знак <code>#</code> — это приглашение к вводу команды для привилегированного пользователя. Наш сервер должен обладать правами привилегированного пользователя, чтобы с помощью функции <code>bind</code> связать зарезервированный порт 13. Если у нас нет прав привилегированного пользователя, вызов функции <code>bind</code> оказывается неудачным:</p>
      <p><code>solaris % <strong>daytimetcpsrv1</strong></code></p>
      <p><code>bind error: Permission denied</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>4.7. Функции fork и exec</p>
     </title>
     <p>Прежде чем рассматривать создание параллельного сервера (что мы сделаем в следующем разделе), необходимо описать функцию Unix <code>fork</code>. Эта функция является единственным способом создания нового процесса в Unix.</p>
     <p><code>#include &lt;unistd.h&gt;</code></p>
     <empty-line/>
     <p><code>pid_t fork(void);</code></p>
     <p><code><emphasis>Возвращает: 0 в дочернем процессе, идентификатор дочернего процесса в родительском процессе, -1 в случае ошибки</emphasis></code></p>
     <p>Если вы никогда не встречались с этой функцией, трудным для понимания может оказаться то, что она вызывается <emphasis>один раз</emphasis>, а возвращает <emphasis>два значения</emphasis>. Одно значение эта функция возвращает в вызывающем процессе (который называется родительским процессом) — этим значением является идентификатор созданного процесса (который называется дочерним процессом). Второе значение (нуль) она возвращает в дочернем процессе. Следовательно, по возвращаемому значению можно определить, является ли данный процесс родительским или дочерним.</p>
     <p>Причина того, что функция <code>fork</code> возвращает в дочернем процессе нуль, а не идентификатор родительского процесса, заключается в том, что у дочернего процесса есть только один родитель, и дочерний процесс всегда может получить идентификатор родительского, вызвав функцию <code>getppid</code>. У родителя же может быть любое количество дочерних процессов, и способа получить их идентификаторы не существует. Если родительскому процессу требуется отслеживать идентификаторы своих дочерних процессов, он должен записывать возвращаемые значения функции <code>fork</code>.</p>
     <p>Все дескрипторы, открытые в родительском процессе перед вызовом функции <code>fork</code>, становятся доступными дочерним процессам. Вы увидите, как это свойство используется сетевыми серверами: родительский процесс вызывает функцию <code>accept</code>, а затем функцию <code>fork</code>. Затем присоединенный сокет совместно используется родительским и дочерним процессами. Обычно дочерний процесс использует присоединенный сокет для чтения и записи, а родительский процесс только закрывает присоединенный сокет.</p>
     <p>Существует два типичных случая применения функции <code>fork</code>:</p>
     <p>1. Процесс создает свои копии таким образом, что каждая из них может обрабатывать одно задание. Это типичная ситуация для сетевых серверов. Далее в тексте вы увидите множество подобных примеров.</p>
     <p>2. Процесс хочет запустить другую программу. Поскольку единственный способ создать новый процесс — это вызвать функцию <code>fork</code>, процесс сначала вызывает функцию <code>fork</code>, чтобы создать свою копию, а затем одна из копий (обычно дочерний процесс) вызывает функцию <code>exec</code> (ее описание следует за описанием функции <code>fork</code>), чтобы заменить себя новой программой. Этот сценарий типичен для таких программ, как интерпретаторы командной строки.</p>
     <p>Единственный способ запустить в Unix на выполнение какой-либо файл — вызвать функцию <code>exec</code>. (Мы будем часто использовать общее выражение «функция <code>exec</code>», когда неважно, какая из шести функций семейства <code>exec</code> вызывается.) Функция <code>exec</code> заменяет копию текущего процесса новым программным файлом, причем в новой программе обычно запускается функция <code>main</code>. Идентификатор процесса при этом не изменяется. Процесс, вызывающий функцию <code>exec</code>, мы будем называть <emphasis>вызывающим процессом</emphasis>, а выполняемую при этом программу — <emphasis>новой программой</emphasis>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В старых описаниях и книгах новая программа ошибочно называется «новым процессом». Это неверно, поскольку новый процесс не создается.</p>
     </cite>
     <p>Различие между шестью функциями <code>exec</code> заключается в том, что они допускают различные способы задания аргументов:</p>
     <p>&#9632; выполняемый программный файл может быть задан или <emphasis>именем файла</emphasis> (<emphasis>filename</emphasis>), или <emphasis>полным именем</emphasis> (<emphasis>pathname</emphasis>);</p>
     <p>&#9632; аргументы новой программы либо перечисляются один за другим, либо на них имеется ссылка через массив указателей;</p>
     <p>&#9632; новой программе либо передается окружение вызывающего процесса, либо задается новое окружение.</p>
     <p><code>#include &lt;unistd.h&gt;</code></p>
     <empty-line/>
     <p><code>int execl(const char *<emphasis>pathname</emphasis>, const char *<emphasis>arg0</emphasis>, ... /* (char*)0 */ );</code></p>
     <p><code>int execv(const char *<emphasis>pathname</emphasis>, char *const <emphasis>argv</emphasis>[]);</code></p>
     <p><code>int execle(const char *<emphasis>pathname</emphasis>, const char *<emphasis>arg0</emphasis> ... /* (char*)0,</code></p>
     <p><code> char *const <emphasis>envp</emphasis>[] */ );</code></p>
     <p><code>int execve(const char *<emphasis>pathname</emphasis>, char *const <emphasis>argv</emphasis>[], char *const <emphasis>envp</emphasis>[]);</code></p>
     <p><code>int execlp(const char *<emphasis>filename</emphasis>, const char *<emphasis>arg0</emphasis>, .... /* (char*)0 */ );</code></p>
     <p><code>int execvp(const char *<emphasis>filename</emphasis>, char *const <emphasis>argv</emphasis>[]);</code></p>
     <p><code><emphasis>Все шесть функций возвращают: -1 в случае ошибки, если же функция выполнена успешно, то ничего не возвращается</emphasis></code></p>
     <p>Эти функции возвращают вызывающему процессу значение -1, только если происходит ошибка. Иначе управление передается в начало новой программы, обычно функции <code>main</code>.</p>
     <p>Отношения между этими шестью функциями показаны на рис. 4.4. Обычно только функция <code>execve</code> является системным вызовом внутри ядра, а остальные представляют собой библиотечные функции, вызывающие <code>execve</code>.</p>
     <image l:href="#img_33.png"/>
     <p><strong>Рис. 4.4</strong>. Отношения между шестью функциями exec</p>
     <p>Отметим различия между этими функциями:</p>
     <p>1. Три верхних функции (см. рис. 4.4) принимают каждую строку как отдельный аргумент, причем перечень аргументов завершается пустым указателем (так как их количество может быть различным). У трех нижних функций имеется массив <code>argv</code>, содержащий указатели на строки. Этот массив должен содержать пустой указатель, определяющий конец массива, поскольку размер массива не задается.</p>
     <p>2. Две функции в левой колонке получают аргумент <code>filename</code>. Он преобразуется в <code>pathname</code> с использованием текущей переменной окружения <code>PATH</code>. Если аргумент <code>filename</code> функций <code>execlp</code> или <code>execvp</code> содержит косую черту (<code>/</code>) в любом месте строки, переменная <code>PATH</code> не используется. Четыре функции в двух правых колонках получают полностью определенный аргумент <code>pathname</code>.</p>
     <p>3. Четыре функции в двух левых колонках не получают явного списка переменных окружения. Вместо этого с помощью текущего значения внешней переменной <code>environ</code> создается список переменных окружения, который передается новой программе. Две функции в правой колонке получают точный список переменных окружения. Массив указателей <code>envp</code> должен быть завершен пустым указателем.</p>
     <p>Дескрипторы, открытые в процессе перед вызовом функции <code>exec</code>, обычно остаются открытыми во время ее выполнения. Мы говорим «обычно», поскольку это свойство может быть отключено при использовании функции <code>fcntl</code> для установки флага дескриптора <code>FD_CLOEXEC</code>. Это нужно серверу <code>inetd</code>, о котором пойдет речь в разделе 13.5.</p>
    </section>
    <section>
     <title>
      <p>4.8. Параллельные серверы</p>
     </title>
     <p>Сервер, представленный в листинге 4.2, является <emphasis>последовательным (итеративным) сервером</emphasis>. Для такого простого сервера, как сервер времени и даты, это допустимо. Но когда обработка запроса клиента занимает больше времени, мы не можем связывать один сервер с одним клиентом, поскольку нам хотелось бы обрабатывать множество клиентов одновременно. Простейшим способом написать параллельный сервер под Unix является вызов функции <code>fork</code>, порождающей дочерний процесс для каждого клиента. В листинге 4.3 представлена общая схема типичного параллельного сервера.</p>
     <p><strong>Листинг 4.3</strong>. Типичный параллельный сервер</p>
     <p><code>pid_t pid;</code></p>
     <p><code>int listenfd, connfd;</code></p>
     <empty-line/>
     <p><code>listenfd = Socket( ... );</code></p>
     <empty-line/>
     <p><code>/* записываем в sockaddr_in{} параметры заранее известного порта сервера */</code></p>
     <p><code>Bind(listenfd, ... );</code></p>
     <p><code>Listen(listenfd, LISTENQ);</code></p>
     <empty-line/>
     <p><code>for (;;) {</code></p>
     <p><code> connfd = Accept(listenfd, ...); /* вероятно, блокировка */</code></p>
     <empty-line/>
     <p><code> if ((pid = Fork() == 0) {</code></p>
     <p><code>  Close(listenfd); /* дочерний процесс закрывает</code></p>
     <p><code>                      прослушиваемый сокет */</code></p>
     <p><code>  doit(connfd);    /* обработка запроса */</code></p>
     <p><code>  Close(connfd);   /* с этим клиентом закончено */</code></p>
     <p><code>  exit(0);         /* дочерний процесс завершен */</code></p>
     <p><code> }</code></p>
     <empty-line/>
     <p><code> Close(connfd);    /* родительский процесс закрывает</code></p>
     <p><code>                      присоединенный сокет */</code></p>
     <p><code>}</code></p>
     <p>Когда соединение установлено, функция <code>accept</code> возвращает управление, сервер вызывает функцию <code>fork</code> и затем дочерний процесс занимается обслуживанием клиента (по присоединенному сокету <code>connfd</code>), а родительский процесс ждет другого соединения (на прослушиваемом сокете <code>listenfd</code>). Родительский процесс закрывает присоединенный сокет, поскольку новый клиент обрабатывается дочерним процессом.</p>
     <p>Мы предполагаем, что функция <code>doit</code> в листинге 4.3 выполняет все, что требуется для обслуживания клиента. Когда эта функция возвращает управление, мы явно закрываем присоединенный сокет с помощью функции <code>close</code> в дочернем процессе. Делать это не обязательно, так как в следующей строке вызывается <code>exit</code>, а прекращение процесса подразумевает, в частности, закрытие ядром всех открытых дескрипторов. Включать явный вызов функции <code>close</code> или нет — дело вкуса программиста.</p>
     <p>В разделе 2.6 мы сказали, что вызов функции <code>close</code> на сокете TCP вызывает отправку сегмента FIN, за которой следует обычная последовательность прекращения соединения TCP. Почему же функция <code>close(connfd)</code> из листинга 4.3, вызванная родительским процессом, не завершает соединение с клиентом? Чтобы понять происходящее, мы должны учитывать, что у каждого файла и сокета есть счетчик ссылок (reference count). Для счетчика ссылок поддерживается своя запись в таблице файла [110, с. 57–60]. Эта запись содержит значения счетчика дескрипторов, открытых в настоящий момент, которые соответствуют этому файлу или сокету. В листинге 4.3 после завершения функции <code>socket</code> запись в таблице файлов, связанная с <code>listenfd</code>, содержит значение счетчика ссылок, равное 1. Но после завершения функции <code>fork</code> дескрипторы дублируются (для совместного использования и родительским, и дочерним процессом), поэтому записи в таблице файла, ассоциированные с этими сокетами, теперь содержат значение 2. Следовательно, когда родительский процесс закрывает <code>connfd</code>, счетчик ссылок уменьшается с 2 до 1. Но фактического закрытия дескриптора не произойдет, пока счетчик ссылок не станет равен 0. Это случится несколько позже, когда дочерний процесс закроет <code>connfd</code>.</p>
     <p>Рассмотрим пример, иллюстрирующий листинг 4.3. Прежде всего, на рис. 4.5 показано состояние клиента и сервера в тот момент, когда сервер блокируется при вызове функции accept и от клиента приходит запрос на соединение.</p>
     <image l:href="#img_34.png"/>
     <p><strong>Рис. 4.5</strong>. Состояние соединения клиент-сервер перед завершением вызванной функции accept</p>
     <p>Сразу же после завершения функции <code>accept</code> мы получаем сценарий, изображенный на рис. 4.6. Соединение принимается ядром и создается новый сокет — <code>connfd</code>. Это присоединенный сокет, и теперь данные могут считываться и записываться по этому соединению.</p>
     <image l:href="#img_35.png"/>
     <p><strong>Рис. 4.6</strong>. Состояние соединения клиент-сервер после завершения функции accept</p>
     <p>Следующим действием параллельного сервера является вызов функции <code>fork</code>. На рис. 4.7 показано состояние соединения после вызова функции <code>fork</code>.</p>
     <image l:href="#img_36.png"/>
     <p><strong>Рис. 4.7</strong>. Состояние соединения клиент-сервер после вызова функции fork</p>
     <p>Обратите внимание, что оба дескриптора <code>listenfd</code> и <code>connfd</code> совместно используются родительским и дочерним процессами.</p>
     <p>Далее родительский процесс закрывает присоединенный сокет, а дочерний процесс закрывает прослушиваемый сокет. Это показано на рис. 4.8.</p>
     <image l:href="#img_37.png"/>
     <p><strong>Рис. 4.8</strong>. Состояние соединения клиент-сервер после закрытия родительским и дочерним процессами соответствующих сокетов</p>
     <p>Это и есть требуемое конечное состояние сокетов. Дочерний процесс управляет соединением с клиентом, а родительский процесс может снова вызвать функцию <code>accept</code> на прослушиваемом сокете, чтобы обрабатывать следующее клиентское соединение.</p>
    </section>
    <section>
     <title>
      <p>4.9. Функция close</p>
     </title>
     <section>
      <p>Обычная функция Unix <code>close</code> также используется для закрытия сокета и завершения соединения TCP.</p>
      <p><code>#include &lt;unistd.h&gt;</code></p>
      <empty-line/>
      <p><code>int close(int <emphasis>sockfd</emphasis>);</code></p>
      <p>По умолчанию функция <code>close</code> помечает сокет TCP как закрытый и немедленно возвращает управление процессу. Дескриптор сокета больше не используется процессом и не может быть передан в качестве аргумента функции <code>read</code> или <code>write</code>. Но TCP попытается отправить данные, которые уже установлены в очередь, и после их отправки осуществит нормальную последовательность завершения соединения TCP (см. раздел 2.5).</p>
      <p>В разделе 7.5 рассказывается о параметре сокета <code>SO_LINGER</code>, который позволяет нам изменять последовательность закрытия сокета TCP. В этом разделе мы также назовем действия, благодаря которым приложение TCP может получить гарантию того, что приложение-собеседник получило данные, поставленные в очередь на отправку, но еще не отправленные.</p>
     </section>
     <section>
      <title>
       <p>Счетчик ссылок дескриптора</p>
      </title>
      <p>В конце раздела 4.8 мы отметили, что когда родительский процесс на нашем параллельном сервере закрывает присоединенный сокет с помощью функции <code>close</code>, счетчик ссылок дескриптора уменьшается лишь на единицу. Поскольку счетчик ссылок при этом все еще оставался больше нуля, вызов функции <code>close</code> не инициировал последовательность завершения TCP-соединения, состоящую из четырех пакетов. Нам нужно, чтобы наш параллельный сервер с присоединенным сокетом, разделяемым между родительским и дочерним процессами, работал именно по этому принципу.</p>
      <p>Если мы хотим отправить сегмент FIN по соединению TCP, вместо функции <code>close</code> должна использоваться функция <code>shutdown</code> (см. раздел 6.6). Причины мы рассмотрим в разделе 6.5.</p>
      <p>Необходимо также знать, что происходит с нашим параллельным сервером, если родительский процесс не вызывает функцию <code>close</code> для каждого присоединенного сокета, возвращаемого функцией <code>accept</code>. Прежде всего, родительский процесс в какой-то момент израсходует все дескрипторы, поскольку обычно число дескрипторов, которые могут быть открыты процессом, ограничено. Но что более важно, ни одно из клиентских соединений не будет завершено. Когда дочерний процесс закрывает присоединенный сокет, его счетчик ссылок уменьшается с 2 до 1 и остается равным 1, поскольку родительский процесс не закрывает присоединенный сокет с помощью функции <code>close</code>. Это помешает выполнить последовательность завершения соединения TCP, и соединение останется открытым.</p>
     </section>
    </section>
    <section>
     <title>
      <p>4.10. Функции getsockname и getpeername</p>
     </title>
     <section>
      <p>Эти две функции возвращают либо локальный (функция <code>getsockname</code>), либо удаленный (функция <code>getpeername</code>) адрес протокола, связанный с сокетом.</p>
      <p><code>#include &lt;sys/socket.h&gt;</code></p>
      <empty-line/>
      <p><code>int getsockname(int <emphasis>sockfd</emphasis>, struct sockaddr *<emphasis>localaddr</emphasis>,</code></p>
      <p><code> socklen_t *<emphasis>addrlen</emphasis>);</code></p>
      <p><code>int getpeername(int <emphasis>sockfd</emphasis>, struct sockaddr *<emphasis>peeraddr</emphasis>,</code></p>
      <p><code> socklen_t *<emphasis>addrlen</emphasis>);</code></p>
      <p>Обратите внимание, что последний аргумент обеих функций относится к типу «значение-результат», то есть обе функции будут заполнять структуру адреса сокета, на которую указывает аргумент <code>localaddr</code> или <code>peeraddr</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Обсуждая функцию bind, мы отметили, что термин «имя» используется некорректно. Эти две функции возвращают адрес протокола, связанный с одним из концов сетевого соединения, что для протоколов IPv4 и IPv6 является сочетанием IP-адреса и номера порта. Эти функции также не имеют ничего общего с доменными именами (глава 11).</p>
      </cite>
      <p>Функции <code>getsockname</code> и <code>getpeername</code> необходимы нам по следующим соображениям:</p>
      <p>&#9632; После успешного выполнения функции <code>connect</code> и возвращения управления в клиентский процесс TCP, который не вызывает функцию <code>bind</code>, функция <code>getsockname</code> возвращает IP-адрес и номер локального порта, присвоенные соединению ядром.</p>
      <p>&#9632; После вызова функции <code>bind</code> с номером порта 0 (что является указанием ядру на необходимость выбрать номер локального порта) функция <code>getsockname</code> возвращает номер локального порта, который был задан.</p>
      <p>&#9632; Функцию <code>getsockname</code> можно вызвать, чтобы получить семейство адресов сокета, как это показано в листинге 4.4.</p>
      <p>&#9632; Сервер TCP, который с помощью функции <code>bind</code> связывается с универсальным IP-адресом (см. листинг 1.5), как только устанавливается соединение с клиентом (функция <code>accept</code> успешно выполнена), может вызвать функцию <code>getsockname</code>, чтобы получить локальный IP-адрес соединения. Аргумент <code>sockfd</code> (дескриптор сокета) в этом вызове должен содержать дескриптор присоединенного, а не прослушиваемого сокета.</p>
      <p>&#9632; Когда сервер запускается с помощью функции <code>exec</code> процессом, вызывающим функцию <code>accept</code>, он может идентифицировать клиента только одним способом - вызвать функцию <code>getpeername</code>. Это происходит, когда функция <code>inetd</code> (см. раздел 13.5) вызывает функции <code>fork</code> и <code>exec</code> для создания сервера TCP. Этот сценарий представлен на рис. 4.9. Функция <code>inetd</code> вызывает функцию <code>accept</code> (верхняя левая рамка), после чего возвращаются два значения: дескриптор присоединенного сокета <code>connfd</code> (это возвращаемое значение функции), а также IP-адрес и номер порта клиента, отмеченные на рисунке небольшой рамкой с подписью «адрес собеседника» (структура адреса сокета Интернета). Далее вызывается функция <code>fork</code> и создается дочерний процесс функции <code>inetd</code>. Поскольку дочерний процесс запускается с копией содержимого памяти родительского процесса, структура адреса сокета доступна дочернему процессу, как и дескриптор присоединенного сокета (так как дескрипторы совместно используются родительским и дочерним процессами). Но когда дочерний процесс с помощью функции <code>exec</code> запускает выполнение реального сервера (скажем, сервера Telnet), содержимое памяти дочернего процесса заменяется новым программным файлом для сервера Telnet (то есть структура адреса сокета, содержащая адрес собеседника, теряется). Однако во время выполнения функции <code>exec</code> дескриптор присоединенного сокета остается открытым. Один из первых вызовов функции, который выполняет сервер Telnet, — это вызов функции <code>getpeername</code> для получения IP-адреса и номера порта клиента.</p>
      <image l:href="#img_38.png"/>
      <p><strong>Рис. 4.9</strong>. Порождение сервера демоном inetd</p>
      <p>Очевидно, что в приведенном примере сервер Telnet при запуске должен знать значение функции <code>connfd</code>. Этого можно достичь двумя способами. Во-первых, процесс, вызывающий функцию <code>exec</code>, может отформатировать номер дескриптора как символьную строку и передать ее в виде аргумента командной строки программе, выполняемой с помощью функции <code>exec</code>. Во-вторых, можно заключить соглашение относительно определенных дескрипторов: некоторый дескриптор всегда присваивается присоединенному сокету перед вызовом функции <code>exec</code>. Последний случай соответствует действию функции <code>inetd</code> — она всегда присваивает дескрипторы 0, 1 и 2 присоединенным сокетам.</p>
     </section>
     <section>
      <title>
       <p>Пример: получение семейства адресов сокета</p>
      </title>
      <p>Функция <code>sockfd_to_family</code>, представленная в листинге 4.4, возвращает семейство адресов сокета.</p>
      <p><strong>Листинг 4.4</strong>. Возвращаемое семейство адресов сокета</p>
      <p><code>//lib/sockfd_to_family.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 sockfd_to_family(int sockfd)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  union {</code></p>
      <p><code> 6   struct sockaddr sa;</code></p>
      <p><code> 7   char data[MAXSOCKADDR];</code></p>
      <p><code> 8  } un;</code></p>
      <p><code> 9  socklen_t len;</code></p>
      <empty-line/>
      <p><code>10  len = MAXSOCKADDR;</code></p>
      <p><code>11  if (getsockname(sockfd, (SA*)un.data, &amp;len) &lt; 0)</code></p>
      <p><code>12   return (-1);</code></p>
      <p><code>13  return (un.sa.sa_family);</code></p>
      <p><code>14 }</code></p>
      <subtitle>Выделение пространства для наибольшей структуры адреса сокета</subtitle>
      <p><code>5-8</code> Поскольку мы не знаем, какой тип структуры адреса сокета нужно будет разместить в памяти, мы используем в нашем заголовочном файле <code>unp.h</code> константу <code>MAXSOCKADDR</code>, которая представляет собой размер наибольшей структуры адреса сокета в байтах. Мы определяем массив типа <code>char</code> соответствующего размера в объединении, включающем универсальную структуру адреса сокета.</p>
      <subtitle>Вызов функции getsockname</subtitle>
      <p><code>10-13</code> Мы вызываем функцию <code>getsockname</code> и возвращаем семейство адресов.</p>
      <p>Поскольку POSIX позволяет вызывать функцию <code>getsockname</code> на неприсоединенном сокете, эта функция должна работать для любого дескриптора открытого сокета.</p>
     </section>
    </section>
    <section>
     <title>
      <p>4.11. Резюме</p>
     </title>
     <p>Все клиенты и серверы начинают работу с вызова функции <code>socket</code>, возвращающей дескриптор сокета. Затем клиенты вызывают функцию <code>connect</code>, в то время как серверы вызывают функции <code>bind</code>, <code>listen</code> и <code>accept</code>. Сокеты обычно закрываются с помощью стандартной функции <code>close</code>, хотя в разделе 6.6 вы увидите другой способ закрытия, реализуемый с помощью функции <code>shutdown</code>. Мы также проверим влияние параметра сокета <code>SO_LINGER</code> (см. раздел 7.5).</p>
     <p>Большинство серверов TCP являются параллельными. При этом для каждого клиентского соединения, которым управляет сервер, вызывается функция <code>fork</code>. Вы увидите, что большинство серверов UDP являются последовательными. Хотя обе эти модели успешно использовались на протяжении ряда лет, имеются и другие возможности создания серверов с использованием программных потоков и процессов, которые мы рассмотрим в главе 30.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. В разделе 4.4 мы утверждали, что константы <code>INADDR_</code>, определенные в заголовочном файле <code>&lt;netinet/in.h&gt;</code>, расположены в порядке байтов узла. Каким образом мы можем это определить?</p>
     <p>2. Измените листинг 1.1 так, чтобы вызвать функцию <code>getsockname</code> после успешного завершения функции <code>connect</code>. Выведите локальный IP-адрес и локальный порт, присвоенный сокету TCP, используя функцию <code>sock_ntop</code>. В каком диапазоне (см. рис. 2.10) будут находиться динамически назначаемые порты вашей системы?</p>
     <p>3. Предположим, что на параллельном сервере после вызова функции <code>fork</code> запускается дочерний процесс, который завершает обслуживание клиента перед тем, как результат выполнения функции <code>fork</code> возвращается родительскому процессу. Что происходит при этих двух вызовах функции <code>close</code> в листинге 4.3?</p>
     <p>4. В листинге 4.2 сначала измените порт сервера с 13 на 9999 (так, чтобы для запуска программы вам не потребовались права привилегированного пользователя). Удалите вызов функции <code>listen</code>. Что происходит?</p>
     <p>5. Продолжайте предыдущее упражнение. Удалите вызов функции <code>bind</code>, но оставьте вызов функции <code>listen</code>. Что происходит?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 5</p>
     <p>Пример TCP-соединения клиент-сервер</p>
    </title>
    <section>
     <title>
      <p>5.1. Введение</p>
     </title>
     <p>Напишем простой пример пары клиент-сервер, используя элементарные функции из предыдущей главы. Наш простой пример — это эхо-сервер, функционирующий следующим образом:</p>
     <p>1. Клиент считывает строку текста из стандартного потока ввода и отправляет ее серверу.</p>
     <p>2. Сервер считывает строку из сети и отсылает эту строку обратно клиенту.</p>
     <p>3. Клиент считывает отраженную строку и помещает ее в свой стандартный поток вывода.</p>
     <p>На рис. 5.1 изображена пара клиент-сервер вместе с функциями, используемыми для ввода и вывода.</p>
     <image l:href="#img_39.png"/>
     <p><strong>Рис. 5.1</strong>. Простой эхо-клиент и эхо-сервер</p>
     <p>Между клиентом и сервером мы показали две стрелки, но на самом деле это одно двустороннее соединение TCP. Функции <code>fgets</code> и <code>fputs</code> имеются в стандартной библиотеке ввода-вывода, а функции <code>writen</code> и <code>readline</code> приведены в разделе 3.9.</p>
     <p>Мы разрабатываем нашу собственную реализацию эхо-сервера, однако большинство реализаций TCP/IP предоставляют готовый эхо-сервер, работающий как с TCP, так и с UDP (см. раздел 2.12). С нашим собственным клиентом мы также будем использовать и готовый сервер.</p>
     <p>Соединение клиент-сервер, отражающее вводимые строки, является корректным и в то же время простым примером сетевого приложения. На этом примере можно проиллюстрировать все основные действия, необходимые для реализации соединения клиент-сервер. Все, что вам нужно сделать, чтобы применить его к вашему приложению, — это изменить операции, которые выполняет сервер с принимаемыми от клиентов данными.</p>
     <p>С помощью этого примера мы можем не только проанализировать запуск нашего клиента и сервера в нормальном режиме (ввести строку и посмотреть, как она отражается), но и исследовать множество «граничных условий»: выяснить, что происходит в момент запуска клиента и сервера; что происходит, когда клиент нормальным образом завершает работу; что происходит с клиентом, если процесс сервера завершается до завершения клиента или если возникает сбой на узле сервера, и т.д. Рассмотрев эти сценарии мы сможем понять, что происходит на уровне сети и как это представляется для API сокетов, и научиться писать приложения так, чтобы они умели обрабатывать подобные ситуации.</p>
     <p>Во всех рассматриваемых далее примерах присутствуют зависящие от протоколов жестко заданные (hard coded) константы, такие как адреса и порты. Это обусловлено двумя причинами. Во-первых, нам необходимо точно понимать, что нужно хранить в структурах адресов, относящихся к конкретным протоколам. Во-вторых, мы еще не рассмотрели библиотечные функции, которые сделали бы наши программы более переносимыми. Эти функции рассматриваются в главе 11.</p>
     <p>В последующих главах код клиента и сервера будет претерпевать многочисленные изменения, по мере того как вы будете больше узнавать о сетевом программировании (см. табл. 1.3 и 1.4).</p>
    </section>
    <section>
     <title>
      <p>5.2. Эхо-сервер TCP: функция main</p>
     </title>
     <p>Наши клиент и сервер TCP используют функции, показанные на рис. 4.1. Программа параллельного сервера представлена в листинге 5.1<a l:href="#n1" type="note">[1]</a>.</p>
     <p><strong>Листинг 5.1</strong>. Эхо-сервер TCP (улучшенный в листинге 5.9)</p>
     <p><code>//tcpcliserv/tcpserv01.с</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int listenfd, connfd;</code></p>
     <p><code> 6  pid_t childpid;</code></p>
     <p><code> 7  socklen_t clilen;</code></p>
     <p><code> 8  struct sockaddr_in cliaddr, servaddr;</code></p>
     <empty-line/>
     <p><code> 9  listenfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code>10  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>11  servaddr.sin_family = AF_INET;</code></p>
     <p><code>12  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>13  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <empty-line/>
     <p><code>14  Bind(listenfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>15  Listen(listenfd, LISTENQ);</code></p>
     <empty-line/>
     <p><code>16  for (;;) {</code></p>
     <p><code>17   clilen = sizeof(cliaddr);</code></p>
     <p><code>18   connfd = Accept(listenfd, (SA*)&amp;cliadd, &amp;clilen);</code></p>
     <empty-line/>
     <p><code>19   if ((childpid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>20    Close(listenfd); /* закрываем прослушиваемый сокет */</code></p>
     <p><code>21    str_echo(connfd); /* обрабатываем запрос */</code></p>
     <p><code>22    exit(0);</code></p>
     <p><code>23   }</code></p>
     <p><code>24   Close(connfd); /* родительский процесс закрывает</code></p>
     <p><code>                     присоединенный сокет */</code></p>
     <p><code>25  }</code></p>
     <p><code>26 }</code></p>
     <subtitle>Создание сокета, связывание с известным портом сервера</subtitle>
     <p><code>9-15</code> Создается сокет TCP. В структуру адреса сокета Интернета записывается универсальный адрес (<code>INADDR_ANY</code>) и номер заранее известного порта сервера (<code>SERV_PORT</code>, который определен как 9877 в нашем заголовочном файле <code>unp.h</code>). В результате связывания с универсальным адресом системе сообщается, что мы примем соединение, предназначенное для любого локального интерфейса в том случае, если система имеет несколько сетевых интерфейсов. Наш выбор номера порта TCP основан на рис. 2.10. Он должен быть больше 1023 (нам не нужен зарезервированный порт), больше 5000 (чтобы не допустить конфликта с динамически назначаемыми портами, которые выделяются многими реализациями, происходящими от Беркли), меньше 49 152 (чтобы избежать конфликта с «правильным» диапазоном динамически назначаемых портов) и не должен конфликтовать ни с одним зарегистрированным портом. Сокет преобразуется в прослушиваемый при помощи функции <code>listen</code>.</p>
     <subtitle>Ожидание завершения клиентского соединения</subtitle>
     <p><code>17-18</code> Сервер блокируется в вызове функции <code>accept</code>, ожидая подключения клиента.</p>
     <subtitle>Параллельный сервер</subtitle>
     <p><code>19-24</code> Для каждого клиента функция <code>fork</code> порождает дочерний процесс, и дочерний процесс обслуживает запрос этого клиента. Как мы говорили в разделе 4.8, дочерний процесс закрывает прослушиваемый сокет, а родительский процесс закрывает присоединенный сокет. Затем дочерний процесс вызывает функцию <code>str_echo</code> (см. листинг 5.2) для обработки запроса клиента.</p>
    </section>
    <section>
     <title>
      <p>5.3. Эхо-сервер TCP: функция str_echo</p>
     </title>
     <p>Функция <code>str_echo</code>, показанная в листинге 5.2, выполняет серверную обработку запроса клиента: считывание строк от клиента и отражение их обратно клиенту.</p>
     <p><strong>Листинг 5.2</strong>. Функция str_echo: отраженные строки на сокете</p>
     <p><code>//lib/str_echo.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 str_echo(int sockfd)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  ssize_t n;</code></p>
     <p><code> 6  char buf[MAXLINE];</code></p>
     <empty-line/>
     <p><code> 7  for (;;) {</code></p>
     <p><code> 8   if ((n = read(sockfd, buf, MAXLINE)) &gt; 0)</code></p>
     <p><code> 9    return; /* соединение закрыто с другого конца */</code></p>
     <empty-line/>
     <p><code>10   Writen(sockfd, line, n);</code></p>
     <p><code>11  }</code></p>
     <p><code>12 }</code></p>
     <subtitle>Чтение строки и ее отражение</subtitle>
     <p><code>7-11</code> Функция <code>read</code> считывает очередную строку из сокета, после чего строка отражается обратно клиенту с помощью функции <code>writen</code>. Если клиент закрывает соединение (нормальный сценарий), то при получении клиентского сегмента FIN функция дочернего процесса <code>read</code> возвращает нуль. После этого происходит возврат из функции <code>str_echo</code> и далее завершается дочерний процесс, приведенный в листинге 5.1.</p>
    </section>
    <section>
     <title>
      <p>5.4. Эхо-клиент TCP: функция main</p>
     </title>
     <p>В листинге 5.3 показана функция <code>main</code> TCP-клиента.</p>
     <p><strong>Листинг 5.3</strong>. Эхо-клиент TCP</p>
     <p><code>//tcpcliserv/tcpcli01.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd;</code></p>
     <p><code> 6  struct sockaddr_in servaddr;</code></p>
     <empty-line/>
     <p><code> 7  if (argc != 2)</code></p>
     <p><code> 8   err_quit("usage: tcpcli &lt;Ipaddress&gt;");</code></p>
     <empty-line/>
     <p><code> 9  sockfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code>10  bzero(&amp;servaddr. sizeof(servaddr));</code></p>
     <p><code>11  servaddr.sin_family = AF_INET;</code></p>
     <p><code>12  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <p><code>13  Inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr);</code></p>
     <empty-line/>
     <p><code>14  Connect(sockfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>15  str_cli(stdin, sockfd); /* эта функция выполняет все необходимые</code></p>
     <p><code>                               действия со стороны клиента */</code></p>
     <p><code>16  exit(0);</code></p>
     <p><code>17 }</code></p>
     <subtitle>Создание сокета, заполнение структуры его адреса</subtitle>
     <p><code>9-13</code> Создается сокет TCP и структура адреса сокета заполняется IP-адресом сервера и номером порта. IP-адрес сервера мы берем из командной строки, а известный номер порта сервера (<code>SERV_PORT</code>) — из нашего заголовочного файла <code>unp.h</code>.</p>
     <subtitle>Соединение с сервером</subtitle>
     <p><code>14-15</code> Функция <code>connect</code> устанавливает соединение с сервером. Затем функция <code>str_cli</code> (см. листинг 5.4) выполняет все необходимые действия со стороны клиента.</p>
    </section>
    <section>
     <title>
      <p>5.5. Эхо-клиент TCP: функция str_cli</p>
     </title>
     <p>Эта функция, показанная в листинге 5.4, обеспечивает отправку запроса клиента и прием ответа сервера в цикле. Функция считывает строку текста из стандартного потока ввода, отправляет ее серверу и считывает отраженный ответ сервера, после чего помещает отраженную строку в стандартный поток вывода.</p>
     <p><strong>Листинг 5.4</strong>. Функция str_cli: цикл формирования запроса клиента</p>
     <p><code>//lib/str_cli.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 str_cli(FILE *fp, int sockfd)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  char sendline[MAXLINE], recvline[MAXLINE];</code></p>
     <empty-line/>
     <p><code> 6  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
     <empty-line/>
     <p><code> 7   Writen(sockfd,. sendline, strlen(sendline));</code></p>
     <empty-line/>
     <p><code> 8   if (Readline(sockfd, recvline, MAXLINE) == 0)</code></p>
     <p><code> 9    err_quit("str_cli: server terminated prematurely");</code></p>
     <empty-line/>
     <p><code>10   Fputs(recvline, stdout);</code></p>
     <p><code>11  }</code></p>
     <p><code>12 }</code></p>
     <subtitle>Считывание строки, отправка серверу</subtitle>
     <p><code>6-7</code> Функция <code>fgets</code> считывает строку текста, а функция <code>writen</code> отправляет эту строку серверу.</p>
     <subtitle>Считывание отраженной сервером строки, запись в стандартный поток вывода</subtitle>
     <p><code>8-10</code> Функция <code>readline</code> принимает отраженную сервером строку, а функция <code>fputs</code> записывает ее в стандартный поток вывода.</p>
     <subtitle>Возврат в функцию main</subtitle>
     <p><code>11-12</code> Цикл завершается, когда функция <code>fgets</code> возвращает пустой указатель, что означает достижение конца файла или обнаружение ошибки. Наша функция-обертка <code>Fgets</code> проверяет наличие ошибки, и если ошибка действительно произошла, прерывает выполнение программы. Таким образом, функция <code>Fgets</code> возвращает пустой указатель только при достижении конца файла.</p>
    </section>
    <section>
     <title>
      <p>5.6. Нормальный запуск</p>
     </title>
     <p>Наш небольшой пример использования TCP (около 150 строк кода для двух функций <code>main</code>, <code>str_echo</code>, <code>str_cli</code>, <code>readline</code> и <code>writen</code>) позволяет понять, как запускаются и завершаются клиент и сервер и, что наиболее важно, как развиваются события, если произошел сбой на узле клиента или в клиентском процессе, потеряна связь в сети и т.д. Только при понимании этих «граничных условий» и их взаимодействия с протоколами TCP/IP мы сможем обеспечить устойчивость клиентов и серверов, которые смогут справляться с подобными ситуациями.</p>
     <p>Сначала мы запускаем сервер в фоновом режиме на узле <code>linux</code>.</p>
     <p><code>linux % <strong>tcpserv01 &amp;</strong></code></p>
     <p><code>[1] 17870</code></p>
     <p>Когда сервер запускается, он вызывает функции <code>socket</code>, <code>bind</code>, <code>listen</code> и <code>accept</code>, а затем блокируется в вызове функции <code>accept</code>. (Мы еще не запустили клиент.) Перед тем, как запустить клиент, мы запускаем программу <code>netstat</code>, чтобы проверить состояние прослушиваемого сокета сервера.</p>
     <p><code>linux % <strong>netstat -a</strong></code></p>
     <p><code>Active Internet connections (servers and established)</code></p>
     <p><code>Proto Recv-Q Send-Q Local Address Foreign Address State</code></p>
     <p><code>tcp        0      0 *:9877        *:*             LISTEN</code></p>
     <p>Здесь мы показываем только первую строку вывода и интересующую нас строку. Эта команда показывает состояние <emphasis>всех</emphasis> сокетов в системе, поэтому вывод может быть большим. Для просмотра прослушиваемых сокетов следует указать параметр <code>-a</code>.</p>
     <p>Результат совпадает с нашими ожиданиями. Сокет находится в состоянии LISTEN, локальный IP-адрес задан с помощью символа подстановки (то есть является универсальным) и указан локальный порт 9877. Функция <code>netstat</code> выводит звездочку для нулевого IP-адреса (<code>INADDR_ANY</code>, универсальный адрес) или для нулевого порта.</p>
     <p>Затем на том же узле мы запускаем клиент, задав IP-адрес сервера 127.0.0.1. Мы могли бы задать здесь и нормальный адрес сервера (его IP-адрес в сети).</p>
     <p><code>linux % <strong>tcpcli01 127.0.0.1</strong></code></p>
     <p>Клиент вызывает функции <code>socket</code> и <code>connect</code>, последняя осуществляет трехэтапное рукопожатие TCP. Когда рукопожатие TCP завершается, функция connect возвращает управление процессу-клиенту, а функция <code>accept</code> — процессу-серверу. Соединение установлено. Затем выполняются следующие шаги:</p>
     <p>1. Клиент вызывает функцию <code>str_cli</code>, которая блокируется в вызове функции <code>fgets</code>, поскольку мы еще ничего не ввели.</p>
     <p>2. Когда функция <code>accept</code> возвращает управление процессу-серверу, последний вызывает функцию <code>fork</code>, а дочерний процесс вызывает функцию <code>str_echo</code>. Та вызывает функцию <code>read</code>, блокируемую в ожидании получения данных от клиента.</p>
     <p>3. Родительский процесс сервера снова вызывает функцию <code>accept</code> и блокируется в ожидании подключения следующего клиента.</p>
     <p>У нас имеется три процесса, и все они находятся в состоянии ожидания (блокированы): клиент, родительский процесс сервера и дочерний процесс сервера.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Мы специально поставили первым пунктом (после завершения трехэтапного рукопожатия) вызов функции str_cli, происходящий на стороне клиента, а затем уже перечислили действия на стороне сервера. Причину объясняет рис. 2.5: функция connect возвращает управление, когда клиент получает второй сегмент рукопожатия. Однако функция accept не возвращает управление до тех пор, пока сервер не получит третий сегмент рукопожатия, то есть пока не пройдет половина периода RTT после завершения функции connect.</p>
     </cite>
     <p>Мы намеренно запускаем и клиент, и сервер на одном узле — так проще всего экспериментировать с клиент-серверными приложениями. Поскольку клиент и сервер запущены на одном узле, функция <code>netstat</code> отображает теперь две дополнительные строки вывода, соответствующие соединению TCP:</p>
     <p>l<code>inux % <strong>netstat -a</strong></code></p>
     <p><code>Proto Recv-Q Send-Q Local Address   Foreign Address State</code></p>
     <p><code>tcp        0      0 localhost:9877  localhost:42758 ESTABLISHED</code></p>
     <p><code>tcp        0      0 localhost:42758 localhost:42758 ESTABLISHED</code></p>
     <p><code>tcp        0      0 *:9877          *:*             LISTEN</code></p>
     <p>Первая из строк состояния <code>ESTABLISHED</code> соответствует дочернему сокету сервера, поскольку локальным портом является порт 9877. Вторая строка <code>ESTABLISHED</code> — это клиентский сокет, поскольку локальный порт — порт 42 758. Если мы запускаем клиент и сервер на разных узлах, на узле клиента будет отображаться только клиентский сокет, а на узле сервера — два серверных сокета.</p>
     <p>Для проверки состояний процессов и отношений между ними можно также использовать команду <code>ps</code>:</p>
     <p><code>linux % <strong>ps -t pts/6 -o pid,ppid,tty,stat,args,wchan</strong></code></p>
     <p><code>PID   PPID  TT    STAT COMMAND    WCHAN</code></p>
     <p><code>22038 22036 pts/6 S   -bash       wait4</code></p>
     <p><code>17870 22038 pts/6 S   ./tcpserv01 wait_for_connect</code></p>
     <p><code>19315 17870 pts/6 S   ./tcpserv01 tcp_data_wait</code></p>
     <p><code>19314 22038 pts/6 S   ./tcpcli01  127.0.0.1 read_chan</code></p>
     <p>Мы вызвали <code>ps</code> с несколько необычным набором аргументов для того, чтобы получить всю необходимую для дальнейшего обсуждения информацию. Мы запустили клиент и сервер из одного окна (<code>pts/6</code>, что означает псевдотерминал 6). В колонках <code>PID</code> и <code>PPID</code> показаны отношения между родительским и дочерним процессами. Можно точно сказать, что первая строка <code>tcpserv01</code> соответствует родительскому процессу, а вторая строка <code>tcpserv01</code> — дочернему, поскольку PPID дочернего процесса — это PID родительского. Кроме того, PPID родительского процесса совпадает с PID интерпретатора команд (<code>bash</code>).</p>
     <p>Колонка <code>STAT</code> для всех трех сетевых процессов отмечена символом <code>S</code>. Это означает, что процессы находятся в состоянии ожидания (sleeping). Если процесс находится в состоянии ожидания, колонка <code>WCHAN</code> сообщит нам о том, чем он занят. В Linux значение <code>wait_for_connect</code> выводится, если процесс блокируется функцией <code>accept</code> или <code>connect</code>, значение <code>tcp_data_wait</code> — если процесс блокируется при вводе или выводе через сокет, a <code>read_chan</code> — если процесс блокируется при терминальном вводе-выводе. Так что для наших трех сетевых процессов значения <code>WCHAN</code> выглядят вполне осмысленно.</p>
    </section>
    <section>
     <title>
      <p>5.7. Нормальное завершение</p>
     </title>
     <p>На этом этапе соединение установлено, и все, что бы мы ни вводили на стороне клиента, отражается обратно.</p>
     <p><code>linux % <strong>tcpcli01 127.0.0.1</strong> <emphasis>эту строку мы показывали раньше</emphasis></code></p>
     <p><code><strong>hello, world</strong> <emphasis>наш ввод</emphasis></code></p>
     <p><code>hello, world <emphasis>отраженная сервером строка</emphasis></code></p>
     <p><code><strong>good bye</strong></code></p>
     <p><code>good bye</code></p>
     <p><code><strong>^D</strong> <emphasis>Ctrl+D - наш завершающий символ для обозначения конца файла</emphasis></code></p>
     <p>Мы вводим две строки, каждая из них отражается, затем мы вводим символ конца файла (EOF) <code><strong>Ctrl+D</strong></code>, который завершает работу клиента. Если мы сразу же выполним команду <code>netstat</code>, то увидим следующее:</p>
     <p><code>linux % <strong>netstat -а | grep 9877</strong></code></p>
     <p><code>tcp 0 0 *:9877           *:*</code></p>
     <p><code>tcp 0 0 local host:42758 localhost:9877</code></p>
     <p>Клиентская часть соединения (локальный порт 42 758) входит в состояние TIME_WAIT (см. раздел 2.6), и прослушивающий сервер все еще ждет подключения другого клиента. (В этот раз мы передаем вывод <code>netstat</code> программе <code>grep</code>, чтобы вывести только строки с заранее известным портом нашего сервера. Но при этом также удаляется строка заголовка.)</p>
     <p>Перечислим этапы нормального завершения работы нашего клиента и сервера.</p>
     <p>1. Когда мы набираем символ EOF, функция <code>fgets</code> возвращает пустой указатель, и функция <code>str_cli</code> возвращает управление (см. листинг 5.4).</p>
     <p>2. Когда функция <code>str_cli</code> возвращает управление клиентской функции <code>main</code> (см. листинг 5.3), последняя завершает работу, вызывая функцию <code>exit</code>.</p>
     <p>3. При завершении процесса выполняется закрытие всех открытых дескрипторов, так что клиентский сокет закрывается ядром. При этом серверу посылается сегмент FIN, на который TCP сервера отвечает сегментом ACK. Это первая половина последовательности завершения работы соединения TCP. На этом этапе сокет сервера находится в состоянии CLOSE_WAIT, а клиентский сокет — в состоянии FIN_WAIT_2 (см. рис. 2.4 и 2.5).</p>
     <p>4. Когда TCP сервера получает сегмент FIN, дочерний процесс сервера находится в состоянии ожидания в вызове функции <code>read</code> (см. листинг 5.2), а затем функция <code>read</code> возвращает нуль. Это заставляет функцию <code>str_echo</code> вернуть управление функции <code>main</code> дочернего процесса сервера.</p>
     <p>5. Дочерний процесс сервера завершается с помощью вызова функции <code>exit</code> (см. листинг 5.1).</p>
     <p>6. Все открытые дескрипторы в дочернем процессе сервера закрываются. Закрытие присоединенного сокета дочерним процессом вызывает отправку двух последних сегментов завершения соединения TCP: FIN от сервера клиенту и ACK от клиента (см. рис. 2.5). На этом этапе соединение полностью завершается. Клиентский сокет входит в состояние TIME_WAIT.</p>
     <p>7. Другая часть завершения процесса относится к сигналу <code>SIGCHLD</code>. Он отправляется родительскому процессу, когда завершается дочерний процесс. Это происходит и в нашем примере, но мы не перехватываем данный сигнал в коде, и по умолчанию он игнорируется. Дочерний процесс входит в состояние зомби (zombie). Мы можем проверить это с помощью команды <code>ps</code>.</p>
     <p><code>linux % <strong>ps -t pts/6 -o pid,ppid,tty,stat,args,wchan</strong></code></p>
     <p><code>PID   PPID  TT    STAT COMMAND     WCHAN</code></p>
     <p><code>22038 22036 pts/6 S    -bash       read_chan</code></p>
     <p><code>17870 22038 pts/6 S    ./tcpserv01 wait_for_connect</code></p>
     <p><code>19315 17870 pts/6 Z    [tcpserv01  &lt;defu do_exit</code></p>
     <p>Теперь дочерний процесс находится в состоянии <code>Z</code> (зомби).</p>
     <p>Процессы-зомби нужно своевременно удалять, а это требует работы с сигналами Unix. Поэтому в следующем разделе мы сделаем обзор управления сигналами, а затем продолжим рассмотрение нашего примера.</p>
    </section>
    <section>
     <title>
      <p>5.8. Обработка сигналов POSIX</p>
     </title>
     <section>
      <p><emphasis>Сигнал</emphasis> — это уведомление процесса о том, что произошло некое событие. Иногда сигналы называют <emphasis>программными прерываниями</emphasis> (<emphasis>software interrupts</emphasis>). Подразумевается, что процесс не знает заранее о том, когда придет сигнал.</p>
      <p>Сигналы могут посылаться в следующих направлениях:</p>
      <p>&#9632; одним процессом другому процессу (или самому себе);</p>
      <p>&#9632; ядром процессу.</p>
      <p>Сигнал <code>SIGCHLD</code>, упомянутый в конце предыдущего раздела, ядро посылает родительскому процессу при завершении дочернего.</p>
      <p>Для каждого сигнала существует определенное <emphasis>действие</emphasis> (<emphasis>action</emphasis> или <emphasis>disposition</emphasis> — <emphasis>характер</emphasis>). Действие, соответствующее сигналу, задается с помощью вызова функции <code>sigaction</code> (ее описание следует далее) и может быть выбрано тремя способами:</p>
      <p>1. Мы можем предоставить функцию, которая вызывается при перехвате определенного сигнала. Эта функция называется <emphasis>обработчиком сигнала</emphasis> (<emphasis>signal handler</emphasis>), а действие называется <emphasis>перехватыванием сигнала</emphasis> (<emphasis>catching</emphasis>). Сигналы <code>SIGKILL</code> и <code>SIGSTOP</code> перехватить нельзя. Наша функция вызывается с одним целочисленным аргументом, который является номером сигнала, и ничего не возвращает. Следовательно, прототип этой функции имеет вид:</p>
      <p><code>void handler(int <emphasis>signo</emphasis>);</code></p>
      <p>Для большинства сигналов вызов функции <code>sigaction</code> и задание функции, вызываемой при получении сигнала, — это все, что требуется для обработки сигнала. Но дальше вы увидите, что для перехватывания некоторых сигналов, в частности <code>SIGIO</code>, <code>SIGPOLL</code> и <code>SIGURG</code>, требуются дополнительные действия со стороны процесса.</p>
      <p>2. Мы можем <emphasis>игнорировать</emphasis> сигнал, если действие задать как <code>SIG_IGN</code>. Сигналы <code>SIGKILL</code> и <code>SIGSTOP</code> не могут быть проигнорированы.</p>
      <p>3. Мы можем установить действие для сигнала <emphasis>по умолчанию</emphasis>, задав его как <code>SIG_DFL</code>. Действие сигнала по умолчанию обычно заключается в завершении процесса по получении сигнала, а некоторые сигналы генерируют копию области памяти процесса в его текущем каталоге (так называемый <emphasis>дамп</emphasis> — <emphasis>core dump</emphasis>). Есть несколько сигналов, для которых действием по умолчанию является игнорирование. Например, <code>SIGCHLD</code> и <code>SIGURG</code> (посылается по получении внеполосных данных, см. главу 24) — это два сигнала, игнорируемых по умолчанию, с которыми мы встретимся в тексте.</p>
     </section>
     <section>
      <title>
       <p>Функция signal</p>
      </title>
      <p>Согласно POSIX, чтобы определить действие для сигнала, нужно вызвать функцию <code>sigaction</code>. Однако это достаточно сложно, поскольку один аргумент этой функции — это структура, для которой необходимо выделение памяти и заполнение. Поэтому проще задать действие сигнала с помощью функции <code>signal</code>. Первый ее аргумент — это имя сигнала, а второй — либо указатель на функцию, либо одна из констант <code>SIG_IGN</code> и <code>SIG_DFL</code>. Но функция <code>signal</code> существовала еще до появления POSIX.1, и ее различные реализации имеют разную семантику сигналов с целью обеспечения обратной совместимости. В то же время POSIX четко диктует семантику при вызове функции <code>sigaction</code>. Это обеспечивает простой интерфейс с соблюдением семантики POSIX. Мы включили эту функцию в нашу собственную библиотеку вместе функциями <code>err_<emphasis>XXX</emphasis></code> и функциями-обертками, которые мы используем для построения всех наших программ. Она представлена в листинге 5.5. Функция-обертка <code>Signal</code> здесь не показана, потому что ее вид не зависит от того, какую именно функцию <code>signal</code> она должна вызывать.</p>
      <p><strong>Листинг 5.5</strong>. Функция signal, вызывающая функцию POSIX sigaction</p>
      <p><code>//lib/signal.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 Sigfunc*</code></p>
      <p><code> 3 signal(int signo, Sigfunc *func)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  struct sigaction act, oact;</code></p>
      <empty-line/>
      <p><code> 6  act.sa_handler = func;</code></p>
      <p><code> 7  sigemptyset(&amp;act.sa_mask);</code></p>
      <p><code> 8  act.sa_flags = 0;</code></p>
      <p><code> 9  if (signo == SIGALRM) {</code></p>
      <p><code>10 #ifdef SA_INTERRUPT</code></p>
      <p><code>11   act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */</code></p>
      <p><code>12 #endif</code></p>
      <p><code>13  } else {</code></p>
      <p><code>14 #ifdef SA_RESTART</code></p>
      <p><code>15   act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */</code></p>
      <p><code>16 #endif</code></p>
      <p><code>17  }</code></p>
      <p><code>18  if (sigaction(signo, &amp;act, &amp;oact) &lt; 0)</code></p>
      <p><code>19   return (SIG_ERR);</code></p>
      <p><code>20  return (oact.sa_handler);</code></p>
      <p><code>21 }</code></p>
      <subtitle>Упрощение прототипа функции при использовании typedef</subtitle>
      <p><code>2-3</code> Обычный прототип для функции <code>signal</code> усложняется наличием вложенных скобок:</p>
      <p><code>void (*signal(int <emphasis>signo</emphasis>, void (*<emphasis>func</emphasis>)(int)))(int);</code></p>
      <p>Чтобы упростить эту запись, мы определяем тип <code>Sigfunc</code> в нашем заголовочном файле <code>unp.h</code> следующим образом:</p>
      <p><code>typedef void Sigfunc(int);</code></p>
      <p>указывая тем самым, что обработчики сигналов — это функции с целочисленным аргументом, ничего не возвращающие (<code>void</code>). Тогда прототип функции выглядит следующим образом:</p>
      <p><code>Sigfunc *signal(int <emphasis>signo</emphasis>, Sigfunc *<emphasis>func</emphasis>);</code></p>
      <p>Указатель на функцию, являющуюся обработчиком сигнала, — это второй аргумент функции и в то же время возвращаемое функцией значение.</p>
      <subtitle>Установка обработчика</subtitle>
      <p><code>6</code> Элемент <code>sa_handler</code> структуры <code>sigaction</code> устанавливается равным аргументу <code>func</code> функции <code>signal</code>.</p>
      <subtitle>Установка маски сигнала для обработчика</subtitle>
      <p><code>7</code> POSIX позволяет нам задавать набор сигналов, которые будут <emphasis>блокированы</emphasis> при вызове обработчика сигналов. Любой блокируемый сигнал не может быть доставлен процессу. Мы устанавливаем элемент <code>sa_mask</code> равным пустому набору. Это означает, что во время работы обработчика дополнительные сигналы не блокируются. POSIX гарантирует, что перехватываемый сигнал всегда блокирован, пока выполняется его обработчик.</p>
      <subtitle>Установка флага SA_RESTART</subtitle>
      <p><code>8-17</code> Флаг <code>SA_RESTART</code> не является обязательным, и если он установлен, то системный вызов, прерываемый этим сигналом, будет автоматически снова выполнен ядром. (В продолжении нашего примера мы более подробно поговорим о прерванных системных вызовах.) Если перехватываемый сигнал не является сигналом <code>SIGALRM</code>, мы задаем флаг <code>SA_RESTART</code>, если таковой определен. (Причина, по которой сигнал <code>SIGALRM</code> обрабатывается отдельно, состоит в том, что обычно цель его генерации - ввести ограничение по времени в операцию ввода-вывода, как показано в листинге 14.2. В этом случае мы хотим, чтобы блокированный системный вызов был прерван сигналом.) Более ранние системы, особенно SunOS 4.x, автоматически перезапускают прерванный системный вызов по умолчанию и затем определяют флаг <code>SA_INTERRUPT</code>. Если этот флаг задан, мы устанавливаем его при перехвате сигнала <code>SIGALRM</code>.</p>
      <subtitle>Вызов функции sigaction</subtitle>
      <p><code>18-20</code> Мы вызываем функцию <code>sigaction</code>, а затем возвращаем старое действие сигнала как результат функции <code>signal</code>.</p>
      <p>В книге мы везде используем функцию <code>signal</code> из листинга 5.5.</p>
     </section>
     <section>
      <title>
       <p>Семантика сигналов POSIX</p>
      </title>
      <p>Сведем воедино следующие моменты, относящиеся к обработке сигналов в системе, совместимой с POSIX.</p>
      <p>&#9632; Однажды установленный обработчик сигналов остается установленным (в более ранних системах обработчик сигналов удалялся каждый раз по выполнении).</p>
      <p>&#9632; На время выполнения функции — обработчика сигнала доставляемый сигнал блокируется. Более того, любые дополнительные сигналы, заданные в наборе сигналов <code>sa_mask</code>, переданном функции <code>sigaction</code> при установке обработчика, также блокируются. В листинге 5.5 мы устанавливаем <code>sa_mask</code> равным пустому набору, что означает, что никакие сигналы, кроме перехватываемого, не блокируются.</p>
      <p>&#9632; Если сигнал генерируется один или несколько раз, пока он блокирован, то обычно после разблокирования он доставляется только один раз, то есть по умолчанию сигналы Unix не <emphasis>устанавливаются в очередь</emphasis>. Пример мы рассмотрим в следующем разделе. Стандарт POSIX реального времени 1003.1b определяет набор <emphasis>надежных</emphasis> сигналов, которые помещаются в очередь, но в этой книге мы их не используем.</p>
      <p>&#9632; Существует возможность выборочного блокирования и разблокирования набора сигналов с помощью функции <code>sigprocmask</code>. Это позволяет нам защитить критическую область кода, не допуская перехватывания определенных сигналов во время ее выполнения.</p>
     </section>
    </section>
    <section>
     <title>
      <p>5.9. Обработка сигнала SIGCHLD</p>
     </title>
     <section>
      <p>Назначение состояния зомби — сохранить информацию о дочернем процессе, чтобы родительский процесс мог ее впоследствии получить. Эта информация включает идентификатор дочернего процесса, статус завершения и данные об использовании ресурсов (время процессора, память и т.д.). Если у завершающегося процесса есть дочерний процесс в зомбированном состоянии, идентификатору родительского процесса всех зомбированных дочерних процессов присваивается значение 1 (процесс <code>init</code>), что позволяет унаследовать дочерние процессы и сбросить их (то есть процесс <code>init</code> будет ждать (<code>wait</code>) их завершения, благодаря чему будут удалены зомби). Некоторые системы Unix в столбце <code>COMMAND</code> выводят для зомбированных процессов значение <code>&lt;defunct&gt;</code>.</p>
     </section>
     <section>
      <title>
       <p>Обработка зомбированных процессов</p>
      </title>
      <p>Очевидно, что нам не хотелось бы оставлять процессы в виде зомби. Они занимают место в ядре, и в конце концов у нас может не остаться идентификаторов для нормальных процессов. Когда мы выполняем функцию <code>fork</code> для дочерних процессов, необходимо с помощью функции <code>wait</code> дождаться их завершения, чтобы они не превратились в зомби. Для этого мы устанавливаем обработчик сигналов для перехватывания сигнала <code>SIGCHLD</code> и внутри обработчика вызываем функцию <code>wait</code>. (Функции <code>wait</code> и <code>waitpid</code> мы опишем в разделе 5.10.) Обработчик сигналов мы устанавливаем с помощью вызова функции</p>
      <p><code>Signal(SIGCHLD, sig_chld);</code></p>
      <p>в листинге 5.1, после вызова функции <code>listen</code>. (Необходимо сделать это до вызова функции <code>fork</code> для первого дочернего процесса, причем только один раз.) Затем мы определяем обработчик сигнала — функцию <code>sig_chld</code>, представленную в листинге 5.6.</p>
      <p><strong>Листинг 5.6</strong>. Версия обработчика сигнала SIGCHLD, вызывающая функцию wait (усовершенствованная версия находится в листинге 5.8)</p>
      <p><code>//tcpcliserv/sigchldwait.с</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 sig_chld(int signo)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  pid_t pid;</code></p>
      <p><code> 6  int stat;</code></p>
      <empty-line/>
      <p><code> 7  pid = wait(&amp;stat);</code></p>
      <p><code> 8  printf("child terrmnated\n", pid);</code></p>
      <p><code> 9  return;</code></p>
      <p><code>10 }</code></p>
      <cite>
       <subtitle>ВНИМАНИЕ</subtitle>
       <p>В обработчике сигналов не рекомендуется вызов стандартных функций ввода-вывода, таких как printf, по причинам, изложенным в разделе 11.18. В данном случае мы вызываем функцию printf как средство диагностики, чтобы увидеть, когда завершается дочерний процесс.</p>
       <p>В системах System V и Unix 98 дочерний процесс не становится зомби, если процесс задает действие SIG_IGN для SIGCHLD. К сожалению, это верно только для System V и Unix 98. В POSIX прямо сказано, что такое поведение этим стандартом не предусмотрено. Переносимый способ обработки зомби состоит в том, чтобы перехватывать сигнал SIGCHLD и вызывать функцию wait или waitpid.</p>
      </cite>
      <p>Если мы откомпилируем в Solaris 9 программу, представленную в листинге 5.1, вызывая функцию <code>Signal</code> с нашим обработчиком <code>sig_chld</code>, и будем использовать функцию <code>signal</code> из системной библиотеки (вместо нашей версии, показанной в листинге 5.5), то получим следующее:</p>
      <p><code>solaris % <strong>tcpserv02 &amp;</strong> <emphasis>запускаем сервер в фоновом режиме</emphasis></code></p>
      <p><code>[2] 16939</code></p>
      <p><code>solaris % <strong>tcpcli01 127.0.0.1</strong> <emphasis>затем клиент</emphasis></code></p>
      <p><code><strong>hi there </strong><emphasis>набираем эту строку</emphasis></code></p>
      <p><code>hi there <emphasis>и она отражается сервером</emphasis></code></p>
      <p><code><strong>^D       </strong><emphasis>вводим символ конца файла</emphasis></code></p>
      <p><code>child 16942 terminated <emphasis>функция printf из обработчика сигнала выводит эту строку</emphasis></code></p>
      <p><code>accept error: Interrupted system call <emphasis>но функция main преждевременно прекращает выполнение</emphasis></code></p>
      <p>Последовательность шагов в этом примере такова:</p>
      <p>1. Мы завершаем работу клиента, вводя символ EOF. TCP клиента посылает сегмент FIN серверу, и сервер отвечает сегментом ACK.</p>
      <p>2. Получение сегмента FIN доставляет EOF ожидающей функции <code>readline</code> дочернего процесса. Дочерний процесс завершается.</p>
      <p>3. Родительский процесс блокирован в вызове функции <code>accept</code>, когда доставляется сигнал <code>SIGCHLD</code>. Функция <code>sig_chld</code> (наш обработчик сигнала) выполняется, функция <code>wait</code> получает PID дочернего процесса и статус завершения, после чего из обработчика сигнала вызывается функция <code>printf</code>. Обработчик сигнала возвращает управление.</p>
      <p>4. Поскольку сигнал был перехвачен родительским процессом, в то время как родительский процесс был блокирован в <emphasis>медленном</emphasis> (см. ниже) системном вызове (функция <code>accept</code>), ядро заставляет функцию <code>accept</code> возвратить ошибку <code>EINTR</code> (прерванный системный вызов). Родительский процесс не обрабатывает эту ошибку корректно (см. листинг 5.1), поэтому функция <code>main</code> преждевременно завершается.</p>
      <p>Цель данного примера — показать, что при написании сетевых программ, перехватывающих сигналы, необходимо получать информацию о прерванных системных вызовах и обрабатывать их. В этом специфичном для Solaris 2.5 примере функция <code>signal</code> из стандартной библиотеки С не осуществляет автоматический перезапуск прерванного вызова, то есть флаг <code>SA_RESTART</code>, установленный нами в листинге 5.5, не устанавливается функцией signal из системной библиотеки. Некоторые другие системы автоматически перезапускают прерванный системный вызов. Если мы запустим тот же пример в 4.4BSD, используя ее библиотечную версию функции <code>signal</code>, ядро перезапустит прерванный системный вызов и функция <code>accept</code> не возвратит ошибки. Одна из причин, по которой мы определяем нашу собственную версию функции <code>signal</code> и используем ее далее, — решение этой потенциальной проблемы, возникающей в различных операционных системах (см. листинг 5.5).</p>
      <p>Кроме того, мы всегда программируем явную функцию <code>return</code> для наших обработчиков сигналов (см. листинг 5.6), даже если функция ничего не возвращает (<code>void</code>), чтобы этот оператор напоминал нам о возможности прерывания системного вызова при возврате из обработчика.</p>
     </section>
     <section>
      <title>
       <p>Обработка прерванных системных вызовов</p>
      </title>
      <p>Термином <emphasis>медленный системный вызов</emphasis> (<emphasis>slow system call</emphasis>), введенным при описании функции <code>accept</code>, мы будем обозначать любой системный вызов, который может быть заблокирован навсегда. Такой системный вызов может никогда не завершиться. В эту категорию попадает большинство сетевых функций. Например, нет никакой гарантии, что вызов функции <code>accept</code> сервером когда-нибудь будет завершен, если нет клиентов, которые соединятся с сервером. Аналогично, вызов нашим сервером функции <code>read</code> (из <code>readline</code>) в листинге 5.2 никогда не возвратит управление, если клиент никогда не пошлет серверу строку для отражения. Другие примеры медленных системных вызовов — чтение и запись в случае программных каналов и терминальных устройств. Важным исключением является дисковый ввод-вывод, который обычно завершается возвращением управления вызвавшему процессу (в предположении, что не происходит фатальных аппаратных ошибок).</p>
      <p>Основное применяемое здесь правило связано с тем, что когда процесс, блокированный в медленном системном вызове, перехватывает сигнал, а затем обработчик сигналов завершает работу, системный вызов <emphasis>может</emphasis> возвратить ошибку <code>EINTR</code>. <emphasis>Некоторые</emphasis> ядра автоматически перезапускают <emphasis>некоторые</emphasis> прерванные системные вызовы. Для обеспечения переносимости программ, перехватывающих сигналы (большинство параллельных серверов перехватывает сигналы SIGCHLD), следует учесть, что медленный системный вызов может возвратить ошибку EINTR. Проблемы переносимости связаны с написанными выше словами «<emphasis>могут</emphasis>» и «<emphasis>некоторые</emphasis>» и тем фактом, что поддержка флага POSIX <code>SA_RESTART</code> не является обязательной. Даже если реализация поддерживает флаг <code>SA_RESTART</code>, не все прерванные системные вызовы могут автоматически перезапуститься. Например, большинство реализаций, происходящих от Беркли, никогда автоматически не перезапускают функцию <code>select</code>, а некоторые из этих реализаций никогда не перезапускают функции <code>accept</code> и <code>recvfrom</code>.</p>
      <p>Чтобы обработать прерванный вызов функции <code>accept</code>, мы изменяем вызов функции <code>accept</code>, приведенной в листинге 5.1, в начале цикла <code>for</code> следующим образом:</p>
      <p><code>for (;;) {</code></p>
      <p><code> clilen = sizeof(cliaddr);</code></p>
      <p><code> if ((connfd = accept(listenfd, (SA*)&amp;cliaddr, &amp;clilen)) &lt; 0) {</code></p>
      <p><code>  if (errno == EINTR)</code></p>
      <p><code>   continue; /* назад в for() */</code></p>
      <p><code>  else</code></p>
      <p><code>   err_sys("accept error");</code></p>
      <p><code> }</code></p>
      <p>Обратите внимание, что мы вызываем функцию <code>accept</code>, а не функцию-обертку <code>Accept</code>, поскольку мы должны обработать неудачное выполнение функции самостоятельно.</p>
      <p>В этой части кода мы сами перезапускаем прерванный системный вызов. Это допустимо для функции <code>accept</code> и таких функций, как <code>read</code>, <code>write</code>, <code>select</code> и <code>open</code>. Но есть функция, которую мы не можем перезапустить самостоятельно, — это функция <code>connect</code>. Если она возвращает ошибку <code>EINTR</code>, мы не можем снова вызвать ее, поскольку в этом случае немедленно возвратится еще одна ошибка. Когда функция connect прерывается перехваченным сигналом и не перезапускается автоматически, нужно вызвать функцию <code>select</code>, чтобы дождаться завершения соединения (см. раздел 16.3).</p>
     </section>
    </section>
    <section>
     <title>
      <p>5.10. Функции wait и waitpid</p>
     </title>
     <section>
      <p>В листинге 5.7 мы вызываем функцию <code>wait</code> для обработки завершенного дочернего процесса.</p>
      <p><code>#include &lt;sys/wait.h&gt;</code></p>
      <empty-line/>
      <p><code>pid_t wait(int *<emphasis>statloc</emphasis>);</code></p>
      <p><code>pid_t waitpid(pid_t <emphasis>pid</emphasis>, int *<emphasis>statloc</emphasis>, int <emphasis>options</emphasis>);</code></p>
      <p><code><emphasis>Обе функции возвращают ID процесса в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
      <p>Обе функции, и <code>wait</code>, и <code>waitpid</code>, возвращают два значения. Возвращаемое значение каждой из этих функций — это идентификатор завершенного дочернего процесса, а через указатель <code>statloc</code> передается статус завершения дочернего процесса (целое число). Для проверки статуса завершения можно вызвать три макроса, которые сообщают нам, что произошло с дочерним процессом: дочерний процесс завершен нормально, уничтожен сигналом или только приостановлен программой управления заданиями (job-control). Дополнительные макросы позволяют получить состояние выхода дочернего процесса, а также значение сигнала, уничтожившего или остановившего процесс. В листинге 15.8 мы используем макроопределения <code>WIFEXITED</code> и <code>WEXITSTATUS</code>.</p>
      <p>Если у процесса, вызывающего функцию <code>wait</code>, нет завершенных дочерних процессов, но есть один или несколько выполняющихся, функция <code>wait</code> блокируется до тех пор, пока первый из дочерних процессов не завершится.</p>
      <p>Функция <code>waitpid</code> предоставляет более гибкие возможности выбора ожидаемого процесса и его блокирования. Прежде всего, в аргументе <code>pid</code> задается идентификатор процесса, который мы будем ожидать. Значение -1 говорит о том, что нужно дождаться завершения первого дочернего процесса. (Существуют и другие значения идентификаторов процесса, но здесь они нам не понадобятся.) Аргумент <code>options</code> позволяет задавать дополнительные параметры. Наиболее общеупотребительным является параметр <code>WNOHANG</code>: он сообщает ядру, что не нужно выполнять блокирование, если нет завершенных дочерних процессов.</p>
     </section>
     <section>
      <title>
       <p>Различия между функциями wait и waitpid</p>
      </title>
      <p>Теперь мы проиллюстрируем разницу между функциями <code>wait</code> и <code>waitpid</code>, используемыми для сброса завершенных дочерних процессов. Для этого мы изменим код нашего клиента TCP так, как показано в листинге 5.7. Клиент устанавливает пять соединений с сервером, а затем использует первое из них (<code>sockfd[0]</code>) в вызове функции <code>str_cli</code>. Несколько соединений мы устанавливаем для того, чтобы породить от параллельного сервера множество дочерних процессов, как показано на рис. 5.2.</p>
      <image l:href="#img_40.png"/>
      <p><strong>Рис. 5.2</strong>. Клиент, установивший пять соединений с одним и тем же параллельным сервером</p>
      <p><strong>Листинг 5.7</strong>. Клиент TCP, устанавливающий пять соединений с сервером</p>
      <p>/<code>/tcpcliserv/tcpcli04.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int i, sockfd[5];</code></p>
      <p><code> 6  struct sockaddr_in servaddr;</code></p>
      <empty-line/>
      <p><code> 7  if (argc != 2)</code></p>
      <p><code> 8   err_quit("usage: tcpcli &lt;Ipaddress&gt;");</code></p>
      <empty-line/>
      <p><code> 9  for (i = 0; i &lt; 5; i++) {</code></p>
      <p><code>10   sockfd[i] = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
      <empty-line/>
      <p><code>11   bzero(&amp;servaddr, sizeof(servaddr));</code></p>
      <p><code>12   servaddr.sin_family = AF_INET;</code></p>
      <p><code>13   servaddr.sin_port = htons(SERV_PORT);</code></p>
      <p><code>14   Inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr);</code></p>
      <empty-line/>
      <p><code>15   Connect(sockfd[i], (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
      <p><code>16  }</code></p>
      <empty-line/>
      <p><code>17  str_cli(stdin, sockfd[0]); /* эта функция выполняет все необходимые</code></p>
      <p><code>                               действия для формирования запроса клиента */</code></p>
      <empty-line/>
      <p><code>18  exit(0);</code></p>
      <p><code>19 }</code></p>
      <p>Когда клиент завершает работу, все открытые дескрипторы автоматически закрываются ядром (мы не вызываем функцию close<code>,</code> а пользуемся только функцией <code>exit</code>) и все пять соединений завершаются приблизительно в одно и то же время. Это вызывает отправку пяти сегментов FIN, по одному на каждое соединение, что, в свою очередь, вызывает примерно одновременное завершение всех пяти дочерних процессов. Это приводит к доставке пяти сигналов <code>SIGCHLD</code> практически в один и тот же момент, что показано на рис. 5.3.</p>
      <p>Доставка множества экземпляров одного и того же сигнала вызывает проблему, к рассмотрению которой мы и приступим.</p>
      <image l:href="#img_41.png"/>
      <p><strong>Рис. 5.3</strong>. Клиент завершает работу, закрывая все пять соединений и завершая все пять дочерних процессов</p>
      <p>Сначала мы запускаем сервер в фоновом режиме, а затем — новый клиент. Наш сервер, показанный в листинге 5.1, несколько модифицирован — теперь в нем вызывается функция <code>signal</code> для установки обработчика сигнала <code>SIGCHLD</code>, приведенного в листинге 5.6.</p>
      <p><code>linux % <strong>tcpserv03 &amp;</strong></code></p>
      <p><code>[1] 20419</code></p>
      <p><code>linux % <strong>tcpcli04 206.62.226.35</strong></code></p>
      <p><code><strong>hello </strong><emphasis>мы набираем эту строку</emphasis></code></p>
      <p><code>hello <emphasis>и она отражается сервером</emphasis></code></p>
      <p><code><strong>^D</strong>    <emphasis>мы набираем символ конца файла</emphasis></code></p>
      <p><code>child 20426 terminated <emphasis>выводится сервером</emphasis></code></p>
      <p>Первое, что мы можем заметить, — данные выводит только одна функция <code>printf</code>, хотя мы предполагаем, что все пять дочерних процессов должны завершиться. Если мы выполним программу <code>ps</code>, то увидим, что другие четыре дочерних процесса все еще существуют как зомби.</p>
      <p><code>PID TTY TIME CMD</code></p>
      <p><code>20419 pts/6 00:00:00 tcpserv03</code></p>
      <p><code>20421 pts/6 00:00:00 tcpserv03 &lt;defunct&gt;</code></p>
      <p><code>20422 pts/6 00:00:00 tcpserv03 &lt;defunct&gt;</code></p>
      <p><code>20423 pts/6 00:00:00 tcpserv03 &lt;defunct&gt;</code></p>
      <p>Установки обработчика сигнала и вызова функции <code>wait</code> из этого обработчика недостаточно для предупреждения появления зомби. Проблема состоит в том, что все пять сигналов генерируются до того, как выполняется обработчик сигнала, и вызывается он только один раз, поскольку сигналы Unix обычно не помещаются в очередь. Более того, эта проблема является недетерминированной. В приведенном примере с клиентом и сервером на одном и том же узле обработчик сигнала выполняется один раз, оставляя четыре зомби. Но если мы запустим клиент и сервер на разных узлах, то обработчик сигналов, скорее всего, выполнится дважды: один раз в результате генерации первого сигнала, а поскольку другие четыре сигнала приходят во время выполнения обработчика, он вызывается повторно только один раз. При этом остаются три зомби. Но иногда в зависимости от точного времени получения сегментов FIN на узле сервера обработчик сигналов может выполниться три или даже четыре раза.</p>
      <p>Правильным решением будет вызвать функцию <code>waitpid</code> вместо <code>wait</code>. В листинге 5.8 представлена версия нашей функции <code>sigchld</code>, корректно обрабатывающая сигнал <code>SIGCHLD</code>. Эта версия работает, потому что мы вызываем функцию <code>waitpid</code> в цикле, получая состояние любого из дочерних процессов, которые завершились. Необходимо задать параметр <code>WNOHANG</code>: это указывает функции <code>waitpid</code>, что не нужно блокироваться, если существуют выполняемые дочерние процессы, которые еще не завершились. В листинге 5.6 мы не могли вызвать функцию <code>wait</code> в цикле, поскольку нет возможности предотвратить блокирование функции <code>wait</code> при наличии выполняемых дочерних процессов, которые еще не завершились.</p>
      <p>В листинге 5.9 показана окончательная версия нашего сервера. Он корректно обрабатывает возвращение ошибки <code>EINTR</code> из функции <code>accept</code> и устанавливает обработчик сигнала (листинг 5.8), который вызывает функцию <code>waitpid</code> для всех завершенных дочерних процессов.</p>
      <p><strong>Листинг 5.8</strong>. Окончательная (корректная) версия функции sig_chld, вызывающая функцию waitpid</p>
      <p><code>//tcpcliserv/sigchldwaitpid.c</code></p>
      <empty-line/>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 sig_chld(int signo)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  pid_t pid;</code></p>
      <p><code> 6  int stat;</code></p>
      <empty-line/>
      <p><code> 7  while ((pid = waitpid(-1, &amp;stat, WNOHANG)) &gt;0)</code></p>
      <p><code> 8   printf("child %d terminated\n", pid);</code></p>
      <p><code> 9  return;</code></p>
      <p><code>10 }</code></p>
      <p><strong>Листинг 5.9</strong>. Окончательная (корректная) версия TCP-сервера, обрабатывающего ошибку EINTR функции accept</p>
      <p><code>//tcpcliserv/tcpserv04.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int listenfd, connfd;</code></p>
      <p><code> 6  pid_t childpid;</code></p>
      <p><code> 7  socklen_t clilen;</code></p>
      <p><code> 8  struct sockaddr_in cliaddr, servaddr;</code></p>
      <p><code> 9  void sig_chld(int);</code></p>
      <empty-line/>
      <p><code>10  listenfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
      <empty-line/>
      <p><code>11  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
      <p><code>12  servaddr.sin_family = AF_INET;</code></p>
      <p><code>13  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
      <p><code>14  servaddr.sin_port = htons(SERV_PORT);</code></p>
      <empty-line/>
      <p><code>15  Bind(listenfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
      <empty-line/>
      <p><code>16  Listen(listenfd, LISTENQ);</code></p>
      <p><code>17  Signal(SIGCHLD, sig_chld); /* нужно вызвать waitpid() */</code></p>
      <empty-line/>
      <p><code>18  for (;;) {</code></p>
      <p><code>19   clilen = sizeof(cliaddr);</code></p>
      <p><code>20   if ((connfd = accept(listenfd, (SA*)&amp;cliaddr, &amp;clilen)) &lt; 0) {</code></p>
      <p><code>21    if (errno == EINTR)</code></p>
      <p><code>22     continue; /* назад к for() */</code></p>
      <p><code>23    else</code></p>
      <p><code>24     err_sys("accept error");</code></p>
      <p><code>25   }</code></p>
      <p><code>26   if ((childpid = Fork()) == 0) { /* дочерний процесс */</code></p>
      <p><code>27    Close(listenfd); /* закрываем прослушиваемый сокет */</code></p>
      <p><code>28    str_echo(connfd); /* обрабатываем запрос */</code></p>
      <p><code>29    exit(0);</code></p>
      <p><code>30   }</code></p>
      <p><code>31   Close(connfd); /* родитель закрывает присоединенный сокет */</code></p>
      <p><code>32  }</code></p>
      <p><code>33 }</code></p>
      <p>Целью этого раздела было продемонстрировать три сценария, которые могут встретиться в сетевом программировании.</p>
      <p>1. При выполнении функции <code>fork</code>, порождающей дочерние процессы, следует перехватывать сигнал <code>SIGCHLD</code>.</p>
      <p>2. При перехватывании сигналов мы должны обрабатывать прерванные системные вызовы.</p>
      <p>3. Обработчик сигналов <code>SIGCHLD</code> должен быть создан корректно с использованием функции <code>waitpid</code>, чтобы не допустить появления зомби.</p>
      <p>Окончательная версия нашего сервера TCP (см. листинг 5.9) вместе с обработчиком сигналов <code>SIGCHLD</code> в листинге 5.8 обрабатывает все три сценария.</p>
     </section>
    </section>
    <section>
     <title>
      <p>5.11. Прерывание соединения перед завершением функции accept</p>
     </title>
     <p>Существует другое условие, аналогичное прерванному системному вызову, пример которого был описан в предыдущем разделе. Оно может привести к возвращению функцией <code>accept</code> нефатальной ошибки, в случае чего следует заново вызвать функцию <code>accept</code>. Последовательность пакетов, показанная на рис. 5.4, встречается на загруженных серверах (эта последовательность типична для загруженных веб-серверов).</p>
     <image l:href="#img_42.png"/>
     <p><strong>Рис. 5.4</strong>. Получение сегмента RST для состояния соединения ESTABLISHED перед вызовом функции accept</p>
     <p>Трехэтапное рукопожатие TCP завершается, устанавливается соединение, а затем TCP клиента посылает сегмент RST. На стороне сервера соединение ставится в очередь в ожидании вызова функции <code>accept</code>, и в это время сервер получает сегмент RST. Спустя некоторое время процесс сервера вызывает функцию <code>accept</code>.</p>
     <p>К сожалению, принцип обработки прерванного соединения зависит от реализации. Реализации, происходящие от Беркли, обрабатывают прерванное соединение полностью внутри ядра, и сервер никогда не узнает об этом. Большинство реализаций SVR4, однако, возвращают процессу ошибку, и эта ошибка зависит от реализации. При этом переменная errno принимает значение <code>EPROTO</code> (ошибка протокола), хотя в POSIX указано, что должна возвращаться ошибка <code>ECONNABORTED</code> (прерывание соединения). POSIX определяет эту ошибку иначе, так как ошибка <code>EPROTO</code> возвращается еще и в том случае, когда в подсистеме потоков происходят какие-либо фатальные события, имеющие отношение к протоколу. Возвращение той же ошибки для нефатального прерывания установленного соединения клиентом приводит к тому, что сервер не знает, вызывать снова функцию <code>accept</code> или нет. В случае ошибки <code>ECONNABORTED</code> сервер может игнорировать ошибку и снова вызывать функцию accept.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Этот сценарий очень просто имитировать. Запустите сервер, который должен вызвать функции socket, bind и listen, а затем перед вызовом функции accept переведите сервер на короткое время в состояние ожидания. Пока процесс сервера находится в состоянии ожидания, запустите клиент, который вызовет функции socket и connect. Как только функция connect завершится, установите параметр сокета SO_LINGER, чтобы сгенерировать сегмент RST (который мы описываем в разделе 7.5 и демонстрируем в листинге 16.14), и завершите процессы.</p>
     </cite>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В [128] описана обработка этой ошибки в Беркли-ядрах (Berkeley-derived kernels), которые никогда не передают ее процессу. Обработка RST с вызовом функции tcp_close представлена в [128, с. 964]. Эта функция вызывает функцию in_pcbdetach [128, с. 897], которая, в свою очередь, вызывает функцию sofree [128, с. 719]. Функция sofree [128, с. 473] обнаруживает, что сокет все еще находится в очереди полностью установленных соединений прослушиваемого сокета. Она удаляет этот сокет из очереди и освобождает сокет. Когда сервер, наконец, вызовет функцию accept, он не сможет узнать, что установленное соединение было удалено из очереди.</p>
     </cite>
     <p>Мы вернемся к подобным прерванным соединениям в разделе 16.6 и покажем, какие проблемы они могут порождать совместно с функцией <code>select</code> и прослушиваемым сокетом в нормальном режиме блокирования.</p>
    </section>
    <section>
     <title>
      <p>5.12. Завершение процесса сервера</p>
     </title>
     <p>Теперь мы запустим соединение клиент-сервер и уничтожим дочерний процесс сервера. Это симулирует сбой процесса сервера, благодаря чему мы сможем выяснить, что происходит с клиентом в подобных ситуациях. (Следует точно различать сбой <emphasis>процесса</emphasis> сервера, который мы рассмотрим здесь, и сбой на самом узле сервера, о котором речь пойдет в разделе 5.14.) События развиваются так:</p>
     <p>1. Мы запускаем сервер и клиент на разных узлах и вводим на стороне клиента одну строку, чтобы проверить, все ли в порядке. Строка отражается дочерним процессом сервера.</p>
     <p>2. Мы находим идентификатор дочернего процесса сервера и уничтожаем его с помощью программы <code>kill</code>. Одним из этапов завершения процесса является закрытие всех открытых дескрипторов в дочернем процессе. Это вызывает отправку сегмента FIN клиенту, и TCP клиента отвечает сегментом ACK. Это первая половина завершения соединения TCP.</p>
     <p>3. Родительскому процессу сервера посылается сигнал <code>SIGCHLD</code>, и он корректно обрабатывается (см. листинг 5.9).</p>
     <p>4. С клиентом ничего не происходит. TCP клиента получает от TCP сервера сегмент FIN и отвечает сегментом ACK, но проблема состоит в том, что клиентский процесс блокирован в вызове функции <code>fgets</code> в ожидании строки от терминала.</p>
     <p>5.  Запуск программы <code>netstat</code> на этом шаге из другого окна на стороне клиента показывает состояние клиентского сокета:</p>
     <p><code>linux % <strong>netstat -a | grep 9877</strong></code></p>
     <p><code>tcp 0 0 *:9877          *:*            LISTEN</code></p>
     <p><code>tcp 0 0 localhost:9877  localhost:9877 FIN_WAIT2</code></p>
     <p><code>tcp 1 0 localhost.43604 localhost:9877 CLOSE_WAIT</code></p>
     <p>Как видите, согласно рис. 2.4, осуществилась половина последовательности завершения соединения TCP.</p>
     <p>6. Мы можем снова ввести строку на стороне клиента. Вот что происходит на стороне клиента (начиная с шага 1):</p>
     <p><code>linux % <strong>tcpcli01 127.0.0.1</strong> <emphasis>запускаем клиент</emphasis></code></p>
     <p><code><strong>hello</strong> <emphasis>первая строка, которую мы ввели</emphasis></code></p>
     <p><code>hello <emphasis>она корректно отражается</emphasis></code></p>
     <p><code><emphasis> теперь мы уничтожаем (</emphasis>kill<emphasis>) дочерний процесс</emphasis></code></p>
     <p><code><emphasis> сервера на узле сервера</emphasis></code></p>
     <p><code><strong>another line</strong> <emphasis>затем мы вводим следующую строку на стороне клиента</emphasis></code></p>
     <p><code>str_cli: server terminated prematurely</code></p>
     <p>Когда мы вводим следующую строку, функция <code>str_cli</code> вызывает функцию <code>writen</code>, и TCP клиента отправляет данные серверу. TCP это допускает, поскольку получение сегмента FIN протоколом TCP клиента указывает только на то, что процесс сервера закрыл свой конец соединения и больше не будет отправлять данные. Получение сегмента FIN не сообщает протоколу TCP клиента, что процесс сервера завершился (хотя в данном случае он завершился). Мы вернемся к этому вопросу в разделе 6.6, когда будем говорить о половинном закрытии TCP.</p>
     <p>Когда TCP сервера получает данные от клиента, он отвечает сегментом RST, поскольку процесс, у которого был открытый сокет, завершился. Мы можем проверить, что этот сегмент RST отправлен, просмотрев пакеты с помощью программы <code>tcpdump</code>.</p>
     <p>7. Однако процесс клиента не увидит сегмента RST, поскольку он вызывает функцию <code>readline</code> сразу же после вызова функции <code>writen</code>, и <code>readline</code> сразу же возвращает 0 (признак конца файла) по причине того, что на шаге 2 был получен сегмент FIN. Наш клиент не предполагает получать признак конца файла на этом этапе (см. листинг 5.3), поэтому он завершает работу, сообщая об ошибке <code>Server terminated prematurely</code> (Сервер завершил работу преждевременно).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Этапы описанной последовательности также зависят от синхронизации времени. Вызов readline на стороне клиента может произойти до получения им пакета RST от сервера, но может произойти и после. Если readline вызывается до получения RST, происходит то, что мы описали выше (клиент считывает символ конца файла). Если же первым будет получен пакет RST, функция readline возвратит ошибку ECONNRESET (соединение сброшено собеседником).</p>
     </cite>
     <p>8. Когда клиент завершает работу (вызывая функцию <code>err_quit</code> в листинге 5.4), все его открытые дескрипторы закрываются.</p>
     <p>Проблема заключается в том, что клиент блокируется в вызове функции <code>fgets</code>, когда сегмент FIN приходит на сокет. Клиент в действительности работает с двумя дескрипторами — дескриптором сокета и дескриптором ввода пользователя, и поэтому он должен блокироваться при вводе из любого источника (сейчас в функции <code>str_cli</code> он блокируется при вводе только из одного источника). Обеспечить подобное блокирование — это одно из назначений функций <code>select</code> и <code>poll</code>, о которых рассказывается в главе 6. Когда в разделе 6.4 мы перепишем функцию <code>str_cli</code>, то как только мы уничтожим с помощью программы <code>kill</code> дочерний процесс сервера, клиенту будет отправлено уведомление о полученном сегменте FIN.</p>
    </section>
    <section>
     <title>
      <p>5.13. Сигнал SIGPIPE</p>
     </title>
     <p>Что происходит, если клиент игнорирует возвращение ошибки из функции <code>readline</code> и отсылает следующие данные серверу? Это может произойти, если, например, клиенту нужно выполнить две операции по отправке данных серверу перед считыванием данных от него, причем первая операция отправки данных вызывает RST.</p>
     <p>Применяется следующее правило: когда процесс производит запись в сокет, получивший сегмент RST, процессу посылается сигнал <code>SIGPIPE</code>. По умолчанию действием этого сигнала является завершение процесса, так что процесс должен перехватить сигнал, чтобы не произошло непроизвольного завершения.</p>
     <p>Если процесс либо перехватывает сигнал и возвращается из обработчика сигнала, либо игнорирует сигнал, то операция записи возвращает ошибку <code>EPIPE</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Часто задаваемым вопросом (FAQ) в Usenet является такой: как получить этот сигнал при первой, а не при второй операции записи? Это невозможно. Как следует из приведенных выше рассуждений, первая операция записи выявляет сегмент RST, а вторая — сигнал. Если запись в сокет, получивший сегмент FIN, допускается, то запись в сокет, получивший сегмент RST, является ошибочной.</p>
     </cite>
     <p>Чтобы увидеть, что происходит с сигналом <code>SIGPIPE</code>, изменим код нашего клиента так, как показано в листинге 5.10.</p>
     <p><strong>Листинг 5.10</strong>. Функция str_cli, дважды вызывающая функцию writen</p>
     <p><code>//tcpcliserv/str_cli11.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 str_cli(FILE *fp, int sockfd)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  char sendline[MAXLINE], recvline[MAXLINE];</code></p>
     <empty-line/>
     <p><code> 6  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
     <empty-line/>
     <p><code> 7   Writen(sockfd, sendline, 1);</code></p>
     <p><code> 8   sleep(1);</code></p>
     <p><code> 9   Writen(sockfd, sendline + 1, strlen(sendline) - 1);</code></p>
     <p><code>10   if (Readline(sockfd, recvline, MAXLINE) == 0)</code></p>
     <p><code>11    err_quit("str_cli, server terminated prematurely");</code></p>
     <empty-line/>
     <p><code>12   Fputs(recvline, stdout);</code></p>
     <p><code>13  }</code></p>
     <p><code>14 }</code></p>
     <p><code>7-9</code> Все изменения, которые мы внесли, — это повторный вызов функции <code>writen</code>: сначала в сокет записывается первый байт данных, за этим следует пауза в 1 с и далее идет запись остатка строки. Наша цель — выявить сегмент RST при первом вызове функции <code>writen</code> и генерировать сигнал <code>SIGPIPE</code> при втором вызове.</p>
     <p>Если мы запустим клиент на нашем узле Linux, мы получим:</p>
     <p><code>linux % <strong>tcpcli11 127.0.0.1</strong></code></p>
     <p><code><strong>hi there</strong>    <emphasis>мы вводим эту строку</emphasis></code></p>
     <p><code>hi there    <emphasis>и она отражается сервером</emphasis></code></p>
     <p><code><emphasis>здесь       мы завершаем дочерний процесс сервера</emphasis></code></p>
     <p><code><strong>bye</strong>         <emphasis>затем мы вводим эту строку</emphasis></code></p>
     <p><code>Broken pipe <emphasis>это сообщение выводится интерпретатором</emphasis></code></p>
     <p>Мы запускаем клиент, вводим одну строку, видим, что строка отражена корректно, и затем завершаем дочерний процесс сервера на узле сервера. Затем мы вводим другую строку (<code>bye</code>), но ничего не отражается, а интерпретатор сообщает нам о том, что процесс получил сигнал SIGPIPE. Некоторые интерпретаторы не выводят никаких сообщений, если процесс завершает работу без дампа памяти, но в нашем примере использовался интерпретатор <code>bash</code>, который берет на себя эту работу.</p>
     <p>Рекомендуемый способ обработки сигнала <code>SIGPIPE</code> зависит от того, что приложение собирается делать, когда получает этот сигнал. Если ничего особенного делать не нужно, проще всего установить действие <code>SIG_IGN</code>, предполагая, что последующие операции вывода перехватят ошибку <code>EPIPE</code> и завершатся. Если при появлении сигнала необходимо проделать специальные действия (возможно, запись в системный журнал), то сигнал следует перехватить и выполнить требуемые действия в обработчике сигнала. Однако отдавайте себе отчет в том, что если используется множество сокетов, то при доставке сигнала мы не получаем информации о том, на каком сокете произошла ошибка. Если нам нужно знать, какая именно операция <code>write</code> вызвала ошибку, следует либо игнорировать сигнал, либо вернуть управление из обработчика сигнала и обработать ошибку <code>EPIPE</code> из функции <code>write</code>.</p>
    </section>
    <section>
     <title>
      <p>5.14. Сбой на узле сервера</p>
     </title>
     <p>В следующем примере мы проследим за тем, что происходит в случае сбоя на узле сервера. Чтобы мы могли имитировать эту ситуацию, клиент и сервер должны работать на разных узлах. Мы запускаем сервер, запускаем клиент, вводим строку на стороне клиента для проверки работоспособности соединения, отсоединяем узел сервера от сети и вводим еще одну строку на стороне клиента. Этот сценарий охватывает также ситуацию, в которой узел сервера становится недоступен во время отправки данных клиентом (например, после того как соединение установлено, выключается некий промежуточный маршрутизатор).</p>
     <p>События развиваются следующим образом:</p>
     <p>1. Когда происходит сбой на узле сервера, по существующим сетевым соединениям от сервера не отправляется никакой информации. Мы считаем, что на узле происходит именно сбой, а не завершение работы компьютера оператором (что мы рассмотрим в разделе 5.16).</p>
     <p>2. Мы вводим строку на стороне клиента, она записывается с помощью функции <code>writen</code> (см. листинг 5.3) и отправляется протоколом TCP клиента как сегмент данных. Затем клиент блокируется в вызове функции <code>readline</code> в ожидании отраженного ответа.</p>
     <p>3. Если мы понаблюдаем за сетью с помощью программы <code>tcpdump</code>, то увидим, что TCP клиента последовательно осуществляет повторные передачи сегмента данных, пытаясь получить сегмент ACK от сервера. В разделе 25.11 [128] показан типичный образец повторных передач TCP: реализации, происходящие от Беркли, делают попытки передачи сегмента данных 12 раз, ожидая около 9 мин перед прекращением попыток. Когда TCP клиента наконец прекращает попытки ретрансляции (считая, что узел сервера за это время не перезагружался или что он все еще недоступен, если на узле сервера сбоя не было, но он был недоступен по сети), клиентскому процессу возвращается ошибка. Поскольку клиент блокирован в вызове функции <code>readline</code>, она и возвращает эту ошибку. Если на узле сервера произошел сбой, и на все сегменты данных клиента не было ответа, будет возвращена ошибка <code>ETIMEDOUT</code>. Но если некий промежуточный маршрутизатор определил, что узел сервера был недоступен, и ответил сообщением ICMP о недоступности получателя, клиент получит либо ошибку <code>EHOSTUNREACH</code>, либо ошибку <code>ENETUNREACH</code>.</p>
     <p>Хотя наш клиент в конце концов обнаруживает, что собеседник выключен или недоступен, бывает, что нужно определить это раньше, чем пройдут условленные девять минут. В таком случае следует поместить тайм-аут в вызов функции <code>readline</code>, о чем рассказывается в разделе 14.2.</p>
     <p>В описанном сценарии сбой на узле сервера можно обнаружить, только послав данные на этот узел. Если мы хотим обнаружить сбой на узле сервера, не посылая <emphasis>данные</emphasis>, требуется другая технология. Мы рассмотрим параметр сокета <code>SO_KEEPALIVE</code> в разделе 7.5.</p>
    </section>
    <section>
     <title>
      <p>5.15. Сбой и перезагрузка на узле сервера</p>
     </title>
     <p>В этом сценарии мы устанавливаем соединение между клиентом и сервером и затем считаем, что на узле сервера происходит сбой, после чего узел перезагружается. В предыдущем разделе узел сервера был выключен, когда мы отправляли ему данные. Здесь же перед отправкой данных серверу узел сервера перезагрузится. Простейший способ имитировать такую ситуацию — установить соединение, отсоединить сервер от сети, выключить узел сервера и перезагрузить его, а затем снова присоединить узел сервера к сети. Мы не хотим, чтобы клиент знал о завершении работы сервера (о такой ситуации речь пойдет в разделе 5.16).</p>
     <p>Как было сказано в предыдущем разделе, если клиент не посылает данные серверу, то он не узнает о произошедшем на узле сервера сбое. (При этом считается, что мы не используем параметр сокета <code>SO_KEEPALIVE</code>.) События развиваются следующим образом:</p>
     <p>1. Мы запускаем сервер, затем — клиент, и вводим строку для проверки установленного соединения. Получаем ответ сервера.</p>
     <p>2. Узел сервера выходит из строя и перезагружается.</p>
     <p>3. Мы вводим строку на стороне клиента, которая посылается как сегмент данных TCP на узел сервера.</p>
     <p>4. Когда узел сервера перезагружается после сбоя, его TCP теряет информацию о существовавших до сбоя соединениях. Следовательно, TCP сервера отвечает на полученный от клиента сегмент данных, посылая RST.</p>
     <p>5. Наш клиент блокирован в вызове функции <code>readline</code>, когда приходит сегмент RST, заставляющий функцию <code>readline</code> возвратить ошибку <code>ECONNRESET</code>.</p>
     <p>Если для нашего клиента важно диагностировать выход из строя узла сервера, даже если клиент активно не посылает данные, то требуется другая технология (с использованием параметра сокета <code>SO_KEEPALIVE</code> или некоторых функций, проверяющих наличие связи в клиент-серверном соединении).</p>
    </section>
    <section>
     <title>
      <p>5.16. Выключение узла сервера</p>
     </title>
     <p>В двух предыдущих разделах рассматривался выход из строя узла сервера или недоступность узла сервера в сети. Теперь мы рассмотрим, что происходит, если узел сервера выключается оператором в то время, когда на этом узле выполняется наш серверный процесс.</p>
     <p>Когда система Unix выключается, процесс <code>init</code> обычно посылает всем процессам сигнал <code>SIGTERM</code> (мы можем перехватить этот сигнал), ждет в течение некоторого фиксированного времени (часто от 5 до 20 с), а затем посылает сигнал <code>SIGKILL</code> (который мы перехватить не можем) всем еще выполняемым процессам. Это дает всем выполняемым процессам короткое время для завершения работы. Если мы не завершили выполнение процесса, это сделает сигнал <code>SIGKILL</code>. При завершении процесса закрываются все открытые дескрипторы, а затем мы проходим ту же последовательность шагов, что описывалась в разделе 5.12. Там же было отмечено, что в нашем клиенте следует использовать функцию <code>select</code> или <code>poll</code>, чтобы клиент определил завершение процесса сервера, как только оно произойдет.</p>
    </section>
    <section>
     <title>
      <p>5.17. Итоговый пример TCP</p>
     </title>
     <p>Прежде чем клиент и сервер TCP смогут взаимодействовать друг с другом, каждый из них должен определить пару сокетов для соединения: локальный IP-адрес, локальный порт, удаленный IP-адрес, удаленный порт. На рис. 5.5 мы схематически изображаем эти значения черными кружками. На этом рисунке ситуация представлена с точки зрения клиента. Удаленный IP-адрес и удаленный порт должны быть заданы клиентом при вызове функции <code>connect</code>. Два локальных значения обычно выбираются ядром тоже при вызове функции <code>connect</code>. У клиента есть выбор: он может задать только одно из локальных значений или оба, вызвав функцию <code>bind</code> перед вызовом функции <code>connect</code>, однако второй подход используется редко.</p>
     <image l:href="#img_43.png"/>
     <p><strong>Рис. 5.5</strong>. TCP-соединение клиент-сервер с точки зрения клиента</p>
     <p>Как мы отмечали в разделе 4.10, клиент может получить два локальных значения, выбранных ядром, вызвав функцию <code>getsockname</code> после установления соединения.</p>
     <p>На рис. 5.6 показаны те же четыре значения, но с точки зрения сервера.</p>
     <image l:href="#img_44.png"/>
     <p><strong>Рис. 5.6</strong>. TCP-соединение клиент-сервер с точки зрения сервера</p>
     <p>Локальный порт (заранее известный порт сервера) задается функцией <code>bind</code>. Обычно сервер также задает в этом вызове универсальный IP-адрес, хотя может и ограничиться получением соединений, предназначенных для одного определенного локального интерфейса путем связывания с IP-адресом, записанным без символов подстановки (то есть не универсального). Если сервер связывается с универсальным IP-адресом на узле с несколькими сетевыми интерфейсами, он может определить локальный IP-адрес (указываемый как адрес отправителя в исходящих пакетах) при помощи вызова функции <code>getsockname</code> после установления соединения (см. раздел 4.10). Два значения удаленного адреса возвращаются серверу при вызове функции accept. Как мы отмечали в разделе 4.10, если сервером, вызывающим функцию accept, выполняется с помощью функции exec другая программа, то эта программа может вызвать функцию <code>getpeername</code>, чтобы при необходимости определить IP-адрес и порт клиента.</p>
    </section>
    <section>
     <title>
      <p>5.18. Формат данных</p>
     </title>
     <section>
      <p>В нашем примере сервер никогда не исследует запрос, который он получает от клиента. Сервер лишь читает все данные, включая символ перевода строки, и отправляет их обратно клиенту, отслеживая только разделитель строк. Это исключение, а не правило, так как обычно необходимо принимать во внимание формат данных, которыми обмениваются клиент и сервер.</p>
     </section>
     <section>
      <title>
       <p>Пример: передача текстовых строк между клиентом и сервером</p>
      </title>
      <p>Изменим наш сервер так, чтобы он, по-прежнему принимая текстовую строку от клиента, предполагал, что строка содержит два целых числа, разделенных пробелом, и возвращал сумму этих чисел. Функции <code>main</code> наших клиента и сервера остаются прежними, как и функция <code>str_cli</code>. Меняется только функция <code>str_echo</code>, что мы показываем в листинге 5.11.</p>
      <p><strong>Листинг 5.11</strong>. Функция str_echo, суммирующая два числа</p>
      <p><code>//tcpcliserv/str_echo08.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 str_echo(int sockfd)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  long arg1, arg2;</code></p>
      <p><code> 6  ssize_t n;</code></p>
      <p><code> 7  char line[MAXLINE];</code></p>
      <empty-line/>
      <p><code> 8  for (;;) {</code></p>
      <p><code> 9   if ((n = Readline(sockfd, line, MAXLINE)) == 0)</code></p>
      <p><code>10    return; /* соединение закрывается удаленным концом */</code></p>
      <empty-line/>
      <p><code>11   if (sscanf(line, "%ld%ld", &amp;arg1, &amp;arg2) == 2)</code></p>
      <p><code>12    snprintf(line, sizeof(line), "%ld\n", arg1 + arg2);</code></p>
      <p><code>13   else</code></p>
      <p><code>14    snprintf(line, sizeof(line), "input error\n");</code></p>
      <p><code>15   n = strlen(line);</code></p>
      <p><code>16   Writen(sockfd, line, n);</code></p>
      <p><code>17  }</code></p>
      <p><code>18 }</code></p>
      <p><code>11-14</code> Мы вызываем функцию <code>sscanf</code>, чтобы преобразовать два аргумента из текстовых строк в целые числа типа <code>long</code>, а затем функцию <code>snprintf</code> для преобразования результата в текстовую строку.</p>
      <p>Эти клиент и сервер работают корректно вне зависимости от порядка байтов на их узлах.</p>
     </section>
     <section>
      <title>
       <p>Пример: передача двоичных структур между клиентом и сервером</p>
      </title>
      <p>Теперь мы изменим код клиента и сервера, чтобы передавать через сокет не текстовые строки, а двоичные значения. Мы увидим, что клиент и сервер работают некорректно, когда они запущены на узлах с различным порядком байтов или на узлах с разными размерами целого типа <code>long</code> (см. табл. 1.5).</p>
      <p>Функции <code>main</code> наших клиента и сервера не изменяются. Мы определяем одну структуру для двух аргументов, другую структуру для результата и помещаем оба определения в наш заголовочный файл <code>sum.h</code>, представленный в листинге 5.12. В листинге 5.13 показана функция <code>str_cli</code>.</p>
      <p><strong>Листинг 5.12</strong>. Заголовочный файл sum.h</p>
      <p><code>//tcpcliserv/sum.h</code></p>
      <p><code>1 struct args {</code></p>
      <p><code>2  long arg1;</code></p>
      <p><code>3  long arg2;</code></p>
      <p><code>4 };</code></p>
      <empty-line/>
      <p><code>5 struct result {</code></p>
      <p><code>6  long sum;</code></p>
      <p><code>7 };</code></p>
      <p><strong>Листинг 5.13</strong>. Функция str_cli, отправляющая два двоичных целых числа серверу</p>
      <p><code>//tcpcliserv/str_cli09.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include "sum.h"</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 str_cli(FILE *fp, int sockfd)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  char sendline[MAXLINE];</code></p>
      <p><code> 7  struct args args;</code></p>
      <p><code> 8  struct result result;</code></p>
      <empty-line/>
      <p><code> 9  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <empty-line/>
      <p><code>10   if (sscanf(sendline, "%ld%ld", &amp;args.arg1, &amp;args.arg2) != 2) {</code></p>
      <p><code>11    printf("invalid input, %s", sendline);</code></p>
      <p><code>12    continue;</code></p>
      <p><code>13   }</code></p>
      <p><code>14   Writen(sockfd, &amp;args, sizeof(args));</code></p>
      <p><code>15   if (Readn(sockfd, &amp;result, sizeof(result)) == 0)</code></p>
      <p><code>16    err_quit("str_cli: server terminated prematurely");</code></p>
      <empty-line/>
      <p><code>17   printf("%ld\n", result.sum);</code></p>
      <p><code>18  }</code></p>
      <p><code>19 }</code></p>
      <p><code>10-14</code> Функция <code>sscanf</code> преобразует два аргумента из текстовых строк в двоичные. Мы вызываем функцию <code>writen</code> для отправки структуры серверу.</p>
      <p><code>15-17</code> Мы вызываем функцию <code>readn</code> для чтения ответа и выводим результат с помощью функции <code>printf</code>.</p>
      <p>В листинге 5.14 показана наша функция <code>str_echo</code>.</p>
      <p><strong>Листинг 5.14</strong>. Функция str_echo, складывающая два двоичных целых числа</p>
      <p><code>//tcpcliserv/str_echo09.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include "sum.h"</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 str_echo(int sockfd)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  ssize_t n;</code></p>
      <p><code> 7  struct args args;</code></p>
      <p><code> 8  struct result result;</code></p>
      <empty-line/>
      <p><code> 9  for (;;) {</code></p>
      <p><code>10   if ((n = Readn(sockfd, &amp;args, sizeof(args))) == 0)</code></p>
      <p><code>11    return; /* соединение закрыто удаленным концом */</code></p>
      <empty-line/>
      <p><code>12   result.sum = args.arg1 + args.arg2;</code></p>
      <p><code>13   Writen(sockfd, &amp;result, sizeof(result));</code></p>
      <p><code>14  }</code></p>
      <p><code>15 }</code></p>
      <p><code>9-14</code> Мы считываем аргументы при помощи вызова функции <code>readn</code>, вычисляем и запоминаем сумму и вызываем функцию <code>writen</code> для отправки результирующей структуры обратно.</p>
      <p>Если мы запустим клиент и сервер на двух машинах с аналогичной архитектурой, например на двух компьютерах SPARC, все будет работать нормально:</p>
      <p><code>solaris % <strong>tcpcli09 12.106.32.254</strong></code></p>
      <p><code><strong>11 22</strong> <emphasis>мы вводим эти числа</emphasis></code></p>
      <p><code>33    <emphasis>а это ответ сервера</emphasis></code></p>
      <p><code><strong>-11 -44</strong></code></p>
      <p><code>-55</code></p>
      <p>Но если клиент и сервер работают на машинах с разными архитектурами, например, сервер в системе FreeBSD на SPARC, в которой используется обратный порядок байтов (big-endian), а клиент — в системе Linux на Intel с прямым порядком байтов (little-endian), результат будет неверным:</p>
      <p><code>linux % <strong>tcpcli09 206.168.112.96</strong></code></p>
      <p><code><strong>1 2</strong>       <emphasis>мы вводим эти числа</emphasis></code></p>
      <p><code>3         <emphasis>и сервер дает правильный ответ</emphasis></code></p>
      <p><code><strong>-22 -77</strong>   <emphasis>потом мы вводим эти числа</emphasis></code></p>
      <p><code>-16777314 <emphasis>и сервер дает неверный ответ</emphasis></code></p>
      <p>Проблема заключается в том, что два двоичных числа передаются клиентом через сокет в формате с прямым порядком байтов, а сервер интерпретирует их как целые числа, записанные с обратным порядком байтов. Мы видим, что это допустимо для положительных целых чисел, но для отрицательных такой подход не срабатывает (см. упражнение 5.8). Действительно, в подобной ситуации могут возникнуть три проблемы:</p>
      <p>1. Различные реализации хранят двоичные числа в различных форматах. Наиболее характерный пример — прямой и обратный порядок байтов, описанный в разделе 3.4.</p>
      <p>2. Различные реализации могут хранить один и тот же тип данных языка С по- разному. Например, большинство 32-разрядных систем Unix используют 32 бита для типа <code>long</code>, но 64-разрядные системы обычно используют 64 бита для того же типа данных (см. табл. 1.5). Нет никакой гарантии, что типы <code>short</code>, <code>int</code> или <code>long</code> имеют какой-либо определенный размер.</p>
      <p>3. Различные реализации по-разному упаковывают структуры в зависимости от числа битов, используемых для различных типов данных, и ограничений по выравниванию для данного компьютера. Следовательно, неразумно передавать через сокет двоичные структуры.</p>
      <p>Есть два общих решения проблемы, связанной с различными форматами данных:</p>
      <p>1. Передавайте все численные данные как текстовые строки. Это то, что мы делали в листинге 5.11. При этом предполагается, что у обоих узлов один и тот же набор символов.</p>
      <p>2. Явно определяйте двоичные форматы поддерживаемых типов данных (число битов и порядок байтов) и передавайте все данные между клиентом и сервером в этом формате. Пакеты удаленного вызова процедур (Remote Procedure Call, RPC) обычно используют именно эту технологию. В RFC 1832 [109] описывается <emphasis>стандарт представления внешних данных</emphasis> (External Data Representation, XDR), используемый с пакетом Sun RPC.</p>
     </section>
    </section>
    <section>
     <title>
      <p>5.19. Резюме</p>
     </title>
     <p>Первая версия наших эхо-клиента и эхо-сервера содержала около 150 строк (включая функции <code>readline</code> и <code>writen</code>), но многие ее детали пришлось модифицировать. Первой проблемой, с которой мы столкнулись, было превращение дочерних процессов в зомби, и для обработки этой ситуации мы перехватывали сигнал <code>SIGCHLD</code>. Затем наш обработчик сигнала вызывал функцию <code>waitpid</code>, и мы показали, что должны вызывать именно эту функцию вместо более старой функции <code>wait</code>, поскольку сигналы Unix не помещаются в очередь. В результате мы рассмотрели некоторые подробности обработки сигналов POSIX, аза дополнительной информацией по этой теме вы можете обратиться к [110, глава 10].</p>
     <p>Следующая проблема, с которой мы столкнулись, состояла в том, что клиент не получал уведомления о завершении процесса сервера. Мы видели, что TCP нашего клиента получал уведомление, но оно не доходило до клиентского процесса, поскольку тот был блокирован в ожидании ввода пользователя. В главе 6 для обработки этого сценария мы будем использовать функции <code>select</code> или <code>poll</code>, позволяющие ожидать готовности любого из множества дескрипторов вместо блокирования при обращении к одному дескриптору.</p>
     <p>Мы также обнаружили, что если узел сервера выходит из строя, мы не можем определить это до тех пор, пока клиент не пошлет серверу какие-либо данные. Некоторые приложения должны узнавать об этом факте раньше, о чем мы поговорим далее, когда в разделе 7.5 будем рассматривать параметр сокета <code>SO_KEEPALIVE</code>.</p>
     <p>В нашем простом примере происходил обмен текстовыми строками, и поскольку от сервера не требовалось просматривать отражаемые им строки, все работало нормально. Передача численных данных между клиентом и сервером может привести к ряду новых проблем, что и было продемонстрировано.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Создайте сервер TCP на основе листингов 5.1 и 5.2 и клиент TCP на основе листингов 5.3 и 5.4. Запустите сервер, затем запустите клиент. Введите несколько строк, чтобы проверить, что клиент и сервер работают. Завершите работу клиента, введя символ конца файла, и заметьте время. Используйте программу <code>netstat</code> на узле клиента для проверки того, что клиентский конец соединения проходит состояние TIME_WAIT. Запускайте <code>netstat</code> примерно каждые 5 с, чтобы посмотреть, когда закончится состояние TIME_WAIT. Каково время MSL для вашей реализации?</p>
     <p>2. Что происходит с нашим соединением клиент-сервер, если мы запускаем клиент и подключаем к стандартному потоку ввода двоичный файл?</p>
     <p>3. В чем разница между нашим соединением клиент-сервер и использованием клиента Telnet для взаимодействия с нашим эхо-сервером?</p>
     <p>4. В нашем примере в разделе 5.12 мы проверили, что первые два сегмента завершения соединения (сегмент FIN от сервера, на который затем клиент отвечает сегментом ACK) отправляются, при просмотре состояний сокета с помощью программы <code>netstat</code>. Происходит ли обмен двумя последними сегментами (FIN от клиента, на который затем сервер отвечает сегментом ACK)? Если да, то когда? Если нет, то почему?</p>
     <p>5. Что произойдет с примером, рассмотренным в разделе 5.14, если между шагами 2 и 3 мы перезапустим сервер на узле сервера?</p>
     <p>6. Чтобы проверить, что происходит с сигналом <code>SIGPIPE</code> в разделе 5.13, измените листинг 5.3 следующим образом. Напишите обработчик сигнала для <code>SIGPIPE</code>, который будет просто выводить сообщение и возвращать управление. Установите этот обработчик сигнала перед вызовом функции <code>connect</code>. Измените номер порта сервера на 13 (порт сервера времени и даты). Когда соединение установится, с помощью функции <code>sleep</code> войдите в состояние ожидания на 2 с, с помощью функции <code>write</code> запишите несколько байтов в сокет, проведите в состоянии ожидания (<code>sleep</code>) еще 2 с и с помощью функции <code>write</code> запишите еще несколько байтов. Запустите программу. Что происходит?</p>
     <p>7. Что произойдет на рис. 5.5, если IP-адрес узла сервера, заданный клиентом при вызове функции <code>connect</code>, является IP-адресом, связанным с крайним правым канальным уровнем на стороне сервера, а не IP-адресом, связанным с крайним левым канальным уровнем?</p>
     <p>8. В нашем примере эхо-сервера, осуществляющего сложение двух целых чисел (см. листинг 5.14), когда клиент и сервер принадлежат системам с различным порядком байтов, для небольших положительных чисел получается правильный ответ, но для небольших отрицательных чисел ответ неверен. Почему? (<emphasis>Подсказка</emphasis>: нарисуйте схему обмена значениями через сокет, аналогичную рис. 3.4.)</p>
     <p>9. В нашем примере в листинге 5.13 и 5.14 можем ли мы решить проблему, связанную с различным порядком байтов на стороне клиента и на стороне сервера, если клиент преобразует два аргумента в сетевой порядок байтов, используя функцию <code>htonl</code>, а сервер затем вызывает функцию <code>ntohl</code> для каждого аргумента перед сложением и выполняет аналогичное преобразование результата?</p>
     <p>10. Что произойдет в листинге 5.13 и 5.14, если в качестве узла клиента используется компьютер SPARC, где данные типа <code>long</code> занимают 32 бита, а в качестве узла сервера — Digital Alpha, где данные типа <code>long</code> занимают 64 бита? Изменится ли что-либо, если клиент и сервер поменяются местами?</p>
     <p>11. На рис. 5.5 указано, что IP-адрес клиента выбирается IP на основе маршрутизации. Что это значит?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 6</p>
     <p>Мультиплексирование ввода-вывода: функции select и poll</p>
    </title>
    <section>
     <title>
      <p>6.1. Введение</p>
     </title>
     <p>В разделе 5.12 мы видели, что наш TCP-клиент обрабатывает два входных потока одновременно: стандартный поток ввода и сокет TCP. Проблема, с которой мы столкнулись, состояла в том, что пока клиент был блокирован в вызове функции <code>fgets</code> (чтение из стандартного потока ввода), процесс сервера мог быть уничтожен. TCP сервера корректно отправляет сегмент FIN протоколу TCP клиента, но поскольку процесс клиента блокирован при чтении из стандартного потока ввода, он не получит признак конца файла, пока не считает данные из сокета (возможно, значительно позже). Нам нужна возможность сообщить ядру, что мы хотим получить уведомления о том, что выполняется одно или несколько условий для ввода-вывода (например, присутствуют данные для считывания или дескриптор готов к записи новых данных). Эта возможность называется <emphasis>мультиплексированием</emphasis> (multiplexing) ввода-вывода и обеспечивается функциями <code>select</code> и <code>poll</code>. Мы рассмотрим также более новый вариант функции <code>select</code>, входящей в стандарт POSIX, называемый <code>pselect</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В некоторых системах предоставляются более мощные средства ожидания событий. Одним из механизмов является устройство опроса (poll device), которое по-разному реализуется разными производителями. Этот механизм описывается в главе 14.</p>
     </cite>
     <p>Мультиплексирование ввода-вывода обычно используется сетевыми приложениями в следующих случаях:</p>
     <p>&#9632; Когда клиент обрабатывает множество дескрипторов (обычно интерактивный ввод и сетевой сокет), должно использоваться мультиплексирование ввода- вывода. Это сценарий, который мы только что рассмотрели.</p>
     <p>&#9632; Возможно, хотя это и редкий случай, что клиент одновременно обрабатывает множество сокетов. Такой пример мы приведем в разделе 16.5 при использовании функции <code>select</code> в контексте веб-клиента.</p>
     <p>&#9632; Если сервер TCP обрабатывает и прослушиваемый сокет, и присоединенные сокеты, обычно используется мультиплексирование ввода-вывода, как это показано в разделе 6.8.</p>
     <p>&#9632; Если сервер работает и с TCP, и с UDP, обычно также используется мультиплексирование ввода-вывода. Такой пример мы приводим в разделе 8.15.</p>
     <p>&#9632; Если сервер обрабатывает несколько служб и, возможно, несколько протоколов (например, демон <code>inetd</code>, который описан в разделе 12.5), обычно используется мультиплексирование ввода-вывода.</p>
     <p>Область применения мультиплексирования ввода-вывода не ограничивается только сетевым программированием. Любому нетривиальному приложению часто приходится использовать эту технологию.</p>
    </section>
    <section>
     <title>
      <p>6.2. Модели ввода-вывода</p>
     </title>
     <section>
      <p>Прежде чем начать описание функций <code>select</code> и <code>poll</code>, мы должны вернуться назад и уяснить основные различия между пятью моделями ввода-вывода, доступными нам в Unix:</p>
      <p>&#9632; блокируемый ввод-вывод;</p>
      <p>&#9632; неблокируемый ввод-вывод;</p>
      <p>&#9632; мультиплексирование ввода-вывода (функции <code>select</code> и <code>poll</code>);</p>
      <p>&#9632; ввод-вывод, управляемый сигналом (сигнал <code>SIGIO</code>);</p>
      <p>&#9632; асинхронный ввод-вывод (функции POSIX <code>aio_</code>).</p>
      <p>Возможно, вы захотите пропустить этот раздел при первом прочтении, а затем вернуться к нему по мере знакомства с различными моделями ввода-вывода, подробно рассматриваемыми в дальнейших главах.</p>
      <p>Как вы увидите в примерах этого раздела, обычно различаются две фазы операции ввода:</p>
      <p>1. Ожидание готовности данных.</p>
      <p>2. Копирование данных от ядра процессу.</p>
      <p>Первый шаг операции ввода на сокете обычно включает ожидание прихода данных по сети. Когда пакет приходит, он копируется в буфер внутри ядра. Второй шаг — копирование этих данных из буфера ядра в буфер приложения.</p>
     </section>
     <section>
      <title>
       <p>Модель блокируемого ввода-вывода</p>
      </title>
      <p>Наиболее распространенной моделью ввода-вывода является модель <emphasis>блокируемого ввода-вывода</emphasis>, которую мы использовали для всех предыдущих примеров. По умолчанию все сокеты являются блокируемыми. Используя в наших примерах сокет дейтаграмм, мы получаем сценарий, показанный на рис. 6.1.</p>
      <image l:href="#img_45.png"/>
      <p><strong>Рис. 6.1</strong>. Модель блокируемого ввода-вывода</p>
      <p>В этом примере вместо TCP мы используем UDP, поскольку в случае UDP признак готовности данных очень прост: получена вся дейтаграмма или нет. В случае TCP он становится сложнее, поскольку приходится учитывать дополнительные переменные, например минимальный объем данных в сокете (low water-mark).</p>
      <p>В примерах этого раздела мы говорим о функции <code>recvfrom</code> как о системном вызове, поскольку делаем различие между нашим приложением и ядром. Вне зависимости от того, как реализована функция <code>recvfrom</code> (как системный вызов в ядре, происходящем от Беркли, или как функция, активизирующая системный вызов <code>getmsg</code> в ядре System V), она обычно выполняет переключение между работой в режиме приложения и работой в режиме ядра, за которым через определенный промежуток времени следует возвращение в режим приложения.</p>
      <p>На рис. 6.1 процесс вызывает функцию <code>recvfrom</code>, и системный вызов не возвращает управление, пока дейтаграмма не придет и не будет скопирована в буфер приложения либо пока не произойдет ошибка. Наиболее типичная ошибка — это прерывание системного вызова сигналом, о чем рассказывалось в разделе 5.9. Процесс блокирован в течение всего времени с момента, когда он вызывает функцию <code>recvfrom</code>, до момента, когда эта функция завершается. Когда функция <code>recvfrom</code> выполняется нормально, наше приложение обрабатывает дейтаграмму.</p>
     </section>
     <section>
      <title>
       <p>Модель неблокируемого ввода-вывода</p>
      </title>
      <p>Когда мы определяем сокет как неблокируемый, мы тем самым сообщаем ядру следующее: «когда запрашиваемая нами операция ввода-вывода не может быть завершена без перевода процесса в состояние ожидания, следует не переводить процесс в состояние ожидания, а возвратить ошибку». Неблокируемый ввод-вывод мы описываем подробно в главе 16, а на рис. 6.2 лишь демонстрируем его свойства.</p>
      <image l:href="#img_46.png"/>
      <p><strong>Рис. 6.2</strong>. Модель неблокируемого ввода-вывода</p>
      <p>В первых трех случаях вызова функции <code>recvfrom</code> данных для возвращения нет, поэтому ядро немедленно возвращает ошибку <code>EWOULDBLOCK</code>. Когда мы в четвертый раз вызываем функцию <code>recvfrom</code>, дейтаграмма готова, поэтому она копируется в буфер приложения и функция <code>recvfrom</code> успешно завершается. Затем мы обрабатываем данные.</p>
      <p>Такой процесс, когда приложение находится в цикле и вызывает функцию <code>recvfrom</code> на неблокируемом дескрипторе, называется <emphasis>опросом</emphasis> (<emphasis>polling</emphasis>). Приложение последовательно опрашивает ядро, чтобы увидеть, что какая-то операция может быть выполнена. Часто это пустая трата времени процессора, но такая модель все же иногда используется, обычно в специализированных системах.</p>
     </section>
     <section>
      <title>
       <p>Модель мультиплексирования ввода-вывода</p>
      </title>
      <p>В случае <emphasis>мультиплексирования ввода-вывода</emphasis> мы вызываем функцию <code>select</code> или <code>poll</code>, и блокирование происходит в одном из этих двух системных вызовов, а не в действительном системном вызове ввода-вывода. На рис. 6.3 обобщается модель мультиплексирования ввода-вывода.</p>
      <image l:href="#img_47.png"/>
      <p><strong>Рис. 6.3</strong>. Модель мультиплексирования ввода-вывода</p>
      <p>Процесс блокируется в вызове функции <code>select</code>, ожидая, когда дейтаграммный сокет будет готов для чтения. Когда функция <code>select</code> возвращает сообщение, что сокет готов для чтения, процесс вызывает функцию <code>recvfrom</code>, чтобы скопировать дейтаграмму в буфер приложения.</p>
      <p>Сравнивая рис. 6.3 и 6.1, мы не найдем в модели мультиплексирования ввода- вывода каких-либо преимуществ, более того, она даже обладает незначительным недостатком, поскольку использование функции <code>select</code> требует двух системных вызовов вместо одного. Но преимущество использования функции <code>select</code>, которое мы увидим далее в этой главе, состоит в том, что мы сможем ожидать готовности не одного дескриптора, а нескольких.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Разновидностью данного способа мультиплексирования является многопоточное программирование с блокируемым вводом-выводом. Отличие состоит в том, что вместо вызова select с блокированием программа использует несколько потоков (по одному на каждый дескриптор), которые могут блокироваться в вызовах типа recvfrom.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Модель ввода-вывода, управляемого сигналом</p>
      </title>
      <p>Мы можем сообщить ядру, что необходимо уведомить процесс о готовности дескриптора с помощью сигнала <code>SIGIO</code>. Такая модель имеет название <emphasis>ввод-вывод, управляемый сигналом</emphasis> (<emphasis>signal-driven I/O</emphasis>). Она представлена в обобщенном виде на рис. 6.4.</p>
      <image l:href="#img_48.png"/>
      <p><strong>Рис. 6.4</strong>. Модель управляемого сигналом ввода-вывода</p>
      <p>Сначала мы включаем на сокете управляемый сигналом ввод-вывод (об этом рассказывается в разделе 22.2) и устанавливаем обработчик сигнала при помощи системного вызова <code>sigaction</code>. Возвращение из этого системного вызова происходит незамедлительно, и наш процесс продолжается (он не блокирован). Когда дейтаграмма готова для чтения, для нашего процесса генерируется сигнал <code>SIGIO</code>. Мы можем либо прочитать дейтаграмму из обработчика сигнала с помощью вызова функции <code>recvfrom</code> и затем уведомить главный цикл о том, что данные готовы для обработки (см. раздел 22.3), либо уведомить основной цикл и позволить ему прочитать дейтаграмму.</p>
      <p>Независимо от способа обработки сигнала эта модель имеет то преимущество, что во время ожидания дейтаграммы не происходит блокирования. Основной цикл может продолжать выполнение, ожидая уведомления от обработчика сигнала о том, что данные готовы для обработки либо дейтаграмма готова для чтения.</p>
     </section>
     <section>
      <title>
       <p>Модель асинхронного ввода-вывода</p>
      </title>
      <p><emphasis>Асинхронный ввод-вывод</emphasis> был введен в редакции стандарта POSIX.1g 1993 г. (расширения реального времени). Мы сообщаем ядру, что нужно начать операцию и уведомить нас о том, когда вся операция (включая копирование данных из ядра в наш буфер) завершится. Мы не обсуждаем эту модель в этой книге, поскольку она еще не получила достаточного распространения. Ее основное отличие от модели ввода-вывода, управляемого сигналом, заключается в том, что при использовании сигналов ядро сообщает нам, когда операция ввода-вывода может быть <emphasis>инициирована</emphasis>, а в случае асинхронного ввода-вывода — когда операция <emphasis>завершается</emphasis>. Пример этой модели приведен на рис. 6.5.</p>
      <image l:href="#img_49.png"/>
      <p><strong>Рис. 6.5</strong>. Модель асинхронного ввода-вывода</p>
      <p>Мы вызываем функцию <code>aio_read</code> (функции асинхронного ввода-вывода POSIX начинаются с <code>aio_</code> или <code>lio_</code>) и передаем ядру дескриптор, указатель на буфер, размер буфера (те же три аргумента, что и для функции read), смещение файла (аналогично функции <code>lseek</code>), а также указываем, как уведомить нас, когда операция полностью завершится. Этот системный вызов завершается немедленно, и наш процесс не блокируется в ожидании завершения ввода-вывода. В этом примере предполагается, что мы указали ядру сгенерировать некий сигнал, когда операция завершится. Сигнал не генерируется до тех пор, пока данные не скопированы в наш буфер приложения, что отличает эту модель от модели ввода-вывода, управляемого сигналом.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>На момент написания книги только некоторые системы поддерживали асинхронный ввод-вывод стандарта POSIX. Например, мы не уверены, что какие-либо системы поддерживают его для сокетов. Мы используем его только как пример для сравнения с моделью управляемого сигналом ввода-вывода.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Сравнение моделей ввода-вывода</p>
      </title>
      <p>На рис. 6.6 сравнивается пять различных моделей ввода-вывода. Здесь видно главное отличие четырех первых моделей в первой фазе, поскольку вторая фаза у них одна и та же: процесс блокируется в вызове функции <code>recvfrom</code> на то время, пока данные копируются из ядра в буфер вызывающего процесса. Асинхронный ввод-вывод отличается от первых четырех моделей в обеих фазах.</p>
      <image l:href="#img_50.png"/>
      <p><strong>Рис. 6.6</strong>. Сравнение моделей ввода-вывода</p>
      <empty-line/>
     </section>
     <section>
      <title>
       <p>Сравнение синхронного и асинхронного ввода-вывода</p>
      </title>
      <p>POSIX дает следующие определения этих терминов:</p>
      <p>&#9632; Операция синхронного ввода-вывода блокирует запрашивающий процесс до тех пор, пока операция ввода-вывода не завершится.</p>
      <p>&#9632; Операция асинхронного ввода-вывода не вызывает блокирования запрашивающего процесса.</p>
      <p>Используя эти определения, можно сказать, что первые четыре модели ввода- вывода — блокируемая, неблокируемая, модель мультиплексирования ввода-вывода и модель управляемого сигналом ввода-вывода — являются синхронными, поскольку фактическая операция ввода-вывода (функция <code>recvfrom</code>) блокирует процесс. Только модель асинхронного ввода-вывода соответствует определению асинхронного ввода-вывода.</p>
     </section>
    </section>
    <section>
     <title>
      <p>6.3. Функция select</p>
     </title>
     <section>
      <p>Эта функция позволяет процессу сообщить ядру, что необходимо подождать, пока не произойдет одно из некоторого множества событий, и вывести процесс из состояния ожидания, только когда произойдет одно или несколько таких событий или когда пройдет заданное количество времени.</p>
      <p>Например, мы можем вызвать функцию <code>select</code> и сообщить ядру, что возвращать управление нужно только когда наступит любое из следующих событий:</p>
      <p>&#9632; любой дескриптор из набора {1, 4, 5} готов для чтения;</p>
      <p>&#9632; любой дескриптор из набора {2, 7} готов для записи;</p>
      <p>&#9632; любой дескриптор из набора {1, 4} вызывает исключение, требующее обработки;</p>
      <p>&#9632; истекает 10,2 с.</p>
      <p>Таким образом, мы сообщаем ядру, какие дескрипторы нас интересуют (готовые для чтения, готовые для записи или требующие обработки исключения) и как долго нужно ждать. Интересующие нас дескрипторы не ограничиваются сокетами: любой дескриптор можно проверить с помощью функции <code>select</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Беркли-реализации всегда допускали мультиплексирование ввода-вывода с любыми дескрипторами. Система SVR3 ограничивала мультиплексирование ввода-вывода дескрипторами, которые являлись устройствами STREAMS (см. главу 31), но это ограничение было снято в SVR4.</p>
      </cite>
      <p><code>#include &lt;sys/select.h&gt;</code></p>
      <p><code>#include &lt;sys/time.h&gt;</code></p>
      <empty-line/>
      <p><code>int select(int <emphasis>maxfdp1</emphasis>, fd_set *<emphasis>readset</emphasis>, fd_set *<emphasis>writeset</emphasis>,</code></p>
      <p><code> fd_set *<emphasis>exceptset</emphasis>, const struct timeval *<emphasis>timeout</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: положительное число - счетчик готовых дескрипторов, 0 в случае тайм-аута, -1 в случае ошибки</emphasis></code></p>
      <p>Описание этой функции мы начнем с последнего аргумента, который сообщает ядру, сколько следует ждать, пока один из заданных дескрипторов не будет готов. Структура <code>timeval</code> задает число секунд и микросекунд:</p>
      <p><code>struct timeval {</code></p>
      <p><code> long tv_sec;  /* секунды */</code></p>
      <p><code> long tv_usec; /* микросекунды */</code></p>
      <p><code>};</code></p>
      <p>С помощью этого аргумента можно реализовать три сценария:</p>
      <p>1. Ждать вечно: завершать работу, только когда один из заданных дескрипторов готов для ввода-вывода. Для этого нужно определить аргумент <code>timeout</code> как пустой указатель.</p>
      <p>2. Ждать в течение определенного времени: завершение будет происходить, когда один из заданных дескрипторов готов для ввода-вывода, но период ожидания ограничивается количеством секунд и микросекунд, заданным в структуре <code>timeval</code>, на которую указывает аргумент <code>timeout</code>.</p>
      <p>3. Не ждать вообще: завершение происходит сразу же после проверки дескрипторов. Это называется <emphasis>опросом</emphasis> (<emphasis>polling</emphasis>). Аргумент <code>timeout</code> должен указывать на структуру <code>timeval</code>, а значение таймера (число секунд и микросекунд, заданных этой структурой) должно быть нулевым.</p>
      <p>Ожидание в первых двух случаях обычно прерывается, когда процесс перехватывает сигнал и возвращается из обработчика сигнала.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Ядра реализаций, происходящих от Беркли, никогда автоматически не перезапускают функцию select [128, с. 527], в то время как ядра SVR4 перезапускают, если задан флаг SA_RESTART при установке обработчика сигнала. Это значит, что в целях переносимости мы должны быть готовы к тому, что функция select возвратит ошибку EINTR, если мы перехватываем сигналы.</p>
      </cite>
      <p>Хотя структура <code>timeval</code> позволяет нам задавать значение с точностью до микросекунд, реальная точность, поддерживаемая ядром, часто значительно ниже. Например, многие ядра Unix округляют значение тайм-аута до числа, кратного 10 мс. Присутствует также и некоторая скрытая задержка: между истечением времени таймера и моментом, когда ядро запустит данный процесс, проходит некоторое время.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В некоторых системах при задании поля tv_sec более 100 млн с функция select завершается с кодом ошибки EINVAL Это, конечно, достаточно большое число (более трех лет), но факт остается фактом: структура timeval может содержать значения, не поддерживаемые функцией select.</p>
      </cite>
      <p>Спецификатор <code>const</code> аргумента <code>timeout</code> означает, что данный аргумент не изменяется функцией <code>select</code> при ее возвращении. Например, если мы зададим предел времени, равный 10 с, и функция <code>select</code> возвратит управление до истечения этого времени с одним или несколькими готовыми дескрипторами или ошибкой <code>EINTR</code>, то структура <code>timeval</code> не изменится, то есть при завершении функции значение тайм-аута не станет равно числу секунд, оставшихся от исходных 10. Чтобы узнать количество неизрасходованных секунд, следует определить системное время до вызова функции <code>select</code>, а когда она завершится, определить его еще раз и вычесть первое значение из второго. Устойчивая программа должна учитывать тот факт, что системное время может периодически корректироваться администратором или демоном типа <code>ntpd</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В современных системах Linux структура timeval изменяема. Следовательно, в целях переносимости будем считать, что структура timeval по возвращении становится неопределенной, и будем инициализировать ее перед каждым вызовом функции select. В POSIX указывается спецификатор const.</p>
      </cite>
      <p>Три средних аргумента, <code>readset</code>, <code>writeset</code> и <code>exceptset</code>, определяют дескрипторы, которые ядро должно проверить на возможность чтения и записи и на наличие исключений (exceptions). В настоящее время поддерживается только два исключения:</p>
      <p>1. На сокет приходят внеполосные данные. Более подробно мы опишем этот случай в главе 24.</p>
      <p>2. Присутствие информации об управлении состоянием (control status information), которая должна быть считана с управляющего (master side) псевдотерминала, помещенного в режим пакетной обработки. Псевдотерминалы в данном томе не рассматриваются.</p>
      <p>Проблема в том, как задать одно или несколько значений дескрипторов для каждого из трех аргументов. Функция <code>select</code> использует <emphasis>наборы дескрипторов</emphasis>, обычно это массив целых чисел, где каждый бит в каждом целом числе соответствует дескриптору. Например, при использовании 32-разрядных целых чисел первый элемент массива (целое число) соответствует дескрипторам от 0 до 31, второй элемент — дескрипторам от 32 до 63, и т.д. Детали реализации не влияют на приложение и скрыты в типе данных <code>fd_set</code> и следующих четырех макросах:</p>
      <p><code>void FD_ZERO(fd_set *<emphasis>fdset</emphasis>); /* сбрасываем все биты в <emphasis>fdset</emphasis> */</code></p>
      <p><code>void FD_SET(int <emphasis>fd</emphasis>, fd_set *<emphasis>fdset</emphasis>); /* устанавливаем бит для <emphasis>fd</emphasis> в <emphasis>fdset</emphasis> */</code></p>
      <p><code>void FD_CLR(int <emphasis>fd</emphasis>, fd_set *<emphasis>fdset</emphasis>); /* сбрасываем бит для <emphasis>fd</emphasis> в <emphasis>fdset</emphasis> */</code></p>
      <p><code>int FD_ISSET(int <emphasis>fd</emphasis>, fd_set *<emphasis>fdset</emphasis>); /* установлен ли бит для <emphasis>fd</emphasis> в <emphasis>fdset</emphasis>? */</code></p>
      <p>Мы размещаем в памяти набор дескрипторов типа <code>fd_set</code>, с помощью этих макросов устанавливаем и проверяем биты в наборе, а также можем присвоить его (как значение) другому набору дескрипторов с помощью оператора присваивания языка С.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Описываемый нами массив целых чисел, использующий по одному биту для каждого дескриптора, — это только один из возможных способов реализации функции select. Тем не менее является обычной практикой ссылаться на отдельные дескрипторы в наборе дескрипторов как на биты, например так: «установить бит для прослушиваемого дескриптора в наборе для чтения».</p>
       <p>В разделе 6.10 мы увидим, что функция poll использует совершенно другое представление: массив структур переменной длины, по одной структуре для каждого дескриптора.</p>
      </cite>
      <p>Например, чтобы определить переменную типа <code>fd_set</code> и затем установить биты для дескрипторов 1, 4 и 5, мы пишем:</p>
      <p><code>fd_set rset;</code></p>
      <empty-line/>
      <p><code>FD_ZERO(&amp;rset); /* инициализируем набор все биты сброшены */</code></p>
      <p><code>FD_SET(1, &amp;rset); /* устанавливаем бит для fd 1 */</code></p>
      <p><code>FD_SET(4, &amp;rset); /* устанавливаем бит для fd 4 */</code></p>
      <p><code>FD_SET(5, &amp;rset); /* устанавливаем бит для fd 5 */</code></p>
      <p>Важно инициализировать набор, так как если набор будет создан в виде автоматической переменной и не проинициализировав, результат может оказаться непредсказуемым.</p>
      <p>Любой из трех средних аргументов функции <code>select</code> — <code>readset</code>, <code>writeset</code> или <code>exceptset</code> — может быть задан как пустой указатель, если нас не интересует определяемое им условие. На самом деле, если все три указателя пустые, мы просто получаем таймер большей точности, чем обычная функция Unix <code>sleep</code> (позволяющая задавать время с точностью до секунды). Функция <code>poll</code> обеспечивает аналогичную функциональность. На рис. С.9 и С.10 [110] показана функция <code>sleep_us</code>, реализованная с помощью функций <code>select</code> и <code>poll</code>, которая позволяет устанавливать время ожидания с точностью до микросекунд.</p>
      <p>Аргумент <code>maxfdp1</code> задает число проверяемых дескрипторов. Его значение на единицу больше максимального номера проверяемого дескриптора (поэтому мы назвали его <code>maxfdp1</code>). Проверяются дескрипторы 0, 1, 2 и далее до <code>maxfdp1</code> - 1 включительно.</p>
      <p>Константа <code>FD_SETSIZE</code>, определяемая при подключении заголовочного файла <code>&lt;sys/select.h&gt;</code>, является максимальным числом дескрипторов для типа данных <code>fd_set</code>. Ее значение часто равно 1024, но такое количество дескрипторов используется очень немногими программами. Аргумент <code>maxfdp1</code> заставляет нас вычислять наибольший интересующий нас дескриптор и затем сообщать ядру его значение. Например, в предыдущем коде, который включает дескрипторы 1, 4 и 5, значение аргумента <code>maxfdp1</code> равно 6. Причина, по которой это 6, а не 5, в том, что мы задаем количество дескрипторов, а не наибольшее значение, а нумерация дескрипторов начинается с нуля.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Зачем нужно было включать этот аргумент и вычислять его значение? Причина в том, что он повышает эффективность работы ядра. Хотя каждый набор типа fd_set может содержать множество дескрипторов (обычно до 1024), реальное количество дескрипторов, используемое типичным процессом, значительно меньше. Эффективность возрастает за счет того, что не копируются ненужные части набора дескрипторов между ядром и процессом и не требуется проверять биты, которые всегда являются нулевыми (см. раздел 16.13 [128]).</p>
      </cite>
      <p>Функция <code>select</code> изменяет наборы дескрипторов, на которые указывают аргументы <code>readset</code>, <code>writeset</code> и <code>exceptset</code>. Эти три аргумента являются аргументами типа «значение-результат». Когда мы вызываем функцию, мы указываем интересующие нас дескрипторы, а по ее завершении результат показывает нам, какие дескрипторы готовы. Проверить определенный дескриптор из структуры <code>fd_set</code> после завершения вызова можно с помощью макроса <code>FD_ISSET</code>. Для дескриптора, не готового для чтения или записи, соответствующий бит в наборе дескрипторов будет сброшен. Поэтому мы устанавливаем все интересующие нас биты во всех наборах дескрипторов каждый раз, когда вызываем функцию <code>select</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Две наиболее общих ошибки программирования при использовании функции select — это забыть добавить единицу к наибольшему номеру дескриптора и забыть, что наборы дескрипторов имеют тип «значение-результат». Вторая ошибка приводит к тому, что функция select вызывается с нулевым битом в наборе дескрипторов, когда мы думаем, что он установлен в единицу.</p>
      </cite>
      <p>Возвращаемое этой функцией значение указывает общее число готовых дескрипторов во всех наборах дескрипторов. Если значение таймера истекает до того, как какой-нибудь из дескрипторов оказывается готов, возвращается нулевое значение. Возвращаемое значение -1 указывает на ошибку (которая может произойти, если, например, выполнение функции прервано перехваченным сигналом).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В ранних реализациях SVR4 функция select содержала ошибку: если один и тот же бит находился в нескольких наборах дескрипторов — допустим, дескриптор был готов и для чтения, и для записи, — он учитывался только один раз. В современных реализациях эта ошибка исправлена.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>При каких условиях дескриптор становится готовым?</p>
      </title>
      <p>Мы говорили об ожидании готовности дескриптора для ввода-вывода (чтения или записи) или возникновения исключительной ситуации, требующей обработки (внеполосные данные). В то время как готовность к чтению и записи очевидна для файловых дескрипторов, в случае дескрипторов сокетов следует более внимательно изучить те условия, при которых функция <code>select</code> сообщает, что сокет готов (см. рис. 16.52 [128]).</p>
      <p>1. Сокет готов для чтения, если выполнено хотя бы одно из следующих условий:</p>
      <p> 1) число байтов данных в приемном буфере сокета больше или равно текущему значению минимального количества данных (low water-mark) для приемного буфера сокета. Операция считывания данных из сокета не блокируется и возвратит значение, большее нуля (то есть данные, готовые для чтения). Мы можем задать значение минимального количества данных (low-water mark) с помощью параметра сокета <code>SO_RCVLOWAT</code>. По умолчанию для сокетов TCP и UDP это значение равно 1;</p>
      <p> 2) на противоположном конце соединение закрывается (нами получен сегмент FIN). Операция считывания данных из сокета не блокируется и возвратит нуль (то есть признак конца файла);</p>
      <p> 3) сокет является прослушиваемым, и число установленных соединений ненулевое. Функция <code>accept</code> на прослушиваемом сокете в таком случае обычно не блокируется, хотя в разделе 16.6 мы описываем ситуацию, в которой функция accept может заблокироваться несмотря на наличие установленных соединений;</p>
      <p> 4) ошибка сокета, ожидающая обработки. Операция чтения на сокете не блокируется и возвратит ошибку (-1) со значением переменной <code>errno</code>, указывающим на конкретное условие ошибки. Эти <emphasis>ошибки, ожидающие обработки</emphasis>, можно также получить, вызвав функцию <code>getsockopt</code> с параметром <code>SO_ERROR,</code> после чего состояние ошибки будет сброшено.</p>
      <p>2. Сокет готов для записи, если выполнено одно из следующих условий:</p>
      <p> 1) количество байтов доступного пространства в буфере отправки сокета больше или равно текущему значению минимального количества данных для буфера отправки сокета <emphasis>и</emphasis> либо сокет является присоединенным, либо сокету не требуется соединения (например, сокет UDP). Это значит, что если мы отключим блокировку для сокета (см. главу 16), операция записи не заблокирует процесс и возвратит положительное значение (например, число байтов, принятых транспортным уровнем). Устанавливать минимальное количество данных мы можем с помощью параметра сокета <code>SO_SNDLOWAT</code>. По умолчанию это значение равно 2048 для сокетов TCP и UDP;</p>
      <p> 2) получатель, которому отправляются данные, закрывает соединение. Операция записи в сокет сгенерирует сигнал <code>SIGPIPE</code> (см. раздел 5.12);</p>
      <p> 3) ошибка сокета, ожидающая обработки. Операция записи в сокет не блокируется и возвратит ошибку (-1) со значением переменной <code>errno</code>, указывающей на конкретное условие ошибки. Эти <emphasis>ошибки, ожидающие обработки</emphasis>, можно также получить и сбросить, вызвав функцию <code>getsockopt</code> с параметром сокета <code>SO_ERROR</code>.</p>
      <p>3. Исключительная ситуация, требующая обработки, может возникнуть на сокете в том случае, если приняты внеполосные данные либо если отметка вне- полосных данных в принимаемом потоке еще не достигнута. (Внеполосные данные описываются в главе 24.)</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Наши определения «готов для чтения» и «готов для записи» взяты непосредственно из макроопределений ядра soreadable и sowritable (которые описываются в [128, с. 530-531]). Аналогично, наше определение «исключительной ситуации» взято из функции soo_select, которая описана там же.</p>
      </cite>
      <p>Обратите внимание, что когда происходит ошибка на сокете, функция <code>select</code> отмечает его готовым как для чтения, так и для записи.</p>
      <p>Значения минимального количества данных (low-water mark) для приема и отправки позволяют приложению контролировать, сколько данных должно быть доступно для чтения или сколько места должно быть доступно для записи перед тем, как функция <code>select</code> сообщит, что сокет готов для чтения или записи. Например, если мы знаем, что наше приложение не может сделать ничего полезного, пока не будет получено как минимум 64 байт данных, мы можем установить значение минимального количества данных равным 64, чтобы функция <code>select</code> не вывела нас из состояния ожидания, если для чтения готово менее 64 байт.</p>
      <p>Пока значение минимального количества данных для отправки в сокете UDP меньше, чем буфер отправки сокета (а такое отношение между ними всегда устанавливается по умолчанию), сокет UDP всегда готов для записи, поскольку соединения не требуется.</p>
      <p>В табл. 6.1 суммируются описанные выше условия, при которых сокет становится готовым для вызова функции <code>select</code>.</p>
      <empty-line/>
      <p><strong>Таблица 6.1</strong>. Условия, при которых функция select сообщает, что сокет готов для чтения или для записи либо, что необходима обработка исключительной ситуации</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Условие</th>
        <th align="left" valign="top">Сокет готов для чтения</th>
        <th align="left" valign="top">Сокет готов для записи</th>
        <th align="left" valign="top">Исключительная ситуация</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Данные для чтения</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Считывающая половина соединения закрыта</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Для прослушиваемого сокета готово новое соединение</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Пространство, доступное для записи</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Записывающая половина соединения закрыта</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Ошибка, ожидающая обработки</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Внеполосные данные TCP</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
       </tr>
      </table>
     </section>
     <section>
      <title>
       <p>Максимальное число дескрипторов для функции select</p>
      </title>
      <p>Ранее мы сказали, что большинство приложений не используют много дескрипторов. Например, редко можно найти приложение, использующее сотни дескрипторов. Но такие приложения существуют, и часто они используют функцию <code>select</code> для мультиплексирования дескрипторов. Когда функция <code>select</code> была создана, операционные системы обычно имели ограничение на максимальное число дескрипторов для каждого процесса (этот предел в реализации 4.2BSD составлял 31), и функция <code>select</code> просто использовала тот же предел. Но современные версии Unix допускают неограниченное число дескрипторов для каждого процесса (часто оно ограничивается только количеством памяти и административными правилами), поэтому возникает вопрос: как же теперь работает функция <code>select</code>?</p>
      <p>Многие реализации имеют объявления, аналогичные приведенному ниже, которое взято из заголовочного файла 4.4BSD <code>&lt;sys/types.h&gt;</code>:</p>
      <p><code>/*</code></p>
      <p><code>  Значение FD_SETSIZE может быть определено пользователем,</code></p>
      <p><code>  но заданное здесь по умолчанию</code></p>
      <p><code>  является достаточным в большинстве случаев.</code></p>
      <p><code>*/</code></p>
      <empty-line/>
      <p><code>#ifndef FD_SETSIZE</code></p>
      <p><code>#define FD_SETSIZE 256</code></p>
      <p><code>#endif</code></p>
      <p>Исходя из этого комментария, можно подумать, что если перед подключением этого заголовочного файла присвоить <code>FD_SETSIZE</code> значение, превышающее 256, то увеличится размер наборов дескрипторов, используемых функцией <code>select</code>. К сожалению, обычно это не действует.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Чтобы понять, в чем дело, обратите внимание, что на рис. 16.53 [128] объявляются три набора дескрипторов внутри ядра, а в качестве верхнего предела используется определенное в ядре значение FD_SETSIZE. Единственный способ увеличить размер наборов дескрипторов — это увеличить значение FD_SETSIZE и затем перекомпилировать ядро. Изменения значения без перекомпиляции ядра недостаточно.</p>
      </cite>
      <p>Некоторые производители изменяют свои реализации функции <code>select</code>, с тем чтобы позволить процессу задавать значение <code>FD_SETSIZE</code>, превышающее значение по умолчанию. BSD/OS также изменила реализацию ядра, чтобы допустить большие наборы дескрипторов, кроме того, в ней добавлено четыре новых макроопределения <code>FD_<emphasis>xxx</emphasis></code> для динамического размещения больших наборов дескрипторов в памяти и для работы с ними. Однако с точки зрения переносимости не стоит злоупотреблять использованием больших наборов дескрипторов.</p>
     </section>
    </section>
    <section>
     <title>
      <p>6.4. Функция str_cli (продолжение)</p>
     </title>
     <p>Теперь мы можем переписать нашу функцию <code>str_cli</code>, представленную в разделе 5.5 (на этот раз используя функцию <code>select</code>), таким образом, чтобы мы получали уведомление, как только завершится процесс сервера. Проблема с предыдущей версией состояла в том, что процесс мог оказаться заблокированным в вызове функции <code>fgets</code>, когда что-то происходило на сокете. Наша новая версия этой функции вместо этого блокируется в вызове функции <code>select</code>, ожидая готовности для чтения либо стандартного потока ввода, либо сокета. На рис. 6.7 показаны различные условия, обрабатываемые с помощью вызова функции <code>select</code>.</p>
     <image l:href="#img_51.png"/>
     <p><strong>Рис. 6.7</strong>. Условия, обрабатываемые функцией select в вызове функции str_cli</p>
     <p>Сокет обрабатывает три условия:</p>
     <p>1. Если протокол TCP собеседника отправляет данные, сокет становится готовым для чтения, и функция <code>read</code> возвращает положительное значение (то есть число байтов данных).</p>
     <p>2. Если протокол TCP собеседника отправляет сегмент FIN (процесс завершается), сокет становится готовым для чтения, и функция <code>read</code> возвращает нуль (признак конца файла).</p>
     <p>3. Если TCP собеседника отправляет RST (узел вышел из строя и перезагрузился), сокет становится готовым для чтения, и функция <code>read</code> возвращает -1, а переменная <code>errno</code> содержит код соответствующей ошибки.</p>
     <p>В листинге 6.1<a l:href="#n1" type="note">[1]</a> представлен исходный код этой версии функции.</p>
     <p><strong>Листинг 6.1</strong>. Реализация функции str_cli с использованием функции select (усовершенствованный вариант находится в листинге 6.2)</p>
     <p><code>//select/strcliselect01.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 str_cli(FILE *fp, int sockfd)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int maxfdp1;</code></p>
     <p><code> 6  fd_set rset;</code></p>
     <p><code> 7  char sendline[MAXLINE], recvline[MAXLINE];</code></p>
     <empty-line/>
     <p><code> 8  FD_ZERO(&amp;rset);</code></p>
     <p><code> 9  for (;;) {</code></p>
     <p><code>10   FD_SET(fileno(fp), &amp;rset);</code></p>
     <p><code>11   FD_SET(sockfd, &amp;rset);</code></p>
     <p><code>12   maxfdp1 = max(fileno(fp), sockfd) + 1;</code></p>
     <p><code>13   Select(maxfdp1, &amp;rset, NULL, NULL, NULL);</code></p>
     <empty-line/>
     <p><code>14   if (FD_ISSET(sockfd, &amp;rset)) { /* сокет готов для чтения */</code></p>
     <p><code>15    if (Readline(sockfd, recvline, MAXLINE) == 0)</code></p>
     <p><code>16     err_quit("str_cli: server terminated prematurely");</code></p>
     <p><code>17    Fputs(recvline, stdout);</code></p>
     <p><code>18   }</code></p>
     <p><code>19   if (FD_ISSET(fileno(fp), &amp;rset)) { /* входное устройство готово для</code></p>
     <p><code>                                           чтения */</code></p>
     <p><code>20    if (Fgets(sendline, MAXLINE, fp) == NULL)</code></p>
     <p><code>21     return; /* все сделано */</code></p>
     <p><code>22    Writen(sockfd, sendline, strlen(sendline));</code></p>
     <p><code>23   }</code></p>
     <p><code>24  }</code></p>
     <p><code>25 }</code></p>
     <subtitle>Вызов функции select</subtitle>
     <p><code>8-13</code> Нам нужен только один набор дескрипторов — для проверки готовности сокета для чтения. Этот набор дескрипторов инициализируется макросом <code>FD_ZERO</code>, после чего с помощью макроса <code>FD_SET</code> устанавливаются два бита: бит, соответствующий указателю файла <code>fp</code> стандартного потока ввода-вывода, и бит, соответствующий дескриптору сокета <code>sockfd</code>. Функция <code>fileno</code> преобразует указатель файла стандартного потока ввода-вывода в соответствующий ему дескриптор. Функция <code>select</code> (а также <code>poll</code>) работает только с дескрипторами.</p>
     <p>Функция <code>select</code> вызывается после определения максимального из двух дескрипторов. В этом вызове указатель на набор дескрипторов для записи и указатель на набор дескрипторов с исключениями являются пустыми. Последний аргумент (ограничение по времени) также является пустым указателем, поскольку мы хотим, чтобы процесс был блокирован, пока не будут готовы данные для чтения.</p>
     <subtitle>Обработка сокета, готового для чтения</subtitle>
     <p><code>14-18</code> Если по завершении функции <code>select</code> сокет готов для чтения, отраженная строка считывается функцией <code>readline</code> и выводится функцией <code>fputs</code>.</p>
     <subtitle>Обработка ввода, допускающего возможность чтения</subtitle>
     <p><code>19-23</code> Если стандартный поток ввода готов для чтения, строка считывается функцией <code>fgets</code> и записывается в сокет с помощью функции <code>writen</code>.</p>
     <p>Обратите внимание, что используются те же четыре функции ввода-вывода, что и в листинге 5.4: <code>fgets</code>, <code>writen</code>, <code>readline</code> и <code>fputs</code>, но порядок их следования внутри функции <code>str_cli</code> изменился. Раньше выполнение функции <code>str_cli</code> определялось функцией <code>fgets</code>, а теперь ее место заняла <code>select</code>. С помощью всего нескольких дополнительных строк кода (сравните листинги 6.1 и 5.4) мы значительно увеличили устойчивость клиента.</p>
    </section>
    <section>
     <title>
      <p>6.5. Пакетный ввод</p>
     </title>
     <p>К сожалению, наша функция <code>str_cli</code> все еще не вполне корректна. Сначала вернемся к ее исходной версии, приведенной в листинге 5.4. Эта функция работает в режиме остановки и ожидания (stop-and-wait mode), что удобно для интерактивного использования: функция отправляет строку серверу и затем ждет его ответа. Время ожидания складывается из одного периода обращения (RTT) и времени обработки сервером (которое близко к нулю в случае простого эхо-сервера). Следовательно, мы можем предположить, сколько времени займет отражение данного числа строк, если мы знаем время обращения (RTT) между клиентом и сервером.</p>
     <p>Измерить RTT позволяет утилита <code>ping</code>. Если мы измерим с ее помощью время обращения к <code>connix.com</code> с нашего узла <code>solaris</code>, то средний период RTT после 30 измерений будет равен 175 мс. В [111, с. 89] показано, что это справедливо для дейтаграммы IP длиной 84 байт. Если мы возьмем первые 2000 строк файла <code>termcap</code> Solaris 2.5, то итоговый размер файла будет равен 98 349 байт, то есть в среднем 49 байт на строку. Если мы добавим размеры заголовка IP (20 байт) и заголовка TCP (20 байт), то средний сегмент TCP будет составлять 89 байт, почти как размер пакета утилиты <code>ping</code>. Следовательно, мы можем предположить, что общее время составит около 350 с для 2000 строк (2000&#215;0,175 с). Если мы запустим наш эхо-клиент TCP из главы 5, действительное время получится около 354 с, что очень близко к нашей оценке.</p>
     <p>Если считать, что сеть между клиентом и сервером является двусторонним каналом, когда запросы идут от клиента серверу, а ответы в обратном направлении, то получится изображенный на рис. 6.8 режим остановки и ожидания.</p>
     <image l:href="#img_52.png"/>
     <p><strong>Рис. 6.8</strong>. Временная диаграмма режима остановки и ожидания: интерактивный ввод</p>
     <p>Запрос отправляется клиентом в нулевой момент времени, и мы предполагаем, что время обращения RTT равно 8 условным единицам. Ответ, отправленный в момент времени 4, доходит до клиента в момент времени 7. Мы также считаем, что время обработки сервером нулевое и что размер запроса равен размеру ответа. Мы показываем только пакеты данных между клиентом и сервером, игнорируя подтверждения TCP, которые также передаются по сети.</p>
     <p>Но поскольку между отправкой пакета и его приходом на другой конец канала существует задержка и канал является двусторонним, в этом примере мы используем только восьмую часть вместимости канала. Режим остановки и ожидания удобен для интерактивного ввода, но поскольку наш клиент считывает данные из стандартного потока ввода и записывает в стандартный поток вывода, а перенаправление ввода и вывода выполнить в интерпретаторе команд крайне просто, мы легко можем запустить наш клиент в пакетном режиме. Однако когда мы перенаправляем ввод и вывод, получающийся файл вывода всегда меньше файла ввода (а для эхо-сервера требуется их идентичность).</p>
     <p>Чтобы понять происходящее, обратите внимание, что в пакетном режиме мы отправляем запросы так быстро, как их может принять сеть. Сервер обрабатывает их и отправляет обратно ответы с той же скоростью. Это приводит к тому, что в момент времени 7 канал целиком заполнен, как показано на рис. 6.9.</p>
     <image l:href="#img_53.png"/>
     <p><strong>Рис. 6.9</strong>. Заполнение канала между клиентом и сервером: пакетный режим</p>
     <p>Предполагается, что после отправки первого запроса мы немедленно посылаем другой запрос и т.д. Также предполагается, что мы можем отправлять запросы с той скоростью, с какой сеть способна их принимать, и обрабатывать ответы так быстро, как сеть их поставляет.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Существуют различные нюансы, имеющие отношение к передаче большого количества данных TCP (bulk data flow), которые мы здесь игнорируем. К ним относятся алгоритм медленного запуска (slow start algorithm), ограничивающий скорость, с которой данные отправляются на новое или незанятое соединение, и возвращаемые сегменты ACK. Все эти вопросы рассматриваются в главе 20 [111].</p>
     </cite>
     <p>Чтобы увидеть, в чем заключается проблема с нашей функцией <code>str_cli</code>, представленной в листинге 6.1, будем считать, что файл ввода содержит только девять строк. Последняя строка отправляется в момент времени 8, как показано на рис. 6.9. Но мы не можем закрыть соединение после записи этого запроса, поскольку в канале еще есть другие запросы и ответы. Причина возникновения проблемы кроется в нашем способе обработки конца файла при вводе, когда процесс возвращается в функцию <code>main</code>, которая затем завершается. Но в пакетном режиме конец файла при вводе не означает, что мы закончили читать из сокета — в нем могут оставаться запросы к серверу или ответы от сервера.</p>
     <p>Нам нужен способ закрыть одну половину соединения TCP. Другими словами, мы хотим отправить серверу сегмент FIN, тем самым сообщая ему, что закончили отправку данных, но оставляем дескриптор сокета открытым для чтения. Это делается с помощью функции <code>shutdown</code>, которая описывается в следующем разделе.</p>
     <p>Вообще говоря, буферизация ввода-вывода для повышения производительности приводит к усложнению сетевых приложений (от чего пострадала и программа в листинге 6.1). Рассмотрим пример, в котором из стандартного потока ввода считывается несколько строк текста. Функция <code>select</code> передаст управление строке 20, в которой функция <code>fgets</code> считает доступные данные в буфер библиотеки <code>stdio</code>. Однако эта функция возвратит приложению только одну строку, а все остальные так и останутся в буфере. Считанная строка будет отправлена серверу, после чего будет снова вызвана функция <code>select</code>, которая будет ждать появления новых данных в стандартном потоке ввода несмотря на наличие еще не обработанных строк в буфере <code>stdio</code>. Причина в том, что <code>select</code> ничего не знает о буферах <code>stdio</code> и сообщает о доступности дескриптора для чтения с точки зрения системного вызова <code>read</code>, а не библиотечного вызова <code>fgets</code>. По этой причине использование <code>fgets</code> и <code>select</code> в одной программе считается опасным и требует особой осторожности.</p>
     <p>Та же проблема связана с вызовом <code>readline</code> в листинге 6.1. Теперь данные скрываются от функции <code>select</code> уже не в буфере <code>stdio</code>, а в буфере <code>readline</code>. Вспомните, что в разделе 3.9 мы создали функцию, проверявшую состояние буфера <code>readline</code>. Мы могли бы воспользоваться ею перед вызовом <code>select</code>, чтобы проверить, нет ли в буфере <code>readline</code> данных, дожидающихся обработки. Наша программа усложнится еще больше, если мы допустим, что буфер <code>readline</code> может содержать лишь часть строки (то есть нам придется дожидаться считывания этой строки целиком).</p>
     <p>Проблемы буферизации мы постараемся решить в усовершенствованной версии <code>str_cli</code> в разделе 6.7.</p>
    </section>
    <section>
     <title>
      <p>6.6. Функция shutdown</p>
     </title>
     <p>Обычный способ завершить сетевое соединение — вызвать функцию <code>close</code>. Но у функции <code>close</code> есть два ограничения, которых лишена функция <code>shutdown</code>:</p>
     <p>1. Функция close последовательно уменьшает счетчик ссылок дескриптора и закрывает сокет, только если счетчик доходит до нуля. Мы рассматривали это в разделе 4.8. Используя функцию <code>shutdown</code>, мы можем инициировать обычную последовательность завершения соединения TCP (четыре сегмента, начинающихся с FIN, на рис. 2.5) независимо от значения счетчика ссылок.</p>
     <p>2. Функция <code>close</code> завершает оба направления передачи данных — и чтение, и запись. Поскольку соединение TCP является двусторонним, возможны ситуации, когда нам понадобится сообщить другому концу соединения, что мы закончили отправку, даже если на том конце соединения имеются данные для отправки нам. Это случай, рассмотренный в предыдущем разделе при описании работы нашей функции <code>str_cli</code> в пакетном режиме. На рис. 6.10 показаны типичные вызовы функций в этом сценарии.</p>
     <image l:href="#img_54.png"/>
     <p><strong>Рис. 6.10</strong>. Вызов функции shutdown для закрытия половины соединения TCP</p>
     <p><code>#include &lt;sys/socket.h&gt;</code></p>
     <empty-line/>
     <p><code>int shutdown(int <emphasis>sockfd</emphasis>, int <emphasis>howto</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Действие функции зависит от значения аргумента <code>howto</code>.</p>
     <p>&#9632; <code>SHUT_RD</code>. Закрывается считывающая половина соединения: из сокета больше нельзя считывать данные, и все данные, находящиеся в данный момент в буфере приема сокета, сбрасываются. Процесс больше не может выполнять функции чтения из сокета. Любые данные для сокета TCP, полученные после вызова функции <code>shutdown</code> с этим аргументом, подтверждаются и «молча» игнорируются.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>По умолчанию все, что записывается в маршрутизирующий сокет (см. главу 17), возвращается как возможный ввод на все маршрутизирующие сокеты узла. Некоторые программы вызывают функцию shutdown со вторым аргументом SHUT_RD, чтобы предотвратить получение подобной копии. Другой способ избежать копирования — отключить параметр сокета SO_USELOOPBACK.</p>
     </cite>
     <p>&#9632; <code>SHUT_WR</code>. Закрывается записывающая половина соединения. В случае TCP это называется <emphasis>половинным закрытием</emphasis> (см. раздел 18.5 [111]). Все данные, находящиеся в данный момент в буфере отправки сокета, будут отправлены, а затем будет выполнена обычная последовательность действий по завершению соединения TCP. Как мы отмечали ранее, закрытие записывающей половины соединения выполняется независимо от того, является ли значение в счетчике ссылок дескриптора сокета положительным или нет. Процесс теряет возможность записывать данные в сокет.</p>
     <p>&#9632; <code>SHUT_RDWR</code>. Закрываются и читающая, и записывающая половины соединения. Это эквивалентно двум вызовам функции <code>shutdown</code>: сначала с аргументом <code>SHUT_RD</code>, затем — с аргументом <code>SHUT_WR</code>.</p>
     <p>В табл. 7.4 приведены все возможные сценарии, доступные процессу при вызове функций <code>shutdown</code> и <code>close</code>. Действие функции close зависит от значения параметра сокета <code>SO_LINGER</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Три константы SHUT_xxx определяются в спецификации POSIX. Типичные значения аргумента howto, с которыми вы встретитесь, — это 0 (закрытие читающей половины), 1 (закрытие записывающей половины) и 2 (закрытие обеих половин).</p>
     </cite>
    </section>
    <section>
     <title>
      <p>6.7. Функция str_cli (еще раз)</p>
     </title>
     <p>В листинге 6.2 представлена наша обновленная (и корректная) функция <code>str_cli</code>. В этой версии используются функции <code>select</code> и <code>shutdown</code>. Первая уведомляет нас о том, когда сервер закрывает свой конец соединения, а вторая позволяет корректно обрабатывать пакетный ввод. Эта версия избавлена от ориентации на строки. Вместо этого она работает с буферами, что позволяет полностью избавиться от проблем, описанных в конце раздела 6.5.</p>
     <p><strong>Листинг 6.2</strong>. функция str_cli, использующая функцию select, которая корректно обрабатывает конец файла</p>
     <p><code>//select/strcliselect02.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 str_cli(FILE *fp, int sockfd)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int maxfdp1, stdineof;</code></p>
     <p><code> 6  fd_set rset;</code></p>
     <p><code> 7  char buf[MAXLINE];</code></p>
     <p><code> 8  int n;</code></p>
     <empty-line/>
     <p><code> 9  stdineof = 0;</code></p>
     <p><code>10  FD_ZERO(&amp;rset);</code></p>
     <p><code>11  for (;;) {</code></p>
     <p><code>12   if (stdineof == 0)</code></p>
     <p><code>13    FD_SET(fileno(fp), &amp;rset);</code></p>
     <p><code>14   FD_SET(sockfd, &amp;rset);</code></p>
     <p><code>15   maxfdp1 = max(fileno(fp), sockfd) + 1;</code></p>
     <p><code>16   Select(maxfdp1, &amp;rset, NULL, NULL, NULL);</code></p>
     <empty-line/>
     <p><code>17   if (FD_ISSET(sockfd, &amp;rset)) { /* сокет готов для чтения */</code></p>
     <p><code>18    if ((n = Read(sockfd, buf, MAXLINE)) == 0) {</code></p>
     <p><code>19     if (stdineof == 1)</code></p>
     <p><code>20      return; /* нормальное завершение */</code></p>
     <p><code>21     else</code></p>
     <p><code>22      err_quit("str_cli: server terminated prematurely");</code></p>
     <p><code>23    }</code></p>
     <empty-line/>
     <p><code>24    Write(fileno(stdout), buf, n);</code></p>
     <p><code>25   }</code></p>
     <empty-line/>
     <p><code>26   if (FD_ISSET(fileno(fp), &amp;rset)) { /* есть данные на входе */</code></p>
     <p><code>27    if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) {</code></p>
     <p><code>28     stdineof = 1;</code></p>
     <p><code>29     Shutdown(sockfd, SHUT_WR); /* отправка сегмента FIN */</code></p>
     <p><code>30     FD_CLR(fileno(fp), &amp;rset);</code></p>
     <p><code>31     continue;</code></p>
     <p><code>32    }</code></p>
     <empty-line/>
     <p><code>33    Writen(sockfd, buf, n);</code></p>
     <p><code>34   }</code></p>
     <p><code>35  }</code></p>
     <p><code>36 }</code></p>
     <p><code>5-8</code> <code>stdineof</code> — это новый флаг, инициализируемый нулем. Пока этот флаг равен нулю, мы будем проверять готовность стандартного потока ввода к чтению с помощью функции <code>select</code>.</p>
     <p><code>16-24</code> Если мы считываем на сокете признак конца файла, когда нам уже встретился ранее признак конца файла в стандартном потоке ввода, это является нормальным завершением и функция возвращает управление. Но если конец файла в стандартном потоке ввода еще не встречался, это означает, что процесс сервера завершился преждевременно. В новой версии мы вызываем функции <code>read</code> и <code>write</code> и работаем с буферами, а не со строками, благодаря чему функция <code>select</code> действует именно так, как мы рассчитывали.</p>
     <p><code>25-33</code> Когда нам встречается признак конца файла на стандартном устройстве ввода, наш новый флаг <code>stdineof</code> устанавливается в единицу и мы вызываем функцию <code>shutdown</code> со вторым аргументом <code>SHUT_WR</code> для отправки сегмента FIN.</p>
     <p>Если мы измерим время работы нашего клиента TCP, использующего функцию <code>str_cli</code>, показанную в листинге 6.2, с тем же файлом из 2000 строк, это время составит 12,3 с, что почти в 30 раз быстрее, чем при использовании версии этой функции, работающей в режиме остановки и ожидания.</p>
     <p>Мы еще не завершили написание нашей функции <code>str_cli</code>: в разделе 15.2 мы разработаем ее версию с использованием неблокируемого ввода-вывода, а в разделе 23.3 — версию, работающую с программными потоками.</p>
    </section>
    <section>
     <title>
      <p>6.8. Эхо-сервер TCP (продолжение)</p>
     </title>
     <section>
      <p>Вернемся к нашему эхо-серверу TCP из разделов 5.2 и 5.3. Перепишем сервер как одиночный процесс, который будет использовать функцию <code>select</code> для обработки любого числа клиентов, вместо того чтобы порождать с помощью функции <code>fork</code> по одному дочернему процессу для каждого клиента. Перед тем как представить этот код, взглянем на структуры данных, используемые для отслеживания клиентов. На рис. 6.11 показано состояние сервера до того, как первый клиент установил соединение.</p>
      <image l:href="#img_55.png"/>
      <p><strong>Рис. 6.11</strong>. Сервер TCP до того, как первый клиент установил соединение</p>
      <p>У сервера имеется одиночный прослушиваемый дескриптор, показанный на рисунке точкой.</p>
      <p>Сервер обслуживает только набор дескрипторов для чтения, который мы показываем на рис. 6.12. Предполагается, что сервер запускается в приоритетном (foreground) режиме, а дескрипторы 0, 1 и 2 соответствуют стандартным потокам ввода, вывода и ошибок. Следовательно, первым доступным для прослушиваемого сокета дескриптором является дескриптор 3. Массив целых чисел <code>client</code> содержит дескрипторы присоединенного сокета для каждого клиента. Все элементы этого массива инициализированы значением -1.</p>
      <image l:href="#img_56.png"/>
      <p><strong>Рис. 6.12</strong>. Структуры данных для сервера TCP с одним прослушиваемым сокетом</p>
      <p>Единственная ненулевая запись в наборе дескрипторов — это запись для прослушиваемого сокета, и поэтому первый аргумент функции <code>select</code> будет равен 4.</p>
      <p>Когда первый клиент устанавливает соединение с нашим сервером, прослушиваемый дескриптор становится доступным для чтения и сервер вызывает функцию <code>accept</code>. Новый присоединенный дескриптор, возвращаемый функцией <code>accept</code>, будет иметь номер 4, если выполняются приведенные выше предположения. На рис. 6.13 показано соединение клиента с сервером.</p>
      <image l:href="#img_57.png"/>
      <p><strong>Рис. 6.13</strong>. Сервер TCP после того как первый клиент устанавливает соединение</p>
      <p>Теперь наш сервер должен запомнить новый присоединенный сокет в своем массиве <code>client</code>, и присоединенный сокет должен быть добавлен в набор дескрипторов. Изменившиеся структуры данных показаны на рис. 6.14.</p>
      <image l:href="#img_58.png"/>
      <p><strong>Рис. 6.14</strong>. Структуры данных после того как установлено соединение с первым клиентом</p>
      <p>Через некоторое время второй клиент устанавливает соединение, и мы получаем сценарий, показанный на рис. 6.15.</p>
      <image l:href="#img_59.png"/>
      <p><strong>Рис. 6.15</strong>. Сервер TCP после того как установлено соединение со вторым клиентом</p>
      <p>Новый присоединенный сокет (который имеет номер 5) должен быть размещен в памяти, в результате чего структуры данных меняются так, как показано на рис. 6.16.</p>
      <image l:href="#img_60.png"/>
      <p><strong>Рис. 6.16</strong>. Структуры данных после того как установлено соединение со вторым клиентом</p>
      <p>Далее мы предположим, что первый клиент завершает свое соединение. TCP-клиент отправляет сегмент FIN, превращая тем самым дескриптор номер 4 на стороне сервера в готовый для чтения. Когда наш сервер считывает этот присоединенный сокет, функция <code>readline</code> возвращает нуль. Затем мы закрываем сокет, и соответственно изменяются наши структуры данных. Значение <code>client[0]</code> устанавливается в -1, а дескриптор 4 в наборе дескрипторов устанавливается в нуль. Это показано на рис. 6.17. Обратите внимание, что значение переменной <code>maxfd</code> не изменяется.</p>
      <image l:href="#img_61.png"/>
      <p><strong>Рис. 6.17</strong>. Структуры данных после того как первый клиент разрывает соединение</p>
      <p>Итак, по мере того как приходят клиенты, мы записываем дескриптор их присоединенного сокета в первый свободный элемент массива <code>client</code> (то есть в первый элемент со значением -1). Следует также добавить присоединенный сокет в набор дескрипторов для чтения. Переменная <code>maxi</code> — это наибольший используемый в данный момент индекс в массиве <code>client</code>, а переменная <code>maxfd</code> (плюс один) — это текущее значение первого аргумента функции select. Единственным ограничением на количество обслуживаемых сервером клиентов является минимальное из двух значений: <code>FD_SETSIZE</code> и максимального числа дескрипторов, которое допускается для данного процесса ядром (о чем мы говорили в конце раздела 6.3).</p>
      <p>В листинге 6.3 показана первая половина этой версии сервера.</p>
      <p><strong>Листинг 6.3</strong>. Сервер TCP, использующий одиночный процесс и функцию select: инициализация</p>
      <p><code>//tcpcliserv/tcpservselect01.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int i, maxi, maxfd, listenfd, connfd, sockfd;</code></p>
      <p><code> 6  int nready, client[FD_SETSIZE],</code></p>
      <p><code> 7  ssize_t n;</code></p>
      <p><code> 8  fd_set rset, allset;</code></p>
      <p><code> 9  char buf[MAXLINE];</code></p>
      <p><code>10  socklen_t clilen;</code></p>
      <p><code>11  struct sockaddr_in cliaddr, servaddr;</code></p>
      <empty-line/>
      <p><code>12  listenfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
      <empty-line/>
      <p><code>13  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
      <p><code>14  servaddr.sin_family = AF_INET;</code></p>
      <p><code>15  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
      <p><code>16  servaddr.sin_port = htons(SERV_PORT);</code></p>
      <empty-line/>
      <p><code>17  Bind(listenfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
      <empty-line/>
      <p><code>18  Listen(listenfd, LISTENQ);</code></p>
      <empty-line/>
      <p><code>19  maxfd = listenfd; /* инициализация */</code></p>
      <p><code>20  maxi = -1; /* индекс в массиве client[] */</code></p>
      <p><code>21  for (i = 0; i &lt; FD_SETSIZE; i++)</code></p>
      <p><code>22   client[i] = -1; /* -1 означает свободный элемент */</code></p>
      <p><code>23  FD_ZERO(&amp;allset);</code></p>
      <p><code>24  FD_SET(listenfd, &amp;allset);</code></p>
      <subtitle>Создание прослушиваемого сокета и инициализация функции select</subtitle>
      <p><code>12-24</code> Этапы создания прослушиваемого сокета те же, что и раньше: вызов функций <code>socket</code>, <code>bind</code> и <code>listen</code>. Мы инициализируем структуры данных при том условии, что единственный дескриптор, который мы с помощью функции <code>select</code> выберем, изначально является прослушиваемым сокетом.</p>
      <p>Вторая половина функции <code>main</code> показана в листинге 6.4.</p>
      <p><strong>Листинг 6.4</strong>. Сервер TCP, использующей одиночный процесс и функцию select: цикл</p>
      <p><code>//tcpcliserv/tcpservselect01.c</code></p>
      <p><code>25  for (;;) {</code></p>
      <p><code>26   rset = allset; /* присваивание значения структуре */</code></p>
      <p><code>27   nready = Select(maxfd + 1, &amp;rset, NULL, NULL, NULL);</code></p>
      <empty-line/>
      <p><code>28   if (FD_ISSET(listenfd, &amp;rset)) { /* соединение с новым клиентом */</code></p>
      <p><code>29    clilen = sizeof(cliaddr);</code></p>
      <p><code>30    connfd = Accept(listenfd, (SA*)&amp;cliaddr, &amp;clilen);</code></p>
      <empty-line/>
      <p><code>31    for (i = 0; i &lt; FD_SETSIZE; i++)</code></p>
      <p><code>32     if (client[i] &lt; 0) {</code></p>
      <p><code>33      client[i] = connfd; /* сохраняем дескриптор */</code></p>
      <p><code>34      break;</code></p>
      <p><code>35     }</code></p>
      <p><code>36    if (i == FD_SETSIZE)</code></p>
      <p><code>37     err_quit("too many clients");</code></p>
      <empty-line/>
      <p><code>38    FD_SET(connfd, &amp;allset); /* добавление нового дескриптора */</code></p>
      <p><code>39    if (connfd &gt; maxfd)</code></p>
      <p><code>40     maxfd = connfd; /* для функции select */</code></p>
      <p><code>41    if (i &gt; maxi)</code></p>
      <p><code>42     maxi = i; /* максимальный индекс в массиве clientf[] */</code></p>
      <empty-line/>
      <p><code>43    if (--nready &lt;= 0)</code></p>
      <p><code>44     continue; /* больше нет дескрипторов, готовых для чтения */</code></p>
      <p><code>45   }</code></p>
      <p><code>46   for (i = 0; i &lt;= maxi; i++) { /* проверяем все клиенты на наличие</code></p>
      <p><code>                                      данных */</code></p>
      <p><code>47    if ((sockfd - client[i]) &lt; 0)</code></p>
      <p><code>48     continue;</code></p>
      <p><code>49    if (FD_ISSET(sockfd, &amp;rset)) {</code></p>
      <p><code>50     if ((n = Read(sockfd, buf, MAXLINE)) == 0) {</code></p>
      <p><code>51      /* соединение закрыто клиентом */</code></p>
      <p><code>52      Close(sockfd);</code></p>
      <p><code>53      FD_CLR(sockfd, &amp;allset);</code></p>
      <p><code>54      client[i] = -1;</code></p>
      <p><code>55     } else</code></p>
      <p><code>56      Writen(sockfd, line, n);</code></p>
      <empty-line/>
      <p><code>57     if (--nready &lt;= 0)</code></p>
      <p><code>58      break; /* больше нет дескрипторов, готовых для чтения */</code></p>
      <p><code>59    }</code></p>
      <p><code>60   }</code></p>
      <p><code>61  }</code></p>
      <p><code>62 }</code></p>
      <subtitle>Блокирование в функции select</subtitle>
      <p><code>26-27</code> Функция <code>select</code> ждет, пока не будет установлено новое клиентское соединение или на существующем соединении не прибудут данные, сегмент FIN или сегмент RST.</p>
      <subtitle>Принятие новых соединений с помощью функции accept</subtitle>
      <p><code>28-45</code> Если прослушиваемый сокет готов для чтения, новое соединение установлено. Мы вызываем функцию <code>accept</code> и соответствующим образом обновляем наши структуры данных. Для записи присоединенного сокета мы используем первый незадействованный элемент массива <code>client</code>. Число готовых дескрипторов уменьшается, и если оно равно нулю, мы можем не выполнять следующий цикл <code>for</code>. Это позволяет нам использовать значение, возвращаемое функцией <code>select</code>, чтобы избежать проверки не готовых дескрипторов.</p>
      <subtitle>Проверка существующих соединений</subtitle>
      <p><code>46-60</code> Каждое существующее клиентское соединение проверяется на предмет того, содержится ли его дескриптор в наборе дескрипторов, возвращаемом функцией <code>select</code>. Если да, то из этого дескриптора считывается строка, присланная клиентом, и отражается обратно клиенту. Если клиент закрывает соединение, функция read возвращает нуль и мы обновляем структуры соответствующим образом.</p>
      <p>Мы не уменьшаем значение переменной <code>maxi</code>, но могли бы проверять возможность сделать это каждый раз, когда клиент закрывает свое соединение.</p>
      <p>Этот сервер сложнее, чем сервер, показанный в листингах 5.1 и 5.2, но он позволяет избежать затрат на создание нового процесса для каждого клиента, что является хорошим примером использования функции <code>select</code>. Тем не менее в разделе 15.6 мы опишем проблему, связанную с этим сервером, которая, однако, легко устраняется, если сделать прослушиваемый сокет неблокируемым, а затем проверить и проигнорировать несколько ошибок из функции <code>accept</code>.</p>
     </section>
     <section>
      <title>
       <p>Атака типа «отказ в обслуживании»</p>
      </title>
      <p>К сожалению, функционирование только что описанного сервера вызывает проблемы. Посмотрим, что произойдет, если некий клиент-злоумышленник соединится с сервером, отправит 1 байт данных (отличный от разделителя строк) и войдет в состояние ожидания. Сервер вызовет функцию <code>readline</code>, которая прочитает одиночный байт данных от клиента и заблокируется в следующем вызове функции <code>read</code>, ожидая следующих данных от клиента. Сервер блокируется (вернее, «подвешивается») этим клиентом и не может предоставить обслуживание никаким другим клиентам (ни новым клиентским соединениям, ни данным существующих клиентов), пока упомянутый клиент-злоумышленник не отправит символ перевода строки или не завершит свой процесс.</p>
      <p>Дело в том, что обрабатывая множество клиентов, сервер <emphasis>никогда</emphasis> не должен блокироваться в вызове функции, относящейся к одному клиенту. В противном можно «подвесить» сервер, что приведет к отказу в обслуживании для всех остальных клиентов. Это называется атакой типа «отказ в обслуживании» (DoS attack — Denial of Service). Такая атака воздействует на сервер, делая невозможным обслуживание нормальных клиентов. Обезопасить себя от подобных атак позволяют следующие решения: использовать неблокируемый ввод-вывод (см. главу 16), предоставлять каждому клиенту обслуживание отдельным потоком (например, для каждого клиента порождать процесс или поток) или установить тайм-аут для ввода-вывода (см. раздел 14.2).</p>
     </section>
    </section>
    <section>
     <title>
      <p>6.9. Функция pselect</p>
     </title>
     <p>Функция <code>pselect</code> была введена в POSIX и в настоящий момент поддерживается множеством версий Unix.</p>
     <p><code>#include &lt;sys/select.h&gt;</code></p>
     <p><code>#include &lt;signal.h&gt;</code></p>
     <p><code>#include &lt;time.h&gt;</code></p>
     <empty-line/>
     <p><code>int pselect(int <emphasis>maxfdp1</emphasis>, fd_set *<emphasis>readset</emphasis>, fd_set *<emphasis>writeset</emphasis>, fd_set *<emphasis>exceptset</emphasis>,</code></p>
     <p><code> const struct timespec *<emphasis>timeout</emphasis>, const sigset_t *<emphasis>sigmask</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: количество готовых дескрипторов, 0 в случае тайм-аута, -1 в случае ошибки</emphasis></code></p>
     <p>Функция <code>pselect</code> имеет два отличия от обычной функции <code>select</code>:</p>
     <p>1. Функция <code>pselect</code> использует структуру <code>timespec</code>, нововведение стандарта реального времени POSIX, вместо структуры <code>timeval</code>.</p>
     <p><code>struct timespec {</code></p>
     <p><code> time_t tv_sec; /* секунды */</code></p>
     <p><code> long tv_nsec;  /* наносекунды */</code></p>
     <p><code>};</code></p>
     <p>Эти структуры отличаются вторыми элементами: элемент <code>tv_nsec</code> новой структуры задает наносекунды, в то время как элемент <code>tv_usec</code> прежней структуры задает микросекунды.</p>
     <p>2. В функции <code>pselect</code> добавляется шестой аргумент — указатель на маску сигналов. Это позволяет программе отключить доставку ряда сигналов, проверить какие-либо глобальные переменные, установленные обработчиками этих отключенных сигналов, а затем вызвать функцию <code>pselect</code>, сообщив ей, что нужно переустановить маску сигналов.</p>
     <p>В отношении второго пункта рассмотрим следующий пример (описанный на с. 308–309 [110]). Обработчик сигнала нашей программы для сигнала <code>SIGINT</code> просто устанавливает глобальную переменную <code>intr_flag</code> и возвращает управление. Если наш процесс блокирован в вызове функции select, возвращение из обработчика сигнала заставляет функцию завершить работу, присвоив <code>errno</code> значение <code>EINTR</code>. Код вызова <code>select</code> выглядит следующим образом:</p>
     <p><code>if (intr_flag)</code></p>
     <p><code> handle_intr(); /* обработка этого сигнала */</code></p>
     <p><code>if ((nready = select(...)) &lt; 0) {</code></p>
     <p><code> if (errno == EINTR) {</code></p>
     <p><code>  if (intr_flag)</code></p>
     <p><code>   handle_intr();</code></p>
     <p><code> }</code></p>
     <p><code> ...</code></p>
     <p><code>}</code></p>
     <p>Проблема заключается в том, что если сигнал придет в промежутке между проверкой переменной <code>intr_flag</code> и вызовом функции <code>select</code>, он будет потерян в том случае, если функция <code>select</code> заблокирует процесс навсегда. С помощью функции <code>pselect</code> мы можем переписать этот пример так, чтобы он работал более надежно:</p>
     <p><code>sigset_t newmask, oldmask, zeromask;</code></p>
     <empty-line/>
     <p><code>sigemptyset(&amp;zeromask);</code></p>
     <p><code>sigemptyset(&amp;newmask);</code></p>
     <p><code>sigaddset(&amp;newmask, SIGINT);</code></p>
     <empty-line/>
     <p><code>sigprocmask(SIG_BLOCK, &amp;newmask, &amp;oldmask); /* блокирование сигнала SIGINT */</code></p>
     <p><code>if (intr_flag)</code></p>
     <p><code> handle_intr(); /* обработка этого сигнала */</code></p>
     <p><code>if ((nready = pselect(..., &amp;zeromask)) &lt; 0) {</code></p>
     <p><code> if (errno == EINTR) {</code></p>
     <p><code>  if (intr_flag)</code></p>
     <p><code>  handle_intr();</code></p>
     <p><code> }</code></p>
     <p><code> ...</code></p>
     <p><code>}</code></p>
     <p>Перед проверкой переменной <code>intr_flag</code> мы блокируем сигнал <code>SIGINT</code>. Когда вызывается функция <code>pselect</code>, она заменяет маску сигналов процесса пустым набором (<code>zeromask</code>), а затем проверяет дескрипторы, возможно, переходя в состояние ожидания. Но когда функция <code>pselect</code> возвращает управление, маске сигналов процесса присваивается то значение, которое предшествовало вызову функции <code>pselect</code> (то есть сигнал <code>SIGINT</code> блокируется).</p>
     <p>Мы поговорим о функции <code>pselect</code> более подробно и приведем ее пример в разделе 20.5. Функцию <code>pselect</code> мы используем в листинге 20.3, а в листинге 20.4 показываем простую, хотя и не вполне корректную реализацию этой функции.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Есть одно незначительное различие между функциями select и pselect. Первый элемент структуры timeval является целым числом типа long со знаком, в то время как первый элемент структуры timspec имеет тип time_t. Число типа long со знаком в первой функции также должно было относиться к типу time_t, но мы не меняли его тип, чтобы не разрушать существующего кода. Однако в новой функции это можно было бы сделать.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>6.10. Функция poll</p>
     </title>
     <p>Функция <code>poll</code> появилась впервые в SVR3, и изначально ее применение ограничивалось потоковыми устройствами (STREAMS devices) (см. главу 31). В SVR4 это ограничение было снято, что позволило функции <code>poll</code> работать с любыми дескрипторами. Функция <code>poll</code> предоставляет функциональность, аналогичную функции <code>select</code>, но позволяет получать дополнительную информацию при работе с потоковыми устройствами.</p>
     <p><code>#include &lt;poll.h&gt;</code></p>
     <empty-line/>
     <p><code>int poll(struct pollfd *<emphasis>fdarray</emphasis>, unsigned long <emphasis>nfds</emphasis>, int <emphasis>timeout</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: количество готовых дескрипторов, 0 в случае тайм-аута, -1 в случае ошибки</emphasis></code></p>
     <p>Первый аргумент — это указатель на первый элемент массива структур. Каждый элемент массива — это структура <code>pollfd</code>, задающая условия, проверяемые для данного дескриптора <code>fd</code>.</p>
     <p><code>struct pollfd {</code></p>
     <p><code> int fd;        /* дескриптор, который нужно проверить */</code></p>
     <p><code> short events;  /* события на дескрипторе, которые нас интересуют */</code></p>
     <p><code> short revents; /* события, произошедшие на дескрипторе fd */</code></p>
     <p><code>};</code></p>
     <p>Проверяемые условия задаются элементом <code>events</code>, и состояние этого дескриптора функция возвращает в соответствующем элементе <code>revents</code>. (Наличие двух переменных для каждого дескриптора, одна из которых — значение, а вторая — результат, дает возможность обойтись без аргументов типа «значение-результат». Вспомните, что три средних аргумента функции <code>select</code> имеют тип «значение-результат».) Каждый из двух элементов состоит из одного или более битов, задающих определенное условие. В табл. 6.2 перечислены константы, используемые для задания флага <code>events</code> и для проверки флага <code>revents</code>.</p>
     <empty-line/>
     <p><strong>Таблица 6.2</strong>. Различные значения флагов events и revents для функции poll</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Константа</th>
       <th align="left" valign="top">На входе (events)</th>
       <th align="left" valign="top">На выходе (revents)</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">POLLIN</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Можно считывать обычные или приоритетные данные</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">POLLRDNORM</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Можно считывать обычные данные</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">POLLRDBAND</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Можно считывать приоритетные данные</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">POLLPRI</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Можно считывать данные с высоким приоритетом</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">POLLOUT</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Можно записывать обычные данные</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">POLLWRNORM</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Можно записывать обычные данные</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">POLLWRBAND</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Можно записывать приоритетные данные</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">POLLERR</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Произошла ошибка</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">POLLHUP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Произошел разрыв соединения</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">POLLNVAL</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Дескриптор не соответствует открытому файлу</td>
      </tr>
     </table>
     <p>Мы разделили эту таблицу на три части: первые четыре константы относятся ко вводу, следующие три — к выводу, а последние три — к ошибкам. Обратите внимание, что последние три константы не могут устанавливаться в элементе events, но всегда возвращаются в revents, когда выполняется соответствующее условие.</p>
     <p>Существует три класса данных, различаемых функцией <code>poll</code>: <emphasis>обычные</emphasis>, <emphasis>приоритетные</emphasis> и <emphasis>данные с высоким приоритетом</emphasis>. Эти термины берут начало в реализациях, основанных на потоках (см. рис. 31.5).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Константа POLLIN может быть задана путем логического сложения констант POLLRDNORM и POLLRDBAND. Константа POLLIN существовала еще в реализациях SVR3, которые предшествовали полосам приоритета в SVR4, то есть эта константа существует в целях обратной совместимости. Аналогично, константа POLLOUT эквивалентна POLLWRNORM, и первая из них предшествовала второй.</p>
     </cite>
     <p>Для сокетов TCP и UDP при описанных условиях функция <code>poll</code> возвращает указанный флаг <code>revent</code>. К сожалению, в определении функции <code>poll</code> стандарта POSIX имеется множество слабых мест (неоднозначностей):</p>
     <p>&#9632; Все регулярные данные TCP и все данные UDP считаются обычными.</p>
     <p>&#9632; Внеполосные данные TCP (см. главу 24) считаются приоритетными.</p>
     <p>&#9632; Когда считывающая половина соединения TCP закрывается (например, если получен сегмент FIN), это также считается равнозначным обычным данным, и последующая операция чтения возвратит нуль.</p>
     <p>&#9632; Наличие ошибки для соединения TCP может расцениваться либо как обычные данные, либо как ошибка (<code>POLLERR</code>). В любом случае последующая функция read возвращает -1, что сопровождается установкой переменной <code>errno</code> в соответствующее значение. Это происходит при получении RST или истечении таймера.</p>
     <p>&#9632; Информация о доступности нового соединения на прослушиваемом сокете может считаться либо обычными, либо приоритетными данными. В большинстве реализаций эта информация рассматривается как обычные данные.</p>
     <p>Число элементов в массиве структур задается аргументом <code>nfds</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Исторически этот аргумент имел тип long без знака, что является некоторым излишеством. Достаточно будет типа int без знака. В Unix 98 для этого аргумента определяется новый тип — nfds_t.</p>
     </cite>
     <p>Аргумент <code>timeout</code> определяет, как долго функция находится в ожидании перед завершением. Положительным значением задается количество миллисекунд — время ожидания. В табл. 6.3 показаны возможные значения аргумента <code>timeout</code>.</p>
     <empty-line/>
     <p><strong>Таблица 6.3</strong>. Значения аргумента timeout для функции poll</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Значение аргумента timeout</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">INFTIM</td>
       <td align="left" valign="top">Ждать вечно</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Возвращать управление немедленно, без блокирования</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">&gt;0</td>
       <td align="left" valign="top">Ждать в течение указанного числа миллисекунд</td>
      </tr>
     </table>
     <p>Константа <code>INFTIM</code> определена как отрицательное значение. Если таймер в данной системе не обеспечивает точность порядка миллисекунд, значение округляется в большую сторону до ближайшего поддерживаемого значения.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>POSIX требует, чтобы константа INFTIM была определена в заголовочном файле &lt;poll.h&gt;, но многие системы все еще определяют ее в заголовочном файле &lt;sys/stropts.h&gt;.</p>
      <p>Как и в случае функции select, любой тайм-аут, установленный для функции poll, ограничивается снизу разрешающей способностью часов в конкретной реализации (обычно 10 мс).</p>
     </cite>
     <p>Функция <code>poll</code> возвращает -1, если произошла ошибка, 0 — если нет готовых дескрипторов до истечения времени таймера, иначе возвращается число дескрипторов с ненулевым элементом <code>revents</code>.</p>
     <p>Если нас больше не интересует конкретный дескриптор, достаточно установить элемент <code>fd</code> структуры <code>pollfd</code> равным отрицательному значению. В этом случае элемент <code>events</code> будет проигнорирован, а элемент <code>revents</code> при возвращении функции будет сброшен в нуль.</p>
     <p>Вспомните наши рассуждения в конце раздела 6.3 относительно константы <code>FD_SETSIZE</code> и максимального числа дескрипторов в наборе в сравнении с максимальным числом дескрипторов для процесса. У нас не возникает подобных проблем с функцией <code>poll</code>, поскольку вызывающий процесс отвечает за размещение массива структур <code>pollfd</code> в памяти и за последующее сообщение ядру числа элементов в массиве. Не существует типа данных фиксированного размера, аналогичного <code>fd_set</code>, о котором знает ядро.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>POSIX требует наличия и функции select, и функции poll. Но если сравнивать их с точки зрения переносимости, то функцию select в настоящее время поддерживает больше систем, чем функцию poll. POSIX определяет также функцию pselect — усовершенствованную версию функции select, которая обеспечивает возможность блокирования сигналов и предоставляет лучшую разрешающую способность по времени, а для функции poll ничего подобного в POSIX нет.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>6.11. Эхо-сервер TCP (еще раз)</p>
     </title>
     <p>Теперь мы изменим наш эхо-сервер TCP из раздела 6.8, используя вместо функции <code>select</code> функцию <code>poll</code>. В предыдущей версии сервера, работая с функцией <code>select</code>, мы должны были выделять массив <code>client</code> вместе с набором дескрипторов <code>rset</code> (см. рис. 6.12). С помощью функции <code>poll</code> мы разместим в памяти массив структур <code>pollfd</code>. В нем же мы будем хранить и информацию о клиенте, не создавая для нее другой массив. Элемент <code>fd</code> этого массива мы обрабатываем тем же способом, которым обрабатывали массив <code>client</code> (см. рис. 6.12): значение -1 говорит о том, что элемент не используется, а любое другое значение является номером дескриптора. Вспомните из предыдущего раздела, что любой элемент в массиве структур <code>pollfd</code>, передаваемый функции <code>poll</code> с отрицательным значением элемента <code>fd</code>, просто игнорируется.</p>
     <p>В листинге 6.5 показана первая часть кода нашего сервера.</p>
     <p><strong>Листинг 6.5</strong>. Первая часть сервера TCP, использующего функцию poll</p>
     <p><code>//tcpcliserv/tcpservpoll01.с</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <p><code> 2 #include &lt;1imits.h&gt; /* для OPEN_MAX */</code></p>
     <empty-line/>
     <p><code> 3 int</code></p>
     <p><code> 4 main(int argc, char **argv)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  int i, maxi, listenfd, connfd, sockfd;</code></p>
     <p><code> 7  int nready;</code></p>
     <p><code> 8  ssize_t n;</code></p>
     <p><code> 9  char buf[MAXLINE];</code></p>
     <p><code>10  socklen_t clilen;</code></p>
     <p><code>11  struct pollfd client[OPEN_MAX];</code></p>
     <p><code>12  struct sockaddr_in cliaddr, servaddr;</code></p>
     <empty-line/>
     <p><code>13  listenfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code>14  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>15  servaddr.sin_family = AF_INET;</code></p>
     <p><code>16  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>17  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <empty-line/>
     <p><code>18  Bind(listenfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>19  Listen(listenfd, LISTENQ);</code></p>
     <empty-line/>
     <p><code>20  client[0].fd = listenfd;</code></p>
     <p><code>21  client[0].events = POLLRDNORM;</code></p>
     <p><code>22  for (i = 1; i &lt; OPEN_MAX; i++)</code></p>
     <p><code>23   client[i].fd = -1; /* -1 означает, что элемент свободен */</code></p>
     <p><code>24  maxi = 0; /* максимальный индекс массива client[] */</code></p>
     <subtitle>Размещение массива структур pollfd в памяти</subtitle>
     <p><code>11</code> Мы объявляем массив структур <code>pollfd</code> размером <code>OPEN_MAX</code>. Не существует простого способа определить максимальное число дескрипторов, которые могут быть открыты процессом. Мы снова столкнемся с этой проблемой в листинге 13.1. Один из способов ее решения — вызвать функцию POSIX <code>sysconf</code> с аргументом <code>_SC_OPEN_MAX</code> [110, с. 42-44], а затем динамически выделять в памяти место для массива соответствующего размера. Однако функция <code>sysconf</code> может возвратить некое «неопределенное» значение, и в этом случае нам придется задавать ограничение самим. Здесь мы используем только константу <code>OPEN_MAX</code> стандарта POSIX.</p>
     <subtitle>Инициализация</subtitle>
     <p><code>20-24</code> Мы используем первый элемент в массиве <code>client</code> для прослушиваемого сокета и присваиваем дескрипторам для оставшихся элементов -1. Мы также задаем в качестве аргумента функции <code>poll</code> событие <code>POLLRDNORM</code>, чтобы получить уведомление от этой функции в том случае, когда новое соединение будет готово к приему. Переменная <code>maxi</code> содержит максимальный индекс массива <code>client</code>, используемый в настоящий момент.</p>
     <p>Вторая часть нашей функции приведена в листинге 6.6.</p>
     <p><strong>Листинг 6.6</strong>. Вторая часть сервера TCP, использующего функцию poll</p>
     <p><code>//tcpcliserv/tcpservpoll01.c</code></p>
     <p><code>25  for (;;) {</code></p>
     <p><code>26   nready = Poll(client, maxi + 1, INFTIM);</code></p>
     <empty-line/>
     <p><code>27   if (client[0].revents &amp; POLLRDNORM) { /* новое соединение</code></p>
     <p><code>                                              с клиентом */</code></p>
     <p><code>28    clilen = sizeof(cliaddr);</code></p>
     <p><code>29    connfd = Accept(listenfd. (SA*)&amp;cliaddr, &amp;clilen);</code></p>
     <empty-line/>
     <p><code>30    for (i = 1; i &lt; OPEN_MAX; i++)</code></p>
     <p><code>31     if (client[1].fd &lt; 0) {</code></p>
     <p><code>32      client[i].fd = connfd; /* сохраняем дескриптор */</code></p>
     <p><code>33      break;</code></p>
     <p><code>34     }</code></p>
     <p><code>35    if (i == OPEN_MAX)</code></p>
     <p><code>36     err_quit("too many clients");</code></p>
     <empty-line/>
     <p><code>37    client[i].events = POLLRDNORM;</code></p>
     <p><code>38    if (i &gt; maxi)</code></p>
     <p><code>39     maxi = i; /* максимальный индекс в массиве client[] */</code></p>
     <empty-line/>
     <p><code>40    if (--nready &lt;= 0)</code></p>
     <p><code>41     continue; /* больше нет дескрипторов, готовых для чтения */</code></p>
     <p><code>42   }</code></p>
     <p><code>43   for (i = 1; i &lt;= maxi; i++) { /* проверяем все клиенты на наличие</code></p>
     <p><code>                                      данных */</code></p>
     <p><code>44    if ((sockfd = client[i].fd) &lt; 0)</code></p>
     <p><code>45     continue;</code></p>
     <p><code>46    if (client[i].revents &amp; (POLLRDNORM | POLLERR)) {</code></p>
     <p><code>47     if ((n = Read(sockfd, buf, MAXLINE)) &lt; 0) {</code></p>
     <p><code>48      if (errno == ECONNRESET) {</code></p>
     <p><code>49       /* соединение переустановлено клиентом */</code></p>
     <p><code>50       Close(sockfd);</code></p>
     <p><code>51       client[i].fd = -1;</code></p>
     <p><code>52      } else</code></p>
     <p><code>53       err_sys("readline error");</code></p>
     <p><code>54     } else if (n == 0) {</code></p>
     <p><code>55      /* соединение закрыто клиентом */</code></p>
     <p><code>56      Close(sockfd);</code></p>
     <p><code>57      client[i].fd = -1;</code></p>
     <p><code>58     } else</code></p>
     <p><code>59      Writen(sockfd, line, n);</code></p>
     <empty-line/>
     <p><code>60     if (--nready &lt;= 0)</code></p>
     <p><code>61      break; /* больше нет дескрипторов, готовых для чтения */</code></p>
     <p><code>62    }</code></p>
     <p><code>63   }</code></p>
     <p><code>64  }</code></p>
     <p><code>65 }</code></p>
     <subtitle>Вызов функции poll, проверка нового соединения</subtitle>
     <p><code>26-42</code> Мы вызываем функцию <code>poll</code> для ожидания нового соединения либо данных на существующем соединении. Когда новое соединение принято, мы находим первый свободный элемент в массиве <code>client</code> — это первый элемент с отрицательным дескриптором. Обратите внимание, что мы начинаем поиск с индекса 1, поскольку элемент <code>client[0]</code> используется для прослушиваемого сокета. Когда свободный элемент найден, мы сохраняем дескриптор и устанавливаем событие <code>POLLRDNORM</code>.</p>
     <subtitle>Проверка данных на существующем соединении</subtitle>
     <p><code>43-63</code> Два события, которые нас интересуют, — это <code>POLLRDNORM</code> и <code>POLLERR</code>. Второй флаг в элементе <code>event</code> мы не устанавливали, поскольку этот флаг возвращается всегда, если соответствующее условие выполнено. Причина, по которой мы проверяем событие <code>POLLERR</code>, в том, что некоторые реализации возвращают это событие, когда приходит сегмент RST, другие же в такой ситуации возвращают событие <code>POLLRDNORM</code>. В любом случае мы вызываем функцию <code>read</code>, и если произошла ошибка, эта функция возвратит ее. Когда существующее соединение завершается клиентом, мы просто присваиваем элементу <code>fd</code> значение -1.</p>
    </section>
    <section>
     <title>
      <p>6.12. Резюме</p>
     </title>
     <p>В Unix существует пять различных моделей ввода-вывода:</p>
     <p>&#9632; блокируемый ввод-вывод;</p>
     <p>&#9632; неблокируемый ввод-вывод;</p>
     <p>&#9632; мультиплексирование ввода-вывода;</p>
     <p>&#9632; управляемый сигналом ввод-вывод;</p>
     <p>&#9632; асинхронный ввод-вывод.</p>
     <p>По умолчанию используется блокируемый ввод-вывод, и этот вариант встречается наиболее часто. Неблокируемый ввод-вывод и управляемый сигналом ввод-вывод мы рассмотрим в последующих главах. В этой главе мы рассмотрели мультиплексирование ввода-вывода. Асинхронный ввод-вывод определяется в стандарте POSIX, но поддерживающих его реализаций не так много.</p>
     <p>Наиболее часто используемой функцией для мультиплексирования ввода- вывода является функция <code>select</code>. Мы сообщаем этой функции, какие дескрипторы нас интересуют (для чтения, записи или условия ошибки), а также передаем ей максимальное время ожидания и максимальное число дескрипторов (увеличенное на единицу). Большинство вызовов функции <code>select</code> определяют количество дескрипторов, готовых для чтения, и, как мы отметили, единственное условие исключения при работе с сокетами — это прибытие внеполосных данных (см. главу 21). Поскольку функция <code>select</code> позволяет ограничить время блокирования функции, мы используем это свойство в листинге 14.3 для ограничения по времени операции ввода.</p>
     <p>Используя эхо-клиент в пакетном режиме с помощью функции <code>select</code>, мы выяснили, что даже если обнаружен признак конца файла, данные все еще могут находиться в канале на пути к серверу или от сервера. Обработка этого сценария требует применения функции <code>shutdown</code>, которая позволяет воспользоваться таким свойством TCP, как возможность половинного закрытия соединения (half-close feature).</p>
     <p>POSIX определяет функцию <code>pselect</code> (повышающую точность таймера с микросекунд до наносекунд) которой передается новый аргумент — указатель на набор сигналов. Это позволяет избежать ситуации гонок (race condition) при перехвате сигналов, о которой мы поговорим более подробно в разделе 20.5.</p>
     <p>Функция <code>poll</code> из System V предоставляет функциональность, аналогичную функции <code>select</code>. Кроме того, она обеспечивает дополнительную информацию при работе с потоковыми устройствами. POSIX требует наличия и функции <code>select</code>, и функции <code>poll</code>, но первая распространена шире.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Мы говорили, что набор дескрипторов можно присвоить другому набору дескрипторов, используя оператор присваивания языка С. Как это сделать, если набор дескрипторов является массивом целых чисел? (<emphasis>Подсказка</emphasis>: посмотрите на свой системный заголовочный файл <code>&lt;sys/select.h&gt;</code> или <code>&lt;sys/types.h&gt;</code>.)</p>
     <p>2. Описывая в разделе 6.3 условия, при которых функция <code>select</code> сообщает, что дескриптор готов для записи, мы указали, что сокет должен быть неблокируемым, для того чтобы операция записи возвратила положительное значение. Почему?</p>
     <p>3. Что произойдет с программой из листинга 6.1, если мы поставим слово <code>else</code> перед <code>if</code> в строке 19?</p>
     <p>4. В листинге 6.3 добавьте необходимый код, чтобы позволить серверу использовать максимальное число дескрипторов, допустимое ядром (<emphasis>Подсказка</emphasis>: изучите функцию <code>setrlimit</code>.)</p>
     <p>5. Посмотрите, что происходит, если в качестве второго аргумента функции <code>shutdown</code> передается <code>SHUT_RD</code>. Возьмите за основу код клиента TCP, представленный в листинге 5.3, и выполните следующие изменения: вместо номера порта <code>SERV_PORT</code> задайте порт 19 (служба <code>chargen</code>, см. табл. 2.1), а также замените вызов функции <code>str_cli</code> вызовом функции <code>pause</code>. Запустите программу, задав IP-адрес локального узла, на котором выполняется сервер <code>chargen</code>. Просмотрите пакеты с помощью такой программы, как, например, <code>tcpdump</code> (см. раздел В.5). Что происходит?</p>
     <p>6. Почему приложение должно вызывать функцию <code>shutdown</code> с аргументом <code>SHUT_RDWR</code>, вместо того чтобы просто вызвать функцию <code>close</code>?</p>
     <p>7. Что происходит в листинге 6.4, когда клиент отправляет RST для завершения соединения?</p>
     <p>8. Перепишите код, показанный в листинге 6.5, чтобы вызывать функцию <code>sysconf</code> для определения максимального числа дескрипторов и размещения соответствующего массива <code>client</code> в памяти.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 7</p>
     <p>Параметры сокетов</p>
    </title>
    <section>
     <title>
      <p>7.1. Введение</p>
     </title>
     <p>Существуют различные способы получения и установки параметров сокетов:</p>
     <p>&#9632; функции <code>getsockopt</code> и <code>setsockopt</code>;</p>
     <p>&#9632; функция <code>fcntl</code>;</p>
     <p>&#9632; функция <code>ioctl</code>.</p>
     <p>Эту главу мы начнем с описания функций <code>getsockopt</code> и <code>setsockopt</code>. Далее мы приведем пример, в котором выводятся заданные по умолчанию значения параметров, а затем дадим подробное описание всех параметров сокетов. Мы разделили описание параметров на следующие категории: общие, IPv4, IPv6, TCP и SCTP. При первом прочтении главы можно пропустить подробное описание параметров и при необходимости прочесть отдельные разделы, на которые даны ссылки. Отдельные параметры подробно описываются в дальнейших главах, например параметры многоадресной передачи IPv4 и IPv6 мы обсуждаем в разделе 19.5.</p>
     <p>Мы также рассмотрим функцию <code>fcntl</code>, поскольку она реализует предусмотренные стандартом POSIX возможности отключить для сокета блокировку ввода-вывода, включить управление сигналами, а также установить владельца сокета. Функцию <code>ioctl</code> мы опишем в главе 17.</p>
    </section>
    <section>
     <title>
      <p>7.2. Функции getsockopt и setsockopt</p>
     </title>
     <p>Эти две функции применяются только к сокетам.</p>
     <p><code>#include &lt;sys/socket.h&gt;</code></p>
     <empty-line/>
     <p><code>int getsockopt(int <emphasis>sockfd</emphasis>, int <emphasis>level</emphasis>, int <emphasis>optname</emphasis>, void *<emphasis>optval</emphasis>, socklen_t *<emphasis>optlen</emphasis>);</code></p>
     <p><code>int setsockopt(int <emphasis>sockfd</emphasis>, int <emphasis>level</emphasis>, int <emphasis>optname</emphasis>, const void *<emphasis>optval</emphasis>, socklen_t <emphasis>optlen</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают 0 в случае успешного завершения, -1 в случае ошибки</emphasis></code></p>
     <p>Переменная <code>sockfd</code> должна ссылаться на открытый дескриптор сокета. Переменная <code>level</code> определяет, каким кодом должен интерпретироваться параметр: общими программами обработки сокетов или зависящими от протокола программами (например, IPv4, IPv6, TCP или SCTP).</p>
     <p><code>optval</code> — это указатель на переменную, из которой извлекается новое значение параметра с помощью функции <code>setsockopt</code> или в которой сохраняется текущее значение параметра с помощью функции <code>getsockopt</code>. Размер этой переменной задается последним аргументом. Для функции <code>setsockopt</code> тип этого аргумента — <emphasis>значение</emphasis>, а для функции <code>getsockopt</code> — «<emphasis>значение-результат</emphasis>».</p>
     <p>В табл. 7.1 и 7.2 сведены параметры, которые могут запрашиваться функцией <code>getsockopt</code> или устанавливаться функцией <code>setsockopt</code>. В колонке «Тип данных» приводится тип данных того, на что указывает указатель <code>optval</code> для каждого параметра. Две фигурные скобки мы используем, чтобы обозначить структуру, например <code>linger{}</code> обозначает <code>struct linger</code>.</p>
     <empty-line/>
     <p><strong>Таблица 7.1</strong>. Параметры сокетов для функций getsockopt и setsockopt</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">level</th>
       <th align="left" valign="top">optname</th>
       <th align="left" valign="top">get</th>
       <th align="left" valign="top">set</th>
       <th align="left" valign="top">Описание</th>
       <th align="left" valign="top">Флаг</th>
       <th align="left" valign="top">Тип данных</th>
      </tr>
      <tr align="left">
       <td rowspan="17" align="left" valign="top">SOL_SOCKET</td>
       <td align="left" valign="top">SO_BROADCAST</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Позволяет посылать широковещательные дейтаграммы</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_DEBUG</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Разрешает отладку</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_DONTROUTE</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Обходит таблицу маршрутизации</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_ERROR</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">Получает ошибку, ожидающую обработки, и возвращает значение параметра в исходное состояние</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_KEEPALIVE</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Периодически проверяет, находится ли соединение в рабочем состоянии</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_LINGER</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Задерживает закрытие сокета, если имеются данные для отправки</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">linger{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_OOBINLINE</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Оставляет полученные внеполосные данные вместе с обычными данными (inline)</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_RCVBUF</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Размер приемного буфера</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_SNDBUF</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Размер буфера отправки</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_RCVLOWAT</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Минимальное количество данных для приемного буфера сокета</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_SNDLOWAT</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Минимальное количество данных для буфера отправки сокета</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_RCVTIMEO</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Тайм-аут при получении</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">timeval{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_SNDTIMEO</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Тайм-аут при отправке</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">timeval{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_REUSEADDR</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Допускает повторное использование локального адреса</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_REUSEPORT</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Допускает повторное использование локального адреса</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_TYPE</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">Возвращает тип сокета</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SO_USELOOPBACK</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Маршрутизирующий сокет получает копию того, что он отправляет</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td rowspan="13" align="left" valign="top">IPPROTO_IP</td>
       <td align="left" valign="top">IP_HDRINCL</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Включается IP- заголовок</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_OPTIONS</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">В заголовке IPv4 устанавливаются параметры IP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">см. текст</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_RECVDSTADDR</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Возвращает IP-адрес получателя</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_RECVIF</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Возвращает индекс интерфейса, на котором принимается дейтаграмма UDP</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_TOS</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Тип сервиса и приоритет</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_TTL</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Время жизни</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_MULTICAST_IF</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Задает интерфейс для исходящих дейтаграмм</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">in_addr{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_MULTICAST_TTL</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Задает TTL для исходящих дейтаграмм</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">u_char</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_MULTICAST_LOOP</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Разрешает или отменяет отправку копии дейтаграммы на тот узел, откуда она была послана (loopback)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">u_char</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_ADD_MEMBERSHIP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Включение в группу многоадресной передачи</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">ip_mreq{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_DROP_MEMBERSHIP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Отключение от группы многоадресной передачи</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">ip_mreq{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_{BLOCK, UNBLOCK}_SOURCE</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Блокирование и разблокирование источника многоадресной передачи</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">ip_mreq_source{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_{ADD, DROP}_SOURCE_MEMBERSHIP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Присоединение или отключение от многоадресной передачи от источника (source-specific)</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">ip_mreq_source{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPPROTO_ICMPV6</td>
       <td align="left" valign="top">ICMP6_FILTER</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Указывает тип сообщения ICMPv6, которое передается процессу</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">icmp6_filter{}</td>
      </tr>
      <tr align="left">
       <td rowspan="20" align="left" valign="top">IPPROTO_IPV6</td>
       <td align="left" valign="top">IPV6_ADDRFORM</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Меняет формат адреса сокета</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_CHECKSUM</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Отступ поля контрольной суммы для символьных (неструктурированных) сокетов</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_DONTFRAG</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Не фрагментировать, а сбрасывать большие пакеты</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_NEXTHOP</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Задает следующий транзитный адрес</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">sockaddr{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_PATHMTU</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">Получение текущей маршрутной МТУ</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">ip6_mtuinfo{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_RECVDSTOPTS</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Получение параметров адресата</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_RECVHOPLIMIT</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Получение ограничения на количество транзитных узлов при направленной передаче</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_RECVHOPOPTS</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Получение параметров прыжков</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_RECVPATHMTU</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Получение маршрутной MTU</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_RECVPKTINFO</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Получение информации о пакетах</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_RECVRTHDR</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Получение маршрута от источника</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_RECVTCLASS</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Получение класса трафика</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_UNICAST_HOPS</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Предел количества транзитных узлов, задаваемый по умолчанию</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_USE_MIN_MTU</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Использовать минимальную MTU</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_V60NLY</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Отключить совместимость с IPv4</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_XXX</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Вспомогательные данные</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">см. текст</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_MULTICAST_IF</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Задает интерфейс для исходящих дейтаграмм</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">u_int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_MULTICAST_HOPS</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Задает предельное количество транзитных узлов для исходящих широковещательных сообщений</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_MULTICAST_LOOP</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Разрешает или отменяет отправку копии дейтаграммы на тот узел, откуда она была послана (loopback)</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">u_int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_LEAVE_GROUP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Выход из группы многоадресной передачи</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">ipv6_mreq{}</td>
      </tr>
      <tr align="left">
       <td rowspan="6" align="left" valign="top">IPPROTO_IP или IPPROTO_IPV6</td>
       <td align="left" valign="top">MCAST_JOIN_GROUP</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">Присоединение к группе многоадресной передачи</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">group_req{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_LEAVE_GROUP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Выход из группы многоадресной передачи</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">group_source_req{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_BLOCK_SOURCE</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Блокирование источника многоадресной передачи</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">group_source_req{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_UNBLOCK_SOURCE</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Разблокирование источника многоадресной передачи</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">group_source_req{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_JOIN_SOURCE_GROUP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Присоединение к группе многоадресной передачи от источника</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">group_source_req{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_LEAVE_SOURCE_GROUP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Выход из группы многоадресной передачи от источника</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">group_source_req{}</td>
      </tr>
     </table>
     <empty-line/>
     <p><strong>Таблица 7.2</strong>. Параметры сокетов транспортного уровня</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Level</th>
       <th align="left" valign="top">optname</th>
       <th align="left" valign="top">get</th>
       <th align="left" valign="top">set</th>
       <th align="left" valign="top">Описание</th>
       <th align="left" valign="top">Флаг</th>
       <th align="left" valign="top">Тип данных</th>
      </tr>
      <tr align="left">
       <td rowspan="2" align="left" valign="top">IPPROTO_TCP</td>
       <td align="left" valign="top">TCP_MAXSEG</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Максимальный размер сегмента TCP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">TCP_NODELAY</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Отключает алгоритм Нагла</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td rowspan="17" align="left" valign="top">IPPROTO_SCTP</td>
       <td align="left" valign="top">SCTP_ADAPTION_LAYER</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Указание на уровень адаптации</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_setadaption</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_ASSOCINFO</td>
       <td align="left" valign="top">+</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Получение и задание сведений об ассоциации</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_assocparamms{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_AUTOCLOSE</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Автоматическое закрытие</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_DEFAULT_SEND_PARAM</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Параметры отправки но умолчанию</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_sndrcvinfo{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_DISABLE_FRAGMENTS</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Фрагментация SCTP</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_EVENTS</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Уведомление об интересующих событиях</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_event_subscribe{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_GET_PEER_ADDR_INFO</td>
       <td align="left" valign="top">+</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">Получение состояния адреса собеседника</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_paddrinfo{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_I_WANT_MAPPED_V4_ADDR</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Отображение адресов IPv4</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_INITMSG</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Параметры пакета INIT по умолчанию</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_initmsg{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_MAXBURST</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Максимальный размер набора пакетов</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_MAXSEG</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Максимальный размер фрагментации</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_NODELAY</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Отключение алгоритма Нагла</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_PEER_ADDR_PARAMS</td>
       <td align="left" valign="top">+</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Параметры адреса собеседника</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_paddrparams{)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_PRIMARY_ADDR</td>
       <td align="left" valign="top">+</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Основной адрес назначения</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_setprim{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_RTOINFO</td>
       <td align="left" valign="top">+</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Информация RTO</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_rtoinfo{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_SET_PEER_PRIMARY_ADDR</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Основной адрес назначения собеседника</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_setpeerprim{}</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_STATUS</td>
       <td align="left" valign="top">+</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">Получение сведений о статусе ассоциации</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sctp_status{}</td>
      </tr>
     </table>
     <p>Существует два основных типа параметров: двоичные параметры, включающие или отключающие определенное свойство (флаги), и параметры, получающие и возвращающие значения параметров, которые мы можем либо задавать, либо проверять. В колонке «Флаг» указывается, относится ли параметр к флагам. Для флагов при вызове функции <code>getsockopt</code> аргумент <code>*optval</code> является целым числом. Возвращаемое значение <code>*optval</code> нулевое, если параметр отключен, и ненулевое, если параметр включен. Аналогично, функция <code>setsockopt</code> требует ненулевого значения <code>*optval</code> для включения параметра, и нулевого значения — для его выключения. Если в колонке «Флаг» не содержится символа «•», то параметр используется для передачи значения заданного типа между пользовательским процессом и системой.</p>
     <p>В последующих разделах этой главы приводятся дополнительные подробности о параметрах сокетов.</p>
    </section>
    <section>
     <title>
      <p>7.3. Проверка наличия параметра и получение значения по умолчанию</p>
     </title>
     <p>Напишем программу, которая проверяет, поддерживается ли большинство параметров, представленных в табл. 7.1 и 7.2, и если да, то выводит их значения, заданные по умолчанию. В листинге 7.1<a l:href="#n1" type="note">[1]</a> содержатся объявления нашей программы.</p>
     <p><strong>Листинг 7.1</strong>. Объявления для нашей программы, проверяющей параметры сокетов</p>
     <p><code>//sockopt/checkopts.с</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <p><code> 2 #include &lt;netinet/tcp.h&gt; /* определения констант TCP_xxx */</code></p>
     <empty-line/>
     <p><code> 3 union val {</code></p>
     <p><code> 4  int i_val;</code></p>
     <p><code> 5  long l_val;</code></p>
     <p><code> 6  struct linger linger_val;</code></p>
     <p><code> 7  struct timeval timeval_val;</code></p>
     <p><code> 8 } val;</code></p>
     <empty-line/>
     <p><code> 9 static char *sock_str_flag(union val*, int);</code></p>
     <p><code>10 static char *sock_str_int(union val*, int);</code></p>
     <p><code>11 static char *sock_str_linger(union val*, int);</code></p>
     <p><code>12 static char *sock_str_timeval(union val*, int);</code></p>
     <empty-line/>
     <p><code>13 struct sock_opts {</code></p>
     <p><code>14  const char *opt_str;</code></p>
     <p><code>15  int opt_level;</code></p>
     <p><code>16  int opt_name;</code></p>
     <p><code>17  char *(*opt_val_str)(union val*, int);</code></p>
     <p><code>18 } sock_opts[] = {</code></p>
     <p><code>19  { "SO_BROADCAST",      SOL_SOCKET,   SO_BROADCAST,   sock_str_flag },</code></p>
     <p><code>20  { "SO_DEBUG",          SOL_SOCKET,   SO_DEBUG,       sock_str_flag },</code></p>
     <p><code>21  { "SO_DONTROUTE",      SOL_SOCKET,   SO_DONTROUTE,   sock_str_flag },</code></p>
     <p><code>22  { "SO_ERROR",          SOL_SOCKET,   SO_ERROR,       sock_str_int },</code></p>
     <p><code>23  { "SO_KEEPALIVE",      SOL_SOCKET,   SO_KEEPALIVE,   sock_str_flag },</code></p>
     <p><code>24  { "SO_LINGER",         SOL_SOCKET,   SO_LINGER,      sock_str_linger },</code></p>
     <p><code>25  { "SO_OOBINLINE",      SOL_SOCKET,   SO_OOBINLINE,   sock_str_flag },</code></p>
     <p><code>26  { "SO_RCVBUF",         SOL_SOCKET,   SO_RCVBUF,      sock_str_int },</code></p>
     <p><code>27  { "SO_SNDBUF",         SOL_SOCKET,   SO_SNDBUF,      sock_str_int },</code></p>
     <p><code>28  { "SO_RCVLOWAT",       SOL_SOCKET,   SO_RCVLOWAT,    sock_str_int },</code></p>
     <p><code>29  { "SO_SNDLOWAT",       SOL_SOCKET,   SO_SNDLOWAT,    sock_str_int },</code></p>
     <p><code>30  { "SO_RCVTIMEO",       SOL_SOCKET,   SO_RCVTIMEO,    sock_str_timeval },</code></p>
     <p><code>31  { "SO_SNDTIMEO",       SOL_SOCKET,   SO_SNDTIMEO,    sock_str_timeval },</code></p>
     <p><code>32  { "SO_REUSEADDR",      SOL_SOCKET,   SO_REUSEADDR,   sock_str_flag },</code></p>
     <p><code>33 #ifdef SO_REUSEPORT</code></p>
     <p><code>34  { "SO_REUSEPORT",      SOL_SOCKET,   SO_REUSEPORT,   sock_str_flag },</code></p>
     <p><code>35 #else</code></p>
     <p><code>36  { "SO_REUSEPORT",      0,            0, NULL },</code></p>
     <p><code>37 #endif</code></p>
     <p><code>38  { "SO_TYPE",           SOL_SOCKET,   SO_TYPE,        sock_str_int },</code></p>
     <p><code>39  { "SO_USELOOPBACK",    SOL_SOCKET,   SO_USELOOPBACK, sock_str_flag },</code></p>
     <p><code>40  { "IP_TOS",            IPPROTO_IP,   IP_TOS,         sock_str_int },</code></p>
     <p><code>41  { "IP_TTL",            IPPROTO_IP,   IP_TTL,         sock_str_int },</code></p>
     <p><code>42  { "IPV6_DONTFRAG",     IPPROTO_IPV6, IPV6_DONTFRAG,  sock_str_flag },</code></p>
     <p><code>43  { "IPV6_UNICAST_HOPS", IPPROTO_IPV6, IPV6_UNICAST_HOPS, sock_str_int },</code></p>
     <p><code>44  { "IPV6_V6ONLY",       IPPROTO_IPV6, IPV6_V6ONLY,    sock_str_flag },</code></p>
     <p><code>45  { "TCP_MAXSEG",        IPPROTO_TCP,  TCP_MAXSEG,     sock_str_int },</code></p>
     <p><code>46  { "TCP_NODELAY",       IPPROTO_TCP,  TCP_NODELAY,    sock_str_flag },</code></p>
     <p><code>47  { "SCTP_AUTOCLOSE",    IPPROTO_SCTP, SCTP_AUTOCLOSE, sock_str_int },</code></p>
     <p><code>48  { "SCTP_MAXBURST",     IPPROTO_SCTP, SCTP_MAXBURST,  sock_str_int },</code></p>
     <p><code>49  { "SCTP_MAXSEG",       IPPROTO_SCTP, SCTP_MAXSEG,    sock_str_int },</code></p>
     <p><code>50  { "SCTP_NODELAY",      IPPROTO_SCTP, SCTP_NODELAY,   sock_str_flag },</code></p>
     <p><code>51  { NULL,                0,            0,              NULL }</code></p>
     <p><code>52 };</code></p>
     <subtitle>Объявление объединения возможных значений</subtitle>
     <p><code>3-9</code> Наше объединение <code>val</code> содержит по одному элементу для каждого возможного возвращаемого значения из функции <code>getsockopt</code>.</p>
     <subtitle>Задание прототипов функций</subtitle>
     <p><code>10-13</code> Мы определяем прототипы для четырех функций, которые вызываются для вывода значения данного параметра сокета.</p>
     <subtitle>Задание структуры и инициализация массива</subtitle>
     <p><code>14-46</code> Наша структура <code>sock_opts</code> содержит всю информацию, которая необходима, чтобы вызвать функцию <code>getsockopt</code> для каждого из параметров сокета и вывести его текущее значение. Последний элемент, <code>opt_val_str</code>, является указателем на одну из четырех функций, которые выводят значение параметра. Мы размещаем в памяти и инициализируем массив этих структур, каждый элемент которого соответствует одному параметру сокета.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Не все реализации поддерживают полный набор параметров сокетов. Чтобы определить, поддерживается ли данный параметр, следует использовать #ifdef или #if defined, как показано для параметра SO_REUSEPORT. Для полноты картины требуется обработать подобным образом все параметры, но в книге мы пренебрегаем этим, потому что #ifdef только удлиняет показанный код и не влияет на суть дела.</p>
     </cite>
     <p>В листинге 7.2 показана наша функция <code>main</code>.</p>
     <p><strong>Листинг 7.2</strong>. Функция main для проверки параметров сокетов</p>
     <p><code>//sockopt/checkopts.c</code></p>
     <p><code>53 int</code></p>
     <p><code>54 main(int argc, char **argv)</code></p>
     <p><code>55 {</code></p>
     <p><code>56  int fd;</code></p>
     <p><code>57  socklen_t len;</code></p>
     <p><code>58  struct sock_opts *ptr;</code></p>
     <empty-line/>
     <p><code>59  for (ptr = sock_opts; ptr-&gt;opt_str != NULL; ptr++) {</code></p>
     <p><code>60   printf("%s: ptr-&gt;opt_str);</code></p>
     <p><code>61   if (ptr-&gt;opt_val_str == NULL)</code></p>
     <p><code>62    printf("(undefined)\n");</code></p>
     <p><code>63   else {</code></p>
     <p><code>64    switch(ptr-&gt;opt_level) {</code></p>
     <p><code>65    case SOL_SOCKET:</code></p>
     <p><code>66    case IPPROTO_IP:</code></p>
     <p><code>67    case IPPROTO_TCP:</code></p>
     <p><code>68     fd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <p><code>69     break;</code></p>
     <p><code>70 #ifdef IPV6</code></p>
     <p><code>71    case IPPROTO_IPV6:</code></p>
     <p><code>72     fd = Socket(AF_INET6, SOCK_STREAM, 0);</code></p>
     <p><code>73     break;</code></p>
     <p><code>74 #endif</code></p>
     <p><code>75 #ifdef IPPROTO_SCTP</code></p>
     <p><code>76    case IPPROTO_SCTP:</code></p>
     <p><code>77     fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);</code></p>
     <p><code>78     break;</code></p>
     <p><code>79 #endif</code></p>
     <p><code>80    default:</code></p>
     <p><code>81     err_quit("Can't create fd for level %d\n", ptr-&gt;opt_level);</code></p>
     <p><code>82    }</code></p>
     <empty-line/>
     <p><code>83    len = sizeof(val);</code></p>
     <p><code>84    if (getsockopt(fd, ptr-&gt;opt_level, ptr-&gt;opt_name,</code></p>
     <p><code>85     &amp;val, &amp;len) == -1) {</code></p>
     <p><code>86     err_ret("getsockopt error");</code></p>
     <p><code>87    } else {</code></p>
     <p><code>88     printf("default = %s\n", (*ptr-&gt;opt_val_str)(&amp;val, len));</code></p>
     <p><code>89    }</code></p>
     <p><code>90    close(fd);</code></p>
     <p><code>91   }</code></p>
     <p><code>92  }</code></p>
     <p><code>93  exit(0);</code></p>
     <p><code>94 }</code></p>
     <subtitle>Перебор всех параметров</subtitle>
     <p><code>59-63</code> Мы перебираем все элементы нашего массива. Если указатель <code>opt_val_str</code> пустой, то параметр не определен реализацией (что, как мы показали, возможно для <code>SO_REUSEPORT</code>).</p>
     <subtitle>Создание сокета</subtitle>
     <p><code>63-82</code> Мы создаем сокет, на котором проверяем действие параметров. Для проверки параметров сокета и уровней IPv4 и TCP мы используем сокет IPv4 TCP. Для проверки параметров сокетов уровня IPv6 мы используем сокет IPv6 TCP, а для проверки параметров SCTP — сокет IPv4 SCTP.</p>
     <subtitle>Вызов функции getsockopt</subtitle>
     <p><code>83-87</code> Мы вызываем функцию <code>getsockopt</code>, но не завершаем ее выполнение, если возвращается ошибка. Многие реализации определяют имена некоторых параметров сокетов, даже если не поддерживают эти параметры. Неподдерживаемые параметры выдают ошибку <code>ENOPROTOOPT</code>.</p>
     <subtitle>Вывод значения параметра по умолчанию</subtitle>
     <p><code>88-89</code> Если функция <code>getsockopt</code> успешно завершается, мы вызываем нашу функцию для преобразования значения параметра в строку и выводим эту строку.</p>
     <p>В листинге 7.1 мы показали четыре прототипа функций, по одному для каждого типа возвращаемого значения параметра. В листинге 7.3 показана одна из этих функций, <code>sock_str_flag</code>, которая выводит значение параметра, являющегося флагом. Другие три функции аналогичны этой.</p>
     <p><strong>Листинг 7.3</strong>. Функция sock_str_flag: преобразование флага в строку</p>
     <p><code>//sockopt/checkopts.с</code></p>
     <p><code> 95 static char strres[128];</code></p>
     <empty-line/>
     <p><code> 96 static char *</code></p>
     <p><code> 97 sock_str_flag(union val *ptr, int len)</code></p>
     <p><code> 98 {</code></p>
     <p><code> 99  if (len != sizeof(int))</code></p>
     <p><code>100   snprint(strres, sizeof(strres), "size (%d) not sizeof(int)", len);</code></p>
     <p><code>101  else</code></p>
     <p><code>102   snprintf(strres, sizeof(strres),</code></p>
     <p><code>103    "%s", (ptr-&gt;i_val == 0) ? "off" : "on");</code></p>
     <p><code>104  return(strres);</code></p>
     <p><code>105 }</code></p>
     <p><code>99-104</code> Вспомните, что последний аргумент функции <code>getsockopt</code> — это аргумент типа «значение-результат». Первое, что мы проверяем, — это то, что размер значения, возвращаемого функцией <code>getsockopt</code>, совпадает с предполагаемым. В зависимости от того, является ли значение флага нулевым или нет, возвращается строка <code>off</code> или <code>on</code>.</p>
     <p>Выполнение этой программы под FreeBSD 4.8 с пакетами обновлений KAME SCTP дает следующий вывод:</p>
     <p><code>freebsd % <strong>checkopts</strong></code></p>
     <p><code>SO_BROADCAST: default = off</code></p>
     <p><code>SO_DEBUG: default = off</code></p>
     <p><code>SO_DONTROUTE: default = off</code></p>
     <p><code>SO_ERROR: default = 0</code></p>
     <p><code>SO_KEEPALIVE: default = off</code></p>
     <p><code>SO_LINGER: default = l_onoff = 0, l_linger = 0</code></p>
     <p><code>SO_OOBINLINE: default = off</code></p>
     <p><code>SO_RCVBUF: default = 57344</code></p>
     <p><code>SO_SNDBUF: default = 32768</code></p>
     <p><code>SO_RCVLOWAT: default = 1</code></p>
     <p><code>SO_SNDLOWAT: default = 2048</code></p>
     <p><code>SO_RCVTIMEO: default = 0 sec, 0 usec</code></p>
     <p><code>SO_SNDTIMEO: default = 0 sec, 0 usec</code></p>
     <p><code>SO_REUSEADDR: default = off</code></p>
     <p><code>SO_REUSEPORT: default = off</code></p>
     <p><code>SO_TYPE: default = 1</code></p>
     <p><code>SO_USELOOPBACK: default = off</code></p>
     <p><code>IP_TOS: default = 0</code></p>
     <p><code>IP_TTL: default = 64</code></p>
     <p><code>IPV6_DONTFRAG: default = off</code></p>
     <p><code>IPV6_UNICAST_HOPS: default = -1</code></p>
     <p><code>IPV6_V6ONLY: default = off</code></p>
     <p><code>TCP_MAXSEG: default = 512</code></p>
     <p><code>TCP_NODELAY: default = off</code></p>
     <p><code>SCTP_AUTOCLOSE: default = 0</code></p>
     <p><code>SCTP_MAXBURST: default = 4</code></p>
     <p><code>SCTP_MAXSEG: default = 1408</code></p>
     <p><code>SCTP_NODELAY: default = off</code></p>
     <p>Значение 1, возвращаемое для параметра <code>SO_TYPE</code>, для этой реализации соответствует <code>SOCK_STREAM</code>.</p>
    </section>
    <section>
     <title>
      <p>7.4. Состояния сокетов</p>
     </title>
     <p>Для некоторых параметров сокетов время их установки или получения зависит некоторым образом от состояния сокета. Далее мы обсудим эту зависимость для тех параметров, к которым это относится.</p>
     <p>Следующие параметры сокетов наследуются присоединенным сокетом TCP от прослушиваемого сокета [128, с. 462-463]: <code>SO_DEBUG</code>, <code>SO_DONTROUTE</code>, <code>SO_KEEPALIVE</code>, <code>SO_LINGER</code>, <code>SO_OOBINLINE</code>, <code>SO_RCVBUF</code>, <code>SO_RCVLOWAT</code>, <code>SO_SNDBUF</code>, <code>SO_SNDLOWAT</code>, <code>TCP_MAXSEG</code> и <code>TCP_NODELAY.</code> Это важно для TCP, поскольку присоединенный сокет не возвращается серверу функцией <code>accept</code>, пока трехэтапное рукопожатие не завершится на уровне TCP. Если при завершении трехэтапного рукопожатия мы хотим убедиться, что один из этих параметров установлен для присоединенного сокета, нам следует установить этот параметр еще для прослушиваемого сокета.</p>
    </section>
    <section>
     <title>
      <p>7.5. Общие параметры сокетов</p>
     </title>
     <section>
      <p>Мы начнем с обсуждения общих параметров сокетов. Эти параметры не зависят от протокола (то есть они управляются не зависящим от протокола кодом внутри ядра, а не отдельным модулем протокола, такого как IPv4), но некоторые из них применяются только к определенным типам сокетов. Например, несмотря на то что параметр сокета <code>SO_BROADCAST</code> называется общим, он применяется только к дейтаграммным сокетам.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SO_BROADCAST</p>
      </title>
      <p>Этот параметр управляет возможностью отправки широковещательных сообщений. Широковещательная передача поддерживается только для сокетов дейтаграмм и только в сетях, поддерживающих концепцию широковещательных сообщений (Ethernet, Token Ring и т.д.). Широковещательная передача в сетях типа «точка-точка» или по ориентированному на установление соединения транспортному протоколу типа SCTP или TCP, неосуществима. Более подробно о широковещательной передаче мы поговорим в главе 18.</p>
      <p>Поскольку перед отправкой широковещательной дейтаграммы приложение должно установить этот параметр сокета, оно не сможет отправить широковещательное сообщение, если это не предполагалось заранее. Например, приложение UDP может принять IP-адрес получателя в качестве аргумента командной строки, но оно может и не предполагать, что пользователь вводит широковещательный адрес. Проверку того, является ли данный адрес широковещательным, осуществляет не приложение, а ядро: если адрес получателя является широковещательным адресом и данный параметр сокета не установлен, возвратится ошибка <code>EACCESS</code> [128, с. 233].</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SO_DEBUG</p>
      </title>
      <p>Этот параметр поддерживается только протоколом TCP. При подключении к сокету TCP ядро отслеживает подробную информацию обо всех пакетах, отправленных или полученных протоколом TCP для сокета. Они хранятся в кольцевом буфере внутри ядра, который можно проверить с помощью программы <code>trpt</code>. В [128, с. 916-920] приводится более подробная информация и пример использования этого параметра.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SO_DONTROUTE</p>
      </title>
      <p>Этот параметр указывает, что исходящие пакеты должны миновать обычные механизмы маршрутизации соответствующего протокола. Например, в IPv4 пакет направляется на соответствующий локальный интерфейс, который задается адресом получателя, а именно сетевым адресом и маской подсети. Если локальный интерфейс не может быть определен по адресу получателя (например, получателем не является другой конец соединения типа «точка-точка» или он не находится в той же сети), возвращается ошибка <code>ENETUNREACH</code>.</p>
      <p>Эквивалент этого параметра можно также применять к индивидуальным дейтаграммам, используя флаг <code>MSG_DONTROUTE</code> с функциями <code>send</code>, <code>sendto</code> или <code>sendmsg</code>.</p>
      <p>Этот параметр часто используется демонами маршрутизации (<code>routed</code> и <code>gated</code>) для того, чтобы миновать таблицу маршрутизации (в случае, если таблица маршрутизации неверна) и заставить пакет отправиться на определенный интерфейс.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SO_ERROR</p>
      </title>
      <p>Когда на сокете происходит ошибка, модуль протокола в ядре, происходящем от Беркли, присваивает переменной <code>so_error</code> для этого сокета одно из стандартных значений Unix <code>E<emphasis>xxx</emphasis></code>. Это так называемая <emphasis>ошибка, требующая обработки</emphasis> (<emphasis>pending error</emphasis>) для данного сокета. Процесс может быть немедленно оповещен об ошибке одним из двух способов:</p>
      <p>1. Если процесс блокируется в вызове функции <code>select</code> (см. раздел 6.3), ожидая готовности данного сокета к чтению или записи, функция <code>select</code> возвращает управление и уведомляет процесс о соответствующем состоянии готовности.</p>
      <p>2. Если процесс использует управляемый сигналом ввод-вывод (см. главу 25), для него или для группы таких процессов генерируется сигнал <code>SIGIO</code>.</p>
      <p>Процесс может получить значение переменной <code>so_error</code>, указав параметр сокета <code>SO_ERROR</code>. Целое значение, возвращаемое функцией <code>getsockopt</code>, является кодом ошибки, требующей обработки. Затем значение переменной <code>so_error</code> сбрасывается ядром в 0 [128, с. 547].</p>
      <p>Если процесс вызывает функцию <code>read</code> и возвращаемых данных нет, а значение <code>so_error</code> ненулевое, то функция <code>read</code> возвращает -1 с <code>errno</code>, которой присвоено значение переменной <code>so_error</code> [128, с. 516]. Это значение <code>so_error</code> затем сбрасывается в 0. Если в очереди для сокета есть данные, эти данные возвращаются функцией <code>read</code> вместо кода ошибки. Если значение <code>so_error</code> ненулевое, то при вызове процессом функции <code>write</code> возвращается -1 с <code>errno</code>, равной значению переменной <code>so_error</code> [128, с. 495], а значение <code>so_error</code> сбрасывается в 0.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В коде, показанном на с. 495 [128], есть ошибка: so_error не сбрасывается в 0. Она была выявлена в реализации BSD/OS. Всегда, когда для сокета возвращается ошибка, требующая обработки, so_error должна быть сброшена в 0.</p>
      </cite>
      <p>Здесь вы впервые встречаетесь с параметром сокета, который можно получить, но нельзя установить.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SO_KEEPALIVE</p>
      </title>
      <p>Когда параметр <code>SO_KEEPALIVE</code> установлен для сокета TCP и в течение двух часов не происходит обмена данными по сокету в любом направлении, TCP автоматически посылает собеседнику проверочное сообщение (keepalive probe). Это сообщение — сегмент TCP, на который собеседник должен ответить. Далее события могут развиваться по одному из трех сценариев.</p>
      <p>1. Собеседник отвечает, присылая ожидаемый сегмент ACK. Приложение не получает уведомления (поскольку все в порядке). TCP снова отправит одно проверочное сообщение еще через два часа отсутствия активности в этом соединении.</p>
      <p>2. Собеседник отвечает, присылая сегмент RST, который сообщает локальному TCP, что узел собеседника вышел из строя и перезагрузился. Ошибка сокета, требующая обработки, устанавливается равной <code>ECONNRESET</code> и сокет закрывается.</p>
      <p>3. На проверочное сообщение не приходит ответ от собеседника. Код TCP, происходящий от Беркли, отправляет восемь дополнительных проверочных сообщений с интервалом в 75 с, пытаясь выявить ошибку. TCP прекратит попытки, если ответа не последует в течение 11 мин и 15 с после отправки первого сообщения.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>HP-UX обрабатывает поверочные сообщения так же, как и обычные данные, то есть второе сообщение отсылается по истечении периода повторной передачи, после чего для каждого последующего пакета интервал ожидания удваивается, пока не будет достигнут максимальный интервал (по умолчанию — 10 мин).</p>
      </cite>
      <p>Если на все проверочные сообщения TCP не приходит ответа, то ошибка сокета, требующая обработки, устанавливается в <code>ETIMEDOUT</code> и сокет закрывается. Но если сокет получает ошибку ICMP (Internet Control Message Protocol — протокол управляющих сообщений Интернета) в ответ на одно из проверочных сообщений, то возвращается одна из соответствующих ошибок (см. табл. А.5 и А.6), но сокет также закрывается. Типичная ошибка ICMP в этом сценарии — <code>Host unreachable</code> (Узел недоступен) — указывает на то, что узел собеседника не вышел из строя, а только является недоступным. При этом ошибка, ожидающая обработки, устанавливается в <code>EHOSTUNREACH</code>. Это может произойти из-за отказа сети или при выходе удаленного узла из строя и обнаружении этого последним маршрутизатором.</p>
      <p>В главе 23 [111] и на с. 828-831 [128] содержатся дополнительные подробности об этом параметре.</p>
      <p>Без сомнения, наиболее типичный вопрос, касающийся этого параметра, состоит в том, могут ли изменяться временные параметры (обычно нас интересует возможность сокращения двухчасовой задержки). В разделе 7.9 мы описываем новый параметр <code>TCP_KEEPALIVE</code>, но он не реализован достаточно широко. В приложении Е [111] обсуждается изменение временных параметров для различных ядер. Необходимо учитывать, что большинство ядер обрабатывают эти параметры глобально, и поэтому сокращение времени ожидания, например с 2 час до 15 мин, повлияет на все сокеты узла, для которых включен параметр <code>SO_KEEPALIVE</code>.</p>
      <p>Назначение этого параметра — обнаружение сбоя на <emphasis>узле</emphasis> собеседника. Если <emphasis>процесс</emphasis> собеседника выходит из строя, его TCP отправит через соединение сегмент FIN, который мы сможем легко обнаружить с помощью функции <code>select</code> (поэтому мы использовали функцию <code>select</code> в разделе 6.4). Также нужно понимать, что если на проверочное сообщение не приходит ответа (сценарий 3), то это не обязательно означает, что на узле сервера произошел сбой и существует вероятность, что TCP закроет действующее соединение. Если, например, промежуточный маршрутизатор вышел из строя на 15 мин, то эти 15 мин полностью перекрывают период отправки проверочных сообщений от нашего узла, равный 11 мин и 15 с. Поэтому правильнее было бы назвать эту функцию не проверкой жизнеспособности (keep-alive), а контрольным выстрелом (make-dead), поскольку она может завершать еще открытые соединения.</p>
      <p>Этот параметр обычно используется серверами, хотя его могут использовать и клиенты. Серверы используют его, поскольку большую часть своего времени они проводят в блокированном состоянии, ожидая ввода по соединению TCP, то есть в ожидании запроса клиента. Но если узел клиента выходит из строя, процесс сервера никогда не узнает об этом и сервер будет продолжать ждать ввода данных, которые никогда не придут. Это называется <emphasis>наполовину открытым соединением</emphasis> (<emphasis>half-open connection</emphasis>). Данный параметр позволяет обнаружить наполовину открытые соединения и завершить их.</p>
      <p>Некоторые серверы, особенно серверы FTP, предоставляют приложению тайм- аут, часто до нескольких минут. Это выполняется самим приложением, обычно при вызове функции <code>read</code>, когда считывается следующая команда клиента. Этот тайм-аут не связан с данным параметром сокета.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В SCTP имеется механизм проверки пульса (heartbeat), аналогичный механизму проверочных сообщений (keep-alive) TCP. Этот механизм настраивается при помощи элементов параметра сокета SCTP_SET_PEER_ADDR_PARAMS, который будет описан далее, а не при помощи параметра SO_KEEPALIVE. Последний полностью игнорируется сокетом SCTP и не мешает работе механизма проверки пульса.</p>
      </cite>
      <p>В табл. 7.3 суммируются различные методы, применяемые для обнаружения того, что происходит на другом конце соединения TCP. Когда мы говорим «использование функции <code>select</code> для проверки готовности к чтению», мы имеем в виду вызов функции <code>select</code> для проверки, готов ли сокет для чтения.</p>
      <empty-line/>
      <p><strong>Таблица 7.3</strong>. Методы определения различных условий TCP</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Сценарий</th>
        <th align="left" valign="top">Процесс собеседника выходит из строя</th>
        <th align="left" valign="top">Узел собеседника выходит из строя</th>
        <th align="left" valign="top">Узел собеседника недоступен</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Наш TCP активно посылает данные</td>
        <td align="left" valign="top">TCP собеседника посылает сегмент FIN, что мы можем сразу же обнаружить, используя функцию select для проверки готовности к чтению. Если TCP посылает второй сегмент, TCP собеседника посылает в ответ сегмент RST. Если TCP посылает еще один сегмент, наш TCP посылает сигнал SIGPIPE</td>
        <td align="left" valign="top">По истечении времени ожидания TCP возвращается ошибка ETIMEDOUT</td>
        <td align="left" valign="top">По истечении времени ожидания TCP возвращается ошибка ETIMEDOUT</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Наш TCP активно принимает данные</td>
        <td align="left" valign="top">TCP собеседника посылает сегмент FIN, который мы прочитаем как признак конца файла (возможно, преждевременный)</td>
        <td align="left" valign="top">Мы больше не получаем никаких данных</td>
        <td align="left" valign="top">Мы больше не получаем никаких данных</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Соединение неактивно, посылается пробный пакет</td>
        <td align="left" valign="top">TCP собеседника посылает сегмент FIN, который мы можем сразу же обнаружить, используя функцию select для проверки готовности к чтению</td>
        <td align="left" valign="top">По истечении двух часов отсутствия активности отсылается 9 сообщений для проверки наличия связи с собеседником, а затем возвращается ошибка ETIMEDOUT</td>
        <td align="left" valign="top">По истечении двух часов отсутствия активности отсылается 9 сообщений для проверки наличия связи с собеседником, а затем возвращается ошибка ETIMEDOUT</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Соединение неактивно, не посылается проверочное сообщение</td>
        <td align="left" valign="top">TCP собеседника посылает сегмент FIN, который мы можем сразу же обнаружить, используя функцию select для проверки готовности к чтению</td>
        <td align="left" valign="top">Ничего не происходит</td>
        <td align="left" valign="top">Ничего не происходит</td>
       </tr>
      </table>
     </section>
     <section>
      <title>
       <p>Параметр сокета SO_LINGER</p>
      </title>
      <p>Этот параметр определяет, как работает функция <code>close</code> для протоколов, ориентированных на установление соединения (например, TCP и SCTP, но не UDP). По умолчанию функция <code>close</code> возвращает управление немедленно, но если в отправляющем буфере сокета остаются какие-либо данные, система попытается доставить данные собеседнику.</p>
      <p>Параметр сокета <code>SO_LINGER</code> позволяет нам изменять поведение по умолчанию. Для этого необходимо, чтобы между пользовательским процессом и ядром была передана следующая структура, определяемая в заголовочном файле <code>&lt;sys/socket.h&gt;</code>:</p>
      <p><code>struct linger {</code></p>
      <p><code> int l_onoff; /* 0=off, ненулевое значение=on */ int l_linger;</code></p>
      <p><code>              /* время ожидания, в POSIX измеряется в секундах */</code></p>
      <p><code>};</code></p>
      <p>Вызов функции <code>setsockopt</code> приводит к одному из трех следующих сценариев в зависимости от значений двух элементов структуры <code>linger</code>.</p>
      <p>1. Если <code>l_onoff</code>  имеет нулевое значение, параметр выключается. Значение <code>l_linger</code> игнорируется и применяется ранее рассмотренный заданный по умолчанию сценарий TCP: функция <code>close</code> завершается немедленно.</p>
      <p>2. Если значение <code>l_onoff</code> ненулевое, а <code>l_linger</code> равно нулю, TCP сбрасывает соединение, когда оно закрывается [128, с. 1019–1020], то есть TCP игнорирует все данные, остающиеся в буфере отправки сокета, и отправляет собеседнику сегмент RST, а не обычную последовательность завершения соединения, состоящую из четырех пакетов (см. раздел 2.5). Пример мы покажем в листинге 16.14. Тогда не наступает состояние TCP TIME_WAIT, но из-за этого возникает возможность создания другого воплощения (incarnation) этого соединения в течение 2MSL секунд (удвоенное максимальное время жизни сегмента). Оставшиеся старые дублированные сегменты из только что завершенного соединения могут быть доставлены новому воплощению, что приведет к ошибкам (см. раздел 2.6).</p>
      <p>При указанных выше значениях <code>l_onoff</code> и <code>l_linger</code> SCTP также выполняет аварийное закрытие сокета, отправляя собеседнику пакет ABORT (см. раздел 9.2 [117]).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Отдельные выступления в Usenet звучат в защиту использования этой возможности, поскольку она позволяет избежать состояния TIME_WAIT и снова запустить прослушивающий сервер, даже если соединения все еще используются с известным портом сервера. Так не нужно делать, поскольку это может привести к искажению данных, как показано в RFC 1337 [11]. Вместо этого перед вызовом функции bind на стороне сервера всегда нужно использовать параметр сокета SO_REUSEADDR, как показано далее. Состояние TIME_WAIT — наш друг, так как оно предназначено для того, чтобы помочь нам дождаться, когда истечет время жизни в сети старых дублированных сегментов. Вместо того, чтобы пытаться избежать этого состояния, следует понять его назначение (см. раздел 2.6).</p>
       <p>Тем не менее в некоторых обстоятельствах использование аварийного закрытия может быть оправдано. Одним из примеров является сервер терминалов RS-232, который может навечно зависнуть в состоянии CLOSE_WAIT, пытаясь доставить данные на забитый порт. Если же он получит сегмент RST, он сможет сбросить накопившиеся данные и заново инициализировать порт.</p>
      </cite>
      <p>3. Если оба значения — <code>l_onoff</code> и <code>l_linger</code> — ненулевые, то при закрытии сокета ядро будет <emphasis>ждать</emphasis> (<emphasis>linger</emphasis>) [128, с. 472]. То есть если в буфере отправки сокета еще имеются какие-либо данные, процесс входит в состояние ожидания до тех пор, пока либо все данные не будут отправлены и подтверждены другим концом TCP, либо не истечет время ожидания. Если сокет был установлен как неблокируемый (см. главу 16), он не будет ждать завершения выполнения функции <code>close</code>, даже если время задержки ненулевое. При использовании этого свойства параметра <code>SO_LINGER</code> приложению важно проверить значение, возвращаемое функцией <code>close</code>. Если время ожидания истечет до того, как оставшиеся данные будут отправлены и подтверждены, функция <code>close</code> возвратит ошибку <code>EWOULDBLOCK</code> и все данные, оставшиеся в буфере отправки сокета, будут сброшены.</p>
      <p>Теперь нам нужно точно определить, когда завершается функция <code>close</code> на сокете в различных сценариях, которые мы рассмотрели. Предполагается, что клиент записывает данные в сокет и вызывает функцию <code>close</code>. На рис. 7.1 показана ситуация по умолчанию.</p>
      <image l:href="#img_62.png"/>
      <p><strong>Рис. 7.1</strong>. Действие функции close, заданное по умолчанию: немедленное завершение</p>
      <p>Мы предполагаем, что когда приходят данные клиента, сервер временно занят. Поэтому данные добавляются в приемный буфер сокета его протоколом TCP. Аналогично, следующий сегмент (сегмент FIN клиента) также добавляется к приемному буферу сокета (каким бы образом реализация ни сохраняла сегмент FIN). Но по умолчанию клиентская функция <code>close</code> сразу же завершается. Как мы показываем в этом сценарии, клиентская функция <code>close</code> может завершиться перед тем, как сервер прочитает оставшиеся данные в приемном буфере его сокета. Если узел сервера выйдет из строя перед тем, как приложение-сервер считает оставшиеся данные, клиентское приложение никогда об этом не узнает.</p>
      <p>Клиент может установить параметр сокета <code>SO_LINGER</code>, задав некоторое положительное время задержки. Когда это происходит, клиентская функция <code>close</code> не завершается до тех пор, пока все данные клиента и его сегмент FIN не будут подтверждены протоколом TCP сервера. Мы показываем это на рис. 7.2.</p>
      <image l:href="#img_63.png"/>
      <p><strong>Рис. 7.2</strong>. Закрытие сокета с параметром SO_LINGER и положительным l_linger</p>
      <p>Но у нас остается та же проблема, что и на рис. 7.1: если на узле сервера происходит сбой до того, как приложение-сервер считает оставшиеся данные, клиентское приложение никогда не узнает об этом. Еще худший вариант развития событий показан на рис. 7.3, где значение SO_<code>LINGER</code> было установлено слишком маленьким.</p>
      <image l:href="#img_64.png"/>
      <p><strong>Рис. 7.3</strong>. Закрытие сокета с параметром SO_LINGER при малом положительном l_linger</p>
      <p>Основным принципом взаимодействия является то, что успешное завершение функции <code>close</code> с установленным параметром сокета <code>SO_LINGER</code> говорит нам лишь о том, что данные, которые мы отправили (и наш сегмент FIN) подтверждены протоколом TCP собеседника. Но это <emphasis>не</emphasis> говорит нам, прочитало ли данные <emphasis>приложение</emphasis> собеседника. Если мы не установим параметр сокета <code>SO_LINGER</code>, мы не будем знать, подтвердил ли другой конец TCP отправленные ему данные.</p>
      <p>Чтобы узнать, что сервер прочитал данные клиента, клиент может вызвать функцию <code>shutdown</code> (со вторым аргументом <code>SHUT_WR</code>) вместо функции <code>close</code> и ждать, когда собеседник закроет с помощью функции <code>close</code> свой конец соединения. Этот сценарий показан на рис. 7.4.</p>
      <image l:href="#img_65.png"/>
      <p><strong>Рис. 7.4</strong>. Использование функции shutdown для проверки того, что собеседник получил наши данные</p>
      <p>Сравнивая этот рисунок с рис. 7.1 и 7.2, мы видим, что когда мы закрываем наш конец соединения, то в зависимости от вызванной функции (<code>close</code> или <code>shutdown</code>) и от того, установлен или нет параметр сокета <code>SO_LINGER</code>, завершение может произойти в один из трех различных моментов времени: '</p>
      <p>1. Функция <code>close</code> завершается немедленно, без всякого ожидания (сценарий, заданный по умолчанию, см. рис. 7.1).</p>
      <p>2. Функция <code>close</code> задерживается до тех пор, пока не будет получен сегмент ACK, подтверждающий получение сервером сегмента FIN от клиента (см. рис. 7.2).</p>
      <p>3. Функция <code>shutdown</code>, за которой следует функция <code>read</code>, ждет, когда мы получим сегмент FIN собеседника (в данном случае сервера) (см. рис. 7.2).</p>
      <p>Другой способ узнать, что приложение-собеседник прочитало наши данные, — использовать <emphasis>подтверждение на уровне приложения</emphasis>, или <emphasis>ACK приложения</emphasis>. Например, клиент отправляет данные серверу и затем вызывает функцию <code>read</code> для одного байта данных:</p>
      <p><code>char ack;</code></p>
      <empty-line/>
      <p><code>Write(sockfd, data, nbytes); /* данные от клиента к серверу */</code></p>
      <p><code>n = Read(sockfd, &amp;ack, 1);   /* ожидание подтверждения на уровне приложения */</code></p>
      <p>Сервер читает данные от клиента и затем отправляет ему 1-байтовый сегмент — подтверждение на уровне приложения:</p>
      <p><code>nbytes = Read(sockfd, buff, sizeof(buff)); /* данные от клиента */</code></p>
      <p><code>/* сервер проверяет, верное ли количество данных он получил от клиента */</code></p>
      <p><code>Write(sockfd, 1); /* сегмент ACK сервера возвращается клиенту */</code></p>
      <p>Таким образом, мы получаем гарантию, что на момент завершения функции read на стороне клиента процесс сервера прочитал данные, которые мы отправили. (При этом предполагается, что либо сервер знает, сколько данных отправляет клиент, либо существует некоторый заданный приложением маркер конца записи, который мы здесь не показываем.) В данном случае сегмент ACK на уровне приложения представляет собой нулевой байт, но вообще содержимое этого сегмента можно использовать для передачи от сервера к клиенту сообщений о других условиях. На рис. 7.5 показан возможный обмен пакетами.</p>
      <image l:href="#img_66.png"/>
      <p><strong>Рис. 7.5</strong>. ACK приложения</p>
      <p>В табл. 7.4 описаны два возможных вызова функции <code>shutdown</code> и три возможных вызова функции <code>close</code>, а также их влияние на сокет TCP.</p>
      <empty-line/>
      <p><strong>Таблица 7.4</strong>. Итоговая таблица сценариев функции shutdown и параметров сокета SO_LINGER</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Функция</th>
        <th align="left" valign="top">Описание</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">shutdown, SHUT_RD</td>
        <td align="left" valign="top">Через сокет больше нельзя принимать данные; процесс может по-прежнему отправлять данные через этот сокет; приемный буфер сокета сбрасывается; все данные, получаемые в дальнейшем, игнорируются протоколом TCP (см. упражнение 6.5); не влияет на буфер отправки сокета</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">shutdown, SHUT_WR</td>
        <td align="left" valign="top">Через сокет больше нельзя отправлять данные; процесс может по-прежнему получать данные через этот сокет; содержимое буфера отправки сокета отсылается на другой конец соединения, затем выполняется обычная последовательность действий по завершению соединения TCP (FIN); не влияет на приемный буфер сокета</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">close, l_onoff = 0 (по умолчанию)</td>
        <td align="left" valign="top">Через сокет больше нельзя отправлять и получать данные; содержимое буфера отправки сокета отсылается на другой конец соединения. Если счетчик ссылок дескриптора становится нулевым, то следом за отправкой данных из буфера отправки сокета выполняется нормальная последовательность завершения соединения TCP (FIN), данные из приемного буфера сокета сбрасываются</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">close, l_onoff = 1 l_linger = 0</td>
        <td align="left" valign="top">Через сокет больше нельзя отправлять и получать данные. Если счетчик ссылок дескриптора становится нулевым, то на другой конец соединения посылается сегмент RST, соединение переходит в состояние в CLOSED (минуя состояние TIME_WAIT), данные из буфера отправки и приемного буфера сокета сбрасываются</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">close, l_onoff = 1 l_linger = 0</td>
        <td align="left" valign="top">Через сокет больше нельзя отправлять и получать данные; содержимое буфера отправки сокета отсылается на другой конец соединения. Если счетчик ссылок дескриптора становится нулевым, то следом за отправкой данных из буфера отправки сокета выполняется нормальная последовательность завершения соединения TCP (FIN), данные из приемного буфера сокета сбрасываются, и если время задержки истекает, прежде чем оставшиеся в буфере данные будут посланы и будет подтвержден их прием, функция close возвратит ошибку EWOULDBLOCK</td>
       </tr>
      </table>
     </section>
     <section>
      <title>
       <p>Параметр сокета SO_OOBINLINE</p>
      </title>
      <p>Когда установлен этот параметр, внеполосные данные помещаются в очередь нормального ввода (то есть вместе с обычными данными (inline)). Когда это происходит, флаг <code>MSG_OOB</code> не может быть использован для чтения полученных внеполосных данных. Более подробно внеполосные данные мы рассмотрим в главе 24.</p>
     </section>
     <section>
      <title>
       <p>Параметры сокета SO_RCVBUF и SO_SNDBUF</p>
      </title>
      <p>У каждого сокета имеется буфер отправки и приемный буфер (буфер приема). Мы изобразили действие буферов отправки TCP, UDP и SCTP на рис. 2.15, 2.16 и 2.17.</p>
      <p>Приемные буферы используются в TCP, UDP и SCTP для хранения полученных данных, пока они не будут считаны приложением. В случае TCP доступное пространство в приемном буфере сокета — это окно, размер которого TCP сообщает другому концу соединения. Приемный буфер сокета TCP не может переполниться, поскольку собеседнику не разрешается отправлять данные, размер которых превышает размер окна. Так действует управление передачей TCP, и если собеседник игнорирует объявленное окно и отправляет данные, превышающие его размер, принимающий TCP игнорирует эти данные. Однако в случае UDP дейтаграмма, не подходящая для приемного буфера сокета, игнорируется. Вспомните, что в UDP отсутствует управление потоком: более быстрый отправитель легко переполнит буфер медленного получателя, заставляя UDP получателя игнорировать дейтаграммы, как мы покажем в разделе 8.13. Более того, быстрый отправитель может переполнить даже собственный сетевой интерфейс, так что дейтаграммы будут сбрасываться еще до отправки их с исходного узла.</p>
      <p>Указанные в заголовке раздела параметры позволяют нам изменять размеры буферов, заданные по умолчанию. Значения по умолчанию сильно отличаются в зависимости от реализации. Более ранние реализации, происходящие от Беркли, по умолчанию имели размеры буферов отправки и приема 4096 байт, а более новые системы используют буферы больших размеров, от 8192 до 61 440 байт. Размер буфера отправки UDP по умолчанию часто составляет около 9000 байт, а если узел поддерживает NFS, то размер приемного буфера UDP увеличивается до 40 000 байт.</p>
      <p>При установке размера приемного буфера сокета TCP важен порядок вызова функций, поскольку в данном случае учитывается параметр масштабирования окна TCP (см. раздел 2.5). При установлении соединения обе стороны обмениваются сегментами SYN, в которых может содержаться этот параметр. Для клиента это означает, что параметр сокета <code>SO_RCVBUF</code> должен быть установлен перед вызовом функции <code>connect</code>. Для сервера это означает, что данный параметр должен быть установлен для прослушиваемого сокета перед вызовом функции <code>listen</code>. Установка этого параметра для присоединенного сокета никак не повлияет на параметр масштабирования окна, поскольку функция <code>accept</code> не возвращает управление процессу, пока не завершится трехэтапное рукопожатие TCP. Поэтому данный параметр должен быть установлен для прослушиваемого сокета. (Размеры буферов сокета всегда наследуются от прослушиваемого сокета создаваемым присоединенным сокетом [128, с. 462-463]).</p>
      <p>Размеры буферов сокета TCP должны быть как минимум вчетверо больше MSS (максимальный размер сегмента) для соединения. Если мы имеем дело с направленной передачей данных, такой как передача файла в одном направлении, то говоря «размеры буферов сокета», мы подразумеваем буфер отправки сокета на отправляющем узле или приемный буфер сокета на принимающем узле. В случае двусторонней передачи данных мы имеем в виду оба размера буферов на обоих узлах. С типичным размером буфера 8192 байт или больше и типичным MSS, равным 512 или 1460 байт, это требование обычно выполняется. Проблемы были замечены в сетях с большими MTU (максимальная единица передачи), которые предоставляют MSS больше обычного (например, в сетях ATM с MTU, равной 9188).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Значение минимального множителя (4) обусловлено принципом работы алгоритма быстрого восстановления TCP. Отправитель использует три двойных подтверждения, чтобы обнаружить утерянный пакет (RFC 2581 [4]). Получатель отправляет двойное подтверждение для каждого сегмента, принятого после того, который был пропущен. Если размер окна меньше четырех сегментов, трех двойных подтверждений не будет и алгоритм быстрого восстановления не сработает.</p>
      </cite>
      <p>Размеры буфера сокета TCP должны быть также четное число раз кратны размеру MSS для соединения. Некоторые реализации выполняют это требование для приложения, округляя размеры в сторону большего размера буфера сокета после установления соединения [128, с. 902]. Это другая причина, по которой следует задавать эти два параметра сокета перед установлением соединения. Например, если использовать размеры, заданные по умолчанию в 4.4BSD (8192 байт), и считать, что используется Ethernet с размером MSS, равным 1460 байт, то при установлении соединения размеры обоих буферов сокета будут округляться до 8760 байт (6&#215;1460). Это требование не жесткое, лишнее место в буфере просто не будет использоваться.</p>
      <p>Другое соображение относительно установки размеров буфера сокета связано с производительностью. На рис. 7.6 показано соединение TCP между двумя конечными точками (которое мы называем <emphasis>каналом</emphasis>) с вместимостью, допускающей передачу восьми сегментов.</p>
      <image l:href="#img_67.png"/>
      <p><strong>Рис. 7.6</strong>. Соединение TCP (канал), вмещающее восемь сегментов</p>
      <p>Мы показываем четыре сегмента данных вверху и четыре сегмента ACK внизу. Даже если в канале только четыре сегмента данных, у клиента должен быть буфер отправки, вмещающий минимум восемь сегментов, потому что TCP клиента должен хранить копию каждого сегмента, пока не получен сегмент ACK от сервера.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Здесь мы игнорируем некоторые подробности. Прежде всего, алгоритм медленного запуска TCP ограничивает скорость, с которой сегменты начинают отправляться по соединению, которое до этого было неактивным. Далее, TCP часто подтверждает каждый второй сегмент, а не каждый сегмент, как мы это показываем. Все эти подробности описаны в главах 20 и 24 [111].</p>
      </cite>
      <p>Нам необходимо понять принцип функционирования двустороннего канала и узнать, что такое его вместимость и как она влияет на размеры буферов сокетов на обоих концах соединения. Вместимость канала характеризуется <emphasis>произведением пропускной способности на задержку</emphasis> (bandwidth-delay product). Мы будем вычислять ее, умножая пропускную способность канала (в битах в секунду) на период обращения (RTT, round-trip time) (в секундах) и преобразуя результат из битов в байты. RTT легко измеряется с помощью утилиты <code>ping</code>. Пропускная способность — это значение, соответствующее наиболее медленной связи между двумя конечными точками; предполагается, что это значение каким-то образом определено. Например, линия T1 (1 536 000 бит/с) с RTT 60 мс дает произведение пропускной способности на задержку, равное 11 520 байт. Если размеры буфера сокета меньше указанного, канал не будет заполнен и производительность окажется ниже предполагаемой. Большие буферы сокетов требуются, когда повышается пропускная способность (например, для линии T3, где она равна 45 Мбит/с) или когда увеличивается RTT (например, спутниковые каналы связи с RTT около 500 мс). Когда произведение пропускной способности на задержку превосходит максимальный нормальный размер окна TCP (65 535 байт), обоим концам соединения требуются также параметры TCP для канала с повышенной пропускной способностью (long fat pipe), о которых мы упоминали в разделе 2.6.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В большинстве реализаций размеры буферов отправки и приема ограничиваются некоторым предельным значением. В более ранних реализациях, происходящих от Беркли, верхний предел был около 52 000 байт, но в новых реализациях предел по умолчанию равен 256 000 байт или больше, и обычно администратор имеет возможность увеличивать его. К сожалению, не существует простого способа, с помощью которого приложение могло бы узнать этот предел. POSIX определяет функцию fpathconf, поддерживаемую большинством реализаций, а в качестве второго аргумента этой функции должна использоваться константа _PC_SOCK_MAXBUF. Приложение может также попытаться установить желаемый размер буфера сокета, а если попытка окажется неудачной, сократить размер вдвое и вызвать функцию снова. Наконец, приложение должно убедиться, что оно не уменьшает размер буфера по умолчанию, задавая свое собственное значение. В первую очередь следует вызвать getsockopt для определения значения, установленного по умолчанию, которое вполне может оказаться достаточным.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Параметры сокета SO_RCVLOWAT и SO_SNDLOWAT</p>
      </title>
      <p>Каждый сокет характеризуется также минимальным количеством данных (low- water mark) для буферов приема и отправки. Эти значения используются функцией <code>select</code>, как мы показали в разделе 6.3. Указанные параметры сокета позволяют нам изменять эти два значения.</p>
      <p>Минимальное количество данных — это количество данных, которые должны находиться в приемном буфере сокета, чтобы функция <code>select</code> возвратила ответ «Сокет готов для чтения». По умолчанию это значение равно 1 для сокетов TCP и UDP. Минимальный объем для буфера отправки — это количество свободного пространства, которое должно быть в буфере отправки сокета, чтобы функция select возвратила «Сокет готов для записи». Для сокетов TCP по умолчанию оно обычно равно 2048. С UDP это значение используется так, как мы показали в разделе 6.3, но поскольку число байтов доступного пространства в буфере отправки для сокета UDP никогда не изменяется (поскольку UDP не хранит копии дейтаграмм, отправленных приложением), сокет UDP всегда готов для записи, пока размер буфера отправки сокета UDP больше минимального объема. Вспомните рис. 2.16: UDP не имеет настоящего буфера отправки, у него есть только параметр размера буфера отправки.</p>
     </section>
     <section>
      <title>
       <p>Параметры сокета SO_RCVTIMEO и SO_SNDTIMEO</p>
      </title>
      <p>Эти два параметра сокета позволяют нам устанавливать тайм-аут при получении и отправке через сокет. Обратите внимание, что аргумент двух функций <code>sockopt</code> — это указатель на структуру <code>timeval</code>, ту же, которую использует функция <code>select</code> (раздел 6.3). Это позволяет использовать для задания тайм-аута секунды и миллисекунды. Отключение тайм-аута осуществляется установкой его значения в 0 секунд и 0 миллисекунд. Оба тайм-аута по умолчанию отключены.</p>
      <p>Тайм-аут приема влияет на пять функций ввода: <code>read</code>, <code>readv</code>, <code>recv</code>, <code>recvfrom</code> и <code>recvmsg</code>. Тайм-аут отправки влияет на пять функций вывода: <code>write</code>, <code>writev</code>, <code>send</code>, <code>sendto</code> и <code>sendmsg</code>. Более подробно о тайм-аутах сокета мы поговорим в разделе 14.2.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Эти два параметра сокета и концепция тайм-аута сокетов вообще были добавлены в реализации 4.3BSD Reno.</p>
       <p>В реализациях, происходящих от Беркли, указанные параметры инициализируют таймер отсутствия активности, а не абсолютный таймер системного вызова чтения или записи. На с. 496 и 516 [128] об этом рассказывается более подробно.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Параметры сокета SO_REUSEADDR и SO_REUSEPORT</p>
      </title>
      <p>Параметр сокета <code>SO_REUSEADDR</code> служит для четырех целей.</p>
      <p>1. Параметр <code>SO_REUSEADDR</code> позволяет прослушивающему серверу запуститься и с помощью функции <code>bind</code> связаться со своим заранее известным портом, даже если существуют ранее установленные соединения, использующие этот порт в качестве своего локального порта. Эта ситуация обычно возникает следующим образом:</p>
      <p> 1) запускается прослушивающий сервер;</p>
      <p> 2) от клиента приходит запрос на соединение, и для обработки этого клиента генерируется дочерний процесс;</p>
      <p> 3) прослушивающий сервер завершает работу, но дочерний процесс продолжает обслуживание клиента на существующем соединении;</p>
      <p> 4) прослушивающий сервер перезапускается.</p>
      <p>По умолчанию, когда прослушивающий сервер перезапускается при помощи вызова функций <code>socket</code>, <code>bind</code> и <code>listen</code>, вызов функции <code>bind</code> оказывается неудачным, потому что прослушивающий сервер пытается связаться с портом, который является частью существующего соединения (обрабатываемого ранее созданным дочерним процессом). Но если сервер устанавливает параметр сокета <code>SO_REUSEADDR</code> между вызовами функций <code>socket</code> и <code>bind</code>, последняя выполнится успешно. Все серверы TCP должны задавать этот параметр сокета, чтобы позволить перезапускать сервер в подобной ситуации.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Этот сценарий вызывает больше всего вопросов в Usenet.</p>
      </cite>
      <p>2. Параметр <code>SO_REUSEADDR</code> позволяет множеству экземпляров одного и того же сервера запускаться на одном и том же порте, если все экземпляры связываются с различными локальными IP-адресами. Это типичная ситуация для узла, на котором размещаются несколько серверов HTTP, использующих технологию альтернативных IP-адресов, или псевдонимов (IP alias technique) (см. раздел А.4). Допустим, первичный IP-адрес локального узла — 198.69.10.2, но он имеет два альтернативных адреса — 198.69.10.128 и 198.69.10.129. Запускаются три сервера HTTP. Первый сервер с помощью функции bind свяжется с локальным IP-адресом 198.69.10.128 и локальным портом 80 (заранее известный порт HTTP). Второй сервер с помощью функции <code>bind</code> свяжется с локальным IP-адресом 198.69.10.129 и локальным портом 80. Но второй вызов функции <code>bind</code> не будет успешным, пока не будет установлен параметр <code>SO_REUSEADDR</code> перед обращением к ней. Третий сервер вызовет функцию bind с универсальным адресом в качестве локального IP-адреса и локальным портом 80. И снова требуется параметр <code>SO_REUSEADDR</code>, для того чтобы последний вызов оказался успешным. Если считать, что установлен параметр <code>SO_REUSEADDR</code> и запущены три сервера, то входящие запросы TCP на соединение с IP-адресом получателя 198.69.10.128 и портом получателя 80 доставляются на второй сервер, входящие запросы на соединение с IP-адресом получателя 198.69.10.129 и портом получателя 80 — на третий сервер, а все остальные входящие запросы TCP на соединение с портом получателя 80 доставляются на первый сервер. Этот сервер обрабатывает запросы, адресованные на 198.69.10.2, в дополнение к другим альтернативным IP-адресам, для которых этот узел может быть сконфигурирован. Символ подстановки означает в данном случае «все, для чего не нашлось более точного совпадения». Заметим, что этот сценарий, допускающий множество серверов для данной службы, обрабатывается автоматически, если сервер всегда устанавливает параметр сокета <code>SO_REUSEADDR</code> (как мы рекомендуем).</p>
      <p>TCP не дает нам возможности запустить множество серверов, которые с помощью функции <code>bind</code> связываются с одним и тем же IP-адресом и одним и тем же портом: это случай <emphasis>полностью дублированного связывания</emphasis> (<emphasis>completely duplicate binding</emphasis>). То есть мы не можем запустить один сервер, связывающийся с адресом 198.69.10.2 и портом 80, и другой сервер, также связывающийся с адресом 198.69.10.2 и портом 80, даже если для второго сервера мы установим параметр <code>SO_REUSEADDR</code>.</p>
      <p>По соображениям безопасности некоторые операционные системы запрещают связывать несколько серверов с адресом подстановки, то есть описанный выше сценарий не работает даже с использованием параметра <code>SO_REUSEADDR</code>. В такой системе сервер, связываемый с адресом подстановки, должен запускаться последним. Таким образом предотвращается привязка сервера злоумышленника к IP-адресу и порту, которые уже обрабатываются системной службой. Особенно это важно для службы NFS, которая обычно не использует выделенный порт.</p>
      <p>3. Параметр <code>SO_REUSEADDR</code> позволяет одиночному процессу связывать один и тот же порт с множеством сокетов, так как при каждом связывании задается уникальный IP-адрес. Это обычное явление для серверов UDP, так как им необходимо знать IP-адрес получателя запросов клиента в системах, не поддерживающих параметр сокета <code>IP_RECVSTADDR</code>. Эта технология обычно не применяется с серверами TCP, поскольку сервер TCP всегда может определить IP-адрес получателя при помощи вызова функции <code>getsockname</code>, после того как соединение установлено. Однако на многоинтерфейсном узле сервер TCP, работающий с частью адресов локального узла, мог бы воспользоваться этой функцией.</p>
      <p>4. Параметр <code>SO_REUSEADDR</code> допускает <emphasis>полностью дублированное связывание</emphasis>: связывание с помощью функции <code>bind</code> с IP-адресом и портом, когда тот же IP-адрес и тот же порт уже связаны с другим сокетом. Обычно это свойство доступно только в системах с поддержкой многоадресной передачи без поддержки параметра сокета SO_REUSEPORT (который мы опишем чуть ниже), и только для сокетов UDP (многоадресная передача не работает с TCP).</p>
      <p>Это свойство применяется при многоадресной передаче для многократного выполнения одного и того же приложения на одном и том же узле. Когда приходит дейтаграмма UDP для одного из многократно связанных сокетов, действует следующее правило: если дейтаграмма предназначена либо для широковещательного адреса, либо для адреса многоадресной передачи, то одна копия дейтаграммы доставляется каждому сокету. Но если дейтаграмма предназначена для адреса направленной передачи, то дейтаграмма доставляется только на один сокет. Какой сокет получит дейтаграмму, если в случае направленной передачи существует множество сокетов, соответствующих дейтаграмме, — зависит от реализации. На с. 777-779 [128] об этом свойстве рассказывается более подробно. О широковещательной и многоадресной передаче мы поговорим соответственно в главах 20 и 21.</p>
      <p>В упражнениях 7.5 и 7.6 показаны примеры использования этого параметра сокета.</p>
      <p>Вместо того чтобы перегружать параметр <code>SO_REUSEADDR</code> семантикой многоадресной передачи, допускающей полностью дублированное связывание, в 4.4BSD был введен новый параметр сокета <code>SO_REUSEPORT</code>, обладающий следующей семантикой:</p>
      <p>1. Этот параметр допускает полностью дублированное связывание, но только если каждый сокет, который хочет связаться с тем же IP-адресом и портом, задает этот параметр сокета.</p>
      <p>2. Параметр <code>SO_REUSEADDR</code> считается эквивалентным параметру <code>SO_REUSEPORT</code>, если связываемый IP-адрес является адресом многоадресной передачи [128, с. 731].</p>
      <p>Проблема с этим параметром сокета заключается в том, что не все системы его поддерживают. В системах без поддержки этого параметра, но с поддержкой многоадресной передачи его функции выполняет параметр <code>SO_REUSEADDR</code>, допускающий полностью дублированное связывание, когда оно имеет смысл (то есть когда имеется сервер UDP, который может быть запущен много раз на одном и том же узле в одно и то же время, предполагающий получать либо широковещательные дейтаграммы, либо дейтаграммы многоадресной передачи).</p>
      <p>Обобщить обсуждение этих параметров сокета можно с помощью следующих рекомендаций:</p>
      <p>1. Устанавливайте параметр SO_<code>REUSEADDR</code> перед вызовом функции <code>bind</code> на всех серверах TCP.</p>
      <p>2. При создании приложения многоадресной передачи, которое может быть запущено несколько раз на одном и том же узле в одно и то же время, устанавливайте параметр <code>SO_REUSEADDR</code> и связывайтесь с адресом многоадресной передачи, используемым в качестве локального IP-адреса.</p>
      <p>Более подробно об этих параметрах сокета рассказывается в главе 22 [128].</p>
      <p>Существует потенциальная проблема безопасности, связанная с использованием параметра <code>SO_REUSEADDR</code>. Если существует сокет, связанный, скажем, с универсальным адресом и портом 5555, то, задав параметр <code>SO_REUSEADDR</code>, мы можем связать этот порт с другим IP-адресом, например с основным (primary) IP-адресом узла. Любые приходящие дейтаграммы, предназначенные для порта 5555 и IP- адреса, который мы связали с нашим сокетом, доставляются на наш сокет, а не на другой сокет, связанный с универсальным адресом. Это могут быть сегменты SYN TCP или дейтаграммы UDP. (В упражнении 11.9 показано это свойство для UDP.) Для большинства известных служб, таких как HTTP, FTP и Telnet, это не составляет проблемы, поскольку все эти серверы связываются с зарезервированным портом. Следовательно, любой процесс, запущенный позже и пытающийся связаться с конкретным экземпляром этого порта (то есть пытающийся завладеть портом), требует прав привилегированного пользователя. Однако NFS (Network File System — сетевая файловая система) может вызвать проблемы, поскольку ее стандартный порт (2049) не зарезервирован.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Одна из сопутствующих проблем API сокетов в том, что установка пары сокетов выполняется с помощью двух вызовов функций (bind и connect) вместо одного. В [122] предлагается одиночная функция, разрешающая эту проблему:</p>
       <p><code>int bind_connect_listen(int <emphasis>sockfd</emphasis>,</code></p>
       <p><code> const struct sockaddr *<emphasis>laddr</emphasis>, int <emphasis>laddrlen</emphasis>,</code></p>
       <p><code> const struct sockaddr *<emphasis>faddr</emphasis>, int <emphasis>faddrlen</emphasis>,</code></p>
       <p><code> int <emphasis>listen</emphasis>);</code></p>
       <p>Аргумент laddr задает локальный IP-адрес и локальный порт, аргумент faddr — удаленный IP-адрес и удаленный порт, аргумент listen задает клиент (0) или сервер (значение ненулевое; то же, что и аргумент backlog функции listen). В таком случае функция bind могла бы быть библиотечной функцией, вызывающей эту функцию с пустым указателем faddr и нулевым faddrlen, а функция connect — библиотечной функцией, вызывающей эту функцию с пустым указателем laddr и нулевым laddrlen. Существует несколько приложений, особенно FTP, которым необходимо задавать и локальную пару, и удаленную пару, которые могут вызывать bind_connect_listen непосредственно. При наличии подобной функции отпадает необходимость в параметре SO_REUSEADDR, в отличие от серверов UDP, которым явно необходимо допускать полностью дублированное связывание с одним и тем же IP-адресом и портом. Другое преимущество этой новой функции в том, что сервер TCP может ограничить себя обслуживанием запросов на соединения, приходящих от одного определенного IP-адреса и порта. Это определяется в RFC 793 [96], но невозможно с существующими API сокетов.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Параметр сокета SO_TYPE</p>
      </title>
      <p>Этот параметр возвращает тип сокета. Возвращаемое целое число — константа <code>SOCK_STREAM</code> или <code>SOCK_DGRAM</code>. Этот параметр обычно используется процессом, наследующим сокет при запуске.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SO_USELOOPBACK</p>
      </title>
      <p>Этот параметр применяется только к маршрутизирующим сокетам (<code>AF_ROUTE</code>). По умолчанию он включен на этих сокетах (единственный из параметров <code>SO_<emphasis>xxx</emphasis></code>, по умолчанию включенный). В этом случае сокет получает копию всего, что отправляется на сокет.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Другой способ отключить получение этих копий — вызвать функцию shutdown со вторым аргументом SHUT_RD.</p>
      </cite>
     </section>
    </section>
    <section>
     <title>
      <p>7.6. Параметры сокетов IPv4</p>
     </title>
     <section>
      <p>Эти параметры сокетов обрабатываются IPv4 и для них аргумент <code>level</code> равен <code>IPPROTO_IP</code>. Обсуждение пяти параметров сокетов многоадресной передачи мы отложим до раздела 19.5.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IP_HRDINCL</p>
      </title>
      <p>Если этот параметр задан для символьного сокета IP (см. главу 28), нам следует создать наш собственный заголовок IP для всех дейтаграмм, которые мы отправляем через символьный сокет. Обычно ядро создает заголовок IP для дейтаграмм, отправляемых через символьный сокет, но существует ряд приложений (в частности, <code>traceroute</code>), создающих свой собственный заголовок IP, заменяющий значения, которые IP поместил бы в определенные поля заголовка.</p>
      <p>Когда установлен этот параметр, мы создаем полный заголовок IP со следующими исключениями:</p>
      <p>&#9632; IP всегда сам вычисляет и записывает контрольную сумму заголовка IP.</p>
      <p>&#9632; Если мы устанавливаем поле идентификации IP в 0, ядро устанавливает это поле самостоятельно.</p>
      <p>&#9632; Если IP-адрес отправителя (source address) — <code>INADDR_ANY</code>, IP устанавливает его равным основному IP-адресу исходящего интерфейса.</p>
      <p>&#9632; Как устанавливать параметры IP, зависит от реализации. Некоторые реализации добавляют любые параметры IP, установленные с использованием параметра сокета <code>IP_OPTIONS</code>, к создаваемому нами заголовку, в то время как другие требуют, чтобы мы сами добавили в заголовок все необходимые параметры IP.</p>
      <p>&#9632; Некоторые поля должны располагаться в порядке байтов узла, тогда как другие — в сетевом порядке байтов. Это тоже зависит от реализации, из-за чего программы, работающие с символьными сокетами с параметром <code>IP_HDRINCL</code>, становятся не такими переносимыми, как хотелось бы.</p>
      <p>Пример использования этого параметра показан в разделе 29.7. Дополнительная информация об этом параметре представлена в [128, с. 1056–1057].</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IP_OPTIONS</p>
      </title>
      <p>Установка этого параметра позволяет нам задавать параметры IP в заголовке IPv4. Это требует точного знания формата параметров IP в заголовке IP. Мы рассмотрим этот параметр в контексте маршрутизации от отправителя IPv4 в разделе 27.3.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IP_RECVDSTADDR</p>
      </title>
      <p>Этот параметр сокета заставляет функцию <code>recvmsg</code> возвращать IP-адрес получателя в получаемой дейтаграмме UDP в качестве вспомогательных данных. Пример использования этого параметра мы приводим в разделе 22.2.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IP_RECVIF</p>
      </title>
      <p>Этот параметр сокета заставляет функцию <code>recvmsg</code> возвращать индекс интерфейса, на котором принимается дейтаграмма UDP, в качестве вспомогательных данных. Пример использования этого параметра мы приводим в разделе 22.2.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IP_TOS</p>
      </title>
      <p>Этот параметр позволяет нам устанавливать поле <emphasis>тип службы</emphasis> (<emphasis>тип сервиса</emphasis>) (TOS, type-of-service) (рис. А.1) в заголовке IP для сокета TCP или UDP. Если мы вызываем для этого сокета функцию getsockopt, возвращается текущее значение, которое будет помещено в поля DSCP и ECN заголовка IP (по умолчанию значение нулевое). Не существует способа извлечь это значение из полученной дейтаграммы IP.</p>
      <p>Приложение может установить DSCP равным одному из значений, о которых существует договоренность с провайдером. Каждому значению соответствует определенный тип обслуживания, например IP-телефонии требуется низкая задержка, а передачи больших объемов данных требуют повышенной пропускной способности. Документ RFC 2474 [82] определяет архитектуру diffserv, которая обеспечивает лишь ограниченную обратную совместимость с историческим определением поля TOS (RFC 1349 [5]). Приложения, устанавливающие параметр <code>IP_TOS</code> равным одной из констант, определенных в файле <code>&lt;netinet/ip.h&gt;</code> (например, <code>IPTOS_LOWDELAY</code> или <code>IPTOS_THROUGHPUT</code>), должны вместо этого использовать различные значения DSCP. Архитектура diffserv сохраняет только два значения (6 и 7, что соответствует константам <code>IPTOS_PREC_NETCONTROL</code> и <code>IPTOS_PREC_INTERNETCONTROL</code>), так что только те приложения, которые используют именно эти константы, будут работать в сетях diffserv.</p>
      <p>Документ RFC 3168 [100] определяет поле ECN. Приложениям рекомендуется предоставлять установку этого поля ядру и сбрасывать в нуль два младших бита значения, заданного <code>IP_TOS</code>.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IP_TTL</p>
      </title>
      <p>С помощью этого параметра мы можем устанавливать и получать заданное по умолчанию значение TTL (time-to-live field — поле времени жизни, рис. А.1), которое система будет использовать для данного сокета. (TTL для многоадресной передачи устанавливается при помощи параметра сокета <code>IP_MULTICAST_TTL</code>, который описывается в разделе 21.6.) В системе 4.4BSD, например, значение TTL по умолчанию для сокетов TCP и UDP равно 64 (оно определяется в RFC 1700), а для символьных сокетов — 255. Как и в случае поля TOS, вызов функции <code>getsockopt</code> возвращает значение поля по умолчанию, которое система будет использовать в исходящих дейтаграммах, и не существует способа определить это значение по полученной дейтаграмме. Мы устанавливаем этот параметр сокета в нашей программе <code>traceroute</code> в листинге 28.15.</p>
     </section>
    </section>
    <section>
     <title>
      <p>7.7. Параметр сокета ICMPv6</p>
     </title>
     <section>
      <p>Единственный параметр сокета, обрабатываемый ICMPv6, имеет аргумент <code>level</code>, равный <code>IPPROTO_ICMPV6</code>.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета ICMP6_FILTER</p>
      </title>
      <p>Этот параметр позволяет нам получать и устанавливать структуру <code>icmp6_filter</code>, которая определяет, какие из 256 возможных типов сообщений ICMPv6 передаются для обработки на символьный сокет. Мы обсудим этот параметр в разделе 28.4.</p>
     </section>
    </section>
    <section>
     <title>
      <p>7.8. Параметры сокетов IPv6</p>
     </title>
     <section>
      <p>Эти параметры сокетов обрабатываются IPv6 и имеют аргумент <code>level</code>, равный <code>IPPROTO_IPV6</code>. Мы отложим обсуждение пяти параметров сокетов многоадресной передачи до раздела 21.6. Отметим, что многие из этих параметров используют <emphasis>вспомогательные данные</emphasis> с функцией <code>recvmsg</code>, и мы покажем это в разделе 14.6. Все параметры сокетов IPv6 определены в RFC 3493 [36] и RFC 3542 [114].</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_CHECKSUM</p>
      </title>
      <p>Этот параметр сокета задает байтовое смещение поля контрольной суммы внутри данных пользователя. Если значение неотрицательное, ядро, во-первых, вычисляет и хранит контрольную сумму для всех исходящих пакетов и, во-вторых, проверяет полученную контрольную сумму на вводе, игнорируя пакеты с неверной контрольной суммой. Этот параметр влияет на символьные сокеты IPv6, отличные от символьных сокетов ICMPv6. (Ядро всегда вычисляет и хранит контрольную сумму для символьных сокетов ICMPv6.) Если задано значение -1 (значение по умолчанию), ядро не будет вычислять и хранить контрольную сумму для исходящих пакетов на этом символьном сокете и не будет проверять контрольную сумму для получаемых пакетов.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Все протоколы, использующие IPv6, должны иметь контрольную сумму в своих собственных заголовках. Эти контрольные суммы включают псевдозаголовок (RFC 2460 [27]), куда входит IPv6-адрес отправителя (что отличает IPv6 от всех остальных протоколов, которые обычно реализуются с использованием символьного сокета IPv4). Ядро не заставляет приложение, использующее символьный сокет, выбирать адрес отправителя, но делает это самостоятельно и затем вычисляет и сохраняет контрольную сумму, включающую псевдозаголовок IPv6.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_DONTFRAG</p>
      </title>
      <p>Установка этого параметра запрещает автоматическое включение заголовка фрагментации для UDP и символьных сокетов. При этом исходящие пакеты, размер которых превышает MTU исходящего интерфейса, просто сбрасываются. Системный вызов ошибку не возвращает, так как пакет может быть сброшен и на промежуточном маршрутизаторе, если он превысит MTU одной из промежуточных линий. Чтобы получать уведомления об изменении маршрутной MTU, приложению следует включить параметр сокета <code>IPV6_RECVPATHMTU</code> (см. раздел 22.9).</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_NEXTHOP</p>
      </title>
      <p>Этот параметр задает адрес следующего транзитного узла для дейтаграммы в виде структуры адреса сокета. Подробнее о нем рассказывается в разделе 22.8.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_PATHMTU</p>
      </title>
      <p>Этот параметр может быть только получен, но не установлен. При его считывании система возвращает текущее значение маршрутной MTU, определенное соответствующим методом (см. раздел 22.9).</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_RECVDSTOPTS</p>
      </title>
      <p>Установка этого параметра означает, что любые полученные IPv6-параметры получателя должны быть возвращены в качестве вспомогательных данных функцией <code>recvmsg</code>. По умолчанию параметр отключен. Мы опишем функции, используемые для создания и обработки этих параметров, в разделе 27.5.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_RECVHOPLIMIT</p>
      </title>
      <p>Установка этого параметра определяет, что полученное поле предельного количества транзитных узлов (hop limit field) должно быть возвращено в качестве вспомогательных данных функцией <code>recvmsg</code>. По умолчанию параметр отключен. Мы опишем функции, используемые для создания и обработки этого параметра, в разделе 22.8.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В IPv4 не существует способа определить значение получаемого поля TTL.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_RECVHOPOPTS</p>
      </title>
      <p>Установка этого параметра означает, что любые полученные параметры транзитных узлов (hop-by-hop options) IPv6 должны быть возвращены в качестве вспомогательных данных функцией <code>recvmsg</code>. По умолчанию параметр отключен. Мы опишем функции, используемые для создания и обработки этого параметра, в разделе 27.5.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_RECVPATHMTU</p>
      </title>
      <p>Установка этого параметра означает, что маршрутная MTU должна быть возвращена в качестве вспомогательных данных функцией <code>recvmsg</code>, при условии, что ее значение изменилось. Параметр будет описан в разделе 22.9.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_RECVPKTINFO</p>
      </title>
      <p>Установка этого параметра означает, что два фрагмента информации о полученной дейтаграмме IPv6 — IPv6-адрес получателя и индекс принимающего интерфейса — должны быть возвращены в качестве вспомогательных данных функцией <code>recvmsg</code>. Мы опишем этот параметр в разделе 22.8.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_RECVRTHDR</p>
      </title>
      <p>Установка этого параметра означает, что получаемый заголовок маршрутизации IPv6 должен быть возвращен в качестве вспомогательных данных функцией <code>recvmsg</code>. По умолчанию этот параметр отключен. Мы опишем функции, которые используются для создания и обработки заголовка маршрутизации IPv6, в разделе 27.6.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_RECVTCLASS</p>
      </title>
      <p>Установка этого параметра означает, что функция <code>recvmsg</code> должна вернуть сведения о классе трафика полученного сообщения (то есть содержимое полей DSCP и ECN) в качестве внешних данных. По умолчанию параметр отключен. Подробнее о нем будет рассказано в разделе 22.8.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_UNICAST_HOPS</p>
      </title>
      <p>Этот параметр аналогичен параметру сокета IPv4 <code>IP_TTL</code>. Он определяет предельное количество транзитных узлов, заданное по умолчанию для исходящих дейтаграмм, отправляемых через этот сокет. При получении значения этого параметра сокета возвращается предельное количество транзитных узлов, которое ядро будет использовать для сокета. Чтобы определить действительное значение предельного количества транзитных узлов из полученной дейтаграммы IPv6, требуется использовать параметр сокета <code>IPV6_RECVHOPLIMIT</code>. Мы устанавливаем этот параметр сокета в нашей программе <code>traceroute</code> в листинге 28.15.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_USE_MIN_MTU</p>
      </title>
      <p>Установка этого параметра равным 1 указывает на то, что определять маршрутную MTU не следует, а пакеты должны отправляться с минимальным значением MTU для IPv6, что предотвращает их фрагментацию. Если же значение параметра равно 0, определение маршрутной MTU выполняется для всех адресов назначения. Значение -1 (установленное по умолчанию) указывает на необходимость определения маршрутной MTU для направленной передачи, но для многоадресной передачи в этом случае используется минимально возможная MTU. Подробнее об этом параметре рассказывается в разделе 22.9.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_V6ONLY</p>
      </title>
      <p>Включение этого параметра для сокета семейства <code>AF_INET6</code> ограничивает его использование исключительно протоколом IPv6. По умолчанию параметр отключен, хотя в некоторых системах существует возможность включить его по умолчанию. Взаимодействие по IPv4 и IPv6 через сокеты <code>AF_INET6</code> будет описано в разделах 12.2 и 12.3.</p>
     </section>
     <section>
      <title>
       <p>Параметры сокета IPV6_XXX</p>
      </title>
      <p>Большинство параметров IPv6, предназначенных для изменения содержимого заголовка, предполагают, что приложение использует сокет UDP и взаимодействует с ядром при помощи функций <code>recvmsg</code> и <code>sendmsg</code>. Сокет TCP получает и устанавливает значения параметров при помощи специальных функций <code>getsockopt</code> и <code>setsockopt</code>. Параметр сокета TCP совпадает с типом вспомогательных данных для UDP, а в буфере после вызова функций <code>getsockopt</code> и <code>setsockopt</code> оказывается та же информация, что и во вспомогательных данных. Подробнее см. раздел 27.7.</p>
     </section>
    </section>
    <section>
     <title>
      <p>7.9. Параметры сокетов TCP</p>
     </title>
     <section>
      <p>Для сокетов TCP предусмотрены два специальных параметра. Для них необходимо указывать <code>level</code> IPPROTO_TCP.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета TCP_MAXSEG</p>
      </title>
      <p>Этот параметр сокета позволяет нам получать или устанавливать <emphasis>максимальный размер сегмента</emphasis> (<emphasis>maximum segment size</emphasis>, <emphasis>MSS</emphasis>) для соединения TCP. Возвращаемое значение — это количество данных, которые наш TCP будет отправлять на другой конец соединения. Часто это значение равно MSS, анонсируемому другим концом соединения в его сегменте SYN, если наш TCP не выбирает меньшее значение, чем объявленный MSS собеседника. Если это значение получено до того, как сокет присоединился, возвращаемым значением будет значение по умолчанию, которое используется в том случае, когда параметр MSS не получен с другого конца соединения. Также помните о том, что значение меньше возвращаемого действительно может использоваться для соединения, если, например, задействуется параметр отметки времени (timestamp), поскольку в каждом сегменте он занимает 12 байт области, отведенной под параметры TCP.</p>
      <p>Максимальное количество данных, которые TCP отправляет в каждом сегменте, также может изменяться во время существования соединения, если TCP поддерживает определение транспортной MTU. Если маршрут к собеседнику изменяется, это значение может увеличиваться или уменьшаться.</p>
      <p>В табл. 7.2 мы отметили, что этот параметр сокета может быть также установлен приложением. Это возможно не во всех системах: изначально параметр был доступен только для чтения. 4.4BSD позволяет приложению только лишь уменьшать это значение, но мы не можем его увеличивать [128, с. 1023]. Поскольку этот параметр управляет количеством данных, которое TCP посылает в каждом сегменте, имеет смысл запретить приложению увеличивать значение. После установления соединения это значение задается величиной MSS, которую объявил собеседник, и мы не можем превысить его. Однако наш TCP всегда может отправить меньше данных, чем было анонсировано собеседником.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета TCP_NODELAY</p>
      </title>
      <p>Если этот параметр установлен, он отключает <emphasis>алгоритм Нагла</emphasis> (<emphasis>Nagle algorithm</emphasis>) (см. раздел 19.4 [111] и с. 858–859 [128]). По умолчанию этот алгоритм включен.</p>
      <p>Назначение алгоритма Нагла — сократить число небольших пакетов в глобальной сети. Согласно этому алгоритму, если у данного соединения имеются неподтвержденные (outstanding) данные (то есть данные, которые отправил наш TCP и подтверждения которых он ждет), то небольшие пакеты не будут отправляться через соединение до тех пор, пока существующие данные не будут подтверждены. Под «небольшим» пакетом понимается любой пакет, меньший MSS. TCP будет по возможности всегда отправлять пакеты нормального размера. Таким образом, назначение алгоритма Нагла — не допустить, чтобы у соединения было множество небольших пакетов, ожидающих подтверждения.</p>
      <p>Два типичных генератора небольших пакетов — клиенты Rlogin и Telnet, поскольку обычно они посылают каждое нажатие клавиши в отдельном пакете. В быстрой локальной сети мы обычно не замечаем действия алгоритма Нагла с этими клиентами, потому что время, требуемое для подтверждения небольшого пакета, составляет несколько миллисекунд — намного меньше, чем промежуток между вводом двух последовательных символов. Но в глобальной сети, где для подтверждения небольшого пакета может потребоваться секунда, мы можем заметить задержку в отражении символов, и эта задержка часто увеличивается при включении алгоритма Нагла.</p>
      <p>Рассмотрим следующий пример. Мы вводим строку из шести символов <code>hello!</code> либо клиенту Rlogin, либо клиенту Telnet, промежуток между вводом символов составляет точно 250 мс. Время обращения к серверу (RTT) составляет 600 мс и сервер немедленно отправляет обратно отражение символа. Мы считаем, что сегмент ACK, подтверждающий получение клиентского символа, отправляется обратно клиенту с отражением символа, а сегменты ACK, которые клиент отправляет для подтверждения приема отраженного сервером символа, мы игнорируем. (Мы поговорим о задержанных сегментах ACK далее.) Считая, что алгоритм Нагла отключен, получаем 12 пакетов, изображенных на рис. 7.7.</p>
      <image l:href="#img_68.png"/>
      <p><strong>Рис. 7.7</strong>. Шесть символов, отраженных сервером при отключенном алгоритме Нагла</p>
      <p>Каждый символ отправляется в индивидуальном пакете: сегменты данных слева направо, а сегменты ACK справа налево.</p>
      <p>Но если алгоритм Нагла включен (по умолчанию), у нас имеется 8 пакетов, показанных на рис. 7.8. Первый символ посылается как пакет, но следующие два символа не отправляются, поскольку у соединения есть небольшой пакет, ожидающий подтверждения. Эти пакеты отправляются, когда прошло 600 мс, то есть когда прибывает сегмент ACK, подтверждающий прием первого пакета, вместе с отражением первого символа. Пока второй пакет не будет подтвержден сегментом ACK в момент времени 1200, не будет отправлен ни один небольшой пакет.</p>
      <image l:href="#img_69.png"/>
      <p><strong>Рис. 7.8</strong>. Пакеты, отправляемые при включенном алгоритме Нагла</p>
      <p>Алгоритм Нагла часто взаимодействует с другим алгоритмом TCP: алгоритмом <emphasis>задержанного сегмента ACK</emphasis> (<emphasis>delayed ACK</emphasis>). Этот алгоритм заставляет TCP не отправлять сегмент ACK сразу же при получении данных — вместо этого TCP ждет в течение небольшого количества времени (типичное значение 50-200 мс) и только после этого отправляет сегмент ACK. Здесь делается расчет на то, что в течение этого непродолжительного времени появятся данные для отправки собеседнику, и сегмент ACK может быть вложен в пакет с этими данными. Таким образом можно будет сэкономить на одном сегменте TCP. Это обычный случай с клиентами Rlogin и Telnet, поэтому сегмент ACK клиентского символа вкладывается в отражение символа сервером.</p>
      <p>Проблема возникает с другими клиентами, серверы которых не генерируют трафика в обратном направлении, в который может быть вложен сегмент ACK. Эти клиенты могут обнаруживать значительные задержки, поскольку TCP клиента не будет посылать никаких данных серверу, пока не истечет время таймера для задержанных сегментов ACK сервера. Таким клиентам нужен способ отключения алгоритма Нагла. Осуществить это позволяет параметр <code>TCP_NODELAY</code>.</p>
      <p>Другой тип клиента, для которого нежелательно использование алгоритма Нагла и задержанных ACK TCP, — это клиент, отправляющий одиночный логический запрос своему серверу небольшими порциями. Например, будем считать, что клиент отправляет своему серверу 400-байтовый запрос, состоящий из 4 байт, задающих тип запроса, за которыми следуют 396 байт данных. Если клиент выполняет функцию <code>write</code>, отправляя 4 байт, и затем функцию <code>write</code>, отправляя остальные 396 байт, вторая часть не будет отправлена со стороны клиента, пока TCP сервера не подтвердит получение первых 4 байт. Кроме того, поскольку сервер не может работать с 4 байтами данных, пока не получит оставшиеся 396 байт, TCP сервера задержит сегмент ACK, подтверждающий получение 4 байт данных (то есть не будет данных от сервера клиенту, в которые можно вложить сегмент ACK). Есть три способа решить проблему с таким клиентом.</p>
      <p>1. Использовать функцию <code>writev</code> (раздел 14.4) вместо двух вызовов функции <code>write</code>. Один вызов функции <code>writev</code> приводит к отправке только одного сегмента TCP в нашем примере. Это предпочтительное решение.</p>
      <p>2. Скопировать 4 байт и 396 байт данных в один буфер и вызвать один раз функцию <code>write</code> для этого буфера.</p>
      <p>3. Установить параметр сокета <code>TCP_NODELAY</code> и продолжать вызывать функцию <code>write</code> дважды. Это наименее желательное решение.</p>
      <p>Упражнения 7.8 и 7.9 продолжают этот пример.</p>
     </section>
    </section>
    <section>
     <title>
      <p>7.10. Параметры сокетов SCTP</p>
     </title>
     <section>
      <p>Относительно большое количество параметров, определенных для сокетов SCTP (17 на момент написания этой книги), дают возможность разработчику приложения более точно контролировать его поведение. Параметр <code>level</code> для сокетов SCTP должен принимать значение <code>IPPROTO_SCTP</code>.</p>
      <p>Несколько параметров, используемых для получения сведений об SCTP, требуют передачи данных ядру (например, идентификатора ассоциации или адреса собеседника). Не все реализации <code>getsockopt</code> поддерживают передачу данных в обе стороны. Интерфейс сокетов SCTP определяет функцию <code>sctp_opt_info</code> (раздел 9.11), которая устраняет эту проблему. В некоторых системах, где <code>getsockopt</code> поддерживает передачу данных в ядро, функция <code>sctp_opt_info</code> является не более, чем оболочкой для <code>getsockopt</code>. В других системах она может вызывать функцию <code>ioctl</code> или какую-либо иную, возможно, созданную специально для данного случая. Мы рекомендуем получать параметры сокетов SCTP при помощи <code>sctp_opt_info</code>, так как в этом случае обеспечивается максимальная переносимость. В табл. 7.2 соответствующие параметры отмечены знаком «<code>+</code>»: <code>SCTP_ASSOCINFO</code>, <code>SCTP_GET_PEER_ADDR_INFO</code>, <code>SCTP_PEER_ADDR_PARAMS</code>, <code>SCTP_PRIMARY_ADDR</code>, <code>SCTP_RTOINFO</code> и <code>SCTP_STATUS</code>.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_ADAPTION_LAYER</p>
      </title>
      <p>При инициализации ассоциации любой собеседник может указать на наличие уровня-адаптора. Это указание должно представлять из себя 32-разрядное беззнаковое целое, которое может использоваться двумя приложениями для координации локального уровня-адаптора приложений. Параметр сокета позволяет получать или устанавливать указание на наличие уровня-адаптора, которое будет предоставляться данной конечной точкой будущим собеседникам. Чтобы получить соответствующее значение, установленное собеседником, приложение должно подписаться на события уровня-адаптора.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_ASSOCINFO</p>
      </title>
      <p>Параметр сокета <code>SCTP_ASSOCINFO</code> выполняет три функции. Во-первых, он позволяет получать сведения о существующей ассоциации. Во-вторых, с его помощью можно изменять параметры существующей ассоциации. Наконец, в-третьих, через этот параметр можно задавать значения по умолчанию для будущих ассоциаций. При получении сведений о существующей ассоциации вместо <code>getsockopt</code> следует использовать <code>sctp_opt_info</code>. Вместе с параметром при вызове функции указывается структура <code>sctp_assocparams</code>:</p>
      <p><code>struct sctp_assocparams {</code></p>
      <p><code> sctp_assoc_t sasoc_assoc_id;</code></p>
      <p><code> uint16_t sasoc_asocmaxrxt;</code></p>
      <p><code> uint16_t sasoc_number_peer_destinations;</code></p>
      <p><code> uint32_t sasoc_peer_rwnd;</code></p>
      <p><code> uint32_t sasoc_local_rwnd;</code></p>
      <p><code> uint32_t sasoc_cookie_life;</code></p>
      <p><code>};</code></p>
      <p>Поля структуры имеют следующий смысл:</p>
      <p>&#9632; <code>sasoc_assoc_id</code> хранит идентификатор ассоциации. Если при вызове <code>setsockopt</code> параметр установлен в нуль, поля <code>sasoc_asocmaxrxt</code> и <code>sasoc_cookie_life</code> трактуются как новые значения по умолчанию для сокета. Вызов <code>getsockopt</code> вернет сведения об ассоциации, если при вызове указать ее идентификатор; если же поле оставить нулевым, будут возвращены значения по умолчанию;</p>
      <p>&#9632; <code>sasoc_asocmaxrxt</code> хранит количество повторных передач без получения подтверждений. При превышении этого ограничения передача прекращается, ассоциация закрывается и SCTP сообщает приложению о недоступности собеседника;</p>
      <p>&#9632; <code>sasoc_number_peer_destinations</code> хранит количество адресов собеседника. Этот параметр может быть только считан, но не установлен;</p>
      <p>&#9632; <code>sasoc_peer_rwnd</code> хранит текущее рассчитанное окно приема собеседника, то есть количество байтов, которые могут быть переданы в данный момент. Это поле изменяется динамически. Когда приложение отправляет данные, значение поля уменьшается, когда удаленное приложение считывает полученные данные, значение увеличивается. Вызовом данного параметра сокета это значение изменено быть не может;</p>
      <p>&#9632; <code>sasoc_local_rwnd</code> хранит размер локального окна приема, о котором SCTP оповещает собеседника. Это значение также изменяется динамически и зависит от параметра сокета <code>SO_SNDBUF</code>. Вызовом параметра <code>SCTP_ASSOCINFO</code> локальное окно изменено быть не может;</p>
      <p>&#9632; <code>sasoc_cookie_life</code> хранит срок действия <code>cookie</code>, выданного собеседнику (в миллисекундах). Каждому <code>cookie</code> присваивается определенный срок действия, благодаря чему обеспечивается защита от атак, основанных на повторах. Значение по умолчанию равно 60 000 и может быть изменено установкой нужного значения в данном поле при условии, что в поле <code>sasoc_assoc_id</code> записано значение 0.</p>
      <p>Рекомендации по настройке <code>sasoc_asocmaxrxt</code> для оптимальной производительности приводятся в разделе 23.11. Для защиты от атак, основанных на повторе, значение <code>sasoc_cookie_life</code> можно уменьшить, но при этом система окажется менее устойчивой к задержкам в процессе инициации. Прочие поля полезны для отладки программ.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_AUTOCLOSE</p>
      </title>
      <p>Этот параметр позволяет получать и устанавливать время автоматического закрытия конечной точки SCTP. Это время задается в секундах и определяет длительность существования ассоциации SCTP, по которой не передаются никакие данные. Передача данных контролируется стеком SCTP. По умолчанию функция автоматического закрытия отключена.</p>
      <p>Параметр предназначен для использования на интерфейсах SCTP типа «один-ко-многим» (см. главу 9). Положительное значение соответствует времени поддержания неиспользуемой ассоциации в секундах, а нулевое отключает функцию автоматического закрытия. Установка параметра влияет только на будущие ассоциации, все существующие ассоциации сохраняют старые значения.</p>
      <p>Автоматическое закрытие может использоваться сервером для закрытия неиспользуемых ассоциаций без дополнительных затрат на хранение информации о состоянии. Однако разработчик сервера должен тщательно оценить максимальную продолжительность бездействия клиентов. Если значение параметра окажется недостаточно большим, ассоциации будут закрываться слишком рано.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_DEFAULT_SEND_PARAM</p>
      </title>
      <p>SCTP поддерживает множество дополнительных параметров отправки, которые обычно передаются в виде вспомогательных данных или используются при вызове функции <code>sctp_sendmsg</code> (который часто реализуется как библиотечный вызов, передающий вспомогательные данные пользователя). Приложение, планирующее отправку большого количества сообщений с одинаковыми параметрами, может воспользоваться параметром <code>SCTP_DEFAULT_SEND_PARAM</code> для настройки значений параметров по умолчанию и тем самым избавиться от необходимости добавлять вспомогательные данные или вызывать <code>sctp_sendmsg</code>. На вход параметра поступает структура <code>sctp_sndrcvinfo</code>:</p>
      <p><code>struct sctp_sndrcvinfo {</code></p>
      <p><code> u_int16_t sinfo_stream;</code></p>
      <p><code> u_int16_t sinfo_ssn;</code></p>
      <p><code> u_int16_t sinfo_flags;</code></p>
      <p><code> u_int32_t sinfo_ppid;</code></p>
      <p><code> u_int32_t sinfo_context;</code></p>
      <p><code> u_int32_t sinfo_timetolive;</code></p>
      <p><code> u_int32_t sinfo_tsn;</code></p>
      <p><code> u_int32_t sinfo_cumtsn;</code></p>
      <p><code> sctp_assoc_t sinfo_assoc_id;</code></p>
      <p><code>};</code></p>
      <p>Поля структуры определяются следующим образом:</p>
      <p>&#9632; <code>sinfo_stream</code> задает поток, в который по умолчанию направляются все сообщения;</p>
      <p>&#9632; <code>sinfo_ssn</code> игнорируется при установке значений параметров по умолчанию. При получении сообщений функцией <code>recvmsg</code> или <code>sctp_recvmsg</code> это поле содержит значение потокового последовательного номера (stream sequence number, SSN), помещенное собеседником в порцию данных;</p>
      <p>&#9632; <code>sinfo_flags</code> устанавливает значения всех флагов для будущих сообщений. Допустимые значения флагов приводятся в табл. 7.5;</p>
      <p>&#9632; <code>sinfo_ppid</code> задает значение идентификатора протокола SCTP для всех будущих передач данных;</p>
      <p>&#9632; <code>sinfo_context</code> задает значение по умолчанию для поля <code>sinfo_context</code>, которое является локальной меткой для сообщений, которые не могли быть доставлены собеседнику;</p>
      <p>&#9632; <code>sinfo_timetolive</code> определяет время жизни отправляемых сообщений. Поле времени жизни используется стеком SCTP для того, чтобы сбрасывать сообщения, задержавшиеся в буфере отправки на слишком большой срок и не переданные ни разу. Если обе конечные точки поддерживают режим частичной надежности, параметр времени жизни влияет и на количество попыток повторной передачи, ограничивая их срок;</p>
      <p>&#9632; <code>sinfo_tsn</code> игнорируется при установке параметров по умолчанию. При получении сообщений функцией <code>recvmsg</code> или <code>sctp_recvmsg</code> это поле содержит значение транспортного последовательного номера (transport sequence number, TSN), помещенное собеседником в порцию данных SCTP;</p>
      <p>&#9632; <code>sinfo_cumtsn</code> игнорируется при установке параметров по умолчанию. При получении сообщений функцией <code>recvmsg</code> или <code>sctp_recvmsg</code> это поле содержит значение кумулятивного транспортного последовательного номера, вычисленного локальным стеком SCTP для удаленного собеседника;</p>
      <p>&#9632; <code>sinfo</code>_assoc_id содержит идентификатор ассоциации, для которой требуется установка параметров по умолчанию. Для сокетов типа «один-к-одному» это поле игнорируется.</p>
      <empty-line/>
      <p><strong>Таблица 7.5</strong>. Допустимые значения флагов SCTP (поле sinfo_flags)</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Константа</th>
        <th align="left" valign="top">Описание</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">MSG_ABORT</td>
        <td align="left" valign="top">Вызывает аварийное завершение ассоциации</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">MSG_ADDR_OVER</td>
        <td align="left" valign="top">Заставляет SCTP использовать указанный адрес вместо адреса по умолчанию</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">MSG_EOF</td>
        <td align="left" valign="top">Корректное завершение ассоциации после отправки сообщения</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">MSG_PR_BUFFER</td>
        <td align="left" valign="top">Включение частичной надежности в зависимости от буфера (если она вообще поддерживается)</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">MSG_PR_SCTP</td>
        <td align="left" valign="top">Включение частичной надежности доставки для данного сообщения (если поддерживается)</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">MSG_UNORDERED</td>
        <td align="left" valign="top">Указывает, что данное сообщение использует сервис неупорядоченной доставки</td>
       </tr>
      </table>
      <p>Обратите внимание, что значения параметров по умолчанию используются только тогда, когда сообщение отправляется без собственной структуры <code>sctp_sndrcvinfo</code>. Если же эта структура добавляется во вспомогательные данные при отправке сообщений, заданные в ней значения имеют приоритет перед значениями по умолчанию. Параметр <code>SCTP_DEFAULT_SEND_PARAM</code> может использоваться для получения текущих значений по умолчанию при помощи функции <code>sctp_opt_info</code>.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_DISАВLE_FRAGМENTS</p>
      </title>
      <p>В обычном режиме работы SCTP фрагментирует все сообщения, не помещающиеся в один пакет SCTP, разбивая их на несколько порций типа DATA. Установка параметра <code>SCTP_DISABLE_FRAGMENTS</code> отключает фрагментацию для данного отправителя. Если сообщение требует фрагментации, а фрагментация отключена, SCTP возвращает ошибку <code>EMSGSIZE</code> и не отсылает сообщение.</p>
      <p>Параметр может использоваться приложениями, которые хотят самостоятельно управлять размерами сообщений, при условии, что любое из этих сообщений может поместиться в IP-пакет. Приложение должно быть готово обработать ошибку, обеспечив фрагментацию на уровне приложения или изменение размера сообщений.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_EVENTS</p>
      </title>
      <p>Этот параметр сокета позволяет включать, выключать и определять состояние подписки на различные уведомления SCTP. Уведомление SCTP представляет собой сообщение, отправляемое стеком SCTP приложению. Сообщение считывается как и обычные данные, однако в поле <code>msg_flags</code> при вызове функции <code>recvmsg</code> должно находиться значение <code>MSG_NOTIFICATION</code>. Приложение, не готовое к использованию <code>recvmsg</code> или <code>sctp_recvmsg</code>, не должно включать подписку на события. Параметр позволяет управлять событиями восьми различных типов и передавать структуру <code>sctp_event_subscribe</code>. Нулевое значение соответствует отключению подписки, а единица — включению.</p>
      <p>Структура <code>sctp_event_subscribe</code> определяется следующим образом:</p>
      <p><code>struct sctp_event_subscribe {</code></p>
      <p><code> u_int8_t sctp_data_io_event;</code></p>
      <p><code> u_int8_t sctp_association_event;</code></p>
      <p><code> u_int8_t sctp_address_event;</code></p>
      <p><code> u_int8_t sctp_send_failure_event;</code></p>
      <p><code> u_int8_t sctp_peer_error_event;</code></p>
      <p><code> u_int8_t sctp_shutdown_event;</code></p>
      <p><code> u_int8_t sctp_partial_delivery_event;</code></p>
      <p><code> u_int8_t sctp_adaption_layer_event;</code></p>
      <p><code>};</code></p>
      <p>В табл. 7.6 описано назначение различных событий. Подробнее об уведомлениях вы узнаете в разделе 9.14.</p>
      <empty-line/>
      <p><strong>Таблица 7.6</strong>. События SCTP</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Константа</th>
        <th align="left" valign="top">Описание</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">sctp_data_io_event</td>
        <td align="left" valign="top">Включение и отключение доставки sctp_sndrcvinfo с каждым вызовом recvmsg</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">sctp_association_event</td>
        <td align="left" valign="top">Включение и отключение уведомлений о состоянии ассоциации</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">sctp_address_event</td>
        <td align="left" valign="top">Включение и отключение уведомлений об адресах</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">sctp_send_failure_event</td>
        <td align="left" valign="top">Включение и отключение уведомлений об ошибках доставки сообщений</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">sctp_peer_error_event</td>
        <td align="left" valign="top">Включение и отключение уведомлений об ошибках протокола собеседника</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">sctp_shutdown_event</td>
        <td align="left" valign="top">Включение и отключение уведомлений о завершении ассоциации</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">sctp_partial_delivery_event</td>
        <td align="left" valign="top">Включение и отключение уведомлений о частичной доставке</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">sctp_adaption_layer_event</td>
        <td align="left" valign="top">Включение и отключение уведомлений уровня-адаптера</td>
       </tr>
      </table>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_GET_PEER_ADDR_INFO</p>
      </title>
      <p>Этот параметр позволяет получить информацию о собеседнике, которая включает окно приема, сглаженные значения RTT и MTU. Параметр может быть применен только к конкретному адресу собеседника. Вызывающее приложение заполняет поле <code>spinfo_address</code> структуры <code>sctp_paddrinfo</code> интересующим его адресом собеседника. Для максимальной переносимости рекомендуется работать с функцией <code>sctp_opt_info</code>, а не <code>getsockopt</code>. Формат структуры <code>sctp_paddrinfo</code> описан ниже:</p>
      <p><code>struct sctp_paddrinfo {</code></p>
      <p><code> sctp_assoc_t spinfo_assoc_id;</code></p>
      <p><code> struct sockaddr_storage spinfo_address;</code></p>
      <p><code> int32_t spinfo_state;</code></p>
      <p><code> uint32_t spinfo_cwnd;</code></p>
      <p><code> u_int32_t spinfo_srtt;</code></p>
      <p><code> u_int32_t spinfo_rto;</code></p>
      <p><code> u_int32_t spinfo_mtu;</code></p>
      <p><code>};</code></p>
      <p>Приложению возвращаются следующие сведения:</p>
      <p>&#9632; <code>spinfo_assoc_id</code> содержит информацию об идентификаторе ассоциации, которая доставляется также в уведомлении об установке ассоциации (<code>SCTP_COMM_UP</code>). Уникальный идентификатор ассоциации может использоваться для обращения к ней в большинстве функций SCTP;</p>
      <p>&#9632; <code>spinfo_address</code> позволяет приложению указать конкретный адрес собеседника, для которого оно хочет получить сведения. По возвращении из <code>getsockopt</code> или <code>sctp_opt_info</code> значение структуры должно оставаться неизменным;</p>
      <p>&#9632; <code>spinfo_state</code> может содержать одно или несколько значений (табл. 7.7).</p>
      <empty-line/>
      <p><strong>Таблица 7.7</strong>. Состояния адреса собеседника SCTP</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Константа</th>
        <th align="left" valign="top">Описание</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_ACTIVE</td>
        <td align="left" valign="top">Адрес активен и доступен</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_INACTIVE</td>
        <td align="left" valign="top">В настоящий момент адрес недоступен</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_ADDR_UNCONFIRMED</td>
        <td align="left" valign="top">Доставка данных или проверочных сообщений на данный адрес не была подтверждена</td>
       </tr>
      </table>
      <p>Неподтвержденным считается адрес, перечисленный собеседником в списке действующих, но не проверенный локальным SCTP. Для проверки адреса требуется, чтобы отправленные на него данные или проверочные сообщения были подтверждены. Для непроверенного адреса не может быть указано корректное значение тайм-аута повторной передачи (RTO). Активными считаются адреса, доступные для передачи данных.</p>
      <p>&#9632; <code>spinfo_cwnd</code> хранит текущий размер окна приема для данного адреса. Описание процедуры расчета параметра cwnd приводится в [117, с. 177];</p>
      <p>&#9632; <code>spinfo_srtt</code> хранит текущую оценку сглаженного RTT для данного адреса;</p>
      <p>&#9632; <code>spinfo_rto</code> хранит текущее значение тайм-аута повторной передачи для данного адреса;</p>
      <p>&#9632; <code>spinfo_mtu</code> хранит текущую транспортную MTU, определенную по соответствующему алгоритму.</p>
      <p>Параметр полезно использовать для получения идентификатора ассоциации по структуре с IP-адресом собеседника. Это будет продемонстрировано в главе 23. Кроме того, приложение может отслеживать функционирование всех адресов собеседника с несколькими интерфейсами и выбирать лучший из них в качестве адреса по умолчанию. Наконец, все эти сведения полезны для ведения журналов и отладки.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_I_WANT_MAPPED_V4_ADDR</p>
      </title>
      <p>Этот флаг позволяет включать и отключать отображение адресов IPv4 для сокетов типа <code>AF_INET6</code>. Если параметр включен (а по умолчанию это именно так), все адреса IPv4 преобразуются в адреса IPv6 перед отправкой приложению. Если же параметр отключен, сокет SCTP не будет отображать адреса IPv4, а вместо этого будет просто передавать их в структуре <code>sockaddr_in</code>.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_INITMSG</p>
      </title>
      <p>Параметр позволяет устанавливать и считывать параметры инициализации, по умолчанию применяемые к сокетам при отправке сообщения INIT. Вместе с параметром передается структура <code>sctp_initmsg</code>, определяемая следующим образом:</p>
      <p><code>struct sctp_initmsg {</code></p>
      <p><code> uint16_t sinit_num_ostreams;</code></p>
      <p><code> uint16_t sinit_max_instreams;</code></p>
      <p><code> uint16_t sinit_max_attempts;</code></p>
      <p><code> uint16_t sinit_max_init_timeo;</code></p>
      <p><code>};</code></p>
      <p>Поля структуры определяются следующим образом:</p>
      <p>&#9632; <code>sinit_num_ostreams</code> содержит количество исходящих потоков SCTP, запрашиваемое приложением. Это значение не подтверждается, пока не будет завершено рукопожатие, и может быть уменьшено в соответствии с возможностями собеседника;</p>
      <p>&#9632; <code>sinit_max_instreams</code> отражает максимальное количество входящих потоков, которое готово обеспечить приложение. Это значение может быть перекрыто стеком SCTP, если оно превышает максимальное количество потоков, поддерживаемое самим стеком;</p>
      <p>&#9632; <code>sinit_max_attempts</code> выражает количество попыток передачи начального сообщения INIT перед тем, как собеседник будет признан недоступным;</p>
      <p>&#9632; <code>sinit_max_init_timeo</code> задает максимальный тайм-аут повторной передачи для сообщений INIT. Это значение используется вместо <code>RTO_MAX</code> в качестве ограничения сверху на тайм-аут повторной передачи. Выражается в миллисекундах.</p>
      <p>Обратите внимание, что установленные в 0 поля структуры игнорируются сокетом SCTP. При использовании сокета типа «один-ко-многим» (см. раздел 9.2) приложение может передать структуру <code>sctp_initmsg</code> во вспомогательных данных при неявной установке ассоциации.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_MAXBURST</p>
      </title>
      <p>Этот параметр позволяет приложению устанавливать и считывать максимальный размер набора пакетов (maximum burst size). SCTP никогда не отправляет более, чем <code>SCTP_MAXBURST</code> пакетов одновременно, что предотвращает переполнение сети. Ограничение может применяться либо путем уменьшения окна до текущего количества пакетов «в пути» (in flight) плюс максимальный размер набора, помноженный на транспортную MTU, либо в качестве отдельного параметра, если при каждой возможности отправки будет пересылаться не более <code>SCTP_MAXBURST</code> пакетов.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_MAXSEG</p>
      </title>
      <p>Параметр позволяет приложению считывать и устанавливать максимальный размер фрагмента, аналогично <code>TCP_MAXSEG</code> (см. раздел 7.8).</p>
      <p>Когда стек SCTP получает от приложения-отправителя сообщение, размер которого превышает значение этого параметра, это сообщение разбивается на несколько фрагментов, которые доставляются на вторую конечную точку по отдельности. Обычно SCTP создает фрагменты такого размера, чтобы они не превышали минимальную MTU для всех адресов собеседника. Параметр позволяет еще сильнее уменьшить это значение. Учтите, что стек SCTP может фрагментировать даже такое сообщение, размер которого не превышает <code>SCTP_MAXSEG</code>. Это произойдет в том случае, если MTU для одного из адресов собеседника окажется меньше значения <code>SCTP_MAXSEG</code>.</p>
      <p>Параметр действует для всех адресов конечной точки и может влиять на несколько ассоциаций при работе с интерфейсами типа «один-ко-многим».</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_NODELAY</p>
      </title>
      <p>Установка параметра отключает алгоритм Нагла протокола SCTP. По умолчанию параметр выключен, то есть алгоритм Нагла включен. С протоколом SCTP этот алгоритм работает так же, как и с TCP, за тем исключением, что он пытается объединять порции данных, а не отдельные байты. Подробнее см. описание параметра <code>TCP_NODELAY</code>.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_PEER_ADDR_PARAMS</p>
      </title>
      <p>Параметр позволяет приложению считывать и устанавливать различные параметры ассоциации. Приложение должно заполнить поле идентификатора ассоциации в структуре <code>sctp_paddrparams</code> и передать ее вместе с параметром сокета. Формат структуры приведен ниже:</p>
      <p><code>struct sctp_paddrparams {</code></p>
      <p><code> sctp_assoc_t spp_assoc_id;</code></p>
      <p><code> struct sockaddr_storage spp_address;</code></p>
      <p><code> u_int32_t spp_hbinterval;</code></p>
      <p><code> u_int16_t spp_pathmaxrxt;</code></p>
      <p><code>};</code></p>
      <p>Поля структуры имеют следующий смысл:</p>
      <p>&#9632; <code>spp_assoc_id</code> содержит идентификатор ассоциации, параметры которой считываются или устанавливаются. Если это значение равно нулю, приложение будет работать с параметрами по умолчанию, а не с конкретной ассоциацией;</p>
      <p>&#9632; <code>spp_address</code> указывает IP-адрес, для которого запрашиваются или устанавливаются параметры. Если значение поля равно нулю, оно игнорируется;</p>
      <p>&#9632; <code>spp_hbinterval</code> задает интервал между проверочными сообщениями (heartbeats). Значение <code>SCTP_NO_HB</code> отключает проверочные сообщения. Значение <code>SCTP_ISSUE_HB</code> приводит к внеочередной отправке проверочного сообщения. Все остальные значения задают интервал проверки в миллисекундах. При установке параметров по умолчанию задание константы <code>SCTP_ISSUE_HB</code> не допускается;</p>
      <p>&#9632; <code>spp_hbpathmaxrxt</code> определяет максимальное количество повторных передач, после которых адресат считается недоступным (<code>INACTIVE</code>). Если основной адрес собеседника признается недоступным, в качестве нового основного адреса выбирается один из доступных адресов.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_PRIMARY_ADDR</p>
      </title>
      <p>Параметр позволяет узнать или установить адрес, используемый локальной конечной точкой SCTP в качестве основного. Основной адрес используется в качестве адреса назначения во всех сообщениях, передаваемых собеседнику. Приложение должно заполнить структуру <code>sctp_setprim</code> идентификатором ассоциации и адресом собеседника.</p>
      <p><code>struct sctp_setprim {</code></p>
      <p><code> sctp_assoc_t ssp_assoc_id;</code></p>
      <p><code> struct sockaddr_storage ssp_addr;</code></p>
      <p><code>};</code></p>
      <p>Поля структуры имеют следующий смысл:</p>
      <p>&#9632; <code>ssp_assoc_id</code> указывает идентификатор ассоциации, для которой следует установить или считать основной адрес. В случае сокета типа «один-к-одному» это поле игнорируется;</p>
      <p>&#9632; <code>ssp_addr</code> определяет основной адрес, который обязательно должен принадлежать собеседнику. Если используется функция <code>setsockopt</code>, значение поля трактуется как новый основной адрес собеседника.</p>
      <p>Получение значения этого параметра для сокета типа «один-к-одному» с единственным локальным адресом эквивалентно вызову функции <code>getsockname</code>.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_RTOINFO</p>
      </title>
      <p>Параметр используется для считывания и установки различных тайм-аутов для конкретной ассоциации или используемых по умолчанию для конечной точки. Для считывания параметров по соображениям переносимости следует использовать функцию <code>sctp_opt_info</code>, а не <code>getsockopt</code>. Перед вызовом необходимо заполнить структуру <code>sctp_rtoinfo</code>, которая определяется следующим образом:</p>
      <p><code>struct sctp_rtoinfo {</code></p>
      <p><code> sctp_assoc_t srto_assoc_id;</code></p>
      <p><code> uint32_t srto_initial;</code></p>
      <p><code> uint32_t srto_max;</code></p>
      <p><code> uint32_t srto_min;</code></p>
      <p><code>};</code></p>
      <p>Поля структуры имеют следующий смысл:</p>
      <p>&#9632; <code>srto_assoc_id</code> содержит либо идентификатор конкретной ассоциации, либо 0. В последнем случае работа осуществляется со значениями по умолчанию;</p>
      <p>&#9632; <code>srto_initial</code> хранит начальное значение RTO для конкретного адреса собеседника. Это значение используется при отправке порции INIT. Измеряется поле в миллисекундах и по умолчанию равно 3000;</p>
      <p>&#9632; <code>srto_max</code> содержит максимальное значение RTO, используемое при изменении таймера повторной передачи. Если рассчитанное значение оказывается больше максимального RTO, в качестве нового тайм-аута используется именно максимальное значение. По умолчанию это поле имеет значение 60 000 мс;</p>
      <p>&#9632; <code>srto_min</code> содержит минимальное значение RTO, используемое при первом запуске таймера повторной передачи. Когда таймер RTO изменяется, новое значение обязательно сравнивается с минимальным. По умолчанию это поле имеет значение 1000 мс.</p>
      <p>Запись 0 в поля <code>srto_initial</code>, <code>srto_max</code> и <code>srto_min</code> означает, что менять текущие параметры по умолчанию не требуется. Все значения измеряются в миллисекундах. Руководство по установке таймеров для достижения максимальной производительности приводится в разделе 23.11.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_SET_PEER_PRIMARY_ADDR</p>
      </title>
      <p>Установка этого параметра приводит к отправке собеседнику сообщения, запрашивающего установку конкретного локального адреса в качестве основного. Процесс должен заполнить структуру <code>sctp_setpeerprim</code> и указать в ней идентификатор ассоциации и локальный адрес, который должен быть сделан основным. Этот адрес должен быть привязан к данной конечной точке. Структура <code>sctp_setpeerprim</code> определяется следующим образом:</p>
      <p><code>struct sctp_setpeerprim {</code></p>
      <p><code> sctp_assoc_t sspp_assoc_id;</code></p>
      <p><code> struct sockaddr_storage sspp_addr;</code></p>
      <p><code>};</code></p>
      <p>Ниже приводится описание полей структуры.</p>
      <p>&#9632; <code>sspp_assoc_id</code> указывает идентификатор ассоциации, для которой требуется установить новый основной адрес. При работе с сокетом типа «один-к-одному» это поле игнорируется;</p>
      <p>&#9632; <code>sspp_addr</code> содержит локальный адрес, который должен использоваться собеседником в качестве основного.</p>
      <p>Поддержка этой функции SCTP не является обязательной. Если локальная конечная точка не поддерживает параметр, процессу будет возвращена ошибка EOPNOTSUPP. Если же параметр не поддерживается удаленной конечной точкой, ошибка будет другой: EINVAL. Обратите внимание, что данный параметр не может использоваться для считывания основного адреса; он служит только для установки нового адреса в качестве основного.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета SCTP_STATUS</p>
      </title>
      <p>Этот параметр сокета служит для получения информации о текущем статусе ассоциации SCTP. Для обеспечения максимальной переносимости пользуйтесь функцией <code>sctp_opt_info</code>, а не <code>getaddrinfo</code>. Приложение должно предоставить структуру <code>sctp_status</code>, указав идентификатор ассоциации <code>sstat_assoc_id</code>. Структура будет заполнена информацией о выбранной ассоциации и возвращена приложению. Формат структуры <code>sctp_status</code> таков:</p>
      <p><code>struct sctp_status {</code></p>
      <p><code> sctp_assoc_t sstat_assoc_id;</code></p>
      <p><code> int32_t sstat_state;</code></p>
      <p><code> u_int32_t sstat_rwnd;</code></p>
      <p><code> u_int16_t sstat_unackdata;</code></p>
      <p><code> u_int16_t sstat_penddata;</code></p>
      <p><code> u_int16_t sstat_instrms;</code></p>
      <p><code> u_int16_t sstat_outstrms;</code></p>
      <p><code> u_int32_t sstat_fragmentation_point;</code></p>
      <p><code> struct sctp_paddrinfo sstat_primary;</code></p>
      <p><code>};</code></p>
      <p>Поля структуры имеют следующий смысл:</p>
      <p>&#9632; <code>sstat_assoc_id</code> содержит идентификатор ассоциации;</p>
      <p>&#9632; <code>sstat_state</code> содержит константу, обозначающую состояние ассоциации (табл. 7.8). Подробное описание состояний конечной точки SCTP, чередующихся при установке и завершении ассоциации, приводится на рис. 2.8;</p>
      <p>&#9632; <code>sstat_rwnd</code> содержит текущее вычисленное значение приемного окна собеседника;</p>
      <p>&#9632; <code>sstat_unackdata</code> содержит количество неподтвержденных порций данных, ждущих ответа собеседника;</p>
      <p>&#9632; <code>sstat_penddata</code> содержит количество непрочитанных порций данных, подготовленных локальной конечной точкой SCTP для приложения;</p>
      <p>&#9632; <code>sstat_instrms</code> содержит количество потоков, используемых собеседником для передачи данных на данную конечную точку;</p>
      <p>&#9632; <code>sstat_outstrms</code> содержит количество потоков, по которым данная конечная точка может передавать данные собеседнику;</p>
      <p>&#9632; <code>sstat_fragmentation_point</code> содержит текущее значение границы фрагментации пользовательских сообщений, используемое локальной конечной точкой SCTP. Это значение обычно равняется минимальной MTU для всех адресатов или еще меньшей величине, установленной при помощи параметра <code>SCTP_MAXSEG</code>;</p>
      <p>&#9632; <code>sstat_primary</code> содержит текущий основной адрес. Основной адрес используется по умолчанию для отправки данных собеседнику.</p>
      <empty-line/>
      <p><strong>Таблица 7.8</strong>. Состояния SCTP</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Константа</th>
        <th align="left" valign="top">Описание</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_CLOSED</td>
        <td align="left" valign="top">Ассоциация закрыта</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_COOKIE_WAIT</td>
        <td align="left" valign="top">Ассоциация отправила пакет INIT</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_COOKIE_ECHOED</td>
        <td align="left" valign="top">Ассоциация отправила эхо-ответ cookie</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_ESTABLISHED</td>
        <td align="left" valign="top">Ассоциация установлена</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_SHUTDOWN_PENDING</td>
        <td align="left" valign="top">Ассоциация ждет отправки сообщения о завершении</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_SHUTDOWN_SENT</td>
        <td align="left" valign="top">Ассоциация отправила сообщение о завершении</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_SHUTDOWN_RECEIVED</td>
        <td align="left" valign="top">Ассоциация получила сообщение о завершении</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SCTP_SHUTDOWN_ACK_SENT</td>
        <td align="left" valign="top">Ассоциация ждет пакета SHUTDOWN-COMPLETE</td>
       </tr>
      </table>
      <p>Эти параметры полезны для диагностики соединения и определения характеристик текущего сеанса. Например, функция <code>sctp_get_no_strms</code> в разделе 10.2 будет считывать <code>sstat_outstrms</code> для определения количества доступных для отправки данных потоков. Низкое значение <code>sstat_rwnd</code> или высокое значение <code>sstat_unackdata</code> позволяет сделать вывод о заполнении приемного буфера собеседника, так что приложение может вовремя замедлить передачу данных. Поле <code>sstat_fragmentation_point</code> может использоваться некоторыми приложениями для уменьшения количества пакетов, создаваемых SCTP, путем уменьшения размеров сообщений.</p>
     </section>
    </section>
    <section>
     <title>
      <p>7.11. Функция fcntl</p>
     </title>
     <p>Сокращение <code>fcntl</code> означает «управление файлами» (file control). Эта функция выполняет различные операции управления дескрипторами. Перед описанием этой функции и ее влияния на сокет нам нужно составить некоторое более общее представление о ее возможностях. В табл. 7.9 приводятся различные операции, выполняемые функциями <code>fcntl</code> и <code>ioctl</code> и маршрутизирующими сокетами.</p>
     <empty-line/>
     <p><strong>Таблица 7.9</strong>. Операции функций fcntl и ioctl и маршрутизирующих сокетов</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Операция</th>
       <th align="left" valign="top">fcntl</th>
       <th align="left" valign="top">ioctl</th>
       <th align="left" valign="top">Маршрутизирующий сокет</th>
       <th align="left" valign="top">Posix.1g</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Установка сокета для неблокируемого ввода-вывода</td>
       <td align="left" valign="top">F_SETFL, O_NONBLOCK</td>
       <td align="left" valign="top">FIONBIO</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">fcntl</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Установка сокета для ввода-вывода, управляемого сигналом</td>
       <td align="left" valign="top">F_SETFL, O_ASYNC</td>
       <td align="left" valign="top">FIOASYNC</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">fcntl</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Установка владельца сокета</td>
       <td align="left" valign="top">F_SETOWN</td>
       <td align="left" valign="top">SIOCSPGRP или FIOSETOWN</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">fcntl</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Получение владельца сокета</td>
       <td align="left" valign="top">F_GETOWN</td>
       <td align="left" valign="top">SIOCGPGRP или FIOGETOWN</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">fcntl</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Получение текущего количества байтов в приемном буфере сокета</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">FIONREAD</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Проверка, находится ли процесс на отметке внеполосных данных</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">SIOCATMARK</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">sockatmark</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Получение списка интерфейсов</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">SIOCGIFCONF</td>
       <td align="left" valign="top">Sysctl</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Операции интерфейсов</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">SIOC[GS]IF<emphasis>xxx</emphasis></td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Кэш-операции ARP</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">SIOC<emphasis>x</emphasis>ARP</td>
       <td align="left" valign="top">RTM_<emphasis>xxx</emphasis></td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Операции таблицы маршрутизации</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">SIOG<emphasis>xxx</emphasis>RT</td>
       <td align="left" valign="top">RTM_<emphasis>xxx</emphasis></td>
       <td align="left" valign="top"/>
      </tr>
     </table>
     <p>Первые шесть операций могут применяться к сокетам любым процессом, следующие две (операции над интерфейсами) используются реже, а последние две (ARP и таблица маршрутизации) выполняются администрирующими программами, такими как <code>ifconfig</code> и <code>route</code>. О различных операциях функции <code>ioctl</code> мы поговорим подробнее в главе 17, а о маршрутизирующих сокетах — в главе 18.</p>
     <p>Существует множество способов выполнения первых четырех операций, но, как указано в последней колонке, стандарт POSIX определяет, что функция <code>fcntl</code> является предпочтительным способом. Отметим также, что POSIX предлагает функцию <code>sockatmark</code> (см. раздел 24.3) как наиболее предпочтительный способ тестирования на предмет пребывания процесса на отметке внеполосных данных. Оставшиеся операции с пустой последней колонкой не стандартизованы POSIX.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Отметим также, что первые две операции, устанавливающие сокет для неблокируемого ввода-вывода и для ввода-вывода, управляемого сигналом, традиционно применялись с использованием команд FNDELAY и FASYNC функции fcntl. POSIX определяет константы О_<emphasis>xxx</emphasis>.</p>
     </cite>
     <p>Функция <code>fcntl</code> предоставляет следующие возможности, относящиеся к сетевому программированию:</p>
     <p>&#9632; Неблокируемый ввод-вывод. Мы можем установить флаг состояния файла <code>O_NONBLOCK</code>, используя команду <code>F_SETFL</code> для отключения блокировки сокета. Неблокируемый ввод-вывод мы описываем в главе 16.</p>
     <p>&#9632; Управляемый сигналом ввод-вывод. Мы можем установить флаг состояния файла <code>O_ASYNC</code>, используя команду <code>F_SETFL</code>, после чего при изменении состояния сокета будет генерироваться сигнал SIGIO. Мы рассмотрим это в главе 25.</p>
     <p>&#9632; Команда <code>F_SETOWN</code> позволяет нам установить владельца сокета (идентификатор процесса или идентификатор группы процессов), который будет получать сигналы <code>SIGIO</code> и <code>SIGURG</code>. Первый сигнал генерируется, если для сокета включен управляемый сигналом ввод-вывод (см. главу 25), второй — когда для сокета приходят новые внеполосные (out-of-band data) данные (см. главу 24). Команда <code>F_GETOWN</code> возвращает текущего владельца сокета.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Термин «владелец сокета» определяется POSIX. Исторически реализации, происходящие от Беркли, называли его «идентификатор группы процессов сокета», потому что переменная, хранящая этот идентификатор, — это элемент so_pgid структуры socket [128, с. 438].</p>
     </cite>
     <p><code>#include &lt;fcntl.h&gt;</code></p>
     <empty-line/>
     <p><code>int fcntl(int <emphasis>fd</emphasis>, int <emphasis>cmd</emphasis>, ... /* int arg */);</code></p>
     <p><code><emphasis>Возвращает: в случае успешного выполнения результат зависит от аргумента cmd, -1 в случае ошибки</emphasis></code></p>
     <p>Каждый дескриптор (включая сокет) имеет набор флагов, которые можно получить с помощью команды <code>F_GETFL</code> и установить с помощью команды <code>F_SETFL</code>. На сокет влияют следующие два флага:</p>
     <p>&#9632; <code>O_NONBLOCK</code> — неблокируемый ввод-вывод;</p>
     <p>&#9632; <code>O_ASYNC</code> — ввод-вывод, управляемый сигналом.</p>
     <p>Позже мы опишем оба эти флага подробнее. Отметим, что типичный код, который устанавливает неблокируемый ввод-вывод с использованием функции <code>fcntl</code>, выглядит следующим образом:</p>
     <p><code>int flags;</code></p>
     <empty-line/>
     <p><code>/* Делаем сокет неблокируемым */</code></p>
     <p><code>if ((flags = fcntl(fd, F_GETFL, 0)) &lt; 0)</code></p>
     <p><code> err_sys("F_GETFL error");</code></p>
     <p><code>flags |= O_NONBLOCK;</code></p>
     <p><code>if (fcntl(fd, F_SETFL, flags) &lt; 0)</code></p>
     <p><code> err_sys("F_SETFL error");</code></p>
     <p>Учтите, что вам может встретиться код, который просто устанавливает желаемый флаг:</p>
     <p><code>/* Неправильный способ сделать сокет неблокируемым */</code></p>
     <p><code>if (fcntl(fd, F_SETFL, O_NONBLOCK) &lt; 0)</code></p>
     <p><code> err_sys("F_SETFL error");</code></p>
     <p>Хотя при этом и устанавливается флаг отключения блокировки, также снимаются все остальные флаги состояния файла. Единственный корректный способ установить один из этих флагов состояния файла — получить текущие флаги, с помощью операции логического ИЛИ добавить новый флаг, а затем установить флаги.</p>
     <p>Следующий код сбрасывает флаг отключения блокировки в предположении, что переменная <code>flags</code> была задана с помощью вызова функции <code>fcntl</code>, показанного ранее:</p>
     <p><code>flags &amp;= ~O_NONBLOCK;</code></p>
     <p><code>if (fcntl(fd, F_SETFL, flags) &lt; 0)</code></p>
     <p><code> err_sys("F_SETFL error");</code></p>
     <p>Сигналы <code>SIGIO</code> и <code>SIGURG</code> отличаются от других тем, что они генерируются для сокета, только если сокету был присвоен владелец с помощью команды <code>F_SETOWN</code>. Целое значение аргумента <code>arg</code> для команды <code>F_SETOWN</code> может быть либо положительным, задающим идентификатор процесса, получающего сигнал, либо отрицательным, абсолютное значение которого — это идентификатор группы процессов, получающей сигнал. Команда <code>F_GETOWN</code> возвращает владельца сокета, так как возвращаемое значение функции <code>fcntl</code> — либо идентификатор процесса (положительное возвращаемое значение), либо идентификатор группы процессов (отрицательное значение, отличное от -1). Разница между заданием процесса и группы процессов, получающих сигнал, в том, что в первом случае сигнал будет получен только одиночным процессом, тогда как во втором случае его получают все процессы в группе.</p>
     <p>Когда создается новый сокет с помощью функции socket, у него нет владельца. Сокет, создаваемый из прослушиваемого сокета, наследует от него принадлежность владельцу (как и многие другие параметры сокетов [128, с. 462-463].</p>
    </section>
    <section>
     <title>
      <p>7.12. Резюме</p>
     </title>
     <p>Параметры сокетов лежат в широком диапазоне от очень общих (<code>SO_ERROR</code>) до очень специфических (параметры заголовка IP). Наиболее общеупотребительные параметры сокетов, которые нам могут встретиться, — это <code>SO_KEEPALIVE</code>, <code>SO_RCVBUF</code>, <code>SO_SNDBUF</code> и <code>SO_REUSEADDR</code>. Последний должен всегда задаваться для сервера TCP до того, как сервер вызовет функцию <code>bind</code> (см. листинг 11.6). Параметр <code>SO_BROADCAST</code> и десять параметров сокетов многоадресной передачи предназначены только для приложений, передающих соответственно широковещательные или многоадресные сообщения.</p>
     <p>Параметр сокета <code>SO_KEEPALIVE</code> устанавливается многими серверами TCP и автоматически закрывает наполовину открытое соединение. Замечательное свойство этого параметра в том, что он обрабатывается на уровне TCP, не требуя на уровне приложения наличия таймера, измеряющего период отсутствия активности. Однако недостаток этого параметра в том, что он не видит разницы между выходом собеседника из строя и временной потерей соединения с ним. SCTP предоставляет 17 параметров сокетов, с помощью которых приложение может управлять транспортным уровнем. <code>SCTP_NODELAY</code> и <code>SCTP_MAXSEG</code> аналогичны <code>TCP_NODELAY</code> и <code>TCP_MAXSEG</code>, и выполняют схожие функции. Остальные 17 параметров позволяют приложению более точно контролировать поведение стека SCTP. Большинство этих параметров будет рассмотрено в главе 23.</p>
     <p>Параметр сокета <code>SO_LINGER</code> расширяет наши возможности в отношении контроля над функцией <code>close</code> — мы можем отложить ее завершение на некоторое время. Кроме того, этот параметр позволяет нам отправить сегмент RST вместо обычной последовательности из четырех пакетов, завершающих соединение TCP. Следует соблюдать осторожность при отправке сегментов RST, поскольку в этом случае не наступает состояние TCP TIME_WAIT. Бывает, что этот параметр сокета не обеспечивает необходимой нам информации, и тогда требуется реализовать подтверждение на уровне приложения.</p>
     <p>У каждого сокета TCP имеется буфер отправки и буфер приема, а у каждого сокета UDP есть буфер приема. Параметры сокета <code>SO_SNDBUF</code> и <code>SO_RCVBUF</code> позволяют нам изменять размеры этих буферов. Основное применение эти функции находят при передаче большого количества данных по каналам с повышенной пропускной способностью, которые представляют собой соединения TCP либо с широкой полосой пропускания, либо с большой задержкой, часто с использованием расширений из RFC 1323. Сокеты UDP, наоборот, могут стремиться увеличить размер приемного буфера, чтобы позволить ядру установить в очередь больше дейтаграмм, если приложение занято.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Напишите программу, которая выводит заданные по умолчанию размеры буферов отправки и приема TCP, UDP и SCTP, и запустите ее в системе, к которой у вас имеется доступ.</p>
     <p>2. Измените листинг 1.1 следующим образом. Перед вызовом функции connect вызовите функцию <code>getsockopt</code>, чтобы получить размер приемного буфера сокета и MSS. Выведите оба значения. После успешного завершения функции извлеките значения тех же двух параметров сокета и выведите их. Изменились ли значения? Почему? Запустите программу, соединяющуюся с сервером в вашей локальной сети, и программу, соединяющуюся с сервером в удаленной сети. Изменяется ли MSS? Почему? Запустите также программу на разных узлах, к которым у вас есть доступ.</p>
     <p>3. Запустите наш сервер TCP, приведенный в листингах 5.1 и 5.2, и наш клиент из листингов 5.3 и 5.4. Измените функцию <code>main</code> клиента, чтобы установить параметр сокета <code>SO_LINGER</code> перед вызовом функции <code>exit</code>, задав <code>l_onoff</code> равным 1, а <code>l_linger</code> — равным 0. Запустите сервер, а затем запустите клиент. Введите строку или две на стороне клиента для проверки работоспособности, а затем завершите работу клиента, введя символ конца файла. Что происходит? После завершения работы клиента запустите программу <code>netstat</code> на узле клиента и посмотрите, проходит ли сокет через состояние TIME_WAIT.</p>
     <p>4. Будем считать, что два клиента TCP запускаются одновременно. Оба устанавливают параметр сокета <code>SO_REUSEADDR</code> и затем с помощью функции <code>bind</code> связываются с одним и тем же локальным IP-адресом и одним и тем же локальным портом (допустим, 1500). Но один из клиентов соединяется с помощью функции connect с адресом 198.69.10.2, порт 7000, а второй — с адресом 198.69.10.2 (тот же IP-адрес собеседника), порт 8000. Опишите возникающую ситуацию гонок.</p>
     <p>5. Получите исходный код для примеров в этой книге (см. предисловие) и откомпилируйте программу sock (см. раздел В.3). Сначала классифицируйте свой узел как узел, не поддерживающий многоадресную передачу, затем — как поддерживающий многоадресную передачу, но не поддерживающий параметр <code>SO_REUSEPORT</code>, и наконец, как узел, поддерживающий многоадресную передачу с предоставлением параметра <code>SO_REUSEPORT</code>. Попытайтесь запустить несколько экземпляров программы sock в качестве сервера TCP (параметр <code>-s</code> командной строки) на одном и том же порте, связывая универсальный адрес, один из адресов интерфейсов вашего узла и адрес закольцовки (loopback address). Нужно ли вам задавать параметр <code>SO_REUSEADDR</code> (параметр <code>-А</code> командной строки)? Используйте программу <code>netstat</code> для просмотра прослушиваемых сокетов.</p>
     <p>6. Продолжайте предыдущий пример, но запустите сервер UDP (параметр <code>-u</code> командной строки) и попытайтесь запустить два экземпляра, связанные с одними и теми же локальным IP-адресом и портом. Если ваша реализация поддерживает параметр <code>SO_REUSEPORT</code>, попытайтесь использовать ее (параметр <code>-T</code> командной строки).</p>
     <p>7. Многие версии утилиты <code>ping</code> имеют флаг <code>-d</code>, задающий параметр сокета <code>SO_DEBUG</code>. В чем его назначение?</p>
     <p>8. Продолжая пример в конце нашего обсуждения параметра сокета <code>TCP_NODELAY</code>, предположим, что клиент выполняет две операции записи с помощью функции <code>write</code>: первую для 4 байт данных и вторую для 396 байт. Также будем считать, что время задержки ACK — 100 мс, период RTT между клиентом и сервером равен 100 мс, а время обработки сервером каждого клиентского запроса — 50 мс. Нарисуйте временную диаграмму, показывающую взаимодействие алгоритма Нагла с задержанными сегментами ACK.</p>
     <p>9. Снова выполните предыдущее упражнение, считая, что установлен параметр сокета <code>TCP_NODELAY</code>.</p>
     <p>10. Снова выполните упражнение 8, считая, что процесс вызывает функцию <code>writev</code> один раз для обоих буферов (4-байтового и 396-байтового).</p>
     <p>11. Прочтите RFC 1122 [10], чтобы определить рекомендуемый интервал для задержанных сегментов ACK.</p>
     <p>12. В какой из версий наш сервер тратит больше времени — в листинге 5.1 или 5.2? Что происходит, если сервер устанавливает параметр сокета <code>SO_KEEPALIVE</code>, через соединение не происходит обмена данными, узел клиента выходит из строя и не перезагружается?</p>
     <p>13. В какой из версий наш клиент тратит больше времени — в листинге 5.3 или 5.4? Что происходит, если клиент устанавливает параметр сокета <code>SO_KEEPALIVE</code>, через соединение не происходит обмена данными и узел сервера выходит из строя и не перезагружается?</p>
     <p>14. В какой из версий наш клиент тратит больше времени — в листинге 5.3 или 6.2? Что происходит, если клиент устанавливает параметр сокета <code>SO_KEEPALIVE</code>, через соединение не происходит обмена данными и узел сервера выходит из строя и не перезагружается?</p>
     <p>15. Будем считать, что и клиент, и сервер устанавливают параметр сокета <code>SO_KEEPALIVE</code>. Между собеседниками поддерживается соединение, но через это соединение не происходит обмена данными между приложениями. Когда проходят условленные 2 ч и требуется проверить наличие связи, сколькими сегментами TCP обмениваются собеседники?</p>
     <p>16. Почти все реализации определяют константу <code>SO_ACCEPTCONN</code> в заголовочном файле <code>&lt;sys/socket.h&gt;</code>, но мы не описывали этот параметр. Прочтите [69], чтобы понять, зачем этот параметр существует.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 8</p>
     <p>Основные сведения о сокетах UDP</p>
    </title>
    <section>
     <title>
      <p>8.1. Введение</p>
     </title>
     <p>Приложения, использующие TCP и UDP, фундаментально отличаются друг от друга, потому что UDP является ненадежным протоколом дейтаграмм, не ориентированным на установление соединения, и этим принципиально непохож на ориентированный на установление соединения и надежную передачу потока байтов TCP. Тем не менее есть случаи, когда имеет смысл использовать UDP вместо TCP. Подобные случаи мы рассматриваем в разделе 22.4. Некоторые популярные приложения построены с использованием UDP, например DNS (Domain Name System — система доменных имен), NFS (сетевая файловая система — Network File System) и SNMP (Simple Network Management Protocol — простой протокол управления сетью).</p>
     <p>На рис. 8.1 показаны вызовы функций для типичной схемы клиент-сервер UDP. Клиент не устанавливает соединения с сервером. Вместо этого клиент лишь отправляет серверу дейтаграмму, используя функцию <code>sendto</code> (она описывается в следующем разделе), которой нужно задать адрес получателя (сервера) в качестве аргумента. Аналогично, сервер не устанавливает соединения с клиентом. Вместо этого сервер лишь вызывает функцию <code>recvfrom</code>, которая ждет, когда придут данные от какого-либо клиента. Функция <code>recvfrom</code> возвращает адрес клиента (для данного протокола) вместе с дейтаграммой, и таким образом сервер может отправить ответ именно тому клиенту, который прислал дейтаграмму.</p>
     <image l:href="#img_70.png"/>
     <p><strong>Рис. 8.1</strong>. Функции сокета для модели клиент-сервер UDP</p>
     <p>Рисунок 8.1 иллюстрирует временную диаграмму типичного сценария обмена UDP-дейтаграммами между клиентом и сервером. Мы можем сравнить этот пример с типичным обменом по протоколу TCP, изображенным на рис. 4.1.</p>
     <p>В этой главе мы опишем новые функции, применяемые с сокетами UDP, — <code>recvfrom</code> и <code>sendto</code>, и переделаем нашу модель клиент-сервер для применения UDP. Кроме того, мы рассмотрим использование функции connect с сокетом UDP и концепцию асинхронных ошибок.</p>
    </section>
    <section>
     <title>
      <p>8.2. Функции recvfrom и sendto</p>
     </title>
     <p>Эти две функции аналогичны стандартным функциям <code>read</code> и <code>write</code>, но требуют трех дополнительных аргументов.</p>
     <p><code>#include &lt;sys/socket.h&gt;</code></p>
     <empty-line/>
     <p><code>ssize_t recvfrom(int <emphasis>sockfd</emphasis>, void *<emphasis>buff</emphasis>, size_t <emphasis>nbytes</emphasis>, int <emphasis>flags</emphasis>,</code></p>
     <p><code> struct sockaddr *<emphasis>from</emphasis>, socklen_t *<emphasis>addrlen</emphasis>);</code></p>
     <p><code>ssize_t sendto(int <emphasis>sockfd</emphasis>, const void *<emphasis>buff</emphasis>, size_t <emphasis>nbytes</emphasis>, int <emphasis>flags</emphasis>,</code></p>
     <p><code> const struct sockaddr *<emphasis>to</emphasis>, socklen_t <emphasis>addrlen</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают количество записанных или прочитанных байтов в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Первые три аргумента, <code>sockfd</code>, <code>buff</code> и <code>nbytes</code>, идентичны первым трем аргументам функций <code>read</code> и <code>write</code>: дескриптор, указатель на буфер, из которого производится чтение или в который происходит запись, и число байтов для чтения или записи.</p>
     <p>Мы расскажем об аргументе <code>flags</code> в главе 14, где мы рассматриваем функции <code>recv</code>, <code>send</code>, <code>recvmsg</code> и <code>sendmsg</code>, поскольку сейчас в нашем простом примере они не нужны. Пока мы всегда будем устанавливать аргумент <code>flags</code> в нуль.</p>
     <p>Аргумент to для функции <code>sendto</code> — это структура адреса сокета, содержащая адрес протокола (например, IP-адрес и номер порта) адресата. Размер этой структуры адреса сокета задается аргументом <code>addrlen</code>. Функция <code>recvform</code> заполняет структуру адреса сокета, на которую указывает аргумент from, записывая в нее протокольный адрес отправителя дейтаграммы. Число байтов, хранящихся в структуре адреса сокета, также возвращается вызывающему процессу в целом числе, на которое указывает аргумент <code>addrlen</code>. Обратите внимание, что последний аргумент функции <code>sendto</code> является целочисленным значением, в то время как последний аргумент функции <code>recvfrom</code> — это указатель на целое значение (аргумент типа «значение-результат»).</p>
     <p>Последние два аргумента функции recvfrom аналогичны двум последним аргументам функции <code>accept</code>: содержимое структуры адреса сокета по завершении сообщает нам, кто отправил дейтаграмму (в случае UDP) или кто инициировал соединение (в случае TCP). Последние два аргумента функции <code>sendto</code> аналогичны двум последним аргументам функции <code>connect</code>: мы заполняем структуру адреса сокета протокольным адресом получателя дейтаграммы (в случае UDP) или адресом узла, с которым будет устанавливаться соединение (в случае TCP).</p>
     <p>Обе функции возвращают в качестве значения функции длину данных, которые были прочитаны или записаны. При типичном использовании функции <code>recvfrom</code> с протоколом дейтаграмм возвращаемое значение — это объем пользовательских данных в полученной дейтаграмме.</p>
     <p>Дейтаграмма может иметь нулевую длину. В случае UDP при этом возвращается дейтаграмма IP, содержащая заголовок IP (обычно 20 байт для IPv4 или 40 байт для IPv6), 8-байтовый заголовок UDP и никаких данных. Это также означает, что возвращаемое из функции <code>recvfrom</code> нулевое значение вполне приемлемо для протокола дейтаграмм: оно не является признаком того, что собеседник закрыл соединение, как это происходит при возвращении нулевого значения из функции <code>read</code> на сокете TCP. Поскольку протокол UDP не ориентирован на установление соединения, то в нем и не существует такого события, как закрытие соединения.</p>
     <p>Если аргумент from функции <code>recvfrom</code> является пустым указателем, то соответствующий аргумент длины (<code>addrlen</code>) также должен быть пустым указателем, и это означает, что нас не интересует адрес отправителя данных.</p>
     <p>И функция <code>recvfrom</code>, и функция <code>sendto</code> могут использоваться с TCP, хотя обычно в этом нет необходимости.</p>
    </section>
    <section>
     <title>
      <p>8.3. Эхо-сервер UDP: функция main</p>
     </title>
     <p>Теперь мы переделаем нашу простую модель клиент-сервер из главы 5, используя UDP. Диаграмма вызовов функций в программах наших клиента и сервера UDP показана на рис. 8.1. На рис. 8.2 представлены используемые функции. В листинге 8.1<a l:href="#n1" type="note">[1]</a> показана функция сервера <code>main</code>.</p>
     <image l:href="#img_71.png"/>
     <p><strong>Рис. 8.2</strong>. Простая модель клиент-сервер, использующая UDP</p>
     <p><strong>Листинг 8.1</strong>. Эхо-сервер UDP</p>
     <p><code>//udpcliserv/udpserv01.с</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2</code></p>
     <p><code> 3 intmain(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd;</code></p>
     <p><code> 6  struct sockaddr_in servaddr, cliaddr;</code></p>
     <empty-line/>
     <p><code> 7  sockfd = Socket(AF_INET, SOCK_DGRAM, 0);</code></p>
     <empty-line/>
     <p><code> 8  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code> 9  servaddr.sin_family = AF_INET;</code></p>
     <p><code>10  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>11  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <empty-line/>
     <p><code>12  Bind(sockfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>13  dg_echo(sodkfd, (SA*)&amp;cliaddr, sizeof(cliaddr));</code></p>
     <p><code>14 }</code></p>
     <subtitle>Создание сокета UDP, связывание с заранее известным портом при помощи функции bind</subtitle>
     <p><code>7-12</code> Мы создаем сокет UDP, задавая в качестве второго аргумента функции <code>socket</code> значение <code>SOCK_DGRAM</code> (сокет дейтаграмм в протоколе IPv4). Как и в примере сервера TCP, адрес IPv4 для функции bind задается как <code>INADDR_ANY</code>, а заранее известный номер порта сервера — это константа <code>SERV_PORT</code> из заголовка <code>unp.h</code>.</p>
     <p><code>13</code> Затем вызывается функция <code>dg_echo</code> для обработки клиентского запроса сервером.</p>
    </section>
    <section>
     <title>
      <p>8.4. Эхо-сервер UDP: функция dg_echo</p>
     </title>
     <p>В листинге 8.2 показана функция <code>dg_echo</code>.</p>
     <p><strong>Листинг 8.2</strong>. Функция dg_echo: отражение строк на сокете дейтаграмм</p>
     <p><code>//lib/dg_echo.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int n;</code></p>
     <p><code> 6  socklen_t len;</code></p>
     <p><code> 7  char mesg[MAXLINE];</code></p>
     <empty-line/>
     <p><code> 8  for (;;) {</code></p>
     <p><code> 9   len = clilen;</code></p>
     <p><code>10   n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &amp;len);</code></p>
     <empty-line/>
     <p><code>11   Sendto(sockfd, mesg, n, 0, pcliaddr, len);</code></p>
     <p><code>12  }</code></p>
     <p><code>13 }</code></p>
     <subtitle>Чтение дейтаграммы, отражение отправителю</subtitle>
     <p><code>8-12</code> Эта функция является простым циклом, в котором очередная дейтаграмма, приходящая на порт сервера, читается функцией <code>recvfrom</code> и с помощью функции <code>sendto</code> отправляется обратно.</p>
     <p>Несмотря на простоту этой функции, нужно учесть ряд важных деталей. Во- первых, эта функция никогда не завершается. Поскольку UDP — это протокол, не ориентированный на установление соединения, в нем не существует никаких аналогов признака конца файла, используемого в TCP.</p>
     <p>Во-вторых, эта функция позволяет <emphasis>создать последовательный сервер</emphasis>, а не параллельный, который мы получали в случае TCP. Поскольку нет вызова функции <code>fork</code>, один процесс сервера выполняет обработку всех клиентов. В общем случае большинство серверов TCP являются параллельными, а большинство серверов UDP — последовательными.</p>
     <p>Для сокета на уровне UDP происходит неявная буферизация дейтаграмм в виде очереди. Действительно, у каждого сокета UDP имеется буфер приема, и каждая дейтаграмма, приходящая на этот сокет, помещается в его буфер приема. Когда процесс вызывает функцию <code>recvfrom</code>, очередная дейтаграмма из буфера возвращается процессу в порядке FIFO (First In, First Out — первым пришел, первым обслужен). Таким образом, если множество дейтаграмм приходит на сокет до того, как процесс может прочитать данные, уже установленные в очередь для сокета, то приходящие дейтаграммы просто добавляются в буфер приема сокета. Но этот буфер имеет ограниченный размер. Мы обсуждали этот размер и способы его увеличения с помощью параметра сокета <code>SO_RCVBUF</code> в разделе 7.5.</p>
     <p>На рис. 8.3 показано обобщение нашей модели TCP клиент-сервер из главы 5, когда два клиента устанавливают соединения с сервером.</p>
     <image l:href="#img_72.png"/>
     <p><strong>Рис. 8.3</strong>. Обобщение модели TCP клиент-сервер с двумя клиентами</p>
     <p>Здесь имеется два присоединенных сокета, и каждый из присоединенных сокетов на узле сервера имеет свой собственный буфер приема. На рис. 8.4 показан случай, когда два клиента отправляют дейтаграммы серверу UDP.</p>
     <image l:href="#img_73.png"/>
     <p><strong>Рис. 8.4</strong>. Обобщение модели UDP клиент-сервер с двумя клиентами</p>
     <p>Существует только один процесс сервера, и у него имеется один сокет, на который сервер получает все приходящие дейтаграммы и с которого отправляет все ответы. У этого сокета имеется буфер приема, в который помещаются все приходящие дейтаграммы.</p>
     <p>Функция <code>main</code> в листинге 8.1 является зависящей от протокола (она создает сокет семейства <code>AF_INET</code>, а затем выделяет и инициализирует структуру адреса сокета IPv4), но функция <code>dg_echo</code> от протокола не зависит. Причина, по которой функция <code>dg_echo</code> не зависит от протокола, заключается в том, что вызывающий процесс (в нашем случае функция <code>main</code>) должен разместить в памяти структуру адреса сокета корректного размера, и указатель на эту структуру вместе с ее размером передаются в качестве аргументов функции <code>dg_echo</code>. Функция <code>dg_echo</code> никогда не углубляется в эту структуру: она просто передает указатель на нее функциям <code>recvfrom</code> и <code>sendto</code>. Функция <code>recvfrom</code> заполняет эту структуру, вписывая в нее IP-адрес и номер порта клиента, и поскольку тот же указатель (<code>pcliaddr</code>) затем передается функции <code>sendto</code> в качестве адреса получателя, таким образом дейтаграмма отражается обратно клиенту, отправившему дейтаграмму.</p>
    </section>
    <section>
     <title>
      <p>8.5. Эхо-клиент UDP: функция main</p>
     </title>
     <p>Функция <code>main</code> клиента UDP показана в листинге 8.3.</p>
     <p><strong>Листинг 8.3</strong>. Эхо-клиент UDP</p>
     <p><code>//udpcliserv/udpcli01.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd;</code></p>
     <p><code> 6  struct sockaddr_in servaddr;</code></p>
     <empty-line/>
     <p><code> 7  if (argc != 2)</code></p>
     <p><code> 8   err_quit("usage: udpcli &lt;Ipaddress&gt;");</code></p>
     <empty-line/>
     <p><code> 9  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>10  servaddr.sin_family = AF_INET;</code></p>
     <p><code>11  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <p><code>12  Inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr);</code></p>
     <empty-line/>
     <p><code>13  sockfd = Socket(AF_INET, SOCK_DGRAM, 0);</code></p>
     <empty-line/>
     <p><code>14  dg_cli(stdin, sockfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>15  exit(0);</code></p>
     <p><code>16 }</code></p>
     <subtitle>Заполнение структуры адреса сокета адресом сервера</subtitle>
     <p><code>9-12</code> Структура адреса сокета IPv4 заполняется IP-адресом и номером порта сервера. Эта структура будет передана функции <code>dg_cli</code>. Она определяет, куда отправлять дейтаграммы.</p>
     <p><code>13-14</code> Создается сокет UDP и вызывается функция <code>dg_cli</code>.</p>
    </section>
    <section>
     <title>
      <p>8.6. Эхо-клиент UDP: функция dg_cli</p>
     </title>
     <p>В листинге 8.4 показана функция <code>dg_cli</code>, которая выполняет большую часть работы на стороне клиента.</p>
     <p><strong>Листинг 8.4</strong>. Функция dg_cli: цикл обработки клиента</p>
     <p><code>//lib/dg_cli.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int n;</code></p>
     <p><code> 6  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
     <empty-line/>
     <p><code> 7  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
     <empty-line/>
     <p><code> 8   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
     <empty-line/>
     <p><code> 9   n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);</code></p>
     <empty-line/>
     <p><code>10   recvline[n] = 0; /* завершающий нуль */</code></p>
     <p><code>11   Fputs(recvline, stdout);</code></p>
     <p><code>12  }</code></p>
     <p><code>13 }</code></p>
     <p><code>7-12</code> В цикле обработки на стороне клиента имеется четыре шага: чтение строки из стандартного потока ввода при помощи функции <code>fgets</code>, отправка строки серверу с помощью функции <code>sendto</code>, чтение отраженного ответа сервера с помощью функции <code>recvfrom</code> и помещение отраженной строки в стандартный поток вывода с помощью функции <code>fputs</code>.</p>
     <p>Наш клиент не запрашивал у ядра присваивания динамически назначаемого порта своему сокету (тогда как для клиента TCP это имело место при вызове функции <code>connect</code>). В случае сокета UDP при первом вызове функции <code>sendto</code> ядро выбирает динамически назначаемый порт, если с этим сокетом еще не был связан никакой локальный порт. Как и в случае TCP, клиент может вызвать функцию bind явно, но это делается редко.</p>
     <p>Обратите внимание, что при вызове функции <code>recvfrom</code> в качестве пятого и шестого аргументов задаются пустые указатели. Таким образом мы сообщаем ядру, что мы не заинтересованы в том, чтобы знать, кто отправил ответ. Существует риск, что любой процесс, находящийся как на том же узле, так и на любом другом, может отправить на IP-адрес и порт клиента дейтаграмму, которая будет прочитана клиентом, предполагающим, что это ответ сервера. Эту ситуацию мы рассмотрим в разделе 8.8.</p>
     <p>Как и в случае функции сервера <code>dg_echo</code>, функция клиента <code>dg_cli</code> является не зависящей от протокола, но функция main клиента зависит от протокола. Функция main размещает в памяти и инициализирует структуру адреса сокета, относящегося к определенному типу протокола, а затем передает функции <code>dg_cli</code> указатель на структуру вместе с ее размером.</p>
    </section>
    <section>
     <title>
      <p>8.7. Потерянные дейтаграммы</p>
     </title>
     <p>Клиент и сервер UDP в нашем примере являются ненадежными. Если дейтаграмма клиента потеряна (допустим, она проигнорирована неким маршрутизатором между клиентом и сервером), клиент навсегда заблокируется в своем вызове функции <code>recvfrom</code> внутри функции <code>dg_cli</code>, ожидая от сервера ответа, который никогда не придет. Аналогично, если дейтаграмма клиента приходит к серверу, но ответ сервера потерян, клиент навсегда заблокируется в своем вызове функции <code>recvfrom</code>. Единственный способ предотвратить эту ситуацию — поместить тайм-аут в клиентский вызов функции <code>recvfrom</code>. Мы рассмотрим это в разделе 14.2.</p>
     <p>Простое помещение тайм-аута в вызов функции <code>recvfrom</code> — еще не полное решение. Например, если заданное время ожидания истекло, а ответ не получен, мы не можем сказать точно, в чем дело — или наша дейтаграмма не дошла до сервера, или же ответ сервера не пришел обратно. Если бы запрос клиента содержал требование типа «перевести определенное количество денег со счета А на счет Б» (в отличие от случая с нашим простым эхо-сервером), то тогда между потерей запроса и потерей ответа существовала бы большая разница. Более подробно о добавлении надежности в модель клиент-сервер UDP мы расскажем в разделе 22.5.</p>
    </section>
    <section>
     <title>
      <p>8.8. Проверка полученного ответа</p>
     </title>
     <p>В конце раздела 8.6 мы упомянули, что любой процесс, который знает номер динамически назначаемого порта клиента, может отправлять дейтаграммы нашему клиенту, и они будут перемешаны с нормальными ответами сервера. Все, что мы можем сделать, — это изменить вызов функции <code>recvfrom</code>, представленный в листинге 8.4, так, чтобы она возвращала IP-адрес и порт отправителя ответа, и игнорировать любые дейтаграммы, приходящие не от того сервера, которому мы отправляем дейтаграмму. Однако здесь есть несколько ловушек, как мы дальше увидим.</p>
     <p>Сначала мы изменяем функцию клиента <code>main</code> (см. листинг 8.3) для работы со стандартным эхо-сервером (см. табл. 2.1). Мы просто заменяем присваивание</p>
     <p><code>servaddr.sin_port = htons(SERV_PORT);</code></p>
     <p>присваиванием</p>
     <p><code>servaddr.sin_port = htons(7);</code></p>
     <p>Теперь мы можем использовать с нашим клиентом любой узел, на котором работает стандартный эхо-сервер.</p>
     <p>Затем мы переписываем функцию <code>dg_cli</code>, с тем чтобы она размещала в памяти другую структуру адреса сокета для хранения структуры, возвращаемой функцией <code>recvfrom</code>. Мы показываем ее в листинге 8.5.</p>
     <p><strong>Листинг 8.5</strong>. Версия функции dg_cli, проверяющая возвращаемый адрес сокета</p>
     <p><code>//udpcliserv/dgcliaddr.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int n;</code></p>
     <p><code> 6  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
     <p><code> 7  socklen_t len;</code></p>
     <p><code> 8  struct sockaddr *preply_addr;</code></p>
     <empty-line/>
     <p><code> 9  preply_addr = Malloc(servlen);</code></p>
     <empty-line/>
     <p><code>10  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
     <empty-line/>
     <p><code>11   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
     <empty-line/>
     <p><code>12   len = servlen;</code></p>
     <p><code>13   n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &amp;len);</code></p>
     <p><code>14   if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) {</code></p>
     <p><code>15    printf("reply from %s (ignored)\n",</code></p>
     <p><code>16    continue;</code></p>
     <p><code>17   }</code></p>
     <p><code>18   recvline[n] = 0; /* завершающий нуль */</code></p>
     <p><code>19   Fputs(recvline, stdout);</code></p>
     <p><code>20  }</code></p>
     <p><code>21 }</code></p>
     <subtitle>Размещение другой структуры адреса сокета в памяти</subtitle>
     <p><code>9</code> Мы размещаем в памяти другую структуру адреса сокета при помощи функции <code>malloc</code>. Обратите внимание, что функция <code>dg_cli</code> все еще является не зависящей от протокола. Поскольку нам не важно, с каким типом структуры адреса сокета мы имеем дело, мы используем в вызове функции <code>malloc</code> только ее размер.</p>
     <subtitle>Сравнение возвращаемых адресов</subtitle>
     <p><code>12-13</code> В вызове функции <code>recvfrom</code> мы сообщаем ядру, что нужно возвратить адрес отправителя дейтаграммы. Сначала мы сравниваем длину, возвращаемую функцией <code>recvfrom</code> в аргументе типа «значение-результат», а затем сравниваем сами структуры адреса сокета при помощи функции <code>memcmp</code>.</p>
     <p>Новая версия нашего клиента работает замечательно, если сервер находится на узле с одним единственным IP-адресом. Но эта программа может не сработать, если сервер имеет несколько сетевых интерфейсов (multihomed server). Запускаем эту программу, обращаясь к узлу <code>freebsd4</code>, у которого имеется два интерфейса и два IP-адреса:</p>
     <p><code>macosx % <strong>host freebsd4</strong></code></p>
     <p><code>freebsd4.unpbook.com has address 172.24.37.94</code></p>
     <p><code>freebsd4.unpbook.com has address 135.197.17.100</code></p>
     <p><code>macosx % <strong>udpcli02 135.197.17.100</strong></code></p>
     <p><code><strong>hello</strong></code></p>
     <p><code>reply from 172.24.37.94:7 (ignored)</code></p>
     <p><code><strong>goodbye</strong></code></p>
     <p><code>reply from 172.24.37.94:7 (ignored)</code></p>
     <p>По рис. 1.7 видно, что мы задали IP-адрес из другой подсети. Обычно это допустимо. Большинство реализаций IP принимают приходящую IP-дейтаграмму, предназначенную для любого из IP-адресов узла, независимо от интерфейса, на который она приходит [128, с. 217-219]. Документ RFC 1122 [10] называет это моделью системы с гибкой привязкой (weak end system model). Если система должна реализовать то, что в этом документе называется моделью системы с жесткой привязкой (strong end system model), она принимает приходящую дейтаграмму, только если дейтаграмма приходит на тот интерфейс, которому она адресована.</p>
     <p>IP-адрес, возвращаемый функцией <code>recvfrom</code> (IP-адрес отправителя дейтаграммы UDP), не является IP-адресом, на который мы посылали дейтаграмму. Когда сервер отправляет свой ответ, IP-адрес получателя — это адрес 172.24.37.94. Функция маршрутизации внутри ядра на узле <code>freebsd4</code> выбирает адрес 172.24.37.94 в качестве исходящего интерфейса. Поскольку сервер не связал IP-адрес со своим сокетом (сервер связал со своим сокетом универсальный адрес, что мы можем проверить, запустив программу <code>netstat</code> на узле <code>freebsd4</code>), ядро выбирает адрес отправителя дейтаграммы IP. Этим адресом становится первичный IP-адрес исходящего интерфейса [128, с. 232-233]. Если мы отправляем дейтаграмму не на первичный IP-адрес интерфейса (то есть на альтернативное имя, <emphasis>псевдоним</emphasis>), то наша проверка, показанная в листинге 8.5, также окажется неудачной.</p>
     <p>Одним из решений будет проверка клиентом доменного имени отвечающего узла вместо его IP-адреса. Для этого имя сервера ищется в DNS (см. главу 11) на основе IP-адреса, возвращаемого функцией <code>recvfrom</code>. Другое решение — сделать так, чтобы сервер UDP создал по одному сокету для каждого IP-адреса, сконфигурированного на узле, связал с помощью функции <code>bind</code> этот IP-адрес с сокетом, вызвал функцию <code>select</code> для каждого из всех этих сокетов (ожидая, когда какой-либо из них станет готов для чтения), а затем ответил с сокета, готового для чтения. Поскольку сокет, используемый для ответа, связан с IP-адресом, который являлся адресом получателя клиентского запроса (иначе дейтаграмма не была бы доставлена на сокет), мы можем быть уверены, что адреса отправителя ответа и получателя запроса совпадают. Мы показываем эти примеры в разделе 22.6.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В системе Solaris с несколькими сетевыми интерфейсами IP-адрес отправителя ответа сервера — это IP-адрес получателя клиентского запроса. Сценарий, описанный в данном разделе, относится к реализациям, происходящим от Беркли, которые выбирают IP-адрес отправителя, основываясь на исходящем интерфейсе.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>8.9. Запуск клиента без запуска сервера</p>
     </title>
     <p>Следующий сценарий, который мы рассмотрим, — это запуск клиента без запуска сервера. Если мы сделаем так и введем одну строку на стороне клиента, ничего не будет происходить. Клиент навсегда блокируется в своем вызове функции <code>recvfrom</code>, ожидая ответа сервера, который никогда не придет. Но в данном примере это не имеет значения, поскольку сейчас мы стремимся глубже понять протоколы и выяснить, что происходит с нашим сетевым приложением.</p>
     <p>Сначала мы запускаем программу <code>tcpdump</code> на узле <code>macosx</code>, а затем — клиент на том же узле, задав в качестве узла сервера freebsd4. Потом мы вводим одну строку, но эта строка не отражается сервером.</p>
     <p><code>macosx % <strong>udpcli01 172.24.37.94</strong></code></p>
     <p><code><strong>hello, world</strong> <emphasis>мы вводим эту строку,</emphasis></code></p>
     <p><code><emphasis>             но ничего не получаем в ответ</emphasis></code></p>
     <p>В листинге 8.6 показан вывод программы <code>tcpdump</code>.</p>
     <p><strong>Листинг 8.6</strong>. Вывод программы tcpdump, когда процесс сервера не запускается на узле сервера</p>
     <p><code>01 0.0               arp who-has freebsd4 tell macosx</code></p>
     <p><code>02 0.003576 (0.0036) arp reply freebsd4 is-at 0:40:5:42:d6:de</code></p>
     <empty-line/>
     <p><code>03 0.003601 (0.0000) macosx.51139 &gt; freebsd4.9877: udp 13</code></p>
     <p><code>04 0.009781 (0.0062) freebsd4 &gt; macosx: icmp: freebsd4 udp port 9877 unreachable</code></p>
     <p>В первую очередь мы замечаем, что запрос и ответ ARP получены до того, как узел клиента смог отправить дейтаграмму UDP узлу сервера. (Мы оставили этот обмен в выводе программы, чтобы еще раз подчеркнуть, что до отправки IP-дейтаграммы всегда следует отправка запроса и получение ответа по протоколу ARP.)</p>
     <p>В строке 3 мы видим, что дейтаграмма клиента отправлена, но узел сервера отвечает в строке 4 сообщением ICMP о недоступности порта. (Длина 13 включает 12 символов плюс символ новой строки.) Однако эта ошибка ICMP не возвращается клиентскому процессу по причинам, которые мы кратко перечислим чуть ниже. Вместо этого клиент навсегда блокируется в вызове функции <code>recvfrom</code> в листинге 8.4. Мы также отмечаем, что в ICMPv6 имеется ошибка «Порт недоступен», аналогичная ошибке ICMPv4 (см. табл. А.5 и А.6), поэтому результаты, представленные здесь, аналогичны результатам для IPv6.</p>
     <p>Эта ошибка ICMP является <emphasis>асинхронной</emphasis> ошибкой. Ошибка была вызвана функцией <code>sendto</code>, но функция <code>sendto</code> завершилась нормально. Вспомните из раздела 2.9, что нормальное возвращение из операции вывода UDP означает только то, что дейтаграмма была добавлена к очереди вывода канального уровня. Ошибка ICMP не возвращается, пока не пройдет определенное количество времени (4 мс для листинга 8.6), поэтому она и называется асинхронной.</p>
     <p>Основное правило состоит в том, что асинхронные ошибки не возвращаются для сокета UDP, если сокет не был присоединен. Мы показываем, как вызвать функцию <code>connect</code> для сокета UDP, в разделе 8.11. Не все понимают, почему было принято это решение, когда сокеты были впервые реализованы. (Соображения о реализациях обсуждаются на с. 748-749 [128].) Рассмотрим клиент UDP, последовательно отправляющий три дейтаграммы трем различным серверам (то есть на три различных IP-адреса) через один сокет UDP. Клиент входит в цикл, вызывающий функцию <code>recvfrom</code> для чтения ответов. Две дейтаграммы доставляются корректно (то есть сервер был запущен на двух из трех узлов), но на третьем узле не был запущен сервер, и третий узел отвечает сообщением ICMP о недоступности порта. Это сообщение об ошибке ICMP содержит IP-заголовок и UDP-заголовок дейтаграммы, вызвавшей ошибку. (Сообщения об ошибках ICMPv4 и ICMPv6 всегда содержат заголовок IP и весь заголовок UDP или часть заголовка TCP, чтобы дать возможность получателю сообщения определить, какой сокет вызвал ошибку. Это показано на рис. 28.5 и 28.6.) Клиент, отправивший три дейтаграммы, должен знать получателя дейтаграммы, вызвавшей ошибку, чтобы точно определить, какая из трех дейтаграмм вызвала ошибку. Но как ядро может сообщить эту информацию процессу? Единственное, что может возвратить функция <code>recvfrom</code>, — это значение переменной <code>errno</code>. Но функция <code>recvfrom</code> не может вернуть в ошибке IP-адрес и номер порта получателя UDP-дейтаграммы. Следовательно, было принято решение, что эти асинхронные ошибки возвращаются процессу, только если процесс присоединил сокет UDP лишь к одному определенному собеседнику.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Linux возвращает большинство ошибок ICMP о недоступности порта даже для неприсоединенного сокета, если не включен параметр сокета SO_DSBCOMPAT. Возвращаются все ошибки о недоступности получателя, показанные в табл. А.5, за исключением ошибок с кодами 0, 1, 4, 5, 11 и 12.</p>
      <p>Мы вернемся к проблеме асинхронных ошибок с сокетами UDP в разделе 28.7 и покажем простой способ получения этих ошибок на неприсоединенном сокете при помощи нашего собственного демона.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>8.10. Итоговый пример клиент-сервера UDP</p>
     </title>
     <p>На рис. 8.5 крупными черными точками показаны четыре значения, которые должны быть заданы или выбраны, когда клиент отправляет дейтаграмму UDP.</p>
     <image l:href="#img_74.png"/>
     <p><strong>Рис. 8.5</strong>. Обобщение модели клиент-сервер UDP с точки зрения клиента</p>
     <p>Клиент должен задать IP-адрес сервера и номер порта для вызова функции <code>sendto</code>. Обычно клиентский IP-адрес и номер порта автоматически выбираются ядром, хотя мы отмечали, что клиент <emphasis>может</emphasis> вызвать функцию <code>bind</code>. Мы также отмечали, что если эти два значения выбираются для клиента ядром, то динамически назначаемый порт клиента выбирается один раз — при первом вызове функции <code>sendto</code>, и более никогда не изменяется. Однако IP-адрес клиента может меняться для каждой дейтаграммы UDP, которую отправляет клиент, если предположить, что клиент не связывает с сокетом определенный IP-адрес при помощи функции <code>bind</code>. Причину объясняет рис. 8.5: если узел клиента имеет несколько сетевых интерфейсов, клиент может переключаться между ними (на рис. 8.5 один адрес относится к канальному уровню, изображенному слева, другой — к изображенному справа). В худшем варианте этого сценария IP-адрес клиента, выбираемый ядром на основе исходящего канального уровня, будет меняться для каждой дейтаграммы.</p>
     <p>Что произойдет, если клиент с помощью функции <code>bind</code> свяжет IP-адрес со своим сокетом, но ядро решит, что исходящая дейтаграмма должна быть отправлена с какого-то другого канального уровня? В этом случае дейтаграмма IP будет содержать IP-адрес отправителя, отличный от IP-адреса исходящего канального уровня (см. упражнение 8.6).</p>
     <p>На рис. 8.6 представлены те же четыре значения, но с точки зрения сервера.</p>
     <image l:href="#img_75.png"/>
     <p><strong>Рис. 8.6</strong>. Обобщение модели клиент-сервер UDP с точки зрения сервера</p>
     <p>Сервер может узнать по крайней мере четыре параметра для каждой полученной дейтаграммы: IP-адрес отправителя, IP-адрес получателя, номер порта отправителя и номер порта получателя. Вызовы, возвращающие эти сведения серверам TCP и UDP, приведены в табл. 8.1.</p>
     <empty-line/>
     <p><strong>Таблица 8.1</strong>. Информация, доступная серверу из приходящей дейтаграммы IP</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">IP-дейтаграмма клиента</th>
       <th align="left" valign="top">TCP-сервер</th>
       <th align="left" valign="top">UDP-сервер</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP-адрес отправителя</td>
       <td align="left" valign="top">accept</td>
       <td align="left" valign="top">recvfrom</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Номер порта отправителя</td>
       <td align="left" valign="top">accept</td>
       <td align="left" valign="top">recvfrom</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP-адрес получателя</td>
       <td align="left" valign="top">getsockname</td>
       <td align="left" valign="top">recvmsg</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Номер порта получателя</td>
       <td align="left" valign="top">getsockname</td>
       <td align="left" valign="top">getsockname</td>
      </tr>
     </table>
     <p>У сервера TCP всегда есть простой доступ ко всем четырем фрагментам информации для присоединенного сокета, и эти четыре значения остаются постоянными в течение всего времени жизни соединения. Однако в случае соединения UDP IP-адрес получателя можно получить только с помощью установки параметра сокета <code>IP_RECVDSTADDR</code> для IPv4 или <code>IPV6_PKTINFO</code> для IPv6 и последующего вызова функции <code>recvmsg</code> вместо функции <code>recvfrom</code>. Поскольку протокол UDP не ориентирован на установление соединения, IP-адрес получателя может меняться для каждой дейтаграммы, отправляемой серверу. Сервер UDP может также получать дейтаграммы, предназначенные для одного из широковещательных адресов узла или для адреса многоадресной передачи, что мы обсуждаем в главах 20 и 21. Мы покажем, как определить адрес получателя дейтаграммы UDP, в разделе 20.2, после того как опишем функцию <code>recvmsg</code>.</p>
    </section>
    <section>
     <title>
      <p>8.11. Функция connect для UDP</p>
     </title>
     <section>
      <p>В конце разделе 8.9 мы упомянули, что асинхронные ошибки не возвращаются на сокете UDP, если сокет не был присоединен. На самом деле мы можем вызвать функцию <code>connect</code> для сокета UDP (см. раздел 4.3). Но это не приведет ни к чему похожему на соединение TCP: здесь не существует трехэтапного рукопожатия. Ядро просто проверяет, нет ли сведений о заведомой недоступности адресата, после чего записывает IP-адрес и номер порта собеседника, которые содержатся в структуре адреса сокета, передаваемой функции connect, и немедленно возвращает управление вызывающему процессу.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Перегрузка функции connect этой новой возможностью для сокетов UDP может внести путаницу. Если используется соглашение о том, что sockname — это адрес локального протокола, a peername — адрес удаленного протокола, то лучше бы эта функция называлась setpeername. Аналогично, функции bind больше подошло бы название setsockname.</p>
      </cite>
      <p>С учетом этого необходимо понимать разницу между двумя видами сокетов UDP.</p>
      <p>&#9632; <emphasis>Неприсоединенный (unconnected) сокет UDP</emphasis> — это сокет UDP, создаваемый по умолчанию.</p>
      <p>&#9632; <emphasis>Присоединенный {connected) сокет UDP</emphasis> — результат вызова функции connect для сокета UDP.</p>
      <p>Присоединенному сокету UDP свойственны три отличия от неприсоединенного сокета, который создается по умолчанию.</p>
      <p>1. Мы больше не можем задавать IP-адрес получателя и порт для операции вывода. То есть мы используем вместо функции <code>sendto</code> функцию <code>write</code> или <code>send</code>. Все, что записывается в присоединенный сокет UDP, автоматически отправляется на адрес (например, IP-адрес и порт), заданный функцией <code>connect</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Аналогично TCP, мы можем вызвать функцию sendto для присоединенного сокета UDP, но не можем задать адрес получателя. Пятый аргумент функции sendto (указатель на структуру адреса сокета) должен быть пустым указателем, а шестой аргумент (размер структуры адреса сокета) должен быть нулевым. В стандарте POSIX определено, что когда пятый аргумент является пустым указателем, шестой аргумент игнорируется.</p>
      </cite>
      <p>2. Вместо функции <code>recvfrom</code> мы используем функцию <code>read</code> или <code>recv</code>. Единственные дейтаграммы, возвращаемые ядром для операции ввода через присоединенный сокет UDP, — это дейтаграммы, приходящие с адреса, заданного в функции <code>connect</code>. Дейтаграммы, предназначенные для адреса локального протокола присоединенного сокета UDP (например, IP-адрес и порт), но приходящие с адреса протокола, отличного от того, к которому сокет был присоединен с помощью функции <code>connect</code>, не передаются присоединенному сокету. Это ограничивает присоединенный сокет UDP, позволяя ему обмениваться дейтаграммами с одним и только одним собеседником.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Точнее, обмен дейтаграммами происходит только с одним IP-адресом, а не с одним собеседником, поскольку это может быть IP-адрес многоадресной передачи, представляющий, таким образом, группу собеседников.</p>
      </cite>
      <p>3. Асинхронные ошибки возвращаются процессу только при операциях с присоединенным сокетом UDP. В результате, как мы уже говорили, неприсоединенный сокет UDP не получает никаких асинхронных ошибок.</p>
      <p>В табл. 8.2 сводятся воедино свойства, перечисленные в первом пункте, применительно к 4.4BSD.</p>
      <empty-line/>
      <p><strong>Таблица 8.2</strong>. Сокеты TCP и UDP: может ли быть задан адрес протокола получателя</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Тип сокета</th>
        <th align="left" valign="top">write или send</th>
        <th align="left" valign="top">sendto, без указания получателя</th>
        <th align="left" valign="top">sendto, с указанием получателя</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Сокет TCP</td>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top">EISCONN</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Сокет UDP, присоединенный</td>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top">EISCONN</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Сокет UDP, неприсоединенный</td>
        <td align="left" valign="top">EDESTADDRREQ</td>
        <td align="left" valign="top">EDESTADDRREQ</td>
        <td align="left" valign="top">Да</td>
       </tr>
      </table>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>POSIX определяет, что операция вывода, не задающая адрес получателя на неприсоединенном сокете UDP, должна возвращать ошибку ENOTCONN, а не EDESTADDRREQ.</p>
       <p>Solaris 2.5 допускает функцию sendto, которая задает адрес получателя для присоединенного сокета UDP. POSIX определяет, что в такой ситуации должна возвращаться ошибка EISCONN.</p>
      </cite>
      <p>На рис. 8.7 обобщается информация о присоединенном сокете UDP.</p>
      <image l:href="#img_76.png"/>
      <p><strong>Рис. 8.7</strong>. Присоединенный сокет UDP</p>
      <p>Приложение вызывает функцию <code>connect</code>, задавая IP-адрес и номер порта собеседника. Затем оно использует функции <code>read</code> и <code>write</code> для обмена данными с собеседником.</p>
      <p>Дейтаграммы, приходящие с любого другого IP-адреса или порта (который мы обозначаем как «???» на рис. 8.7), не передаются на присоединенный сокет, поскольку либо IP-адрес, либо UDP-порт отправителя не совпадают с адресом протокола, с которым сокет соединяется с помощью функции <code>connect</code>. Эти дейтаграммы могут быть доставлены на какой-то другой сокет UDP на узле. Если нет другого совпадающего сокета для приходящей дейтаграммы, UDP проигнорирует ее и сгенерирует ICMP-сообщение о недоступности порта.</p>
      <p>Обобщая вышесказанное, мы можем утверждать, что клиент или сервер UDP может вызвать функцию <code>connect</code>, только если этот процесс использует сокет UDP для связи лишь с одним собеседником. Обычно именно клиент UDP вызывает функцию <code>connect</code>, но существуют приложения, в которых сервер UDP связывается с одним клиентом на длительное время (например, TFTP), и в этом случае и клиент, и сервер вызывают функцию <code>connect</code>.</p>
      <p>Еще один пример долгосрочного взаимодействия — это DNS (рис. 8.8).</p>
      <image l:href="#img_77.png"/>
      <p><strong>Рис. 8.8</strong>. Пример клиентов и серверов DNS и функции connect</p>
      <p>Клиент DNS может быть сконфигурирован для использования одного или более серверов, обычно с помощью перечисления IP-адресов серверов в файле <code>/etc/resolv.conf</code>. Если в этом файле указан только один сервер (на рисунке этот клиент изображен в крайнем слева прямоугольнике), клиент может вызвать функцию connect, но если перечислено множество серверов (второй справа прямоугольник на рисунке), клиент не может вызвать функцию <code>connect</code>. Обычно сервер DNS обрабатывает также любые клиентские запросы, следовательно, серверы не могут вызывать функцию <code>connect</code>.</p>
     </section>
     <section>
      <title>
       <p>Многократный вызов функции connect для сокета UDP</p>
      </title>
      <p>Процесс с присоединенным сокетом UDP может снова вызвать функцию <code>connect</code> Для этого сокета, чтобы:</p>
      <p>&#9632; задать новый IP-адрес и порт;</p>
      <p>&#9632; отсоединить сокет.</p>
      <p>Первый случай, задание нового собеседника для присоединенного сокета UDP, отличается от использования функции <code>connect</code> с сокетом TCP: для сокета TCP функция <code>connect</code> может быть вызвана только один раз.</p>
      <p>Чтобы отсоединить сокет UDP, мы вызываем функцию <code>connect</code>, но присваиваем элементу семейства структуры адреса сокета (<code>sin_family</code> для IPv4 или <code>sin6_family</code> для IPv6) значение <code>AF_UNSPEC</code>. Это может привести к ошибке <code>EAFNOSUPPORT</code> [128, с. 736], но это нормально. Именно процесс вызова функции <code>connect</code> на уже присоединенном сокете UDP позволяет отсоединить сокет [128, с. 787–788].</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В руководстве BSD по поводу функции connect традиционно говорилось: «Сокеты дейтаграмм могут разрывать связь, соединяясь с недействительными адресами, такими как пустые адреса». К сожалению, ни в одном руководстве не сказано, что представляет собой «пустой адрес», и не упоминается, что в результате возвращается ошибка (что нормально). Стандарт POSIX явно указывает, что семейство адресов должно быть установлено в AF_UNSPEC, но затем сообщает, что этот вызов функции connect может возвратить, а может и не возвратить ошибку EAFNOSUPPORT.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Производительность</p>
      </title>
      <p>Когда приложение вызывает функцию <code>sendto</code> на неприсоединенном сокете UDP, ядра реализаций, происходящих от Беркли, временно соединяются с сокетом, отправляют дейтаграмму и затем отсоединяются от сокета [128, с. 762–763]. Таким образом, вызов функции <code>sendto</code> для последовательной отправки двух дейтаграмм на неприсоединенном сокете включает следующие шесть шагов, выполняемых ядром:</p>
      <p>&#9632; присоединение сокета;</p>
      <p>&#9632; вывод первой дейтаграммы;</p>
      <p>&#9632; отсоединение сокета;</p>
      <p>&#9632; присоединение сокета;</p>
      <p>&#9632; вывод второй дейтаграммы;</p>
      <p>&#9632; отсоединение сокета.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Другой момент, который нужно учитывать, — количество поисков в таблице маршрутизации. Первое временное соединение производит поиск в таблице маршрутизации IP-адреса получателя и сохраняет (кэширует) эту информацию. Второе временное соединение отмечает, что адрес получателя совпадает с кэшированным адресом из таблицы маршрутизации (мы считаем, что обеим функциям sendto задан один и тот же получатель), и ему не нужно снова проводить поиск в таблице маршрутизации [128, с. 737–738].</p>
      </cite>
      <p>Когда приложение знает, что оно будет отправлять множество дейтаграмм одному и тому же собеседнику, эффективнее будет присоединить сокет явно. Вызов функции <code>connect</code>, за которым следуют два вызова функции <code>write</code>, теперь будет включать следующие шаги, выполняемые ядром:</p>
      <p>&#9632; присоединение сокета;</p>
      <p>&#9632; вывод первой дейтаграммы;</p>
      <p>&#9632; вывод второй дейтаграммы.</p>
      <p>В этом случае ядро копирует структуру адреса сокета, содержащую IP-адрес получателя и порт, только один раз, а при двойном вызове функции <code>sendto</code> копирование выполняется дважды. В [89] отмечается, что на временное присоединение отсоединенного сокета UDP приходится примерно треть стоимости каждой передачи UDP.</p>
     </section>
    </section>
    <section>
     <title>
      <p>8.12. Функция dg_cli (продолжение)</p>
     </title>
     <p>Вернемся к функции <code>dg_cli</code>, показанной в листинге 8.4, и перепишем ее, с тем чтобы она вызывала функцию <code>connect</code>. В листинге 8.7 показана новая функция.</p>
     <p><strong>Листинг 8.7</strong>. Функция dg_cli, вызывающая функцию connect</p>
     <p><code>//udpcliserv/dgcliconnect.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int n;</code></p>
     <p><code> 6  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
     <p><code> 7  Connect(sockfd, (SA*)pservaddr, servlen);</code></p>
     <empty-line/>
     <p><code> 8  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
     <empty-line/>
     <p><code> 9   Write(sockfd, sendline, strlen(sendline));</code></p>
     <empty-line/>
     <p><code>10   n = Read(sockfd, recvline, MAXLINE);</code></p>
     <empty-line/>
     <p><code>11   recvline[n] = 0; /* завершающий нуль */</code></p>
     <p><code>12   Fputs(recvline, stdout);</code></p>
     <p><code>13  }</code></p>
     <p><code>14 }</code></p>
     <p>Изменения по сравнению с предыдущей версией — это добавление вызова функции <code>connect</code> и замена вызовов функций <code>sendto</code> и recvfrom вызовами функций <code>write</code> и <code>read</code>. Функция <code>dg_cli</code> остается не зависящей от протокола, поскольку она не вникает в структуру адреса сокета, передаваемую функции <code>connect</code>. Наша функция <code>main</code> клиента, показанная в листинге 8.3, остается той же.</p>
     <p>Если мы запустим программу на узле <code>macosx</code>, задав IP-адрес узла <code>freebsd4</code> (который не запускает наш сервер на порте 9877), мы получим следующий вывод:</p>
     <p><code>macosx % <strong>udpcli04 172.24.37.94</strong></code></p>
     <p><code><strong>hello, world</strong></code></p>
     <p><code>read error: Connection refused</code></p>
     <p>Первое, что мы замечаем, — мы <emphasis>не</emphasis> получаем ошибку, когда запускаем процесс клиента. Ошибка происходит только после того, как мы отправляем серверу первую дейтаграмму. Именно отправка этой дейтаграммы вызывает ошибку ICMP от узла сервера. Но когда клиент TCP вызывает функцию <code>connect</code>, задавая узел сервера, на котором не запущен процесс сервера, функция <code>connect</code> возвращает ошибку, поскольку вызов функции <code>connect</code> вызывает отправку первого пакета трехэтапного рукопожатия TCP, и именно этот пакет вызывает получение сегмента RST от собеседника (см. раздел 4.3).</p>
     <p>В листинге 8.8 показан вывод программы <code>tcpdump</code>.</p>
     <p><strong>Листинг 8.8</strong>. Вывод программы tcpdump при запуске функции dg_cli</p>
     <p><code>macosx % <strong>tcpdump</strong></code></p>
     <p><code>01 0.0            macosx.51139 &gt; freebsd4 9877:udp 13</code></p>
     <p><code>02 0.006180 ( 0.0062) freebsd4 &gt; macosx: icmp: freebsd4 udp port 9877 unreachable</code></p>
     <p>В табл. A.5 мы также видим, что возникшую ошибку ICMP ядро сопоставляет ошибке <code>ECONNREFUSED</code>, которая соответствует выводу строки сообщения <code>Connection refused</code> (В соединении отказано) функцией <code>err_sys</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>К сожалению, не все ядра возвращают сообщения ICMP присоединенному сокету UDP, как мы показали в этом разделе. Обычно ядра реализаций, происходящих от Беркли, возвращают эту ошибку, а ядра System V — не возвращают. Например, если мы запустим тот же клиент на узле Solaris 2.4 и с помощью функции connect соединимся с узлом, на котором не запущен наш сервер, то с помощью программы tcpdump мы сможем убедиться, что ошибка ICMP о недоступности порта возвращается узлом сервера, но вызванная клиентом функция read никогда не завершается. Эта ситуация была исправлена в Solaris 2.5. UnixWare не возвращает ошибку, в то время как AIX, Digital Unix, HP-UX и Linux возвращают.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>8.13. Отсутствие управления потоком в UDP</p>
     </title>
     <section>
      <p>Теперь мы проверим, как влияет на работу приложения отсутствие какого-либо управления потоком в UDP. Сначала мы изменим нашу функцию <code>dg_cli</code> так, чтобы она отправляла фиксированное число дейтаграмм. Она больше не будет читать из стандартного потока ввода. В листинге 8.9 показана новая версия функции. Эта функция отправляет серверу 2000 дейтаграмм UDP по 1400 байт каждая.</p>
      <p><strong>Листинг 8.9</strong>. Функция dg_cli, отсылающая фиксированное число дейтаграмм серверу</p>
      <p><code>//udpcliserv/dgcliloop1.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 #define NDG 2000 /* количество дейтаграмм для отправки */</code></p>
      <p><code> 3 #define DGLEN 1400 /* длина каждой дейтаграммы */</code></p>
      <empty-line/>
      <p><code> 4 void</code></p>
      <p><code> 5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  int i;</code></p>
      <p><code> 8  char sendline[DGLEN];</code></p>
      <empty-line/>
      <p><code> 9  for (i = 0; i &lt; NDG; i++) {</code></p>
      <p><code>10   Sendto(sockfd, sendline, DGLEN, 0, pservaddr, servlen);</code></p>
      <p><code>11  }</code></p>
      <p><code>12 }</code></p>
      <p>Затем мы изменяем сервер так, чтобы он получал дейтаграммы и считал число полученных дейтаграмм. Сервер больше не отражает дейтаграммы обратно клиенту. В листинге 8.10 показана новая функция <code>dg_echo</code>. Когда мы завершаем процесс сервера нажатием клавиши прерывания на терминале (что приводит к отправке сигнала <code>SIGINT</code> процессу), сервер выводит число полученных дейтаграмм и завершается.</p>
      <p><strong>Листинг 8.10</strong>. Функция dg_echo, считающая полученные дейтаграммы</p>
      <p><code>//udpcliserv/dgecholoop1.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 static void recvfrom_int(int);</code></p>
      <p><code> 3 static int count;</code></p>
      <empty-line/>
      <p><code> 4 void</code></p>
      <p><code> 5 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  socklen_t len;</code></p>
      <p><code> 8  char mesg[MAXLINE];</code></p>
      <empty-line/>
      <p><code> 9  Signal (SIGINT, recvfrom_int);</code></p>
      <empty-line/>
      <p><code>10  for (;;) {</code></p>
      <p><code>11   len = clilen;</code></p>
      <p><code>12   Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &amp;len);</code></p>
      <empty-line/>
      <p><code>13   count++;</code></p>
      <p><code>14  }</code></p>
      <p><code>15 }</code></p>
      <empty-line/>
      <p><code>16 static void</code></p>
      <p><code>17 recvfrom_int(int signo)</code></p>
      <p><code>18 {</code></p>
      <p><code>19  printf("\nreceived %d datagrams\n", count);</code></p>
      <p><code>20  exit(0);</code></p>
      <p><code>21 }</code></p>
      <p>Теперь мы запускаем сервер на узле <code>freebsd</code>, который представляет собой медленный компьютер SPARCStation. Клиент мы запускаем в значительно более быстрой системе RS/6000 с операционной системой <code>aix</code>. Они соединены друг с другом напрямую каналом Ethernet на 100 Мбит/с. Кроме того, мы запускаем программу <code>netstat -s</code> на узле сервера и до, и после запуска клиента и сервера, поскольку выводимая статистика покажет, сколько дейтаграмм мы потеряли. В листинге 8.11 показан вывод сервера.</p>
      <p><strong>Листинг 8.11</strong>. Вывод на узле сервера</p>
      <p><code>freebsd % <strong>netstat -s -p udp</strong></code></p>
      <p><code>udp:</code></p>
      <p><code> 71208 datagrams received</code></p>
      <p><code> 0 with incomplete header</code></p>
      <p><code> 0 with bad data length field</code></p>
      <p><code> 0 with bad checksum</code></p>
      <p><code> 0 with no checksum</code></p>
      <p><code> 832 dropped due to no socket</code></p>
      <p><code> 16 broadcast/multicast datagrams dropped due to no socket</code></p>
      <p><code> 1971 dropped due to full socket buffers</code></p>
      <p><code> 0 not for hashed pcb</code></p>
      <p><code> 68389 delivered</code></p>
      <p><code> 137685 datagrams output</code></p>
      <p><code>freebsd % <strong>udpserv06</strong> <emphasis>запускаем наш сервер</emphasis></code></p>
      <p><code><emphasis>                    клиент посылает дейтаграммы</emphasis></code></p>
      <p><code><strong>^C </strong><emphasis>для окончания работы клиента вводим наш символ прерывания</emphasis></code></p>
      <p><code>freebsd % <strong>netstat -s -р udp</strong></code></p>
      <p><code>udp</code></p>
      <p><code> 73208 datagrams received</code></p>
      <p><code> 0 with incomplete header</code></p>
      <p><code> 0 with bad data length field</code></p>
      <p><code> 0 with bad checksum</code></p>
      <p><code> 0 with no checksum</code></p>
      <p><code> 832 dropped due to no socket</code></p>
      <p><code> 16 broadcast/multicast datagrams dropped due to no socket</code></p>
      <p><code> 3941 dropped due to full socket buffers</code></p>
      <p><code> 0 not for hashed pcb</code></p>
      <p><code> 68419 delivered</code></p>
      <p><code> 137685 datagrams output</code></p>
      <p>Клиент отправил 2000 дейтаграмм, но приложение-сервер получило только 30 из них, что означает уровень потерь 98%. Ни сервер, ни клиент <emphasis>не получают</emphasis> сообщения о том, что эти дейтаграммы потеряны. Как мы и говорили, UDP не имеет возможности управления потоком — он ненадежен. Как мы показали, для отправителя UDP не составляет труда переполнить буфер получателя.</p>
      <p>Если мы посмотрим на вывод программы <code>netstat</code>, то увидим, что общее число дейтаграмм, полученных узлом сервера (не приложением-сервером) равно 2000 (73 208 – 71 208). Счетчик <code>dropped due to full socket buffers</code> (отброшено из-за переполнения буферов сокета) показывает, сколько дейтаграмм было получено UDP и проигнорировано из-за того, что приемный буфер принимающего сокета был полон [128, с. 775]. Это значение равно 1970 (3941 – 1971), что при добавлении к выводу счетчика дейтаграмм, полученных приложением (30), дает 2000 дейтаграмм, полученных узлом. К сожалению, счетчик дейтаграмм, отброшенных из-за заполненного буфера, в программе <code>netstat</code> распространяется на всю систему. Не существует способа определить, на какие приложения (например, какие порты UDP) это влияет.</p>
      <p>Число дейтаграмм, полученных сервером в этом примере, недетерминировано. Оно зависит от многих факторов, таких как нагрузка сети, загруженность узла клиента и узла сервера.</p>
      <p>Если мы запустим тот же клиент и тот же сервер, но на этот раз клиент на медленной системе Sun, а сервер на быстрой системе RS/6000, никакие дейтаграммы не теряются.</p>
      <p><code>aix % <strong>udpserv06</strong></code></p>
      <p><code><strong>^?</strong> <emphasis>после окончания работы клиента вводим наш символ прерывания</emphasis></code></p>
      <p><code>received 2000 datagrams</code></p>
     </section>
     <section>
      <title>
       <p>Приемный буфер сокета UDP</p>
      </title>
      <p>Число дейтаграмм UDP, установленных в очередь UDP, для данного сокета ограничено размером его приемного буфера. Мы можем изменить его с помощью параметра сокета <code>SO_RCVBUF</code>, как мы показали в разделе 7.5. В FreeBSD по умолчанию размер приемного буфера сокета UDP равен 42 080 байт, что допускает возможность хранения только 30 из наших 1400-байтовых дейтаграмм. Если мы увеличим размер приемного буфера сокета, то можем рассчитывать, что сервер получит дополнительные дейтаграммы. В листинге 8.12 представлена измененная функция <code>dg_echo</code> из листинга 8.10, которая увеличивает размер приемного буфера сокета до 240 Кбайт. Если мы запустим этот сервер в системе Sun, а клиент — в системе RS/6000, то счетчик полученных дейтаграмм будет иметь значение 103. Поскольку это лишь немногим лучше, чем в предыдущем примере с размером буфера, заданным по умолчанию, ясно, что мы пока не получили решения проблемы.</p>
      <p><strong>Листинг 8.12</strong>. Функция dg_echo, увеличивающая размер приемного буфера сокета</p>
      <p><code>//udpcliserv/dgecholоор2.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 static void recvfrom_int(int);</code></p>
      <p><code> 3 static int count;</code></p>
      <empty-line/>
      <p><code> 4 void</code></p>
      <p><code> 5 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  int n;</code></p>
      <p><code> 8  socklen_t len;</code></p>
      <p><code> 9  char mesg[MAXLINE];</code></p>
      <empty-line/>
      <p><code>10  Signal(SIGINT, recvfrom_int);</code></p>
      <empty-line/>
      <p><code>11  n = 240 * 1024;</code></p>
      <p><code>12  Setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &amp;n, sizeof(n));</code></p>
      <empty-line/>
      <p><code>13  for (;;) {</code></p>
      <p><code>14   len = clilen;</code></p>
      <p><code>15   Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &amp;len);</code></p>
      <empty-line/>
      <p><code>16   count++;</code></p>
      <p><code>17  }</code></p>
      <p><code>18 }</code></p>
      <empty-line/>
      <p><code>19 static void</code></p>
      <p><code>20 recvfrom_int(int signo)</code></p>
      <p><code>21 {</code></p>
      <p><code>22  printf("\nreceived %d datagrams\n", count);</code></p>
      <p><code>23  exit(0);</code></p>
      <p><code>24 }</code></p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Почему мы устанавливаем размер буфера приема сокета равным 240&#215;1024 байт в листинге 8.12? Максимальный размер приемного буфера сокета в BSD/OS 2.1 по умолчанию равен 262 144 байта (256&#215;1024), но из-за способа размещения буфера в памяти (описанного в главе 2 [128]) он в действительности ограничен до 246 723 байт. Многие более ранние системы, основанные на 4.3BSD, ограничивали размер буфера приема сокета примерно до 52 000 байт.</p>
      </cite>
     </section>
    </section>
    <section>
     <title>
      <p>8.14. Определение исходящего интерфейса для UDP</p>
     </title>
     <p>С помощью присоединенного сокета UDP можно также задавать исходящий интерфейс, который будет использован для отправки дейтаграмм к определенному получателю. Это объясняется побочным эффектом функции <code>connect</code>, примененной к сокету UDP: ядро выбирает локальный IP-адрес (предполагается, что процесс еще не вызвал функцию <code>bind</code> для явного его задания). Локальный адрес выбирается в процессе поиска адреса получателя в таблице маршрутизации, причем берется основной IP-адрес интерфейса, с которого, согласно таблице, будут отправляться дейтаграммы.</p>
     <p>В листинге 8.13 показана простая программа UDP, которая с помощью функции connect соединяется с заданным IP-адресом и затем вызывает функцию <code>getsockname</code>, выводя локальный IP-адрес и порт.</p>
     <p><strong>Листинг 8.13</strong>. Программа UDP, использующая функцию connect для определения исходящего интерфейса</p>
     <p><code>//udpcliserv/udpcli09.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd;</code></p>
     <p><code> 6  socklen_t len;</code></p>
     <p><code> 7  struct sockaddr_in cliaddr, servaddr;</code></p>
     <empty-line/>
     <p><code> 8  if (argc != 2)</code></p>
     <p><code> 9   err_quit("usage: udpcli &lt;Ipaddress&gt;");</code></p>
     <empty-line/>
     <p><code>10  sockfd = Socket(AF_INET, SOCK_DGRAM, 0);</code></p>
     <empty-line/>
     <p><code>11  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>12  servaddr.sin_family = AF_INET;</code></p>
     <p><code>13  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <p><code>14  Inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr);</code></p>
     <empty-line/>
     <p><code>15  Connect(sockfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>16  len = sizeof(cliaddr);</code></p>
     <p><code>17  Getsockname(sockfd, (SA*)&amp;cliaddr, &amp;len);</code></p>
     <p><code>18  printf("local address %s\n", Sock_ntop((SA*)&amp;cliaddr, len));</code></p>
     <empty-line/>
     <p><code>19  exit(0);</code></p>
     <p><code>20 }</code></p>
     <p>Если мы запустим программу на узле <code>freebsd</code> с несколькими сетевыми интерфейсами, то получим следующий вывод:</p>
     <p><code>freebsd % <strong>udpcli09 206.168.112.96</strong></code></p>
     <p><code>local address 12.106.32.254:52329</code></p>
     <empty-line/>
     <p><code>freebsd % <strong>udpcli09 192.168.42.2</strong></code></p>
     <p><code>local address 192.168.42.1:52330</code></p>
     <empty-line/>
     <p><code>freebsd % <strong>udpcli09 127.0.0.1</strong></code></p>
     <p><code>local address 127.0.0.1:52331</code></p>
     <p>По рис. 1.7 видно, что когда мы запускаем программу первые два раза, аргументом командной строки является IP-адрес в разных сетях Ethernet. Ядро присваивает локальный IP-адрес первичному адресу интерфейса в соответствующей сети Ethernet. При вызове функции <code>connect</code> на сокете UDP ничего не отправляется на этот узел — это полностью локальная операция, которая сохраняет IP-адрес и порт собеседника. Мы также видим, что вызов функции connect на неприсоединенном сокете UDP также присваивает сокету динамически назначаемый порт.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>К сожалению, эта технология действует не во всех реализациях, что особенно касается ядер, происходящих от SVR4. Например, это не работает в Solaris 2.5, но работает в AIX, Digital Unix, Linux, MacOS X и Solaris 2.6.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>8.15. Эхо-сервер TCP и UDP, использующий функцию select</p>
     </title>
     <p>Теперь мы объединим наш параллельный эхо-сервер TCP из главы 5 и наш последовательный эхо-сервер UDP из данной главы в один сервер, использующий функцию <code>select</code> для мультиплексирования сокетов TCP и UDP. В листинге 8.14 представлена первая часть этого сервера.</p>
     <p><strong>Листинг 8.14</strong>. Первая часть эхо-сервера, обрабатывающего сокеты TCP и UDP при помощи функции select</p>
     <p><code>//udpcliserv/udpservselect01.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int listenfd, connfd, udpfd, nready, maxfdp1;</code></p>
     <p><code> 6  char mesg[MAXLINE];</code></p>
     <p><code> 7  pid_t childpid;</code></p>
     <p><code> 8  fd_set rset;</code></p>
     <p><code> 9  ssize_t n;</code></p>
     <p><code>10  socklen_t len;</code></p>
     <p><code>11  const int on = 1;</code></p>
     <p><code>12  struct sockaddr_in cliaddr, servaddr;</code></p>
     <p><code>13  void sig_chld(int);</code></p>
     <empty-line/>
     <p><code>14  /* создание прослушиваемого сокета TCP */</code></p>
     <p><code>15  listenfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code>16  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>17  servaddr.sin_family = AF_INET;</code></p>
     <p><code>18  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>19  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <empty-line/>
     <p><code>20  Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
     <p><code>21  Bind(listenfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>22  Listen(listenfd, LISTENQ);</code></p>
     <empty-line/>
     <p><code>23  /* создание сокета UDP */</code></p>
     <p><code>24  udpfd = Socket(AF_INET, SOCK_DGRAM, 0);</code></p>
     <empty-line/>
     <p><code>25  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>26  servaddr.sin_family = AF_INET;</code></p>
     <p><code>27  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>28  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <empty-line/>
     <p><code>29  Bind(udpfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <subtitle>Создание прослушиваемого сокета TCP</subtitle>
     <p><code>14-22</code> Создается прослушиваемый сокет TCP, который связывается с заранее известным портом сервера. Мы устанавливаем параметр сокета <code>SO_REUSEADDR</code> в случае, если на этом порте существуют соединения.</p>
     <subtitle>Создание сокета UDP</subtitle>
     <p><code>23-29</code> Также создается сокет UDP и связывается с тем же портом. Даже если один и тот же порт используется для сокетов TCP и UDP, нет необходимости устанавливать параметр сокета <code>SO_REUSEADDR</code> перед этим вызовом функции <code>bind</code>, поскольку порты TCP не зависят от портов UDP.</p>
     <p>В листинге 8.15 показана вторая часть нашего сервера.</p>
     <p><strong>Листинг 8.15</strong>. Вторая половина эхо-сервера, обрабатывающего TCP и UDP при помощи функции select</p>
     <p><code>udpcliserv/udpservselect01.c</code></p>
     <p><code>30  Signal(SIGCHLD, sig_chld); /* требуется вызвать waitpid() */</code></p>
     <empty-line/>
     <p><code>31  FD_ZERO(&amp;rset);</code></p>
     <p><code>32  maxfdp1 = max(listenfd, udpfd) + 1;</code></p>
     <p><code>33  for (;;) {</code></p>
     <p><code>34   FD_SET(listenfd, &amp;rset);</code></p>
     <p><code>35   FD_SET(udpfd, &amp;rset);</code></p>
     <p><code>36   if ((nready = select(maxfdp1, &amp;rset, NULL, NULL, NULL)) &lt; 0) {</code></p>
     <p><code>37    if (errno == EINTR)</code></p>
     <p><code>38     continue; /* назад в for() */</code></p>
     <p><code>39    else</code></p>
     <p><code>40     err_sys("select error");</code></p>
     <p><code>41   }</code></p>
     <p><code>42   if (FD_ISSET(listenfd, &amp;rset)) {</code></p>
     <p><code>43    len = sizeof(cliaddr);</code></p>
     <p><code>44    connfd = Accept(listenfd, (SA*)&amp;cliaddr, &amp;len);</code></p>
     <empty-line/>
     <p><code>45    if ((childpid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>46     Close(listenfd); /* закрывается прослушиваемый сокет */</code></p>
     <p><code>47     str_echo(connfd); /* обработка запроса */</code></p>
     <p><code>48     exit(0);</code></p>
     <p><code>49    }</code></p>
     <p><code>50    Close(connfd); /* родитель закрывает присоединенный сокет */</code></p>
     <p><code>51   }</code></p>
     <p><code>52   if (FD_ISSET(udpfd, &amp;rset)) {</code></p>
     <p><code>53    len = sizeof(cliaddr);</code></p>
     <p><code>54    n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA*)&amp;cliaddr, &amp;len);</code></p>
     <empty-line/>
     <p><code>55    Sendto(udpfd, mesg, n, 0, (SA*)&amp;cliaddr, len);</code></p>
     <p><code>56   }</code></p>
     <p><code>57  }</code></p>
     <p><code>58 }</code></p>
     <subtitle>Установка обработчика сигнала SIGCHLD</subtitle>
     <p><code>30</code> Для сигнала <code>SIGCHLD</code> устанавливается обработчик, поскольку соединения TCP будут обрабатываться дочерним процессом. Этот обработчик сигнала мы показали в листинге 5.8.</p>
     <subtitle>Подготовка к вызову функции select</subtitle>
     <p><code>31-32</code> Мы инициализируем набор дескрипторов для функции <code>select</code> и вычисляем максимальный из двух дескрипторов, готовности которого будем ожидать.</p>
     <subtitle>Вызов функции select</subtitle>
     <p><code>34-41</code> Мы вызываем функцию <code>select</code>, ожидая только готовности к чтению прослушиваемого сокета TCP или сокета UDP. Поскольку наш обработчик сигнала <code>sig_chld</code> может прервать вызов функции <code>select</code>, обрабатываем ошибку <code>EINTR</code>.</p>
     <subtitle>Обработка нового клиентского соединения</subtitle>
     <p><code>42-51</code> С помощью функции <code>accept</code> мы принимаем новое клиентское соединение, а когда прослушиваемый сокет TCP готов для чтения, с помощью функции <code>fork</code> порождаем дочерний процесс и вызываем нашу функцию <code>str_echo</code> в дочернем процессе. Это та же последовательность действий, которую мы выполняли в главе 5.</p>
     <subtitle>Обработка приходящей дейтаграммы</subtitle>
     <p><code>52-57</code> Если сокет UDP готов для чтения, дейтаграмма пришла. Мы читаем ее с помощью функции <code>recvfrom</code> и отправляем обратно клиенту с помощью функции <code>sendto</code>.</p>
    </section>
    <section>
     <title>
      <p>8.16. Резюме</p>
     </title>
     <p>Преобразовать наши эхо-клиент и эхо-сервер так, чтобы использовать UDP вместо TCP, оказалось несложно. Но при этом мы лишились множества возможностей, предоставляемых протоколом TCP: определение потерянных пакетов и повторная передача, проверка, приходят ли пакеты от корректного собеседника, и т.д. Мы возвратимся к этой теме в разделе 22.5 и увидим, как можно улучшить надежность приложения UDP.</p>
     <p>Сокеты UDP могут генерировать асинхронные ошибки, то есть ошибки, о которых сообщается спустя некоторое время после того, как пакет был отправлен. Сокеты TCP всегда сообщают приложению о них, но в случае UDP для получения этих ошибок сокет должен быть присоединенным.</p>
     <p>В UDP отсутствует возможность управления потоком, что очень легко продемонстрировать. Обычно это не создает проблем, поскольку многие приложения UDP построены с использованием модели «запрос-ответ» и не предназначены для передачи большого количества данных.</p>
     <p>Есть еще ряд моментов, которые нужно учитывать при написании приложений UDP, но мы рассмотрим их в главе 22 после описания функций интерфейсов, широковещательной и многоадресной передачи.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Допустим, у нас имеется два приложения, одно использует TCP, а другое — UDP. В приемном буфере сокета TCP находится 4096 байт данных, а в приемном буфере для сокета UDP — две дейтаграммы по 2048 байт. Приложение TCP вызывает функцию <code>read</code> с третьим аргументом 4096, а приложение UDP вызывает функцию <code>recvfrom</code> с третьим аргументом 4096. Есть ли между этими вызовами какая-нибудь разница?</p>
     <p>2. Что произойдет в листинге 8.2, если мы заменим последний аргумент функции <code>sendto</code> (который мы обозначили <code>len</code>) аргументом <code>clilen</code>?</p>
     <p>3. Откомпилируйте и запустите сервер UDP из листингов 8.1 и 8.4, а затем — клиент из листингов 8.3 и 8.4. Убедитесь в том, что клиент и сервер работают вместе.</p>
     <p>4. Запустите программу <code>ping</code> в одном окне, задав параметр <code>-i 60</code> (отправка одного пакета каждые 60 секунд; некоторые системы используют ключ <code>I</code> вместо <code>i</code>), параметр <code>-v</code> (вывод всех полученных сообщений об ошибках ICMP) и задав адрес закольцовки на себя (обычно 127.0.0.1). Мы будем использовать эту программу, чтобы увидеть ошибку ICMP недоступности порта, возвращаемую узлом сервера. Затем запустите наш клиент из предыдущего упражнения в другом окне, задав IP-адрес некоторого узла, на котором не запущен сервер. Что происходит?</p>
     <p>5. Рассматривая рис. 8.3, мы сказали, что каждый присоединенный сокет TCP имеет свой собственный буфер приема. Как вы думаете, есть ли у прослушиваемого сокета свой собственный буфер приема?</p>
     <p>6. Используйте программу <code>sock</code> (см. раздел В.3) и такое средство, как, например, <code>tcpdump</code> (см. раздел В.5), чтобы проверить утверждение из раздела 8.10: если клиент с помощью функции <code>bind</code> связывает IP-адрес со своим сокетом, но отправляет дейтаграмму, исходящую от другого интерфейса, то результирующая дейтаграмма содержит IP-адрес, который был связан с сокетом, даже если он не соответствует исходящему интерфейсу.</p>
     <p>7. Откомпилируйте программы из раздела 8.13 и запустите клиент и сервер на различных узлах. Помещайте <code>printf</code> в клиент каждый раз, когда дейтаграмма записывается в сокет. Изменяет ли это процент полученных пакетов? Почему? Вызывайте <code>printf</code> из сервера каждый раз, когда дейтаграмма читается из сокета. Изменяет ли это процент полученных пакетов? Почему?</p>
     <p>8. Какова наибольшая длина, которую мы можем передать функции <code>sendto</code> для сокета UDP/IPv4, то есть каково наибольшее количество данных, которые могут поместиться в дейтаграмму UDP/IPv4? Что изменяется в случае UDP/IPv6?</p>
     <p>Измените листинг 8.4, с тем чтобы отправить одну дейтаграмму UDP максимального размера, считать ее обратно и вывести число байтов, возвращаемых функцией <code>recvfrom</code>.</p>
     <p>9. Измените листинг 8.15 таким образом, чтобы он соответствовал RFC 1122: для сокета UDP следует использовать параметр <code>IP_RECVDSTADDR</code>.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 9</p>
     <p>Основы сокетов SCTP</p>
    </title>
    <section>
     <title>
      <p>9.1. Введение</p>
     </title>
     <p>SCTP — новый транспортный протокол, принятый IETF в качестве стандарта в 2000 году. (Для сравнения, протокол TCP был стандартизован в 1981 году.) Изначально SCTP проектировался с учетом потребностей растущего рынка IP-телефонии, и предназначался, в частности, для передачи телефонного сигнала через Интернет. Требования, которым должен был отвечать SCTP, описываются в RFC 2719 [84]. SCTP — надежный протокол, ориентированный на передачу сообщений, предоставляющий возможность работать с несколькими потоками каждой паре конечных точек, а также обеспечивающий поддержку концепции многоинтерфейсного узла на транспортном уровне. Поскольку это относительно новый протокол, он распространен не так широко, как TCP и UDP, однако он обладает особенностями, облегчающими проектирование некоторых видов приложений. Выбору между SCTP и TCP будет посвящен раздел 23.12.</p>
     <p>Несмотря на принципиальную разницу между SCTP и TCP, с точки зрения приложения интерфейс SCTP типа «один-к-одному» почти ничем не отличается от интерфейса TCP. Это делает перенос приложений достаточно тривиальным, однако при таком переносе некоторые усовершенствованные функции SCTP остаются незадействованными. Интерфейс типа «один-ко-многим» задействует эти функции «на всю катушку», но переход к нему может потребовать значительной переделки существующих приложений. Новый интерфейс рекомендуется использовать большинству новых приложений, разрабатываемых в расчете на SCTP.</p>
     <p>Эта глава описывает дополнительные элементарные функции сокетов, которые могут использоваться с SCTP. Сначала мы опишем две модели интерфейса, доступные разработчику приложения. В главе 10 мы разработаем новую версию эхо-сервера, использующую модель «один-ко-многим». Кроме того, мы опишем новые функции, которые предназначены только для SCTP. Особое внимание будет уделено функции <code>shutdown</code> и отличиям процедуры завершения ассоциации SCTP от процедуры завершения соединения TCP. В разделе 23.4 мы рассмотрим пример использования уведомлений для оповещения приложения о важных событиях, связанных с протоколом (помимо прибытия новых пользовательских данных).</p>
     <p>Интерфейс функций SCTP еще не стабилизировался полностью, что объясняется молодостью этого протокола. На момент написания этой книги описываемые в ней интерфейсы считались стабилизировавшимися, однако они еще не были распространены так широко, как остальные части API сокетов. Те, кто работает с приложениями, ориентированными исключительно на SCTP, должны быть готовы устанавливать обновления для ядра или для операционной системы в целом, а приложения, рассчитанные на повсеместное использование, должны уметь работать с TCP, потому что протокол SCTP пока что доступен далеко не на всех системах.</p>
    </section>
    <section>
     <title>
      <p>9.2. Модели интерфейса</p>
     </title>
     <section>
      <p>Сокеты SCTP бывают двух типов: «один-к-одному» и «один-ко-многим». Сокету типа «один-к-одному» всегда сопоставляется ровно одна ассоциация SCTP. Вспомните, что в разделе 2.5 мы отмечали, что ассоциация является соединением между двумя системами, которое может задействовать более двух IP-адресов, если хотя бы одна из систем имеет несколько интерфейсов. Связь между сокетом и ассоциацией SCTP такая же, как между сокетом и соединением TCP. Сокету типа «один-ко-многим» может сопоставляться одновременно несколько активных ассоциаций. То же самое имеет место и в UDP, где сокет, привязанный к конкретному порту, может получать дейтаграммы от нескольких конечных точек UDP, передающих данные одновременно.</p>
      <p>Выбор интерфейса при разработке приложения должен осуществляться с учетом нескольких факторов:</p>
      <p>&#9632; тип сервера (последовательный или параллельный);</p>
      <p>&#9632; количество дескрипторов сокетов, с которыми должен работать сервер;</p>
      <p>&#9632; важно ли оптимизировать работу приложения, разрешив передачу данных в третьем (и, возможно, четвертом) пакете четырехэтапного рукопожатия;</p>
      <p>&#9632; для какого количества соединений существует необходимость хранить информацию о состоянии.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Когда API сокетов для протокола SCTP еще только разрабатывался, сокеты разных типов назывались по-разному. Читатели до сих пор могут столкнуться со старой терминологией в документации или исходном коде. Изначально сокет типа «один-к-одному» назывался сокетом типа TCP (TCP-style socket), а сокет типа «один-ко-многим» — сокетом типа UDP (UDP-style socket).</p>
       <p>Впоследствии от этих терминов пришлось отказаться, так как они создавали впечатление, что SCTP будет вести себя, как TCP или UDP, при использовании сокетов соответствующих типов. На самом деле имелось в виду только одно различие между TCP и UDP: возможность одновременной работы с несколькими адресатами на транспортном уровне. Современные термины («один-к-одному» и «один-ко-многим») фокусируют наше внимание на главном отличии двух типов сокетов.</p>
       <p>Наконец, обратите внимание, что некоторые авторы используют термин «несколько-к-одному» вместо «один-ко-многим». Эти термины взаимозаменяемы.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Сокет типа «один-к-одному»</p>
      </title>
      <p>Данный тип сокета был разработан специально для облегчения переноса существующих приложений с TCP на SCTP. Его модель практически идентична описанной в главе 4. Существуют, конечно, некоторые отличия, о которых следует помнить (в особенности, при переносе приложений):</p>
      <p>1. Все параметры сокетов должны быть преобразованы к соответствующим эквивалентам SCTP. Чаще всего используются параметры <code>TCP_NODELAY</code> и <code>TCP_MAXSEG</code>, вместо которых следует задавать <code>SCTP_NODELAY</code> и <code>SCTP_MAXSEG</code>.</p>
      <p>2. Протокол SCTP сохраняет границы сообщений, поэтому приложению не приходится кодировать их самостоятельно. Например, приложение, основанное на TCP, может отправлять записи, чередуя двухбайтовые поля длины с полями данных переменной длины (каждое поле записывается в буфер отправки отдельным вызовом <code>write</code>). Если так поступить с SCTP, адресат получит два отдельных сообщения, то есть функция <code>read</code> возвратится дважды: один раз с двухбайтовым сообщением (поле длины), а второй — с сообщением неопределенной длины.</p>
      <p>3. Некоторые TCP-приложения используют половинное закрытие для извещения собеседника о конце считываемых данных. Для переноса таких приложений на SCTP потребуется переписать их таким образом, чтобы сигнал о конце данных передавался в обычном потоке.</p>
      <p>4. Функция <code>send</code> может использоваться обычным образом. Функции <code>sendto</code> и <code>sendmsg</code> трактуют информацию об адресе получателя как приоритетную перед основным адресом собеседника (см. раздел 2.8).</p>
      <p>Типичное приложение, работающее в стиле «один-к-одному», будет вести себя так, как показано на временной диаграмме рис. 9.1. Запущенный сервер открывает сокет, привязывается к адресу, после чего ожидает подсоединения клиента в системном вызове <code>accept</code>. Через некоторое время запускается клиент, который открывает свой сокет и инициирует установление ассоциации с сервером. Предполагается, что клиент отправляет серверу запрос, сервер обрабатывает этот запрос и отправляет свой ответ обратно клиенту. Взаимодействие продолжается до тех пор, пока клиент не начнет процедуру завершения ассоциации. После закрытия ассоциации сервер либо завершает работу, либо ожидает установления новой ассоциации. Из сравнения с временной диаграммой TCP (см. рис. 4.1) становится ясно, что обмен пакетами через сокет SCTP типа «один-к-одному» осуществляется приблизительно так же.</p>
      <image l:href="#img_78.png"/>
      <p><strong>Рис. 9.1</strong>. Временная диаграмма для сокета SCTP типа «один-к-одному»</p>
      <p>Сокет SCTP типа «один-к-одному» является IP-сокетом (семейство <code>AF_INET</code> или <code>AF_INET6</code>) со значением типа <code>SOCK_STREAM</code> и значением протокола <code>IPPROTO_SCTP</code>.</p>
     </section>
     <section>
      <title>
       <p>Сокет типа «один-ко-многим»</p>
      </title>
      <p>Сокет типа «один-ко-многим» дает разработчику приложения возможность написать сервер, не использующий большого количества дескрипторов сокетов. Один дескриптор для такого сервера будет представлять несколько ассоциаций, подобно сокету UDP, способному принимать дейтаграммы от множества клиентов. Для обращения к конкретной ассоциации, установленной для сокета типа «один-ко-многим», используется идентификатор. Идентификатор ассоциации представляет собой значение типа <code>sctp_assoc_t</code> (обычно это целое число). Значение идентификатора скрывается от приложения, то есть оно не должно использовать идентификатор, если тот еще не был предоставлен приложению ядром.</p>
      <p>При написании приложения, использующего сокеты данного типа, рекомендуется помнить о следующих важных моментах:</p>
      <p>1. Когда клиент закрывает ассоциацию, она автоматически закрывается и на стороне сервера. При этом удаляются все сведения о состоянии ассоциации в ядре.</p>
      <p>2. Только при использовании типа «один-ко-многим» возможна передача данных в третьем и четвертом пакетах четырехэтапного рукопожатия (см. упражнение 9.3).</p>
      <p>3. Вызов <code>sendto</code>, <code>sendmsg</code> или <code>sctp_sendmsg</code> для адресата, с которым еще не установлена ассоциация, приведет к попытке активного открытия, в результате чего будет создана новая ассоциация с указанным адресом. Это происходит даже в том случае, если приложение, вызвавшее <code>send</code>, перед этим вызвало для того же сокета функцию <code>listen</code>, запросив пассивное открытие.</p>
      <p>4. Приложение должно использовать функции <code>sendto</code>, <code>sendmsg</code> и <code>sctp_sendmsg</code>, но не <code>send</code> и <code>write</code>. (Если вы создали сокет типа «один-к-одному» вызовом <code>sctp_peeloff</code>, то <code>send</code> и <code>write</code> вызывать можно.)</p>
      <p>5. При вызове одной из функций отправки данных используется основной адрес получателя, выбранный системой в момент установки ассоциации (раздел 2.8), если вызывающий процесс не установит флаг <code>MSG_ADDR_OVER</code> в структуре <code>sctp_sndrcvinfo</code>. Для этого необходимо вызвать функцию <code>sendmsg</code> с вспомогательными данными или воспользоваться функцией <code>sctp_sendmsg</code>.</p>
      <p>6. Уведомление о событиях для ассоциации может быть включено по умолчанию, так что если приложению не требуется получать эти уведомления, оно должно явным образом отключить их при помощи параметра сокета <code>SCTP_EVENTS</code>. (Одно из множества уведомлений SCTP обсуждается в разделе 9.14.) По умолчанию единственным включенным событием является <code>sctp_data_io_event</code>. Уведомление о нем передается в виде вспомогательных данных при вызове <code>recvmsg</code> и <code>sctp_recvmsg</code>. Это относится к сокетам обоих типов.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Когда интерфейс API сокетов SCTP находился на стадии разработки, для сокетов типа «один-ко-многим» по умолчанию было включено еще и уведомление об установке ассоциации. В более поздних версиях документации API говорится о том, что по умолчанию для сокетов обоих типов отключены все уведомления, за исключением sctp_data_io_event. Однако не все реализации могут соответствовать этому утверждению. Хорошим тоном будет включать все нужные уведомления и отключать ненужные в явном виде. Благодаря этому разработчик получает гарантию того, что приложение будет вести себя так, как он этого хочет, в любой операционной системе.</p>
      </cite>
      <p>Типичная временная диаграмма для сокета типа «один-ко-многим» приведена на рис. 9.2. Сначала запускается сервер, который создает сокет, привязывает его к адресу, вызывает функцию <code>listen</code> для того, чтобы разрешить клиентам устанавливать ассоциации, после чего он вызывает <code>sctp_recvmsg</code> и приостанавливается в ожидании первого сообщения. В свою очередь, клиент открывает сокет и вызывает функцию <code>sctp_sendto</code>, которая неявно инициирует ассоциацию и вкладывает данные в третий пакет четырехэтапного рукопожатия. Сервер получает запрос, обрабатывает его и отсылает свой ответ. Клиент получает ответ сервера и закрывает сокет, тем самым закрывая и ассоциацию. Сервер переходит к ожиданию следующего сообщения.</p>
      <image l:href="#img_79.png"/>
      <p><strong>Рис. 9.2</strong>. Временная диаграмма работы сокета типа «один-ко-многим»</p>
      <p>В этом примере рассматривается последовательный сервер, один программный поток которого обрабатывает сообщения, полученные через несколько ассоциаций. SCTP позволяет использовать сокет типа «один-ко-многим» с функцией <code>sctp_peeloff</code> (см. раздел 9.12) для реализации комбинированной параллельно- последовательной модели сервера.</p>
      <p>1. Функция <code>sctp_peeloff</code> позволяет выделить конкретную ассоциацию (например, долговременный сеанс связи) из сокета типа «один-ко-многим» в отдельный сокет типа «один-к-одному».</p>
      <p>2. Полученный таким образом сокет типа «один-к-одному» может быть передан новому потоку или порожденному процессу (как в модели параллельного сервера).</p>
      <p>3. Основной поток обрабатывает сообщения от всех остальных ассоциаций в последовательном режиме.</p>
      <p>Сокет SCTP типа «один-ко-многим» является IP-сокетом (семейство <code>AF_INET</code> или <code>AF_INET6</code>) со значением типа <code>SOCK_SEQPACKET</code> и значением протокола <code>IPPROTO_SCTP</code>.</p>
     </section>
    </section>
    <section>
     <title>
      <p>9.3. Функция sctp_bindx</p>
     </title>
     <p>Сервер SCTP может привязаться к некоторому подмножеству IP-адресов узла, на котором он запущен. Серверы TCP и UDP могли привязываться либо к одному, либо ко всем адресам узла, но не могли указывать конкретный набор адресов. Функция <code>sctp_bindx</code> делает программирование приложений более гибким, предоставляя возможность связывать сокет SCTP с заданными адресами.</p>
     <p><code>#include &lt;netinet/sctp.h&gt;</code></p>
     <empty-line/>
     <p><code>int sctp_bindx(int <emphasis>sockfd</emphasis>, const struct sockaddr *<emphasis>addrs</emphasis>, int <emphasis>addrcnt</emphasis>, int <emphasis>flags</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного завершения, -1 в случае ошибки</emphasis></code></p>
     <p>Аргумент <code>sockfd</code> представляет собой дескриптор сокета, возвращаемый функцией <code>socket</code>. Второй аргумент — указатель на упакованный список адресов. Каждая структура адреса сокета помещается в буфер непосредственно после предшествующей структуры, без всяких дополняющих нулей (пример приводится на рис. 9.3).</p>
     <image l:href="#img_80.png"/>
     <p><strong>Рис. 9.3</strong>. Формат упакованного списка адресов для функций SCTP</p>
     <p>Количество адресов, передаваемых <code>sctp_bindx</code>, указывается в параметре <code>addrcnt</code>. Параметр <code>flags</code> сообщает функции <code>sctp_bindx</code> о необходимости выполнения действий, перечисленных в табл. 9.1.</p>
     <empty-line/>
     <p><strong>Таблица 9.1</strong>. Флаги функции sctp_bindx</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Значение аргумента flags</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_BINDX_ADD_ADDR</td>
       <td align="left" valign="top">Добавляет адреса к уже определенным для сокета</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_BINDX_REM_ADDR</td>
       <td align="left" valign="top">Удаляет адреса из списка адресов сокета</td>
      </tr>
     </table>
     <p>Функцию <code>sctp_bindx</code> можно вызывать независимо от того, привязан ли сокет к каким-нибудь адресам. Для несвязанного сокета вызов <code>sctp_bindx</code> приведет к привязке указанного набора адресов. При работе с уже связанным сокетом указание флага <code>SCTP_BINDX_ADD_ADDR</code> позволяет добавить адреса к данному дескриптору. Флаг SCTP_BINDX_<code>REM</code>_ADDR предназначен для удаления адресов из списка связанных с данным дескриптором. Если <code>sctp_bindx</code> вызывается для прослушиваемого сокета, новая конфигурация будет использоваться только для новых ассоциаций; вызов никак не затронет уже установленные ассоциации. Флаги <code>sctp_bindx</code> взаимно исключают друг друга: если указать оба, функция вернет ошибку <code>EINVAL</code>. Номер порта во всех структурах адреса сокета должен быть одним и тем же. Он должен совпадать с тем номером порта, который был связан с данным сокетом ранее. В противном случае <code>sctp_bindx</code> тоже вернет ошибку <code>EINVAL</code>.</p>
     <p>Если конечная точка поддерживает динамическую адресацию, вызов sctp_bindx с флагом <code>SCTP_BINDX_REM_ADDR</code> или <code>SCTP_BINDX_ADD_ADDR</code> приведет к передаче собеседнику сообщения о необходимости изменения списка адресов. Поскольку изменение списка адресов для установленной ассоциации не является обязательным, реализации, не поддерживающие эту функцию, будут при попытке ее использования возвращать ошибку <code>EOPNOTSUPP</code>. Обратите внимание, что для нормальной работы динамической адресации она должна поддерживаться обеими сторонами. Все это полезно в том случае, если система поддерживает динамическое предоставление интерфейсов: когда открывается доступ к новому интерфейсу Ethernet, приложение может вызвать <code>SCTP_BINDX_ADD_ADDR</code> и начать работать с этим интерфейсом по уже установленным ассоциациям.</p>
    </section>
    <section>
     <title>
      <p>9.4. Функция sctp_connectx</p>
     </title>
     <p><code>#include &lt;netinet/sctp.h&gt;</code></p>
     <empty-line/>
     <p><code>int sctp_connectx(int <emphasis>sockfd</emphasis>, const struct sockaddr *<emphasis>addrs</emphasis>, int <emphasis>addrcnt</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного завершения, -1 в случае ошибки</emphasis></code></p>
     <p>Функция <code>sctp_connectx</code> используется для соединения с многоинтерфейсным узлом. При ее вызове мы должны указать адреса собеседника в параметре <code>addrs</code> (количество адресов определяется параметром <code>addrcnt</code>). Формат структуры <code>addrs</code> представлен на рис. 9.3. Стек SCTP устанавливает ассоциацию, используя один или несколько адресов из переданного списка. Все адреса <code>addrs</code> считаются действующими и подтвержденными.</p>
    </section>
    <section>
     <title>
      <p>9.5. Функция sctp_getpaddrs</p>
     </title>
     <p>Функция <code>getpeername</code> не предназначена для использования протоколом, рассчитанным на работу с многоинтерфейсными узлами. Для сокетов SCTP она способна вернуть лишь основной адрес собеседника. Если нужны все адреса, следует вызывать функцию <code>sctp_getpaddrs</code>.</p>
     <p><code>#include &lt;netinet/sctp.h&gt;</code></p>
     <empty-line/>
     <p><code>int sctp_getpaddrs(int <emphasis>sockfd</emphasis>, sctp_assoc_t <emphasis>id</emphasis>, struct sockadrrd **<emphasis>addrs</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного завершения, -1 в случае ошибки</emphasis></code></p>
     <p>Аргумент <code>sockfd</code> представляет собой дескриптор сокета, возвращаемый функцией <code>socket</code>. Второй аргумент задает идентификатор ассоциации для сокетов типа «один-ко-многим». Для сокетов типа «один-к-одному» этот аргумент игнорируется. <code>addrs</code> — адрес указателя, который функция <code>sctp_getpaddrs</code> заполнит упакованным списком адресов, выделив под него локальный буфер (см. рис. 9.3 и листинг 23.12). Для освобождения буфера, созданного <code>sctp_getpaddrs</code>, следует использовать вызов <code>sctp_freepaddrs</code>.</p>
    </section>
    <section>
     <title>
      <p>9.6. Функция sctp_freepaddrs</p>
     </title>
     <p>Функция <code>sctp_freepaddrs</code> освобождает ресурсы, выделенные вызовом <code>sctp_getpaddrs</code>.</p>
     <p><code>#include &lt;netinet/sctp.h&gt;</code></p>
     <empty-line/>
     <p><code>void sctp_freepaddrs(struct sockaddr *<emphasis>addrs</emphasis>);</code></p>
     <p>Здесь аргумент <code>addrs</code> — указатель на массив адресов, возвращаемый <code>sctp_getpaddrs</code>.</p>
    </section>
    <section>
     <title>
      <p>9.7. Функция sctp_getladdrs</p>
     </title>
     <p>Функция <code>sctp_getladdrs</code> может использоваться для получения списка локальных адресов, относящихся к определенной ассоциации. Эта функция бывает необходима в тех случаях, когда приложению требуется узнать, какие именно локальные адреса оно использует (набор адресов, напомним, может быть произвольным подмножеством всех адресов системы).</p>
     <p><code>#include &lt;netinet/sctp.h&gt;</code></p>
     <empty-line/>
     <p><code>int sctp_getladdrs(int <emphasis>sockfd</emphasis>, sctp_assoc_t <emphasis>id</emphasis>, struct sockaddr **<emphasis>addrs</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: количество локальных адресов, помещенных в addrs, или -1 в случае ошибки.</emphasis></code></p>
     <p>Здесь <code>sockfd</code> — дескриптор сокета, возвращаемый функцией <code>socket</code>. Аргумент <code>id</code> — идентификатор ассоциации для сокетов типа «один-ко-многим». Поле <code>id</code> игнорируется для сокетов типа «один-к-одному». Параметр представляет собой адрес указателя на буфер, выделяемый и заполняемый функцией <code>sctp_getladdrs</code>. В этот буфер помещается упакованный список адресов. Структура списка представлена на рис. 9.3 и в листинге 23.12. Для освобождения буфера процесс должен вызвать функцию <code>sctp_freeladdrs</code>.</p>
    </section>
    <section>
     <title>
      <p>9.8. Функция sctp_freeladdrs</p>
     </title>
     <p>Функция <code>sctp_freeladdrs</code> освобождает ресурсы, выделенные при вызове <code>sctp_getladdrs</code>.</p>
     <p><code>#include &lt;netinet/sctp.h&gt;</code></p>
     <empty-line/>
     <p><code>void sctp_freeladdrs(struct sockaddr *<emphasis>addrs</emphasis>);</code></p>
     <p>Здесь <code>addrs</code> указывает на список адресов, возвращаемый <code>sctp_getladdrs</code>.</p>
    </section>
    <section>
     <title>
      <p>9.9. Функция sctp_sendmsg</p>
     </title>
     <p>Приложение может управлять параметрами SCTP, используя функцию <code>sendmsg</code> со вспомогательными данными (см. главу 14). Однако из-за неудобств, связанных с применением вспомогательных данных, многие реализации SCTP предоставляют дополнительный библиотечный вызов (который на самом деле может быть и системным вызовом), упрощающий обращение к расширенным функциям SCTP. Вызов функции должен иметь следующий формат:</p>
     <p><code>ssize_t sctp_sendmsg(int <emphasis>sockfd</emphasis>, const void *<emphasis>msg</emphasis>, size_t <emphasis>msgsz</emphasis>,</code></p>
     <p><code> const struct sockaddr *<emphasis>to</emphasis>, socklen_t <emphasis>tolen</emphasis>, uint32_t <emphasis>ppid</emphasis>,</code></p>
     <p><code> uint32_t <emphasis>flags</emphasis>, uint16_t <emphasis>stream</emphasis>, uint32_t <emphasis>timetolive</emphasis>,</code></p>
     <p><code> uint32_t <emphasis>context</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: количество записанных байтов в случае успешного завершения, -1 в случае ошибки</emphasis></code></p>
     <p>Использование <code>sctp_sendmsg</code> значительно упрощает отправку параметров, но требует указания большего количества аргументов. В поле <code>sockfd</code> помещается дескриптор сокета, возвращенный системным вызовом socket. Аргумент <code>msg</code> указывает на буфер размера <code>msgsz</code>, содержимое которого должно быть передано собеседнику. В поле <code>tolen</code> помещается длина адреса, передаваемого через аргумент <code>to</code>. В поле <code>ppid</code> помещается идентификатор протокола, который будет передан вместе с порцией данных. Поле <code>flags</code> передается стеку SCTP. Разрешенные значения этого поля приводятся в табл. 7.5.</p>
     <p>Номер потока SCTP указывается вызывающим приложением в аргументе <code>stream</code>. Процесс может указать время жизни сообщения в миллисекундах в поле <code>lifetime</code>. Значение 0 соответствует бесконечному времени жизни. Пользовательский контекст, при наличии такового, может быть указан в поле <code>context</code>. Пользовательский контекст связывает неудачную передачу сообщения (о которой получено уведомление) с локальным контекстом, имеющим отношение к приложению. Например, чтобы отправить сообщение в поток 1 с флагом отправки <code>MSG_PR_SCTP_TTL</code>, временем жизни равным 1000 мс, идентификатором протокола 24 и контекстом 52, процесс должен сделать следующий вызов:</p>
     <p><code>ret =</code></p>
     <p><code> sctp_sendmsg(sockfd, data, datasz, &amp;dest, sizeof(dest), 24,</code></p>
     <p><code>  MSG_PR_SCTP_TTL, 1, 1000, 52);</code></p>
     <p>Этот подход значительно проще выделения памяти под необходимые вспомогательные данные и настройки структур, входящих в <code>msghdr</code>. Обратите внимание, что если функция <code>sctp_sendmsg</code> реализована через вызов <code>sendmsg</code>, то поле <code>flags</code> в последнем устанавливается равным 0.</p>
    </section>
    <section>
     <title>
      <p>9.10. Функция sctp_recvmsg</p>
     </title>
     <p>Функция <code>sctp_recvmsg</code>, подобно <code>sctp_sendmsg</code>, предоставляет удобный интерфейс к расширенным возможностям SCTP. С ее помощью пользователь может получить не только адрес собеседника, но и поле <code>msg_flags</code>, которое обычно заполняется при вызове <code>recvmsg</code> (например, <code>MSG_NOTIFICATION</code>, <code>MSG_EOR</code> и так далее). Кроме того, функция дает возможность получить структуру <code>sctp_sndrcvinfo</code>, которая сопровождает сообщение, считанное в буфер. Обратите внимание, что если приложение хочет получать информацию, содержащуюся в структуре <code>sctp_sndrcvinfo</code>, оно должно быть подписано на событие <code>sctp_data_io_event</code> с параметром сокета <code>SCTP_EVENTS</code> (по умолчанию эта подписка включена).</p>
     <p><code>ssize_t sctp_recvmsg(int <emphasis>sockfd</emphasis>, void *<emphasis>msg</emphasis>, size_t <emphasis>msgsz</emphasis>,</code></p>
     <p><code> struct sockaddr *<emphasis>from</emphasis>, socklen_t *<emphasis>fromlen</emphasis>,</code></p>
     <p><code> struct sctp_sndrcvinfo *<emphasis>sinfo</emphasis>, int *msg_<emphasis>flags</emphasis>);</code></p>
     <p><code><emphasis>Возвращает, количество считанных байтов в случае успешного завершения, -1 в случае ошибки</emphasis></code></p>
     <p>По возвращении из этого вызова аргумент <code>msg</code> оказывается заполненным не более, чем <code>msgsz</code> байтами данных. Адрес отправителя сообщения помещается в аргумент <code>from</code>, а размер адреса — в аргумент <code>fromlen</code>. Флаги сообщения будут помещены в аргумент <code>msg_flags</code>. Если уведомление <code>sctp_data_io_event</code> включено (а по умолчанию это так и есть), структура <code>sctp_sndrcvinfo</code> заполняется подробными сведениями о сообщении. Обратите внимание, что если функция <code>sctp_recvmsg</code> реализована через вызов <code>recvmsg</code>, то поле <code>flags</code> в последнем устанавливается равным нулю.</p>
    </section>
    <section>
     <title>
      <p>9.11. Функция sctp_opt_info</p>
     </title>
     <p>Эта функция предназначена для тех приложений, которым недостаточно возможностей, предоставляемых функциями <code>getsockopt</code> для протокола SCTP. Дело в том, что некоторые параметры сокетов SCTP (например, <code>SCTP_STATUS</code>) требуют использования переменных типа «значение-результат» для передачи идентификатора ассоциации. Если функция <code>getsockopt</code> не поддерживает работу с такими переменными, разработчику придется вызывать <code>sctp_opt_info</code>. В системах типа FreeBSD, разрешающих указывать переменные типа «значение-результат» с параметрами сокетов, функция <code>sctp_opt_info</code> представляет собой оболочку, передающую аргументы функции <code>getsockopt</code> в нужном формате. В целях обеспечения переносимости разработчикам приложений рекомендуется использовать <code>sctp_opt_info</code> для всех параметров, требующих работы с переменными типа «значение-результат» (см. раздел 7.10).</p>
     <p><code>int sctp_opt_info(int <emphasis>sockfd</emphasis>, sctp_assoc_t <emphasis>assoc_id</emphasis>, int <emphasis>opt</emphasis>,</code></p>
     <p><code> void *<emphasis>arg</emphasis>, socklen_t *<emphasis>siz</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного завершения, -1 в случае ошибки</emphasis></code></p>
     <p>Здесь <code>sockfd</code> — дескриптор сокета, с параметрами которого хочет работать пользователь. Аргумент <code>assoc_id</code> задает идентификатор ассоциации, которую нужно выделить из списка всех ассоциаций данного сокета. Аргумент <code>opt</code> задает параметр сокета для SCTP (список параметров приводится в разделе 7.10). <code>Arg</code> — аргумент параметра сокета, <code>siz</code> — указатель на переменную типа <code>socklen_t</code>, в которой хранится размер аргумента параметра сокета.</p>
    </section>
    <section>
     <title>
      <p>9.12. Функция sctp_peeloff</p>
     </title>
     <p>Как отмечалось ранее, любую ассоциацию, установленную через сокет типа «один- ко-многим», можно выделить в собственный сокет типа «один-к-одному». По семантике новая функция подобна <code>accept</code> с дополнительным аргументом. Процесс передает дескриптор <code>sockfd</code> сокета типа «один-ко-многим» и идентификатор <code>id</code> выделяемой ассоциации. Функция возвращает дескриптор нового сокета. Этот дескриптор имеет тип «один-к-одному», и он изначально связан с выбранной ассоциацией.</p>
     <p><code>int sctp_peeloff(int <emphasis>sockfd</emphasis>, sctp_assoc_t <emphasis>id</emphasis>);</code></p>
     <empty-line/>
     <p><code><emphasis>Возвращает: дескриптор нового сокета в случае успешного завершения, -1 в случае ошибки</emphasis></code></p>
    </section>
    <section>
     <title>
      <p>9.13. Функция shutdown</p>
     </title>
     <p>Обсуждавшаяся в разделе 9.6 функция <code>shutdown</code> может использоваться с конечной точкой SCTP, использующей интерфейс типа «один-к-одному». Поскольку архитектура SCTP не предусматривает наполовину закрытого состояния, реакция на вызов <code>shutdown</code> конечной точки SCTP отличается от реакции TCP. Когда конечная точка SCTP инициирует процедуру завершения ассоциации, оба собеседника должны закончить передачу данных, находящихся в очереди, после чего закрыть ассоциацию. Конечная точка, выполнявшая активное открытие, может вызвать <code>shutdown</code> вместо <code>close</code> для того, чтобы впоследствии подключиться к новому собеседнику. В отличие от TCP, закрывать сокет функцией <code>close</code>, а затем создавать его снова здесь не требуется. SCTP разрешает конечной точке вызвать <code>shutdown</code>, а после завершения этой функции — открывать новые ассоциации через тот же сокет. Обратите внимание, что если конечная точка не дождется завершения последовательности закрытия ассоциации, установка нового соединения закончится неудачей. На рис. 9.4 приведена типичная временная диаграмма вызовов для этого сценария.</p>
     <image l:href="#img_81.png"/>
     <p><strong>Рис. 9.4</strong>. Закрытие ассоциации SCTP вызовом shutdown</p>
     <p>Обратите внимание, что на рис. 9.4 мы подразумеваем, что процесс подписан на события <code>MSG_NOTIFICATION</code>. Если же он не подписался на эти события, функция <code>read</code> считает нулевое количество байтов. Результаты вызова shutdown для TCP были описаны в разделе 6.6. В документации <emphasis>howto</emphasis> на функцию <code>shutdown</code> для SCTP перечислены следующие константы:</p>
     <p>&#9632; <code>SHUT_RD</code> — та же семантика, что и для TCP (см. раздел 6.6); никаких особых действий протокол SCTP не предусматривает;</p>
     <p>&#9632; <code>SHUT_WR</code> — запрещает отправку сообщений и инициирует процедуру завершения ассоциации SCTP. Этот параметр не дает возможности работать в наполовину закрытом состоянии, однако позволяет локальной конечной точке считать все данные, которые собеседник отправит до получения сообщения SCTP SHUTDOWN;</p>
     <p>&#9632; <code>SHUT_RDWR</code> — запрещает вызовы read и write и инициирует процедуру завершения ассоциации SCTP. Данные, передававшиеся в момент вызова <code>shutdown</code> на локальную конечную точку, будут подтверждены и сброшены без всякого уведомления процесса.</p>
    </section>
    <section>
     <title>
      <p>9.14. Уведомления</p>
     </title>
     <p>SCTP предоставляет разработчику приложений большое количество разнообразных уведомлений. С их помощью процесс может отслеживать состояние ассоциаций, с которыми он работает. Уведомления сообщают о событиях транспортного уровня, включая изменения состояния сети, установку ассоциаций, протокольные ошибки удаленного узла и неудачи при доставке сообщений. По умолчанию уведомления обо всех событиях отключены для сокетов обоих типов. Исключение делается для события <code>sctp_data_io_event</code>. Пример использования уведомлений будет приведен в разделе 23.7.</p>
     <p>Параметр сокета <code>SCTP_EVENTS</code> позволяет подписаться на восемь событий. Из них семь штук генерируют дополнительные данные, которые процесс может получить через обычный дескриптор сокета. Уведомления добавляются к обычным данным, приходящим на соответствующий сокет, по мере того, как происходят события, генерирующие эти уведомления. При чтении из сокета, для которого включена подписка на уведомления, пользовательские данные и сообщения смешиваются друг с другом. Чтобы различить их, процесс должен использовать функции <code>recvmsg</code> или <code>sctp_recvmsg</code>. Для уведомлений о событиях поле <code>msg_flags</code> содержит флаг <code>MSG_NOTIFICATION</code>. Этот флаг говорит приложению о том, что считанное сообщение представляет собой не обычные данные, принятые от собеседника, а уведомление о каком-либо событии от локального стека SCTP.</p>
     <p>Уведомление любого типа имеет следующий формат. Первые восемь байтов идентифицируют тип уведомления и его полную длину. Включение подписки на событие <code>sctp_data_io_event</code> приводит к тому, что с каждой операцией чтения пользовательских данных процесс принимает структуру <code>sctp_sndrcvinfo</code>. Вызовом <code>recvmsg</code> эта структура помещается во вспомогательные данные. Приложение может также вызвать <code>sctp_recvmsg</code>, которая использует указатель на структуру <code>sctp_sndrcvinfo</code>.</p>
     <p>Два уведомления содержат поле кода причины ошибки SCTP (SCTP error cause field). Значения этого поля перечислены в разделе 3.3.10 RFC 2960 [118] и в разделе «CAUSE CODES» (коды причин) документа <code>http://www.iana.org/assignments/sctp-parameters</code>.</p>
     <p>Уведомления определяются следующим образом.</p>
     <p><code>struct sctp_tlv {</code></p>
     <p><code> u_int16_t sn_type;</code></p>
     <p><code> u_int16_t sn_flags;</code></p>
     <p><code> u_int32_t sn_length;</code></p>
     <p><code>};</code></p>
     <empty-line/>
     <p><code>/* уведомление о событии */</code></p>
     <p><code>union sctp_notification {</code></p>
     <p><code> struct sctp_tlv sn_header;</code></p>
     <p><code> struct sctp_assoc_change sn_assoc_change;</code></p>
     <p><code> struct sctp_paddr_change sn_paddr_change;</code></p>
     <p><code> struct sctp_remote_error sn_remote_error;</code></p>
     <p><code> struct sctp_send_failed sn_send_failed;</code></p>
     <p><code> struct sctp_shutdown_event sn_shutdown_event;</code></p>
     <p><code> struct sctp_adaption_event sn_adaption_event;</code></p>
     <p><code> struct sctp_pdapi_event sn_pdapi_event;</code></p>
     <p><code>};</code></p>
     <p>Обратите внимание, что для интерпретации значения типа используется поле <code>sn_header</code>. Таблица 9.2 содержит значения, которые могут помещаться в поля <code>sn_header</code>, <code>sn_type</code>, а также соответствующие значения поля подписки, которые используются с параметром сокета <code>SCTP_EVENTS</code>.</p>
     <empty-line/>
     <p><strong>Таблица 9.2</strong>. Тип и поле подписки</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">sn_type</th>
       <th align="left" valign="top">Поле подписки</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_ASSOC_CHANGE</td>
       <td align="left" valign="top">sctp_association_event</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_PEER_ADDR_CHANGE</td>
       <td align="left" valign="top">sctp_address_event</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_REMOTE_ERROR</td>
       <td align="left" valign="top">sctp_peer_error_event</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_SEND_FAILED</td>
       <td align="left" valign="top">sctp_send_failure_event</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_SHUTDOWN_EVENT</td>
       <td align="left" valign="top">sctp_shutdown_event</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_ADAPTION_INDICATON</td>
       <td align="left" valign="top">sctp_adaption_layer_event</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_PARTIAL_DELIVERY_EVENT</td>
       <td align="left" valign="top">sctp_partial_delivery_event</td>
      </tr>
     </table>
     <p>У каждого уведомления имеется своя собственная структура, которая содержит подробную информацию о произошедшем событии.</p>
     <p>&#9632; <code>SCTP_ASSOC_CHANGE</code></p>
     <p>Это уведомление сообщает приложению о том, что произошло изменение, связанное с ассоциациями: возникла новая ассоциация или завершилась существующая. Структура данных имеет следующий формат:</p>
     <p><code>struct sctp_assoc_change {</code></p>
     <p><code> u_int16_t sac_type;</code></p>
     <p><code> u_int16_t sac_flags;</code></p>
     <p><code> u_int32_t sac_length;</code></p>
     <p><code> u_int16_t sac_state;</code></p>
     <p><code> u_int16_t sac_error;</code></p>
     <p><code> u_int16_t sac_outbound_streams;</code></p>
     <p><code> u_int16_t sac_inbound_streams;</code></p>
     <p><code> sctp_assoc_t sac_assoc_id;</code></p>
     <p><code> uint8_t sac_info[];</code></p>
     <p><code>};</code></p>
     <p>Поле <code>sac_state</code> определяет тип события, связанного с ассоциацией. Оно может принимать следующие значения:</p>
     <p> &#9633; <code>SCTP_COMM_UP</code> — создана новая ассоциация. Поля входящих и исходящих потоков (<code>inbound_streams</code> и <code>outbound_streams</code>) говорят о том, сколько потоков доступно в соответствующих направлениях. Идентификатор ассоциации позволяет взаимодействовать со стеком SCTP;</p>
     <p> &#9633; <code>SCTP_COMM_LOST</code> — ассоциация закрыта из-за превышения порога недоступности (заданное количество раз был превышен тайм-аут) или собеседник выполнил аварийное закрытие ассоциации (при помощи параметра сокета <code>SO_LINGER</code> или вызовом <code>sendmsg</code> с флагом <code>MSG_ABORT</code>). Пользовательские данные могут быть помещены в поле <code>sac_info</code>;</p>
     <p> &#9633; <code>SCTP_RESTART</code> — собеседник перезапущен. Наиболее типичной причиной этого уведомления бывает выход из строя и перезапуск собеседника. Приложение должно проверить количество потоков в обоих направлениях, потому что при перезапуске эти значения могут измениться;</p>
     <p> &#9633; <code>SCTP_SHUTDOWN_COMP</code> — закончено завершение соединения, инициированное локальной конечной точкой (вызовом <code>shutdown</code> или <code>sendmsg</code> с флагом <code>MSG_EOF</code>). После получения этого сообщения сокет типа «один-к-одному» может быть использован для подключения к другому собеседнику;</p>
     <p> &#9633; <code>SCTP_CANT_STR_ASSOC</code> — собеседник не ответил при попытке установления ассоциации.</p>
     <p>Поле <code>sac_error</code> содержит коды причин ошибок протокола SCTP, которые могли привести к изменению состояния ассоциации. Поля <code>sac_inbound_streams</code> и <code>sac_outbound_streams</code> говорят о том, какое количество потоков в каждом направлении было согласовано во время установки ассоциации. Поле sac_<code>assoc</code>_id содержит уникальный идентификатор ассоциации, который может использоваться как при работе с параметрами сокета, так и в последующих уведомлениях. Наконец, поле <code>sac</code>_info может содержать дополнительные пользовательские сведения. Например, если ассоциация была разорвана собеседником в связи с ошибкой, определенной пользователем, код этой ошибки будет помещен в поле <code>sac_info</code>.</p>
     <p>&#9632; <code>SCTP_PEER_ADDR_CHANGE</code> Это уведомление говорит об изменении состояния одного из адресов собеседника. Изменение может заключаться либо в отказе (отсутствии подтверждения отправленных на этот адрес данных), либо в восстановлении (ответе отказавшего ранее адреса). Структура данных имеет следующий формат:</p>
     <p><code>struct sctp_paddr_change {</code></p>
     <p><code> u_int16_t spc_type;</code></p>
     <p><code> u_int16_t spc_flags;</code></p>
     <p><code> u_int32_t spc_length;</code></p>
     <p><code> struct sockaddr_storage spc_aaddr;</code></p>
     <p><code> u_int32_t spc_state;</code></p>
     <p><code> u_int32_t spc_error;</code></p>
     <p><code> sctp_assoc_t spc_assoc_id;</code></p>
     <p><code>};</code></p>
     <p>Поле <code>spc_aaddr</code> содержит адрес собеседника, с которым связано данное событие. Поле <code>spc_state</code> может принимать одно из значений, перечисленных в табл. 9.3.</p>
     <empty-line/>
     <p><strong>Таблица 9.3</strong>. Уведомление о состоянии адреса собеседника</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">spc_state</th>
       <th align="left" valign="top">Значение</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_ADDR_ADDED</td>
       <td align="left" valign="top">Адрес добавлен к ассоциации</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_ADDR_AVAILABLE</td>
       <td align="left" valign="top">Адрес доступен</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_ADDR_CONFIRMED</td>
       <td align="left" valign="top">Адрес подтвержден и считается действующим</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_ADDR_MADE_PRIM</td>
       <td align="left" valign="top">Адрес сделан основным</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_ADDR_REMOVED</td>
       <td align="left" valign="top">Адрес удален из списка адресов ассоциации</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCTP_ADDR_UNREACHABLE</td>
       <td align="left" valign="top">Адрес недоступен</td>
      </tr>
     </table>
     <p>Данные, отправленные на недоступный (<code>SCTP_ADDR_UNREACHABLE</code>) адрес, будут направляться на альтернативный адрес. Некоторые состояния доступны только в тех реализациях SCTP, которые поддерживают динамическую адресацию (в частности, <code>SCTP_ADDR_ADDED</code> и <code>SCTP_ADDR_REMOVED</code>).</p>
     <p>Поле <code>spc_error</code> содержит код ошибки, дающий больше сведений о событии, а поле <code>spc_assoc_id</code>, как обычно, хранит идентификатор ассоциации.</p>
     <p>&#9632; <code>SCTP_REMOTE_ERROR</code></p>
     <p>Собеседник может отправить на локальную конечную точку сообщение об ошибке. Такие сообщения могут описывать различные ошибочные состояния ассоциации. Если это уведомление включено, вся сбойная порция данных передается приложению в сетевом формате. Сообщение имеет следующий формат:</p>
     <p><code>struct sctp_remote_error {</code></p>
     <p><code> u_int16_t sre_type;</code></p>
     <p><code> u_int16_t sre_flags;</code></p>
     <p><code> u_int32_t sre_length;</code></p>
     <p><code> u_int16_t sre_error;</code></p>
     <p><code> sctp_assoc_t sre_assoc_id;</code></p>
     <p><code> u_int8_t sre_data[];</code></p>
     <p><code>};</code></p>
     <p>Поле <code>sre_error</code> содержит код причины ошибки протокола SCTP; <code>sre_assoc_id</code> — идентификатор ассоциации, a <code>sre_data</code> — ошибочную порцию данных в сетевом формате.</p>
     <p>&#9632; <code>SCTP_SEND_FAILED</code></p>
     <p>Сообщение, которое невозможно доставить собеседнику, возвращается отправителю в этом уведомлении. За таким уведомлением обычно следует уведомление об отказе ассоциации. В большинстве случаев доставка сообщения оказывается невозможной именно по причине отказа ассоциации. Если же используется режим частичной надежности SCTP, сообщение может быть возвращено и в том случае, если отказа ассоциации реально не произошло.</p>
     <p>Данные, возвращаемые приложению с этим уведомлением, имеют следующий формат:</p>
     <p><code>struct sctp_send_failed {</code></p>
     <p><code> u_int16_t ssf_type;</code></p>
     <p><code> u_int16_t ssf_flags;</code></p>
     <p><code> u_int32_t ssf_length;</code></p>
     <p><code> u_int32_t ssf_error;</code></p>
     <p><code> struct sctp_sndrcvinfo ssf_info;</code></p>
     <p><code> sctp_assoc_t ssf_assoc_id;</code></p>
     <p><code> u_int8_t ssf_data[];</code></p>
     <p><code>};</code></p>
     <p>Поле <code>ssf_flags</code> может иметь одно из двух значений:</p>
     <p> &#9633; <code>SCTP_DATA_UNSENT</code> — сообщение не было послано собеседнику (управление потоком не позволило отправить сообщение до истечения его времени жизни);</p>
     <p> &#9633; <code>SCTP_DATA_SENT</code> — сообщение было передано по крайней мере один раз, но собеседник не подтвердил его получение. Собеседник мог получить сообщение, но он не смог подтвердить его.</p>
     <p>Эта разница может быть существенной для протоколов обработки транзакций, которые при восстановлении соединения могут предпринимать разные действия в зависимости от того, было принято конкретное сообщение или нет. Поле <code>ssf_error</code> может содержать код ошибки, относящейся к конкретному уведомлению, или быть нулевым. Поле <code>ssf_info</code> содержит сведения, переданные ядру при отправке данных (например, номер потока, контекст и так далее). Поле <code>ssf_assoc_id</code> содержит идентификатор ассоциации, а в поле <code>ssf_data</code> помещается недоставленное сообщение.</p>
     <p>&#9632; <code>SCTP_SHUTDOWN_EVENT</code></p>
     <p>Это уведомление передается приложению при приеме от собеседника порции SHUTDOWN. После этой порции никакие новые данные на том же сокете получены быть не могут. Все данные, уже помещенные в очередь, будут переданы собеседнику, после чего ассоциация будет закрыта. Уведомление имеет следующий формат:</p>
     <p><code>struct sctp_shutdown_event {</code></p>
     <p><code> uint16_t sse_type;</code></p>
     <p><code> uint16_t sse_flags;</code></p>
     <p><code> uint32_t sse_length;</code></p>
     <p><code> sctp_assoc_t sse_assoc_id;</code></p>
     <p><code>};</code></p>
     <p>Поле <code>sse_assoc_id</code> содержит идентификатор ассоциации, которая закрывается и потому не может более использоваться для передачи данных.</p>
     <p>&#9632; <code>SCTP_ADAPTION_INDICATION</code></p>
     <p>Некоторые реализации поддерживают параметр <emphasis>индикации адаптирующего уровня</emphasis> (<emphasis>adaption layer indication</emphasis>). Этот параметр передается в пакетах INIT и INIT-ACK и уведомляет собеседника о выполняемой адаптации приложения. Уведомление имеет следующий формат:</p>
     <p><code>struct sctp_adaption_event {</code></p>
     <p><code> u_int16_t sai_type;</code></p>
     <p><code> u_int16_t sai_flags;</code></p>
     <p><code> u_int32_t sai_length;</code></p>
     <p><code> u_int32_t sai_adaption_ind;</code></p>
     <p><code> sctp_assoc_t sai_assoc_id;</code></p>
     <p><code>};</code></p>
     <p>Поле <code>sai_assoc_id</code> содержит обычный идентификатор ассоциации. Поле <code>sai_adaption_ind</code> представляет собой 32-разрядное целое число, переданное собеседником локальной конечной точке в сообщении INIT или INIT-ACK. Уровень адаптации для исходящих сообщений устанавливается при помощи параметра сокета <code>SCTP_ADAPTION_LAYER</code> (см. раздел 7.10). Все это описано в стандарте [116], а пример использования параметра для удаленного прямого доступа к памяти и прямой записи данных описывается в [115].</p>
     <p>&#9632; <code>SCTP_PARTIAL_DELIVERY_EVENT</code></p>
     <p>Интерфейс частичной доставки используется для передачи больших сообщений пользователю через буфер сокета. Представьте, что процесс отправил сообщение размером 4 Мбайт. Сообщение такого размера может сильно перегрузить системные ресурсы. Реализация SCTP не смогла бы обработать такое сообщение, если бы у нее не было механизма доставки сообщений по частям до полного их получения. Реализация, обеспечивающая частичную доставку, называется <emphasis>интерфейсом частичной доставки</emphasis> (<emphasis>partial delivery API</emphasis>). SCTP передает данные приложению, не устанавливая флаги в поле <code>msg_flags</code> до тех пор, пока не будет готов последний сегмент сообщения. Для этого сегмента устанавливается флаг <code>MSG_EOR</code> (конец записи). Обратите внимание, что если приложение рассчитывает принимать большие сообщения, оно должно использовать функции <code>recvmsg</code> и <code>sctp_recvmsg</code>, чтобы иметь возможность проверять поле <code>msg_flags</code> на наличие флага окончания записи.</p>
     <p>В некоторых ситуациях интерфейсу частичной доставки может потребоваться информировать приложение о состоянии сообщения. Например, если при доставке большого сообщения произошел сбой, приложению доставляется уведомление <code>SCTP_PARTIAL_DELIVERY_EVENT</code>, имеющее следующий формат:</p>
     <p><code>struct sctp_pdapi_event {</code></p>
     <p><code> uint16_t pdapi_type;</code></p>
     <p><code> uint16_t pdapi_flags;</code></p>
     <p><code> uint32_t pdapi_length;</code></p>
     <p><code> uint32_t pdapi_indication;</code></p>
     <p><code> sctp_assoc_t pdapi_assoc_id;</code></p>
     <p><code>};</code></p>
     <p>Идентификатор <code>pdapi_assoc_id</code> указывает на ассоциацию, к которой относится принятое уведомление. Поле <code>pdapi_indication</code> содержит сведения о произошедшем событии. На данный момент поле может иметь единственное значение <code>SCTP_PARTIAL_DELIVERY_ABORTED</code>, указывающее на аварийное завершение частичной доставки сообщения, обрабатываемого в данный момент.</p>
    </section>
    <section>
     <title>
      <p>9.15. Резюме</p>
     </title>
     <p>SCTP предлагает разработчику приложений два вида интерфейсов: «один-к-одному», облегчающий миграцию существующих TCP-приложений на SCTP, и «один-ко-многим», реализующий все новые возможности SCTP. Функция <code>sctp_peeloff</code> позволяет выделять ассоциации из множественных сокетов в одиночные. Кроме того, SCTP предоставляет множество уведомлений о событиях транспортного уровня, на которые приложение при необходимости может подписываться. События помогают приложению управлять ассоциациями, с которыми оно работает.</p>
     <p>Поскольку протокол SCTP ориентирован на многоинтерфейсные узлы, не все стандартные функции сокетов, рассмотренные в главе 4, оказываются эффективны при работе с ним. Функции <code>sctp_bindx</code>, <code>sctp_connectx</code>, <code>sctp_getladdrs</code> и <code>sctp_getpaddrs</code> позволяют управлять адресами и ассоциациями. Функции <code>sctp_sendmsg</code> и <code>sctp_recvmsg</code> упрощают использование расширенных возможностей SCTP. В главах 10 и 23 мы приведем примеры, наглядно демонстрирующие рассмотренные в этой главе новые концепции.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. В какой ситуации разработчик приложения скорее всего воспользуется функцией <code>sctp_peeloff</code>?</p>
     <p>2. Говоря о сокетах типа «один-ко-многим», мы утверждаем, что на стороне сервера также происходит автоматическое закрытие. Почему это верно?</p>
     <p>3. Почему передача пользовательских данных в третьем пакете рукопожатия возможна только для сокетов типа «один-ко-многим»? (Подсказка: нужно иметь возможность отправлять данные во время установки ассоциации.)</p>
     <p>4. В какой ситуации пользовательские данные могут быть переданы в третьем и четвертом пакетах четырехэтапного рукопожатия?</p>
     <p>5. В разделе 9.7 говорится о том, что набор локальных адресов может быть подмножеством связанных адресов. В какой ситуации это возможно?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 10</p>
     <p>Пример SCTP-соединения клиент-сервер</p>
    </title>
    <section>
     <title>
      <p>10.1. Введение</p>
     </title>
     <p>Воспользуемся некоторыми элементарными функциями из глав 4 и 9 для написания полнофункционального приложения SCTP с архитектурой клиент-сервер типа «один-ко-многим». Сервер из нашего примера будет аналогичен эхо-серверу из главы 5. Приложение будет функционировать следующим образом:</p>
     <p>1. Клиент считывает строку текста из стандартного потока ввода и отсылает ее серверу. Строка имеет формат <code>[#]text</code>, где номер в скобках обозначает номер потока SCTP, по которому должно быть отправлено это текстовое сообщение.</p>
     <p>2. Сервер принимает текстовое сообщение из сети, увеличивает номер потока, по которому было получено сообщение, на единицу и отправляет сообщение обратно клиенту через поток с новым номером.</p>
     <p>3. Клиент считывает полученную строку и выводит ее в стандартный поток вывода, добавляя к ней номер потока и порядковый номер для данного потока.</p>
     <p>Наше приложение вместе с функциями, используемыми для операций ввода и вывода, изображено на рис. 10.1.</p>
     <image l:href="#img_82.png"/>
     <p><strong>Рис. 10.1</strong>. Простое потоковое приложение SCTP с архитектурой клиент-сервер</p>
     <p>Две стрелки между клиентом и сервером обозначают два однонаправленных потока (ассоциация в целом является полностью двусторонней). Функции <code>fgets</code> и <code>fputs</code> входят в стандартную библиотеку ввода-вывода. Мы не пользуемся функциями <code>writen</code> и <code>readline</code> из раздела 3.9, потому что в них нет необходимости. Вместо них мы вызываем <code>sctp_sendmsg</code> и <code>sctp_recvmsg</code> из разделов 9.9 и 9.10 соответственно.</p>
     <p>Сервер в нашем примере будет относиться к типу «один-ко-многим». Этот вариант был выбран нами по одной важной причине. Примеры из главы 5 могут быть переделаны под SCTP внесением крайне незначительных изменений: достаточно изменить вызов socket, указав в качестве третьего аргумента <code>IPPROTO_SCTP</code> вместо <code>IPPROTO_TCP</code>. Однако приложение, полученное таким образом, не использовало бы дополнительные возможности, предоставляемые SCTP, за исключением поддержки многоинтерфейсных узлов. Написав сервер типа «один-ко-многим», мы смогли показать все достоинства SCTP.</p>
    </section>
    <section>
     <title>
      <p>10.2. Потоковый эхо-сервер SCTP типа «один-ко-многим»: функция main</p>
     </title>
     <p>Наши клиент и сервер SCTP вызывают функции в последовательности, представленной на рис. 9.2. Код последовательного сервера представлен в листинге 10.1<a l:href="#n1" type="note">[1]</a>.</p>
     <p><strong>Листинг 10.1</strong>. Потоковый эхо-сервер SCTP</p>
     <p><code>//sctp/sctpserv01.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sock_fd, msg_flags;</code></p>
     <p><code> 6  char readbuf[BUFFSIZE];</code></p>
     <p><code> 7  struct sockaddr_in servaddr, cliaddr;</code></p>
     <p><code> 8  struct sctp_sndrcvinfo sri;</code></p>
     <p><code> 9  struct sctp_event_subscribe evnts;</code></p>
     <p><code>10  int stream_increment=1;</code></p>
     <p><code>11  socklen_t len;</code></p>
     <p><code>12  size_t rd_sz;</code></p>
     <empty-line/>
     <p><code>13  if (argc == 2)</code></p>
     <p><code>14   stream_increment = atoi(argv[1]);</code></p>
     <p><code>15  sock_fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);</code></p>
     <p><code>16  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>17  servaddr.sin_family = AF_INET;</code></p>
     <p><code>18  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>19  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <empty-line/>
     <p><code>20  Bind(sock_fd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>21  bzero(&amp;evnts, sizeof(evnts));</code></p>
     <p><code>22  evnts.sctp_data_io_event = 1;</code></p>
     <p><code>23  Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &amp;evnts, sizeof(evnts));</code></p>
     <empty-line/>
     <p><code>24  Listen(sock_fd, LISTENQ);</code></p>
     <p><code>25  for (;;) {</code></p>
     <p><code>26   len = sizeof(struct sockaddr_in);</code></p>
     <p><code>27   rd_sz = Sctp_recvmsg(sock_fd, readbuf, sizeof(readbuf),</code></p>
     <p><code>28    (SA*)&amp;cliaddr, &amp;len, &amp;sri, &amp;msg_flags);</code></p>
     <p><code>29   if (stream_increment) {</code></p>
     <p><code>30    sri.sinfo_stream++;</code></p>
     <p><code>31    if (sri.sinfo_stream &gt;=</code></p>
     <p><code>32     sctp_get_no_strms(sock_fd, (SA*)&amp;cliaddr, len))</code></p>
     <p><code>33     sri.sinfo_stream = 0;</code></p>
     <p><code>34   }</code></p>
     <p><code>35   Sctp_sendmsg(sock_fd, readbuf, rd_sz,</code></p>
     <p><code>36   (SA*)&amp;cliaddr, len,</code></p>
     <p><code>37   sri.sinfo_ppid,</code></p>
     <p><code>38   sri.sinfo_flags, sri.sinfo_stream, 0, 0);</code></p>
     <p><code>39  }</code></p>
     <p><code>40 }</code></p>
     <subtitle>Настройка приращения номера потока</subtitle>
     <p><code>13-14</code> По умолчанию наш сервер отвечает клиенту через поток, номер которого на единицу больше номера потока, по которому было получено сообщение. Если приложению в строке вызова передается целочисленный аргумент, он интерпретируется как значение флага <code>stream_increment</code>, с помощью которого приращение номера потока можно отключить. Мы воспользуемся этим параметром командной строки, когда будем говорить о блокировании в разделе 10.5.</p>
     <subtitle>Создание сокета SCTP</subtitle>
     <p><code>15</code> Создается сокет SCTP типа «один-ко-многим».</p>
     <subtitle>Связывание с адресом</subtitle>
     <p><code>16-20</code> Структура адреса сокета Интернета заполняется универсальным адресом (<code>INADDR_ANY</code>) и номером заранее известного порта сервера <code>SERV_PORT</code>. Связывание с универсальным адресом означает, что конечная точка SCTP будет использовать все доступные локальные адреса для всех создаваемых ассоциаций. Для многоинтерфейсных узлов это означает, что удаленная конечная точка сможет устанавливать ассоциации и передавать пакеты на любой локальный интерфейс. Выбор номера порта SCTP основывался на рис. 2.10. Обратите внимание, что ход рассуждений для сервера тот же, что и в одном из предшествовавших примеров в разделе 5.2.</p>
     <subtitle>Подписка на уведомления</subtitle>
     <p><code>21-23</code> Сервер изменяет параметры подписки на уведомления для сокета SCTP. Сервер подписывается только на событие <code>sctp_data_io_event</code>, что позволяет ему получать структуру <code>sctp_sndrcvinfo</code>. По ее содержимому сервер сможет определять номер потока полученного сообщения.</p>
     <subtitle>Разрешение установки входящих ассоциаций</subtitle>
     <p><code>24</code> Сервер разрешает устанавливать входящие ассоциации, вызывая функцию <code>listen</code>. Затем управление передается главному циклу.</p>
     <subtitle>Ожидание сообщения</subtitle>
     <p><code>26-28</code> Сервер инициализирует размер структуры адреса сокета клиента, после чего блокируется в ожидании сообщения от какого-либо удаленного собеседника.</p>
     <subtitle>Увеличение номера потока</subtitle>
     <p><code>29-34</code> Сервер проверяет состояние флага <code>stream_increment</code> и определяет, нужно ли увеличивать номер потока. Если флаг установлен (никакие аргументы в командной строке не передавались), сервер увеличивает номер потока, по которому было получено сообщение, на единицу. Если полученное число достигает предельного количества потоков (получаемого вызовом <code>sctp_get_no_strms</code>), сервер сбрасывает номер потока в 0. Функция <code>sctp_get_no_strms</code> в листинге не приведена. Она использует параметр <code>SCTP_STATUS</code> (см. раздел 7.10) для определения согласованного количества потоков.</p>
     <subtitle>Отправка ответа</subtitle>
     <p><code>35-38</code> Сервер отсылает сообщения, используя идентификатор протокола, флаги и номер потока (который, возможно, был увеличен), хранящиеся в структуре <code>sri</code>.</p>
     <p>Заметьте, что нашему серверу не нужны уведомления об установке ассоциаций, поэтому он отключает все события, которые привели бы к передаче сообщений в буфер сокета. Сервер полагается на сведения из структуры <code>sctp_sndrcvinfo</code>, а обратный адрес берет из переменной <code>cliaddr</code>. Этого оказывается достаточно для отправки эхо-ответа собеседнику через установленную им ассоциацию.</p>
     <p>Программа работает до тех пор, пока пользователь не завершит ее передачей сигнала.</p>
    </section>
    <section>
     <title>
      <p>10.3. Потоковый эхо-клиент SCTP типа «один-ко-многим»: функция main</p>
     </title>
     <p>В листинге 10.2 приведена функция <code>main</code> нашего клиента SCTP.</p>
     <p><strong>Листинг 10.2</strong>. Потоковый эхо-клиент SCTP</p>
     <p><code>//sctp/sctpclient01.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sock_fd;</code></p>
     <p><code> 6  struct sockaddr_in servaddr;</code></p>
     <p><code> 7  struct sctp_event_subscribe evnts;</code></p>
     <p><code> 8  int echo_to_all=0;</code></p>
     <empty-line/>
     <p><code> 9  if (argc &lt; 2)</code></p>
     <p><code>10   err_quit("Missing host argument - use '%s host [echo]'\n", argv[0]);</code></p>
     <p><code>11  if (argc &gt; 2) {</code></p>
     <p><code>12   printf("Echoing messages to all streams\n");</code></p>
     <p><code>13   echo_to_all = 1;</code></p>
     <p><code>14  }</code></p>
     <p><code>15  sock_fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);</code></p>
     <p><code>16  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>17  servaddr.sin_family = AF_INET;</code></p>
     <p><code>18  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>19  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <p><code>20  Inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr);</code></p>
     <empty-line/>
     <p><code>21  bzero(&amp;evnts, sizeof(evnts));</code></p>
     <p><code>22  evnts.sctp_data_io_event = 1;</code></p>
     <p><code>23  Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &amp;evnts, sizeof(evnts));</code></p>
     <p><code>24  if (echo_to_all == 0)</code></p>
     <p><code>25   sctpstr_cli(stdin, sock_fd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>26  else</code></p>
     <p><code>27   sctpstr_cli_echoall(stdin, sock_fd, (SA*)&amp;servaddr,</code></p>
     <p><code>28    sizeof(servaddr));</code></p>
     <p><code>29  Close(sock_fd);</code></p>
     <p><code>30  return(0);</code></p>
     <p><code>31 }</code></p>
     <subtitle>Проверка аргументов и создание сокета</subtitle>
     <p><code>9-15</code> Клиент проверяет переданные ему при запуске аргументы командной строки. Сначала проверяется, указан ли в строке IP-адрес узла, на который нужно отправлять сообщения. Затем проверяется, указан ли параметр отправки эхо-сообщений всем (мы воспользуемся им в разделе 10.5). Наконец, клиент создает сокет SCTP типа «один-ко-многим».</p>
     <subtitle>Подготовка адреса сервера</subtitle>
     <p><code>16-20</code> Клиент преобразует IP-адрес сервера, переданный ему в командной строке, с помощью функции <code>inet_pton</code>. К адресу он добавляет заранее известный номер порта сервера. Полученная структура используется для всех обращений к данному серверу.</p>
     <subtitle>Подписка на уведомления</subtitle>
     <p><code>21-23</code> Клиент явно указывает, какие именно уведомления он хочет получать от созданного сокета SCTP. События <code>MSG_NOTIFICATION</code> ему не нужны, поэтому он отключает их, оставляя лишь структуру <code>sctp_sndrcvinfo</code>.</p>
     <subtitle>Вызов функции обработки сообщений</subtitle>
     <p><code>24-28</code> Если флаг <code>echo_to_all</code> не установлен, клиент вызывает функцию <code>sctpstr_cli</code>, которая будет обсуждаться в разделе 10.4. В противном случае вызывается <code>sctpstr_cli_echoall</code> (раздел 10.5, где рассматривается применение потоков SCTP).</p>
     <subtitle>Завершение работы</subtitle>
     <p><code>29-31</code> Закончив работу с сообщениями, клиент закрывает сокет SCTP, что приводит к закрытию всех ассоциаций, использующих этот сокет. Затем функция <code>main</code> завершается и возвращает код 0 — никаких ошибок не произошло.</p>
    </section>
    <section>
     <title>
      <p>10.4. Потоковый эхо-клиент SCTP: функция str_cli</p>
     </title>
     <section>
      <p>В листинге 10.3 приведена основная функция эхо-клиента SCTP.</p>
      <p><strong>Листинг 10.3</strong>. Функция sctp_strcli</p>
      <p><code>//sctp/sctp_strcli.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 sctpstr_cli(FILE *fp, int sock_fd, struct sockaddr *to, socklen_t tolen)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  struct sockaddr_in peeraddr;</code></p>
      <p><code> 6  struct sctp_sndrcvinfo sri;</code></p>
      <p><code> 7  char sendline[MAXLINE], recvline[MAXLINE];</code></p>
      <p><code> 8  socklen_t len;</code></p>
      <p><code> 9  int out_sz, rd_sz;</code></p>
      <p><code>10  int msg_flags;</code></p>
      <empty-line/>
      <p><code>11  bzero(&amp;sri, sizeof(sri));</code></p>
      <p><code>12  while (fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <p><code>13   if (sendline[0] != '[') {</code></p>
      <p><code>14    printf("Error, line must be of the form '[streamnum]text'\n");</code></p>
      <p><code>15    continue;</code></p>
      <p><code>16   }</code></p>
      <p><code>17   sri.sinfo_stream = strtol(&amp;sendline[1], NULL, 0);</code></p>
      <p><code>18   out_sz = strlen(sendline);</code></p>
      <p><code>19   Sctp_sendmsg(sock_fd, sendline, out_sz,</code></p>
      <p><code>20    to, tolen, 0, 0, sri.sinfo_stream, 0, 0);</code></p>
      <empty-line/>
      <p><code>21   len = sizeof(peeraddr);</code></p>
      <p><code>22   rd_sz = Sctp_recvmsg(sock_fd, recvline, sizeof(recvline),</code></p>
      <p><code>23    (SA*)&amp;peeraddr, &amp;len, &amp;sri, &amp;msg_flags);</code></p>
      <p><code>24   printf("From str:%d seq:%d (assoc:0x%x):",</code></p>
      <p><code>25    sri.sinfo_stream.sri.sinfo_ssn, (u_int)sri.sinfo_assoc_id);</code></p>
      <p><code>26   printf("%*s", rd_sz.recvline);</code></p>
      <p><code>27  }</code></p>
      <p><code>28 }</code></p>
      <subtitle>Инициализация структуры sri и вход в цикл</subtitle>
      <p><code>11-12</code> Основная функция клиента начинает работу с очистки структуры <code>sctp_sndrcvinfo</code> (переменная <code>sri</code>). Затем функция входит в цикл, считывающий из дескриптора <code>fp</code>, переданного вызывающей функцией, при помощи блокирующего вызова <code>fgets</code>. Главная программа (<code>main</code>) передает этой функции <code>stdin</code> в качестве аргумента <code>fp</code>, поэтому функция считывает и обрабатывает пользовательский ввод до тех пор, пока пользователь не введет завершающий EOF (Ctrl+D). При этом функция завершается и управление передается вызвавшей функции.</p>
      <subtitle>Проверка ввода</subtitle>
      <p><code>13-16</code> Клиент проверяет введенный пользователем текст на соответствие шаблону <code>[#]текст</code>. Если формат строки нарушен, клиент выводит сообщение об ошибке и снова вызывает <code>fgets</code>.</p>
      <subtitle>Преобразование номера потока</subtitle>
      <p><code>17</code> Клиент записывает запрошенный пользователем номер потока из текстовой строки в поле <code>sinfo_stream</code> структуры <code>sri</code>.</p>
      <subtitle>Отправка сообщения</subtitle>
      <p><code>18-20</code> После инициализации длины структуры адреса и размера пользовательских данных клиент отсылает сообщение серверу при помощи функции <code>sctp_sendmsg</code>.</p>
      <subtitle>Блокирование в ожидании ответа</subtitle>
      <p><code>21-23</code> Клиент блокируется и ожидает получения эхо-ответа сервера.</p>
      <subtitle>Отображение полученного эхо-ответа</subtitle>
      <p><code>24-26</code> Клиент выводит на экран полученное от сервера сообщение, вместе с номером потока и последовательным номером сообщения в этом потоке. После этого клиент возвращается на начало цикла, ожидая, что пользователь введет следующую строку.</p>
     </section>
     <section>
      <title>
       <p>Запуск программы</p>
      </title>
      <p>Мы запустили эхо-сервер SCTP без аргументов командной строки на компьютере, работающем под управлением FreeBSD. Клиенту при запуске необходимо указать IP-адрес сервера.</p>
      <p><code>freebsd4% <strong>sctpclient01 10.1.1.5</strong></code></p>
      <p><code><strong>[0]Hello </strong>                                    <emphasis>Отправка сообщения по потоку 0</emphasis></code></p>
      <p><code>From str:1 seq:0 (assoc:0xc99e15a0):[0]Hello <emphasis>Эхо-ответ сервера в потоке 1</emphasis></code></p>
      <p><code><strong>[4]Message two</strong>                               <emphasis>Отправка сообщения по потоку 4</emphasis></code></p>
      <p><code>From str:5 seq:0 (assoc.0xc99e15a0):[4]Message two   <emphasis>Эхо-ответ сервера</emphasis></code></p>
      <p><code><emphasis>                                                     в потоке 5</emphasis></code></p>
      <p><code><strong>[4]Message three</strong>                             <emphasis>Отправка сообщения по потоку 4</emphasis></code></p>
      <p><code>From str:5 seq:1 (assoc 0xc99e15a0):[4]Message three <emphasis>Эхо-ответ сервера</emphasis></code></p>
      <p><code><emphasis>                                                     в потоке 5</emphasis></code></p>
      <p><code><strong>^D</strong>                                           <emphasis>Ввод символа EOF</emphasis></code></p>
      <p><code>freebsd4%</code></p>
      <p>Обратите внимание, что клиент отправляет сообщения по потокам 0 и 4, а сервер отвечает ему по потокам 1 и 5. Именно такое поведение и ожидается в том случае, когда наш сервер запускается без аргументов командной строки. Заметьте также, что порядковый номер сообщения по пятому потоку увеличился на единицу при приеме третьего сообщения, как и должно было произойти.</p>
     </section>
    </section>
    <section>
     <title>
      <p>10.5. Блокирование очереди</p>
     </title>
     <section>
      <p>Наш сервер позволяет отправлять текстовые сообщения по любому из нескольких потоков. Поток SCTP — это вовсе не поток байтов, как в TCP. Это последовательность сообщений, упорядоченных в пределах ассоциации. Потоки с собственным порядком используются для того, чтобы обойти <emphasis>блокирование очереди</emphasis> (<emphasis>head-of-line blocking</emphasis>), которое может возникать в TCP.</p>
      <p>Блокирование возникает при потере сегмента TCP при передаче и приходе следующего за ним сегмента, который удерживается до тех пор, пока утраченный сегмент не будет передан повторно и получен адресатом. Задержка доставки последующих сегментов гарантирует, что приложение получит данные в том порядке, в котором они были отправлены. Это совершенно необходимая функция, которая, к сожалению, обладает определенными недостатками. Представьте, что семантически независимые сообщения передаются по одному соединению TCP. Например, веб-сервер может передать браузеру три картинки для отображения на экране. Чтобы картинки выводились на экран одновременно, сервер передает сначала часть первого изображения, затем часть второго и часть третьего. Процесс повторяется до тех пор, пока все три картинки не будут переданы клиенту целиком. Что произойдет, если потеряется сегмент TCP, относящийся к первому изображению? Клиент не получит никаких данных до тех пор, пока недостающий сегмент не будет передан повторно и доставлен ему. Задержаны будут все три изображения, хотя сегмент относился только к одному из них (первому). Эту ситуацию иллюстрирует рис. 10.2.</p>
      <image l:href="#img_83.png"/>
      <p><strong>Рис. 10.2</strong>. Отправка трех изображений по одному TCP-соединению</p>
      <empty-line/>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Хотя HTTP работает иначе, были предложены расширения этого протокола, такие как SCP [108] и SMUX [33], которые обеспечивают описанную функциональность поверх TCP. Эти протоколы мультиплексирования позволяют избежать проблем, связанных с параллельными TCP-соединениями, не имеющими общей информации о состоянии [123]. Несмотря на то что создание одного TCP-соединения для каждого изображения (как обычно и делают клиенты HTTP) позволяет избежать блокирования, каждому соединению приходится тратить время на определение времени обращения и доступной пропускной способности. Потеря сегмента, относящегося к одному соединению (признак затора на линии) не обязательно приводит к замедлению передачи по остальным соединениям. В результате совокупное использование загруженных сетей падает.</p>
      </cite>
      <p>Для приложения было бы лучше, если бы транспортный протокол вел себя иначе. В идеале задерживаться должны только сегменты первой картинки, тогда как сегменты второй и третьей должны доставляться так, как если бы сегмент первой картинки не был утерян вовсе.</p>
      <p>Многопоточный режим SCTP позволяет свести к минимуму блокирование очереди. На рис. 10.3 мы показываем процесс отправки тех же трех изображений. На этот раз сервер использует потоки, так что блокируется только одно изображение, а второе и третье доставляются без помех. Первое изображение не доставляется до тех пор, пока не будет восстановлен порядок сегментов.</p>
      <image l:href="#img_84.png"/>
      <p><strong>Рис. 10.3</strong>. Отправка трех изображений по потокам SCTP</p>
      <p>Теперь мы можем привести полный код нашего клиента (с функцией <code>sctpstr_cli_echoall</code>, листинг 10.4), чтобы на его примере продемонстрировать устранение проблем с блокированием очереди при помощи SCTP. Новая функция аналогична <code>sctpstr_cli</code> за тем исключением, что клиент больше не требует указания номера потока в квадратных скобках в каждом сообщении. Функция передает сообщение пользователя по всем потокам, количество которых определяется константой <code>SERV_MAX_SCTP_STRM</code>. После отправки сообщения клиент ждет прихода всех ответных сообщений сервера. Запуская сервер, мы передаем ему аргумент командной строки, указывающий на то, что сервер должен отвечать на сообщения по тем же потокам, по которым они приходят. Это позволяет пользователю отслеживать ответы и порядок их прибытия.</p>
      <p><strong>Листинг 10.4</strong>. Функция sctp_strcliecho</p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 #define SCTP_MAXLINE 800</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 sctpstr_cli_echoall(FILE *fp, int sock_fd, struct sockaddr to,</code></p>
      <p><code> 5  socklen_t tolen)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  struct sockaddr_in peeraddr;</code></p>
      <p><code> 8  struct sctp_sndrcvinfo sri;</code></p>
      <p><code> 9  char sendline[SCTP_MAXLlNE], recvline[SCTP_MAXLINE];</code></p>
      <p><code>10  socklen_t len;</code></p>
      <p><code>11  int rd_sz, i, strsz;</code></p>
      <p><code>12  int msg_flags;</code></p>
      <empty-line/>
      <p><code>13  bzero(sendline, sizeof(sendline));</code></p>
      <p><code>14  bzero(&amp;sri, sizeof(sri));</code></p>
      <p><code>15  while (fgets(sendline, SCTP_MAXLINE - 9, fp) != NULL) {</code></p>
      <p><code>16   strsz = strlen(sendline);</code></p>
      <p><code>17   if (sendline[strsz-1] == '\n') {</code></p>
      <p><code>18    sendline[strsz-1] = '\0';</code></p>
      <p><code>19    strsz--;</code></p>
      <p><code>20   }</code></p>
      <p><code>21   for (i=0; i&lt;SERV_MAX_SCTP_STRM; i++) {</code></p>
      <p><code>22    snprintf(sendline + strsz, sizeof(sendline) - strsz,</code></p>
      <p><code>23     ".msg %d", i);</code></p>
      <p><code>24    Sctp_sendmsg(sock_fd, sendline, sizeof(sendline),</code></p>
      <p><code>25     to, tolen, 0, 0, i, 0, 0);</code></p>
      <p><code>26   }</code></p>
      <p><code>27   for (i =0; i &lt; SERV_MAX_SCTP_STRM; i++) {</code></p>
      <p><code>28    len = sizeof(peeraddr);</code></p>
      <p><code>29    rd_sz = Sctp_recvmsg(sock_fd, recvline, sizeof(recvline),</code></p>
      <p><code>30     (SA*)&amp;peeraddr, &amp;len, &amp;sri, &amp;msg_flags);</code></p>
      <p><code>31    printf("From str:%d seq:%d (assoc:0x%x)",</code></p>
      <p><code>32     sri.sinfo_stream, sri.sinfo_ssn,</code></p>
      <p><code>33     (u_int)sri, sinfo_assoc_id);</code></p>
      <p><code>34    printf("%.*s\n", rd_sz, recvline);</code></p>
      <p><code>35   }</code></p>
      <p><code>36  }</code></p>
      <p><code>37 }</code></p>
      <subtitle>Инициализация структур данных и ожидание ввода</subtitle>
      <p><code>13-15</code> Как и в предыдущем примере, клиент инициализирует структуру <code>sri</code>, предназначенную для настройки потока, с которым клиент будет работать. Кроме того, клиент обнуляет буфер данных, из которого считывается пользовательский ввод. Затем программа входит в основной цикл, блокируясь в вызове <code>fgets</code>.</p>
      <subtitle>Предварительная обработка сообщения</subtitle>
      <p><code>16-20</code> Клиент определяет размер сообщения и удаляет символ перевода строки, если таковой находится в конце буфера.</p>
      <subtitle>Отправка сообщения по всем потокам</subtitle>
      <p><code>21-26</code> Клиент отсылает сообщение с помощью функции <code>sctp_sendmsg</code>. Передается все содержимое буфера длиной <code>SCTP_MAXLINE</code>. Перед отправкой сообщения к нему добавляется строка <code>.msg</code>, и номер потока, чтобы мы могли впоследствии определить порядок получения сообщений и сравнить его с порядком отправки сообщений. Обратите внимание, что клиент отправляет сообщения по заданному количеству потоков, не проверяя, сколько потоков было согласовано с сервером. Может получиться так, что некоторые операции отправки сообщений завершатся с ошибкой, если количество потоков будет снижено по запросу собеседника.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Этот код может работать неправильно, если окна приема и отправки будут слишком малы. Если окно приема собеседника слишком мало, клиент может заблокироваться. Поскольку клиент не переходит к считыванию данных из приемного буфера, пока он не отправит все сообщения, сервер также может заблокироваться в ожидании освобождения буфера клиента. В результате обе конечные точки SCTP зависнут. Приведенная программа не рассчитана на масштабирование. Она предназначена лишь для иллюстрации потоков и блокирования очередей в простейшем варианте.</p>
      </cite>
      <subtitle>Считывание и отображение эхо-ответа</subtitle>
      <p><code>27-35</code> Клиент блокируется в цикле считывания всех ответных сообщений сервера, которые отображаются на экране по мере поступления. После считывания последнего сообщения сервера клиент возвращается к обработке ввода пользователя.</p>
     </section>
     <section>
      <title>
       <p>Запуск программы</p>
      </title>
      <p>Мы запустили клиент и сервер на разных компьютерах с FreeBSD, между которыми был установлен настраиваемый маршрутизатор (рис. 10.4). Маршрутизатор может создавать задержку и сбрасывать часть пакетов. Сначала мы запускаем программу без сброса пакетов на маршрутизаторе.</p>
      <image l:href="#img_85.png"/>
      <p><strong>Рис. 10.4</strong>. Тестовая конфигурация сети</p>
      <p>Мы запускаем сервер с аргументом 0 в командной строке, благодаря чему сервер не увеличивает номер потока при отправке эхо-ответа.</p>
      <p>Затем мы запускаем клиент, передавая ему в командной строке адрес эхо-сервера и дополнительный аргумент, указывающий на необходимость отправки сообщений по всем потокам одновременно.</p>
      <p><code>freebsd4% <strong>sctpclient01 10.1.4.1 echo</strong></code></p>
      <p><code>Echoing messages to all streams</code></p>
      <p><code><strong>Hello</strong></code></p>
      <p><code>From str:0 seq:0 (assoc:0xc99e15a0):Hello.msg.0</code></p>
      <p><code>From str:1 seq:0 (assoc.0xc99e15a0):Hello.msg.1</code></p>
      <p><code>From str:2 seq:0 (assoc:0xc99e15a0):Hello.msg.2</code></p>
      <p><code>From str:3 seq:0 (assoc 0xc99e15a0):Hello.msg.3</code></p>
      <p><code>From str:4 seq:0 (assoc.0xc99e15a0):Hello.msg.4</code></p>
      <p><code>From str:5 seq:0 (assoc:0xc99e15a0):Hello.msg.5</code></p>
      <p><code>From str:6 seq:0 (assoc.0xc99e15a0):Hello.msg.6</code></p>
      <p><code>From str:7 seq:0 (assoc:0xc99e15a0):Hello.msg.7</code></p>
      <p><code>From str:8 seq:0 (assoc:0xc99e15a0):Hello.msg.8</code></p>
      <p><code>From str:9 seq:0 (assoc:0xc99e15a0).Hello.msg.9</code></p>
      <p><code><strong>^D</strong></code></p>
      <p><code>freebsd4%</code></p>
      <p>В отсутствие потерь при передаче клиент получает ответы сервера в том же порядке, в котором отправляет запросы. Изменим параметры маршрутизатора таким образом, чтобы терять 10% всех пакетов, передаваемых в обоих направлениях, и перезапустим клиент.</p>
      <p><code>freebsd4% <strong>sctpclient01 10.1.4.1 echo</strong></code></p>
      <p><code>Echoing messages to all streams</code></p>
      <p><code><strong>Hello</strong></code></p>
      <p><code>From str:0 seq:0 (assoc:0xc99e15a0):Hello.msg.0</code></p>
      <p><code>From str:2 seq:0 (assoc:0xc99e15a0):Hello.msg.2</code></p>
      <p><code>From str:3 seq:0 (assoc:0xc99e15a0):Hello.msg.3</code></p>
      <p><code>From str:5 seq:0 (assoc:0xc99e15a0):Hello.msg.5</code></p>
      <p><code>From str:1 seq:0 (assoc:0xc99e15a0):Hello.msg.1</code></p>
      <p><code>From str:8 seq:0 (assoc:0xc99e15a0):Hello.msg.8</code></p>
      <p><code>From str:4 seq:0 (assoc:0xc99e15a0):Hello.msg.4</code></p>
      <p><code>From str:7 seq:0 (assoc:0xc99e15a0):Hello.msg.7</code></p>
      <p><code>From str:9 seq:0 (assoc:0xc99e15a0):Hello.msg.9</code></p>
      <p><code>From str:6 seq:0 (assoc:0xc99e15a0):Hello msg.6</code></p>
      <p><code><strong>^D</strong></code></p>
      <p><code>freebsd4%</code></p>
      <p>Можно проверить, действительно ли сообщения в каждом из потоков доставляются в правильном порядке, если изменить клиента так, чтобы он отправлял по два сообщения в поток. Кроме того, мы добавим к сообщению суффикс с его номером, чтобы отличать эхо-ответы друг от друга. Измененная функция клиента представлена в листинге 10.5.</p>
      <p><strong>Листинг 10.5</strong>. Изменения в функции sctp_strcliecho</p>
      <p><code>//sctp/sctp_strcliecho2.c</code></p>
      <p><code>21 for (i =0; i &lt; SERV_MAX_SCTP_STRM; i++) {</code></p>
      <p><code>22  snprintf(sendline + strsz, sizeof(sendline) - strsz,</code></p>
      <p><code>23   ".msg.%d 1", i);</code></p>
      <p><code>24  Sctp_sendmsg(sock_fd, sendline, sizeof(sendline),</code></p>
      <p><code>25   to, tolen, 0, 0, i, 0, 0);</code></p>
      <p><code>26  snprintf(sendline + strsz, sizeof(sendline) - strsz,</code></p>
      <p><code>27   ".msg.%d 2", i);</code></p>
      <p><code>28  Sctp_sendmsg(sock_fd, sendline, sizeof(sendline),</code></p>
      <p><code>29   to, tolen, 0, 0, i, 0, 0);</code></p>
      <p><code>30 }</code></p>
      <p><code>31 for (i = 0; i &lt; SERV_MAX_SCTP_STRM*2, i++) {</code></p>
      <p><code>32  len = sizeof(peeraddr);</code></p>
      <subtitle>Первое сообщение: добавление номера и отправка</subtitle>
      <p><code>22-25</code> Клиент добавляет к первому сообщению его номер, с помощью которого мы сможем отслеживать отправленные сообщения. Затем сообщение отсылается вызовом <code>sctp_sendmsg</code>.</p>
      <subtitle>Второе сообщение: добавление номера и отправка</subtitle>
      <p><code>26-29</code> Номер сообщения изменяется с единицы на двойку, после чего сообщение отсылается по тому же потоку.</p>
      <subtitle>Считывание и отображение эхо-ответа</subtitle>
      <p><code>31</code> Здесь требуется лишь одно незначительное изменение: количество ожидаемых ответов эхо-сервера должно быть удвоено.</p>
     </section>
     <section>
      <title>
       <p>Запуск измененной программы</p>
      </title>
      <p>Запустив сервер и измененный клиент, мы получаем следующий результат:</p>
      <p><code>freebsd4% <strong>sctpclient01 10.1.4.1 echo</strong></code></p>
      <p><code>Echoing messages to all streams</code></p>
      <p><code><strong>Hello</strong></code></p>
      <p><code>From str:0 seq:0 (assoc:0xc99e15a0):Hello.msg.0 1</code></p>
      <p><code>From str:0 seq:1 (assoc:0xc99e15a0):Hello.msg.0 2</code></p>
      <p><code>From str:1 seq:0 (assoc:0xc99e15a0):Hello.msg.1 1</code></p>
      <p><code>From str:4 seq:0 (assoc:0xc99e15a0):Hello.msg.4 1</code></p>
      <p><code>From str:5 seq:0 (assoc:0xc99e15a0):Hello.msg.5 1</code></p>
      <p><code>From str:7 seq:0 (assoc:0xc99e15a0):Hello.msg.7 1</code></p>
      <p><code>From str:8 seq:0 (assoc:0xc99e15a0):Hello.msg.8 1</code></p>
      <p><code>From str:9 seq:0 (assoc:0xc99e15a0):Hello.msg.9 1</code></p>
      <p><code>From str:3 seq:0 (assoc:0xc99e15a0):Hello.msg.3 1</code></p>
      <p><code>From str:3 seq:0 (assoc:0xc99e15a0):Hello.msg.3 2</code></p>
      <p><code>From str:1 seq:0 (assoc:0xc99e15a0):Hello.msg.1 2</code></p>
      <p><code>From str:5 seq:0 (assoc:0xc99e15a0):Hello.msg.5 2</code></p>
      <p><code>From str:2 seq:0 (assoc:0xc99e15a0):Hello.msg.2 1</code></p>
      <p><code>From str:6 seq:0 (assoc:0xc99e15a0):Hello.msg.6 1</code></p>
      <p><code>From str:6 seq:0 (assoc:0xc99e15a0):Hello.msg.6 2</code></p>
      <p><code>From str:2 seq:0 (assoc:0xc99e15a0):Hello.msg.2 2</code></p>
      <p><code>From str:7 seq:0 (assoc:0xc99e15a0):Hello.msg.7 2</code></p>
      <p><code>From str:8 seq:0 (assoc:0xc99e15a0):Hello.msg.8 2</code></p>
      <p><code>From str:9 seq:0 (assoc:0xc99e15a0):Hello.msg.9 2</code></p>
      <p><code>From str:4 seq:0 (assoc:0xc99e15a0):Hello.msg.4 2</code></p>
      <p><code><strong>^D</strong></code></p>
      <p><code>freebsd4%</code></p>
      <p>Как видно из вывода, сообщения действительно теряются, но при этом задерживаются только те, которые относятся к конкретному потоку. Данные в остальных потоках не задерживаются. Потоки SCTP могут послужить мощным средством борьбы с блокированием очереди, позволяющим в то же время сохранять порядок данных в рамках конкретного набора сообщений.</p>
     </section>
    </section>
    <section>
     <title>
      <p>10.6. Управление количеством потоков</p>
     </title>
     <p>Мы рассмотрели пример использования потоков SCTP, но пока что мы не знаем, каким образом можно контролировать количество потоков, запрашиваемых конечной точкой в процессе инициализации ассоциации. В предыдущих примерах мы работали с тем количеством исходящих потоков, которое было установлено в системе по умолчанию. В реализации SCTP для FreeBSD, созданной в рамках проекта KAME, это значение равно 10. А что, если серверу и клиенту нужно больше десяти потоков? В листинге 10.6 мы приводим модификацию кода сервера, позволяющую увеличивать количество потоков, запрашиваемое при создании ассоциации. Обратите внимание, что данный параметр сокета должен быть изменен до создания ассоциации.</p>
     <p><strong>Листинг 10.6</strong>. Вариант сервера, допускающий увеличение числа потоков</p>
     <p><code>//sctp/sctpserv02.c</code></p>
     <p><code>14 if (argc 2)</code></p>
     <p><code>15  stream_increment = atoi(argv[1]);</code></p>
     <p><code>16 sock_fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);</code></p>
     <p><code>17 bzero(&amp;initm, sizeof(initm));</code></p>
     <p><code>18 initm.sinit_num_ostreams = SERV_MORE_STRMS_SCTP;</code></p>
     <p><code>19 Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_INITMSG, &amp;initm, sizeof(initm));</code></p>
     <subtitle>Предварительная настройка</subtitle>
     <p><code>14-16</code> Как и в предыдущей версии программы, сервер устанавливает флаг <code>stream_increment</code> в соответствии с дополнительным параметром командной строки, после чего открывает сокет.</p>
     <subtitle>Изменение запрашиваемого количества потоков</subtitle>
     <p><code>17-19</code> Все сделанные модификации относятся именно к этим строкам. Сначала сервер обнуляет структуру <code>sctp_initmsg</code>. Это изменение гарантирует, что вызов <code>setsockopt</code> не приведет к непреднамеренному изменению каких-либо иных значений кроме того, которое нас интересует. Затем сервер устанавливает поле <code>sinit_max_ostreams</code> равным количеству запрашиваемых потоков. После этого вызывается функция <code>setsockopt</code> с параметром сокета <code>SCTP_INITMSG</code> для установки параметров сообщения INIT.</p>
     <p>Альтернативой установке параметра сокета может быть вызов функции <code>sendmsg</code> со вспомогательными данными, запрашивающими требуемое количество потоков. Передача вспомогательных данных приведет к желаемому результату только для сокетов типа «один-ко-многим».</p>
    </section>
    <section>
     <title>
      <p>10.7. Управление завершением соединения</p>
     </title>
     <p>В наших примерах на клиента была возложена ответственность по завершению ассоциации, для чего ему приходилось закрывать сокет. Но закрытие сокета не всегда является желаемой операцией с точки зрения приложения. Кроме того, серверу не нужно оставлять ассоциацию открытой после отправки эхо-ответа. В описанных ситуациях применяются альтернативные механизмы завершения ассоциации. Для сокетов типа «один-ко-многим» доступно два метода: корректное и аварийное закрытие.</p>
     <p>Если сервер хочет закрыть ассоциацию после отправки сообщения, он должен добавить флаг MSG_EOF в это сообщение, поместив его в поле <code>sinfo_flags</code> структуры <code>sctp_sndrcvinfo</code>. Этот флаг закрывает ассоциацию после подтверждения приема отсылаемого сообщения. Альтернативный метод состоит в установке флага <code>MSG_ABORT</code> в том же поле <code>sinfo_flags</code>. При этом происходит немедленное закрытие ассоциации с отправкой порции ABORT (аналог TCP-сегмента RST). Данные, находящиеся в буфере отправки, сбрасываются. Однако закрытие сеанса SCTP порцией ABORT не приводит к негативным последствиям типа пропущенного состояния TIME_WAIT, как это происходит в TCP. В листинге 10.7 показана новая версия эхо-сервера, инициирующая корректное завершение соединения одновременно с отправкой эхо-ответа клиенту. В листинге 10.8 показана версия клиента, отправляющая порцию ABORT перед закрытием сокета.</p>
     <p><strong>Листинг 10.7</strong>. Сервер, закрывающий ассоциацию после отправки ответа</p>
     <p><code>//sctp/sctpserv03.c</code></p>
     <p><code>25 for (;;) {</code></p>
     <p><code>26  len = sizeof(struct sockaddr_in);</code></p>
     <p><code>27  rd_sz = Sctp_recvmsg(sock_fd, readbuf, sizeof(readbuf),</code></p>
     <p><code>28   (SA*)&amp;cliaddr, &amp;len, &amp;sri, &amp;msg_flags);</code></p>
     <p><code>29  if (stream_increment) {</code></p>
     <p><code>30   sri.sinfo_stream++;</code></p>
     <p><code>31   if (sri.sinfo_stream &gt;=</code></p>
     <p><code>32    sctp_get_no_strms(sock_fd, (SA*)&amp;cliaddr, len))</code></p>
     <p><code>33    sri.sinfo_stream = 0;</code></p>
     <p><code>34  }</code></p>
     <p><code>35  Sctp_sendmsg(sock_fd, readbuf, rd_sz,</code></p>
     <p><code>36   (SA*)&amp;cliaddr, len,</code></p>
     <p><code>37   sri.sinfo_ppid,</code></p>
     <p><code>38   (sri.sinfo_flags | MSG_EOF), sri.sinfo_stream, 0, 0);</code></p>
     <p><code>39 }</code></p>
     <subtitle>Отправка ответа с закрытием ассоциации</subtitle>
     <p><code>38</code> Изменение кода сервера состоит в том, что мы добавляем флаг <code>MSG_EOF</code> к прочим флагам в вызове <code>sctp_sendmsg</code> операцией логического ИЛИ. Благодаря этому сервер закрывает ассоциацию после подтверждения доставки сообщения.</p>
     <p><strong>Листинг 10.8</strong>. Клиент, выполняющий аварийное закрытие ассоциации</p>
     <p><code>//sctp/sctpclient02.c</code></p>
     <p><code>25 if (echo_to_all == 0)</code></p>
     <p><code>26  sctpstr_cli(stdin, sock_fd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>27 else</code></p>
     <p><code>28  sctpstr_cli_echoall(stdin, sock_fd, (SA*)&amp;servaddr,</code></p>
     <p><code>29   sizeof(servaddr));</code></p>
     <p><code>30 strcpy(byemsg, "goodbye");</code></p>
     <p><code>31 Sctp_sendmsg(sock_fd, byemsg, strlen(byemsg),</code></p>
     <p><code>32  (SA*)&amp;servaddr, sizeof(servaddr), 0, MSG_ABORT, 0, 0, 0);</code></p>
     <p><code>33 Close(sock_fd);</code></p>
     <subtitle>Аварийное закрытие ассоциации</subtitle>
     <p><code>30-32</code> Клиент подготавливает сообщение об аварийном закрытии ассоциации, вызванном пользовательской ошибкой. Затем функция <code>sctp_sendmsg</code> вызывается с флагом <code>MSG_ABORT</code>. При этом отправляется порция данных ABORT, что приводит к немедленному закрытию ассоциации. В порцию данных включается код пользовательской ошибки и сообщение («goodbye») в поле причины ошибки вышележащего уровня.</p>
     <subtitle>Закрытие дескриптора сокета</subtitle>
     <p><code>33</code> Хотя ассоциация и была завершена, дескриптор сокета все равно закрыть нужно, чтобы освободить связанные с ним системные ресурсы.</p>
    </section>
    <section>
     <title>
      <p>10.8. Резюме</p>
     </title>
     <p>Мы изучили простой пример клиента и сервера SCTP общим объемом около 150 строк кода. Обе программы работали с сокетами SCTP типа «один-ко-многим». Сервер был написан в последовательном стиле, который часто используется при работе с такими сокетами. Он считывал сообщения и отвечал на них по тому же потоку, из которого они приходили, или по потоку с увеличенным на единицу номером. Затем мы исследовали проблему блокирования очереди, изменив программу клиента таким образом, чтобы подчеркнуть особенности ситуации и продемонстрировать использование потоков SCTP для решения проблемы. После этого мы показали, каким образом можно изменить количество потоков при помощи одного из множества параметров сокета, используемых для управления поведением SCTP. Наконец, мы снова изменили код сервера и клиента, чтобы показать корректное и аварийное закрытие ассоциации.</p>
     <p>Углубленное исследование SCTP будет проведено в главе 23.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Что произойдет с программой в листинге 10.1, если SCTP вернет сообщение об ошибке? Каким образом вы можете устранить указанный недостаток программы?</p>
     <p>2. Что произойдет, если сервер завершит работу, не ответив на сообщения? Может ли клиент каким-либо образом получить уведомление об этом событии?</p>
     <p>3. В листинге 10.7 в строке 22 аргумент <code>out_sz</code> устанавливается равным 800 байт. Как вы думаете, почему мы выбрали именно это значение? Существует ли лучший способ найти оптимальное значение этого аргумента?</p>
     <p>4. Как повлияет алгоритм Нагла (см. раздел 7.10) на нашего клиента из листинга 10.7? Не лучше ли будет отключить алгоритм Нагла для этой программы? Воплотите это изменение в код клиента и сервера.</p>
     <p>5. В разделе 10.6 мы утверждали, что приложению следует изменять количество потоков до установки ассоциации. Что произойдет в противном случае?</p>
     <p>6. Когда мы говорили о количестве потоков, мы подчеркнули, что только для сокетов типа «один-ко-многим» можно увеличить количество потоков при помощи вспомогательных данных. Почему это так? (Подсказка: вспомогательные данные необходимо передавать с сообщениями.)</p>
     <p>7. Почему сервер может не отслеживать открытые ассоциации? Опасно ли это?</p>
     <p>8. В разделе 10.7 мы изменили сервер так, что он стал закрывать ассоциацию после отправки каждого сообщения. Вызовет ли это какие-либо проблемы? Хорошее ли это решение с точки зрения архитектуры приложения?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 11</p>
     <p>Преобразования имен и адресов</p>
    </title>
    <section>
     <title>
      <p>11.1. Введение</p>
     </title>
     <p>Во всех предшествующих примерах мы использовали численные адреса узлов (например, 206.6.226.33) и численные номера портов для идентификации серверов (например, порт 13 для стандартного сервера времени и даты и порт 9877 для нашего эхо-сервера). Однако по ряду соображений предпочтительнее использовать имена вместо чисел: во-первых, имена проще запоминаются, во-вторых, если численный адрес поменяется, имя можно сохранить, и в-третьих, с переходом на IPv6 численные адреса становятся значительно длиннее, что увеличивает вероятность ошибки при вводе адреса вручную. В этой главе описываются функции, выполняющие преобразование имен и адресов: <code>gethostbyname</code> и <code>gethostbyaddr</code> для преобразования имен узлов и IP-адресов, и <code>getservbyname</code> и <code>getservbyport</code> для преобразования имен служб и номеров портов. Здесь же мы рассмотрим две независимые от протоколов функции <code>getaddrinfo</code> и <code>getnameinfo</code>, осуществляющие преобразование между IP-адресами и именами узлов, а также между именами служб и номерами портов.</p>
    </section>
    <section>
     <title>
      <p>11.2. Система доменных имен</p>
     </title>
     <section>
      <p><emphasis>Система доменных имен</emphasis> (<emphasis>Domain Name System</emphasis>, <emphasis>DNS</emphasis>) используется прежде всего для сопоставления имен узлов и IP-адресов. Имя узла может быть либо <emphasis>простым</emphasis> (simple name), таким как <code>solaris</code> или <code>bsdi</code>, либо <emphasis>полным доменным именем</emphasis> (f<emphasis>ully qualified domain name</emphasis>, <emphasis>FQDN</emphasis>), например <code>solaris.unpbook.com.</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В техническом отношении FQDN может также называться абсолютным именем и должно оканчиваться точкой, но пользователи часто игнорируют точку в конце. Точка сообщает распознавателю о том, что имя является абсолютным и не требует проведения поиска но различным доменам верхних уровней.</p>
      </cite>
      <p>В этом разделе мы рассмотрим только основы DNS, необходимые нам для сетевого программирования. Читатели, интересующиеся более подробным изложением вопроса, могут обратиться к главе 14 [111] и к [1]. Дополнения, требуемые для IPv6, изложены в RFC 1886 [121].</p>
     </section>
     <section>
      <title>
       <p>Записи ресурсов</p>
      </title>
      <p>Записи в DNS называются <emphasis>записями ресурсов</emphasis> (<emphasis>resource records</emphasis>, <emphasis>RR</emphasis>). Нас интересуют только несколько типов RR.</p>
      <p>&#9632; А. Запись типа А преобразует имя узла в 32-разрядный адрес IPv4. Вот, например, четыре записи DNS для узла <code>freebsd</code> в домене <code>unpbook.com</code>, первая из которых — это запись типа А:</p>
      <p><code>freebsd IN А    12.106.32.254</code></p>
      <p><code>        IN AAAA 3ffe:b80:1f8d:1:a00:20ff:fea7:686b</code></p>
      <p><code>        IN MX   5 freebsd.unpbook.com.</code></p>
      <p><code>        IN MX   10 mailhost.unpbook.com.</code></p>
      <p>&#9632; AAAA. Запись типа AAAA, называемая «четыре А» (quad А), преобразует имя узла в 128-разрядный адрес IPv6. Название «четыре А» объясняется тем, что 128-разрядный адрес в четыре раза больше 32-разрядного адреса.</p>
      <p>&#9632; PTR. Запись PTR (pointer records — запись указателя) преобразует IP-адрес в имя узла. Четыре байта адреса IPv4 располагаются в обратном порядке. Каждый байт преобразуется в десятичное значение ASCII (0-255), а затем добавляется <code>in-addr.arpa</code>. Получившаяся строка используется в запросе PTR.</p>
      <p>32 полубайта 128-разрядного адреса IPv6 также располагаются в обратном порядке. Каждый полубайт преобразуется в соответствующее шестнадцатеричное значение ASCII (<code>0-9</code>, <code>a-f)</code> и добавляется к <code>ip6.arpa</code>.</p>
      <p>Например, две записи PTR для нашего узла <code>freebsd</code> будут выглядеть так:</p>
      <p><code>254.32.106.12 in-addr.arpa</code></p>
      <p><code>b.6.8.6.7.a.e.f.f.f.0.2.0.0.a.0.1.0.0.0.d.8.f.1.0.8.b.0.e.f.f.3.ip6.arpa</code></p>
      <p>&#9632; MX. Запись типа MX (Mail Exchange Record) определяет, что узел выступает в роли «маршрутизирующего почтового сервера» для заданного узла. В приведенном выше примере для узла <code>solaris</code> предоставлено две записи типа MX. Первая имеет предпочтительное значение 5, вторая — 10. Когда существует множество записей типа MX, они используются в порядке предпочтения начиная с наименьшего значения.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Мы не используем в примерах книги записей типа MX, но упоминаем о них, потому что они широко используются в реальной жизни.</p>
      </cite>
      <p>&#9632; CNAME. Аббревиатура CNAME означает «каноническое имя» (canonical name). Обычно такие записи используются для присвоения имен распространенным службам, таким как <code>ftp</code> и <code>www</code>. При использовании имен служб вместо действительного имени узла перемещение службы на другой узел становится прозрачным (то есть незаметным для пользователя). Например, для нашего узла <code>linux</code> каноническими именами могут быть следующие записи:</p>
      <p><code>ftp IN CNAME linux.unpbook.com.</code></p>
      <p><code>www IN CNAME linux.unpbook.com.</code></p>
      <p>Сейчас прошло еще слишком мало времени с момента появления протокола IPv6, чтобы сказать, каких соглашений будут придерживаться администраторы для узлов, поддерживающих и IPv4, и IPv6. В нашем примере мы задали узлу <code>freebsd</code> и запись типа А, и запись типа AAAA. Автор помещает и запись типа А, и запись типа AAAA под каноническим именем узла (как показано ниже) и создает три записи RR. Первая запись RR, имя которой оканчивается на <code>-4</code>, содержит запись типа А; вторая, с именем, оканчивающимся на <code>-6</code>, содержит запись типа AAAA; а третья запись RR, имя которой оканчивается на <code>-611</code>, содержит запись типа AAAA с локальным в пределах физической подсети (link-local, см. главу 19) адресом узла (что иногда удобно использовать в целях отладки). Все записи для другого нашего узла будут выглядеть так:</p>
      <p><code>aix-4   IN А    206.62.226.43</code></p>
      <p><code>aix     IN А    206.62.226.43</code></p>
      <p><code>        IN MX   5 aix.unpbook.com.</code></p>
      <p><code>        IN MX   10 mailhost.unpbook.com.</code></p>
      <p><code>Aix-4   IN A    192.168.42.2</code></p>
      <p><code>aix-6   IN AAAA 3ffe:b80:1f8d:2:204:acff:fe17:bf38</code></p>
      <p><code>aix-611 IN AAAA fe80::204:acff:fe17:bf38</code></p>
      <p>Эта запись дает нам дополнительный контроль над протоколом, выбранным некоторыми приложениями, как мы увидим в следующей главе.</p>
     </section>
     <section>
      <title>
       <p>Распознаватели и серверы имен</p>
      </title>
      <p>Организации обычно работают с одним или несколькими серверами имен (name servers). Часто в качестве сервера используется программа BIND (Berkeley Internet Name Domain). Приложения, такие как клиенты и серверы, которые мы создаем в этой книге, соединяются с сервером DNS при помощи вызова функций из библиотеки, называемой <emphasis>распознавателем</emphasis> (<emphasis>resolver</emphasis>). Обычные функции распознавателя — <code>gethostbyname</code> и <code>gethostbyaddr</code>, и обе они описаны в этой главе. Первая находит адрес узла по его имени, а вторая — наоборот.</p>
      <p>На рис. 11.1 показано типичное расположение приложений, распознавателей и серверов имен. В некоторых системах код распознавателя содержится в системной библиотеке и встраивается в приложение, когда оно создается. В других системах имеется централизованный демон-распознаватель, к которому обращаются все приложения. Системная библиотека выполняет удаленные вызовы процедур такого распознавателя. В любом случае код приложения вызывает код распознавателя посредством обычных вызовов, чаще всего <code>gethostbyname</code> и <code>gethostbyaddr</code>.</p>
      <image l:href="#img_86.png"/>
      <p><strong>Рис. 11.1</strong>. Типичное расположение приложений, распознавателей и серверов имен</p>
      <p>Код распознавателя считывает из файлов конфигурации, зависящих от системы, расположение серверов имен организации. (Мы говорим «серверы имен», употребляя множественное число, потому что большинство организаций работают с несколькими серверами имен, хотя мы и показываем на рисунке только один локальный сервер.) Файл <code>/etc/resolv.conf</code> обычно содержит IP-адреса локальных серверов имен.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Было бы удобно указывать в файле /etc/resolv.conf имена, а не IP-адреса серверов имен, потому что имена удобнее запоминать и редактировать, однако это возвратило бы нас к вечной проблеме курицы и яйца: каким образом распознать имя сервера имен?</p>
      </cite>
      <p>Распознаватель посылает запрос локальному серверу имен, используя UDP. Если локальный сервер имен не знает ответа, он обычно запрашивает другие серверы имен через Интернет, также используя UDP. Если ответ слишком велик, чтобы поместиться в один UDP-пакет, распознаватель автоматически переключается на TCP.</p>
      <empty-line/>
     </section>
     <section>
      <title>
       <p>Альтернативы DNS</p>
      </title>
      <p>Можно получить информацию об имени и адресе без использования DNS. Типичной альтернативой служат статические файлы со списком узлов (обычно файл <code>/etc/hosts</code>, как мы указываем в табл. 11.2), информационная система сети (Network Information System, NIS) и упрощенный протокол службы каталогов (Lightweight Directory Access Protocol — LDAP). К сожалению, способ конфигурирования узла для использования различных типов служб имен зависит от реализации. Solaris 2.x, HP-UX 10 и более новых версий, а также FreeBSD 5.x используют файл <code>/etc/nswitch.conf</code>, тогда как AIX использует файл <code>/etc/netsvc.conf</code>. BIND 9.9 предоставляет свою собственную версию, которая называется IRS (Information Retrieval Service — служба получения информации), использующую файл <code>/etc/irs.conf</code>. Если сервер имен должен применяться для поиска имен узлов, все эти системы используют для задания IP-адресов серверов имен файл <code>/etc/resolv.conf</code>. К счастью, эти различия обычно скрыты от программиста приложений, поэтому мы просто вызываем функции распознавателя, такие как <code>gethostbyname</code> и <code>gethostbyaddr</code>.</p>
     </section>
    </section>
    <section>
     <title>
      <p>11.3. Функция gethostbyname</p>
     </title>
     <section>
      <p>Узлы компьютерных сетей мы обычно идентифицируем по их именам, удобным для человеческого восприятия. Но во всех примерах книги специально использовались IP-адреса вместо имен, поэтому мы точно знаем, что входит в структуры адресов сокетов для таких функций, как <code>connect</code> и <code>sendto</code>, и что возвращается функциями <code>accept</code> и <code>recvfrom</code>. Тем не менее большинство приложений имеют дело с именами, а не с адресами. Это особенно актуально при переходе на IPv6, поскольку адреса IPv6 (шестнадцатеричные строки) значительно длиннее адресов IPv4, записанных в точечно-десятичном представлении. (Например, запись типа AAAA и запись типа PTR для <code>ip6.arpa</code> в предыдущем разделе показывают это со всей очевидностью.)</p>
      <p>Самая основная функция, выполняющая поиск имени узла, — это функция <code>gethostbyname</code>. При успешном выполнении она возвращает указатель на структуру <code>hostent</code>, содержащую все адреса IPv4 для узла. Однако она может возвращать только адреса IPv4. В разделе 11.6 рассматривается функция, возвращающая адреса IPv4 и IPv6. Стандарт POSIX предупреждает, что функция <code>gethostbyname</code> может быть исключена из будущей его версии.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Маловероятно, что реализации gethostbyname исчезнут раньше, чем весь Интернет перейдет на протокол IPv6, а произойдет это еще очень не скоро. Однако удаление функции из стандарта POSIX гарантирует, что она не будет использоваться в новых программах. Вместо нее мы рекомендуем использовать getaddrinfo (раздел 11.6).</p>
      </cite>
      <p><code>#include &lt;netdb.h&gt;</code></p>
      <empty-line/>
      <p><code>struct hostent *gethostbyname(const char *<emphasis>hostname</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: непустой указатель в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
      <p>Непустой указатель, возвращаемый этой функцией, указывает на следующую структуру <code>hostent</code>:</p>
      <p><code>struct hostent {</code></p>
      <p><code> char *h_name;        /* официальное (каноническое) имя узла */</code></p>
      <p><code> char **h_alihases;   /* указатель на массив указателей на псевдонимы */</code></p>
      <p><code> int   h_addrtype;    /* тип адреса узла: AF_INET */</code></p>
      <p><code> int   h_length;      /* длина адреса: 4 */</code></p>
      <p><code> char  **h_addr_list; /* указатель на массив указателей с адресами IPv4 или IPv6 */</code></p>
      <p><code>};</code></p>
      <p>В терминах DNS функция <code>gethostbyname</code> выполняет запрос на запись типа А. Функция возвращает только адреса IPv4.</p>
      <p>На рис. 11.2 представлено устройство структуры <code>hostent</code> и содержащаяся в ней информация, в предположении, что искомое имя узла имеет два альтернативных имени и три адреса IPv4. Все имена узла представляют собой строки языка С.</p>
      <image l:href="#img_87.png"/>
      <p><strong>Рис. 11.2</strong>. Структура hostent и ее одержимое</p>
      <p>Возвращаемое имя <code>h_name</code> называется каноническим именем узла. Например, с показанными в предыдущем разделе записями CNAME каноническое имя узла <code>ftp://ftp.unpbook.com</code> будет иметь вид <code>linux.unpbook.com</code>. Также если мы вызываем функцию <code>gethostbyname</code> с узла <code>aix</code> с неполным именем, например <code>solaris</code>, то в качестве канонического имени возвращается полное доменное имя (FQDN) <code>solaris.unpbook.com.</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Некоторые версии функции gethostbyname допускают, что аргумент hostname может быть записан в виде строки десятичных чисел, разделенных точками. То есть вызов в форме hptr = gethostbyname("206.62.226.33"); будет работать. Этот код был добавлен, поскольку клиент Rlogin принимает только имя узла, вызывая функцию gethostbyname, и не принимает точечно-десятичную запись [127]. Стандарт POSIX допускает это, но не устанавливает такое поведение в качестве обязательного, поэтому переносимое приложение не может использовать указанную особенность.</p>
      </cite>
      <empty-line/>
      <p>Функция <code>gethostbyname</code> отличается от других функций сокетов, описанных нами, тем, что она не задает значение переменной <code>errno</code>, когда происходит ошибка. Вместо этого она присваивает глобальной целочисленной переменной <code>h_errno</code> одну из следующих констант, определяемых в заголовке <code>&lt;netdb.h&gt;</code>:</p>
      <p>&#9632; <code>HOST_NOT_FOUND</code>;</p>
      <p>&#9632; <code>TRY_AGAIN</code>;</p>
      <p>&#9632; <code>NO_RECOVERY</code>;</p>
      <p>&#9632; <code>NO_DATA</code> (идентично <code>NO_ADDRESS</code>).</p>
      <p>Ошибка <code>NO_DATA</code> означает, что заданное имя действительно, но у него нет записи типа А. Примером может служить имя узла, имеющего только запись типа MX.</p>
      <p>Самые современные распознаватели предоставляют функцию <code>hstrerror</code>, которая в качестве единственного аргумента получает значение <code>h_errno</code> и возвращает указатель типа <code>const char*</code> на описание ошибки. Некоторые примеры строк, возвращаемых этой функцией, мы увидим в следующем примере.</p>
     </section>
     <section>
      <title>
       <p>Пример</p>
      </title>
      <p>В листинге 11.1<a l:href="#n1" type="note">[1]</a> показана простая программа, вызывающая функцию <code>gethostbyname</code> для любого числа аргументов командной строки и выводящая всю возвращаемую информацию.</p>
      <p><strong>Листинг 11.1</strong>. Вызов функции и вывод возвращаемой информации</p>
      <p><code>//names/hostent.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  char *ptr, **pptr;</code></p>
      <p><code> 6  char str[INET_ADDRSTRLEN];</code></p>
      <p><code> 7  struct hostent *hptr;</code></p>
      <empty-line/>
      <p><code> 8  while (--argc &gt; 0) {</code></p>
      <p><code> 9   ptr = *++argv;</code></p>
      <p><code>10   if ((hptr = gethostbyname(ptr)) == NULL) {</code></p>
      <p><code>11    err_msg("gethostbyname error for host, %s: %s",</code></p>
      <p><code>12     ptr, hstrerror(h_errno));</code></p>
      <p><code>13    continue;</code></p>
      <p><code>14   }</code></p>
      <p><code>15   printf("official hostname: %s\n", hptr-&gt;h_name);</code></p>
      <empty-line/>
      <p><code>16   for (pptr = hptr-&gt;h_aliases; *pptr != NULL; pptr++)</code></p>
      <p><code>17    printf("\talias: %s\n", *pptr);</code></p>
      <empty-line/>
      <p><code>18   switch (hptr-&gt;h_addrtype) {</code></p>
      <p><code>19   case AF_INET:</code></p>
      <p><code>20    pptr = hptr-&gt;h_addr_list;</code></p>
      <p><code>21    for (; *pptr != NULL; pptr++)</code></p>
      <p><code>22     printf("\taddress: %s\n",</code></p>
      <p><code>23    Inet_ntop(hptr-&gt;h_addrtype, *pptr, str, sizeof(str)));</code></p>
      <p><code>24    break;</code></p>
      <empty-line/>
      <p><code>25   default:</code></p>
      <p><code>26    err_ret("unknown address type");</code></p>
      <p><code>27    break;</code></p>
      <p><code>28   }</code></p>
      <p><code>29  }</code></p>
      <p><code>30  exit(0);</code></p>
      <p><code>31 }</code></p>
      <p><code>8-14</code> Функция <code>gethostbyname</code> вызывается для каждого аргумента командной строки.</p>
      <p><code>15-17</code> Выводится каноническое имя узла, за которым идет список альтернативных имен.</p>
      <p><code>18-24</code> Переменная <code>pptr</code> указывает на массив указателей на индивидуальные адреса. Для каждого адреса мы вызываем функцию <code>inet_ntop</code> и выводим возвращаемую строку.</p>
      <p>Сначала мы выполняем программу с именем нашего узла <code>aix</code>, у которого имеется только один адрес IPv4:</p>
      <p><code>freebsd % <strong>hostent aix</strong></code></p>
      <p><code>official hostname: aix.unpbook.com</code></p>
      <p><code>         address:  192.168 42.2</code></p>
      <p>Обратите внимание, что официальное имя узла — это FQDN. Кроме того, хотя у узла имеется адрес IPv6, возвращается только адрес IPv4. Следующим будет веб-сервер с несколькими адресами IPv4:</p>
      <p><code>solaris % <strong>hostent cnn.com</strong></code></p>
      <p><code>official hostname: cnn.com</code></p>
      <p><code>         address: 64.236.16.20</code></p>
      <p><code>         address: 64.236.16.52</code></p>
      <p><code>         address: 64.236 16.84</code></p>
      <p><code>         address: 64.236.16.116</code></p>
      <p><code>         address: 64.236.24.4</code></p>
      <p><code>         address: 64.236.24.12</code></p>
      <p><code>         address: 64.236.24.20</code></p>
      <p><code>         address: 64.236.24.28</code></p>
      <p>Далее идет имя, представленное в разделе 11.2 как имя с записью типа CNAME:</p>
      <p><code>solaris % <strong>hostent www</strong></code></p>
      <p><code>official hostname: linux.unpbook.com</code></p>
      <p><code>         alias: www.unpbook.com</code></p>
      <p><code>         address: 206.168.112.219</code></p>
      <p>Как мы и предполагали, официальное имя узла отличается от нашего аргумента командной строки.</p>
      <p>Чтобы увидеть строки ошибок, возвращаемые функцией <code>hstrerror</code>, мы сначала задаем несуществующее имя узла, а затем имя, имеющее только запись типа MX:</p>
      <p><code>solaris % <strong>hostent nosuchname.invalid</strong></code></p>
      <p><code>gethostbyname error for host: nosuchname.invalid: Unknown host</code></p>
      <empty-line/>
      <p><code>solaris % <strong>hostent uunet.uu.net</strong></code></p>
      <p><code>gethostbyname error for host: uunet.uu.net: No address associated with name</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>11.4 Функция gethostbyaddr</p>
     </title>
     <p>Функция <code>gethostbyaddr</code> получает в качестве аргумента двоичный IP-адрес и пытается найти имя узла, соответствующее этому адресу. Ее действие обратно действию функции <code>gethostbyname</code>.</p>
     <p><code>#include &lt;netdb.h&gt;</code></p>
     <empty-line/>
     <p><code>struct hostent *gethostbyaddr(const char *<emphasis>addr</emphasis>, size_t <emphasis>len</emphasis>, int <emphasis>family</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: непустой указатель в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Эта функция возвращает указатель на ту же структуру <code>hostent</code>, которую мы описывали при рассмотрении функции <code>gethostbyname</code>. Обычно в этой структуре нас интересует поле <code>h_name</code>, каноническое имя узла.</p>
     <p>Аргумент <code>addr</code> не относится к типу <code>char*</code>, но в действительности это указатель на структуру <code>in_addr</code>, содержащую адрес IPv4. Поле <code>len</code> — это длина структуры: 4 для адресов IPv4. Аргумент <code>family</code> будет иметь значение <code>AF_INET</code>.</p>
     <p>В терминах DNS функция <code>gethostbyaddr</code> запрашивает у сервера имен запись типа PTR в домене <code>in-addr.arpa</code>.</p>
    </section>
    <section>
     <title>
      <p>11.5. Функции getservbyname и getservbyport</p>
     </title>
     <section>
      <p>Службы, как и узлы, также часто идентифицируются по именам. Используя в нашем коде имя службы вместо номера порта, при условии, что имена служб сопоставляются номерам портов в некотором файле (обычно <code>/etc/services</code>), мы получаем следующее преимущество. Если этой службе будет назначен другой номер порта, то нам будет достаточно изменить одну строку в файле <code>/etc/services</code>, вместо того чтобы перекомпилировать все приложения. Следующая функция, <code>getservbyname</code>, ищет службу по ее заданному имени.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Канонический список номеров портов, назначенных определенным службам, поддерживается IANA и располагается по адресу http://www.iana.org/assignments/port-numbers (см. раздел 2.9). Файл /etc/services чаще всего содержит некоторое подмножество списка IANA.</p>
      </cite>
      <p><code>#include &lt;netdb.h&gt;</code></p>
      <empty-line/>
      <p><code>struct servent *getservbyname(const char *<emphasis>servname</emphasis>, const char *<emphasis>protoname</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: непустой указатель в случае успешного выполнения, NULL в случае ошибки</emphasis></code></p>
      <p>Функция возвращает указатель на следующую структуру:</p>
      <p><code>struct servent {</code></p>
      <p><code> char *s_name;     /* официальное имя службы */</code></p>
      <p><code> char **s_aliases; /* список псевдонимов */</code></p>
      <p><code> int s_port;       /* номер порта, записанный в сетевом порядке байтов */</code></p>
      <p><code> char *s_proto;    /* протокол, который нужно использовать */</code></p>
      <p><code>};</code></p>
      <p>Имя службы <code>servname</code> должно быть указано обязательно. Если задан и протокол (то есть если <code>protoname</code> — непустой указатель), то в структуре должен быть указан совпадающий протокол. Некоторые службы Интернета позволяют использовать и TCP, и UDP (например, DNS и все службы, представленные в табл. 2.1), в то время как другие поддерживают только один протокол (протоколу FTP требуется TCP). Если аргумент <code>protoname</code> не задан и служба поддерживает несколько протоколов, то возвращаемый номер порта зависит от реализации. Обычно это не имеет значения, поскольку службы, поддерживающие множество протоколов, как правило, используют один и тот же номер порта для протоколов TCP и UDP, но вообще говоря это не гарантируется.</p>
      <p>Более всего в структуре <code>servent</code> нас интересует поле номера порта. Поскольку номер порта возвращается в сетевом порядке байтов, мы не должны вызывать функцию <code>htons</code> при записи его в структуру адреса сокета.</p>
      <p>Типичные вызовы этой функции могут быть такими:</p>
      <p><code>struct servent *sptr;</code></p>
      <empty-line/>
      <p><code>sptr = getservbyname("domain", "udp"); /* DNS с использованием UDP */</code></p>
      <p><code>sptr = getservbyname("ftp", "tcp");    /* FTP с использованием TCP */</code></p>
      <p><code>sptr = getservbyname("ftp", NULL);     /* FTP с использованием TCP */</code></p>
      <p><code>sptr = getservbyname("ftp", "udp");    /* этот вызов приведет к ошибке */</code></p>
      <p>Поскольку протоколом FTP поддерживается только TCP, второй и третий вызовы эквивалентны, а четвертый вызов приводит к ошибке. Вот соответствующие строки из файла <code>/etc/services</code>:</p>
      <p><code>freebsd % <strong>grep -e ^ftp -e ^domain /etc/services</strong></code></p>
      <p><code>ftp-data  20/tcp  #File Transfer [Default Data]</code></p>
      <p><code>ftp       21/tcp  #File Transfer [Control]</code></p>
      <p><code>domain    53/tcp  #Domain Name Server</code></p>
      <p><code>domain    53/udp  #Domain Name Server</code></p>
      <p><code>ftp-agent 574/tcp #FTP Software Agent System</code></p>
      <p><code>ftp-agent 574/udp #FTP Software Agent System</code></p>
      <p><code>ftps-data 989/tcp # ftp protocol, data, over TLS/SSL</code></p>
      <p><code>ftps      990/tcp # ftp protocol, control, over TLS/SSL</code></p>
      <p>Следующая функция, <code>getservbyport</code>, ищет службу по заданному номеру порта и (не обязательно) протоколу.</p>
      <p><code>#include &lt;netdb.h&gt;</code></p>
      <empty-line/>
      <p><code>struct servent *getservbyport(int <emphasis>port</emphasis>, const char *<emphasis>protname</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: непустой указатель в случае успешного выполнения, NULL в случае ошибки</emphasis></code></p>
      <p>Значение аргумента <code>port</code> должно быть записано в сетевом порядке байтов. Типичные примеры вызова этой функции приведены ниже:</p>
      <p><code>struct servent *sptr;</code></p>
      <empty-line/>
      <p><code>sptr = getservbyport(htons(53), "udp"); /* DNS с использованием UDP */</code></p>
      <p><code>sptr = getservbyport(htons(21), "tcp"); /* FTP с использованием TCP */</code></p>
      <p><code>sptr = getservbyport(htons(21), NULL);  /* FTP с использованием TCP */</code></p>
      <p><code>sptr = getservbyport(htons(21), "udp"); /* этот вызов приведет к ошибке */</code></p>
      <p>Последний вызов оказывается неудачным, поскольку нет службы, использующей порт 21 с протоколом UDP.</p>
      <p>Помните, что некоторые номера портов используются с TCP для одной службы, а с UDP — для совершенно другой, например:</p>
      <p><code>freebsd % <strong>grep 514 /etc/services</strong></code></p>
      <p><code>shell  514/tcp cmd #like exec, but automatic</code></p>
      <p><code>syslog 514/udp</code></p>
      <p>Здесь показано, что порт 514 используется командой <code>rsh</code> с TCP и демоном <code>syslog</code> с UDP. Это характерно для портов 512-514.</p>
     </section>
     <section>
      <title>
       <p>Пример: использование функций gethostbyname и getservbyname</p>
      </title>
      <p>Теперь мы можем изменить код нашего TCP-клиента времени и даты, показанный в листинге 1.1, так, чтобы использовать функции <code>gethostbyname</code> и <code>getservbyname</code> и принимать два аргумента командной строки: имя узла и имя службы. Наша программа показана в листинге 11.2. Эта программа также демонстрирует желательное поведение при установлении соединения со всеми IP-адресами сервера на узле, имеющем несколько сетевых интерфейсов: попытки продолжаются до тех пор, пока соединение не будет успешно установлено или пока не будут перебраны все адреса.</p>
      <p><strong>Листинг 11.2</strong>. Наш клиент времени и даты, использующий функции gethostbyname и getservbyname</p>
      <p><code>//names/daytimetcpcli1.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd, n;</code></p>
      <p><code> 6  char recvline[MAXLINE + 1];</code></p>
      <p><code> 7  struct sockaddr_in servaddr;</code></p>
      <p><code> 8  struct in_addr **pptr;</code></p>
      <p><code> 9  struct in_addr *inetaddrp[2];</code></p>
      <p><code>10  struct in_addr inetaddr;</code></p>
      <p><code>11  struct hostent *hp;</code></p>
      <p><code>12  struct servent *sp;</code></p>
      <empty-line/>
      <p><code>13  if (argc != 3)</code></p>
      <p><code>14   err_quit("usage: daytimetcpcli1 &lt;hostname&gt; &lt;service&gt;");</code></p>
      <empty-line/>
      <p><code>15  if ((hp = gethostbyname(argv[1])) == NULL) {</code></p>
      <p><code>16   if (inet_aton(argv[1], &amp;inetaddr) == 0) {</code></p>
      <p><code>17    err_quit("hostname error for %s: %s", argv[1],</code></p>
      <p><code>18    hstrerror(h_errno));</code></p>
      <p><code>19   } else {</code></p>
      <p><code>20    inetaddrp[0] = &amp;inetaddr;</code></p>
      <p><code>21    inetaddrp[1] = NULL;</code></p>
      <p><code>22    pptr = inetaddrp;</code></p>
      <p><code>23   }</code></p>
      <p><code>24  } else {</code></p>
      <p><code>25   pptr = (struct in_addr**)hp-&gt;h_addr_list;</code></p>
      <p><code>26  }</code></p>
      <empty-line/>
      <p><code>27  if ((sp = getservbyname(argv[2], "tcp")) == NULL)</code></p>
      <p><code>28   err_quit("getservbyname error for %s", argv[2]);</code></p>
      <empty-line/>
      <p><code>29  for (; *pptr != NULL; pptr++) {</code></p>
      <p><code>30   sockfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
      <empty-line/>
      <p><code>31   bzero(&amp;servaddr, sizeof(servaddr));</code></p>
      <p><code>32   servaddr.sin_family = AF_INET;</code></p>
      <p><code>33   servaddr.sin_port = sp-&gt;s_port;</code></p>
      <p><code>34   memcpy(&amp;servaddr.sin_addr, *pptr, sizeof(struct in_addr));</code></p>
      <p><code>35   printf("trying %s\n", Sock_ntop((SA*)&amp;servaddr, sizeof(servaddr)));</code></p>
      <empty-line/>
      <p><code>36   if (connect(sockfd, (SA*)&amp;servaddr, sizeof(servaddr)) == 0)</code></p>
      <p><code>37    break; /* успешное завершение */</code></p>
      <p><code>38   err_ret("connect error");</code></p>
      <p><code>39   close(sockfd);</code></p>
      <p><code>40  }</code></p>
      <p><code>41  if (*pptr == NULL)</code></p>
      <p><code>42   err_quit("unable to connect");</code></p>
      <empty-line/>
      <p><code>43  while ((n = Read(sockfd, recvline, MAXLINE)) &gt; 0) {</code></p>
      <p><code>44   recvline[n] = 0; /* null terminate */</code></p>
      <p><code>45   Fputs(recvline, stdout);</code></p>
      <p><code>46  }</code></p>
      <p><code>47  exit(0);</code></p>
      <p><code>48 }</code></p>
      <subtitle>Вызов функций gethostbyname и getservbyname</subtitle>
      <p><code>13-28</code> Первый аргумент командной строки — это имя узла, передаваемое в качестве аргумента функции <code>gethostbyname</code>, а второй — имя службы, передаваемое в качестве аргумента функции <code>getservbyname</code>. Наш код подразумевает использование протокола TCP, что мы указываем во втором аргументе функции <code>getservbyname</code>. Если функции <code>gethostbyname</code> не удается найти нужное имя, мы вызываем функцию <code>inet_aton</code> (см. раздел 3.6), чтобы проверить, не является ли аргумент командной строки IP-адресом в формате ASCII. В этом случае формируется список из одного элемента — этого IP-адреса.</p>
      <subtitle>Перебор всех адресов</subtitle>
      <p><code>29-35</code> Теперь мы пишем вызовы функций <code>socket</code> и <code>connect</code> в цикле, который выполняется для каждого адреса сервера, пока попытка вызова функции <code>connect</code> не окажется успешной или пока не закончится список серверов. После вызова функции <code>socket</code> мы заполняем структуру адреса сокета Интернета IP-адресом и номером порта сервера. Хотя в целях увеличения производительности мы могли бы вынести из цикла вызов функции <code>bzero</code> и последующие два присваивания, наш код легче читать в таком виде, как он представлен сейчас. Установление соединения с сервером редко является основным источником проблем с производительностью сетевого клиента.</p>
      <subtitle>Вызов функции connect</subtitle>
      <p><code>36-39</code> Вызывается функция <code>connect</code>, и если вызов оказывается успешным, функция <code>break</code> завершает цикл. Если установить соединение не удается, мы выводим сообщение об ошибке и закрываем сокет. Вспомните, что дескриптор, для которого вызов функции <code>connect</code> оказался неудачным, не может больше использоваться и должен быть закрыт.</p>
      <subtitle>Завершение программы</subtitle>
      <p><code>41-42</code> Если цикл завершается, потому что ни один вызов функции <code>connect</code> не закончился успехом, программа завершает работу.</p>
      <subtitle>Чтение ответа сервера</subtitle>
      <p><code>43-47</code> Мы считываем ответ сервера и завершаем программу, когда сервер закрывает соединение.</p>
      <p>Если мы запустим эту программу, указав один из наших узлов, на котором работает сервер времени и даты, мы получим ожидаемый результат:</p>
      <p><code>freebsd % <strong>daytimetcpcli1 aix daytime</strong></code></p>
      <p><code>trying 192.168.42.2:13</code></p>
      <p><code>Sun Jul 27 22:44:19 2003</code></p>
      <p>Но еще интереснее запустить программу, обратившись к маршрутизатору с несколькими сетевыми интерфейсами, на котором не работает сервер времени и даты:</p>
      <p><code>solaris % <strong>daytimetcpcli1 gateway.tuc.noao.edu daytime</strong></code></p>
      <p><code>trying 140.252.108.1:13</code></p>
      <p><code>connect error: Operation timed out</code></p>
      <p><code>trying 140.252.1.4:13</code></p>
      <p><code>connect error: Operation timed out</code></p>
      <p><code>trying 140.252.104.1:13</code></p>
      <p><code>connect error: Connection refused</code></p>
      <p><code>unable to connect</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>11.6. Функция getaddrinfo</p>
     </title>
     <p>Функции <code>gethostbyname</code> и <code>gethostbyaddr</code> поддерживают только IPv4. Интерфейс IPv6 разрабатывался в несколько этапов (история разработки описана в разделе 11.20), и в конечном итоге получилась функция <code>getaddrinfo</code>. Последняя осуществляет трансляцию имен в адреса и служб в порты, причем возвращает она список структур <code>sockaddr</code>, а не список адресов. Такие структуры могут непосредственно использоваться функциями сокетов. Благодаря этому функция <code>getaddrinfo</code> скрывает все различия между протоколами в библиотеке функций. Приложение работает только со структурами адресов сокетов, которые заполняются <code>getaddrinfo</code>. Эта функция определяется стандартом POSIX.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Определение этой функции в POSIX происходит от более раннего предложения Кейта Склоуэра (Keith Sklower) для функции, называемой getconninfo. Эта функция стала результатом обсуждений с Эриком Олменом (Eric Allman), Вилльямом Дастом (William Durst), Майклом Карелсом (Michael Karels) и Стивеном Вайсом (Steven Wise), а также более ранней реализации, написанной Эриком Олменом. Замечание о том, что указания имени узла и имени службы достаточно для соединения с этой службой независимо от деталей протокола, было сделано Маршалом Роузом (Marshall Rose) в проекте X/Open.</p>
     </cite>
     <p><code>#include &lt;netdb.h&gt;</code></p>
     <empty-line/>
     <p><code>int getaddrinfo(const char *<emphasis>hostname</emphasis>, const char *<emphasis>service</emphasis>,</code></p>
     <p><code> const struct addrinfo *<emphasis>hints</emphasis>, struct addrinfo **<emphasis>result</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, ненулевое значение в случае ошибки</emphasis></code></p>
     <p><code><emphasis>(см. табл. 11.2).</emphasis></code></p>
     <p>Через указатель <code>result</code> функция возвращает указатель на связный список структур <code>addrinfo</code>, который задается в заголовочном файле <code>&lt;netdb.h&gt;</code>:</p>
     <p><code>struct addrinfo {</code></p>
     <p><code> int    ai_flags;          /* AI_PASSIVE, AI_CANONNAME */</code></p>
     <p><code> int    ai_family;         /* AF_xxx */</code></p>
     <p><code> int    ai_socktype;       /* SOCK_xxx */</code></p>
     <p><code> int    ai_protocol;       /* 0 или IPPROTO_xxx для IPv4 и IPv6 */</code></p>
     <p><code> size_t ai_addrlen;        /* длина ai_addr */</code></p>
     <p><code> char*  ai_canonname;      /* указатель на каноническое имя узла */</code></p>
     <p><code> struct sockaddr *ai_addr; /* указатель на структуру адреса сокета */</code></p>
     <p><code> struct addrinfo *ai_next; /* указатель на следующую структуру в связном</code></p>
     <p><code>                              списке */</code></p>
     <p><code>};</code></p>
     <p>Переменная <code>hostname</code> — это либо имя узла, либо строка адреса (точечно-десятичная запись для IPv4 или шестнадцатеричная строка для IPv6). Переменная <code>service</code> — это либо имя службы, либо строка, содержащая десятичный номер порта. (См. также упражнение 11.4.)</p>
     <p>Аргумент <code>hints</code> — это либо пустой указатель, либо указатель на структуру <code>addrinfo</code>, заполненную рекомендациями вызывающего процесса о типах информации, которую он хочет получить. Например, если заданная служба предоставляется и для TCP, и для UDP (служба <code>domain</code>, которая ссылается на сервер DNS), вызывающий процесс может присвоить элементу <code>ai_socktype</code> структуры <code>hints</code> значение <code>SOCK_DGRAM</code>. Тогда возвращение информации будет иметь место только для дейтаграммных сокетов.</p>
     <p>Вызывающим процессом могут быть установлены значения следующих элементов структуры <code>hints</code>:</p>
     <p>&#9632; <code>ai_flags</code> (несколько констант <code>AI_XXX</code>, объединенных операцией ИЛИ);</p>
     <p>&#9632; <code>ai_family</code> (значение <code>AF_xxx</code>);</p>
     <p>&#9632; <code>ai_socktype</code> (значение <code>SOCK_xxx</code>);</p>
     <p>&#9632; <code>ai_protocol</code>.</p>
     <p>Поле <code>ai_flags</code> может содержать следующие константы:</p>
     <p>&#9632; <code>AI_PASSIVE</code> указывает, что сокет будет использоваться для пассивного открытия;</p>
     <p>&#9632; <code>AI_CANONNAME</code> указывает функции на необходимость возвратить каноническое имя узла;</p>
     <p>&#9632; <code>AI_NUMERICHOST</code> запрещает преобразование между именами и адресами. Аргумент <code>hostname</code> должен представлять собой строку адреса;</p>
     <p>&#9632; <code>AI_NUMERICSERV</code> запрещает преобразование между именами служб и номерами портов. Аргумент <code>service</code> должен представлять собой строку с десятичным номером порта;</p>
     <p>&#9632; <code>AI_V4MAPPED</code> вместе с <code>ai_family = AF_INET6</code> указывает функции на необходимость вернуть адреса IPv4 из записей А, преобразованные к IPv6, если записи типа AAAA отсутствуют;</p>
     <p>&#9632; <code>AI_ALL</code> при указании вместе с <code>AI_V4MAPPED</code> говорит о необходимости вернуть адреса IPv4, преобразованные к IPv6, вместе с истинными адресами IPv6;</p>
     <p>&#9632; <code>AI_ADDRCONFIG</code> возвращает адреса, относящиеся к заданной версии IP, когда имеется несколько интерфейсов, имеющих IP-адреса другой версии.</p>
     <p>Если аргументом структуры <code>hints</code> является пустой указатель, функция подразумевает нулевое значение для <code>ai_flags</code>, <code>ai_socktype</code> и <code>ai_protocol</code> и значение <code>AF_UNSPEC</code> для <code>ai_family</code>.</p>
     <p>Если функция завершается успешно (0), то в переменную, на которую указывает аргумент <code>result</code>, записывается указатель на список структур <code>addrinfo</code>, связанных через указатель <code>ai_next</code>. Имеется два способа возвращения множественных структур.</p>
     <p>1. Если существует множество адресов, связанных с узлом <code>hostname</code>, то одна структура возвращается для каждого адреса, который может использоваться с запрашиваемым семейством адресов (значение <code>ai_family</code>, если задано).</p>
     <p>2. Если служба предоставляется для множества типов сокетов, то одна структура может быть возвращена для каждого типа сокета в зависимости от <code>ai_socktype</code>. (Заметьте, что большинство реализаций <code>getaddrinfo</code> считают, что номер порта используется только тем типом сокета, который запрашивается в <code>ai_socktype</code>. Если аргумент <code>ai_socktype</code> не определен, функция возвращает ошибку.)</p>
     <p>Например, если структура <code>hints</code> пуста, а вы запрашиваете записи для службы <code>domain</code> на узле с двумя IP-адресами, возвращаются четыре структуры <code>addrinfo</code>:</p>
     <p>&#9632; одна для первого IP-адреса и типа сокета SOCK_STREAM;</p>
     <p>&#9632; одна для первого IP-адреса и типа сокета SOCK_DGRAM;</p>
     <p>&#9632; одна для второго IP-адреса и типа сокета SOCK_STREAM;</p>
     <p>&#9632; одна для второго IP-адреса и типа сокета SOCK_DGRAM.</p>
     <p>Мы показываем схематическое изображение этого примера на рис. 11.3. Не существует никакого гарантированного порядка структур при возвращении множества элементов. Например, мы не можем считать, что службы TCP возвращаются перед службами UDP.</p>
     <image l:href="#img_88.png"/>
     <p><strong>Рис. 11.3</strong>. Пример информации, возвращаемой функцией getaddrinfo</p>
     <empty-line/>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Хотя это и не гарантируется, реализация должна возвращать IP-адреса в том же порядке, в котором они возвращаются DNS. Некоторые распознаватели позволяют администратору указывать порядок сортировки адресов в файле /etc/resolv.conf. Протокол IPv6 определяет правила выбора адресов (RFC 3484 [28]), которые могут влиять на порядок адресов, возвращаемых getaddrinfo.</p>
     </cite>
     <p>Информация, возвращаемая в структурах <code>addrinfo</code>, готова для передачи функциям <code>socket</code> и <code>connect</code> или <code>sendto</code> (для клиента) и <code>bind</code> (для сервера). Аргументы функции <code>socket</code> — это элементы <code>ai_family</code>, <code>ai_socktype</code> и <code>ai_protocol</code>. Второй и третий аргументы функций <code>connect</code> и <code>bind</code> — это элементы <code>ai_addr</code> (указатель на структуру адреса сокета соответствующего типа, заполняемую функцией <code>getaddrinfo</code>) и <code>ai_addrlen</code> (длина этой структуры адреса сокета).</p>
     <p>Если в структуре <code>hints</code> установлен флаг <code>AI_CANONNAME</code>, элемент <code>ai_canonname</code> первой возвращаемой структуры указывает на каноническое имя узла. В терминах DNS это обычно полное доменное имя (FQDN). Программы типа <code>telnet</code> широко используют этот флаг для того, чтобы выводить канонические имена систем, к которым производится подключение. Пользователь может указать короткое имя узла или его альтернативное имя, но он должен знать, с какой системой он в результате соединился.</p>
     <p>На рис. 11.3 представлена возвращаемая информация для следующего вызова:</p>
     <p><code>struct addrinfo hints, *res;</code></p>
     <empty-line/>
     <p><code>bzero(&amp;hints, sizeof(hints));</code></p>
     <p><code>hints.ai_flags = AI_CANONNAME;</code></p>
     <p><code>hints.ai_family = AF_INET;</code></p>
     <empty-line/>
     <p><code>getaddrinfo("bsdi", "domain", &amp;hints, &amp;res);</code></p>
     <p>На этом рисунке все, кроме переменной <code>res</code>, относится к динамически выделяемой памяти (например, с помощью функции <code>malloc</code>). Предполагается, что каноническое имя узла <code>freebsd4</code> — <code>freebsd4.unpbook.com</code>, и что этот узел имеет два адреса IPv4 в DNS.</p>
     <p>Порт 53 предназначен для службы <code>domain</code>, и нужно учитывать, что этот номер порта будет представлен в структурах адресов сокетов в сетевом порядке байтов. Мы приводим возвращаемые значения <code>ai_protocol</code> IPPROTO_TCP и IPPROTO_UDP. Функция <code>getaddrinfo</code> может возвращать значение <code>ai_protocol</code> равное 0 для структур SOCK_STREAM, если этого достаточно для однозначного определения протокола (типа сокета недостаточно, например, если в системе помимо TCP реализован и SCTP), и 0 для структур SOCK_DGRAM, если в системе не реализованы другие протоколы дейтаграмм для IP (на момент написания этой книги стандартизованных протоколов еще не было, но два уже разрабатывались IETF). Лучше всего, если <code>getaddrinfo</code> всегда будет возвращать конкретный тип протокола.</p>
     <p>В табл. 11.1 показано число структур <code>addrinfo</code> для каждого возвращаемого адреса, определяемое на основе заданного имени службы (которое может быть представлено десятичным номером порта) и рекомендации <code>ai_socktype</code>.</p>
     <empty-line/>
     <p><strong>Таблица 11.1</strong>. Число структур addrinfo, возвращаемых для каждого IP-адреса</p>
     <table>
      <tr align="left">
       <th rowspan="2" align="left" valign="top">Элемент ai_socktype</th>
       <th colspan="6" align="left" valign="top">Служба обозначена именем и предоставляется:</th>
       <th rowspan="2" align="left" valign="top">Служба обозначена именем порта</th>
      </tr>
      <tr align="left">
       <th align="left" valign="top">Только TCP</th>
       <th align="left" valign="top">Только UDP</th>
       <th align="left" valign="top">Только SCTP</th>
       <th align="left" valign="top">TCP и UDP</th>
       <th align="left" valign="top">TCP и SCTP</th>
       <th align="left" valign="top">TCP, UDP и SCTP</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">Ошибка</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SOCK_STREAM</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Ошибка</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">2</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SOCK_DGRAM</td>
       <td align="left" valign="top">Ошибка</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Ошибка</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">1</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SOCK_SEQPACKET</td>
       <td align="left" valign="top">Ошибка</td>
       <td align="left" valign="top">Ошибка</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Ошибка</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">1</td>
      </tr>
     </table>
     <p>Более одной структуры <code>addrinfo</code> возвращается для каждого IP-адреса только в том случае, когда поле <code>ai_socktype</code> структуры <code>hints</code> пусто и либо служба поддерживается TCP и UDP (как указано в файле <code>/etc/services</code>), либо задан номер порта для этой службы.</p>
     <p>Если бы мы рассматривали все 64 возможных варианта сочетаний входных данных для функции <code>getaddrinfo</code> (имеется шесть входных переменных), многие сочетания оказались бы недопустимыми, а некоторые не имели бы смысла. Вместо этого рассмотрим наиболее типичные случаи.</p>
     <p>&#9632; Задание <emphasis>имени узла</emphasis> и <emphasis>службы</emphasis>. Это традиционный случай для клиента TCP и UDP. По завершении клиент TCP перебирает в цикле все возвращаемые IP-адреса, вызывая функции <code>socket</code> и <code>connect</code> для каждого из них, пока не установится соединение или пока не будут перебраны все адреса. Мы показываем такой пример с нашей функцией <code>tcp_connect</code> в листинге 11.2.</p>
     <p>Для клиента UDP структура адреса сокета, заполняемая с помощью функции <code>getaddrinfo</code>, будет использоваться в вызове функции <code>sendto</code> или <code>connect</code>. Если клиент сообщит, что первый адрес не работает (ошибка на присоединенном сокете UDP или тайм-аут на неприсоединенном сокете), будет предпринята попытка обратиться к другому адресу.</p>
     <p>Если клиент знает, что он обрабатывает только один тип сокета (например, клиентами Telnet и FTP обрабатываются только сокеты TCP, а клиентами TFTP — только сокеты UDP), то элементу <code>ai_socktype</code> структуры <code>hints</code> должно быть задано соответственно либо значение <code>SOCK_STREAM</code>, либо значение <code>SOCK_DGRAM</code>.</p>
     <p>&#9632; Типичный сервер задает службу (service), но не имя узла (hostname), и задает флаг <code>AI_PASSIVE</code> в структуре <code>hints</code>. Возвращаемая структура адреса сокета должна содержать IP-адрес, равный <code>INADDR_ANY</code> (для IPv4) или <code>IN6ADDR_ANY_INIT</code> (для IPv6). Сервер TCP затем вызывает функции <code>socket</code>, <code>bind</code> и <code>listen</code>. Если сервер хочет разместить в памяти с помощью функции <code>malloc</code> другую структуру адреса сокета, чтобы получить адрес клиента из функции <code>accept</code>, то возвращаемое значение <code>ai_addrlen</code> задает требуемый для этого размер.</p>
     <p>Сервер UDP вызовет функции <code>socket</code>, <code>bind</code> и затем <code>recvfrom</code>. Если сервер хочет разместить в памяти с помощью функции <code>malloc</code> другую структуру адреса сокета, чтобы получить адрес клиента из функции <code>recvfrom</code>, возвращаемое значение <code>ai_addrlen</code> также задает нужный размер.</p>
     <p>Как и в случае типичного клиентского кода, если сервер знает, что он обрабатывает только один тип сокета, то элемент <code>ai_socktype</code> структуры <code>hints</code> должен быть задан либо как <code>SOCK_STREAM</code>, либо как <code>SOCK_DGRAM</code>. Это позволяет избежать возвращения множества структур, с (возможно) неверным значением элемента <code>ai_socktype</code>.</p>
     <p>&#9632; До сих пор мы демонстрировали серверы TCP, создающие один прослушиваемый сокет, и серверы UDP, создающие один сокет дейтаграмм. Это тот вариант, который подразумевался в предыдущем абзаце. Альтернативным устройством является сервер, который обрабатывает множество сокетов с помощью функции <code>select</code>. В этом сценарии сервер должен последовательно перебрать все структуры из списка, возвращаемого функцией <code>getaddrinfo</code>, создать по одному сокету для каждой структуры и вызвать функцию <code>select</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Проблема этой технологии состоит в том, что условие, по которому функция getaddrinfo возвращает множество структур, возникает, когда служба может обрабатываться как протоколом IPv4, так и протоколом IPv6 (см. табл. 11.3). Но эти два протокола не полностью независимы, как мы увидели в разделе 10.2, то есть если мы создаем прослушиваемый сокет IPv6 для данного порта, нет необходимости создавать для него прослушиваемый сокет IPv4, поскольку соединения, приходящие от клиентов IPv4, автоматически обрабатываются стеком протоколов и прослушиваемым сокетом IPv6, при условии, что параметр сокета IPV6_V6ONLY не установлен.</p>
     </cite>
     <p>Невзирая на тот факт, что функция <code>getaddrinfo</code> «лучше», чем функции <code>gethostbyname</code> и <code>gethostbyaddr</code> (помимо того что эта функция упрощает написание кода, не зависящего от протокола, она обрабатывает и имя узла, и имя службы, и к тому же вся возвращаемая ею информация размещается в памяти динамически, а не статически), ее все же не так просто использовать, как это могло показаться. Проблема в том, что нам требуется разместить в памяти структуру <code>hints</code>, инициализировать ее нулем, заполнить необходимые поля, вызвать функцию <code>getaddrinfo</code> и затем пройти весь связный список, проверяя каждый его элемент. В последующих разделах мы предоставим более простые интерфейсы для типичных клиентов TCP и UDP и серверов, которые будем создавать в оставшейся части книги.</p>
     <p>Функция <code>getaddrinfo</code> решает проблему преобразования имен узлов и имен служб в структуры адресов сокетов. В разделе 11.17 мы опишем обратную функцию <code>getnameinfo</code>, которая преобразует структуры адресов сокетов в имена узлов и имена служб.</p>
    </section>
    <section>
     <title>
      <p>11.7. Функция gai_strerror</p>
     </title>
     <p>Ненулевые значения ошибок, возвращаемых функцией <code>getaddrinfo</code>, имеют названия и значения, показанные в табл. 11.2. Функция <code>gai_strerror</code> получает одно из этих значений в качестве аргумента и возвращает указатель на соответствующую текстовую строку с описанием ошибки.</p>
     <p><code>#include &lt;netdb.h&gt;</code></p>
     <empty-line/>
     <p><code>char *gai_strerror(int <emphasis>error</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: указатель на строку с описанием ошибки</emphasis></code></p>
     <empty-line/>
     <p><strong>Таблица 11.2</strong>. Ненулевые возвращаемые значения (константы) ошибок функции getaddrinfo</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Константа</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">EAI_AGAIN</td>
       <td align="left" valign="top">Временный сбой при попытке разрешения имен</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">EAI_BADFLAGS</td>
       <td align="left" valign="top">Недопустимое значение ai_flags</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">EAI_FAIL</td>
       <td align="left" valign="top">Неисправимая ошибка при разрешении имен</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">EAI_FAMILY</td>
       <td align="left" valign="top">Семейство ai_family не поддерживается</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">EAI_MEMORY</td>
       <td align="left" valign="top">Ошибка при выделении памяти</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">EAI_NONAME</td>
       <td align="left" valign="top">Имя узла или имя службы неизвестны или равны NULL</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">EAI_OVERFLOW</td>
       <td align="left" valign="top">Переполнен буфер пользовательских аргументов (только для getnameinfo)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">EAI_SERVICE</td>
       <td align="left" valign="top">Запрошенная служба не поддерживается для данного типа сокета ai_socktype</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">EAI_SOCKTYPE</td>
       <td align="left" valign="top">Тип сокета ai_socktype не поддерживается</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">EAI_SYSTEM</td>
       <td align="left" valign="top">Другая системная ошибка, возвращаемая в переменной errno</td>
      </tr>
     </table>
    </section>
    <section>
     <title>
      <p>11.8. Функция freeaddrinfo</p>
     </title>
     <p>Вся память, занимаемая структурами <code>addrinfo</code>, структурами <code>ai_addr</code> и строкой <code>ai_canonname</code>, которые возвращаются функцией <code>getaddrinfo</code>, динамически выделяется функцией <code>malloc</code>. Эта память освобождается при вызове функции <code>freeaddrinfo</code>.</p>
     <p><code>#include &lt;netdb.h&gt;</code></p>
     <empty-line/>
     <p><code>void freeaddrinfo(struct addrinfo *<emphasis>ai</emphasis>);</code></p>
     <p>Переменная <code>ai</code> должна указывать на первую из структур <code>addrinfo</code>, возвращаемых функцией <code>getaddrinfo</code>. Освобождается вся область памяти, занятая структурами из связного списка, вместе с динамически выделенной областью памяти, содержащей данные, на которые указывают эти структуры (например, структуры адресов сокетов и канонические имена узлов).</p>
     <p>Предположим, что мы вызываем функцию <code>getaddrinfo</code>, проходим последовательно по всему связному списку структур <code>addrinfo</code> и находим нужную структуру. Если далее мы попытаемся сохранить нужную нам информацию простым копированием структуры <code>addrinfo</code>, а затем вызовем функцию <code>freeaddrinfo</code>, мы получим скрытую ошибку. Причина в том, что структура <code>addrinfo</code> сама указывает на динамически выделенный участок памяти (для структуры адреса сокета и, возможно, для канонического имени). Но эта область памяти, на которую указывает сохраненная нами структура, при вызове функции <code>freeaddrinfo</code> освобождается и может использоваться для хранения какой-либо иной информации.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Создание копии только самой структуры addrinfo, а не структур, на которые она, в свою очередь, указывает, называется поверхностным копированием (shallow сору). Копирование структуры addrinfo и всех структур, на которые она указывает, называется детальным копированием (deep сору).</p>
     </cite>
    </section>
    <section>
     <title>
      <p>11.9. Функция getaddrinfo: IPv6</p>
     </title>
     <p>Стандарт POSIX определяет как <code>getaddrinfo</code>, так и возвращаемые этой функцией данные для протоколов IPv4 и IPv6. Отметим следующие моменты, прежде чем свести возвращаемые значения воедино в табл. 11.3.</p>
     <p>&#9632; Входные данные функции <code>getaddrinfo</code> могут относиться к двум различным типам, которые выбираются в зависимости от того, какой тип структуры адреса сокета вызывающий процесс хочет получить обратно и какой тип записей нужно искать в DNS или иной базе данных.</p>
     <p>&#9632; Семейством адресов, указанным вызывающим процессом в структуре <code>hints</code>, задается тип структуры адреса сокета, который вызывающий процесс предполагает получить. Если вызывающий процесс задает <code>AF_INET</code>, функция не должна возвращать структуры <code>sockaddr_in6</code>, а для <code>AF_INET6</code> функция не должна возвращать структур <code>sockaddr_in</code>.</p>
     <p>&#9632; POSIX утверждает, что при задании семейства <code>AF_UNSPEC</code> должны возвращаться адреса, которые могут использоваться с <emphasis>любым</emphasis> семейством протоколов, допускающим применение имени узла и имени службы. Это подразумевает, что если у узла имеются как записи типа AAAA, так и записи типа А, то записи типа AAAA возвращаются как структуры <code>sockaddr_in6</code>, а записи типа A — как структуры <code>sockaddr_in</code>. Нет смысла возвращать еще и записи типа А как адреса IPv4, преобразованные к виду IPv6, в структурах <code>sockaddr_in6</code>, потому что при этом не возвращается никакой дополнительной информации — эти адреса уже возвращены в структурах <code>sockaddr_in</code>.</p>
     <p>&#9632; Это утверждение POSIX также подразумевает, что если флаг <code>AI_PASSIVE</code> задан без имени узла, то должен быть возвращен универсальный адрес IPv6 (<code>IN6ADDR_ANY_INIT</code> или 0::0) в структуре <code>sockaddr_in6</code> вместе с универсальным адресом IPv4 (<code>INADDR_ANY</code> или 0.0.0.0) в структуре <code>sockaddr_in</code>. Также нет смысла возвращать сначала универсальный адрес IPv4, поскольку мы увидим в разделе 12.2, что на узле с двойным стеком сокет сервера IPv6 может обрабатывать и клиенты IPv4, и клиенты IPv6.</p>
     <p>&#9632; Семейство адресов, указанное в поле <code>ai_family</code> структуры <code>hint</code> вместе с флагами <code>AI_V4MAPPED</code> и <code>AI_ALL</code> поля <code>ai_flags</code>, задают тип записей, поиск которых ведется в DNS (тип А или тип AAAA), и тип возвращаемых адресов (IPv4, IPv6 или IPv4, преобразованные к виду IPv6). Мы обобщили это в табл. 11.3.</p>
     <p>&#9632; Имя узла может также быть либо шестнадцатеричной строкой IPv6, либо строкой в точечно-десятичной записи IPv4. Допустимость этой строки зависит от семейства адресов, заданного вызывающим процессом. Шестнадцатеричная строка IPv6 неприемлема, если задано семейство <code>AF_INET</code>, а строка в точечно-десятичной записи IPv4 неприемлема, если задано семейство <code>AF_INET6</code>. Но если задано семейство <code>AF_UNSPEC</code>, то допустимы оба варианта, и при этом возвращается соответствующий тип структуры адреса сокета.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Можно возразить, что если в качестве семейства протоколов задано AF_INET6, строка в точечно-десятичной записи должна возвращаться как адрес IPv4, преобразованный к виду IPv6 в структуре sockaddr_in6. Но другим способом получения этого результата является установка префикса строки с десятичной точкой 0::ffff:.</p>
     </cite>
     <p>В табл. 11.3 показано, как будут обрабатываться адреса IPv4 и IPv6 функцией <code>getaddrinfo</code>. Колонка «Результат» отражает то, что мы хотим возвратить вызывающему процессу, если входные переменные таковы, как показано в первых трех колонках. Колонка «Действия» — то, каким образом мы получаем этот результат.</p>
     <empty-line/>
     <p><strong>Таблица 11.3</strong>. Функция getaddrinfo: ее действия и результаты</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Имя узла, указанное вызывающим процессом</th>
       <th align="left" valign="top">Семейство адресов, указанное вызывающим процессом</th>
       <th align="left" valign="top">Строка с именем узла содержит</th>
       <th align="left" valign="top">Результат</th>
       <th align="left" valign="top">Действия</th>
      </tr>
      <tr align="left">
       <td rowspan="9" align="left" valign="top">Ненулевая строка с именем узла; активное или пассивное открытие</td>
       <td rowspan="3" align="left" valign="top">AF_UNSPEC</td>
       <td align="left" valign="top">Имя узла</td>
       <td align="left" valign="top">Все записи AAAA возвращаются как структуры sockaddr_in6{} и все записи А возвращаются как структуры sockaddr_in{}</td>
       <td align="left" valign="top">Поиск по записям AAAA и поиск по записям A</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Шестнадцатеричная строка</td>
       <td align="left" valign="top">Одна структура sockaddr_in6{}</td>
       <td align="left" valign="top">inet_pton(AF_INET6)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Строка в точечно- десятичной записи</td>
       <td align="left" valign="top">Одна структура sockaddr_in{}</td>
       <td align="left" valign="top">inet_pton(AF_INET)</td>
      </tr>
      <tr align="left">
       <td rowspan="3" align="left" valign="top">AF_INET6</td>
       <td align="left" valign="top">Имя узла</td>
       <td align="left" valign="top">Все записи AAAA возвращаются как структуры sockaddr_in6{} либо все записи А возвращаются как структуры sockaddr_in6{} с адресами IPv4, преобразованными к виду IPv6</td>
       <td align="left" valign="top">Поиск по записям AAAA</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Шестнадцатеричная строка</td>
       <td align="left" valign="top">Одна структура sockaddr_in6{}</td>
       <td align="left" valign="top">inet_pton(AF_INET6)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Строка в точечно-десятичной записи</td>
       <td align="left" valign="top">Ищется как имя узла</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td rowspan="3" align="left" valign="top">AF_INET</td>
       <td align="left" valign="top">Имя узла</td>
       <td align="left" valign="top">Все записи А возвращаются как структуры sockaddr_in{}</td>
       <td align="left" valign="top">Поиск по записям типа A</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Шестнадцатеричная строка</td>
       <td align="left" valign="top">Ошибка: EAI_ADDRFAMILY</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Строка в точечно-десятичной записи</td>
       <td align="left" valign="top">Одна структура sockaddr_in{}</td>
       <td align="left" valign="top">inet_pton(AF_INET)</td>
      </tr>
      <tr align="left">
       <td rowspan="3" align="left" valign="top">Пустая строка с именем узла; пассивное открытие</td>
       <td align="left" valign="top">AF_UNSPEC</td>
       <td align="left" valign="top">Неявный адрес 0::0 Неявный адрес 0.0.0.0</td>
       <td align="left" valign="top">Одна структура sockaddr_in6{} и одна структура sockaddr_in{}</td>
       <td align="left" valign="top">inet_pton(AF_INET6) inet_pton(AF_INET)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">AF_INET6</td>
       <td align="left" valign="top">Неявный адрес 0::0</td>
       <td align="left" valign="top">Одна структура sockaddr_in6{}</td>
       <td align="left" valign="top">inet_pton(AF_INET6)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">AF_INET</td>
       <td align="left" valign="top">Неявный адрес 0.0.0.0</td>
       <td align="left" valign="top">Одна структура sockaddr_in{}</td>
       <td align="left" valign="top">inet_pton(AF_INET)</td>
      </tr>
      <tr align="left">
       <td rowspan="3" align="left" valign="top">Пустая строка с именем узла; активное открытие</td>
       <td align="left" valign="top">AF_UNSPEC</td>
       <td align="left" valign="top">Неявный адрес 0::1 Неявный адрес 127.0.0.1</td>
       <td align="left" valign="top">Одна структура sockaddr_in6{} и одна структура sockaddr_in{}</td>
       <td align="left" valign="top">inet_pton(AF_INET6) inet_pton(AF_INET)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">AF_INET6</td>
       <td align="left" valign="top">Неявный адрес 0::1</td>
       <td align="left" valign="top">Одна структура sockaddr_in6{}</td>
       <td align="left" valign="top">inet_pton(AF_INET6)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">AF_INET</td>
       <td align="left" valign="top">Неявный адрес 127.0.0.1</td>
       <td align="left" valign="top">Одна структура sockaddr_in{}</td>
       <td align="left" valign="top">inet_pton(AF_INET)</td>
      </tr>
     </table>
     <p>Обратите внимание, что табл. 11.3 описывает только обработку адресов IPv4 и IPv6 функцией <code>getaddrinfo</code>, то есть количество и тип адресов, возвращаемых процессу в различных ситуациях. Реальное количество структур <code>addrinfo</code> зависит также от типа сокета и имени службы, о чем уже говорилось в связи с табл. 11.1.</p>
    </section>
    <section>
     <title>
      <p>11.10. Функция getaddrinfo: примеры</p>
     </title>
     <p>Теперь мы покажем некоторые примеры работы функции <code>getaddrinfo</code>, используя тестовую программу, которая позволяет нам вводить все параметры: имя узла, имя службы, семейство адресов, тип сокета и флаги <code>AI_CANONNAME</code> и <code>AI_PASSIVE</code>. (Мы не показываем эту тестовую программу, поскольку она содержит около 350 строк малоинтересного кода. Ее можно получить тем же способом, что и прочие исходные коды для этой книги.) Тестовая программа выдает информацию о переменном числе возвращаемых структур <code>addrinfo</code>, показывая аргументы вызова функции <code>socket</code> и адрес в каждой структуре адреса сокета. Сначала показываем тот же пример, что и на рис. 11.3:</p>
     <p><code>freebsd % <strong>testga -f inet -c -h freebsd4 -s domain</strong></code></p>
     <empty-line/>
     <p><code>socket(AF_INET, SOCK_DGRAM, 17) ai_canonname = freebsd4.unpbook.com</code></p>
     <p><code>      address: 135.197.17.100:53</code></p>
     <p><code>socket(AF_INET, SOCK_DGRAM, 17)</code></p>
     <p><code>      address: 172:24.37.94:53</code></p>
     <p><code>socket(AF_INET, SOCK_STREAM, 6) ai_canonname = freebsd4.unpbook.com</code></p>
     <p><code>      address: 135.197.17.100:53</code></p>
     <p><code>socket(AF_INET, SOCK_STREAM, 6)</code></p>
     <p><code>      address: 172.24.37.94:53</code></p>
     <p>Параметр <code>-f inet</code> задает семейство адресов, -с указывает, что нужно возвратить каноническое имя, <code>-h freebsd4</code> задает имя узла, <code>-s domain</code> задает имя службы.</p>
     <p>Типичный сценарий клиента — задать семейство адресов, тип сокета (параметр <code>-t</code>), имя узла и имя службы. Следующий пример показывает это для узла с несколькими сетевыми интерфейсами с шестью адресами Ipv4:</p>
     <p><code>freebsd % <strong>testga -f inet -t stream -h gateway.tuc.noao.edu -s daytime</strong></code></p>
     <p><code>socket(AF_INET, SOCK_STREAM, 6)</code></p>
     <p><code>      address: 140.252.108.1:13</code></p>
     <empty-line/>
     <p><code>socket(AF_INET, SOCK_STREAM, 6)</code></p>
     <p><code>      address: 140.252.1.4:13</code></p>
     <empty-line/>
     <p><code>socket(AF_INET, SOCK_STREAM, 6)</code></p>
     <p><code>      address: 140.252.104.1:13</code></p>
     <empty-line/>
     <p><code>socket(AF_INET, SOCK_STREAM, 0)</code></p>
     <p><code>      address: 140.252.3.6.13</code></p>
     <empty-line/>
     <p><code>socket(AF_INET, SOCK_STREAM, 0)</code></p>
     <p><code>      address: 140.252.4.100.13</code></p>
     <empty-line/>
     <p><code>socket(AF_INET, SOCK_STREAM, 0)</code></p>
     <p><code>      address: 140.252.1.4.13</code></p>
     <p>Затем мы задаем наш узел <code>aix</code>, у которого имеется и запись типа AAAA, и запись типа А, не указывая семейства адресов. Имя службы — <code>ftp</code>, которая предоставляется только TCP.</p>
     <p><code>freebsd % <strong>testga -h aix -s ftp -t stream</strong></code></p>
     <empty-line/>
     <p><code>socket(AF_NET6, SOCK_STREAM, 6)</code></p>
     <p><code>      address: [3ffe:b80:1f8d:2:204:acff:fe17:bf38]:21</code></p>
     <empty-line/>
     <p><code>socket(AF_INET, SOCK_STREAM, 6)</code></p>
     <p><code>      address: 192.168.42.2:21</code></p>
     <p>Поскольку мы не задали семейство адресов и запустили этот пример на узле, который поддерживает и IPv4, и IPv6, возвращаются две структуры: одна для IPv6 и одна для IPv4.</p>
     <p>Затем мы задаем флаг <code>AI_PASSIVE</code> (параметр <code>-р</code>), не указываем ни семейства адресов, ни имени узла (подразумевая универсальный адрес), задаем номер порта 8888 и не указываем тип сокета.</p>
     <p><code>freebsd % <strong>testga -р -s 8888 -t stream</strong></code></p>
     <empty-line/>
     <p><code>socket(AF_INET6, SOCK_STREAM, 6)</code></p>
     <p><code>address: [::]:8888</code></p>
     <empty-line/>
     <p><code>socket(AF_INET, SOCK_STREAM, 6)</code></p>
     <p><code>address: 0.0.0.0:8888</code></p>
     <p>Возвращаются две структуры. Поскольку мы запустили эту программу на узле, поддерживающем и IPv4, и IPv6, не задав семейства адресов, функция <code>getaddrinfo</code> возвращает универсальный адрес IPv6 и универсальный адрес IPv4. Структура IPv6 возвращается перед структурой IPv4, поскольку, как мы увидим в главе 12, клиент или сервер IPv6 на узле с двойным стеком может взаимодействовать с собеседниками по IPv6 и по IPv4.</p>
    </section>
    <section>
     <title>
      <p>11.11. Функция host_serv</p>
     </title>
     <p>Наш первый интерфейс функции <code>getaddrinfo</code> не требует от вызывающего процесса размещать в памяти структуру рекомендаций и заполнять ее. Вместо этого аргументами нашей функции <code>host_serv</code> будут интересующие нас поля — семейство адресов и тип сокета.</p>
     <p><code>#include "unp.h"</code></p>
     <empty-line/>
     <p><code>struct addrinfo *host_serv(const char *<emphasis>hostname</emphasis>, const char *<emphasis>service</emphasis>, int <emphasis>family</emphasis>, int <emphasis>socktype</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: в случае успешного выполнения указатель на структуру addrinfo. NULL в случае ошибки</emphasis></code></p>
     <p>В листинге 11.3 показан исходный код этой функции.</p>
     <p><strong>Листинг 11.3</strong>. Функция host_serv</p>
     <p><code>//lib/host_serv.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 struct addrinfo*</code></p>
     <p><code> 3 host_serv(const char *host, const char *serv, int family, int socktype)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int n;</code></p>
     <p><code> 6  struct addrinfo hints, *res;</code></p>
     <empty-line/>
     <p><code> 7  bzero(&amp;hints, sizeof(struct addrinfo));</code></p>
     <p><code> 8  hints.ai_flags = AI_CANONNAME; /* всегда возвращает каноническое имя */</code></p>
     <p><code> 9  hints.ai_family = family; /* AF_UNSPEC, AF_INET, AF_INET6, ... */</code></p>
     <p><code>10  hints.ai_socktype = socktype; /* 0, SOCK_STREAM, SOCK_DGRAM, ... */</code></p>
     <empty-line/>
     <p><code>11  if ((n = getaddrinfo(host, serv, &amp;hints, &amp;res)) != 0)</code></p>
     <p><code>12   return (NULL);</code></p>
     <empty-line/>
     <p><code>13  return (res); /* возвращает указатель на первый элемент в связном</code></p>
     <p><code>                     списке */</code></p>
     <p><code>14 }</code></p>
     <p><code>7-13</code> Функция инициализирует структуру рекомендаций (<code>hints</code>), вызывает функцию <code>getaddrinfo</code> и возвращает пустой указатель, если происходит ошибка.</p>
     <p>Мы вызываем эту функцию в листинге 16.11, когда нам нужно использовать <code>getaddrinfo</code> для получения информации об узле и о службе и при этом мы хотим установить соединение самостоятельно.</p>
    </section>
    <section>
     <title>
      <p>11.12. Функция tcp_connect</p>
     </title>
     <section>
      <p>Теперь мы напишем две функции, использующие функцию <code>getaddrinfo</code> для обработки большинства сценариев клиентов и серверов TCP, которые мы создаем. Первая из этих функций, <code>tcp_connect</code>, выполняет обычные шаги клиента: создание сокета TCP и соединение с сервером.</p>
      <p><code>#include "unp.h"</code></p>
      <empty-line/>
      <p><code>int tcp_connect(const char *<emphasis>hostname</emphasis>, const char *<emphasis>service</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: в случае успешного соединения - дескриптор присоединенного сокета, в случае ошибки не возвращается ничего</emphasis></code></p>
      <p>В листинге 11.4 показан исходный код.</p>
      <p><strong>Листинг 11.4</strong>. Функция tcp_connect: выполнение обычных шагов клиента</p>
      <p>/<code>/lib/tcp_connect.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 tcp_connect(const char *host, const char *serv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd, n;</code></p>
      <p><code> 6  struct addrinfo hints, *res, *ressave;</code></p>
      <empty-line/>
      <p><code> 7  bzero(&amp;hints, sizeof(struct addrinfo));</code></p>
      <p><code> 8  hints.ai_family = AF_UNSPEC;</code></p>
      <p><code> 9  hints.ai_socktype = SOCK_STREAM;</code></p>
      <empty-line/>
      <p><code>10  if ((n = getaddrinfo(host, serv, &amp;hints, &amp;res)) != 0)</code></p>
      <p><code>11   err_quit("tcp_connect error for %s, %s: %s",</code></p>
      <p><code>12   host, serv, gai_strerror(n));</code></p>
      <p><code>13  ressave = res;</code></p>
      <empty-line/>
      <p><code>14  do {</code></p>
      <p><code>15   sockfd = socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);</code></p>
      <p><code>16   if (sockfd &lt; 0)</code></p>
      <p><code>17    continue; /* игнорируем этот адрес */</code></p>
      <p><code>18   if (connect(sockfd, res-&gt;ai_addr, res-&gt;ai_addrlen) == 0)</code></p>
      <p><code>19    break; /* успех */</code></p>
      <empty-line/>
      <p><code>20   Close(sockfd); /* игнорируем этот адрес */</code></p>
      <p><code>21  } while ((res = res-&gt;ai_next) != NULL);</code></p>
      <empty-line/>
      <p><code>22  if (res == NULL) /* значение errno устанавливается при</code></p>
      <p><code>                        последней попытке connect() */</code></p>
      <p><code>23   err_sys("tcp_connect error for %s, %s", host, serv);</code></p>
      <empty-line/>
      <p><code>24  freeaddrinfo(ressave);</code></p>
      <empty-line/>
      <p><code>25  return (sockfd);</code></p>
      <p><code>26 }</code></p>
      <subtitle>Вызов функции getaddrinfo</subtitle>
      <p><code>7-13</code> функция <code>getaddrinfo</code> вызывается один раз, когда мы задаем семейство адресов <code>AF_UNSPEC</code> и тип сокета <code>SOCK_STREAM</code>.</p>
      <subtitle>Перебор всех структур addrinfo до успешного выполнения или до окончания списка</subtitle>
      <p><code>14-25</code> Затем пробуется каждый IP-адрес: вызываются функции <code>socket</code> и <code>connect</code>. Если выполнение функции <code>socket</code> неудачно, это не фатальная ошибка, так как такое может случиться, если был возвращен адрес IPv6, а ядро узла не поддерживает IPv6. Если выполнение функции <code>connect</code> успешно, выполняется функция <code>break</code> для выхода из цикла. В противном случае, после того как перепробованы все адреса, цикл также завершается. Функция <code>freeaddrinfo</code> освобождает всю динамически выделенную память.</p>
      <p>Эта функция (как и другие наши функции, предоставляющие более простой интерфейс для функции <code>getaddrinfo</code> в следующих разделах) завершается, если либо оказывается неудачным вызов функции <code>getaddrinfo</code>, либо вызов функции <code>connect</code> не выполняется успешно. Возвращение из нашей функции возможно лишь в случае успешного выполнения. Было бы сложно возвратить код ошибки (одну из констант <code>EAI_<emphasis>xxx</emphasis></code>), не добавляя еще одного аргумента. Это значит, что наша функция-обертка тривиальна:</p>
      <p><code>Tcp_connect(const char *host, const char *serv) {</code></p>
      <p><code> return(tcp_connect(host, serv));</code></p>
      <p><code>}</code></p>
      <p>Тем не менее мы по-прежнему вызываем функцию-обертку вместо функции <code>tcp_connect</code> ради сохранения единообразия в оставшейся части книги.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Проблема с возвращаемым значением заключается в том, что дескрипторы неотрицательные, но мы не знаем, положительны или отрицательны значения EAI_xxx. Если бы эти значения были положительными, мы могли бы возвратить равные им по абсолютной величине отрицательные значения, когда вызов функции getaddrinfo окажется неудачным. Но мы также должны возвратить некое другое отрицательное значение, чтобы указать, что все структуры были перепробованы безуспешно.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Пример: клиент времени и даты</p>
      </title>
      <p>В листинге 11.5 показан наш клиент времени и даты из листинга 1.1, переписанный с использованием функции <code>tcp_connect</code>.</p>
      <p><strong>Листинг 11.5</strong>. Клиент времени и даты, переписанный с использованием функции tcp_connect</p>
      <p><code>//names/daytimetcpcli.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd, n;</code></p>
      <p><code> 6  char recvline[MAXLINE + 1];</code></p>
      <p><code> 7  socklen_t len;</code></p>
      <p><code> 8  struct sockaddr_storage *ss;</code></p>
      <empty-line/>
      <p><code> 9  if (argc != 3)</code></p>
      <p><code>10   err_quit</code></p>
      <p><code>11    ("usage, daytimetcpcli &lt;hostname/IPaddress&gt; &lt;service/port#&gt;");</code></p>
      <empty-line/>
      <p><code>12  sockfd = Tcp_connect(argv[1], argv[2]);</code></p>
      <empty-line/>
      <p><code>13  len = sizeof(ss);</code></p>
      <p><code>14  Getpeername(sockfd, (SA*)&amp;ss, &amp;len);</code></p>
      <p><code>15  printf("connected to %s\n", Sock_ntop_host((SA*)&amp;ss, len));</code></p>
      <empty-line/>
      <p><code>16  while ((n = Read(sockfd, recvline, MAXLINE)) &gt; 0) {</code></p>
      <p><code>17   recvline[n] = 0; /* завершающий нуль */</code></p>
      <p><code>18   Fputs(recvline, stdout);</code></p>
      <p><code>19  }</code></p>
      <p><code>20  exit(0);</code></p>
      <p><code>21 }</code></p>
      <subtitle>Аргументы командной строки</subtitle>
      <p><code>9-11</code> Теперь нам требуется второй аргумент командной строки для задания либо имени службы, либо номера порта, что позволит нашей программе соединяться с другими портами.</p>
      <subtitle>Соединение с сервером</subtitle>
      <p><code>12</code> Теперь весь код сокета для этого клиента выполняется функцией <code>tcp_connect</code>.</p>
      <subtitle>Вывод ответа сервера</subtitle>
      <p><code>13-15</code> Мы вызываем функцию <code>getpeername</code>, чтобы получить адрес протокола сервера и вывести его. Мы делаем это для проверки протокола, используемого в примерах, которые скоро покажем.</p>
      <p>Обратите внимание, что функция <code>tcp_connect</code> не возвращает размера структуры адреса сокета, который использовался для функции <code>connect</code>. Мы могли добавить еще один аргумент-указатель, чтобы получить это значение, но при создании этой функции мы стремились добиться меньшего числа аргументов, чем у функции <code>getaddrinfo</code>. Поэтому мы определяем константу <code>MAXSOCKADDR</code> в нашем заголовке <code>unp.h</code> так, чтобы ее размер равнялся размеру наибольшей структуры адреса сокета. Обычно это размер структуры адреса доменного сокета Unix (см. раздел 14.2), немного более 100 байт. Мы выделяем в памяти пространство для структуры указанного размера и заполняем ее с помощью функции <code>getpeername</code>.</p>
      <p>Эта версия нашего клиента работает и с IPv4, и с IPv6, тогда как версия, представленная в листинге 1.1, работала только с IPv4, а версия из листинга 1.2 — только с IPv6. Сравните нашу новую версию с представленной в листинге Д.6, которую мы написали, чтобы использовать функции <code>gethostbyname</code> и <code>getservbyname</code> для поддержки и IPv4, и IPv6.</p>
      <p>Сначала мы задаем имя узла, поддерживающего только IPv4:</p>
      <p><code>freebsd % <strong>daytimetcpcli linux daytime</strong></code></p>
      <p><code>connected to 206 168.112.96</code></p>
      <p><code>Sun Jul 27 23:06:24 2003</code></p>
      <p>Затем мы задаем имя узла, поддерживающего и IPv4, и IPv6:</p>
      <p><code>freebsd % <strong>daytimetcpcli aix daytime</strong></code></p>
      <p><code>connected to 3ffe:b80:1f8d:2:204:acff:fe17:bf38</code></p>
      <p><code>Sun Jul 27 23:17:13 2003</code></p>
      <p>Используется адрес IPv6, поскольку у узла имеется и запись типа AAAA, и запись типа А. Кроме того, функция <code>tcp_connect</code> устанавливает семейство адресов <code>AF_UNSPEC</code>, поэтому, как было отмечено в табл. 11.3, сначала идет поиск записей типа AAAA, и только если этот поиск неудачен, выполняется поиск записей типа А.</p>
      <p>В следующем примере мы указываем на необходимость использования именно адреса IPv4, задавая имя узла с суффиксом <code>-4</code>, что, как мы отмечали в разделе 11.2, в соответствии с принятым нами соглашением означает имя узла, который поддерживает только записи типа А:</p>
      <p><code>freebsd % <strong>daytimetcpcli aix-4 daytime</strong></code></p>
      <p><code>connected to 192.168.42.2</code></p>
      <p><code>Sun Jul 27 23:17:48 2003</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>11.13. Функция tcp_listen</p>
     </title>
     <section>
      <p>Наша следующая функция, <code>tcp_listen</code>, выполняет обычные шаги сервера TCP: создание сокета TCP, связывание его с заранее известным портом с помощью функции bind и разрешение приема входящих запросов через соединение. В листинге 11.6 представлен исходный код.</p>
      <p><code>#include "unp.h"</code></p>
      <empty-line/>
      <p><code>int tcp_listen(const char *<emphasis>hostname</emphasis>, const char *<emphasis>service</emphasis>, socklen_t *<emphasis>lenptr</emphasis>);</code></p>
      <p><code><emphasis>В случае успешного выполнения возвращает дескриптор присоединенного сокета, в случае ошибки не возвращает ничего</emphasis></code></p>
      <p><strong>Листинг 11.6</strong>. Функция tcp_listen: выполнение обычных шагов сервера TCP</p>
      <p><code>//lib/tcp_listen.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 tcp_listen(const char *host, const char *serv, socklen_t *addrlenp)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int listenfd, n;</code></p>
      <p><code> 6  const int on = 1;</code></p>
      <p><code> 7  struct addrinfo hints, *res, *ressave;</code></p>
      <empty-line/>
      <p><code> 8  bzero(&amp;hints, sizeof(struct addrinfo));</code></p>
      <p><code> 9  hints.ai_flags = AI_PASSIVE;</code></p>
      <p><code>10  hints.ai_family = AF_UNSPEC;</code></p>
      <p><code>11  hints.ai_socktype = SOCK_STREAM;</code></p>
      <empty-line/>
      <p><code>12  if ((n = getaddrinfo(host, serv, &amp;hints, &amp;res)) != 0)</code></p>
      <p><code>13   err_quit("tcp_listen error for %s, %s: %s",</code></p>
      <p><code>14    host, serv, gai_strerror(n));</code></p>
      <p><code>15  ressave = res;</code></p>
      <empty-line/>
      <p><code>16  do {</code></p>
      <p><code>17   listenfd =</code></p>
      <p><code>18    socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);</code></p>
      <p><code>19   if (listenfd &lt; 0)</code></p>
      <p><code>20    continue; /* ошибка, пробуем следующий адрес */</code></p>
      <p><code>21   Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
      <p><code>22   if (bind(listenfd, res-&gt;ai_addr, res-&gt;ai_addrlen) == 0)</code></p>
      <p><code>23    break; /* успех */</code></p>
      <empty-line/>
      <p><code>24   Close(listenfd); /* ошибка при вызове функции bind, закрываем</code></p>
      <p><code>                         сокет и пробуем следующий адрес*/</code></p>
      <p><code>25  } while ((res = res-&gt;ai_next) != NULL);</code></p>
      <empty-line/>
      <p><code>26  if (res == NULL) /* значение errno устанавливается при последнем</code></p>
      <p><code>                        вызове функции socket() или bind() */</code></p>
      <p><code>27   err_sys("tcp_listen error for %s, %s", host, serv);</code></p>
      <empty-line/>
      <p><code>28  Listen(listenfd, LISTENQ);</code></p>
      <empty-line/>
      <p><code>29  if (addrlenp)</code></p>
      <p><code>30   *addrlenp = res-&gt;ai_addrlen; /* возвращает размер адреса протокола */</code></p>
      <empty-line/>
      <p><code>31  freeaddrinfo(ressave);</code></p>
      <empty-line/>
      <p><code>32  return (listenfd);</code></p>
      <p><code>33 }</code></p>
      <subtitle>Вызов функции getaddrinfo</subtitle>
      <p><code>8-15</code> Мы инициализируем структуру <code>addrinfo</code> с учетом следующих рекомендаций (элементов структуры <code>hints</code>): <code>AI_PASSIVE</code>, поскольку это функция для сервера, <code>AF_UNSPEC</code> для семейства адресов и <code>SOCK_STREAM</code>. Вспомните табл. 11.3: если имя узла не задано (что вполне нормально для сервера, который хочет связать с дескриптором универсальный адрес), то наличие значений <code>AI_PASSIVE</code> и <code>AF_UNSPEC</code> вызовет возвращение двух структур адреса сокета: первой для IPv6 и второй для IPv4 (в предположении, что это узел с двойным стеком).</p>
      <subtitle>Создание сокета и связывание с адресом</subtitle>
      <p><code>16-24</code> Вызываются функции <code>socket</code> и <code>bind</code>. Если любой из вызовов окажется неудачным, мы просто игнорируем данную структуру <code>addrinfo</code> и переходим к следующей. Как было сказано в разделе 7.5, для сервера TCP мы всегда устанавливаем параметр сокета <code>SO_REUSEADDR</code>.</p>
      <subtitle>Проверка на наличие ошибки</subtitle>
      <p><code>25-26</code> Если все вызовы функций <code>socket</code> и <code>bind</code> окажутся неудачными, мы сообщаем об ошибке и завершаем выполнение. Как и в случае с нашей функцией <code>tcp_connect</code> из предыдущего раздела, мы не пытаемся возвратить ошибку из этой функции.</p>
      <p><code>27</code> Сокет превращается в прослушиваемый сокет с помощью функции <code>listen</code>.</p>
      <subtitle>Возвращение размера структуры адреса</subtitle>
      <p><code>28-31</code> Если аргумент <code>addrlenp</code> является непустым указателем, мы возвращаем размер адресов протокола через этот указатель. Это позволяет вызывающему процессу выделять память для структуры адреса сокета, чтобы получить адрес протокола клиента из функции accept (см. также упражнение 11.7).</p>
     </section>
     <section>
      <title>
       <p>Пример: сервер времени и даты</p>
      </title>
      <p>В листинге 11.7 показан наш сервер времени и даты из листинга 4.2, переписанный с использованием функции <code>tcp_listen</code>.</p>
      <p><strong>Листинг 11.7</strong>. Сервер времени и даты, переписанный с использованием функции tcp_listen</p>
      <p><code>//names/daytimetcpsrv1.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;time.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 main(int argc, char **argv)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int listenfd, connfd;</code></p>
      <p><code> 7  socklen_t addrlen, len;</code></p>
      <p><code> 8  char = buff[MAXLINE];</code></p>
      <p><code> 9  time_t ticks;</code></p>
      <p><code>10  struct sockaddr_storage cliaddr;</code></p>
      <empty-line/>
      <p><code>11  if (argc != 2)</code></p>
      <p><code>12   err_quit("usage: daytimetcpsrv1 &lt;service or port#&gt;");</code></p>
      <empty-line/>
      <p><code>13  listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
      <empty-line/>
      <p><code>14  for (;;) {</code></p>
      <p><code>15   len = sizeof(cliaddr);</code></p>
      <p><code>16   connfd = Accept(listenfd, (SA*)&amp;cliaddr, &amp;len);</code></p>
      <p><code>17   printf("connection from %s\n", Sock_ntop((SA*)&amp;cliaddr, len));</code></p>
      <empty-line/>
      <p><code>18   ticks = time(NULL);</code></p>
      <p><code>19   snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&amp;ticks));</code></p>
      <p><code>20   Write(connfd, buff, strlen(buff));</code></p>
      <empty-line/>
      <p><code>21   Close(connfd);</code></p>
      <p><code>22  }</code></p>
      <p><code>23 }</code></p>
      <subtitle>Ввод имени службы или номера порта в качестве аргумента командной строки</subtitle>
      <p><code>11-12</code> Нам нужно использовать аргумент командной строки, чтобы задать либо имя службы, либо номер порта. Это упрощает проверку нашего сервера, поскольку связывание с портом 13 для сервера времени и даты требует прав привилегированного пользователя.</p>
      <subtitle>Создание прослушиваемого сокета</subtitle>
      <p><code>13</code> Функция <code>tcp_listen</code> создает прослушиваемый сокет. В качестве третьего аргумента мы передаем нулевой указатель, потому что нам безразличен размер структуры адреса, используемого данным семейством: мы будем работать со структурой <code>sockaddr_storage</code>.</p>
      <subtitle>Цикл сервера</subtitle>
      <p><code>14-22</code> Функция <code>accept</code> ждет соединения с клиентом. Мы выводим адрес клиента, вызывая функцию <code>sock_ntop</code>. В случае IPv4 или IPv6 эта функция выводит IP-адрес и номер порта. Мы могли бы использовать функцию <code>getnameinfo</code> (описанную в разделе 11.17), чтобы попытаться получить имя узла клиента, но это подразумевает запрос PTR в DNS, что может занять некоторое время, особенно если запрос PTR окажется неудачным. В разделе 14.8 [112] упоминается, что на занятом веб-сервере почти у 25% всех клиентов, соединяющихся с этим сервером, в DNS нет записей типа PTR. Поскольку мы не хотим, чтобы наш сервер (особенно последовательный сервер) в течение нескольких секунд ждал запрос PTR, мы просто выводим IP-адрес и порт.</p>
     </section>
     <section>
      <title>
       <p>Пример: сервер времени и даты с указанием протокола</p>
      </title>
      <p>В листинге 11.7 есть небольшая проблема: первый аргумент функции <code>tcp_listen</code> — пустой указатель, объединенный с семейством адресов <code>AF_UNSPEC</code>, который задает функция <code>tcp_listen</code>, — может заставить функцию <code>getaddrinfo</code> возвратить структуру адреса сокета с семейством адресов, отличным от желаемого. Например, первой на узле с двойным стеком будет возвращена структура адреса сокета для IPv6 (см. табл. 11.3), но, возможно, нам требуется, чтобы наш сервер обрабатывал только IPv4.</p>
      <p>У клиентов такой проблемы нет, поскольку клиент должен всегда задавать либо IP-адрес, либо имя узла. Клиентские приложения обычно позволяют пользователю вводить этот параметр как аргумент командной строки. Это дает нам возможность задавать имя узла, связанное с определенным типом IP-адреса (вспомните наши имена узлов -4 и -6 в разделе 11.2), или же задавать либо строку в точечно-десятичной записи (для IPv4), либо шестнадцатеричную строку (для IPv6).</p>
      <p>И для серверов существует простая методика, позволяющая нам указать, какой именно протокол следует использовать — IPv4 или IPv6. Для этого нужно позволить пользователю ввести либо IP-адрес, либо имя узла в качестве аргумента командной строки и передать его функции <code>getaddrinfo</code>. В случае IP-адреса строка точечно-десятичной записи IPv4 отличается от шестнадцатеричной строки IPv6. Следующие вызовы функции <code>inet_pton</code> оказываются либо успешными либо нет, как это показано в данном случае:</p>
      <p><code>inet_pton(AF_INET,  "0.0.0.0", &amp;foo); /* успешно */</code></p>
      <p><code>inet_pton(AF_INET,  "0::0",    &amp;foo); /* неудачно*/</code></p>
      <p><code>inet_pton(AF_INET6, "0.0.0.0", &amp;foo); /* неудачно */</code></p>
      <p><code>inet_pton(AF_INET6, "0::0",    &amp;foo); /* успешно */</code></p>
      <p>Следовательно, если мы изменим наши серверы таким образом, чтобы они получали дополнительный аргумент, то при вводе</p>
      <p><code>% <strong>server</strong></code></p>
      <p>по умолчанию мы получим IPv6 на узле с двойным стеком, но при вводе</p>
      <p><code>% <strong>server 0.0.0.0</strong></code></p>
      <p>явно задается IPv4, а при вводе</p>
      <p><code>% <strong>server 0::0</strong></code></p>
      <p>явно задается IPv6.</p>
      <p>В листинге 11.8 показана окончательная версия нашего сервера времени и даты.</p>
      <p><strong>Листинг 11.8</strong>. Не зависящий от протокола сервер времени и даты, использующий функцию tcp_listen</p>
      <p><code>names/daytimetcpsrv2.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;time.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 main(int argc, char **argv)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int listenfd, connfd;</code></p>
      <p><code> 7  socklen_t addrlen, len;</code></p>
      <p><code> 8  struct sockaddr_storage cliaddr;</code></p>
      <p><code> 9  char buff[MAXLINE];</code></p>
      <p><code>10  time_t ticks;</code></p>
      <empty-line/>
      <p><code>11  if (argc == 2)</code></p>
      <p><code>12   listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
      <p><code>13  else if (argc == 3)</code></p>
      <p><code>14   listenfd = Tcp_listen(argv[1], argv[2], &amp;addrlen);</code></p>
      <p><code>15  else</code></p>
      <p><code>16   err_quit("usage; daytimetcpsrv2 [ &lt;host&gt; ] &lt;service or port&gt;");</code></p>
      <empty-line/>
      <p><code>17  for (;;) {</code></p>
      <p><code>18   len = sizeof(cliaddr);</code></p>
      <p><code>19   connfd = Accept(listenfd, (SA*)&amp;cliaddr, &amp;len);</code></p>
      <p><code>20   printf("connection from %s\n", Sock_ntop((SA*)&amp;cliaddr, len));</code></p>
      <empty-line/>
      <p><code>21   ticks = time(NULL);</code></p>
      <p><code>21   snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&amp;ticks));</code></p>
      <p><code>23   Write(connfd, buff, strlen(buff));</code></p>
      <empty-line/>
      <p><code>24   Close(connfd);</code></p>
      <p><code>25  }</code></p>
      <p><code>26 }</code></p>
      <subtitle>Обработка аргументов командной строки</subtitle>
      <p><code>11-16</code> Единственное изменение по сравнению с листингом 11.6 — это обработка аргументов командной строки, позволяющая пользователю в дополнение к имени службы или порту задавать либо имя узла, либо IP-адрес для связывания с сервером.</p>
      <p>Сначала мы запускаем этот сервер с сокетом IPv4 и затем соединяемся с сервером от клиентов на двух различных узлах, расположенных в локальной подсети:</p>
      <p><code>freebsd % <strong>daytimetcpsrv2 0.0.0.0 9999</strong></code></p>
      <p><code>connection from 192.168.42.2:32961</code></p>
      <p><code>connection from 192.168.42.2:1389</code></p>
      <p>А теперь мы запустим сервер с сокетом IPv6:</p>
      <p><code>solaris % <strong>daytimetcpsrv2 0::0 9999</strong></code></p>
      <p><code>connection from [3ffe:b80:1f8d:2:204:acff:fe17:bf38]:32964</code></p>
      <p><code>connection from [3ffe:b80:1f8d:2:230:65ff:fe15:caa7]:49601</code></p>
      <p><code>connection from [::ffff:192:168:42:3]:32967</code></p>
      <p><code>connection from [::ffff:192:168:42:3]:49602</code></p>
      <p>Первое соединение — от узла <code>aix</code>, использующего IPv6, а второе — от узла <code>macosx</code>, использующего IPv6. Два следующих соединения — от узлов <code>aix</code> и <code>macosx</code>, но они используют IPv4, а не IPv6. Мы можем определить это, потому что оба адреса клиента, возвращаемые функцией <code>accept</code>, являются адресами IPv4, преобразованными к виду IPv6.</p>
      <p>Мы только что показали, что сервер IPv6, работающий на узле с двойным стеком, может обрабатывать как клиенты IPv4, так и клиенты IPv6. Адреса IPv4-клиента передаются серверу IPv6 как адреса IPv4, преобразованные к виду IPv6, что мы рассматривали в разделе 12.2.</p>
     </section>
    </section>
    <section>
     <title>
      <p>11.14. Функция udp_client</p>
     </title>
     <section>
      <p>Наши функции, предоставляющие более простой интерфейс для функции <code>getaddrinfo</code>, в случае UDP изменяются: в этом разделе мы представляем клиентскую функцию, создающую неприсоединенный сокет UDP, а в следующем — другую функцию, создающую присоединенный сокет UDP.</p>
      <p><code>#include "unp.h"</code></p>
      <empty-line/>
      <p><code>int udp_client(const char *<emphasis>hostname</emphasis>, const char *<emphasis>service</emphasis>,</code></p>
      <p><code> void **<emphasis>saptr</emphasis>, socklen_t *<emphasis>lenp</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: дескриптор неприсоединенного сокета в случае успешного выполнения, в случае ошибки не возвращает ничего</emphasis></code></p>
      <p>Эта функция создает неприсоединенный сокет UDP, возвращая три элемента. Во-первых, возвращаемое значение функции — это дескриптор сокета. Во-вторых, <code>saptr</code> — это адрес указателя (объявляемого вызывающим процессом) на структуру адреса сокета (которая динамически размещается в памяти функцией <code>udp_client</code>), и в этой структуре функция хранит IP-адрес получателя и номер порта для будущих вызовов функции <code>sendto</code>. Размер этой структуры адреса сокета возвращается как значение переменной, на которую указывает <code>lenp</code>. Последний аргумент не может быть пустым указателем (как это допустимо для последнего аргумента функции <code>tcp_listen</code>), поскольку длина структуры адреса сокета требуется в любых вызовах функций <code>sendto</code> и <code>recvfrom</code>.</p>
      <p>В листинге 11.9 показан исходный код для этой функции.</p>
      <p><strong>Листинг 11.9</strong>. Функция udp_client: создание неприсоединенного сокета UDP</p>
      <p><code>//lib/udp_client.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 udp_client(const char *host, const char *serv, void **saptr, socklen_t *lenp)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd, n;</code></p>
      <p><code> 6  struct addrinfo hints, *res, *ressave;</code></p>
      <empty-line/>
      <p><code> 7  bzero(&amp;hints, sizeof(struct addrinfo));</code></p>
      <p><code> 8  hints.ai_family = AF_UNSPEC;</code></p>
      <p><code> 9  hints.ai_socktype = SOCK_DGRAM;</code></p>
      <empty-line/>
      <p><code>10  if ((n = getaddrinfo(host, serv, &amp;hints, &amp;res)) != 0)</code></p>
      <p><code>11   err_quit("udp_client error for %s, %s: %s",</code></p>
      <p><code>12   host, serv, gai_strerror(n));</code></p>
      <p><code>13  ressave = res;</code></p>
      <empty-line/>
      <p><code>14  do {</code></p>
      <p><code>15   sockfd = socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);</code></p>
      <p><code>16   if (sockfd &gt;= 0)</code></p>
      <p><code>17    break; /* успех */</code></p>
      <p><code>18  } while ((res = res-&gt;ai_next) != NULL);</code></p>
      <empty-line/>
      <p><code>19  if (res == NULL) /* значение errno устанавливается при последнем</code></p>
      <p><code>                        вызове функции socket() */</code></p>
      <p><code>20  err_sys("udp_client error for %s, %s", host, serv);</code></p>
      <empty-line/>
      <p><code>21  *saptr = Malloc(res-&gt;ai_addrlen);</code></p>
      <p><code>22  memcpy(*saptr, res-&gt;ai_addr, res-&gt;ai_addrlen);</code></p>
      <p><code>23  *lenp = res-&gt;ai_addrlen;</code></p>
      <empty-line/>
      <p><code>24  freeaddrinfo(ressave);</code></p>
      <empty-line/>
      <p><code>25  return (sockfd);</code></p>
      <p><code>26 }</code></p>
      <p>Функция <code>getaddrinfo</code> преобразует аргументы <code>hostname</code> и <code>service</code>. Создается дейтаграммный сокет. Выделяется память для одной структуры адреса сокета, и структура адреса сокета, соответствующая созданному сокету, копируется в память.</p>
     </section>
     <section>
      <title>
       <p>Пример: не зависящий от протокола UDP-клиент времени и даты</p>
      </title>
      <p>Теперь мы перепишем наш клиент времени и даты, показанный в листинге 11.3, так, чтобы в нем использовалась наша функция <code>udp_client</code>. В листинге 11.10 представлен не зависящий от протокола исходный код.</p>
      <p><strong>Листинг 11.10</strong>. UDP-клиент времени и даты, использующий нашу функцию udp_client</p>
      <p><code>//names/daytimeudpcli1.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd, n;</code></p>
      <p><code> 6  char recvline[MAXLINE + 1];</code></p>
      <p><code> 7  socklen_t salen;</code></p>
      <p><code> 8  struct sockaddr *sa;</code></p>
      <empty-line/>
      <p><code> 9  if (argc != 3)</code></p>
      <p><code>10   err_quit</code></p>
      <p><code>11    ("usage; daytimeudpcli1 &lt;hostname/IPaddress&gt; &lt;service/port#&gt;");</code></p>
      <empty-line/>
      <p><code>12  sockfd = Udp_client(argv[1], argv[2], (void**)&amp;sa, &amp;salen);</code></p>
      <empty-line/>
      <p><code>13  printf("sending to %s\n", Sock_ntop_host(sa, salen));</code></p>
      <empty-line/>
      <p><code>14  Sendto(sockfd, "", 1, 0, sa, salen); /* посылается 1-байтовая</code></p>
      <p><code>                                            дейтаграмма */</code></p>
      <empty-line/>
      <p><code>15  n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);</code></p>
      <p><code>16  recvline[n] = 0; /* завершающий пустой байт */</code></p>
      <p><code>17  Fputs(recvline, stdout);</code></p>
      <empty-line/>
      <p><code>18  exit(0);</code></p>
      <p><code>19 }</code></p>
      <p><code>12-17</code> Мы вызываем нашу функцию <code>udp_client</code> и затем выводим IP-адрес и порт сервера, которому мы отправим нашу дейтаграмму UDP. Мы посылаем однобайтовую дейтаграмму и затем читаем и выводим ответ сервера.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Нам достаточно отправить дейтаграмму, содержащую 0 байт, поскольку ответ сервера времени и даты инициируется самим получением дейтаграммы от клиента, независимо от ее длины и содержания. Но многие реализации SVR4 не допускают нулевой длины дейтаграмм UDP.</p>
      </cite>
      <p>Мы запускаем наш клиент, задавая имя узла с записью типа AAAA и типа А. Поскольку функция <code>getaddrinfo</code> в первую очередь возвращает структуру с записью типа AAAA, создается сокет IPv6:</p>
      <p><code>freebsd % <strong>daytimeudpcli1 aix daytime</strong></code></p>
      <p><code>sending to 3ffe:b80:1f8d:2:204:acff:fe17:bf38</code></p>
      <p><code>Sun Jul 23:21:12 2003</code></p>
      <p>Затем мы задаем адрес того же узла в точечно-десятичной записи, в результате чего создается сокет IPv4:</p>
      <p><code>freebsd % <strong>daytimeudpcli1 192.168.42.2 daytime</strong></code></p>
      <p><code>sending to 192.168.42.2</code></p>
      <p><code>Sun Jul 23:21:40 2003</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>11.15. Функция udp_connect</p>
     </title>
     <p>Наша функция <code>udp_connect</code> создает присоединенный сокет UDP.</p>
     <p><code>#include "unp.h"</code></p>
     <empty-line/>
     <p><code>int udp_connect(const char *<emphasis>hostname</emphasis>, const char *<emphasis>service</emphasis>);</code></p>
     <p><code><emphasis>Возвращает; дескриптор присоединенного сокета в случае успешного выполнения, в случае ошибки ничего не возвращает</emphasis></code></p>
     <p>В случае присоединенного сокета UDP два последних аргумента, которые требовались в функции <code>udp_client</code>, больше не нужны. Вызывающий процесс может вызвать функцию <code>write</code> вместо <code>sendto</code>, таким образом нашей функции не нужно возвращать структуру адреса сокета и ее длину. В листинге 11.11 представлен исходный код.</p>
     <p><strong>Листинг 11.11</strong>. Функция udp_connect: создание присоединенного сокета UDP</p>
     <p><code>//lib/udp_connect.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 udp_connect(const char *host, const char *serv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd, n;</code></p>
     <p><code> 6  struct addrinfo hints, *res, *ressave;</code></p>
     <empty-line/>
     <p><code> 7  bzero(&amp;hints, sizeof(struct addrinfo));</code></p>
     <p><code> 8  hints.ai_family = AF_UNSPEC;</code></p>
     <p><code> 9  hints.ai_socktype = SOCK_DGRAM;</code></p>
     <empty-line/>
     <p><code>10  if ((n = getaddrinfo(host, serv, &amp;hints, &amp;res)) != 0)</code></p>
     <p><code>11   err_quit("udp_connect error for %s, %s: %s",</code></p>
     <p><code>12    host, serv, gai_strerror(n));</code></p>
     <p><code>13  ressave = res;</code></p>
     <empty-line/>
     <p><code>14  do {</code></p>
     <p><code>15   sockfd = socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);</code></p>
     <p><code>16   if (sockfd &lt; 0)</code></p>
     <p><code>17    continue; /* игнорируем этот адрес */</code></p>
     <empty-line/>
     <p><code>18   if (connect(sockfd, res-&gt;ai_addr, res-&gt;ai_addrlen) == 0)</code></p>
     <p><code>19    break; /* успех */</code></p>
     <empty-line/>
     <p><code>20   Close(sockfd); /* игнорируем этот адрес */</code></p>
     <p><code>21  } while ((res = res-&gt;ai_next) != NULL);</code></p>
     <empty-line/>
     <p><code>22  if (res == NULL) /* значение errno устанавливается при</code></p>
     <p><code>                        последнем вызове функции connect() */</code></p>
     <p><code>23  err_sys("udp_connect error for %s, %s", host, serv);</code></p>
     <empty-line/>
     <p><code>24  freeaddrinfo(ressave);</code></p>
     <empty-line/>
     <p><code>25  return (sockfd);</code></p>
     <p><code>26 }</code></p>
     <p>Эта функция почти идентична функции <code>tcp_connect</code>. Однако отличие в том, что при вызове функции <code>connect</code> для сокета UDP ничего не отправляется собеседнику. Если что-то не в порядке (собеседник недоступен или на заданном порте не запущен сервер), вызывающий процесс не обнаружит этого, пока не пошлет собеседнику дейтаграмму.</p>
    </section>
    <section>
     <title>
      <p>11.16. Функция udp_server</p>
     </title>
     <section>
      <p>Наша последняя функция, предоставляющая более простой интерфейс для функции <code>getaddrinfo</code>, — это функция <code>udp_server</code>.</p>
      <p><code>#include "unp.h"</code></p>
      <empty-line/>
      <p><code>int udp_server(const char *<emphasis>hostname</emphasis>, const char *<emphasis>service</emphasis>, socklen_t *<emphasis>lenptr</emphasis>);</code></p>
      <p><code><emphasis>Возвращает; дескриптор неприсоединенного сокета в случае успешного выполнения, в случае ошибки не возвращает ничего</emphasis></code></p>
      <p>Аргументы функции те же, что и для функции <code>tcp_listen</code>: необязательный <code>hostname</code>, обязательный <code>service</code> (для связывания номер порта) и необязательный указатель на переменную, в которой возвращается размер структуры адреса сокета. В листинге 11.12 представлен исходный код.</p>
      <p><strong>Листинг 11.12</strong>. Функция udp_server: создание неприсоединенного сокета для сервера UDP</p>
      <p><code>//lib/udp_server.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 udp_server(const char *host, const char *serv, socklen_t *addrlenp)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd, n;</code></p>
      <p><code> 6  struct addrinfo hints, *res, *ressave;</code></p>
      <empty-line/>
      <p><code> 7  bzero(&amp;hints, sizeof(struct addrinfo));</code></p>
      <p><code> 8  hints.ai_flags = AI_PASSIVE;</code></p>
      <p><code> 9  hints.ai_family = AF_UNSPEC;</code></p>
      <p><code>10  hints.ai_socktype = SOCK_DGRAM;</code></p>
      <empty-line/>
      <p><code>11  if ((n = getaddrinfo(host, serv, &amp;hints, &amp;res)) != 0)</code></p>
      <p><code>12   err_quit("udp_server error for %s, %s: %s",</code></p>
      <p><code>13    host, serv, gai_strerror(n));</code></p>
      <p><code>14  ressave = res;</code></p>
      <empty-line/>
      <p><code>15  do {</code></p>
      <p><code>16   sockfd = socket(res-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);</code></p>
      <p><code>17   if (sockfd &lt; 0)</code></p>
      <p><code>18    continue; /* ошибка, пробуем следующий адрес */</code></p>
      <empty-line/>
      <p><code>19   if (bind(sockfd, res-&gt;ai_addr, res-&gt;ai_addrlen) == 0)</code></p>
      <p><code>20    break; /* успех */</code></p>
      <empty-line/>
      <p><code>21   Close(sockfd); /* ошибка при вызове функции bind, закрываем</code></p>
      <p><code>                       сокет и пробуем следующий адрес */</code></p>
      <p><code>22  } while ((res = res-&gt;ai_next) != NULL);</code></p>
      <empty-line/>
      <p><code>23  if (res == NULL) /* значение errno устанавливается при</code></p>
      <p><code>                        последнем вызове функции socket() or bind() */</code></p>
      <p><code>24   err_sys("udp_server error for %s, %s", host, serv);</code></p>
      <empty-line/>
      <p><code>25  if (addrlenp)</code></p>
      <p><code>26   *addrlenp = res-&gt;ai_addrlen; /* возвращается размер адреса</code></p>
      <p><code>                                     протокола */</code></p>
      <p><code>27  freeaddrinfo(ressave);</code></p>
      <empty-line/>
      <p><code>28  return (sockfd);</code></p>
      <p><code>29 }</code></p>
      <p>Эта функция практически идентична функции <code>tcp_listen</code>, в ней нет только вызова функции <code>listen</code>. Мы устанавливаем семейство адресов <code>AF_UNSPEC</code>, но вызывающий процесс может использовать ту же технологию, которую мы описали при рассмотрении листинга 11.6, чтобы потребовать использование определенного протокола (IPv4 или IPv6).</p>
      <p>Мы не устанавливаем параметр сокета <code>SO_REUSEADDR</code> для сокета UDP, поскольку этот параметр сокета может допустить связывание множества сокетов с одним и тем же портом UDP на узлах, поддерживающих многоадресную передачу, как мы говорили в разделе 7.5. Поскольку у сокета UDP нет аналога состояния TIME_WAIT, свойственного сокетам TCP, нет необходимости устанавливать этот параметр при запуске сервера.</p>
     </section>
     <section>
      <title>
       <p>Пример: не зависящий от протокола UDP-сервер времени и даты</p>
      </title>
      <p>В листинге 11.13 представлен наш сервер времени и даты, полученный путем модификации листинга 11.8 и предназначенный для использования UDP.</p>
      <p><strong>Листинг 11.13</strong>. Не зависящий от протокола UDP-сервер времени и даты</p>
      <p><code>//names/daytimeudpsrv2.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;time.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 main(int argc, char **argv)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int sockfd;</code></p>
      <p><code> 7  ssize_t n;</code></p>
      <p><code> 8  char buff[MAXLINE];</code></p>
      <p><code> 9  time_t ticks;</code></p>
      <p><code>10  socklen_t addrlen, len;</code></p>
      <p><code>11  struct sockaddr_storage cliaddr;</code></p>
      <empty-line/>
      <p><code>12  if (argc == 2)</code></p>
      <p><code>13   sockfd = Udp_server(NULL, argv[1], &amp;addrlen);</code></p>
      <p><code>14  else if (argc == 3)</code></p>
      <p><code>15   sockfd = Udp_server(argv[1], argv[2], &amp;addrlen);</code></p>
      <p><code>16  else</code></p>
      <p><code>17   err_quit("usage: daytimeudpsrv [ &lt;host&gt; ] &lt;service or port&gt;");</code></p>
      <empty-line/>
      <p><code>18  for (;;) {</code></p>
      <p><code>19   len = sizeof(cliaddr);</code></p>
      <p><code>20   n = Recvfrom(sockfd, buff, MAXLINE, 0, (SA*)&amp;cliaddr, &amp;len);</code></p>
      <p><code>21   printf("datagram from %s\n", Sock_ntop((SA*)&amp;cliaddr, len));</code></p>
      <empty-line/>
      <p><code>22   ticks = time(NULL);</code></p>
      <p><code>23   snprintf(buff, sizeof(buff), "% 24s\r\n", ctime(&amp;ticks));</code></p>
      <p><code>24   Sendto(sockfd, buff, strlen(buff), 0, (SA*)&amp;cliaddr, len);</code></p>
      <p><code>25  }</code></p>
      <p><code>26 }</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>11.17. Функция getnameinfo</p>
     </title>
     <p>Эта функция дополняет функцию <code>getaddrinfo</code>: она получает адрес сокета и возвращает одну символьную строку с описанием узла и другую символьную строку с описанием службы. Эта функция предоставляет указанную информацию в не зависящем от протокола виде, то есть вызывающему процессу неважно, какой тип адреса протокола содержится в структуре адреса сокета, поскольку эти подробности обрабатываются функцией.</p>
     <p><code>#include &lt;netdb.h&gt;</code></p>
     <empty-line/>
     <p><code>int getnameinfo(const struct sockaddr *<emphasis>sockaddr</emphasis>, socklen_t <emphasis>addrlen</emphasis>, char *<emphasis>host</emphasis>,</code></p>
     <p><code> size_t <emphasis>hostlen</emphasis>, char *<emphasis>serv</emphasis>, size_t <emphasis>servlen</emphasis>, int <emphasis>flags</emphasis>);</code></p>
     <p><code><emphasis>Возвращает 0 в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Аргумент <code>sockaddr</code> указывает на структуру адреса сокета, содержащую адрес протокола, преобразуемый в строку, удобную для человеческого восприятия, а аргумент <code>addrlen</code> содержит длину этой структуры. Эта структура и ее длина обычно возвращаются любой из следующих функций: <code>accept</code>, <code>recvfrom</code>, <code>getsockname</code> или <code>getpeername</code>.</p>
     <p>Вызывающий процесс выделяет в памяти пространство для двух строк, удобных для человеческого восприятия: аргументы <code>host</code> и <code>hostlen</code> определяют строку, описывающую узел, а аргументы <code>serv</code> и <code>servlen</code> определяют строку, которая описывает службы. Если вызывающему процессу не нужна возвращаемая строка с описанием узла, задается нулевая длина этой строки (<code>hostlen</code>). Аналогично, нулевое значение аргумента <code>servlen</code> означает, что не нужно возвращать информацию о службе.</p>
     <p>Разница между функциями <code>sock_ntop</code> и <code>getnameinfo</code> состоит в том, что первая не задействует DNS, а только возвращает IP-адрес и номер порта. Последняя же обычно пытается получить имя и для узла, и для службы.</p>
     <p>В табл. 11.4 показаны шесть <emphasis>флагов</emphasis>, которые можно задать для изменения действия, выполняемого функцией <code>getnameinfo</code>.</p>
     <empty-line/>
     <p><strong>Таблица 11.4</strong>. Флаги функции getnameinfo</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Константа</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">NI_DGRAM</td>
       <td align="left" valign="top">Дейтаграммный сокет</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">NI_NAMEREQD</td>
       <td align="left" valign="top">Возвращать ошибку, если невозможно получить имя узла по его адресу</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">NI_NOFQDN</td>
       <td align="left" valign="top">Возвращать только ту часть FQDN, которая содержит имя узла</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">NI_NUMERICHOST</td>
       <td align="left" valign="top">Возвращать численное значение адреса вместо имени узла</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">NI_NUMERICSCOPE</td>
       <td align="left" valign="top">Возвращать численное значение идентификатора области</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">NI_NUMERICSERV</td>
       <td align="left" valign="top">Возвращать номер порта вместо имени службы</td>
      </tr>
     </table>
     <p>&#9632; Флаг <code>NI_DGRAM</code> должен быть задан, когда вызывающий процесс знает, что работает с дейтаграммным сокетом. Причина в том, что если функции <code>getnameinfo</code> задать только IP-адрес и номер порта в структуре адреса сокета, она не сможет определить протокол (TCP или UDP). Существует несколько номеров портов, которые в случае TCP задействованы для одной службы, а в случае UDP для совершенно другой. Примером может служить порт 514, используемый службой <code>rsh</code> в TCP и службой <code>syslog</code> в UDP.</p>
     <p>&#9632; Флаг <code>NI_NAMEREQD</code> приводит к возвращению ошибки, если имя узла не может быть разрешено при использовании DNS. Этот флаг может использоваться серверами, которым требуется, чтобы IP-адресу клиента было сопоставлено имя узла. Затем эти серверы получают возвращаемое имя узла, вызывают функцию <code>gethostbyname</code> и проверяют, совпадают ли результаты вызова этих двух функций хотя бы частично.</p>
     <p>&#9632; Флаг <code>NI_NOFQDN</code> вызывает сокращение имени узла, отбрасывая все, что идет после первой точки. Например, если в структуре адреса сокета содержится IP-адрес 192.168.42.2, то функция <code>gethostbyaddr</code> возвратит имя <code>aix.unpbook.com</code>. Но если в функции <code>getnameinfo</code> задан флаг <code>NI_NOFQDN</code>, она возвратит в имени узла только <code>aix</code>.</p>
     <p>&#9632; Флаг <code>NI_NUMERICHOST</code> сообщает функции <code>getnameinfo</code>, что не нужно вызывать DNS (поскольку это занимает некоторое время). Вместо этого возвращается численное представление IP-адреса, вероятно, при помощи вызова функции <code>inet_ntop</code>. Аналогично, флаг <code>NI_NUMERICSERV</code> определяет, что вместо имени службы должен быть возвращен десятичный номер порта. Обычно серверы должны задавать этот флаг, поскольку номера портов клиента, как правило, не имеют соответствующего имени службы — это динамически назначаемые порты. <code>NI_NUMERICSCOPE</code> указывает на необходимость возвращения идентификатора области в численном, а не в текстовом виде.</p>
     <p>Можно объединять несколько флагов путем логического сложения, если их сочетание имеет смысл, например <code>NI_DGRAM</code> и <code>NI_NUMERICHOST</code>.</p>
    </section>
    <section>
     <title>
      <p>11.18. Функции, допускающие повторное вхождение</p>
     </title>
     <p>Функция <code>gethostbyname</code> из раздела 11.3 имеет интересную особенность, которую мы еще не рассматривали: она не допускает повторное вхождение (nonreentrant). Мы еще столкнемся с этой проблемой в главе 23, когда будем обсуждать потоки, но не менее интересно найти решение этой проблемы сейчас, без необходимости обращаться к понятию потоков.</p>
     <p>Сначала посмотрим, как эта функция работает. Если мы изучим ее исходный код (это несложно, поскольку исходный код для всей реализации BIND свободно доступен), то увидим, что обе функции — и <code>gethostbyname</code>, и <code>gethostbyaddr</code> — содержатся в одном файле, который имеет следующий вид:</p>
     <p><code><strong>static</strong> struct hostent host; /* здесь хранится результат */</code></p>
     <empty-line/>
     <p><code>struct hostent*</code></p>
     <p><code>gethostbyname(const char *hostname) {</code></p>
     <p><code> return(gethostbyname2(hostname, family));</code></p>
     <p><code>}</code></p>
     <empty-line/>
     <p><code>struct hostent*</code></p>
     <p><code>gethostbyname2(const char *hostname, int family) {</code></p>
     <p><code> /* вызов функций DNS для запроса А или AAAA */</code></p>
     <empty-line/>
     <p><code> /* заполнение структуры адреса узла */</code></p>
     <p><code> return(&amp;host);</code></p>
     <p><code>}</code></p>
     <empty-line/>
     <p><code>struct hostent*</code></p>
     <p><code>gethostbyaddr(const char *addr, size_t len, int family) {</code></p>
     <p><code> /* вызов функций DNS для запроса PTR в домене in-addr.arpa */</code></p>
     <empty-line/>
     <p><code> /* заполнение структуры адреса узла */</code></p>
     <p><code> return(&amp;host);</code></p>
     <p><code>}</code></p>
     <p>Мы выделили полужирным шрифтом спецификатор класса памяти <code>static</code> итоговой структуры, потому что основная проблема в нем. Тот факт, что эти три функции используют общую переменную <code>host</code>, представляет другую проблему, которую мы обсудим в упражнении 11.1. (Вспомните табл. 11.4.) Функция <code>gethostbyname2</code> появилась в BIND 4.9.4 с добавлением поддержки IPv6. Мы будем игнорировать тот факт, что когда мы вызываем функцию <code>gethostbyname</code>, задействуется функция <code>gethostbyname2</code>, поскольку это не относится к предмету обсуждения.</p>
     <p>Проблема повторного вхождения может возникнуть в нормальном процессе Unix, вызывающем функцию <code>gethostbyname</code> или <code>gethostbyaddr</code> и из управляющего элемента главного потока, и из обработчика сигнала. Когда вызывается обработчик сигнала (допустим, это сигнал <code>SIGALRM</code>, который генерируется раз в секунду), главный поток управляющего элемента процесса временно останавливается и вызывается функция обработки сигнала. Рассмотрим следующую ситуацию:</p>
     <p><code>main() {</code></p>
     <p><code> struct hostent *hptr;</code></p>
     <p><code> ...</code></p>
     <p><code> signal(SIGALRM, sig_alrm);</code></p>
     <p><code> ...</code></p>
     <p><code> hptr = gethostbyname( ... );</code></p>
     <p><code> ...</code></p>
     <p><code>}</code></p>
     <empty-line/>
     <p><code>void</code></p>
     <p><code>sig_alrm(int signo) {</code></p>
     <p><code> struct hostent *hptr;</code></p>
     <p><code> ...</code></p>
     <p><code> hptr = gethostbyname( ... );</code></p>
     <p><code> ...</code></p>
     <p><code>}</code></p>
     <p>Если главный поток управления в момент остановки находится в середине выполнения функции <code>gethostbyname</code> (допустим, функция заполнила переменную <code>host</code> и должна сейчас возвратить управление), а затем обработчик сигналов вызывает функцию <code>gethostbyname</code>, то поскольку в процессе существует только один экземпляр переменной <code>host</code>, эта переменная используется снова. При этом значения переменных, вычисленные при вызове из главного потока управления, заменяются значениями, вычисленными при вызове из обработчика сигнала.</p>
     <p>Если мы посмотрим на функции преобразования имен и адресов, представленные в этой главе и в главе 9, вместе с функциями <code>inet_<emphasis>XXX</emphasis></code> из главы 4, мы заметим следующее:</p>
     <p>&#9632; Функции <code>gethostbyname</code>, <code>gethostbyname2</code>, <code>gethostbyaddr</code>, <code>getservbyname</code> и <code>getservbyport</code> традиционно не допускают повторного вхождения, поскольку все они возвращают указатель на статическую структуру.</p>
     <p>Некоторые реализации, поддерживающие программные потоки (Solaris 2.x), предоставляют версии этих четырех функций, допускающие повторное вхождение, с именами, оканчивающимися суффиксом <code>_r</code>. О них рассказывается в следующем разделе.</p>
     <p>В качестве альтернативы некоторые реализации с поддержкой программных потоков (Digital Unix 4.0 и HP_UX 10.30) предоставляют версии этих функций, допускающие повторное вхождение за счет использования собственных данных программных потоков.</p>
     <p>&#9632; Функции <code>inet_pton</code> и <code>inet_ntop</code> всегда допускают повторное вхождение.</p>
     <p>&#9632; Исторически функция <code>inet_ntoa</code> не допускает повторное вхождение, но некоторые реализации с поддержкой потоков предоставляют версию, допускающую повторное вхождение, которая строится на основе собственных данных потоков.</p>
     <p>&#9632; Функция <code>getaddrinfo</code> допускает повторное вхождение, только если она сама вызывает функции, допускающие повторное вхождение, то есть если она вызывает соответствующую версию функции <code>gethostbyname</code> или <code>getservbyname</code> для имени узла или имени службы. Одной из причин, по которым вся память для результатов ее выполнения выделяется динамически, является возможность повторного вхождения.</p>
     <p>&#9632; Функция <code>getnameinfo</code> допускает повторное вхождение, только если она сама вызывает такие функции, то есть если она вызывает соответствующую версию функции <code>gethostbyaddr</code> для получения имени узла или функции <code>getservbyport</code> для получения имени службы. Обратите внимание, что обе результирующих строки (для имени узла и для имени службы) размещаются в памяти вызывающим процессом, чтобы обеспечить возможность повторного вхождения.</p>
     <p>Похожая проблема возникает с переменной <code>errno</code>. Исторически существовало по одной копии этой целочисленной переменной для каждого процесса. Если процесс выполняет системный вызов, возвращающий ошибку, то в этой переменной хранится целочисленный код ошибки. Например, функция <code>close</code> из стандартной библиотеки языка С может выполнить примерно такую последовательность действий:</p>
     <p>&#9632; поместить аргумент системного вызова (целочисленный дескриптор) в регистр;</p>
     <p>&#9632; поместить значение в другой регистр, указывая, что был сделан системный вызов функции <code>close</code>;</p>
     <p>&#9632; активизировать системный вызов (переключиться на ядро со специальной инструкцией);</p>
     <p>&#9632; проверить значение регистра, чтобы увидеть, что произошла ошибка;</p>
     <p>&#9632; если ошибки нет, возвратить (0);</p>
     <p>&#9632; сохранить значение какого-то другого регистра в переменной <code>errno</code>;</p>
     <p>&#9632; возвратить (-1).</p>
     <p>Прежде всего заметим, что если ошибки не происходит, значение переменной <code>errno</code> не изменяется. Поэтому мы не можем посмотреть значение этой переменной, пока мы не узнаем, что произошла ошибка (обычно на это указывает возвращаемое функцией значение -1).</p>
     <p>Будем считать, что программа проверяет возвращаемое значение функции <code>close</code> и затем выводит значение переменной <code>errno</code>, если произошла ошибка, как в следующем примере:</p>
     <p><code>if (close(fd) &lt; 0) {</code></p>
     <p><code> fprintf(stderr, "close error, errno = $d\n", errno);</code></p>
     <p><code> exit(1);</code></p>
     <p><code>}</code></p>
     <p>Существует небольшой промежуток времени между сохранением кода ошибки в переменной errno в тот момент, когда системный вызов возвращает управление, и выводом этого значения программой. В течение этого промежутка другой программный поток внутри процесса (то есть обработчик сигналов) может изменить значение переменной <code>errno</code>. Если, например, при вызове обработчика сигналов главный поток управления находится между <code>close</code> и <code>fprintf</code> и обработчик сигналов делает какой-то другой системный вызов, возвращающий ошибку (допустим, вызывается функция <code>write</code>), то значение переменной <code>errno</code>, записанное при вызове функции <code>close</code>, заменяется на значение, записанное при вызове функции <code>write</code>.</p>
     <p>При рассмотрении этих двух проблем в связи с обработчиками сигналов одним из решений проблемы с функцией <code>gethostbyname</code> (возвращающей указатель на статическую переменную) будет не вызывать из обработчика сигнала функции, которые не допускают повторное вхождение. Проблемы с переменной <code>errno</code> (одна глобальная переменная, которая может быть изменена обработчиком сигнала) можно избежать, перекодировав обработчик сигнала так, чтобы он сохранял и восстанавливал значение переменной <code>errno</code> следующим образом:</p>
     <p><code>void sig_alrm(int signo) {</code></p>
     <p><code>int errno_save;</code></p>
     <empty-line/>
     <p><code>errno_save = errno; /* сохраняем значение этой переменной</code></p>
     <p><code>                       при вхождении */</code></p>
     <p><code>if (write( ... ) != nbytes)</code></p>
     <p><code> fprintf(stderr, "write error, errno = %d\n", errno);</code></p>
     <p><code> errno = errno_save; /* восстанавливаем значение этой переменной</code></p>
     <p><code>                        при завершении */</code></p>
     <p><code>}</code></p>
     <p>В этом коде мы также вызываем функцию <code>fprintf</code>, стандартную функцию ввода-вывода, из обработчика сигнала. Это еще одна проблема повторного вхождения, поскольку многие версии функций стандартной библиотеки ввода-вывода не допускают повторного вхождения: стандартные функции ввода-вывода не должны вызываться из обработчиков сигналов.</p>
     <p>Мы вернемся к проблеме повторного вхождения в главе 26 и увидим, как проблема с переменной <code>errno</code> решается с помощью потоков. В следующем разделе описываются некоторые версии функций имен узлов, допускающие повторное вхождение.</p>
    </section>
    <section>
     <title>
      <p>11.19. Функции gethostbyname_r и gethostbyaddr_r</p>
     </title>
     <p>Чтобы превратить функцию, не допускающую повторное вхождение, такую как <code>gethostbyname</code>, в повторно входимую, можно воспользоваться двумя способами.</p>
     <p>1. Вместо заполнения и возвращения статической структуры вызывающий процесс размещает структуру в памяти, и функция, допускающая повторное вхождение, заполняет эту структуру. Эта технология используется для перехода от функции <code>gethostbyname</code> (которая не допускает повторное вхождение) к функции <code>gethostbyname_r</code> (которая допускает повторное вхождение). Но это решение усложняется, поскольку помимо того, что вызывающий процесс должен предоставить структуру <code>hostent</code> для заполнения, эта структура также указывает на другую информацию: каноническое имя, массив указателей на псевдонимы, строки псевдонимов, массив указателей на адреса и сами адреса (см., например, рис. 11.2). Вызывающий процесс должен предоставить один большой буфер, используемый для дополнительной информации, и заполняемая структура <code>hostent</code> будет содержать различные указатели на этот буфер. При этом добавляется как минимум три аргумента функции: указатель на заполняемую структуру <code>hostent</code>, указатель на буфер, используемый для всей прочей информации, и размер этого буфера. Требуется также четвертый дополнительный аргумент — указатель на целое число, в котором будет храниться код ошибки, поскольку глобальная целочисленная переменная <code>h_errno</code> больше не может использоваться. (Глобальная целочисленная переменная <code>h_errno</code> создает ту же проблему повторного вхождения, которая описана нами для переменной <code>errno</code>.)</p>
     <p>Эта технология также используется функциями <code>getnameinfo</code> и <code>inet_ntop</code>.</p>
     <p>2. Входящая функция вызывает функцию <code>malloc</code> и динамически выделяет память. Это технология, используемая функцией <code>getaddrinfo</code>. Проблема при таком подходе заключается в том, что приложение, вызывающее эту функцию, должно вызвать также функцию <code>freeaddrinfo</code>, чтобы освободить динамическую память. Если эта функция не вызывается, происходит утечка памяти: каждый раз, когда процесс вызывает функцию, выделяющую память, объем памяти, задействованной процессом, возрастает. Если процесс выполняется в течение длительного времени (что свойственно сетевым серверам), то потребление памяти этим процессом с течением времени неуклонно растет.</p>
     <p>Обсудим функции Solaris 2.x, допускающие повторное вхождение, не используемые для сопоставления имен с адресами, и наоборот (то есть для разрешения имен).</p>
     <p><code>#include &lt;netdb.h&gt;</code></p>
     <empty-line/>
     <p><code>struct hostent *gethostbyname_r(const char *<emphasis>hostname</emphasis>,</code></p>
     <p><code>struct hostent *<emphasis>result</emphasis>, char *<emphasis>buf</emphasis>, int <emphasis>buflen</emphasis>, int *<emphasis>h_errnop</emphasis>);</code></p>
     <p><code>struct hostent *gethostbyaddr_r(const char *<emphasis>addr</emphasis>, int <emphasis>len</emphasis>,</code></p>
     <p><code> int <emphasis>type</emphasis>, struct hostent *<emphasis>result</emphasis>, char *<emphasis>buf</emphasis>, int <emphasis>buflen</emphasis>,</code></p>
     <p><code> int *<emphasis>h_errnop</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают: непустой указатель в случае успешного выполнения, NULL в случае ошибки</emphasis></code></p>
     <p>Для каждой функции требуется четыре дополнительных аргумента. Аргумент <code>result</code> — это структура <code>hostent</code>, размещенная в памяти вызывающим процессом и заполняемая данной функцией. При успешном выполнении функции этот указатель также является возвращаемым значением.</p>
     <p>Аргумент <code>buf</code> — это буфер, размещенный в памяти вызывающим процессом, a <code>buflen</code> — его размер. Буфер будет содержать каноническое имя, массив указателей на псевдонимы, строки псевдонимов, массив указателей на адреса и сами адреса. Все указатели в структуре <code>hostent</code>, на которую указывает <code>result</code>, указывают на этот буфер. Насколько большим должен быть этот буфер? К сожалению, все, что сказано в большинстве руководств, это что-то неопределенное вроде «Буфер должен быть достаточно большим, чтобы содержать все данные, связанные с записью узла». Текущие реализации функции <code>gethostbyname</code> могут возвращать до 35 указателей на альтернативные имена (псевдонимы), до 35 указателей на адреса и использовать буфер размером 8192 байт для хранения альтернативных имен (псевдонимов) и адресов. Поэтому буфер размером 8192 байт можно считать подходящим.</p>
     <p>Если происходит ошибка, код ошибки возвращается через указатель <code>h_errnop</code>, а не через глобальную переменную <code>h_errno</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>К сожалению, проблема повторного вхождения гораздо серьезнее, чем может показаться. Во-первых, не существует стандарта относительно повторного вхождения и функций gethostbyname и gethostbyaddr. POSIX утверждает, что эти две функции не обязаны быть безопасными в многопоточной среде.</p>
      <p>Во-вторых, не существует стандарта для функций _r. В этом разделе (в качестве примера) мы привели две функции _r, предоставляемые Solaris 2.x. В Linux присутствуют аналогичные функции, возвращающие hostent в качестве аргумента типа значение-результат. В Digital Unix и HP-UX имеются версии этих функций с другими аргументами. Первые два аргумента функции gethostbyname_r такие же, как и в версии Solaris, но оставшиеся три аргумента версии Solaris объединены в новую структуру hostent_data (которая должна быть размещена в памяти вызывающим процессом), а указатель на эту структуру — это третий и последний аргумент. Обычные функции gethostbyname и gethostbyaddr в Digital Unix 4.0 и в HP-UX 10.30 допускают повторное вхождение при использовании собственных данных потоков (см. раздел 23.5). Интересный рассказ о разработке функций _r Solaris 2.x содержится в [70].</p>
      <p>Наконец, хотя версия функции gethostbyname, допускающая повторное вхождение, может обеспечить безопасность, когда ее одновременно вызывают несколько различных потоков, это ничего не говорит нам о возможности повторного вхождения для лежащих в ее основе функций распознавателя.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>11.20. Устаревшие функции поиска адресов IPv6</p>
     </title>
     <section>
      <p>В процессе разработки IPv6 интерфейс поиска адресов IPv6 много раз претерпевал серьезные изменения. В какой-то момент интерфейс был сочтен усложненным и недостаточно гибким, так что от него полностью отказались в RFC 2553 [38]. Документ RFC 2553 предлагал собственные функции, которые в RFC 3493 [36] были попросту заменены <code>getaddrinfo</code> и <code>getnameinfo</code>. В этом разделе мы вкратце рассмотрим старые интерфейсы на тот случай, если вам придется переписывать программы, использующие их.</p>
     </section>
     <section>
      <title>
       <p>Константа RES_USE_INET6</p>
      </title>
      <p>Поскольку функция <code>gethostbyname</code> не имеет аргумента для указания нужного семейства адресов (подобного <code>hints.ai_family</code> для <code>getaddrinfo</code>), в первом варианте API использовалась константа <code>RES_USE_INET6</code>, которая должна была добавляться к флагам распознавателя посредством внутреннего интерфейса. Этот API был недостаточно переносимым, поскольку системам, использовавшим альтернативные внутренние интерфейсы распознавателя, приходилось имитировать интерфейс BIND.</p>
      <p>Включение <code>RES_USE_INET6</code> приводило к тому, что функция <code>gethostbyname</code> начинала поиск с записей AAAA, а записи А возвращались только в случае отсутствия первых. Поскольку в структуре <code>hostent</code> есть только одно поле длины адреса, функция <code>gethostbyname</code> могла возвращать адреса только одного типа (либо IPv6, либо IPv4).</p>
      <p>Кроме того, включение <code>RES_USE_INET6</code> приводило к тому, что функция <code>gethostbyname2</code> начинала возвращать адреса IPv4 в преобразованном к IPv6 виде.</p>
     </section>
     <section>
      <title>
       <p>Функция gethostbyname2</p>
      </title>
      <p>Функция <code>gethostbyname2</code> имеет добавочный аргумент, позволяющий задать семейство адресов.</p>
      <p><code>#include &lt;netdb.h&gt;</code></p>
      <empty-line/>
      <p><code>struct hostent *gethostbyname2(const char *<emphasis>hostname</emphasis>, int <emphasis>family</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: непустой указатель в случае успешного выполнения, в случае ошибки возвращает NULL и задает значение переменной h_errno</emphasis></code></p>
      <p>Возвращаемое значение то же, что и у функции <code>gethostbyname</code> — указатель на структуру <code>hostent</code>, и сама эта структура устроена так же. Логика функции зависит от аргумента <code>family</code> и параметра распознавателя <code>RES_USE_INET6</code> (который мы упомянули в конце предыдущего раздела).</p>
     </section>
     <section>
      <title>
       <p>Функция getipnodebyname</p>
      </title>
      <p>Документ RFC 2553 [38] запретил использование <code>RES_USE_INET6</code> и <code>gethostbyname2</code> из-за глобальности флага <code>RES_USE_INET6</code> и желания предоставить больше возможностей по управлению возвращаемыми сведениями. Для решения перечисленных проблем была предложена функция <code>getipnodebyname</code>.</p>
      <p><code>#include &lt;sys/socket.h&gt;</code></p>
      <p><code>#include &lt;netdb.h&gt;</code></p>
      <empty-line/>
      <p><code>struct hostent *getipnodebyname(const char *<emphasis>name</emphasis>, int <emphasis>af</emphasis>,</code></p>
      <p><code> int <emphasis>flags</emphasis>, int *<emphasis>error_num</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: ненулевой указатель в случае успешного завершения, нулевой в случае ошибки</emphasis></code></p>
      <p>Функция возвращает указатель на ту же структуру <code>hostent</code>, которая использовалась <code>gethostbyname</code>. Аргументы <code>af</code> и <code>flags</code> непосредственно соответствуют полям <code>hints.ai_family</code> и <code>hints.ai_flags</code>. Для обеспечения безопасности в многопоточной среде возвращаемое значение выделяется динамически, поэтому его приходится освобождать вызовом <code>freehostent</code>.</p>
      <p><code>#include &lt;netdb.h&gt;</code></p>
      <empty-line/>
      <p><code>void freehostent(struct hostent *<emphasis>ptr</emphasis>);</code></p>
      <p>Функции <code>getipnodebyname</code> и <code>getipnodebyaddr</code> были отменены в RFC 3493 [36], а вместо них было предложено использовать <code>getaddrinfo</code> и <code>getnameinfo</code>.</p>
     </section>
    </section>
    <section>
     <title>
      <p>11.21. Другая информация о сетях</p>
     </title>
     <p>В этой главе мы сфокусировали внимание на именах узлов, IP-адресах, именах и номерах портов служб. Если же обобщить полученную информацию, мы увидим, что существует четыре типа данных (имеющих отношение к сетям), которые могут понадобиться приложению: узлы, сети, протоколы и службы. В большинстве случаев происходит поиск данных, относящихся к узлам (функции <code>gethostbyname</code> и <code>gethostbyaddr</code>), реже — к службам (функции <code>getservbyname</code> и <code>getservbyaddr</code>) и еще реже — к сетям и протоколам.</p>
     <p>Все четыре типа данных могут храниться в файле, и для каждого из четырех типов определены три функции:</p>
     <p>1. Функция <code>get<emphasis>XXX</emphasis>ent</code>, читающая следующую запись в файле, при необходимости открывающая файл.</p>
     <p>2. Функция <code>set<emphasis>XXX</emphasis>ent</code>, которая открывает файл (если он еще не открыт) и переходит к началу файла.</p>
     <p>3. Функция <code>end<emphasis>XXX</emphasis>ent</code>, закрывающая файл.</p>
     <p>Для каждого из четырех типов данных определяется его собственная структура (соответственно, структуры <code>hostent</code>, <code>netent</code>, <code>protoent</code> и <code>servent</code>), что требует включения заголовка <code>&lt;netdb.h&gt;</code>.</p>
     <p>В дополнение к трем функциям <code>get</code>, <code>set</code> и <code>end</code>, которые допускают последовательную обработку файла, для каждого из четырех типов данных предоставляются функции <emphasis>ключевого поиска</emphasis>, или <emphasis>поиска по ключу</emphasis> (<emphasis>keyed lookup</emphasis>). Эти функции последовательно проходят файл (вызывая функцию <code>get<emphasis>XXX</emphasis>ent</code> для чтения каждой строки файла), но вместо того чтобы возвращать каждую строку вызывающему процессу, эти функции ищут элемент, совпадающий с аргументом. Имена функций поиска по ключу имеют вид <code>get<emphasis>XXX</emphasis>by<emphasis>YYY</emphasis></code>. Например, две функции ключевого поиска для информации об узле — это функции <code>gethostbyname</code> (ищет элемент, совпадающий с именем узла) и <code>gethostbyaddr</code> (ищет элемент, совпадающий с IP-адресом). Таблица 11.5 обобщает эту информацию.</p>
     <empty-line/>
     <p><strong>Таблица 11.5</strong>. Четыре типа данных, относящихся к сетям</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Тип данных</th>
       <th align="left" valign="top">Файл</th>
       <th align="left" valign="top">Структура</th>
       <th align="left" valign="top">Функции поиска по ключу</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Узлы</td>
       <td align="left" valign="top">/etc/hosts</td>
       <td align="left" valign="top">Hostent</td>
       <td align="left" valign="top">gethostbyaddr, gethostbyname</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Сети</td>
       <td align="left" valign="top">/etc/networks</td>
       <td align="left" valign="top">Netent</td>
       <td align="left" valign="top">getnetbyaddr, getnetbyname</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Протоколы</td>
       <td align="left" valign="top">/etc/protocols</td>
       <td align="left" valign="top">Protoent</td>
       <td align="left" valign="top">getprotobyname, getprotobynumber</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Службы</td>
       <td align="left" valign="top">/etc/services</td>
       <td align="left" valign="top">Servent</td>
       <td align="left" valign="top">getservbyname, getservbyport</td>
      </tr>
     </table>
     <p>Как это применяется, если используется DNS? Прежде всего, с помощью DNS возможен доступ только к информации об узле и о сети. Информация о протоколе и службах всегда считывается из соответствующего файла. Ранее в этой главе мы отмечали (см. подраздел «Альтернативы DNS»), что в разных реализациях отличаются способы, с помощью которых администратор определяет, что именно использовать для получения информации об узле и сети — DNS или файл.</p>
     <p>Далее, если DNS используется для получения информации об узле и о сети, имеют смысл только функции поиска по ключу. Используя, например, функцию <code>gethostent</code>, не стоит надеяться, что она выполнит последовательный перебор всех записей DNS! Если вызывается функция <code>gethostent</code>, она считывает только информацию об узлах и не использует DNS.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Хотя информацию о сети можно сделать доступной с помощью DNS, очень немногие пользуются этим. На с. 347-348 [1] рассказывается об этой возможности. Однако обычно администраторы создают и обслуживают файл /etc/networks, используемый вместо DNS. Программа netstat с параметром -i использует этот файл, если он есть, и выводит имя каждой сети. Однако бесклассовая адресация (см. раздел А.4) делает эти функции бесполезными, а поскольку они не поддерживают IPv6, новые приложения не должны использовать их.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>11.22. Резюме</p>
     </title>
     <p>Набор функций, вызываемых приложением для преобразования имени узла в IP- адрес и обратно, называется распознавателем. Две функции, <code>gethostbyname</code> и <code>gethostbyaddr</code>, являются типичными точками входа. С переходом на IPv6 и многопоточное программирование полезными становятся <code>getaddrinfo</code> и <code>getnameinfo</code>, способные работать с адресами IPv6 и безопасные в многопоточной среде.</p>
     <p>Для работы с именами служб и номерами портов широко используется функция <code>getservbyname</code>, принимающая имя службы и возвращающая структуру, содержащую номер порта. Преобразование чаще всего осуществляется на основании данных, содержащихся в некотором текстовом файле. Существует возможность сопоставления имен и номеров протоколов, а также имен и номеров сетей, но используется она реже.</p>
     <p>Альтернативой DNS, которую мы не упомянули, является непосредственный вызов функций распознавателя вместо использования функций <code>gethostbyname</code> и <code>gethostbyaddr</code>. Таким способом пользуется, например, программа <code>sendmail</code>, предназначенная для поиска записи типа MX, чего не может сделать функция <code>gethostby<emphasis>XXX</emphasis></code>. У функций распознавателя имена начинаются с <code>res_</code>. Примером такой функции является функция <code>res_init</code>, которую мы описали в разделе 11.4. Описание этих функций и пример вызывающей их программы находятся в главе 15 книги [1]. При вводе в командной строке man <code>resolver</code> должны отобразиться страницы руководства для этих функций.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Измените программу, представленную в листинге 11.1, так, чтобы для каждого возвращаемого адреса вызывалась функция <code>gethostbyaddr</code>, а затем выведите возвращаемое имя <code>h_name</code>. Сначала запустите программу, задав имя узла только с одним IP-адресом, а затем — с несколькими IP-адресами. Что происходит?</p>
     <p>2. Устраните проблему, показанную в предыдущем упражнении.</p>
     <p>3. Запустите программу, показанную в листинге 11.4, задав имя службы <code>chargen</code>.</p>
     <p>4. Запустите программу, показанную в листинге 11.4, задав IP-адрес в точечно- десятичной записи в качестве имени узла. Допускает ли это ваш распознаватель? Измените листинг 11.4, чтобы разрешить IP-адрес в виде строки десятичных чисел с точками в качестве имени узла и строку с десятичным номером порта в качестве имени службы. В каком порядке должно выполняться тестирование IP-адреса для строки в точечно-десятичной записи и для имени?</p>
     <p>5. Измените программу в листинге 11.4 так, чтобы можно было работать либо с IPv4, либо с IPv6.</p>
     <p>6. Измените программу в листинге 8.5 так, чтобы сделать запрос DNS, и сравните возвращаемый IP-адрес со всеми IP-адресами узла получателя, то есть вызовите функцию <code>gethostbyaddr</code>, используя IP-адрес, возвращаемый функцией <code>recvfrom</code>, а затем вызовите <code>gethostbyname</code> для поиска всех IP-адресов для узла.</p>
     <p>7. Измените листинг 11.6, чтобы вызвать функцию <code>getnameinfo</code> вместо функции <code>sock_ntop</code>. Какие флаги вы должны передать функции <code>getnameinfo</code>?</p>
     <p>8. В разделе 7.5 мы обсуждали завладение портом с помощью параметра сокета <code>SO_REUSEADDR</code>. Чтобы увидеть, как это происходит, создайте не зависящий от протокола сервер времени и даты UDP, показанный в листинге 11.13. Запустите один экземпляр сервера в одном окне, свяжите его с универсальным адресом и некоторым портом, который вы выберете. Запустите в другом окне клиент и убедитесь, что этот сервер выполняет обработку клиента (отметьте вызов функции <code>printf</code> на узле сервера). Затем запустите другой экземпляр сервера в другом окне, и на этот раз свяжите его с одним из адресов направленной передачи узла и тем же портом, что и первый сервер. С какой проблемой вы сразу же столкнетесь? Устраните эту проблему и перезапустите второй сервер. Запустите клиент, отправьте дейтаграмму и проверьте, что второй сервер захватил порт первого сервера. Если возможно, запустите второй сервер снова с учетной записью, отличной от учетной записи первого сервера, чтобы проверить, происходит ли по-прежнему захват порта, поскольку некоторые производители не допускают второго связывания, если идентификатор пользователя отличен от идентификатора процесса, уже связанного с портом.</p>
     <p>9. В конце раздела 2.12 мы показали два примера Telnet: сервер времени и даты и эхо-сервер. Зная, что клиент проходит через два этапа — функцию <code>gethostbyname</code> и функцию connect, определите, к каким этапам относятся строки вывода клиента.</p>
     <p>10. Функции <code>getnameinfo</code> может потребоваться длительное время (до 80 с) на возвращение ошибки, если для IP-адреса не может быть найдено имя узла. Напишите новую функцию <code>getnameinfo_timeo</code>, которая получает дополнительный целочисленный аргумент, задающий максимальную длительность ожидания ответа в секундах. Если время таймера истекает и флаг <code>NI_NAMEREQD</code> не задан, вызовите функцию <code>inet_ntop</code> и возвратите строку адреса.</p>
    </section>
   </section>
  </section>
  <section>
   <title>
    <p>Часть 3</p>
    <p>Дополнительные возможности сокетов</p>
   </title>
   <section>
    <title>
     <p>Глава 12</p>
     <p>Совместимость IPv4 и IPv6</p>
    </title>
    <section>
     <title>
      <p>12.1. Введение</p>
     </title>
     <p>В течение ближайших лет, возможно, произойдет постепенный переход Интернета с IPv4 на IPv6. Во время этого переходного периода важно, чтобы существующие приложения IPv4 продолжали работать с более новыми приложениями IPv6. Например, производитель не может предложить клиент Telnet, работающий только с серверами IPv6, — он должен предоставить и клиент для серверов IPv4, и клиент для серверов IPv6. Мы бы предпочли обойтись одним Telnet-клиентом IPv6, способным работать с серверами и IPv4, и IPv6, и одним сервером Telnet, который работал бы с клиентами и IPv4, и IPv6. В этой главе мы увидим, как это сделать.</p>
     <p>В этой главе мы предполагаем, что на узлах работают <emphasis>двойные стеки протоколов</emphasis> (<emphasis>dual stacks</emphasis>), то есть набор протоколов IPv4 и набор протоколов IPv6. На рис. 2.1 представлен узел с двойным стеком. Возможно, узлы и маршрутизаторы будут работать подобным образом в течение многих лет в процессе перехода к IPv6. В какой-то момент многие системы смогут отключить свои стеки IPv4, но только с течением времени можно будет сказать, когда это произойдет, да и произойдет ли вообще.</p>
     <p>В этой главе мы обсудим, каким образом приложения IPv4 и IPv6 могут взаимодействовать друг с другом. Существует четыре комбинации клиентов и серверов, использующих либо IPv4, либо IPv6, что показано в табл. 12.1.</p>
     <empty-line/>
     <p><strong>Таблица 12.1</strong>. Сочетания клиентов и серверов, использующих IPv4 или IPv6</p>
     <table>
      <tr align="left">
       <th align="left" valign="top"/>
       <th align="left" valign="top">Сервер IPv4</th>
       <th align="left" valign="top">Сервер IPv6</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Клиент IPv4 и серверы</td>
       <td align="left" valign="top">Почти все существующие клиенты</td>
       <td align="left" valign="top">Обсуждается в разделе 12.2</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Клиент IPv6</td>
       <td align="left" valign="top">Обсуждается в разделе 12.3</td>
       <td align="left" valign="top">Простые модификации большинства существующих клиентов (например, клиент из листинга 1.1 модифицируется к виду, представленному в листинге 1.2)</td>
      </tr>
     </table>
     <p>Мы не будем подробно рассматривать два сценария, когда клиент и сервер используют один и тот же протокол. Более интересны случаи, когда клиент и сервер используют разные протоколы.</p>
    </section>
    <section>
     <title>
      <p>12.2. Клиент IPv4, сервер IPv6</p>
     </title>
     <p>Общим свойством узла с двойным стеком является то, что серверы IPv6 могут выполнять обслуживание клиентов IPv4 и IPv6. Это достигается за счет преобразования адресов IPv4 к виду IPv6 (см. рис. А.6). Пример такого преобразования приведен на рис. 12.1.</p>
     <image l:href="#img_89.png"/>
     <p><strong>Рис. 12.1</strong>. Сервер IPv6 на узле с двойным стеком, обслуживающий клиенты IPv4 и IPv6</p>
     <p>Слева у нас находятся клиент IPv4 и клиент IPv6. Сервер (справа) написан с использованием IPv6 и запущен на узле с двойным стеком. Сервер создал прослушиваемый TCP-сокет IPv6, связанный с универсальным адресом IPv6, и порт TCP 9999.</p>
     <p>Мы считаем, что клиент и сервер находятся в одной сети Ethernet. Они могут быть соединены и через маршрутизаторы, поскольку все маршрутизаторы поддерживают и IPv4, и IPv6, но в данном случае это ничего не меняет. В разделе Б.3 описывается другой случай, когда клиенты и серверы IPv6 соединяются через маршрутизаторы, поддерживающие только IPv4.</p>
     <p>Мы считаем, что оба клиента посылают сегменты SYN для установления соединения с сервером. Узел клиента IPv4 посылает сегмент SYN и дейтаграмму IPv4, а клиент IPv6 посылает сегмент SYN и дейтаграмму IPv6. Сегмент TCP от клиента IPv4 выглядит в сети как заголовок Ethernet, за которым идет заголовок IPv4, заголовок TCP и данные TCP. Заголовок Ethernet содержит поле типа 0x0800, которое идентифицирует кадр как кадр IPv4. Заголовок TCP содержит порт получателя 9999 (в приложении А рассказывается более подробно о форматах и содержании этих заголовков). IP-адрес получателя в заголовке IPv4, который мы не показываем, — это 206.62.226.42.</p>
     <p>Сегмент TCP от клиента IPv6 выглядит в сети как заголовок Ethernet, за которым следует заголовок IPv6, заголовок TCP и данные TCP. Заголовок Ethernet содержит поле типа 0x86dd, которое идентифицирует кадр как кадр IPv6. Заголовок TCP имеет тот же формат, что и заголовок TCP в пакете IPv4, и содержит порт получателя 9999. IP-адрес получателя в заголовке IPv6, который мы не показываем, будет таким: <code>5f1b:df00:ce3e:e200:20:800:2b37:6426</code>.</p>
     <p>Принимающий канальный уровень просматривает поле типа Ethernet и передает каждый кадр соответствующему модулю IP. Модуль IPv4 (возможно, вместе с модулем TCP) определяет, что сокетом получателя является сокет IPv6, и IPv4-адрес отправителя в заголовке IPv4 заменяется на эквивалентный ему адрес IPv4, преобразованный к виду IPv6. Этот преобразованный адрес возвращается сокету IPv6 как IPv6-адрес клиента, когда функция <code>accept</code> сервера соединяется с клиентом IPv4. Все оставшиеся дейтаграммы для этого соединения являются дейтаграммами IPv4.</p>
     <p>Когда функция сервера accept соединяется с клиентом IPv6, клиентский адрес IPv6 остается таким же, каким был адрес отправителя в заголовке IPv6. Все оставшиеся дейтаграммы для этого соединения являются дейтаграммами IPv6.</p>
     <p>Теперь мы можем свести воедино шаги, позволяющие TCP-клиенту IPv4 соединяться с сервером IPv6.</p>
     <p>1. Сервер IPv6 запускается, создает прослушиваемый сокет IPv6, и мы считаем, что с помощью функции <code>bind</code> он связывает с сокетом универсальный адрес.</p>
     <p>2. Клиент IPv4 вызывает функцию <code>gethostbyname</code> и находит запись типа А для сервера. У узла сервера будут записи и типа А, и типа AAAA, поскольку он поддерживает оба протокола, но клиент IPv4 запрашивает только запись типа А.</p>
     <p>3. Клиент вызывает функцию <code>connect</code>, и клиентский узел отправляет серверу сегмент SYN IPv4.</p>
     <p>4. Узел сервера получает сегмент SYN IPv4, направленный прослушиваемому сокету IPv6, устанавливает флаг, указывающий, что это соединение использует адреса IPv4, преобразованные к виду IPv6, и отвечает сегментом IPv4 SYN/ACK. Когда соединение установлено, адрес, возвращаемый серверу функцией accept, является адресом IPv4, преобразованным к виду IPv6.</p>
     <p>5. Все взаимодействие между клиентом и сервером происходит с использованием дейтаграмм IPv4.</p>
     <p>6. Пока сервер не определит при помощи явного запроса, является ли данный IPv6-адрес адресом IPv4, преобразованным к виду IPv6 (с использованием макроопределения <code>IN6_IS_ADDR_V4MAPPED</code>, описанного в разделе 10.4), он не будет знать, что взаимодействует с клиентом IPv4. Двойной стек протоколов решает эту проблему. Аналогично, клиент IPv4 не знает, что он взаимодействует с сервером IPv6.</p>
     <p>Главное в данном сценарии то, что узел сервера с двойным стеком имеет и адрес IPv4, и адрес IPv6. Этот сценарий будет работать, пока используются адреса IPv4.</p>
     <p>Сценарий работы UDP-сервера IPv6 аналогичен, но формат адреса может меняться для каждой дейтаграммы. Например, если сервер IPv6 получает дейтаграмму от клиента IPv4, адрес, возвращаемый функцией recvfrom, будет адресом IPv4, преобразованным к виду IPv6. Сервер отвечает на запрос клиента, вызывая функцию <code>sendto</code> с адресом IPv4, преобразованным к виду IPv6, в качестве адреса получателя. Формат адреса сообщает ядру, что нужно отправить клиенту дейтаграмму IPv4. Но следующей дейтаграммой, полученной сервером, может быть дейтаграмма IPv6, и функция <code>recvfrom</code> возвратит адрес IPv6. Если сервер отвечает, ядро генерирует дейтаграмму IPv6.</p>
     <p>На рис. 12.2 показано, как обрабатывается полученная дейтаграмма IPv4 или IPv6 в зависимости от типа принимающего сокета для TCP и UDP. Предполагается, что это узел с двойным стеком.</p>
     <image l:href="#img_90.png"/>
     <p><strong>Рис. 12.2</strong>. Обработка полученных дейтаграмм IPv4 или IPv6 в зависимости от типа принимающего сокета</p>
     <p>&#9632; Если дейтаграмма IPv4 приходит на сокет IPv4, ничего особенного не происходит. На рисунке изображены две стрелки, помеченные «IPv4»: одна для TCP, другая для UDP. Между клиентом и сервером происходит обмен дейтаграммами IPv4.</p>
     <p>&#9632; Если дейтаграмма IPv6 приходит на сокет IPv6, ничего особенного не происходит. На рисунке изображены две стрелки, помеченные «IPv6»: одна для TCP. другая для UDP. Между клиентом и сервером происходит обмен дейтаграммами IPv6.</p>
     <p>&#9632; Когда дейтаграмма IPv4 приходит на сокет IPv6, ядро возвращает соответствующий адрес IPv4, преобразованный к виду IPv6, в качестве адреса, возвращаемого функцией accept (TCP) или recvfrom (UDP). На рисунке это показано двумя штриховыми стрелками. Такое сопоставление возможно, поскольку адрес IPv4 можно всегда представить как адрес IPv6. Между клиентом и сервером происходит обмен дейтаграммами IPv4.</p>
     <p>&#9632; Обратное неверно: поскольку, вообще говоря, адрес IPv6 нельзя представить как адрес IPv4, на рисунке отсутствуют стрелки от протокола IPv6 к двум сокетам IPv4.</p>
     <p>Большинство узлов с двойным стеком должны использовать следующие правила обращения с прослушиваемыми сокетами:</p>
     <p>1. Прослушиваемый сокет IPv4 может принимать соединения только от клиентов IPv4.</p>
     <p>2. Если у сервера есть прослушиваемый сокет IPv6, связанный с универсальным адресом, и параметр сокета IPV6_V6ONLY (см. раздел 7.8) не установлен, этот сокет может принимать исходящие соединения как от клиентов IPv4, так и от клиентов IPv6. Для соединения с клиентом IPv4 локальный адрес сервера для соединения будет соответствующим адресом IPv4, преобразованным к виду IPv6.</p>
     <p>3. Если у сервера есть прослушиваемый сокет IPv6, связанный с адресом IPv6, не являющимся адресом IPv4, преобразованным к виду IPv6, или его сокет связан с универсальным адресом при установленном параметре сокета IPV6_V6ONLY (раздел 7.8), этот сокет может принимать исходящие соединения только от клиентов IPv6.</p>
    </section>
    <section>
     <title>
      <p>12.3. Клиент IPv6, сервер IPv4</p>
     </title>
     <section>
      <p>Теперь мы поменяем протоколы, используемые клиентом и сервером в примере из предыдущего раздела. Сначала рассмотрим TCP-клиент IPv6, запущенный на узле с двойным стеком протоколов.</p>
      <p>1. Сервер IPv4 запускается на узле, поддерживающем только IPv4, и создает прослушиваемый сокет IPv4.</p>
      <p>2. Запускается клиент IPv6 и вызывает функцию <code>gethostbyname</code>, запрашивая только адреса IPv6 (запрашивает семейство <code>AF_INET6</code> и устанавливает флаг <code>AI_V4MAPPED</code> в структуре <code>hints</code>). Поскольку у сервера, поддерживающего только IPv4, есть лишь записи типа А, мы видим, согласно табл. 11.3, что клиенту возвращается адрес IPv4, преобразованный к виду IPv6.</p>
      <p>3. Клиент IPv6 вызывает функцию connect с адресом IPv4, преобразованным к виду IPv6, в структуре адреса сокета IPv6. Ядро обнаруживает преобразованный адрес и автоматически посылает серверу сегмент SYN IPv4.</p>
      <p>4. Сервер отвечает сегментом SYN/ACK IPv4, и устанавливается соединение, по которому происходит обмен дейтаграммами IPv4. Этот сценарий мы схематически изображаем на рис. 12.3.</p>
      <image l:href="#img_91.png"/>
      <p><strong>Рис. 12.3</strong>. Обработка клиентских запросов в зависимости от типа адреса и типа сокета</p>
      <p>&#9632; Если TCP-клиент IPv4 вызывает функцию <code>connect</code>, задавая адрес IPv4, или если UDP-клиент IPv4 вызывает функцию <code>sendto</code>, задавая адрес IPv4, ничего особенного не происходит. На рисунке это изображено двумя стрелками, помеченными «IPv4».</p>
      <p>&#9632; Если TCP-клиент IPv6 вызывает функцию <code>connect</code>, задавая адрес IPv6, или если UDP-клиент IPv6 вызывает функцию <code>sendto</code>, задавая адрес IPv6, тоже ничего особенного не происходит. На рисунке это показано двумя стрелками, помеченными «IPv6».</p>
      <p>&#9632; Если TCP-клиент IPv6 вызывает функцию <code>connect</code>, задавая адрес IPv4, преобразованный к виду IPv6, или если UDP-клиент вызывает функцию <code>sendto</code>, задавая адрес IPv4, преобразованный к виду IPv6, ядро обнаруживает сопоставленный адрес и инициирует отправку дейтаграммы IPv4 вместо дейтаграммы IPv6. На рисунке это показано двумя штриховыми стрелками.</p>
      <p>&#9632; Клиент IPv4 не может задать адрес IPv6 ни функции <code>connect</code>, ни функции <code>sendto</code>, поскольку 16-байтовый адрес IPv6 не соответствует 4-байтовой структуре <code>in_addr</code> в структуре IPv4 <code>sockaddr_in</code>. Следовательно, на рисунке нет стрелок от сокетов IPv4 к протоколу IPv6.</p>
      <p>В предыдущем разделе (дейтаграмма IPv4, приходящая для сокета сервера IPv6) преобразование полученного адреса IPv4 к виду IPv6 выполняется ядром и результат прозрачно (то есть незаметно для приложения) возвращается приложению функцией <code>accept</code> или <code>recvfrom</code>. В этом разделе (если необходимо отправить дейтаграмму IPv4 на сокете IPv6) преобразование адреса IPv4 к виду IPv6 выполняется распознавателем в соответствии с правилами, представленными в табл. 11.3, и затем преобразованный адрес прозрачно передается приложению функцией <code>connect</code> или <code>sendto</code>.</p>
     </section>
     <section>
      <title>
       <p>Резюме: совместимость IPv4 и IPv6</p>
      </title>
      <p>Таблица 12.2, содержащая сочетания клиентов и серверов, подводит итог обсуждению, проведенному в данном и предыдущем разделах.</p>
      <empty-line/>
      <p><strong>Таблица 12.2</strong>. Обобщение совместимости клиентов и серверов IPv4 и IPv6</p>
      <table>
       <tr align="left">
        <th align="left" valign="top"/>
        <th align="left" valign="top">Сервер IPv4, узел только IPv4 (только А)</th>
        <th align="left" valign="top">Сервер IPv4, узел только IPv6 (только AAAA)</th>
        <th align="left" valign="top">Сервер IPv4, узел с двойным стеком (А и AAAA)</th>
        <th align="left" valign="top">Сервер IPv6, узел с двойным стеком (А и AAAA)</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Клиент IPv4, узел только IPv4</td>
        <td align="left" valign="top">IPv4</td>
        <td align="left" valign="top">Нет</td>
        <td align="left" valign="top">IPv4</td>
        <td align="left" valign="top">IPv4</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Клиент IPv6, узел только IPv6</td>
        <td align="left" valign="top">Нет</td>
        <td align="left" valign="top">IPv6</td>
        <td align="left" valign="top">Нет</td>
        <td align="left" valign="top">IPv6</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Клиент IPv4, узел с двойным стеком</td>
        <td align="left" valign="top">IPv4</td>
        <td align="left" valign="top">Нет</td>
        <td align="left" valign="top">IPv4</td>
        <td align="left" valign="top">IPv4</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Клиент IPv6, узел с двойным стеком</td>
        <td align="left" valign="top">IPv4</td>
        <td align="left" valign="top">IPv6</td>
        <td align="left" valign="top">Нет*</td>
        <td align="left" valign="top">IPv6</td>
       </tr>
      </table>
      <p>Каждая ячейка этой таблицы содержит поля «IPv4» или «IPv6» с указанием используемого протокола, если данное сочетание работает, либо «нет», если комбинация недопустима. Ячейка в последней строке третьей колонки отмечена звездочкой, поскольку совместимость зависит от адреса, выбранного клиентом. При выборе записи типа AAAA отправка дейтаграммы IPv6 будет невозможна. Но выбор записи типа А, которая возвращается клиенту как адрес IPv4, преобразованный к виду IPv6, приведет к отправке дейтаграммы IPv4. Перебрав все адреса, возвращаемые <code>getaddrinfo</code>, мы обязательно доберемся до адреса IPv4, преобразованного к виду IPv6, пусть даже и потратив некоторое время на безуспешное ожидание.</p>
      <p>Хотя четверть из представленных в таблице сочетаний недопустима, в обозримом будущем большинство реализаций IPv6 будут использоваться на узлах с двойным стеком протоколов и поддерживать не только IPv6. Если мы удалим из таблицы вторую строку и вторую колонку, все записи «Нет» исчезнут и единственной проблемой останется запись, помеченная звездочкой.</p>
     </section>
    </section>
    <section>
     <title>
      <p>12.4. Макроопределения проверки адреса IPv6</p>
     </title>
     <p>Существует небольшой класс приложений IPv6, которые должны знать, с каким собеседником они взаимодействуют (IPv4 или IPv6). Эти приложения должны знать, является ли адрес собеседника адресом IPv4, преобразованным к виду IPv6. Определены двенадцать макросов, проверяющих некоторые свойства адреса Ipv6.</p>
     <p><code>#include &lt;netinet/in.h&gt;</code></p>
     <empty-line/>
     <p><code>int IN6_IS_ADDR_UNSPECIFIED(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code>int IN6_IS_ADDR_LOOPBACK(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code>int IN6_IS_ADDR_MULTICAST(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code>int IN6_IS_ADDR_LINKLOCAL(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code>int IN6_IS_ADDR_SITELOCAL(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code>int IN6_IS_ADDR_V4MAPPED(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code>int IN6_IS_ADDR_V4COMPAT(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <empty-line/>
     <p><code>int IN6_IS_ADDR_MC_NODELOCAL(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code>int IN6_IS_ADDR_MC_LINKLOCAL(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code>int IN6_IS_ADDR_MC_SITELOCAL(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code>int IN6_IS_ADDR_MC_ORGLOCAL(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code>int IN6_IS_ADDR_MC_GLOBAL(const struct in6_addr *<emphasis>aptr</emphasis>);</code></p>
     <p><code><emphasis>Все возвращают: ненулевое значение, если адрес IPv6 имеет указанный тип, 0 в противном случае</emphasis></code></p>
     <p>Первые семь макросов проверяют базовый тип адреса IPv6. Мы покажем различные типы адресов в разделе А.5. Последние пять макросов проверяют область действия адреса многоадресной передачи IPv6 (см. раздел 19.2).</p>
     <p>Клиент IPv6 может вызвать макрос <code>IN6_IS_ADDR_V4MAPPED</code> для проверки адреса IPv6, возвращенного распознавателем. Сервер IPv6 может вызвать этот макрос для проверки адреса IPv6, возвращенного функцией accept или <code>recvfrom</code>.</p>
     <p>Как пример приложения, которому нужен этот макрос, можно привести FTP и его команду <code>PORT</code>. Если мы запустим FTP-клиент, зарегистрируемся на FTP-сервере и выполним команду FTP <code>dir</code>, FTP-клиент пошлет команду <code>PORT</code> FTP-серверу через управляющее соединение. Она сообщит серверу IP-адрес и порт клиента, с которым затем сервер создаст соединение. (В главе 27 [111] содержатся подробные сведения о протоколе приложения FTP.) Но FTP-клиент IPv6 должен знать, с каким сервером имеет дело — IPv4 или IPv6, поскольку сервер IPv4 требует команду в формате <code>PORT a1, a2, a3, a4, p1, p2</code> (где первые четыре числа, каждое от 0 до 255, формируют 4-байтовый адрес IPv4, а два последних — 2-байтовый номер порта), а серверу IPv6 необходима команда <code>EPRT</code> (RFC 2428 [3]), содержащая семейство адреса, адрес в текстовом формате и порт в текстовом формате. В упражнении 12.1 приводятся примеры использования обеих команд.</p>
    </section>
    <section>
     <title>
      <p>12.5. Переносимость исходного кода</p>
     </title>
     <p>Большинство существующих сетевых приложений написаны для IPv4. Структуры <code>sockaddr_in</code> размещаются в памяти и заполняются, а функция <code>socket</code> задает <code>AF_INET</code> в качестве первого аргумента. При переходе от листинга 1.1 к листингу 1.2 мы видели, что эти приложения IPv4 можно преобразовать в приложения IPv6 без особых усилий. Многие показанные нами изменения можно выполнить автоматически, используя некоторые сценарии редактирования. Программы, более зависящие от IPv4, использующие такие свойства, как многоадресная передача, параметры IP или символьные (неструктурированные) сокеты, потребуют больших усилий при преобразовании.</p>
     <p>Если мы преобразуем приложение для работы с IPv6 и распространим его исходный код, нам придется думать о том, поддерживает ли принимающая система протокол IPv6. Типичный способ решения этой проблемы — применять в коде <code>#ifdef</code>, используя по возможности IPv6 (поскольку мы видели в этой главе, что клиент IPv6 может взаимодействовать с серверами IPv4 и наоборот). Проблема такого подхода в том, что код очень быстро засоряется директивами <code>#ifdef</code>, и его становится сложнее отслеживать и обслуживать.</p>
     <p>Наилучшим подходом будет рассмотрение перехода на IPv6 как возможности сделать программу не зависящей от протокола. Первым шагом здесь будет удаление вызовов функций <code>gethostbyname</code> и <code>gethostbyaddr</code> и использование функций <code>getaddrinfo</code> и <code>getnameinfo</code>, описанных в предыдущей главе. Это позволит нам обращаться со структурами адресов сокетов как с непрозрачными объектами, ссылаться на которые можно с помощью указателя и размера, что как раз и выполняют основные функции сокетов: <code>bind</code>, <code>connect</code>, <code>recvfrom</code> и т.д. Наши функции <code>sock_XXX</code> из раздела 3.8 помогут работать с ними независимо от IPv4 и IPv6. Очевидно, эти функции содержат <code>#ifdef</code> для работы с IPv4 и IPv6, но если мы скроем эту зависимость от протокола в нескольких библиотечных функциях, наш код станет проще. В разделе 21.7 мы разработаем ряд функций <code>mcast_XXX</code>, которые помогут сделать приложения многоадресной передачи не зависящими от версии протокола IP.</p>
     <p>Другой момент, который нужно учесть, — что произойдет, если мы откомпилируем наш исходный код в системе, поддерживающей и IPv4, и IPv6, затем распространим либо исполняемый код, либо объектные файлы (но не исходный код) и кто-то запустит наше приложение в системе, не поддерживающей IPv6. Есть вероятность, что сервер локальных имен поддерживает записи типа AAAA и возвращает как записи типа AAAA, так и записи типа А некоему собеседнику, с которым пытается соединиться наше приложение. Если наше приложение, работающее с IPv6, вызовет функцию <code>socket</code> для создания сокета IPv6, она не будет работать, если узел не поддерживает IPv6. Мы решаем этот вопрос с помощью функций, описанных в следующей главе, игнорируя ошибку функции <code>socket</code> и пытаясь использовать следующий адрес в списке, возвращаемом сервером имен. Если предположить, что у собеседника имеется запись типа А и что сервер имен возвращает запись типа А в дополнение к любой записи типа AAAA, то сокет IPv4 успешно создастся. Этот тип функциональности имеется в библиотечной функции, но не в исходном коде каждого приложения.</p>
     <p>Чтобы получить возможность передавать дескрипторы сокетов, программам, работающим только с одним из протоколов, в стандарте RFC 2133 [37] предлагается использовать параметр сокета <code>IPV6_ADDRFORM</code>, позволяющий получить или изменить семейство сокета. Однако семантика параметра не была описана полностью, да и использоваться он мог только в очень специфических ситуациях, поэтому в следующей версии интерфейса сокетов данный параметр был отменен.</p>
    </section>
    <section>
     <title>
      <p>12.6. Резюме</p>
     </title>
     <p>Сервер IPv6 на узле с двойным стеком протоколов может предоставлять сервис как клиентам IPv4, так и клиентам IPv6. Клиент IPv4 посылает серверу дейтаграммы IPv4, но стек протоколов сервера преобразует адрес клиента к виду IPv6, поскольку сервер IPv6 работает со структурами адресов сокетов IPv6.</p>
     <p>Аналогично, клиент IPv6 на узле с двойным стеком протоколов может взаимодействовать с сервером IPv4. Распознаватель клиента возвращает адреса IPv4, преобразованные к виду IPv6, для всех записей сервера типа А, и вызов функции connect для одного из этих адресов приводит к тому, что двойной стек посылает сегмент SYN IPv4. Только отдельным специальным клиентам и серверам необходимо знать протокол, используемый собеседником (например, FTP), и чтобы определить, что собеседник использует IPv4, можно использовать макрос <code>IN6_IS_ADDR_V4MAPPED</code>.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Запустите FTP-клиент IPv6 на узле с двойным стеком протоколов. Соединитесь с FTP-сервером IPv4, запустите команду <code>debug</code>, а затем команду <code>dir</code>. Далее выполните те же операции, но для сервера IPv6, и сравните команды PORT, являющиеся результатом выполнения команд <code>dir</code>.</p>
     <p>2. Напишите программу, требующую ввода одного аргумента командной строки, который является адресом IPv4 в точечно-десятичной записи. Создайте TCP-сокет IPv4 и свяжите этот адрес и некоторый порт, например 8888, с сокетом при помощи функции <code>bind</code>. Вызовите функцию <code>listen</code>, а затем <code>pause</code>. Напишите аналогичную программу, которая в качестве аргумента командной строки принимает шестнадцатеричную строку IPv6 и создает прослушиваемый TCP-сокет IPv6. Запустите программу IPv4, задав в качестве аргумента универсальный адрес. Затем перейдите в другое окно и запустите программу IPv6, задав в качестве аргумента универсальный адрес IPv6. Можете ли вы запустить программу IPv6, если программа IPv4 уже связана с этим портом? Появляется ли разница при использовании параметра сокета <code>SO_REUSEADDR</code>? Что будет, если вы сначала запустите программу IPv6, а затем попытаетесь запустить программу IPv4?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 13</p>
     <p>Процессы-демоны и суперсервер inetd</p>
    </title>
    <section>
     <title>
      <p>13.1. Введение</p>
     </title>
     <p><emphasis>Демон</emphasis> (<emphasis>daemon</emphasis>) — это процесс, выполняющийся в фоновом режиме и не связанный с управляющим терминалом. Системы Unix обычно имеют множество процессов (от 20 до 50), которые являются демонами, работают в фоновом режиме и выполняют различные административные задачи.</p>
     <p>Независимость от терминала обычно является побочным эффектом запуска из системного сценария инициализации (например, в процессе загрузки компьютера). Если же демон запускается командой интерпретатора, он должен самостоятельно отключиться от терминала во избежание нежелательного взаимодействия с системами управления задачами, сеансами терминалов, а также вывода на терминал при работе в фоновом режиме.</p>
     <p>Существует несколько способов запустить демон:</p>
     <p>1. Во время запуска системы многие демоны запускаются сценариями инициализации системы. Эти сценарии часто находятся в каталоге <code>/etc</code>  или в каталоге, имя которого начинается с <code>/etc/rc</code>, но их расположение и содержание зависят от реализации. Такие демоны запускаются с правами привилегированного пользователя.</p>
     <p>Некоторые сетевые серверы часто запускаются из сценариев инициализации: суперсервер <code>inetd</code> (следующий пункт, который мы рассмотрим), веб-сервер и почтовый сервер (обычно это программа <code>sendmail</code>). Демон <code>syslogd</code>, обсуждаемый в разделе 13.2, тоже обычно запускается одним из этих сценариев.</p>
     <p>2. Многие сетевые серверы запускаются суперсервером <code>inetd</code>, который мы опишем далее в этой главе. Сам <code>inetd</code> запускается в одном из сценариев на этапе 1. Суперсервер <code>inetd</code> прослушивает сетевые порты (Telnet, FTP и т.д.), и когда приходит запрос, активизирует требуемый сервер (сервер Telnet, сервер FTP и т.д.).</p>
     <p>3. За периодические процессы в системе отвечает демон <code>cron</code>, и программы, которые он активизирует, выполняются как демоны. Сам демон <code>cron</code> запускается на этапе 1 во время загрузки системы.</p>
     <p>4. Если программа должна быть выполнена однократно в определенный момент времени в будущем, применяется команда <code>at</code>. Демон <code>cron</code> обычно инициирует эти программы, когда приходит время их выполнения, поэтому они выполняются как демоны.</p>
     <p>5. Демоны можно запускать с пользовательских терминалов, как в основном, так и в фоновом режимах. Это часто осуществляется при тестировании демона или перезапуске демона, завершенного по некоей причине.</p>
     <p>Поскольку у демона нет управляющего терминала, ему необходимы средства для вывода сообщений о некоторых событиях — это могут быть обычные информационные сообщения или экстренные сообщения об аварийных ситуациях, которые должен обрабатывать администратор. Использование функции <code>syslog</code> — стандартный способ вывода таких сообщений. Эта функция посылает сообщения демону <code>syslogd</code>.</p>
    </section>
    <section>
     <title>
      <p>13.2. Демон syslogd</p>
     </title>
     <p>Системы Unix обычно запускают демон <code>syslogd</code> в одном из сценариев инициализации системы, и он функционирует, пока система работает. Реализации <code>syslogd</code>, происходящие от Беркли, выполняют при запуске следующие действия:</p>
     <p>1. Считывается файл конфигурации, обычно <code>/etc/syslog.conf</code>, в котором указано, что делать с каждым типом сообщений, получаемых демоном. Эти сообщения могут добавляться в файл (особой разновидностью такого файла является <code>/dev/console</code>, который записывает сообщение на консоль), передаваться определенному пользователю (если этот пользователь вошел в систему) или передаваться демону <code>syslogd</code> на другом узле.</p>
     <p>2. Создается доменный сокет Unix и связывается с полным именем <code>/var/run/log</code> (в некоторых системах <code>/dev/log</code>).</p>
     <p>3. Создается сокет UDP и связывается с портом 514 (служба <code>syslog</code>).</p>
     <p>4. Открывается файл (устройство) <code>/dev/klog</code>. Любые сообщения об ошибках внутри ядра появляются как входные данные на этом устройстве.</p>
     <p>Демон <code>syslogd</code> выполняется в бесконечном цикле, в котором вызывается функция <code>select</code>, ожидающая, когда один из трех его дескрипторов (из п. 2, 3 и 4) станет готов для чтения. Этот демон считывает сообщение и выполняет то, что предписывает делать с этим сообщением файл конфигурации. Если демон получает сигнал <code>SIGHUP</code>, он заново считывает файл конфигурации.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Более новые реализации отключают возможность создания сокета UDP, если она не задана администратором, поскольку если позволить кому угодно отправлять дейтаграммы UDP на этот порт (возможно, заполняя приемный буфер его сокета), это может привести к тому, что законные сообщения не будут получены (атака типа отказ в обслуживании) или переполнится файловая система из-за неограниченного роста журналов.</p>
      <p>Между реализациями демона syslogd существуют различия. Например, доменные сокеты Unix используются Беркли-реализациями, а реализации System V используют потоковый драйвер (streams log driver). Различные реализации, происходящие от Беркли, используют для доменных сокетов Unix различные полные имена. Мы можем игнорировать все эти тонкости, если используем функцию syslog.</p>
     </cite>
     <p>Мы можем отправлять сообщения о событиях для записи в журнал (log messages) демону <code>syslogd</code> из наших демонов, создав дейтаграммный доменный сокет Unix и указывая при отправке полное имя, с которым связан демон, но более простым интерфейсом является функция <code>syslog</code>, которую мы описываем в следующем разделе. В качестве альтернативы мы можем создать сокет UDP и отправлять наши сообщения на адрес закольцовки и порт 514.</p>
    </section>
    <section>
     <title>
      <p>13.3. Функция syslog</p>
     </title>
     <p>Поскольку у демона нет управляющего терминала, он не может просто вызвать функцию <code>fprintf</code> для вывода в стандартный поток сообщений об ошибках (<code>stderr</code>). Обычная техника записи в журнал сообщений для демона — это вызов функции <code>syslog</code>.</p>
     <p><code>#include &lt;syslog.h&gt;</code></p>
     <empty-line/>
     <p><code>void syslog(int <emphasis>priority</emphasis>, const char *<emphasis>message</emphasis>, ...);</code></p>
     <p>Хотя эта функция изначально разрабатывалась для BSD, в настоящее время она предоставляется большинством производителей систем Unix. Описание <code>syslog</code> в POSIX соответствует тому, что мы пишем здесь. RFC 3164 содержит документацию, касающуюся протокола <code>syslog</code> BSD.</p>
     <p>Аргумент <code>priority</code> — это комбинация аргументов <code>level</code> и <code>facility</code>, которые мы показываем в табл. 13.1 и 13.2. Дополнительные сведения об этом аргументе можно найти в RFC 3164. Аргумент <code>message</code> аналогичен строке формата функции <code>printf</code> с добавлением спецификации <code>%m</code>, которая заменяется сообщением об ошибке, соответствующим текущему значению переменной <code>errno</code>. Символ перевода строки может появиться в конце строки <code>message</code>, но он не является обязательным.</p>
     <p>Сообщения для журнала имеют значение <code>level</code> (уровень) от 0 до 7, что мы показываем в табл. 13.1. Это упорядоченные значения. Если отправитель не задает значение <code>level</code>, используется значение по умолчанию <code>LOG_NOTICE</code>.</p>
     <empty-line/>
     <p><strong>Таблица 13.1</strong>. Аргумент level журнальных сообщений</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Level</th>
       <th align="left" valign="top">Значение</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_EMERG</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Система не может функционировать, экстренная ситуация (наивысший приоритет)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_ALERT</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Следует немедленно принять меры, срочная ситуация</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_CRIT</td>
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">Критическая ситуация</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_ERR</td>
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">Состояние ошибки</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_WARNING</td>
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">Предупреждение</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_NOTICE</td>
       <td align="left" valign="top">5</td>
       <td align="left" valign="top">Необычное, хотя и не ошибочное состояние (значение аргумента level по умолчанию)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_INFO</td>
       <td align="left" valign="top">6</td>
       <td align="left" valign="top">Информационное сообщение</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_DEBUG</td>
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">Отладочные сообщения (низший приоритет)</td>
      </tr>
     </table>
     <p>Сообщения также содержат аргумент <code>facility</code> для идентификации типа процесса, посылающего сообщение. Мы показываем его различные значения в табл. 13.2. Если не задано значение аргумента <code>facility</code>, используется его значение по умолчанию — <code>LOG_USER</code>.</p>
     <empty-line/>
     <p><strong>Таблица 13.2</strong>. Аргумент facility журнальных сообщений</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">facility</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_AUTH</td>
       <td align="left" valign="top">Сообщения no безопасности/авторизации</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_AUTHPRIV</td>
       <td align="left" valign="top">Сообщения по безопасности/авторизации (частные)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_CRON</td>
       <td align="left" valign="top">Демон cron</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_DAEMON</td>
       <td align="left" valign="top">Системные демоны</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_FTP</td>
       <td align="left" valign="top">Демон FTP</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_KERN</td>
       <td align="left" valign="top">Сообщения ядра</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_LOCAL0</td>
       <td align="left" valign="top">Локальное использование</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_LOCAL1</td>
       <td align="left" valign="top">Локальное использование</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_LOCAL2</td>
       <td align="left" valign="top">Локальное использование</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_LOCAL3</td>
       <td align="left" valign="top">Локальное использование</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_LOCAL4</td>
       <td align="left" valign="top">Локальное использование</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_LOCAL5</td>
       <td align="left" valign="top">Локальное использование</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_LOCAL6</td>
       <td align="left" valign="top">Локальное использование</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_LOCAL7</td>
       <td align="left" valign="top">Локальное использование</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_LPR</td>
       <td align="left" valign="top">Демон принтера</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_MAIL</td>
       <td align="left" valign="top">Почтовая система</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_NEWS</td>
       <td align="left" valign="top">Система телеконференций</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_SYSLOG</td>
       <td align="left" valign="top">Внутренние сообщения системы syslog</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_USER</td>
       <td align="left" valign="top">Сообщения пользовательского уровня (значение аргумента facility по умолчанию)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_UUCP</td>
       <td align="left" valign="top">Система UUCP</td>
      </tr>
     </table>
     <p>Например, демон может сделать следующий вызов, когда вызов функции <code>rename</code> неожиданно оказывается неудачным:</p>
     <p><code>syslog(LOG_INFO|LOG_LOCAL2, "rename(%s, %s): %m", file1, file2);</code></p>
     <p>Назначение аргументов <code>facility</code> и <code>level</code> в том, чтобы все сообщения, которые посылаются процессами определенного типа (то есть с одним значением аргумента <code>facility</code>), могли обрабатываться одинаково в файле <code>/etc/syslog.conf</code> или чтобы все сообщения одного уровня (с одинаковым значением аргумента <code>level</code>) обрабатывались одинаково. Например, файл конфигурации может содержать строки</p>
     <p><code>kern.* /dev/console</code></p>
     <p><code>local7.debug /var/log/cisco.log</code></p>
     <p>для указания, что все сообщения ядра направляются на консоль, а сообщения относительно отладки со значением аргумента <code>facility</code>, равным <code>local7</code>, добавляются в файл <code>/var/log/cisco.log</code>.</p>
     <p>Когда приложение впервые вызывает функцию <code>syslog</code>, она создает дейтаграммный доменный сокет Unix и затем вызывает функцию <code>connect</code> для сокета с заранее известным полным именем, которое создано демоном <code>syslogd</code> (например, <code>/var/run/log</code>). Этот сокет остается открытым, пока процесс не завершится. Другим вариантом является вызов процессом функций <code>openlog</code> и <code>closelog</code>.</p>
     <p><code>#include &lt;syslog.h&gt;</code></p>
     <empty-line/>
     <p><code>void openlog(const char *<emphasis>ident</emphasis>, int <emphasis>options</emphasis>, int <emphasis>facility</emphasis>);</code></p>
     <p><code>void closelog(void);</code></p>
     <p>Функция <code>openlog</code> может быть вызвана перед первым вызовом функции <code>syslog</code>, а функция <code>closelog</code> — когда приложение закончит отправлять сообщения в журнал.</p>
     <p>Аргумент <code>ident</code> — это строка, которая будет добавлена в начало каждого журнального сообщения функцией <code>syslog</code>. Часто это имя программы.</p>
     <p>Обычно аргумент <code>options</code> формируется путем применения операции логического ИЛИ к константам из табл. 13.3.</p>
     <empty-line/>
     <p><strong>Таблица 13.3</strong>. Аргумент options (параметр) для функции openlog</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Параметр</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_CONS</td>
       <td align="left" valign="top">Выводить журнал на консоль, если невозможно послать сообщение демону syslogd</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_NDELAY</td>
       <td align="left" valign="top">Не откладывать создание сокета, открыть его сейчас</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_PERROR</td>
       <td align="left" valign="top">Записывать сообщение в stderr, а также посылать его демону syslogd</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">LOG_PID</td>
       <td align="left" valign="top">Включать идентификатор процесса (PID) в каждую запись журнала</td>
      </tr>
     </table>
     <p>Обычно доменный сокет Unix не создается при вызове функции <code>openlog</code>. Вместо этого сокет открывается при первом вызове функции <code>syslog</code>. Параметр <code>LOG_NDELAY</code> указывает, что сокет должен создаваться при вызове функции <code>openlog</code>.</p>
     <p>Аргумент <code>facility</code> функции <code>openlog</code> задает значение <code>facility</code>, используемое по умолчанию для любого последующего вызова функции <code>syslog</code>, при котором не задается аргумент <code>facility</code>. Некоторые демоны вызывают функцию <code>openlog</code> и задают значение аргумента <code>facility</code> (которое обычно не изменяется для данного демона) и затем в каждом вызове функции <code>syslog</code> задают только аргумент <code>level</code> (поскольку <code>level</code> может изменяться в зависимости от ошибки).</p>
     <p>Сообщения для записи в журнал могут также генерироваться командой <code>logger</code>. Это может использоваться в сценариях интерпретатора команд, например для отправки сообщений демону <code>syslogd</code>.</p>
    </section>
    <section>
     <title>
      <p>13.4. Функция daemon_init</p>
     </title>
     <section>
      <p>В листинге 13.1<a l:href="#n1" type="note">[1]</a> показана функция, называемая <code>daemon_init</code>, которую мы можем вызвать (обычно с сервера), чтобы придать процессу свойства демона.</p>
      <p><strong>Листинг 13.1</strong>. Функция daemon_init: придание процессу свойств демона</p>
      <p><code>//daemon _init.с</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;syslog.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 #define MAXFD 64</code></p>
      <empty-line/>
      <p><code> 4 extern int daemon_proc; /* определен в error.с */</code></p>
      <empty-line/>
      <p><code> 5 int</code></p>
      <p><code> 6 daemon_init(const char *pname, int facility)</code></p>
      <p><code> 7 {</code></p>
      <p><code> 8  int i;</code></p>
      <p><code> 9  pid_t pid;</code></p>
      <empty-line/>
      <p><code>10  if ((pid = Fork()) &lt; 0)</code></p>
      <p><code>11   return (-1);</code></p>
      <p><code>12  else if (pid)</code></p>
      <p><code>13   _exit(0); /* родитель завершается */</code></p>
      <empty-line/>
      <p><code>14  /* 1-й дочерний процесс продолжает работу... */</code></p>
      <empty-line/>
      <p><code>15  if (setsid() &lt; 0) /* становится главным процессом сеанса */</code></p>
      <p><code>16   return (-1);</code></p>
      <empty-line/>
      <p><code>17  Signal(SIGHUP, SIG_IGN);</code></p>
      <p><code>18  if ((pid = Fork()) &lt; 0)</code></p>
      <p><code>19   return (-1);</code></p>
      <p><code>20  else if (pid)</code></p>
      <p><code>21   _exit(0); /* 1-й дочерний процесс завершается */</code></p>
      <empty-line/>
      <p><code>22  /* 2-й дочерний процесс продолжает работу */</code></p>
      <empty-line/>
      <p><code>23  daemon_proc = 1; /* для функций err_XXX() */</code></p>
      <empty-line/>
      <p><code>24  chdir("/"); /* смена текущего каталога */</code></p>
      <empty-line/>
      <p><code>25  /* закрытие дескрипторов файлов*/</code></p>
      <p><code>26  for (i = 0; i &lt; MAXFD; i++)</code></p>
      <p><code>27   close(i);</code></p>
      <empty-line/>
      <p><code>28  /* перенаправление stdin, stdout и stderr в /dev/null */</code></p>
      <p><code>29  open("/dev/null", O_RDONLY);</code></p>
      <p><code>30  open("/dev/null", O_RDWR);</code></p>
      <p><code>31  open("/dev/null", O_RDWR);</code></p>
      <empty-line/>
      <p><code>32  openlog(pname, LOG_PID, facility);</code></p>
      <empty-line/>
      <p><code>33  return (0); /* успешное завершение */</code></p>
      <p><code>34 }</code></p>
      <subtitle>Вызов функции fork</subtitle>
      <p><code>10-13</code> Сначала мы вызываем функцию <code>fork</code>, после чего родительский процесс завершается, а дочерний продолжается. Если процесс был запущен из интерпретатора команд в фоновом режиме, то, когда родительский процесс завершается, оболочка считает, что команда выполнена. Это автоматически запускает дочерний процесс в фоновом режиме. Дочерний процесс наследует идентификатор группы процессов от родительского процесса, но получает свой собственный идентификатор процесса. Это гарантирует, что дочерний процесс не является главным в группе процессов, что требуется для следующего вызова функции <code>setsid</code>.</p>
      <subtitle>Вызов функции setsid</subtitle>
      <p><code>15-16</code> Функция <code>setsid</code> — это функция POSIX, создающая новый сеанс. (В главе 9 [110] подробно рассказывается о взаимоотношениях процессов.) Процесс становится главным в новом сеансе, становится главным в новой группе процессов и не имеет управляющего терминала.</p>
      <subtitle>Игнорирование сигнала SIGHUP и новый вызов функции fork</subtitle>
      <p><code>17-21</code> Мы игнорируем сигнал <code>SIGHUP</code> и снова вызываем функцию <code>fork</code>. Когда эта функция завершается, родительский процесс на самом деле является первым дочерним процессом, и он завершается, оставляя выполняться второй дочерний процесс. Назначение второй функции <code>fork</code> — гарантировать, что демон не сможет автоматически получить управляющий терминал, если потом он откроет устройство терминала. В SVR4, когда главный процесс сеанса без управляющего терминала открывает устройство терминала (которое в этот момент не является управляющим терминалом для другого сеанса), терминал становится управляющим терминалом главного процесса сеанса. Но вызывая второй раз функцию <code>fork</code>, мы гарантируем, что второй дочерний процесс больше не является главным в сеансе, поэтому он не может получить управляющий терминал. Сигнал <code>SIGHUP</code> приходится игнорировать, поскольку, когда главный процесс сеанса завершает работу (первый дочерний процесс), всем процессам в сеансе (нашему второму дочернему процессу) посылается сигнал SIGHUP.</p>
      <subtitle>Установка флага для функций ошибок</subtitle>
      <p><code>23</code> Мы присваиваем глобальной переменной <code>daemon_proc</code> ненулевое значение. Эта внешняя переменная задается нашими функциями <code>err_<emphasis>XXX</emphasis></code> (см. раздел Г.4), и ее ненулевое значение сообщает этим функциям, что нужно вызвать функцию <code>syslog</code> вместо функции <code>fprintf</code> (которая выводит сообщение об ошибке в стандартный поток сообщений об ошибках). Это спасает нас от необходимости проходить через весь наш код и вызывать одну из наших функций ошибок, если сервер не работает как демон (то есть когда мы проверяем сервер), а при работе в режиме демона заменять все вызовы на вызовы <code>syslog</code>.</p>
      <subtitle>Изменение рабочего каталога и сброс всех битов в маске режима создания файла</subtitle>
      <p><code>24</code> Мы изменяем рабочий каталог на корневой каталог, хотя у некоторых демонов могут быть причины изменить рабочий каталог на какой-либо другой. Например, демон печати может изменить его на каталог, в котором накапливается содержимое заданий для принтера и происходит вся работа по выводу данных на печать. Если демоном сбрасывается дамп (файл <code>core</code>), он появляется в текущем рабочем каталоге. Другой причиной для изменения рабочего каталога является то, что демон мог быть запущен в любой файловой системе, и если он там останется, эту систему нельзя будет размонтировать, во всяком случае, без жестких мер.</p>
      <subtitle>Закрытие всех открытых дескрипторов</subtitle>
      <p><code>25-27</code> Мы закрываем все открытые дескрипторы, которые наследуются от процесса, запустившего демон (обычно этим процессом бывает интерпретатор команд). Проблема состоит в определении наибольшего используемого дескриптора: в Unix нет ни одной функции, предоставляющей это значение. Есть способы определения максимального числа дескрипторов, которое может открыть процесс, но даже это достаточно сложно [110, с. 43], поскольку предел может быть бесконечным. Наше решение — закрыть первые 64 дескриптора, даже если большинство из них, возможно, не было открыто.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Solaris предоставляет функцию closefrom, позволяющую демонам решать эту проблему.</p>
      </cite>
      <subtitle>Перенаправление stdin, stdout и stderr в /dev/null</subtitle>
      <p><code>29-31</code> Некоторые демоны открывают <code>/dev/null</code> для чтения и записи и подключают к нему дескрипторы стандартных потоков ввода, вывода и сообщений об ошибках. Это гарантирует, что наиболее типичные дескрипторы открыты и операция чтения из любого из них возвращает 0 (конец файла), а ядро игнорирует все, что записано в любой из этих трех дескрипторов. Причина, по которой требуется открыть эти дескрипторы, заключается в том, что любая библиотечная функция, вызываемая демоном и считающая, что она может читать из стандартного потока ввода или записывать либо в стандартный поток вывода, либо в стандартный поток сообщений об ошибках, не должна завершиться с ошибкой. Отказ был бы потенциально опасен: если демон открывает сокет для связи с клиентом, дескриптор сокета воспринимается как стандартный поток вывода, поэтому ошибочный вызов какой-нибудь функции типа <code>perror</code> может привести к отправке клиенту нежелательных данных.</p>
      <subtitle>Использование демона syslogd для вывода сообщений об ошибках</subtitle>
      <p><code>32</code> Вызывается функция <code>openlog</code>. Первый ее аргумент берется из вызывающего процесса и обычно является именем программы (например, <code>argv[0]</code>). Мы указываем, что идентификатор процесса должен добавляться к каждому сообщению. Аргумент <code>facility</code> также задается вызывающим процессом, и его значением может быть константа из табл. 13.2 либо, если приемлемо значение по умолчанию <code>LOG_USER</code>, нулевое значение.</p>
      <p>Отметим, что поскольку демон выполняется без управляющего терминала, он никогда не должен получать сигнал <code>SIGHUP</code> от ядра. Следовательно, многие демоны используют этот сигнал в качестве уведомления от администратора, что файл конфигурации демона изменился и демон должен еще раз считать файл. Два других сигнала, которые демон никогда не должен получать, — это сигналы <code>SIGINT</code> и <code>SIGWINCH</code>, и они также могут использоваться для уведомления демона о некоторых изменениях.</p>
     </section>
     <section>
      <title>
       <p>Пример: сервер времени и даты в качестве демона</p>
      </title>
      <p>В листинге 13.2 представлено изменение нашего сервера времени и даты, не зависящего от протокола. В отличие от сервера, показанного в листинге 11.8, в нем вызывается функция <code>daemon_init</code>, чтобы этот сервер мог выполняться в качестве демона.</p>
      <p><strong>Листинг 13.2</strong>. Не зависящий от протокола сервер времени и даты, работающий в качестве демона</p>
      <p><code>//inetd/daytimetcpsrv2.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;time.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 main(int argc, char **argv)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int listenfd, connfd;</code></p>
      <p><code> 7  socklen_t addrlen, len;</code></p>
      <p><code> 8  struct sockaddr *cliaddr;</code></p>
      <p><code> 9  char buff[MAXLINE];</code></p>
      <p><code>10  time_t ticks;</code></p>
      <empty-line/>
      <p><code>11  daemon_init(argv[0], 0);</code></p>
      <empty-line/>
      <p><code>12  if (argc == 2)</code></p>
      <p><code>13   listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
      <p><code>14  else if (argc == 3)</code></p>
      <p><code>15   listenfd = Tcp_listen(argv[1], argv[2], &amp;addrlen);</code></p>
      <p><code>16  else</code></p>
      <p><code>17   err_quit("usage: daytimetcpsrv2 [ &lt;host&gt; ] &lt;service or port&gt;");</code></p>
      <empty-line/>
      <p><code>18  cliaddr = Malloc(addrlen);</code></p>
      <empty-line/>
      <p><code>19  for (;;) {</code></p>
      <p><code>20   len = addrlen;</code></p>
      <p><code>21   connfd = Accept(listenfd, cliaddr, &amp;len);</code></p>
      <p><code>22   err_msg("connection from %s", Sock_ntop(cliaddr, len));</code></p>
      <empty-line/>
      <p><code>23   ticks = time(NULL);</code></p>
      <p><code>24   snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&amp;ticks));</code></p>
      <p><code>25   Write(connfd, buff, strlen(buff));</code></p>
      <empty-line/>
      <p><code>26   Close(connfd);</code></p>
      <p><code>27  }</code></p>
      <p><code>28 }</code></p>
      <p>Изменений всего два: мы вызываем нашу функцию <code>daemon_init</code>, как только программа запускается, а затем вызываем нашу функцию <code>err_msg</code> вместо <code>printf</code>, чтобы вывести IP-адрес и порт клиента. На самом деле, если мы хотим, чтобы наши программы могли выполняться как демоны, мы должны исключить вызов функций <code>printf</code> и <code>fprintf</code> и вместо них использовать нашу функцию <code>err_msg</code>.</p>
      <p>Обратите внимание, что мы проверяем argc и выводим соответствующее сообщение до вызова <code>daemon_init</code>. Таким образом пользователь, запустивший демона, получает немедленное уведомление о недопустимом количестве аргументов. После вызова <code>daemon_init</code> все сообщения направляются в системный журнал.</p>
      <p>Если мы запустим эту программу на нашем узле <code>linux</code> и затем проверим файл <code>/var/log/messages</code> (куда мы отправляем все сообщения <code>LOG_USER</code>) после соединения с тем же узлом, мы получим:</p>
      <p><code>Jul 10 09:54:37 linux daytimetcpsrv2[24288]: connection from 127.0.0.1.55862</code></p>
      <p>Дата, время и имя узла автоматически ставятся в начале сообщения демоном <code>syslogd</code>.</p>
     </section>
    </section>
    <section>
     <title>
      <p>13.5. Демон inetd</p>
     </title>
     <p>В типичной системе Unix может существовать много серверов, ожидающих запроса клиента. Примерами являются FTP, Telnet, Rlogin, TFTP и т.д. В системах, предшествующих 4.3BSD, каждая из этих служб имела связанный с ней процесс. Этот процесс запускался во время загрузки из файла <code>/etc/rc</code>, и каждый процесс выполнял практически идентичные задачи запуска: создание сокета, связывание при помощи функции <code>bind</code> заранее известного порта с сокетом, ожидание соединения (TCP) или получения дейтаграммы (UDP) и последующее выполнение функции <code>fork</code>. Дочерний процесс выполнял обслуживание клиента, а родительский процесс ждал, когда поступит следующий запрос клиента. Эта модель характеризуется двумя недостатками.</p>
     <p>1. Все демоны содержали практически идентичный код запуска, направленный сначала на создание сокета, а затем на превращение процесса в процесс демона (аналогично нашей функции <code>daemon_init</code>).</p>
     <p>2. Каждый демон занимал некоторое место в таблице процессов, но при этом большую часть времени находился в состоянии ожидания.</p>
     <p>Реализация 4.3BSD упростила ситуацию, предоставив <emphasis>суперсервер</emphasis> (<emphasis>superserver</emphasis>) Интернета — демон <code>inetd</code>. Этот демон может применяться серверами, использующими TCP или UDP, и не поддерживает других протоколов, таких как доменные сокеты Unix. Демон <code>inetd</code> решает две вышеупомянутые проблемы.</p>
     <p>1. Он упрощает написание процессов демонов, поскольку обрабатывает большинство подробностей запуска. Таким образом устраняется необходимость вызова нашей функции <code>daemon_init</code> для каждого сервера.</p>
     <p>2. Этот демон позволяет одиночному процессу (<code>inetd</code>) ждать входящие клиентские запросы ко множеству служб (вместо одного процесса для каждой службы). Это сокращает общее число процессов в системе.</p>
     <p>Процесс <code>inetd</code> сам становится демоном, используя технологии, которые мы изложили при описании функции <code>daemon_init</code>. Затем он считывает и обрабатывает файл конфигурации, обычно файл <code>/etc/inetd.conf</code>. Этот файл задает, какие службы должен обрабатывать суперсервер, а также что нужно делать, когда приходит запрос к одной из этих служб. Каждая строка содержит поля, показанные в табл. 13.4. Вот несколько строк в качестве примера:</p>
     <p><code>ftp    stream tcp nowait root   /usr/bin/ftpd ftpd -l</code></p>
     <p><code>telnet stream tcp nowait root   /usr/bin/telnetd telnetd</code></p>
     <p><code>login  stream tcp nowait root   /usr/bin/rlogind rlogind -s</code></p>
     <p><code>tftp   dgram  udp wait   nobody /usr/bin/tftpd tftpd -s /tftpboot</code></p>
     <p>Действительное имя сервера всегда передается в качестве первого аргумента программе, выполняемой с помощью функции <code>exec</code>.</p>
     <empty-line/>
     <p><strong>Таблица 13.4</strong>. Поля файла inetd.conf</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Поле</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">service-name</td>
       <td align="left" valign="top">Должен быть в /etc/services</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">socket-type</td>
       <td align="left" valign="top">stream (TCP) или dgram (UDP)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Protocol</td>
       <td align="left" valign="top">Должен быть в /etc/protocols; либо tcp, либо udp</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">wait-flag</td>
       <td align="left" valign="top">Обычно nowait для TCP и wait для UDP</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">login-name</td>
       <td align="left" valign="top">Из /etc/password; обычно root</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">server-program</td>
       <td align="left" valign="top">Полное имя программы для вызова exec</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">server-program-arguments</td>
       <td align="left" valign="top">Аргументы программы для вызова exec</td>
      </tr>
     </table>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Таблица и приведенные строки — это только пример. Большинство производителей добавили демону inetd свои собственные функции. Примером может служить возможность обрабатывать серверы вызовов удаленных процедур (RPC) в дополнение к серверам TCP и UDP, а также возможность обрабатывать другие протоколы, отличные от TCP и UDP. Полное имя для функции exec и аргументы командной строки сервера, очевидно, зависят от приложения.</p>
      <p>Флаг wait-flag может быть достаточно труден для понимания. Он указывает, собирается ли демон, запускаемый inetd, взять на себя работу с прослушиваемым сокетом. Сервисы UDP лишены деления на прослушиваемые и принятые сокеты, и потому практически всегда создаются с флагом wait-flag, равным wait. Сервисы TCP могут вести себя по-разному, но чаще всего для них указывается флаг wait-flag со значением nowait.</p>
      <p>Взаимодействие IPv6 с файлом /etc/inetd.conf зависит от производителя. Иногда в качестве поля protocol указывается tcp6 или udp6, чтобы подчеркнуть, что для сервера должен быть создан сокет IPv6. Некоторые разрешают использовать значения protocol, равные tcp46 и udp46, если сервер готов принимать соединения по обоим протоколам. Специальные названия протоколов обычно не включаются в файл /etc/protocols.</p>
     </cite>
     <p>Иллюстрация действий, выполняемых демоном <code>inetd</code>, представлена на рис. 13.1.</p>
     <image l:href="#img_92.png"/>
     <p><strong>Рис. 13.1</strong>. Действия, выполняемые демоном inetd</p>
     <p>1. При запуске демон читает файл <code>/etc/inetd.conf</code> и создает сокет соответствующего типа (потоковый или дейтаграммный сокет) для всех служб, заданных в файле. Максимальное число серверов, которые может обрабатывать демон <code>inetd</code>, зависит от максимального числа дескрипторов, которые он может создать. Каждый новый сокет добавляется к набору дескрипторов, который будет использован при вызове функции <code>select</code>.</p>
     <p>2. Для каждого сокета вызывается функция <code>bind</code>, задающая заранее известный порт для сервера и универсальный IP-адрес. Этот номер порта TCP или UDP получается при вызове функции <code>getservbyname</code> с полями <code>service</code>-name и <code>protocol</code> из файла конфигурации в качестве аргументов.</p>
     <p>3. Для сокетов TCP вызывается функция <code>listen</code>, так что принимаются входящие запросы на соединение. Этот шаг не выполняется для дейтаграммных сокетов.</p>
     <p>4. После того как созданы все сокеты, вызывается функция <code>select</code>, ожидающая, когда какой-либо из сокетов станет готов для чтения. Вспомните (раздел 6.3), что прослушиваемый сокет TCP становится готов для чтения, когда новое соединение готово быть принятым с помощью функции <code>accept</code>, а сокет UDP становится готов для чтения, когда приходит дейтаграмма. Демон <code>inetd</code> большую часть времени блокирован в вызове функции <code>select</code>, ожидая, когда сокет станет готов для чтения.</p>
     <p>5. При указании флага <code>nowait</code> для сокетов TCP вызывается функция <code>accept</code> сразу же, как только дескриптор сокета становится готов для чтения.</p>
     <p>6. Демон <code>inetd</code> запускает функцию <code>fork</code>, и дочерний процесс обрабатывает запрос клиента. Это аналогично стандартному параллельному серверу (см. раздел 4.8).</p>
     <p>Дочерний процесс закрывает все дескрипторы, кроме дескриптора, который он обрабатывает: новый присоединенный сокет, возвращаемый функцией <code>accept</code> для сервера TCP, или исходный сокет UDP. Дочерний процесс трижды вызывает функцию <code>dup2</code>, подключая сокет к дескрипторам 0, 1 и 2 (стандартные потоки ввода, вывода и сообщений об ошибках). Исходный дескриптор сокета затем закрывается. При этом в дочернем процессе открытыми остаются только дескрипторы 0, 1 и 2. Если дочерний процесс читает из стандартного потока ввода, он читает из сокета, и все, что он записывает в стандартный поток вывода или стандартный поток сообщений об ошибках, записывается в сокет. Дочерний процесс вызывает функцию <code>getpwnam</code>, чтобы получить значение поля <code>login-name</code>, заданного в файле конфигурации. Если это не поле root, дочерний процесс становится указанным пользователем при помощи функций <code>setgid</code> и <code>setuid</code>. (Поскольку процесс <code>inetd</code> выполняется с идентификатором пользователя, равным 0, дочерний процесс наследует этот идентификатор пользователя при выполнении функции <code>fork</code>, поэтому он имеет возможность стать любым пользователем по своему выбору.)</p>
     <p>Теперь дочерний процесс вызывает функцию <code>exec</code>, чтобы выполнить соответствующую <emphasis>программу сервера</emphasis> (поле <code>server-program</code>) для обработки запроса, передавая аргументы, указанные в файле конфигурации.</p>
     <p>7. Если сокет является потоковым сокетом, родительский процесс должен закрыть присоединенный сокет (как наш стандартный параллельный сервер). Родительский процесс снова вызывает функцию <code>select</code>, ожидая, когда следующий сокет станет готов для чтения.</p>
     <p>Чтобы рассмотреть более подробно, что происходит с дескрипторами, на рис. 13.2 показаны дескрипторы демона <code>inetd</code> в момент прихода нового запроса на соединение от клиента FTP.</p>
     <image l:href="#img_93.png"/>
     <p><strong>Рис. 13.2</strong>. Дескрипторы демона inetd в тот момент, когда приходит запрос на порт 21 TCP</p>
     <p>Запрос на соединение направляется на порт 21 TCP; новый присоединенный сокет создается функцией <code>accept</code>.</p>
     <p>На рис. 13.3 показаны дескрипторы в дочернем процессе после вызова функции <code>fork</code>, после того как дочерний процесс закрывает все остальные дескрипторы, кроме дескрипторов присоединенного сокета.</p>
     <image l:href="#img_94.png"/>
     <p><strong>Рис. 13.3</strong>. Дескрипторы демона inetd в дочернем процессе</p>
     <p>Следующий шаг для дочернего процесса — подключение присоединенного сокета к дескрипторам 0, 1 и 2 и последующее закрытие присоединенного сокета. При этом мы получаем дескрипторы, изображенные на рис. 13.4.</p>
     <image l:href="#img_95.png"/>
     <p><strong>Рис. 13.4</strong>. Дескрипторы демона inetd после выполнения функции dup2</p>
     <p>Затем дочерний процесс вызывает функцию <code>exec</code>, и, как сказано в разделе 4.7, во время выполнения функции <code>exec</code> все дескрипторы обычно остаются открытыми, поэтому реальный сервер, на котором выполняется функция <code>exec</code>, использует любой из дескрипторов 0, 1 и 2 для взаимодействия с клиентом. Эти дескрипторы должны быть единственными открытыми на стороне сервера дескрипторами.</p>
     <p>Описанный нами сценарий относится к ситуации, при которой файл конфигурации задает в поле <code>wait-flag</code> значение <code>nowait</code> для сервера. Это типично для всех служб TCP и означает, что демону <code>inetd</code> не нужно ждать завершения его дочернего процесса, перед тем как он примет другое соединение для данной службы. Если приходит другой запрос на соединение для той же службы, он возвращается родительскому процессу, как только тот снова вызовет функцию <code>select</code>. Шаги 4, 5 и 6, перечисленные выше, выполняются снова, и новый запрос обрабатывается другим дочерним процессом.</p>
     <p>Задание флага <code>wait</code> для дейтаграммного сервиса изменяет шаги, выполняемые родительским процессом. Флаг указывает на то, что демон <code>inetd</code> должен ждать завершения своего дочернего процесса, прежде чем снова вызвать функцию <code>select</code> для определения готовности этого сокета UDP для чтения. Происходят следующие изменения:</p>
     <p>1. После выполнения функции <code>fork</code> в родительском процессе сохраняется идентификатор дочернего процесса. Это дает возможность родительскому процессу узнать, когда завершается определенный дочерний процесс, анализируя значение, возвращаемое функцией <code>waitpid</code>.</p>
     <p>2. Родительский процесс отключает способность сокета выполнять последующие функции <code>select</code>, сбрасывая соответствующий бит в наборе дескрипторов с помощью макроса <code>FD_CLR</code>. Это значит, что дочерний процесс завладевает сокетом до своего завершения.</p>
     <p>3. Когда завершается дочерний процесс, родительский процесс уведомляется об этом с помощью сигнала <code>SIGCHLD</code>, и обработчик сигналов родительского процесса получает идентификатор завершающегося дочернего процесса. Он снова включает функцию <code>select</code> для соответствующего сокета, устанавливая бит для этого сокета в своем наборе дескрипторов.</p>
     <p>Причина, по которой дейтаграммный сервер должен завладевать сокетом, пока он не завершит работу, лишая тем самым демон <code>inetd</code> возможности выполнять функцию <code>select</code> на этом сокете для проверки готовности его для чтения (в ожидании другой дейтаграммы клиента), в том, что для сервера дейтаграмм существует только один сокет, в отличие от сервера TCP, у которого имеется прослушиваемый сокет и по одному присоединенному сокету для каждого клиента. Если демон <code>inetd</code> не отключил чтение на сокете дейтаграмм и, допустим, родительский процесс (<code>inetd</code>) завершил выполнение перед дочерним, дейтаграмма от клиента все еще будет находиться в приемном буфере сокета. Это приводит к тому, что функция <code>select</code> снова сообщает, что сокет готов для чтения, и демон <code>inetd</code> снова выполняет функцию <code>fork</code>, порождая другой (ненужный) дочерний процесс. Демон <code>inetd</code> должен игнорировать дейтаграммный сокет до тех пор, пока он не узнает, что дочерний процесс прочитал дейтаграмму из приемного буфера сокета. Демон <code>inetd</code> узнает, что дочерний процесс закончил работу с сокетом, путем получения сигнала <code>SIGCHLD</code>, указывающего на то, что дочерний процесс завершился. Подобный пример мы показываем в разделе 22.7.</p>
     <p>Пять стандартных служб Интернета, описанных в табл. 2.1, обеспечиваются самим демоном <code>inetd</code> (см. упражнение 13.2).</p>
     <p>Поскольку функцию <code>accept</code> для сервера TCP вызывает демон <code>inetd</code> (а не сам сервер), реальный сервер, запускаемый демоном <code>inetd</code>, обычно вызывает функцию <code>getpeername</code> для получения IP-адреса и номера порта клиента. Вспомните рис. 4.9, где мы показывали, что после выполнения вызовов <code>fork</code> и <code>exec</code> (что выполняет демон <code>inetd</code>) у реального сервера есть единственный способ получить идентификацию клиента — вызвать функцию <code>getpeername</code>.</p>
     <p>Демон <code>inetd</code> обычно не используется для серверов, работающих с большими объемами данных, в особенности почтовыми серверами и веб-серверами. Например, функция <code>sendmail</code> обычно запускается как стандартный параллельный сервер, как мы отмечали в разделе 4.8. В этом режиме стоимость порождения процесса для каждого клиентского соединения равна стоимости функции <code>fork</code>, тогда как в случае сервера TCP, активизированного демоном <code>inetd</code>, — стоимости функций <code>fork</code> и <code>exec</code>. Веб-серверы используют множество технологий для минимизации накладных расходов при порождении процессов для обслуживания клиентов, как мы покажем в главе 30.</p>
    </section>
    <section>
     <title>
      <p>13.6. Функция daemon_inetd</p>
     </title>
     <section>
      <p>В листинге 13.3 показана функция <code>daemon_inetd</code>, которую мы можем вызвать с сервера, запущенного демоном <code>inetd</code>.</p>
      <p><strong>Листинг 13.3</strong>. Функция daemon_inetd для придания свойств демона процессу, запущенному демоном inetd</p>
      <p><code>//daemon_inetd.c</code></p>
      <p><code>1 #include "unp.h"</code></p>
      <p><code>2 #include &lt;syslog.h&gt;</code></p>
      <empty-line/>
      <p><code>3 extern int daemon_proc; /* определено в error.c */</code></p>
      <empty-line/>
      <p><code>4 void</code></p>
      <p><code>5 daemon_inetd(const char *pname, int facility)</code></p>
      <p><code>6 {</code></p>
      <p><code>7  daemon_proc = 1; /* для наших функций err_XXX() */</code></p>
      <p><code>8  openlog(pname, LOG_PID, facility);</code></p>
      <p><code>9 }</code></p>
      <p>Эта функция тривиальна по сравнению с <code>daemon_init</code>, потому что все шаги выполняются демоном <code>inetd</code> при запуске. Все, что мы делаем, — устанавливаем флаг <code>daemon_proc</code> для наших функций ошибок (см. табл. Г.1) и вызываем функцию <code>openlog</code> с теми же аргументами, что и при вызове функции <code>daemon_init</code>, представленной в листинге 13.1.</p>
     </section>
     <section>
      <title>
       <p>Пример: сервер времени и даты, активизированный демоном inetd</p>
      </title>
      <p>Листинг 13.4 представляет собой модификацию нашего сервера времени и даты, показанного в листинге 13.2, который может быть активизирован демоном <code>inetd</code>.</p>
      <p><strong>Листинг 13.4</strong>. Не зависящий от протокола сервер времени и даты, который может быть активизирован демоном inetd</p>
      <p><code>//inetd/daytimetcpsrv3.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;time.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 main(int argc, char **argv)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  socklen_t len;</code></p>
      <p><code> 7  struct sockaddr *cliaddr;</code></p>
      <p><code> 8  char buff[MAXLINE];</code></p>
      <p><code> 9  time_t ticks;</code></p>
      <empty-line/>
      <p><code>10  daemon_inetd(argv[0], 0);</code></p>
      <empty-line/>
      <p><code>11  cliaddr = Malloc(MAXSOCKADDR);</code></p>
      <p><code>12  len = MAXSOCKADDR;</code></p>
      <p><code>13  Getpeername(0, cliaddr, &amp;len);</code></p>
      <p><code>14  err_msg("connection from %s", Sock_ntop(cliaddr, len));</code></p>
      <empty-line/>
      <p><code>15  ticks = time(NULL);</code></p>
      <p><code>16  snprintf(buff, sizeof(buff), "%.24s\r\n\", ctime(&amp;ticks));</code></p>
      <p><code>17  Write(0, buff, strlen(buff));</code></p>
      <empty-line/>
      <p><code>18  Close(0); /* закрываем соединение TCP */</code></p>
      <p><code>19  exit(0);</code></p>
      <p><code>20 }</code></p>
      <p>В программе сделано два важных изменения. Во-первых, исчез весь код создания сокета: вызовы функций <code>tcp_listen</code> и <code>accept</code>. Эти шаги выполняются демоном <code>inetd</code>, и мы ссылаемся на соединение TCP, используя нулевой дескриптор (стандартный поток ввода). Во-вторых, исчез бесконечный цикл <code>for</code>, поскольку сервер активизируется по одному разу для каждого клиентского соединения. После предоставления сервиса клиенту сервер завершает свою работу.</p>
      <subtitle>Вызов функции getpeername</subtitle>
      <p><code>11-14</code> Поскольку мы не вызываем функцию <code>tcp_listen</code>, мы не знаем размера структуры адреса сокета, которую она возвращает, а поскольку мы не вызываем функцию <code>accept</code>, то не знаем и адреса протокола клиента. Следовательно, мы выделяем буфер для структуры адреса сокета, используя нашу константу <code>MAXSOCKADDR</code> и вызываем функцию <code>getpeername</code> с нулевым дескриптором в качестве первого аргумента.</p>
      <p>Чтобы выполнить этот пример в нашей системе Solaris, сначала мы присваиваем службе имя и порт, добавляя следующую строку в <code>/etc/services</code>:</p>
      <p><code>mydaytime 9999/tcp</code></p>
      <p>Затем добавляем строку в <code>/etc/inetd.conf</code>:</p>
      <p><code>mydaytime stream tcp nowait andy</code></p>
      <p><code>/home/andy/daytimetcpsrv3 daytimetcpsrv3</code></p>
      <p>(Мы разбили длинную строку на более короткие.) Мы помещаем выполняемый код в заданный файл и отправляем демону <code>inetd</code> сигнал <code>SIGHUP</code>, сообщающий ему, что нужно заново считать файл конфигурации. Следующий шаг — выполнить программу <code>netstat</code>, чтобы проверить, что на порте TCP 9999 создан прослушиваемый сокет:</p>
      <p><code>solaris % <strong>netstat -na | grep 9999</strong></code></p>
      <p><code>*.9999 *.* 0 0 49152 0 LISTEN</code></p>
      <p>Затем мы запускаем сервер с другого узла:</p>
      <p><code>linux % <strong>telnet solaris 9999</strong></code></p>
      <p><code>Trying 192.168.1.20...</code></p>
      <p><code>Connected to solaris.</code></p>
      <p><code>Escape character is '^]'.</code></p>
      <p><code>Tue Jun 10 11:04:02 2003</code></p>
      <p><code>Connection closed by foreign host.</code></p>
      <p>Файл <code>/var/amd/messages</code> (в который, как указано в нашем файле <code>/etc/syslog.conf</code>, должны направляться наши сообщения с аргументом <code>facility=LOG_USER</code>) содержит запись:</p>
      <p><code>Jun 10 11:04:02 solaris daytimetcpsrv3[28724]: connection from 192.168.1.10.58145</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>13.7. Резюме</p>
     </title>
     <p>Демоны — это процессы, выполняемые в фоновом режиме независимо от управления с терминалов. Многие сетевые серверы работают как демоны. Все выходные данные демона обычно отправляются демону <code>syslogd</code> при помощи вызова функции <code>syslog</code>. Администратор полностью контролирует все, что происходит с этими сообщениями, основываясь на том, какой демон отправил данное сообщение и насколько оно серьезно.</p>
     <p>Чтобы запустить произвольную программу и выполнять ее в качестве демона, требуется пройти несколько шагов: вызвать функцию <code>fork</code> для запуска в фоновом режиме, вызвать функцию <code>setsid</code> для того, чтобы создать новый сеанс POSIX и стать главным процессом сеанса, снова вызвать функцию <code>fork</code>, чтобы избежать перехода в режим управления с терминала, изменить рабочий каталог и маску режима создания файла и закрыть все ненужные файлы. Наша функция <code>daemon_init</code> выполняет все эти шаги.</p>
     <p>Многие серверы Unix запускаются демоном <code>inetd</code>. Он осуществляет все необходимые шаги по превращению процесса в демон, и при запуске действительного сервера открывается сокет для стандартных потоков ввода, вывода и сообщений об ошибках. Это позволяет нам опустить вызовы функций <code>socket</code>, <code>bind</code>, <code>listen</code> и <code>accept</code>, поскольку все эти шаги выполняются демоном <code>inetd</code>.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Что произойдет в листинге 13.2, если мы отложим вызов функции <code>daemon_init</code> до завершения обработки аргументов командной строки и функция <code>err_quit</code> будет вызвана до того, как программа станет демоном?</p>
     <p>2. Как вы думаете, какие из 10 серверов, перечисленных в табл. 2.1 (учитываются версии TCP и UDP для каждой из пяти служб, управляемых демоном <code>inetd</code>), реализуются с помощью вызова функции fork, а какие не требуют этой функции?</p>
     <p>3. Что произойдет, если мы создадим сокет UDP, свяжем порт 7 с сокетом (стандартный эхо-сервер в табл. 2.1) и отправим дейтаграмму UDP-серверу <code>chargen</code>?</p>
     <p>4. В руководстве Solaris 2.x для демона <code>inetd</code> описывается флаг <code>-t</code>, заставляющий демон <code>inetd</code> вызывать функцию <code>syslog</code> (с аргументами <code>facility=LOG_DAEMON</code> и <code>level=LOG_NOTICE</code>) для протоколирования клиентского IP-адреса и порта любой службы TCP, которые обрабатывает демон <code>inetd</code>. Как демон <code>inetd</code> получает эту информацию?</p>
     <p>В этом же руководстве сказано, что демон <code>inetd</code> не может выполнить это для сокета UDP. Почему?</p>
     <p>Есть ли способ обойти эти ограничения для служб UDP?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 14</p>
     <p>Дополнительные функции ввода-вывода</p>
    </title>
    <section>
     <title>
      <p>14.1. Введение</p>
     </title>
     <p>Эта глава охватывает разнообразные функции и технологии, которые мы помещаем в общую категорию «расширенного ввода-вывода». Сначала мы описываем установку тайм-аута для операции ввода-вывода, которую можно выполнить тремя различными способами. Затем мы рассматриваем три варианта функций <code>read</code> и <code>write</code>: <code>recv</code> и <code>send</code>, допускающие четвертый аргумент, содержащий флаги, передаваемые от процесса к ядру; <code>readv</code> и <code>writev</code>, позволяющие нам задавать массив буферов для ввода или вывода; <code>recvmsg</code> и <code>sendmsg</code>, объединяющие все свойства других функций ввода-вывода и обладающие новой возможностью получения и отправки вспомогательных данных.</p>
     <p>Мы также рассказываем о том, как определить, сколько данных находится в приемном буфере сокета и как использовать с сокетами стандартную библиотеку ввода-вывода С, и обсуждаем более совершенные способы ожидания событий.</p>
    </section>
    <section>
     <title>
      <p>14.2. Тайм-ауты сокета</p>
     </title>
     <section>
      <p>Существует три способа установки тайм-аута для операции ввода-вывода через сокет.</p>
      <p>1. Вызов функции <code>alarm</code>, которая генерирует сигнал <code>SIGALRM</code>, когда истекает заданное время. Это подразумевает обработку сигналов, которая может варьироваться от одной реализации к другой. К тому же такой подход может стать помехой другим существующим вызовам функции <code>alarm</code> в данном процессе.</p>
      <p>2. Блокирование при ожидании ввода-вывода в функции <code>select</code>, имеющей встроенное ограничение времени, вместо блокирования в вызове функции <code>read</code> или <code>write</code>.</p>
      <p>3. Использование более новых параметров сокета — <code>SO_RCVTIMEO</code> и <code>SO_SNDTIMEO</code>. Проблема при использовании этого подхода заключается в том, что не все реализации поддерживают новые параметры сокетов.</p>
      <p>Все три технологии работают с функциями ввода и вывода (такими как <code>read</code>, <code>write</code> и их вариациями, например <code>recvfrom</code> и <code>sendto</code>), но нам также хотелось бы иметь технологию, работающую с функцией <code>connect</code>, поскольку процесс соединения TCP может занять длительное время (обычно 75 с). Функцию <code>select</code> можно использовать для установки тайм-аута функции <code>connect</code>, только когда сокет находится в неблокируемом режиме (который мы рассматриваем в разделе 16.3), а параметры сокетов, устанавливающие тайм-аут, не работают с функцией <code>connect</code>. Мы также должны отметить, что первые две технологии работают с любым дескриптором, в то время как третья технология только с дескрипторами сокетов.</p>
      <p>Теперь мы представим примеры применения всех трех технологий.</p>
     </section>
     <section>
      <title>
       <p>Тайм-аут для функции connect (сигнал SIGALRM)</p>
      </title>
      <p>В листинге 14.1<a l:href="#n1" type="note">[1]</a> показана наша функция <code>connect_timeo</code>, вызывающая функцию connect с ограничением по времени, заданным вызывающим процессом. Первые три аргумента — это аргументы, которых требует функция <code>connect</code>, а четвертый — это длительность ожидания в секундах.</p>
      <p><strong>Листинг 14.1</strong>. Функция connect с тайм-аутом</p>
      <p><code>//lib/connect_timeo.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 static void connect_alarm(int);</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  Sigfunc *sigfunc;</code></p>
      <p><code> 7  int n;</code></p>
      <empty-line/>
      <p><code> 8  sigfunc = Signal(SIGALRM, connect_alarm);</code></p>
      <p><code> 9  if (alarm(nsec) != 0)</code></p>
      <p><code>10   err_msg("connect_timeo: alarm was already set");</code></p>
      <empty-line/>
      <p><code>11  if ((n = connect(sockfd, saptr, salen)) &lt; 0) {</code></p>
      <p><code>12   close(sockfd);</code></p>
      <p><code>13   if (errno == EINTR)</code></p>
      <p><code>14    errno = ETIMEDOUT;</code></p>
      <p><code>15  }</code></p>
      <p><code>16  alarm(0); /* отключение alarm */</code></p>
      <p><code>17  Signal(SIGALRM, sigfunc); /* восстанавливаем прежний обработчик</code></p>
      <p><code>                                 сигнала */</code></p>
      <p><code>18  return (n);</code></p>
      <p><code>19 }</code></p>
      <empty-line/>
      <p><code>20 static void</code></p>
      <p><code>21 connect_alarm(int signo)</code></p>
      <p><code>22 {</code></p>
      <p><code>23  return; /* просто прерываем connect() */</code></p>
      <p><code>24 }</code></p>
      <subtitle>Установка обработчика сигналов</subtitle>
      <p><code>8</code> Для <code>SIGALRM</code> устанавливается обработчик сигнала. Текущий обработчик сигнала (если таковой имеется) сохраняется, и таким образом мы можем восстановить его в конце функции.</p>
      <subtitle>Установка таймера</subtitle>
      <p><code>9-10</code> Таймер для процесса устанавливается на время (число секунд), заданное вызывающим процессом. Возвращаемое значение функции <code>alarm</code> — это число секунд, остающихся в таймере для процесса (если он уже установлен для процесса) в настоящий момент или 0 (если таймер не был установлен прежде). В первом случае мы выводим сообщение с предупреждением, поскольку мы стираем предыдущую установку таймера (см. упражнение 14.2).</p>
      <subtitle>Вызов функции connect</subtitle>
      <p><code>11-15</code> Вызывается функция <code>connect</code>, и если функция прерывается (<code>EINTR</code>), мы присваиваем переменной errno значение <code>ETIMEDOUT</code>. Сокет закрывается, чтобы не допустить продолжения трехэтапного рукопожатия.</p>
      <subtitle>Выключение таймера и восстановление предыдущего обработчика сигнала</subtitle>
      <p><code>16-18</code> Таймер при обнулении выключается, и восстанавливается предыдущий обработчик сигналов (если таковой имеется).</p>
      <subtitle>Обработка сигнала SIGALRM</subtitle>
      <p><code>20-24</code> Обработчик сигнала просто возвращает управление. Предполагается, что это прервет ожидание функции <code>connect</code>, заставив ее возвратить ошибку <code>EINTR</code>. Вспомните нашу функцию <code>signal</code> (см. листинг 5.5), которая не устанавливает флага <code>SA_RESTART</code>, когда перехватываемый сигнал — это сигнал <code>SIGALRM</code>.</p>
      <p>Одним из важных моментов в этом примере является то, что мы всегда можем сократить период ожидания для функции <code>connect</code>, используя эту технологию, но мы не можем увеличить период, заданный для ядра. В Беркли-ядре тайм-аут для функции <code>connect</code> обычно равен 75 с. Мы можем задать меньшее значение для нашей функции, допустим 10, но если мы задаем большее значение, скажем 80, тайм- аут самой функции <code>connect</code> все равно составит 75 с.</p>
      <p>Другой важный момент в данном примере — то, что мы используем возможность прерывания системного вызова (<code>connect</code>) для того, чтобы возвратить управление, прежде чем истечет время ожидания ядра. Такой подход допустим, когда мы выполняем системный вызов и можем обработать возвращение ошибки <code>EINTR</code>. Но в разделе 29.7 мы встретимся с библиотечной функцией, выполняющей системный вызов, которая сама выполняет заново системный вызов при возвращении ошибки <code>EINTR</code>. Мы можем продолжать работать с сигналом <code>SIGALRM</code> и в этом случае, но в листинге 29.6 мы увидим, что нам придется воспользоваться функциями <code>sigsetjmp</code> и <code>siglongjmp</code>, поскольку библиотечная функция игнорирует ошибку <code>EINTR</code>.</p>
     </section>
     <section>
      <title>
       <p>Тайм-аут для функции recvfrom (сигнал SIGALRM)</p>
      </title>
      <p>В листинге 14.2 показана новая версия функции <code>dg_cli</code>, приведенной в листинге 8.4, в которую добавлен вызов функции <code>alarm</code> для прерывания функции recvfrom при отсутствии ответа в течение 5 с.</p>
      <p><strong>Листинг 14.2</strong>. Функция dg_cli, в которой при установке тайм-аута для функции recvfrom используется функция alarm</p>
      <p><code>//advio/dgclitimeo3.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 static void signalrm(int);</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int n;</code></p>
      <p><code> 7  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
      <empty-line/>
      <p><code> 8  Signal(SIGALRM, signalrm);</code></p>
      <empty-line/>
      <p><code> 9  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <empty-line/>
      <p><code>10   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
      <empty-line/>
      <p><code>11   alarm(5);</code></p>
      <p><code>12   if ((n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) &lt; 0) {</code></p>
      <p><code>13    if (errno == EINTR)</code></p>
      <p><code>14     fprintf(stderr, "socket timeout\n");</code></p>
      <p><code>15    else</code></p>
      <p><code>16     err_sys("recvfrom error");</code></p>
      <p><code>17   } else {</code></p>
      <p><code>18    alarm(0);</code></p>
      <p><code>19    recvline[n] = 0; /* завершающий нуль */</code></p>
      <p><code>20    Fputs(recvline, stdout);</code></p>
      <p><code>21   }</code></p>
      <p><code>22  }</code></p>
      <p><code>23 }</code></p>
      <empty-line/>
      <p><code>24 static void</code></p>
      <p><code>25 sig_alrm(int signo)</code></p>
      <p><code>26 {</code></p>
      <p><code>27  return; /* просто прерываем recvfrom() */</code></p>
      <p><code>28 }</code></p>
      <subtitle>Обработка тайм-аута из функции recvfrom</subtitle>
      <p><code>8-22</code> Мы устанавливаем обработчик для сигнала <code>SIGALRM</code> и затем вызываем функцию <code>alarm</code> для 5-секундного тайм-аута при каждом вызове функции <code>recvfrom</code>. Если функция <code>recvfrom</code> прерывается нашим обработчиком сигнала, мы выводим сообщение об ошибке и продолжаем работу. Если получена строка от сервера, мы отключаем функцию alarm и выводим ответ.</p>
      <subtitle>Обработчик сигнала SIGALRM</subtitle>
      <p><code>24-28</code> Наш обработчик сигналов возвращает управление, прерывая блокированную функцию <code>recvfrom</code>.</p>
      <p>Этот пример работает корректно, потому что каждый раз, когда мы устанавливаем функцию <code>alarm</code>, мы читаем только один ответ. В разделе 20.4 мы попытаемся использовать ту же технологию, но поскольку мы будем считывать множество ответов для данной функции <code>alarm</code>, возникнет ситуация гонок, которую нам придется разрешить.</p>
     </section>
     <section>
      <title>
       <p>Тайм-аут для функции recvfrom (функция select)</p>
      </title>
      <p>Мы демонстрируем вторую технологию для установки тайм-аута (использование функции <code>select</code>) в листинге 14.3. Здесь показана наша функция <code>readable_timeo</code>, которая ждет, когда дескриптор станет готов для чтения, но не более заданного числа секунд.</p>
      <p><strong>Листинг 14.3</strong>. Функция readable_timeo: ожидание, когда дескриптор станет готов для чтения</p>
      <p><code>//lib/readable_timео.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 readable_timeo(int fd, int sec)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  fd_set rset;</code></p>
      <p><code> 6  struct timeval tv;</code></p>
      <empty-line/>
      <p><code> 7  FD_ZERO(&amp;rset);</code></p>
      <p><code> 8  FD_SET(fd, &amp;rset);</code></p>
      <empty-line/>
      <p><code> 9  tv.tv_sec = sec;</code></p>
      <p><code>10  tv.tv_usec = 0;</code></p>
      <empty-line/>
      <p><code>11  return (select(fd + 1, &amp;rset, NULL, NULL, &amp;tv));</code></p>
      <p><code>12  /* &gt; если дескриптор готов для чтения */</code></p>
      <p><code>13 }</code></p>
      <subtitle>Подготовка аргументов для функции select</subtitle>
      <p><code>7-10</code> В наборе дескрипторов для чтения включается бит, соответствующий данному дескриптору. В структуре <code>timeval</code> устанавливается время (число секунд), в течение которого вызывающий процесс готов ждать.</p>
      <subtitle>Блокирование в функции select</subtitle>
      <p><code>11-12</code> Функция <code>select</code> ждет, когда дескриптор станет готов для чтения или истечет заданное время ожидания. Возвращаемое значение этой функции — это возвращаемое значение функции <code>select</code>: -1 в случае ошибки, 0, если истекло время ожидания, и положительное значение, задающее число готовых дескрипторов, если таковые появились.</p>
      <p>Эта функция не выполняет операции чтения — она просто ждет, когда дескриптор будет готов к чтению. Следовательно, эту функцию можно использовать с любым типом сокета — TCP или UDP.</p>
      <p>Создание аналогичной функции, называемой <code>writable_timeo</code>, тривиально. Эта функция ждет, когда дескриптор будет готов для записи.</p>
      <p>Мы используем эту функцию в листинге 14.4, где показана еще одна версия нашей функции <code>dg_cli</code>, приведенной в листинге 8.4. Эта новая версия вызывает функцию <code>recvfrom</code>, только когда наша функция <code>readable_timeo</code> возвращает положительное значение.</p>
      <p>Мы не вызываем функцию <code>recvfrom</code>, пока функция <code>readable_timeo</code> не сообщит нам, что дескриптор готов для чтения. Тем самым мы гарантируем, что функция <code>recvfrom</code> не заблокируется.</p>
      <p><strong>Листинг 14.4</strong>. Функция dg_cli, вызывающая функцию readable_timeo для установки тайм-аута</p>
      <p><code>//advio/dgclitimeo1.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int n;</code></p>
      <p><code> 6  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
      <empty-line/>
      <p><code> 7  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <empty-line/>
      <p><code> 8   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
      <empty-line/>
      <p><code> 9   if (Readable_timeo(sockfd, 5) == 0) {</code></p>
      <p><code>10    fprintf(stderr, "socket timeout\n");</code></p>
      <p><code>11   } else {</code></p>
      <p><code>12    n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);</code></p>
      <p><code>13    recvline[n] = 0; /* завершающий нуль */</code></p>
      <p><code>14    Fputs(recvline, stdout);</code></p>
      <p><code>15   }</code></p>
      <p><code>16  }</code></p>
      <p><code>17 }</code></p>
     </section>
     <section>
      <title>
       <p>Тайм-аут для функции recvfrom (параметр сокета SO_RCVTIMEO)</p>
      </title>
      <p>В нашем последнем примере демонстрируется применение параметра сокета <code>SO_RCVTIMEO</code>. Мы устанавливаем этот параметр один раз для дескриптора, задавая значение тайм-аута, и этот тайм-аут затем применяется ко всем операциям чтения этого дескриптора. Одна из замечательных особенностей этого метода состоит в том, что мы устанавливаем данный параметр только один раз, тогда как предыдущие два метода требовали выполнения некоторых действий перед каждой операцией, для которой мы хотели задать временной предел. Но этот параметр сокета применяется только к операциям чтения. Аналогичный параметр <code>SO_SNDTIMEO</code> применяется только к операциям записи, и ни один параметр сокета не может использоваться для установки тайм-аута для функции <code>connect</code>.</p>
      <p><strong>Листинг 14.5</strong>. Функция dg_cli, использующая параметр сокета SO_RCVTIMEO для установки тайм-аута</p>
      <p><code>//advio/dgclitimeo2.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int n;</code></p>
      <p><code> 6  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
      <p><code> 7  struct timeval tv;</code></p>
      <empty-line/>
      <p><code> 8  tv.tv_sec = 5;</code></p>
      <p><code> 9  tv.tv_usec = 0;</code></p>
      <p><code>10  Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &amp;tv, sizeof(tv));</code></p>
      <empty-line/>
      <p><code>11  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <empty-line/>
      <p><code>12    Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
      <empty-line/>
      <p><code>13    n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);</code></p>
      <p><code>14    if (n &lt; 0) {</code></p>
      <p><code>15     if (errno == EWOULDBLOCK) {</code></p>
      <p><code>16     fprintf(stderr, "socket timeout\n");</code></p>
      <p><code>17     continue;</code></p>
      <p><code>18    } else</code></p>
      <p><code>19     err_sys("recvfrom error");</code></p>
      <p><code>20   }</code></p>
      <p><code>21   recvline[n] = 0; /* завершающий нуль */</code></p>
      <p><code>22   Fputs(recvline, stdout);</code></p>
      <p><code>23  }</code></p>
      <p><code>24 }</code></p>
      <subtitle>Установка параметра сокета</subtitle>
      <p><code>8-10</code> Четвертый аргумент функции <code>setsockopt</code> — это указатель на структуру <code>timeval</code>, в которую записывается желательное значение тайм-аута.</p>
      <subtitle>Проверка тайм-аута</subtitle>
      <p><code>15-17</code> Если тайм-аут операции ввода-вывода истекает, функция (в данном случае <code>recvfrom</code>) возвращает ошибку <code>EWOULDBLOCK</code>.</p>
     </section>
    </section>
    <section>
     <title>
      <p>14.3. Функции recv и send</p>
     </title>
     <p>Эти две функции аналогичны стандартным функциям <code>read</code> и <code>write</code>, но для них требуется дополнительный аргумент.</p>
     <p><code>#include &lt;sys/socket.h&gt;</code></p>
     <empty-line/>
     <p><code>ssize_t recv(int <emphasis>sockfd</emphasis>, void *<emphasis>buff</emphasis>, size_t <emphasis>nbytes</emphasis>, int <emphasis>flags</emphasis>);</code></p>
     <p><code>ssize_t send(int <emphasis>sockfd</emphasis>, const void *<emphasis>buff</emphasis>, size_t <emphasis>nbytes</emphasis>, int <emphasis>flags</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают: количество прочитанных или записанных байтов в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Первые три аргумента функций <code>recv</code> и <code>send</code> совпадают с тремя первыми аргументами функций read и write. Аргумент <code>flags</code> либо имеет нулевое значение, либо формируется в результате применения операции логического ИЛИ к константам, представленным в табл. 14.1.</p>
     <empty-line/>
     <p><strong>Таблица 14.1</strong>. Аргумент flags для функций ввода-вывода</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">flags</th>
       <th align="left" valign="top">Описание</th>
       <th align="left" valign="top">recv</th>
       <th align="left" valign="top">send</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_DONTROUTE</td>
       <td align="left" valign="top">He искать в таблице маршрутизации</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_DONTWAIT</td>
       <td align="left" valign="top">Только эта операция является неблокируемой</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_OOB</td>
       <td align="left" valign="top">Отправка или получение внеполосных данных</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_PEEK</td>
       <td align="left" valign="top">Просмотр приходящих сообщений</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_WAITALL</td>
       <td align="left" valign="top">Ожидание всех данных</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
     </table>
     <p>&#9632; <code>MSG_DONTROUTE</code>. Этот флаг сообщает ядру, что получатель находится в нашей сети, и поэтому не нужно выполнять поиск в таблице маршрутизации. Дополнительную информацию об этом свойстве мы приводим при описании параметра сокета <code>SO_DONTROUTE</code> (см. раздел 7.5). Это свойство можно включить для одной операции вывода с флагом <code>MSG_DONTROUTE</code> или для всех операций вывода данного сокета, используя указанный параметр сокета.</p>
     <p>&#9632; <code>MSG_DONTWAIT</code>. Этот флаг указывает, что отдельная операция ввода-вывода является неблокируемой. Таким образом, отпадает необходимость включать флаг отсутствия блокировки для сокета, выполнять операцию ввода-вывода и затем выключать флаг отсутствия блокировки. Неблокируемый ввод-вывод мы опишем в главе 15 вместе с включением и выключением флага отсутствия блокировки для всех операций ввода-вывода через сокет.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Этот флаг введен в Net/3 и может не поддерживаться в некоторых системах.</p>
     </cite>
     <p>&#9632; <code>MSG_OOB</code>. С функцией <code>send</code> этот флаг указывает, что отправляются внеполосные данные. В случае TCP в качестве внеполосных данных должен быть отправлен только 1 байт, как показано в главе 21. С функцией <code>recv</code> этот флаг указывает на то, что вместо обычных данных должны читаться внеполосные данные.</p>
     <p>&#9632; <code>MSG_PEEK</code>. Этот флаг позволяет нам просмотреть пришедшие данные, готовые для чтения, при этом после выполнения функции <code>recv</code> или <code>recvfrom</code> данные не сбрасываются (при повторном вызове этих функций снова возвращаются уже просмотренные данные). Подробнее мы поговорим об этом в разделе 14.7.</p>
     <p>&#9632; <code>MSG_WAITALL</code>. Этот флаг был впервые введен в 4.3BSD Reno. Он сообщает ядру, что операция чтения должна выполняться до тех пор, пока не будет прочитано запрашиваемое количество байтов. Если система поддерживает этот флаг, мы можем опустить функцию <code>readn</code> (см. листинг 3.9) и заменить ее макроопределением</p>
     <p><code>#define readn(fd, ptr, n) recv(fd, ptr, n, MSG_WAITALL)</code></p>
     <p>Даже если мы задаем флаг <code>MSG_WAITALL</code>, функция может возвратить количество байтов меньше запрашиваемого в том случае, если или перехватывается сигнал, или соединение завершается, или есть ошибка сокета, требующая обработки.</p>
     <p>Существуют дополнительные флаги, используемые протоколами, отличными от TCP/IP. Например, транспортный уровень OSI основан на записях (а не на потоке байтов, как TCP), и для операций вывода поддерживает флаг <code>MSG_EOR</code>, задающий конец логической записи.</p>
     <p>С аргументом <code>flags</code> связана одна фундаментальная проблема: он передается по значению и не является аргументом типа «значение-результат». Следовательно, он может использоваться только для передачи флагов от процесса к ядру. Ядро не может передать флаги обратно процессу. Это не представляет проблемы с TCP/IP, поскольку очень редко бывает необходимо передавать флаги обратно процессу от ядра. Но когда к 4.3BSD Reno были добавлены протоколы OSI, появилась необходимость возвращать процессу флаг <code>MSG_EOR</code> при операции ввода. В 4.3BSD Reno было принято решение оставить аргументы для общеупотребительных функций (<code>recv</code> и <code>recvfrom</code>) как есть и изменить структуру <code>msghdr</code>, которая используется с функциями <code>recvmsg</code> и <code>sendmsg</code>. В разделе 14.5 мы увидим, что в эту структуру был добавлен целочисленный элемент <code>msg_flags</code>, и поскольку структура передается по ссылке, ядро может изменить флаги, содержащиеся в этом элементе, по завершении функции. Это значит также, что если процессу необходимо, чтобы флаги изменялись ядром, процесс должен вызвать функцию <code>recvmsg</code> вместо вызова функции <code>recv</code> или <code>recvfrom</code>.</p>
    </section>
    <section>
     <title>
      <p>14.4. Функции readv и writev</p>
     </title>
     <p>Эти две функции аналогичны функциям <code>read</code> и <code>write</code>, но <code>readv</code> и <code>writev</code> позволяют использовать для чтения или записи один или более буферов с помощью одного вызова функции. Эти операции называются операциями <emphasis>распределяющего чтения</emphasis> (<emphasis>scatter read</emphasis>) (поскольку вводимые данные распределяются по нескольким буферам приложения) и <emphasis>объединяющей записи</emphasis> (<emphasis>gather write</emphasis>) (поскольку данные из нескольких буферов объединяется для одной операции вывода).</p>
     <p><code>#include &lt;sys/uio.h&gt;</code></p>
     <empty-line/>
     <p><code>ssize_t readv(int <emphasis>filedes</emphasis>, const struct iovec *<emphasis>iov</emphasis>, int <emphasis>iovcnt</emphasis>);</code></p>
     <p><code>ssize_t writev(int <emphasis>filedes</emphasis>, const struct iovec *<emphasis>iov</emphasis>, int <emphasis>iovcnt</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают: количество считанных или записанных байтов, -1 в случае ошибки</emphasis></code></p>
     <p>Второй аргумент обеих функций — это указатель на массив структур iovec, для определения которого требуется включить заголовочный файл <code>&lt;sys/uio.h&gt;</code>:</p>
     <p><code>struct iovec {</code></p>
     <p><code> void *iov_base; /* начальный адрес буфера */</code></p>
     <p><code> size_t iov_len; /* размер буфера */</code></p>
     <p><code>};</code></p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Типы данных элементов структуры iovec определяются POSIX. Вам могут встретиться реализации, определяющие iov_base как char*, a iov_len как int.</p>
     </cite>
     <p>Существует некоторый предел числа элементов в массиве структур iovec, зависящий от реализации. Linux позволяет использовать до 1024 элементов, а HP-UD — до 2100. POSIX требует, чтобы константа <code>IOV_MAX</code> определялась включением заголовочного файла <code>&lt;sys/uio.h&gt;</code> и чтобы ее значение было не менее 16.</p>
     <p>Функции <code>readv</code> и <code>writev</code> могут использоваться с любым дескриптором, а не только с сокетами. Кроме того, <code>writev</code> является атомарной операцией. Для протокола, основанного на записях, такого как UDP, один вызов функции <code>writev</code> генерирует одну дейтаграмму UDP.</p>
     <p>Мы отметили одно использование функции <code>writev</code> с параметром сокета <code>TCP_NODELAY</code> в разделе 7.9. Мы сказали, что при записи с помощью функции <code>write</code> 4 байт и затем 396 байт может активизироваться алгоритм Нагла, и предпочтительное решение в данном случае заключается в вызове функции <code>writev</code> для двух буферов.</p>
    </section>
    <section>
     <title>
      <p>14.5. Функции recvmsg и sendmsg</p>
     </title>
     <p>Эти две функции являются наиболее общими для всех операций ввода-вывода. Действительно, мы можем заменить все вызовы функций ввода <code>read</code>, <code>readv</code>, <code>recv</code> и <code>recvfrom</code> вызовами функции <code>recvmsg</code>. Аналогично, все вызовы различных функций вывода можно заменить вызовами функции <code>sendmsg</code>.</p>
     <p><code>#include &lt;sys/socket.h&gt;</code></p>
     <empty-line/>
     <p><code>ssize_t recvmsg(int <emphasis>sockfd</emphasis>, struct msghdr *<emphasis>msg</emphasis>, int <emphasis>flags</emphasis>);</code></p>
     <p><code>ssize_t sendmsg(int <emphasis>sockfd</emphasis>, struct msghdr *<emphasis>msg</emphasis>, int <emphasis>flags</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают: количество прочитанных или записанных байтов в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Большинство аргументов обеих функций скрыто в структуре <code>msghdr</code>:</p>
     <p><code>struct msghdr {</code></p>
     <p><code> void         *msg_name;     /* адрес протокола */</code></p>
     <p><code> socklen_t    msg_namelen;   /* размер адреса протокола */</code></p>
     <p><code> struct iovec *msg_iov;      /* массив буферов */</code></p>
     <p><code> size_t       msg_iovlen;    /* количество элементов в массиве msg_iov */</code></p>
     <p><code> void         *msg_control;  /* вспомогательные данные: должны быть</code></p>
     <p><code>                                выровнены для структуры cmsghdr */</code></p>
     <p><code> socklen_t    msg_controllen; /* размер вспомогательных данных */</code></p>
     <p><code> int          msg_flags;      /* флаги, возвращенные функцией recvmsg() */</code></p>
     <p><code>};</code></p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Показанная нами структура msghdr восходит к 4.3BSD Reno и определяется POSIX. Некоторые системы (например, Solaris 2.5) используют более раннюю структуру msghdr, которая появилась в 4.2BSD. У более ранней структуры нет элемента msg_flags, а элементы msg_control и msg_controllen называются msg_accrights и msg_accrightslen. В этой системе поддерживается только одна форма вспомогательных данных — передача дескрипторов файлов (так называемые права доступа). При появлении протоколов OSI в 4.3BSD Reno были добавлены новые формы вспомогательных данных, вследствие чего были обобщены имена элементов структуры.</p>
     </cite>
     <p>Элементы <code>msg_name</code> и <code>msg_namelen</code> используются, когда сокет не является присоединенным (например, неприсоединенный сокет UDP). Они аналогичны пятому и шестому аргументам функций <code>recvfrom</code> и <code>sendto</code>: <code>msg_name</code> указывает на структуру адреса сокета, в которой вызывающий процесс хранит адрес протокола получателя для функции <code>sendmsg</code> или функция <code>recvmsg</code> хранит адрес протокола отправителя. Если нет необходимости задавать адрес протокола (например, сокет TCP или присоединенный сокет UDP), элемент <code>msg</code>_name должен быть пустым указателем. Элемент <code>msg_namelen</code> является аргументом типа «значение» для функции <code>sendmsg</code>, но для функции <code>recvmsg</code> это аргумент типа «значение-результат».</p>
     <p>Элементы <code>msg_iov</code> и <code>msg_iovlen</code> задают массив буферов ввода и вывода (массив структур <code>iovec</code>), аналогичный второму и третьему аргументам функций <code>readv</code> и <code>writev</code>.</p>
     <p>Элементы <code>msg_control</code> и <code>msg_controllen</code> задают расположение и размер необязательных вспомогательных данных. Элемент <code>msg_controllen</code> — это аргумент типа «значение-результат» функции <code>recvmsg</code>. Вспомогательные данные мы рассматриваем в разделе 14.6.</p>
     <p>Работая с функциями <code>recvmsg</code> и <code>sendmsg</code>, следует учитывать различие между двумя флаговыми переменными: это аргумент <code>flags</code>, который передается по значению, и элемент <code>msg_flags</code> структуры <code>msghdr</code>, который передается по ссылке (поскольку функции передается адрес этой структуры).</p>
     <p>&#9632; Элемент <code>msg_flags</code> используется только функцией <code>recvmsg</code>. Когда вызывается функция <code>recvmsg</code>, аргумент <code>flags</code> копируется в элемент <code>msg_flags</code> [128, с. 502], и это значение используется ядром для управления приемом данных. Затем это значение обновляется в зависимости от результата функции <code>recvmsg</code>.</p>
     <p>&#9632; Элемент <code>msg_flags</code> игнорируется функцией <code>sendmsg</code>, поскольку эта функция использует аргумент <code>flags</code> для управления выводом данных. Это значит, что если мы хотим установить флаг <code>MSG_DONTWAIT</code> при вызове функции <code>sendmsg</code>, то мы должны присвоить это значение аргументу <code>flags</code>, а присваивание значения <code>MSG_DONTWAIT</code> элементу <code>msg_flags</code> не имеет никакого эффекта.</p>
     <p>В табл. 14.2 показано, какие флаги проверяются ядром для функций ввода и вывода и какие элементы <code>msg_flags</code> может возвращать функция <code>recvmsg</code>. Для элемента <code>sendmsg.msg_flags</code> нет колонки, потому что, как мы отмечали, он не используется.</p>
     <empty-line/>
     <p><strong>Таблица 14.2</strong>. Флаги для различных функций ввода-вывода</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Флаг</th>
       <th align="left" valign="top">Проверяются функциями send <emphasis>flags</emphasis> sendto <emphasis>flags</emphasis> sendmsg <emphasis>flags</emphasis></th>
       <th align="left" valign="top">Проверяются функциями recv <emphasis>flags</emphasis> recvfrom <emphasis>flags</emphasis> recvmsg <emphasis>flags</emphasis></th>
       <th align="left" valign="top">Возвращаются функцией recvmsg <emphasis>msg_flags</emphasis></th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_DONTROUTE</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_DONTWAIT</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_PEEK</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_WAITALL</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_EOR</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_OOB</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_BCAST</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_MCAST</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_TRUNC</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MSG_CTRUNC</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
      </tr>
     </table>
     <p>Первые четыре флага только проверяются и никогда не возвращаются, вторые два проверяются и возвращаются, а последние четыре флага только возвращаются. Следующие ниже комментарии относятся к шести флагам, возвращаемым функцией <code>recvmsg</code>.</p>
     <p>&#9632; <code>MSG_BCAST</code>. Этот флаг введен в BSD/OS и возвращается, если дейтаграмма была получена как широковещательная дейтаграмма канального уровня или если ее IP-адрес получателя является широковещательным адресом. Этот флаг предоставляет более удачную возможность определить, что дейтаграмма UDP была отправлена на широковещательный адрес, чем параметр сокета <code>IP_RECVDSTADDR</code>.</p>
     <p>&#9632; <code>MSG_MCAST</code>. Этот флаг введен в BSD_OS и возвращается, если дейтаграмма была получена как дейтаграмма многоадресной передачи канального уровня.</p>
     <p>&#9632; <code>MSG_TRUNC</code>. Этот флаг возвращается, если дейтаграмма была усечена: у ядра имеется больше данных для возвращения, чем позволяет пространство в памяти, выделенное для них процессом (сумма всех элементов <code>iov_len</code>). Более подробно мы рассмотрим это в разделе 22.3.</p>
     <p>&#9632; <code>MSG_CTRUNC</code>. Этот флаг возвращается, если были усечены вспомогательные данные: у ядра имеется больше вспомогательных данных для возвращения, чем позволяет выделенное для них процессом пространство в памяти (<code>msg_controllen</code>).</p>
     <p>&#9632; <code>MSG_EOR</code>. Этот флаг означает конец записи. Он сбрасывается, если возвращаемые данные не заканчивают запись. Если же возвращаемые данные заканчивают логическую запись, этот флаг устанавливается. TCP не использует этот флаг, поскольку это потоковый протокол.</p>
     <p>&#9632; <code>MSG_OOB</code>. Этот флаг никогда не возвращается для внеполосных данных TCP. Он возвращается другими наборами протоколов (например, протоколами OSI).</p>
     <p>Реализации могут возвращать некоторые из входных аргументов <code>flags</code> в элементе <code>msg_flags</code>, поэтому мы должны проверять только те значения флагов, которые нас интересуют (например, последние шесть в табл. 14.2).</p>
     <p>На рис. 14.1 представлена структура <code>msghdr</code> и информация, на которую она указывает. На этом рисунке отражена ситуация, предшествующая вызову функции <code>recvmsg</code> для сокета UDP.</p>
     <image l:href="#img_96.png"/>
     <p><strong>Рис. 14.1</strong>. Структуры данных в тот момент, когда функция recvmsg вызывается для сокета UDP</p>
     <p>Для адреса протокола в памяти выделяется 16 байт, а для вспомогательных данных — 20 байт. Инициализируется массив из трех структур iovec: первая задает 100-байтовый буфер, вторая — 60-байтовый буфер, третья — 80-байтовый буфер. Мы также предполагаем, что был установлен параметр сокета <code>IP_RECVDSTADDR</code> для получения IP-адреса получателя из дейтаграммы UDP.</p>
     <p>Затем будем считать, что с адреса 198.6.38.100, порт 2000, приходит 170-байтовая дейтаграмма UDP, предназначенная для нашего сокета UDP с IP-адресом получателя 206.168.112.96. На рис. 14.2 показана вся информация, содержащаяся в структуре <code>msghdr</code> в момент завершения функции <code>recvmsg</code>.</p>
     <image l:href="#img_97.png"/>
     <p><strong>Рис. 14.2</strong>. Изменение рис. 14.1 при завершении функции</p>
     <p>Затемненными показаны поля, изменяемые функцией <code>recvmsg</code>. По сравнению с рис. 14.1 на рис. 14.2 изменяется следующее:</p>
     <p>&#9632; В буфер, на который указывает элемент <code>msg_name</code>, записывается структура адреса сокета Интернета, содержащая IP-адрес и UDP-порт отправителя, определенные по полученной дейтаграмме.</p>
     <p>&#9632; Обновляется аргумент <code>msg_namelen</code>, имеющий тип «значение-результат». Его новым значением становится количество данных, хранящихся в <code>msg_name</code>. Но на самом деле его значение как перед вызовом функции <code>recvmsg</code>, так и при ее завершении равно 16.</p>
     <p>&#9632; Первые 100 байт данных записываются в первый буфер, следующие 60 байт — во второй буфер и последние 10 байт — в третий буфер. Последние 70 байт третьего буфера не изменяются. Возвращаемое значение функции <code>recvmsg</code> — это размер дейтаграммы (170).</p>
     <p>&#9632; Буфер, на который указывает <code>msg_control</code>, заполняется как структура <code>cmsghdr</code>. (Более подробно о вспомогательных данных мы поговорим в разделе 14.6, а об этом параметре сокета — в разделе 22.2.) Значение <code>cmsg_len</code> равно 16, <code>cmsg_level</code> — <code>IPPROTO_IP</code>, <code>cmsg_type</code> — <code>IP_RECVDSTADDR</code>, а следующие 4 байта 20-байтового буфера содержат IP-адрес получателя из полученной дейтаграммы UDP. Последние 4 байта 20-байтового буфера, которые мы предоставили для хранения вспомогательных данных, не изменяются.</p>
     <p>&#9632; Обновляется элемент <code>msg_controllen</code> — его новым значением становится фактический размер записанных вспомогательных данных. Этот аргумент также является аргументом типа «значение-результат», и его результат по завершении функции равен 16.</p>
     <p>&#9632; Элемент <code>msg_flags</code> изменяется функцией <code>recvmsg</code>, но процессу никакие флаги не возвращаются.</p>
     <p>В табл. 14.3 показаны различия между рассмотренными пятью группами функций ввода-вывода.</p>
     <empty-line/>
     <p><strong>Таблица 14.3</strong>. Сравнение пяти групп функций ввода-вывода</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Функция</th>
       <th align="left" valign="top">Произвольный дескриптор</th>
       <th align="left" valign="top">Только дескриптор сокета</th>
       <th align="left" valign="top">Один буфер для чтения и записи</th>
       <th align="left" valign="top">Распределяющее чтение, объединяющая запись</th>
       <th align="left" valign="top">Наличие флагов</th>
       <th align="left" valign="top">Указание адреса собеседника</th>
       <th align="left" valign="top">Управляющая информация</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">read, write</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">readv, writev</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">recv, send</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">recvfrom, sendto</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td align="left" valign="top">recvmsg, sendsg</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
      </tr>
     </table>
    </section>
    <section>
     <title>
      <p>14.6. Вспомогательные данные</p>
     </title>
     <p>Вспомогательные данные (ancillary data) можно отправлять и получать, используя элементы <code>msg_control</code> и <code>msg_controllen</code> структуры <code>msghdr</code> с функциями <code>sendmsg</code> и <code>recvmsg</code>. Другой термин, используемый для обозначения вспомогательных данных, — <emphasis>управляющая информация</emphasis> (<emphasis>control information</emphasis>). В этом разделе мы рассматриваем данное понятие и показываем структуру и макросы, используемые для создания и обработки вспомогательных данных. Примеры программ мы откладываем до следующих глав, в которых рассказывается о применении вспомогательных данных.</p>
     <p>В табл. 14.4 приводится обобщение различных вариантов применения вспомогательных данных, рассматриваемых в этой книге.</p>
     <empty-line/>
     <p><strong>Таблица 14.4</strong>. Использование вспомогательных данных</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Протокол</th>
       <th align="left" valign="top">cmsg_level</th>
       <th align="left" valign="top">cmsg_type</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td rowspan="2" align="left" valign="top">IPv4</td>
       <td rowspan="2" align="left" valign="top">IPPROTO_IP</td>
       <td align="left" valign="top">IP_RECVDSTADDR</td>
       <td align="left" valign="top">Получает адрес получателя с дейтаграммой UDP</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_RECVIF</td>
       <td align="left" valign="top">Получает индекс интерфейса с дейтаграммой UDP</td>
      </tr>
      <tr align="left">
       <td rowspan="6" align="left" valign="top">IPv6</td>
       <td rowspan="6" align="left" valign="top">IPPROTO_IPV6</td>
       <td align="left" valign="top">IPV6_DSTOPTS</td>
       <td align="left" valign="top">Задает/получает параметры получателя</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_HOPLIMIT</td>
       <td align="left" valign="top">Задает/получает предел количества транзитных узлов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_HOPOPTS</td>
       <td align="left" valign="top">Задает/получает параметры для транзитных узлов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_NEXTHOP</td>
       <td align="left" valign="top">Задает следующий транзитный адрес</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_PKTINFO</td>
       <td align="left" valign="top">Задает/получает информацию о пакете</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_RTHDR</td>
       <td align="left" valign="top">Задает/получает информацию о пакете</td>
      </tr>
      <tr align="left">
       <td rowspan="2" align="left" valign="top">Домен Unix</td>
       <td rowspan="2" align="left" valign="top">SOL_SOCKET</td>
       <td align="left" valign="top">SCM_RIGHTS</td>
       <td align="left" valign="top">Посылает/получает дескрипторы</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SCM_CREDS</td>
       <td align="left" valign="top">Посылает/получает данные, идентифицирующие пользователя</td>
      </tr>
     </table>
     <p>Набор протоколов OSI также использует вспомогательные данные для различных целей, которые мы не рассматриваем в этой книге.</p>
     <p>Вспомогательные данные состоят из одного или более <emphasis>объектов вспомогательных данных</emphasis> (<emphasis>ancillary data objects</emphasis>), каждый из которых начинается со структуры <code>cmsghdr</code>, определяемой подключением заголовочного файла <code>&lt;sys/socket.h&gt;</code>:</p>
     <p><code>struct cmsghdr {</code></p>
     <p><code> socklen_t cmsg_len;   /* длина структуры в байтах */</code></p>
     <p><code> int       cmsg_level; /* исходящий протокол */</code></p>
     <p><code> int       cmsg_type;  /* тип данных, специфичный для протокола */</code></p>
     <p><code> /* далее следует массив символов без знака cmsg_data[] */</code></p>
     <p><code>};</code></p>
     <p>Мы уже видели эту структуру на рис. 14.2, когда она использовалась с параметром сокета <code>IP_RECVDSTADDR</code> для возвращения IP-адреса получателя полученной дейтаграммы UDP. Вспомогательные данные, на которые указывает элемент <code>msg_control</code>, должны быть соответствующим образом выровнены для структуры <code>cmsghdr</code>. Один из способов выравнивания мы показываем в листинге 15.7.</p>
     <p>На рис. 14.3 приводится пример двух объектов вспомогательных данных, содержащихся в буфере управляющей информации.</p>
     <image l:href="#img_98.png"/>
     <p><strong>Рис. 14.3</strong>. Два объекта вспомогательных данных</p>
     <p>Элемент <code>msg_control</code> указывает на первый объект вспомогательных данных, а общая длина вспомогательных данных задается элементом <code>msg_controllen</code>. Каждому объекту предшествует структура <code>cmsghdr</code>, которая описывает объект. Между элементом <code>cmsg_type</code> и фактическими данными может существовать заполнение, а также заполнение может быть в конце данных, перед следующим объектом вспомогательных данных. Пять макросов <code>CMSG_xxx</code>, которые мы описываем далее, учитывают это возможное заполнение.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Не все реализации поддерживают наличие нескольких объектов вспомогательных данных в буфере управляющей информации.</p>
     </cite>
     <empty-line/>
     <p>На рис. 14.4 приводится формат структуры <code>cmsghdr</code> при ее использовании с доменным сокетом Unix для передачи дескрипторов (см. раздел 15.7) или передачи данных, идентифицирующих пользователя (см. раздел 15.8).</p>
     <image l:href="#img_99.png"/>
     <p><strong>Рис. 14.4</strong>. Структура cmsghdr при использовании с доменными сокетами Unix</p>
     <p>Предполагается, что каждый из трех элементов структуры <code>cmsghdr</code> занимает 4 байта и между структурой <code>cmsghdr</code> и данными нет заполнения. При передаче дескрипторов содержимое массива <code>cmsg_data</code> — это фактические значения дескрипторов. На этом рисунке мы показываем только один передаваемый дескриптор, но в общем может передаваться и более одного дескриптора (тогда значение элемента <code>cmsg_len</code> будет равно 12 плюс число дескрипторов, умноженное на 4, если считать, что каждый дескриптор занимает 4 байта).</p>
     <p>Вспомогательные данные, возвращаемые функцией <code>recvmsg</code>, могут содержать любое число объектов вспомогательных данных. Чтобы скрыть возможное заполнение от приложения, для упрощения обработки вспомогательных данных определены следующие пять макросов (что требует включения заголовочного файла <code>&lt;sys/socket.h&gt;</code>).</p>
     <p><code>#include &lt;sys/socket.h&gt;</code></p>
     <p><code>#include &lt;sys/param.h&gt; /* для макроса ALIGN во многих реализациях */</code></p>
     <empty-line/>
     <p><code>struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *<emphasis>mhdrptr</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: указатель на первую структуру cmsghdr или NULL, если нет вспомогательных данных</emphasis></code></p>
     <empty-line/>
     <p><code>struct cmsghdr *CMSG_NXTHDR(struct msghdr *<emphasis>mhdrptr</emphasis>, struct cmsghdr *<emphasis>cmsgptr</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: указатель на структуру cmsghdr или NULL, если нет больше объектов вспомогательных данных</emphasis></code></p>
     <empty-line/>
     <p><code>unsigned char *CMSG_DATA(struct cmsghdr *<emphasis>cmsgptr</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: указатель на первый байт данных, связанных со структурой cmsghdr</emphasis></code></p>
     <empty-line/>
     <p><code>unsigned int CMSG_LEN(unsigned int <emphasis>length</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: значение, которое записывается в cmsg_len</emphasis></code></p>
     <empty-line/>
     <p><code>unsigned int CMSG_SPACE(unsigned int <emphasis>length</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: общий размер объекта вспомогательных данных</emphasis></code></p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В POSIX определены первые пять макросов, а в [113] определены последние два.</p>
     </cite>
     <p>Эти макросы могли бы быть использованы в следующем псевдокоде:</p>
     <p><code>struct msghdr msg;</code></p>
     <p><code>struct cmsghdr *cmsgptr;</code></p>
     <empty-line/>
     <p><code>/* заполнение структуры msg */</code></p>
     <empty-line/>
     <p><code>/* вызов recvmsg() */</code></p>
     <empty-line/>
     <p><code>for (cmsgptr = CMSG_FIRSTHDR(&amp;msg); cmsgptr != NULL;</code></p>
     <p><code> cmsgptr = CMSG_NXTHDR(&amp;msg, cmsgptr)) {</code></p>
     <p><code> if (cmsgptr-&gt;cmsg_level == ... &amp;&amp;</code></p>
     <p><code>  cmsgptr-&gt;cmsg_type == ...) {</code></p>
     <p><code>  u_char *ptr;</code></p>
     <empty-line/>
     <p><code>  ptr = CMSG_DATA(cmsgptr);</code></p>
     <p><code>  /* обработка данных, на которые указывает ptr */</code></p>
     <p><code> }</code></p>
     <p><code>}</code></p>
     <p>Макрос <code>CMSG_FIRSTHDR</code> возвращает указатель на первый объект вспомогательных данных или пустой указатель, если в структуре <code>msghdr</code> нет вспомогательных данных (или <code>msg_control</code> является пустым указателем, или <code>cmsg_len</code> меньше размера структуры <code>cmsghdr</code>). Макрос <code>CMSG_NXTHDR</code> возвращает пустой указатель, когда в буфере управления нет другого объекта вспомогательных данных.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Многие существующие реализации макроса CMSG_FIRSTHRD никогда не используют элемент msg_controllen и просто возвращают значение cmsg_control. В листинге 22.2 мы проверяем значение msg_controllen перед вызовом макроопределения.</p>
     </cite>
     <p>Разница между макросами <code>CMSG_LEN</code> и <code>CMSG_SPACE</code> заключается в том, что первый возвращает длину объекта вместе с дополняющими нулями (это значение хранится в <code>cmsg_len</code>), а последний возвращает длину собственно объекта (это значение может использоваться для динамического выделения памяти под объект).</p>
    </section>
    <section>
     <title>
      <p>14.7. Сколько данных находится в очереди?</p>
     </title>
     <p>Иногда требуется узнать, сколько данных находится в очереди для чтения данного сокета, не считывая эти данные. Для этого имеется три способа.</p>
     <p>1. Если нашей целью не является блокирование в ядре (поскольку мы можем выполнять другие задачи, пока данные для чтения еще не готовы), может использоваться неблокируемый ввод-вывод. Мы обсуждаем его в главе 16.</p>
     <p>2. Если мы хотим проверить данные, но при этом оставить их в приемном буфере для считывания какой-либо другой частью процесса, мы можем использовать флаг <code>MSG_PEEK</code> (см. табл. 14.1). Если мы не уверены, что какие-либо данные готовы для чтения, мы можем объединить этот флаг с отключением блокировки для сокета или с флагом <code>MSG_DONTWAIT</code>.</p>
     <p>Помните о том, что для потокового сокета количество данных в приемном буфере может изменяться между двумя последовательными вызовами функции <code>recv</code>. Например, предположим, что мы вызываем recv для сокета TCP, задавая буфер длиной 1024 и флаг <code>MSG_PEEK</code>, и возвращаемое значение равно 100. Если затем мы снова вызовем функцию recv, возможно, возвратится более 100 байт (мы задаем длину буфера больше 100), поскольку в промежутке между двумя нашими вызовами <code>recv</code> могли быть получены дополнительные данные.</p>
     <p>А что произойдет в случае сокета UDP, когда в приемном буфере имеется дейтаграмма? При вызове <code>recvfrom</code> с флагом <code>MSG_PEEK</code>, за которым последует другой вызов без задания <code>MSG_PEEK</code>, возвращаемые значения обоих вызовов (размер дейтаграммы, ее содержимое и адрес отправителя) будут совпадать, даже если в приемный буфер сокета между двумя вызовами добавляются дополнительные дейтаграммы. (Мы считаем, конечно, что никакой другой процесс не использует тот же дескриптор и не осуществляет чтение из данного сокета в это же время.)</p>
     <p>3. Некоторые реализации поддерживают команду <code>FIONREAD</code> функции <code>ioctl</code>. Третий аргумент функции <code>ioctl</code> — это указатель на целое число, а возвращаемое в этом целом числе значение — это текущее число байтов в приемном буфере сокета [128, с. 553]. Это значение является общим числом установленных в очередь байтов, которое для сокета UDP включает все дейтаграммы, установленные в очередь. Также помните о том, что значение, возвращаемое для сокета UDP, в Беркли-реализациях включает пространство, требуемое для структуры адреса сокета, содержащей IP-адрес отправителя и порт для каждой дейтаграммы (16 байт для IP4, 24 байта для IP6).</p>
    </section>
    <section>
     <title>
      <p>14.8. Сокеты и стандартный ввод-вывод</p>
     </title>
     <section>
      <p>Во всех наших примерах мы применяли то, что иногда называется <emphasis>вводом-выводом Unix</emphasis>, вызывали функции <code>read</code> и <code>write</code> и их разновидности (<code>recv</code>, <code>send</code> и т.д.). Эти функции работают с дескрипторами и обычно реализуются как системные вызовы внутри ядра Unix.</p>
      <p>Другой метод выполнения ввода-вывода заключается в использовании <emphasis>стандартной библиотеки ввода-вывода</emphasis>. Она задается стандартом ANSI С и была задумана как библиотека, совместимая с не-Unix системами, поддерживающими ANSI С. Стандартная библиотека ввода-вывода обрабатывает некоторые моменты, о которых мы должны заботиться сами при использовании функций ввода- вывода Unix, таких как автоматическая буферизация потоков ввода и вывода. К сожалению, ее обработка буферизации потока может представить новый ряд проблем, о которых следует помнить. Глава 5 [110] подробно описывает стандартную библиотеку ввода-вывода, а в [92] представлена полная реализация стандартной библиотеки ввода-вывода и ее обсуждение.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>При обсуждении стандартной библиотеки ввода-вывода используется термин «поток» в выражениях типа «мы открываем поток ввода» или «мы очищаем поток вывода». Не путайте это с подсистемой потоков STREAMS, которую мы обсуждаем в главе 31.</p>
      </cite>
      <p>Стандартная библиотека ввода-вывода может использоваться с сокетами, но есть несколько моментов, которые необходимо при этом учитывать.</p>
      <p>&#9632; Стандартный поток ввода-вывода может быть создан из любого дескриптора при помощи вызова функции <code>fdopen</code>. Аналогично, имея стандартный поток ввода-вывода, мы можем получить соответствующий дескриптор, вызывая функцию <code>fileno</code>. С функцией <code>fileno</code> мы впервые встретились в листинге 6.1, когда мы хотели вызвать функцию <code>select</code> для стандартного потока ввода-вывода. Функция <code>select</code> работает только с дескрипторами, поэтому нам необходимо было получить дескриптор для стандартного потока ввода-вывода.</p>
      <p>&#9632; Сокеты TCP и UDP являются двусторонними. Стандартные потоки ввода- вывода также могут быть двусторонними: мы просто открываем поток типа <code>r+</code>, что означает чтение-запись. Но в таком потоке за функцией вывода не может следовать функция ввода, если между ними нет вызова функции <code>fflush</code>, <code>fseek</code>, <code>fsetpots</code> или <code>rewind</code>. Аналогично, за функцией вывода не может следовать функция ввода, если между ними нет вызова функции <code>fseek</code>, <code>fsetpots</code>, <code>rewind</code>, в том случае, когда при вводе не получен признак конца файла. Проблема с последними тремя функциями состоит в том, что все они вызывают функцию <code>lseek</code>, которая не работает с сокетами.</p>
      <p>&#9632; Простейший способ обработки подобной проблемы чтения-записи — это открытие двух стандартных потоков ввода-вывода для данного сокета: одного для чтения и другого для записи.</p>
     </section>
     <section>
      <title>
       <p>Пример: функция str_echo, использующая стандартный ввод-вывод</p>
      </title>
      <p>Сейчас мы модифицируем наш эхо-сервер TCP (см. листинг 5.2) для использования стандартного ввода-вывода вместо функций <code>readline</code> и <code>writen</code>. В листинге 14.6 представлена версия нашей функции <code>str_echo</code>, использующая стандартный ввод-вывод. (С этой версией связана проблема, которую мы вскоре опишем.)</p>
      <p><strong>Листинг 14.6</strong>. Функция str_echo, переписанная с использованием стандартного ввода-вывода</p>
      <p><code>//advio/str_echo_stdiо02.с</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 str_echo(int sockfd)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  char line[MAXLINE];</code></p>
      <p><code> 6  FILE *fpin, *fpout;</code></p>
      <empty-line/>
      <p><code> 7  fpin = Fdopen(sockfd, "r");</code></p>
      <p><code> 8  fpout = Fdopen(sockfd, "w");</code></p>
      <empty-line/>
      <p><code> 9  while (Fgets(line, MAXLINE, fpin) != NULL)</code></p>
      <p><code>10  Fputs(line, fpout);</code></p>
      <p><code>11 }</code></p>
      <subtitle>Преобразование дескриптора в поток ввода и поток вывода</subtitle>
      <p><code>7-10</code> Функцией <code>fdopen</code> создаются два стандартных потока ввода-вывода: один для ввода и другой для вывода. Вызовы функций <code>readline</code> и <code>writen</code> заменены вызовами функций <code>fgets</code> и <code>fputs</code>.</p>
      <p>Если мы запустим наш сервер с этой версией функции <code>str_echo</code> и затем запустим наш клиент, мы увидим следующее:</p>
      <p><code>hpux % <strong>tcpcli02 206.168.112.96</strong></code></p>
      <p><code><strong>hello, world</strong> <emphasis>мы набираем эту строку, но не получаем отражения</emphasis></code></p>
      <p><code><strong>and hi</strong>       <emphasis>и на эту строку нет ответа</emphasis></code></p>
      <p><code><strong>hello??</strong>      <emphasis>и на эту строку нет ответа</emphasis></code></p>
      <p><code><strong>^D</strong>           <emphasis>наш символ конца файла</emphasis></code></p>
      <p><code>hello, world <emphasis>затем выводятся три отраженные строки</emphasis></code></p>
      <p><code>and hi</code></p>
      <p><code>hello??</code></p>
      <p>Здесь возникает проблема буферизации, поскольку сервер ничего не отражает, пока мы не введем наш символ конца файла. Выполняются следующие шаги:</p>
      <p>&#9632; Мы набираем первую строку ввода, и она отправляется серверу.</p>
      <p>&#9632; Сервер читает строку с помощью функции <code>fgets</code> и отражает ее с помощью функции <code>fputs</code>.</p>
      <p>&#9632; Но стандартный поток ввода-вывода сервера <emphasis>полностью буферизован</emphasis> стандартной библиотекой ввода-вывода. Это значит, что библиотека копирует отраженную строку в свой стандартный буфер ввода-вывода для этого потока, но не выдает содержимое буфера в дескриптор, поскольку буфер не заполнен.</p>
      <p>&#9632; Мы набираем вторую строку ввода, и она отправляется серверу.</p>
      <p>&#9632; Сервер читает строку с помощью функции <code>fgets</code> и отражает ее с помощью функции <code>fputs</code>.</p>
      <p>&#9632; Снова стандартная библиотека ввода-вывода сервера только копирует строку в свой буфер, но не выдает содержимое буфера в дескриптор, поскольку он не заполнен.</p>
      <p>&#9632; По тому же сценарию вводится третья строка.</p>
      <p>&#9632; Мы набираем наш символ конца файла, и функция <code>str_cli</code> (см. листинг 6.2) вызывает функцию <code>shutdown</code>, посылая серверу сегмент FIN.</p>
      <p>&#9632; TCP сервера получает сегмент FIN, который читает функция <code>fgets</code>, в результате чего функция <code>fgets</code> возвращает пустой указатель.</p>
      <p>&#9632; Функция <code>str_echo</code> возвращает серверу функцию <code>main</code> (см. листинг 5.9), и дочерний процесс завершается при вызове функции <code>exit</code>.</p>
      <p>&#9632; Библиотечная функция <code>exit</code> языка С вызывает стандартную функцию очистки ввода-вывода [110, с. 162-164], и буфер вывода, который был частично заполнен нашими вызовами функции <code>fputs</code>, теперь выводит скопившиеся в нем данные.</p>
      <p>&#9632; Дочерний процесс сервера завершается, в результате чего закрывается его присоединенный сокет, клиенту отсылается сегмент FIN и заканчивается последовательность завершения соединения TCP.</p>
      <p>&#9632; Наша функция <code>str_cli</code> получает и выводит три отраженных строки.</p>
      <p>&#9632; Затем функция <code>str_cli</code> получает символ конца файла на своем сокете, и клиент завершает свою работу.</p>
      <p>Проблема здесь заключается в том, что буферизация на стороне сервера выполняется автоматически стандартной библиотекой ввода-вывода. Существует три типа буферизации, выполняемой стандартной библиотекой ввода-вывода.</p>
      <p>1. <emphasis>Полная буферизация</emphasis> (<emphasis>fully buffered</emphasis>) означает, что ввод-вывод имеет место, только когда буфер заполнен, процесс явно вызывает функцию <code>fflush</code> или процесс завершается посредством вызова функции <code>exit</code>. Обычный размер стандартного буфера ввода-вывода — 8192 байта.</p>
      <p>2. <emphasis>Буферизация по строкам</emphasis> (<emphasis>line buffered</emphasis>) означает, что ввод-вывод имеет место, только когда встречается символ перевода строки, процесс вызывает функцию <code>fflush</code> или процесс завершается вызовом функции <code>exit</code>.</p>
      <p>3. <emphasis>Отсутствие буферизации</emphasis> (<emphasis>unbuffered</emphasis>) означает, что ввод-вывод имеет место каждый раз, когда вызывается функция стандартного ввода-вывода.</p>
      <p>Большинство реализаций Unix стандартной библиотеки ввода-вывода используют следующие правила:</p>
      <p>&#9632; Стандартный поток ошибок никогда не буферизуется.</p>
      <p>&#9632; Стандартные потоки ввода и вывода буферизованы полностью, если они не подключены к терминальному устройству, в противном случае они буферизуются по строкам.</p>
      <p>&#9632; Все остальные потоки тоже буферизованы полностью, если они не подключены к терминалу, в случае чего они буферизованы по строкам.</p>
      <p>Поскольку сокет не является терминальным устройством, проблема, отмеченная с нашей функцией <code>str_echo</code> в листинге 14.6, заключается в том, что поток вывода (<code>fpot</code>) полностью буферизован. Есть два решения: мы можем сделать поток вывода буферизованным по строкам при помощи вызова функции <code>setvbuf</code> либо заставить каждую отраженную строку выводиться при помощи вызова функции <code>fflush</code> после каждого вызова функции <code>fputs</code>. Применение любого из этих изменений скорректирует поведение нашей функции <code>str_echo</code>. На практике оба варианта чреваты ошибками и могут плохо взаимодействовать с алгоритмом Нагла. В большинстве случаев оптимальным решением будет отказаться от использования стандартной библиотеки ввода-вывода для сокетов и работать с буферами, а не со строками (см. раздел 3.9). Использование стандартных функций ввода-вывода имеет смысл в тех случаях, когда потенциальный выигрыш перевешивает затруднения.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Будьте осторожны — некоторые реализации стандартной библиотеки ввода-вывода все еще вызывают проблемы при работе с дескрипторами, большими 255. Эта проблема может возникнуть с сетевыми серверами, обрабатывающими множество дескрипторов. Проверьте определение структуры FILE в вашем заголовочном файле &lt;stdio.h&gt;, чтобы увидеть, к какому типу переменных относится дескриптор.</p>
      </cite>
     </section>
    </section>
    <section>
     <title>
      <p>14.9. Расширенный опрос</p>
     </title>
     <section>
      <p>В начале этой главы мы рассказывали о способах установки таймеров для операций с сокетами. Во многих операционных системах для этого существуют функции <code>poll</code> и <code>select</code>, которые были описаны в главе 6. Ни один из этих методов еще не стандартизован POSIX, поэтому между реализациями существуют определенные различия. Код, использующий подобные механизмы, должен считаться непереносимым. Мы рассмотрим два механизма, прочие весьма похожи на них.</p>
     </section>
     <section>
      <title>
       <p>Интерфейс /dev/poll</p>
      </title>
      <p>В Solaris имеется специальный файл <code>/dev/poll</code>, с помощью которого можно опрашивать большее количество дескрипторов файлов. Проблема <code>select</code> и <code>poll</code> состоит в том, что список дескрипторов приходится передавать при каждом вызове. Устройство опроса поддерживает информацию о состоянии между вызовами, так что программа может подготовить список подлежащих опросу дескрипторов, а потом спокойно зациклиться в опросе и не заполнять список каждый раз.</p>
      <p>После открытия <code>/dev/poll</code> программа должна инициализировать массив структур <code>pollfd</code> (тех же, которые используются функцией <code>poll</code>, но в этом случае поле <code>revents</code> не используется). Затем массив передается ядру вызовом <code>write</code> (структура записывается непосредственно в <code>/dev/poll</code>). После этого программа может вызывать <code>ioctl DP_POLL</code> и ждать событий. При вызове <code>ioctl</code> передается следующая структура:</p>
      <p><code>struct dvpoll {</code></p>
      <p><code> struct pollfd* dp_fds;</code></p>
      <p><code> int            dp_nfds;</code></p>
      <p><code> int            dp_timeout;</code></p>
      <p><code>};</code></p>
      <p>Поле <code>dp_fds</code> указывает на буфер, используемый для хранения массива структур <code>pollfd</code>, возвращаемых вызовом <code>ioctl</code>. Поле <code>dp_nfds</code> задает размер буфера. Вызов <code>ioctl</code> блокируется до появления интересующих программу событий на любом из опрашиваемых дескрипторов, или до прохождения <code>dp_timeout</code> миллисекунд. При нулевом значении тайм-аута функция <code>ioctl</code> возвращается немедленно (то есть данный способ может использоваться для реализации неблокируемых сокетов). Тайм-аут, равный -1, означает неопределенно долгое ожидание.</p>
      <p>Измененный код функции <code>str_cli</code>, переписанной из листинга 6.2 с использованием <code>/dev/poll</code>, приведен в листинге 14.7.</p>
      <p><strong>Листинг 14.7</strong>. Функция str_cli, использующая /dev/poll</p>
      <p><code>//advio/str_cli_poll03.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;sys/devpoll.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 str_cli(FILE *fp, int sockfd)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int stdineof;</code></p>
      <p><code> 7  char buf[MAXLINE];</code></p>
      <p><code> 8  int n;</code></p>
      <p><code> 9  int wfd;</code></p>
      <p><code>10  struct pollfd pollfd[2];</code></p>
      <p><code>11  struct dvpoll dopoll;</code></p>
      <p><code>12  int i;</code></p>
      <p><code>13  int result;</code></p>
      <empty-line/>
      <p><code>14  wfd = Open("/dev/poll", O_RDWR, 0);</code></p>
      <empty-line/>
      <p><code>15  pollfd[0].fd = fileno(fp);</code></p>
      <p><code>16  pollfd[0].events = POLLIN;</code></p>
      <p><code>17  pollfd[0].revents = 0;</code></p>
      <empty-line/>
      <p><code>18  pollfd[1].fd = sockfd;</code></p>
      <p><code>19  pollfd[1].events = POLLIN;</code></p>
      <p><code>20  pollfd[1].revents = 0;</code></p>
      <empty-line/>
      <p><code>21  Write(wfd, pollfd, sizeof(struct pollfd) * 2);</code></p>
      <empty-line/>
      <p><code>22  stdineof = 0;</code></p>
      <p><code>23  for (;;) {</code></p>
      <p><code>24   /* блокирование до готовности сокета */</code></p>
      <p><code>25   dopoll.dp_timeout = -1;</code></p>
      <p><code>26   dopoll.dp_nfds = 2;</code></p>
      <p><code>27   dopoll.dp_fds = pollfd;</code></p>
      <p><code>28   result = Ioctl(wfd, DP_POLL, &amp;dopoll);</code></p>
      <empty-line/>
      <p><code>29   /* цикл по готовым дескрипторам */</code></p>
      <p><code>30   for (i = 0; i &lt; result; i++) {</code></p>
      <p><code>31    if (dopoll.dp_fds[i].fd == sockfd) {</code></p>
      <p><code>32     /* сокет готов к чтению */</code></p>
      <p><code>33     if ((n = Read(sockfd, buf, MAXLINE)) == 0) {</code></p>
      <p><code>34      if (stdineof == 1)</code></p>
      <p><code>35       return; /* нормальное завершение */</code></p>
      <p><code>36      else</code></p>
      <p><code>37       err_quit("str_cli: server terminated prematurely");</code></p>
      <p><code>38     }</code></p>
      <empty-line/>
      <p><code>39     Write(fileno(stdout), buf, n);</code></p>
      <p><code>40    } else {</code></p>
      <p><code>41     /* дескриптор готов к чтению */</code></p>
      <p><code>42     if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) {</code></p>
      <p><code>43      stdineof = 1;</code></p>
      <p><code>44      Shutdown(sockfd, SHUT_WR); /* отправка FIN */</code></p>
      <p><code>45      continue;</code></p>
      <p><code>46     }</code></p>
      <empty-line/>
      <p><code>47     Writen(sockfd, buf, n);</code></p>
      <p><code>48    }</code></p>
      <p><code>49   }</code></p>
      <p><code>50  }</code></p>
      <p><code>51 }</code></p>
      <subtitle>Составление списка дескрипторов для /dev/poll</subtitle>
      <p><code>14-21</code> Заполнив массив структур <code>pollfd</code>, мы передаем его в <code>/dev/poll</code>. В нашем примере используются только два файловых дескриптора, так что мы помещаем их в статический массив. На практике программы, использующие <code>/dev/poll</code>, обычно следят за сотнями или даже тысячами дескрипторов одновременно, поэтому массив выделяется динамически.</p>
      <subtitle>Ожидание данных</subtitle>
      <p><code>24-28</code> Программа не вызывает <code>select</code>, а блокируется в вызове <code>ioctl</code> в ожидании поступления данных. Возвращаемое значение представляет собой количество готовых к чтению дескрипторов файлов.</p>
      <subtitle>Цикл по дескрипторам</subtitle>
      <p><code>30-49</code> Наша программа относительно проста, потому что мы знаем, что дескрипторов всего два. В большой программе цикл будет более сложным. Возможно даже разделение программы на потоки для обработки данных, полученных по разным дескрипторам.</p>
     </section>
     <section>
      <title>
       <p>Интерфейс kqueue</p>
      </title>
      <p>Система FreeBSD версии 4.1 предложила сетевым программистам новый интерфейс, получивший название <code>kqueue</code>. Этот интерфейс позволяет процессу зарегистрировать фильтр событий, описывающий интересующие данный процесс события <code>kqueue</code>. К событиям этого типа относятся операции ввода-вывода с файлами и тайм-ауты, а также асинхронный ввод-вывод, уведомление об изменении файлов и обработка сигналов.</p>
      <p><code>#include &lt;sys/types.h&gt;</code></p>
      <p><code>#include &lt;sys/event.h&gt;</code></p>
      <p><code>#include &lt;sys/time.h&gt;</code></p>
      <empty-line/>
      <p><code>int kqueue(void);</code></p>
      <p><code>int kevent(int <emphasis>kq</emphasis>, const struct kevent *<emphasis>changelist</emphasis>, int <emphasis>nchanges</emphasis>,</code></p>
      <p><code> struct kevent *<emphasis>eventlist</emphasis>, int <emphasis>nevents</emphasis>, const struct timespec *<emphasis>timeout</emphasis>);</code></p>
      <p><code>void EV_SET(struct kevent *<emphasis>kev</emphasis>, uintptr_t <emphasis>ident</emphasis>, short <emphasis>filter</emphasis>,</code></p>
      <p><code> u_short <emphasis>flags</emphasis>, u_int <emphasis>fflags</emphasis>, intptr_t <emphasis>data</emphasis>, void *<emphasis>udata</emphasis>);</code></p>
      <p>Функция <code>kqueue</code> возвращает новый дескриптор <code>kqueue</code>, который может использоваться в последующих вызовах <code>kevent</code>. Функция <code>kevent</code> применяется для регистрации интересующих событий, а также для получения уведомлений об этих событиях. Параметры <code>changelist</code> и <code>nchanges</code> описывают изменения в предыдущем варианте списка событий. Если <code>nchanges</code> отлично от нуля, выполняются все запрошенные в структуре <code>changelist</code> изменения. Функция <code>kevent</code> возвращает количество событий или нуль, если произошел выход по тайм-ауту. В аргументе <code>timeout</code> хранится значение тайм-аута, обрабатываемое подобно тому, как при вызове <code>select</code> (<code>NULL</code> для блокирования, ненулевое значение для задания конкретного тайм- аута, а нулевое значение трактуется как необходимость неблокирующего вызова). Обратите внимание, что параметр <code>timeout</code> имеет тип <code>struct timespec</code>, отличающийся от <code>struct timeval</code> в вызове <code>select</code> тем, что первый имеет наносекундное разрешение, а второй — микросекундное.</p>
      <p>Структура <code>kevent</code> определяется в заголовочном файле <code>&lt;sys/event.h&gt;</code>:</p>
      <p><code>struct kevent {</code></p>
      <p><code> uintptr_t ident;  /* идентификатор (например, дескриптор файла) */</code></p>
      <p><code> short     filter; /* тип фильтра (например, EVFILT_READ) */</code></p>
      <p><code> u_short   flags;  /* флаги действий (например, EV_ADD); */</code></p>
      <p><code> u_int     fflags; /* флаги, относящиеся к конкретным фильтрам */</code></p>
      <p><code> intptr_t  data;   /* данные, относящиеся к конкретным фильтрам */</code></p>
      <p><code> void      uidata; /* непрозрачные пользовательские данные */</code></p>
      <p><code>};</code></p>
      <p>Действия по смене фильтра и флаговые возвращаемые значения приведены в табл. 14.5.</p>
      <empty-line/>
      <p><strong>Таблица 14.5</strong>. Флаги для операций kevent</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Значение flags</th>
        <th align="left" valign="top">Описание</th>
        <th align="left" valign="top">Изменяется</th>
        <th align="left" valign="top">Возвращается</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EV_ADD</td>
        <td align="left" valign="top">Добавить новое событие, подразумевается по умолчанию, если не указан флаг EV_DISABLE</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EV_CLEAR</td>
        <td align="left" valign="top">Сброс состояния события после считывания его пользователем</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EV_DELETE</td>
        <td align="left" valign="top">Удаление события из фильтра</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EV_DISABLE</td>
        <td align="left" valign="top">Отключение события без удаления его из фильтра</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EV_ENABLE</td>
        <td align="left" valign="top">Включение отключенного перед этим события</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EV_ONESHOT</td>
        <td align="left" valign="top">Удаление события после его однократного срабатывания</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EV_EOF</td>
        <td align="left" valign="top">Достигнут конец файла</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EV_ERROR</td>
        <td align="left" valign="top">Произошла ошибка, код errno записан в поле data</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
       </tr>
      </table>
      <p>Типы фильтров приведены в табл. 14.6.</p>
      <empty-line/>
      <p><strong>Таблица 14.6</strong>. Типы фильтров</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Значение filter</th>
        <th align="left" valign="top">Описание</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EVFILT_AIO</td>
        <td align="left" valign="top">События асинхронного ввода-вывода</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EVFILT_PROC</td>
        <td align="left" valign="top">События exit, fork, exec для процесса</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EVFILT_READ</td>
        <td align="left" valign="top">Дескриптор готов для чтения (аналогично select)</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EVFILT_SIGNAL</td>
        <td align="left" valign="top">Описание сигнала</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EVFILT_TIMER</td>
        <td align="left" valign="top">Периодические или одноразовые таймеры</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EVFILT_VNODE</td>
        <td align="left" valign="top">Изменение и удаление файлов</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EVFILT_WRITE</td>
        <td align="left" valign="top">Дескриптор готов для записи (аналогично select)</td>
       </tr>
      </table>
      <p>Перепишем функцию <code>str_cli</code> из листинга 6.2 так, чтобы она использовала <code>kqueue</code>. Результат представлен в листинге 14.8.</p>
      <p><strong>Листинг 14.8</strong>. Функция str_cli, использующая kqueue</p>
      <p><code>//advio/str_cli_kqueue04.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 str_cli(FILE *fp, int sockfd)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int kq, i, n, nev, stdineof = 0, isfile;</code></p>
      <p><code> 6  char buf[MAXLINE];</code></p>
      <p><code> 7  struct kevent kev[2];</code></p>
      <p><code> 8  struct timespec ts;</code></p>
      <p><code> 9  struct stat st;</code></p>
      <empty-line/>
      <p><code>10  isfile = ((fstat(fileno(fp), &amp;st) 0) &amp;&amp;</code></p>
      <p><code>11   (st.st_mode &amp; S_IFMT) == S_IFREG);</code></p>
      <empty-line/>
      <p><code>12  EV_SET(&amp;kev[0], fileno(fp), EVFILT_READ, EV_ADD, 0, 0, NULL);</code></p>
      <p><code>13  EV_SET(&amp;kev[1], sockfd, EVFILT_READ, EV_ADD, 0, 0, NULL);</code></p>
      <empty-line/>
      <p><code>14  kq = Kqueue();</code></p>
      <p><code>15  ts.tv_sec = ts.tv_nsec = 0;</code></p>
      <p><code>16  Kevent(kq, kev, 2, NULL, 0, &amp;ts);</code></p>
      <empty-line/>
      <p><code>17  for (;;) {</code></p>
      <p><code>18   nev = Kevent(kq, NULL, 0, kev, 2, NULL);</code></p>
      <empty-line/>
      <p><code>19   for (i = 0; i &lt; nev; i++) {</code></p>
      <p><code>20    if (kev[i].ident == sockfd) { /* сокет готов для чтения */</code></p>
      <p><code>21     if ((n = Read(sockfd, buf, MAXLINE)) == 0) {</code></p>
      <p><code>22      if (stdineof == 1)</code></p>
      <p><code>23       return; /* нормальное завершение*/</code></p>
      <p><code>24      else</code></p>
      <p><code>25       err_quit("str_cli: server terminated prematurely");</code></p>
      <p><code>26     }</code></p>
      <empty-line/>
      <p><code>27     Write(fileno(stdout), buf, n);</code></p>
      <p><code>28    }</code></p>
      <empty-line/>
      <p><code>29    if (kev[i].ident == fileno(fp)) { /* входной поток готов к чтению */</code></p>
      <p><code>30     n = Read(fileno(fp), buf, MAXLINE);</code></p>
      <p><code>31     if (n &gt; 0)</code></p>
      <p><code>32      Writen(sockfd, buf, n);</code></p>
      <empty-line/>
      <p><code>33     if (n == 0 || (isfile &amp;&amp; n == kev[i].data)) {</code></p>
      <p><code>34      stdineof = 1;</code></p>
      <p><code>35      Shutdown(sockfd, SHUT_WR); /* отправка FIN */</code></p>
      <p><code>36      kev[i].flags = EV_DELETE;</code></p>
      <p><code>37      Kevent(kq, &amp;kev[i], 1, NULL, 0, &amp;ts); /* удаление</code></p>
      <p><code>                                                 kevent */</code></p>
      <p><code>38      continue;</code></p>
      <p><code>39     }</code></p>
      <p><code>40    }</code></p>
      <p><code>41   }</code></p>
      <p><code>42  }</code></p>
      <p><code>43 }</code></p>
      <subtitle>Проверка, указывает ли дескриптор на файл</subtitle>
      <p><code>10-11</code> Поведение <code>kqueue</code> при достижении конца файла зависит от того, связан ли данный дескриптор с файлом, каналом или терминалом, поэтому мы вызываем <code>fstat</code>, чтобы убедиться, что мы работаем с файлом. Эти сведения понадобятся позже.</p>
      <subtitle>Настройка структур kevent для kqueue</subtitle>
      <p><code>12-13</code> При помощи макроса <code>EV_SET</code> мы настраиваем две структуры <code>kevent</code>. Обе содержат фильтр событий готовности к чтению (<code>EVFILT_READ</code>) и запрос на добавление этого события к фильтру (<code>EV_ADD</code>).</p>
      <subtitle>Создание kqueue и добавление фильтров</subtitle>
      <p><code>14-16</code> Мы вызываем <code>kqueue</code>, чтобы получить дескриптор <code>kqueue</code>, устанавливаем тайм- аут равным нулю, чтобы сделать вызов <code>kevent</code> неблокируемым, и наконец, вызываем <code>kevent</code> с массивом <code>kevent</code> на месте соответствующего аргумента.</p>
      <subtitle>Бесконечный цикл с блокированием в kevent</subtitle>
      <p><code>17-18</code> Мы входим в бесконечный цикл и блокируемся в <code>kevent</code>. Функции передается пустой список изменений, потому что все интересующие нас события уже зарегистрированы, и нулевой тайм-аут, что позволяет заблокироваться навечно.</p>
      <subtitle>Перебор возвращаемых событий в цикле</subtitle>
      <p><code>19</code> Мы проверяем все возвращаемые события и обрабатываем их последовательно.</p>
      <subtitle>Сокет готов для чтения</subtitle>
      <p><code>20-28</code> Эта часть кода ничем не отличается от листинга 6.2.</p>
      <subtitle>Вход готов для чтения</subtitle>
      <p><code>29-40</code> Код практически аналогичен листингу 6.2 за тем отличием, что нам приходится обрабатывать конец файла, возвращаемый <code>kqueue</code>. Для каналов и терминалов <code>kqueue</code> возвращает событие готовности дескриптора к чтению, подобно <code>select</code>, так что мы можем считать из этого дескриптора символ конца файла. Для файлов <code>kqueue</code> возвращает количество байтов, оставшихся в поле <code>data</code> структуры <code>struct kevent</code> и предполагает, что приложение само определит, когда оно доберется до конца этих данных. Поэтому мы переписываем цикл таким образом, чтобы отправлять данные по сети, если они были считаны из дескриптора. Затем проверяется достижение конца файла: если мы считали нуль байтов или если мы считали все оставшиеся байты из дескриптора файла, значит, это и есть <code>EOF</code>. Еще одно изменение состоит в том, что вместо <code>FD_CLR</code> для удаления дескриптора из набора файлов мы используем флаг <code>EV_DELETE</code> и вызываем <code>kevent</code> для удаления события из фильтра в ядре.</p>
     </section>
     <section>
      <title>
       <p>Рекомендации</p>
      </title>
      <p>Новыми интерфейсами следует пользоваться аккуратно. Читайте свежую документацию, относящуюся к конкретному выпуску операционной системы. Интерфейсы часто меняются от одного выпуска к другому, причем таким образом, что заметно это далеко не сразу. Все это продолжается до тех пор, пока поставщики не проработают все детали функционирования новых интерфейсов.</p>
      <p>В целом, лучше избегать написания непереносимых программ. Однако для оптимизации ресурсоемких приложений годятся все средства.</p>
     </section>
    </section>
    <section>
     <title>
      <p>14.10. Резюме</p>
     </title>
     <p>Существует три способа установить ограничение времени для операции с сокетом:</p>
     <p>&#9632; Использовать функцию <code>alarm</code> и сигнал <code>SIGALRM</code>.</p>
     <p>&#9632; Задать предел времени в функции <code>select</code>.</p>
     <p>&#9632; Использовать более новые параметры сокета <code>SO_RCVTIMEO</code> и <code>SO_SNDTIMEO</code>.</p>
     <p>Первый способ легко использовать, но он включает обработку сигналов и, как показано в разделе 20.5, может привести к ситуации гонок. Использование функции <code>select</code> означает, что блокирование происходит в этой функции (с заданным в ней пределом времени) вместо блокирования в вызове функции <code>read</code>, <code>write</code> или <code>connect</code>. Другая альтернатива — использование новых параметров сокета — также проста в использовании, но предоставляется не всеми реализациями.</p>
     <p>Функции <code>recvmsg</code> и <code>sendmsg</code> являются наиболее общими из пяти групп предоставляемых функций ввода-вывода. Они объединяют целый ряд возможностей, свойственных других функциям ввода-вывода, позволяя задавать флаг <code>MSG_xxx</code> (как функции <code>recv</code> и <code>send</code>), возвращать или задавать адрес протокола собеседника (как функции <code>recvfrom</code> и <code>sendto</code>), использовать множество буферов (как функции <code>readv</code> и <code>writev</code>). Кроме того, они обеспечивают две новых возможности: возвращение флагов приложению и получение или отправку вспомогательных данных.</p>
     <p>В тексте книги мы описываем десять различных форм вспомогательных данных, шесть из которых появились в IPv6. Вспомогательные данные состоят из объектов вспомогательных данных. Перед каждым объектом идет структура <code>cmsghdr</code>, задающая его длину, уровень протокола и тип данных. Пять макросов, начинающихся с префикса <code>CMSG_</code>, используются для создания и анализа вспомогательных данных.</p>
     <p>Сокеты могут использоваться со стандартной библиотекой ввода-вывода С, но это добавляет еще один уровень буферизации к уже имеющемуся в TCP. На самом деле недостаток понимания буферизации, выполняемой стандартной библиотекой ввода-вывода, является наиболее общей проблемой при работе с этой библиотекой. Поскольку сокет не является терминальным устройством, общим решением этой потенциальной проблемы будет отключение буферизации стандартного потока ввода-вывода.</p>
     <p>Многие производители предоставляют усовершенствованные средства опроса событий без накладных расходов на <code>select</code> и <code>poll</code>. Не стоит увлекаться написанием непереносимого кода, однако иногда преимущества перевешивают риск непереносимости.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Что происходит в листинге 14.1, когда мы переустанавливаем обработчик сигналов, если процесс не установил обработчик для сигнала <code>SIGALRM</code>?</p>
     <p>2. В листинге 14.1 мы выводим предупреждение, если у процесса уже установлен таймер <code>alarm</code>. Измените эту функцию так, чтобы новое значение <code>alarm</code> для процесса задавалось после выполнения connect до завершения функции.</p>
     <p>3. Измените листинг 11.5 следующим образом: перед вызовом функции <code>read</code> вызовите функцию <code>recv</code> с флагом <code>MSG_PEEK</code>. Когда она завершится, вызовите функцию <code>ioctl</code> с командой <code>FIONREAD</code> и выведите число байтов, установленных в очередь в буфере приема сокета. Затем вызовите функцию <code>read</code> для фактического чтения данных.</p>
     <p>4. Что происходит с оставшимися в стандартном буфере ввода-вывода данными, если процесс, дойдя до конца функции <code>main</code>, не обнаруживает там функции <code>exit</code>?</p>
     <p>5. Примените каждое из двух изменений, описанных после листинга 14.6, и убедитесь в том, что каждое из них решает проблему буферизации.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 15</p>
     <p>Доменные протоколы Unix</p>
    </title>
    <section>
     <title>
      <p>15.1. Введение</p>
     </title>
     <p>Доменные протоколы Unix — это не набор протоколов, а способ связи клиентов и серверов на отдельном узле, использующий тот же API, который используется для клиентов и серверов на различных узлах, — сокеты или XTI. Доменные протоколы Unix представляют альтернативу методам IPC (Interprocess Communications — взаимодействие процессов), которым посвящен второй том<a l:href="#n13" type="note">[2]</a> этой серии, применяемым, когда клиент и сервер находятся на одном узле. Подробности действительной реализации доменных сокетов Unix в ядре, происходящем от Беркли, приводятся в третьей части [112].</p>
     <p>В домене Unix предоставляются два типа сокетов: потоковые (аналогичные сокетам TCP) и дейтаграммные (аналогичные сокетам UDP). Хотя предоставляется также и символьный сокет, но его семантика никогда не документировалась, он не используется никакой из известных автору программ и не определяется в POSIX.</p>
     <p>Доменные сокеты Unix используются по трем причинам.</p>
     <p>1. В реализациях, происходящих от Беркли, доменные сокеты Unix часто вдвое быстрее сокетов TCP, когда оба собеседника находятся на одном и том же узле [112, с. 223–224]. Есть приложение, которое использует это преимущество: X Window System. Когда клиент X11 запускается и открывает соединение с сервером X11, клиент проверяет значение переменной окружения DISPLAY, которая задает имя узла сервера, окно и экран. Если сервер находится на том же узле, что и клиент, клиент открывает потоковое соединение с сервером через доменный сокет Unix, в противном случае клиент открывает соединение TCP.</p>
     <p>2. Доменные сокеты Unix используются при передаче дескрипторов между процессами на одном и том же узле. Пример мы приводим в разделе 15.7.</p>
     <p>3. Более новые реализации доменных сокетов Unix предоставляют регистрационные данные клиента (идентификатор пользователя и идентификаторы группы) серверу, что может служить дополнительной проверкой безопасности. Мы покажем это в разделе 15.8.</p>
     <p>Адреса протоколов, используемые для идентификации клиентов и серверов в домене Unix, — это полные имена в обычной файловой системе. Вспомните, что IPv4 использует комбинацию 32-разрядных адресов и 16-разрядных номеров портов для своих адресов протоколов, а IPv6 для своих адресов протоколов использует комбинацию 128-разрядных адресов и 16-разрядных номеров портов. Эти полные имена не являются обычными именами файлов Unix: в общем случае мы не можем читать из этих файлов или записывать в них. Это может делать только программа, связывающая полное имя с доменным сокетом Unix.</p>
    </section>
    <section>
     <title>
      <p>15.2. Структура адреса доменного сокета Unix</p>
     </title>
     <section>
      <p>В листинге 15.1<a l:href="#n1" type="note">[1]</a> показана структура адреса доменного сокета Unix, задаваемая включением заголовочного файла <code>&lt;sys/un.h&gt;</code>.</p>
      <p><strong>Листинг 15.1</strong>. Структура адреса доменного сокета Unix: sockaddr_un</p>
      <p><code>struct sockaddr_un {</code></p>
      <p><code> uint8_t     sun_len;</code></p>
      <p><code> sa_family_t sun_family;    /* AF_LOCAL */</code></p>
      <p><code> char        sun_path[104]; /* полное имя, оканчивающееся нулем */</code></p>
      <p><code>};</code></p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>POSIX не задает длину массива sun_path и предупреждает, что разработчику приложения не следует делать предположений об этой длине. Воспользуйтесь оператором sizeof для определения длины массива во время выполнения программы. Убедитесь, что полное имя помещается в этот массив. Длина, скорее всего, будет где-то между 92 и 108. Причина этих ограничений — в артефакте реализации, возникшем еще в 4.2BSD, где требовалось, чтобы структура помещалась в 128-байтовый буфер памяти ядра mbuf.</p>
      </cite>
      <p>Полное имя, хранимое в символьном массиве <code>sun_path</code>, должно завершаться нулем. Имеется макрос <code>SUN_LEN</code>, который получает указатель на структуру <code>sockaddr_un</code> и возвращает длину структуры, включая число непустых байтов в полном имени. Неопределенный адрес обозначается пустой строкой, то есть элемент <code>sun_path[0]</code> должен быть равен нулю. Это эквивалент константы <code>INADDR_ANY</code> протокола IPv4 и константы <code>IN6ADDR_ANY_INIT</code> протокола IPv6 для домена Unix.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В POSIX доменным протоколам Unix дали название «локального IPC», чтобы не подчеркивать зависимость от операционной системы Unix. Историческая константа AF_UNIX становится константой AF_LOCAL. Тем не менее мы будем продолжать использовать термин «домен Unix», так как он стал именем де-факто, независимо от соответствующей операционной системы. Кроме того, несмотря на попытку POSIX исключить зависимость от операционной системы, структура адреса сокета сохраняет суффикс _un!</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Пример: функция bind и доменный сокет Unix</p>
      </title>
      <p>Программа, показанная в листинге 15.2, создает доменный сокет Unix, с помощью функции <code>bind</code> связывает с ним полное имя и затем вызывает функцию <code>getsockname</code> и выводит это полное имя.</p>
      <p><strong>Листинг 15.2</strong>. Связывание полного имени с доменным сокетом Unix</p>
      <p><code>unixdomain/unixbind.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd;</code></p>
      <p><code> 6  socklen_t len;</code></p>
      <p><code> 7  struct sockaddr_un addr1, addr2;</code></p>
      <empty-line/>
      <p><code> 8  if (argc != 2)</code></p>
      <p><code> 9   err_quit("usage: unixbind &lt;pathname&gt;");</code></p>
      <empty-line/>
      <p><code>10  sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);</code></p>
      <empty-line/>
      <p><code>11  unlink(argv[1]); /* игнорируем возможную ошибку */</code></p>
      <empty-line/>
      <p><code>12  bzero(&amp;addr1, sizeof(addr1));</code></p>
      <p><code>13  addr1.sun_family = AF_LOCAL;</code></p>
      <p><code>14  strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path) - 1);</code></p>
      <p><code>15  Bind(sockfd, (SA*)&amp;addr1, SUN_LEN(&amp;addr1));</code></p>
      <empty-line/>
      <p><code>16  len = sizeof(addr2);</code></p>
      <p><code>17  Getsockname(sockfd, (SA*)&amp;addr2, &amp;len);</code></p>
      <p><code>18  printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);</code></p>
      <empty-line/>
      <p><code>19  exit(0);</code></p>
      <p><code>20 }</code></p>
      <subtitle>Удаление файла</subtitle>
      <p><code>11</code> Полное имя, которое функция <code>bind</code> должна связать с сокетом, — это аргумент командной строки. Если это полное имя уже существует в файловой системе, при выполнении функции <code>bind</code> возникает ошибка. Следовательно, мы вызываем функцию <code>unlink</code>, чтобы удалить файл в том случае, если он уже существует. Если его не существует, функция <code>unlink</code> возвращает ошибку, которую мы игнорируем.</p>
      <subtitle>Вызов функций bind и getsockname</subtitle>
      <p><code>12-18</code> Мы копируем аргумент командной строки, используя функцию <code>strncpy</code>, чтобы избежать переполнения структуры, если полное имя слишком длинное. Поскольку мы инициализируем структуру нулем и затем вычитаем единицу из размера массива <code>sun_path</code>, мы знаем, что полное имя оканчивается нулем. Далее вызывается функция <code>bind</code> и мы используем макрос <code>SUN_LEN</code> для вычисления длины аргумента функции. Затем мы вызываем функцию <code>getsockname</code>, чтобы получить имя, которое было только что связано с сокетом, и выводим результат.</p>
      <p>Если мы запустим программу в Solaris, то получим следующие результаты:</p>
      <p><code>solaris % <strong>umask</strong> <emphasis>сначала выводим наше значение umask</emphasis></code></p>
      <p><code>022             <emphasis>оно отображается в восьмеричной системе</emphasis></code></p>
      <p><code>solaris % <strong>unixbind /tmp/moose</strong></code></p>
      <p><code>bound name = /tmp/moose, returned len = 13</code></p>
      <p><code>solaris % <strong>unixbind /tmp/moose</strong> <emphasis>снова запускаем программу</emphasis></code></p>
      <p><code>bound name = /tmp/moose, returned len = 13</code></p>
      <p><code>solaris % <strong>ls -l /tmp/moose</strong></code></p>
      <p><code>srwxr-xr-x 1 andy staff 0 Aug 10 13:13 /tmp/moose</code></p>
      <p><code>solaris % <strong>ls -lF /tmp/foo.bar</strong></code></p>
      <p><code>srwxr-xr-x 1 andy staff 0 Aug 10 13:13 /tmp/moose=</code></p>
      <p>Сначала мы выводим наше значение <code>umask</code>, поскольку в POSIX указано, что права доступа к создаваемому объекту определяются этим значением. Наше значение 022 выключает биты, разрешающие запись в файл для пользователей из группы (group-write) и прочих пользователей (other-write). Затем мы запускаем программу и видим, что длина, возвращаемая функцией <code>getsockname</code>, равна 13: один байт для элемента <code>sun_len</code>, один байт для элемента <code>sun_family</code> и 11 байт для полного имени (исключая завершающий нуль). Это пример аргумента типа «значение-результат», значение которого при завершении функции отличается от значения при вызове функции. Мы можем вывести полное имя, используя спецификатор формата <code>%s</code> функции <code>printf</code>, поскольку полное имя, хранящееся в <code>sun_path</code>, представляет собой завершающуюся нулем строку. Затем мы снова запускаем программу, чтобы проверить, что вызов функции <code>unlink</code> удаляет соответствующий файл.</p>
      <p>Мы запускаем команду <code>ls -l</code>, чтобы увидеть биты разрешения для файла и тип файла. В Solaris (и большинстве версий Unix) тип файла — это сокет, что обозначается символом s. Мы также замечаем, что все девять битов разрешения включены, так как Solaris не изменяет принятые по умолчанию биты разрешения на наше значение <code>umask</code>. Наконец, мы снова запускаем <code>ls</code> с параметром <code>-F</code>, что заставляет Solaris добавить знак равенства (соответствующий типу «сокет») к полному имени.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Изначально значение umask не действовало на создаваемые процессами доменные сокеты Unix, но с течением времени производители исправили это упущение, чтобы устанавливаемые разрешения соответствовали ожиданиям разработчиков. Тем не менее все еще существуют системы, в которых разрешения доменного сокета могут не зависеть от значения umask. В других системах сокеты могут отображаться как каналы (символ р), а значок равенства при вызове ls -F может не отображаться вовсе. Однако поведение, демонстрируемое в нашем примере, является наиболее типичным.</p>
      </cite>
     </section>
    </section>
    <section>
     <title>
      <p>15.3. Функция socketpair</p>
     </title>
     <p>Функция <code>socketpair</code> создает два сокета, которые затем соединяются друг с другом. Эта функция применяется только к доменным сокетам Unix.</p>
     <p><code>#include &lt;sys/socket.h&gt;</code></p>
     <empty-line/>
     <p><code>int socketpair(int <emphasis>family</emphasis>, int <emphasis>type</emphasis>, int <emphasis>protocol</emphasis>, int <emphasis>sockfd</emphasis>[2]);</code></p>
     <p><code><emphasis>Возвращает: ненулевое значение в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Аргумент <code>family</code> должен быть равен <code>AF_LOCAL</code>, а аргумент <code>protocol</code> должен быть нулевым. Однако аргумент type может быть равен как <code>SOCK_STREAM</code>, так и <code>SOCK_DGRAM</code>. Два дескриптора сокета создаются и возвращаются как <code>sockfd[0]</code> и <code>sockfd[1]</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Эта функция аналогична функции Unix pipe: при ее вызове возвращаются два дескриптора, причем каждый дескриптор соединен с другим. Действительно, в Беркли-реализации внутреннее устройство функции pipe полностью аналогично функции socketpair [112, с. 253-254].</p>
     </cite>
     <p>Два созданных сокета не имеют имен. Это значит, что не было неявного вызова функции <code>bind</code>.</p>
     <p>Результат выполнения функции <code>socketpair</code> с аргументом type, равным <code>SOCK_STREAM</code>, называется <emphasis>потоковым каналом</emphasis> (<emphasis>stream pipe</emphasis>). Потоковый канал является аналогом обычного канала Unix (который создается функцией <code>pipe</code>), но он двусторонний, что позволяет использовать оба дескриптора и для чтения, и для записи. Потоковый канал, созданный функцией <code>socketpair</code>, изображен на рис. 15.1.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>POSIX не требует поддержки двусторонних каналов. В SVR4 функция pipe возвращает два двусторонних дескриптора, в то время как ядра, происходящие от Беркли, традиционно возвращают односторонние дескрипторы (см. рис. 17.31 [112]).</p>
     </cite>
    </section>
    <section>
     <title>
      <p>15.4. Функции сокетов</p>
     </title>
     <p>Функции сокетов применяются к доменным сокетам Unix с учетом некоторых особенностей и ограничений. Далее мы перечисляем требования POSIX, указывая, где они применимы. Отметим, что на сегодняшний день не все реализации соответствуют этим требованиям.</p>
     <p>1. Права доступа к файлу по умолчанию для полного имени, созданного функцией <code>bind</code>, задаются значением 0777 (чтение, запись и выполнение данного файла разрешены владельцу файла, группе пользователей, в которую он входит, и всем остальным пользователям) и могут быть изменены в соответствии с текущим значением <code>umask</code>.</p>
     <p>2. Имя, связанное с доменным сокетом Unix, должно быть абсолютным, а не относительным именем. Причина, по которой нужно избегать относительного имени, в том, что в таком случае разрешение имени зависит от текущего рабочего каталога вызывающего процесса. То есть если сервер связывается с относительным именем, клиент должен находиться в том же каталоге, что и сервер (или должен знать этот каталог), для того чтобы вызов клиентом функции <code>connect</code> или <code>sendto</code> был успешным.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В POSIX сказано, что связывание относительного имени с доменным сокетом Unix приводит к непредсказуемым результатам.</p>
     </cite>
     <p>3. Полное имя, заданное в вызове функции <code>connect</code>, должно быть именем, в настоящий момент связанным с открытым доменным сокетом Unix того же типа (потоковым или дейтаграммным). Ошибка происходит в следующих случаях: если имя существует, но не является сокетом; если имя существует и является сокетом, но ни один открытый дескриптор с ним не связан; если имя существует и является открытым сокетом, но имеет неверный тип (то есть потоковый доменный сокет Unix не может соединиться с именем, связанным с дейтаграммным доменным сокетом Unix, и наоборот).</p>
     <p>4. С функцией <code>connect</code> доменного сокета Unix связана такая же проверка прав доступа, какая имеет место при вызове функции open для доступа к файлу только на запись.</p>
     <p>5. Потоковые доменные сокеты Unix аналогичны сокетам TCP: они предоставляют интерфейс байтового потока без границ записей.</p>
     <p>6. Если при вызове функции connect для потокового доменного сокета Unix обнаруживается, что очередь прослушиваемого сокета переполнена (см. раздел 4.5), немедленно возвращается ошибка <code>ECONNREFUSED</code>. В этом отличие от сокета TCP: прослушиваемый сокет TCP игнорирует приходящий сегмент SYN, если очередь сокета заполнена, благодаря чему стеком клиента выполняется несколько попыток отправки сегмента SYN.</p>
     <p>7. Дейтаграммные доменные сокеты Unix аналогичны сокетам UDP: они предоставляют ненадежный сервис дейтаграмм, сохраняющий границы записей.</p>
     <p>8. В отличие от сокетов UDP, при отправке дейтаграммы на неприсоединенный дейтаграммный доменный сокет Unix с сокетом не связывается полное имя. (Вспомните, что отправка дейтаграммы UDP на неприсоединенный сокет UDP заставляет динамически назначаемый порт связываться с сокетом.) Это означает, что получатель дейтаграммы не будет иметь возможности отправить ответ, если отправитель не связал со своим сокетом полное имя. Аналогично, в отличие от TCP и UDP, при вызове функции <code>connect</code> для дейтаграммного доменного сокета Unix с сокетом не связывается полное имя.</p>
    </section>
    <section>
     <title>
      <p>15.5. Клиент и сервер потокового доменного протокола Unix</p>
     </title>
     <p>Теперь мы перепишем наш эхо-клиент и эхо-сервер TCP из главы 5 с использованием доменных сокетов Unix. В листинге 15.3 показан сервер, который является модификацией сервера из листинга 5.9 и использует потоковый доменный протокол Unix вместо протокола TCP.</p>
     <p><strong>Листинг 15.3</strong>. Эхо-сервер потокового доменного протокола Unix</p>
     <p><code>//unixdomain/unixstrserv01.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int listenfd, connfd;</code></p>
     <p><code> 6  pid_t childpid;</code></p>
     <p><code> 7  socklen_t clilen;</code></p>
     <p><code> 8  struct sockaddr_un cliaddr, servaddr;</code></p>
     <p><code> 9  void sig_chld(int);</code></p>
     <empty-line/>
     <p><code>10  listenfd = Socket(AF_LOCAL, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code>11  unlink(UNIXSTR_PATH);</code></p>
     <p><code>12  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>13  servaddr.sun_family = AF_LOCAL;</code></p>
     <p><code>14  strcpy(servaddr.sun_path, UNIXSTR_PATH);</code></p>
     <empty-line/>
     <p><code>15  Bind(listenfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>16  Listen(listenfd, LISTENQ);</code></p>
     <p><code>17  Signal(SIGCHLD, sig_chld);</code></p>
     <empty-line/>
     <p><code>18  for (;;) {</code></p>
     <p><code>19   clilen = sizeof(cliaddr);</code></p>
     <p><code>20   if ((connfd = accept(listenfd, (SA*)&amp;cliaddr, &amp;clilen)) &lt; 0) {</code></p>
     <p><code>21    if (errno == EINTR)</code></p>
     <p><code>22     continue; /* назад в for() */</code></p>
     <p><code>23    else</code></p>
     <p><code>24     err_sys("accept error");</code></p>
     <p><code>25   }</code></p>
     <p><code>26   if ((childpid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>27    Close(listenfd); /* закрывается прослушиваемый сокет */</code></p>
     <p><code>28    str_echo(connfd); /* обработка запроса */</code></p>
     <p><code>29    exit(0);</code></p>
     <p><code>30   }</code></p>
     <p><code>31   Close(connfd); /* родитель закрывает присоединенный сокет */</code></p>
     <p><code>32  }</code></p>
     <p><code>33 }</code></p>
     <p><code>8</code> Теперь две структуры адреса сокета относятся к типу <code>sockaddr_un</code>.</p>
     <p><code>10</code> Для создания потокового доменного сокета Unix первый аргумент функции socket должен иметь значение <code>AF_LOCAL</code>.</p>
     <p><code>11-15</code> Константа <code>UNIXSTR_PATH</code> определяется в файле <code>unp.h</code> как <code>/tmp/unix/str</code>. Сначала мы вызываем функцию <code>unlink</code>, чтобы удалить полное имя в случае, если оно сохранилось после предыдущего запуска сервера, а затем инициализируем структуру адреса сокета перед вызовом функции <code>bind</code>. Ошибка при выполнении функции <code>unlink</code> не является аварийной ситуацией.</p>
     <p>Обратите внимание, что этот вызов функции <code>bind</code> отличается от вызова, показанного в листинге 15.2. Здесь мы задаем размер структуры адреса сокета (третий аргумент) как общий размер структуры <code>sockaddr_un</code>, а не просто число байтов, занимаемое полным именем. Оба значения длины приемлемы, поскольку полное имя должно оканчиваться нулем.</p>
     <p>Оставшаяся часть функции такая же, как и в листинге 5.9. Используется та же функция <code>str_echo</code> (см. листинг 5.2).</p>
     <p>В листинге 15.4 представлен эхо-клиент потокового доменного протокола Unix. Это модификация листинга 5.3.</p>
     <p><strong>Листинг 15.4</strong>. Эхо-клиент потокового доменного протокола Unix</p>
     <p><code>//unixdomain/umxstrcli01.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd;</code></p>
     <p><code> 6  struct sockaddr_un servaddr;</code></p>
     <empty-line/>
     <p><code> 7  sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code> 8  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code> 9  servaddr sun_family = AF_LOCAL;</code></p>
     <p><code>10  strcpy(servaddr.sun_path, UNIXSTR_PATH);</code></p>
     <p><code>11  Connect(sockfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>12  str_cli(stdin, sockfd); /* выполняет всю работу */</code></p>
     <empty-line/>
     <p><code>13  exit(0);</code></p>
     <p><code>14 }</code></p>
     <p><code>6</code> Теперь структурой адреса сокета, которая должна содержать адрес сервера, будет структура <code>sockaddr_un</code>.</p>
     <p><code>7</code> Первый аргумент функции <code>socket</code> — <code>AF_LOCAL</code>.</p>
     <p><code>8-10</code> Код для заполнения структуры адреса сокета идентичен коду, показанному для сервера: инициализация структуры нулем, установка семейства протоколов <code>AF_LOCAL</code> и копирование полного имени в элемент <code>sun_path</code>.</p>
     <p><code>12</code> Функция <code>str_cli</code> — та же, что и раньше (в листинге 6.2 представлена последняя разработанная нами версия).</p>
    </section>
    <section>
     <title>
      <p>15.6. Клиент и сервер дейтаграммного доменного протокола Unix</p>
     </title>
     <p>Теперь мы перепишем наши клиент и сервер UDP из разделов 8.3 и 8.5 с использованием сокетов. В листинге 15.5 показан сервер, который является модификацией листинга 8.1.</p>
     <p><strong>Листинг 15.5</strong>. Эхо-сервер дейтаграммного доменного протокола Unix</p>
     <p><code>//unixdomain/unixdgserv01.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd;</code></p>
     <p><code> 6  struct sockaddr_un servaddr, cliaddr;</code></p>
     <empty-line/>
     <p><code> 7  sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0);</code></p>
     <empty-line/>
     <p><code> 8  unlink(UNIXDG_PATH);</code></p>
     <p><code> 9  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>10  servaddr.sun_family = AF_LOCAL;</code></p>
     <p><code>11  strcpy(servaddr.sun_path, UNIXDG_PATH);</code></p>
     <empty-line/>
     <p><code>12  Bind(sockfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>13  dg_echo(sockfd, (SA*)&amp;cliaddr, sizeof(cliaddr));</code></p>
     <p><code>14 }</code></p>
     <p><code>6</code> Две структуры адреса сокета относятся теперь к типу <code>sockaddr_un</code>.</p>
     <p><code>7</code> Для создания дейтаграммного доменного сокета Unix первый аргумент функции <code>socket</code> должен иметь значение <code>AF_LOCAL</code>.</p>
     <p><code>8-12</code> Константа <code>UNIXDG_PATH</code> определяется в заголовочном файле <code>unp.h</code> как <code>/tmp/unix.dg</code>. Сначала мы вызываем функцию <code>unlink</code>, чтобы удалить полное имя в случае, если оно сохранилось после предыдущего запуска сервера, а затем инициализируем структуру адреса сокета перед вызовом функции <code>bind</code>. Ошибка при выполнении функции <code>unlink</code> — это нормальное явление.</p>
     <p><code>13</code> Используется та же функция <code>dg_echo</code> (см. листинг 8.2).</p>
     <p>В листинге 15.6 представлен эхо-клиент дейтаграммного доменного протокола Unix. Это модификация листинга 8.3.</p>
     <p><strong>Листинг 15.6</strong>. Эхо-клиент дейтаграммного доменного протокола Unix</p>
     <p><code>//unixdomain/unixdgcli01.с</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd;</code></p>
     <p><code> 6  struct sockaddr_un cliaddr, servaddr;</code></p>
     <empty-line/>
     <p><code> 7  sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0);</code></p>
     <empty-line/>
     <p><code> 8  bzero(&amp;cliaddr, sizeof(cliaddr)); /* связывание сокета с адресом */</code></p>
     <p><code> 9  cliaddr.sun_family = AF_LOCAL;</code></p>
     <p><code>10  strcpy(cliaddr.sun_path, tmpnam(NULL);</code></p>
     <empty-line/>
     <p><code>11  Bind(sockfd, (SA*)&amp;cliaddr, sizeof(cliaddr));</code></p>
     <empty-line/>
     <p><code>12  bzero(&amp;servaddr, sizeof(servaddr)); /* заполняем структуру адреса</code></p>
     <p><code>                                           сокета сервера */</code></p>
     <p><code>13  servaddr.sun_family = AF_LOCAL;</code></p>
     <p><code>14  strcpy(servaddr.sun_path, UNIXDG_PATH);</code></p>
     <empty-line/>
     <p><code>15  dg_cli(stdin, sockfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>16  exit(0);</code></p>
     <p><code>17 }</code></p>
     <p><code>6</code> Структурой адреса сокета, содержащей адрес сервера, теперь будет структура <code>sockaddr_un</code>. Мы также размещаем в памяти одну из этих структур, чтобы она содержала адрес клиента, о чем мы расскажем далее.</p>
     <p><code>7</code> Первый аргумент функции <code>socket</code> — это <code>AF_LOCAL</code>.</p>
     <p><code>8-11</code> В отличие от клиента UDP при использовании дейтаграммного доменного протокола Unix требуется явно связать с помощью функции <code>bind</code> полное имя с нашим сокетом, чтобы сервер имел полное имя, на которое он мог бы отправить свой ответ. Мы вызываем функцию <code>tmpnam</code>, чтобы получить уникальное полное имя, с которым затем при помощи функции <code>bind</code> свяжем наш сокет. Вспомните из раздела 15.4, что при отправке дейтаграммы на неприсоединенный дейтаграммный доменный сокет Unix не происходит неявного связывания полного имени с сокетом. Следовательно, если мы опустим этот шаг, вызов сервером функции <code>recvfrom</code> в функции <code>dg_echo</code> возвращает пустое полное имя, что затем приведет к ошибке, когда сервер вызовет функцию <code>sendto</code>.</p>
     <p><code>12-14</code> Код для заполнения структуры адреса сокета заранее известным полным именем идентичен коду, представленному ранее для сервера.</p>
     <p><code>15</code> Функция <code>dg_cli</code> остается той же, что и раньше (см. листинг 8.4).</p>
    </section>
    <section>
     <title>
      <p>15.7. Передача дескрипторов</p>
     </title>
     <section>
      <p>Когда нам требуется передать дескриптор от одного процесса другому, обычно мы выбираем одно из двух решений:</p>
      <p>1. Дочерний процесс использует все открытые дескрипторы совместно с родительским процессом после вызова функции <code>fork</code>.</p>
      <p>2. Все дескрипторы обычно остаются открытыми при вызове функции <code>exec</code>.</p>
      <p>В первом случае процесс открывает дескриптор, вызывает функцию <code>fork</code>, а затем родительский процесс закрывает дескриптор, позволяя дочернему процессу с ним работать. При этом открытый дескриптор передается от родительского процесса дочернему. Но нам также хотелось бы, чтобы у дочернего процесса была возможность открывать дескриптор и передавать его обратно родительскому процессу.</p>
      <p>Современные системы Unix предоставляют способ передавать любой открытый дескриптор от одного процесса любому другому процессу. При этом вовсе не обязательно, чтобы процессы были родственными, как родительский и дочерний. Эта технология требует, чтобы мы сначала создали между двумя процессами доменный сокет Unix и затем использовали функцию <code>sendmsg</code> для отправки специального сообщения через этот доменный сокет. Ядро обрабатывает это сообщение специальным образом, передавая открытый дескриптор от отправителя получателю.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Передача ядром 4.4BSD открытого дескриптора через доменный сокет Unix описывается в главе 18 [112].</p>
       <p>SVR4 использует другую технологию внутри ядра для передачи открытого дескриптора: команды I_SENDFD и I_RECVFD функции ioctl, описанные в разделе 15.5.1 [110]. Но процесс все же имеет возможность доступа к указанному свойству ядра за счет доменного сокета Unix. В этой книге мы описываем применение доменных сокетов Unix для передачи открытых дескрипторов, поскольку это наиболее переносимая технология программирования: она работает как с Беркли-ядрами, так и с SVR4, в то время как команды I_SENDFD и I_RECVFD функции ioctl работают только в SVR4.</p>
       <p>Технология 4.4BSD позволяет передавать множество дескрипторов с помощью одиночной функции sendmsg, в то время как технология SVR4 передает за один раз только один дескриптор. Во всех наших примерах за один раз передается один дескриптор.</p>
      </cite>
      <p>Шаги при передаче дескриптора между процессами будут такими:</p>
      <p>1. Создание доменного сокета Unix, или потокового сокета, или дейтаграммного сокета.</p>
      <p>Если целью является породить с помощью функции <code>fork</code> дочерний процесс, с тем чтобы дочерний процесс открыл дескриптор и передал его обратно родительскому процессу, родительский процесс может вызвать функцию <code>socketpair</code> для создания потокового канала, который может использоваться для передачи дескриптора.</p>
      <p>Если процессы не являются родственными, сервер должен создать потоковый доменный сокет Unix, связать его при помощи функции <code>bind</code> с полным именем, тем самым позволяя клиенту соединиться с этим сокетом при помощи функции connect. Затем клиент может отправить запрос серверу для открытия некоторого дескриптора, а сервер может передать дескриптор обратно через доменный сокет Unix. Как альтернатива между клиентом и сервером может также использоваться дейтаграммный доменный сокет Unix, однако преимущества этого способа невелики, к тому же существует возможность игнорирования дейтаграммы. Далее в примерах этой главы мы будем использовать потоковый сокет между клиентом и сервером.</p>
      <p>2. Один процесс открывает дескриптор при помощи вызова любой из функций Unix, возвращающей дескриптор, например <code>open</code>, <code>piре</code>, <code>mkfifo</code>, <code>socket</code> или <code>accept</code>. От одного процесса к другому можно передать дескриптор <emphasis>любого</emphasis> типа, поэтому мы называем эту технологию «передачей дескриптора», а не «передачей дескриптора файла».</p>
      <p>3. Отправляющий процесс строит структуру <code>msghdr</code> (см. раздел 14.5), содержащую дескриптор, который нужно передать. В POSIX определено, что дескриптор должен отправляться как вспомогательные данные (элемент <code>msg_control</code> структуры <code>msghdr</code>, см. раздел 14.6), но более старые реализации используют элемент <code>msg_accrights</code>. Отправляющий процесс вызывает функцию <code>sendmsg</code> для отправки дескриптора через доменный сокет Unix, созданный на шаге 1. На этом этапе мы говорим, что дескриптор находится «в полете». Даже если отправляющий процесс закроет дескриптор после вызова функции <code>sendmsg</code>, но до вызова принимающим процессом функции <code>recvmsg</code>, дескриптор останется открытым для принимающего процесса. Отправка дескриптора увеличивает счетчик ссылок дескриптора на единицу.</p>
      <p>4. Принимающий процесс вызывает функцию <code>recvmsg</code> для получения дескриптора через доменный сокет Unix, созданный на шаге 1. Номер дескриптора в принимающем процессе может отличаться от номера дескриптора в отправляющем процессе. Передача дескриптора — это не передача номера дескриптора. Этот процесс включает создание нового дескриптора в принимающем процессе, который ссылается на ту же запись таблицы файлов в ядре, что и дескриптор, отправленный отправляющим процессом.</p>
      <p>Клиент и сервер должны располагать некоторым протоколом уровня приложения, с тем чтобы получатель дескриптора имел информацию о времени его появления. Если получатель вызывает функцию <code>recvmsg</code>, не выделив места в памяти для получения дескриптора, и дескриптор передается как готовый для чтения, то передаваемый дескриптор закрывается [128, с. 518]. Кроме того, нужно избегать установки флага <code>MSG_PEEK</code> в функции <code>recvmsg</code>, если предполагается получение дескриптора, поскольку в этом случае результат непредсказуем.</p>
     </section>
     <section>
      <title>
       <p>Пример передачи дескриптора</p>
      </title>
      <p>Теперь мы представим пример передачи дескриптора. Мы напишем программу под названием <code>mycat</code>, которой в качестве аргумента командной строки передается полное имя файла. Эта программа открывает файл и копирует его в стандартный поток вывода. Но вместо вызова обычной функции Unix <code>open</code> мы вызываем нашу собственную функцию <code>my_open</code>. Эта функция создает потоковый канал и вызывает функции <code>fork</code> и <code>exec</code> для запуска другой программы, открывающей нужный файл. Эта программа должна затем передать дескриптор обратно родительскому процессу по потоковому каналу.</p>
      <p>На рис. 15.1 показан первый шаг: наша программа <code>mycat</code> после создания потокового канала при помощи вызова функции <code>socketpair</code>. Мы обозначили два дескриптора, возвращаемых функцией <code>socketpair</code>, как <code>[0]</code> и <code>[1]</code>.</p>
      <image l:href="#img_100.png"/>
      <p><strong>Рис. 15.1</strong>. Программа mycat после создания потокового канала при использовании функции socketpair</p>
      <p>Затем процесс взывает функцию <code>fork</code>, и дочерний процесс вызывает функцию <code>exec</code> для выполнения программы <code>openfile</code>. Родительский процесс закрывает дескриптор <code>[1]</code>, а дочерний процесс закрывает дескриптор <code>[0]</code>. (Нет разницы, на каком конце потокового канала происходит закрытие. Дочерний процесс мог бы закрыть <code>[1]</code>, а родительский — <code>[0]</code>.) При этом получается схема, показанная на рис. 15.2.</p>
      <image l:href="#img_101.png"/>
      <p><strong>Рис. 15.2</strong>. Программа mycat после запуска программы openfile</p>
      <p>Родительский процесс должен передать программе <code>openfile</code> три фрагмента информации: полное имя открываемого файла, режим открытия (только чтение чтение и запись или только запись) и номер дескриптора, соответствующий его концу потокового канала (который мы обозначили <code>[1]</code>). Мы выбрали такой способ передачи этих трех элементов, как ввод аргументов командной строки при вызове функции <code>exec</code>. Альтернативным способом будет отправка этих элементов в качестве данных по потоковому каналу. Программа отправляет обратно открытый дескриптор по потоковому каналу и завершается. Статус выхода программы сообщает родительскому процессу, смог ли файл открыться, и если нет, то какого типа ошибка произошла.</p>
      <p>Преимущество выполнения дополнительной программы для открытия файла заключается в том, что за счет приравнивания привилегий пользователя к привилегиям владельца файла мы получаем возможность открывать те файлы, которые не имеем права открывать в обычной ситуации. Эта программа позволяет расширить концепцию обычных прав доступа Unix (пользователь, группа и все остальные) и включить любые формы проверки прав доступа. Мы начнем с программы <code>mycat</code>, показанной в листинге 15.7.</p>
      <p><strong>Листинг 15.7</strong>. Программа mycat: копирование файла в стандартный поток вывода</p>
      <p><code>//unixdomain/mycat.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int my_open(const char*, int);</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 main(int argc, char **argv)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int fd, n;</code></p>
      <p><code> 7  char buff[BUFFSIZE];</code></p>
      <empty-line/>
      <p><code> 8  if (argc != 2)</code></p>
      <p><code> 9   err_quit("usage: mycat &lt;pathname&gt;");</code></p>
      <empty-line/>
      <p><code>10  if ((fd = my_open(argv[1], O_RDONLY)) &lt; 0)</code></p>
      <p><code>11   err_sys("cannot open %s", argv[1]);</code></p>
      <empty-line/>
      <p><code>12  while ((n = Read(fd, buff, BUFFSIZE)) &gt; 0)</code></p>
      <p><code>13   Write(STDOUT_FILENO, buff, n);</code></p>
      <empty-line/>
      <p><code>14  exit(0);</code></p>
      <p><code>15 }</code></p>
      <p>Если мы заменим вызов функции <code>my_open</code> вызовом функции <code>open</code>, эта простая программа всего лишь скопирует файл в стандартный поток вывода.</p>
      <p>Функция <code>my_open</code>, показанная в листинге 15.8, должна выглядеть для вызывающего процесса как обычная функция Unix <code>open</code>. Она получает два аргумента — полное имя и режим открытия (например, <code>O_RDONLY</code> обозначает, что файл доступен только для чтения), открывает файл и возвращает дескриптор.</p>
      <p><strong>Листинг 15.8</strong>. Функция my_open: открытие файла и возвращение дескриптора</p>
      <p><code>//unixdomain/myopen.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 my_open(const char *pathname, int mode)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int fd, sockfd[2], status;</code></p>
      <p><code> 6  pid_t childpid;</code></p>
      <p><code> 7  char c, argsockfd[10], argmode[10];</code></p>
      <empty-line/>
      <p><code> 8  Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);</code></p>
      <empty-line/>
      <p><code> 9  if ((childpid = Fork()) == 0) { /* дочерний процесс */</code></p>
      <p><code>10   Close(sockfd[0]);</code></p>
      <p><code>11   snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]);</code></p>
      <p><code>12   snprintf(argmode, sizeof(argmode), "%d", mode);</code></p>
      <p><code>13   execl("./openfile", "openfile", argsockfd, pathname, argmode,</code></p>
      <p><code>14    (char*)NULL);</code></p>
      <p><code>15   err_sys("execl error");</code></p>
      <p><code>16  }</code></p>
      <p><code>17  /* родительский процесс - ожидание завершения дочернего процесса */</code></p>
      <p><code>18  Close(sockfd[1]); /* закрываем конец, который мы не используем */</code></p>
      <empty-line/>
      <p><code>19  Waitpid(childpid, &amp;status, 0);</code></p>
      <p><code>20  if (WIFEXITED(status) == 0)</code></p>
      <p><code>21   err_quit("child did not terminate");</code></p>
      <p><code>22  if ((status = WEXITSTATUS(status)) == 0)</code></p>
      <p><code>23   Read_fd(sockfd[0], &amp;c, 1, &amp;fd);</code></p>
      <p><code>24  else {</code></p>
      <p><code>25   errno = status; /* установка значения errno в статус дочернего</code></p>
      <p><code>                        процесса */</code></p>
      <p><code>26   fd = -1;</code></p>
      <p><code>27  }</code></p>
      <empty-line/>
      <p><code>28  Close(sockfd[0]);</code></p>
      <p><code>29  return (fd);</code></p>
      <p><code>30 }</code></p>
      <subtitle>Создание потокового канала</subtitle>
      <p><code>8</code> Функция <code>socketpair</code> создает потоковый канал. Возвращаются два дескриптора: <code>sockfd[0]</code> и <code>sockfd[1]</code>. Это состояние, которое мы показали на рис. 15.1.</p>
      <subtitle>Функции fork и exec</subtitle>
      <p><code>9-16</code> Вызывается функция <code>fork</code>, после чего дочерний процесс закрывает один конец потокового канала. Номер дескриптора другого конца потокового канала помещается в массив <code>argsockfd</code>, а режим открытия помещается в массив <code>argmode</code>. Мы вызываем функцию <code>snprintf</code>, поскольку аргументы функции exec должны быть символьными строками. Выполняется программа <code>openfile</code>. Функция <code>execl</code> возвращает управление только в том случае, если она встретит ошибку. При удачном выполнении начинает выполняться функция <code>main</code> программы <code>openfile</code>.</p>
      <subtitle>Родительский процесс в ожидании завершения дочернего процесса</subtitle>
      <p><code>17-22</code> Родительский процесс закрывает другой конец потокового канала и вызывает функцию <code>waitpid</code> для ожидания завершения дочернего процесса. Статус завершения дочернего процесса возвращается в переменной <code>status</code>, и сначала мы проверяем, что программа завершилась нормально (то есть не была завершена из-за возникновения какого-либо сигнала). Затем макрос <code>WEXITSTATUS</code> преобразует статус завершения в статус выхода, значение которого должно быть между 0 и 255. Мы вскоре увидим, что если при открытии необходимого файла программой <code>openfile</code> происходит ошибка, то эта программа завершается, причем статус ее завершения равен соответствующему значению переменной <code>errno</code>.</p>
      <subtitle>Получение дескриптора</subtitle>
      <p><code>23</code> Наша функция <code>read_fd</code>, которую мы показываем в следующем листинге, получает дескриптор потокового канала. Кроме получения дескриптора мы считываем 1 байт данных, но ничего с этими данными не делаем.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>При отправке и получении дескриптора по потоковому каналу мы всегда отправляем как минимум 1 байт данных, даже если получатель никак эти данные не обрабатывает. Иначе получатель не сможет распознать, что значит нулевое возвращаемое значение из функции read_fd: отсутствие данных (но, возможно, есть дескриптор) или конец файла.</p>
      </cite>
      <p>В листинге 15.9 показана функция <code>readfd</code>, вызывающая функцию <code>recvmsg</code> для получения данных и дескриптора через доменный сокет Unix. Первые три аргумента этой функции те же, что и для функции <code>read</code>, а четвертый (<code>recvfd</code>) является указателем на целое число. После выполнения этой функции <code>recvfd</code> будет указывать на полученный дескриптор.</p>
      <p><strong>Листинг 15.9</strong>. Функция read_fd: получение данных и дескриптора</p>
      <p><code>//lib/read_fd.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 ssize_t</code></p>
      <p><code> 3 read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  struct msghdr msg;</code></p>
      <p><code> 6  struct iovec iov[1];</code></p>
      <p><code> 7  ssize_t n;</code></p>
      <p><code> 8  int newfd;</code></p>
      <empty-line/>
      <p><code> 9 #ifdef HAVE_MSGHDR_MSG_CONTROL</code></p>
      <p><code>10  union {</code></p>
      <p><code>11   struct cmsghdr cm;</code></p>
      <p><code>12   char control[CMSG_SPACE(sizeof(int))];</code></p>
      <p><code>13  } control_un;</code></p>
      <p><code>14  struct cmsghdr *cmptr;</code></p>
      <empty-line/>
      <p><code>15  msg.msg_control = control_un.control;</code></p>
      <p><code>16  msg.msg_controllen = sizeof(control_un.control);</code></p>
      <p><code>17 #else</code></p>
      <p><code>18  msg.msg_accrights = (caddr_t)&amp;newfd;</code></p>
      <p><code>19  msg.msg_accrightslen = sizeof(int);</code></p>
      <p><code>20 #endif</code></p>
      <empty-line/>
      <p><code>21  msg.msg_name = NULL;</code></p>
      <p><code>22  msg.msg_namelen = 0;</code></p>
      <empty-line/>
      <p><code>23  iov[0].iov_base = ptr;</code></p>
      <p><code>24  iov[0].iov_len = nbytes;</code></p>
      <p><code>25  msg.msg_iov = iov;</code></p>
      <p><code>26  msg.msg_iovlen = 1;</code></p>
      <empty-line/>
      <p><code>27  if ((n = recvmsg(fd, &amp;msg, 0)) &lt;= 0)</code></p>
      <p><code>28   return (n);</code></p>
      <empty-line/>
      <p><code>29 #ifdef HAVE_MSGHDR_MSG_CONTROL</code></p>
      <p><code>30  if ((cmptr = CMSG_FIRSTHDR(&amp;msg)) != NULL &amp;&amp;</code></p>
      <p><code>31   mptr-&gt;cmsg_len == CMSG_LEN(sizeof(int))) {</code></p>
      <p><code>32   if (cmptr-&gt;cmsg_level != SOL_SOCKET)</code></p>
      <p><code>33    err_quit("control level != SOL_SOCKET");</code></p>
      <p><code>34   if (cmptr-&gt;cmsg_type != SCM_RIGHTS)</code></p>
      <p><code>35    err_quit("control type != SCM_RIGHTS");</code></p>
      <p><code>36   *recvfd = *((int*)CMSG_DATA(cmptr));</code></p>
      <p><code>37  } else</code></p>
      <p><code>38   *recvfd = -1; /* дескриптор не был передан */</code></p>
      <p><code>39 #else</code></p>
      <p><code>40  if (msg.msg_accrightslen == sizeof(int))</code></p>
      <p><code>41   *recvfd = newfd;</code></p>
      <p><code>42  else</code></p>
      <p><code>43   *recvfd = -1; /* дескриптор не был передан */</code></p>
      <empty-line/>
      <p><code>44 #endif</code></p>
      <empty-line/>
      <p><code>45  return (n);</code></p>
      <p><code>46 }</code></p>
      <p><code>8-26</code> Эта функция должна работать с обеими версиями функции <code>recvmsg</code>: с элементом <code>msg_control</code> и с элементом <code>msg_accrights</code>. Наш заголовочный файл <code>config.h</code> (см. листинг Г.2) определяет константу <code>HAVE_MSGHDR_MSG_CONTROL</code>, если поддерживается версия функции <code>recvmsg</code> с <code>msg_control</code>.</p>
      <subtitle>Проверка выравнивания буфера msg_control</subtitle>
      <p><code>10-13</code> Буфер <code>msg_control</code> должен быть выровнен в соответствии со структурой <code>msghdr</code>. Просто выделить в памяти массив типа <code>char</code> недостаточно. Здесь мы объявляем объединение, состоящее из структуры <code>cmsghdr</code> и символьного массива, что гарантирует необходимое выравнивание массива. Возможно и другое решение — вызвать функцию <code>malloc</code>, но это потребует освобождения памяти перед завершением функции.</p>
      <p><code>27-45</code> Вызывается функция <code>recvmsg</code>. Если возвращаются вспомогательные данные, их формат будет таким, как показано на рис. 14.4. Мы проверяем, верны ли длина, уровень и тип, затем получаем вновь созданный дескриптор и возвращаем его через указатель вызывающего процесса <code>recvfd</code>. Макрос <code>CMSG_DATA</code> возвращает указатель на элемент <code>cmsg_data</code> объекта вспомогательных данных как указатель на элемент типа <code>unsigned char</code>. Мы преобразуем его к указателю на элемент типа <code>int</code> и получаем целочисленный дескриптор, на который указывает этот указатель.</p>
      <p>Если поддерживается более старый элемент <code>msg_accrights</code>, то длина должна быть равна размеру целого числа, а вновь созданный дескриптор возвращается через указатель <code>recvfd</code> вызывающего процесса.</p>
      <p>В листинге 15.10 показана программа <code>openfile</code>. Она получает три аргумента командной строки, которые должны быть переданы, и вызывает обычную функцию <code>open</code>.</p>
      <p><strong>Листинг 15.10</strong>. Программа openfile: открытие файла и передача дескриптора обратно</p>
      <p><code>//unixdomain/openfile.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int fd;</code></p>
      <p><code> 6  ssize_t n;</code></p>
      <empty-line/>
      <p><code> 7  if (argc != 4)</code></p>
      <p><code> 8   err_quit("openfile &lt;sockfd#&gt; &lt;filename&gt; &lt;mode&gt;");</code></p>
      <empty-line/>
      <p><code> 9  if ((fd = open(argv[2], atoi(argv[3]))) &lt; 0)</code></p>
      <p><code>10   exit((errno &gt; 0) ? errno : 255);</code></p>
      <empty-line/>
      <p><code>11  if ((n = write_fd(atoi(argv[1]), "", 1, fd)) &lt; 0)</code></p>
      <p><code>12   exit((errno &gt; 0) ? errno : 255);</code></p>
      <empty-line/>
      <p><code>13  exit(0);</code></p>
      <p><code>14 }</code></p>
      <subtitle>Аргументы командной строки</subtitle>
      <p><code>6-7</code> Поскольку два из трех аргументов командной строки были превращены в символьные строки функцией <code>my_open</code>, они преобразуются обратно в целые числа при помощи функции <code>atoi</code>.</p>
      <subtitle>Открытие файла</subtitle>
      <p><code>9-10</code> Файл открывается с помощью функции <code>open</code>. Если встречается ошибка, статус завершения этого процесса содержит значение переменной <code>errno</code>, соответствующее ошибке функции <code>open</code>.</p>
      <subtitle>Передача дескриптора обратно</subtitle>
      <p><code>11-12</code> Дескриптор передается обратно функцией <code>write_fd</code>, которую мы покажем в следующем листинге. Затем этот процесс завершается, но ранее в этой главе мы сказали, что отправляющий процесс может закрыть переданный дескриптор (это происходит, когда мы вызываем функцию <code>exit</code>), поскольку ядро знает, что дескриптор находится в состоянии передачи («в полете»), и оставляет его открытым для принимающего процесса.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Статус выхода должен лежать в пределах от 0 до 255. Максимальное значение переменной errno — около 150. Альтернативный способ, при котором не требуется, чтобы значение переменной errno было меньше 256, заключается в том, чтобы передать обратно указание на ошибку в виде обычных данных при вызове функции sendmsg.</p>
      </cite>
      <p>В листинге 15.11 показана последняя функция, <code>write_fd</code>, вызывающая функцию <code>sendmsg</code> для отправки дескриптора (и, возможно, еще каких-либо данных, которые мы не используем) через доменный сокет Unix.</p>
      <p><strong>Листинг 15.11</strong>. Функция write_fd: передача дескриптора при помощи вызова функции sendmsg</p>
      <p><code>//lib/write_fd.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 ssize_t</code></p>
      <p><code> 3 write_fd(int fd, void *ptr, size_t nbytes, int sendfd)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  struct msghdr msg;</code></p>
      <p><code> 6  struct iovec iov[1];</code></p>
      <empty-line/>
      <p><code> 7 #ifdef HAVE_MSGHDR_MSG_CONTROL</code></p>
      <p><code> 8  union {</code></p>
      <p><code> 9   struct cmsghdr cm;</code></p>
      <p><code>10   char control[CMSG_SPACE(sizeof(int))];</code></p>
      <p><code>11  } control_un;</code></p>
      <p><code>12  struct cmsghdr *cmptr;</code></p>
      <empty-line/>
      <p><code>13  msg.msg_control = control_un.control;</code></p>
      <p><code>14  msg.msg_controllen = sizeof(control_un.control);</code></p>
      <empty-line/>
      <p><code>15  cmptr = CMSG_FIRSTHDR(&amp;msg);</code></p>
      <p><code>16  cmptr-&gt;cmsg_len = CMSG_LEN(sizeof(int));</code></p>
      <p><code>17  cmptr-&gt;cmsg_level = SOL_SOCKET;</code></p>
      <p><code>18  cmptr-&gt;cmsg_type = SCM_RIGHTS;</code></p>
      <p><code>19  *((int*)CMSG_DATA(cmptr)) = sendfd;</code></p>
      <p><code>20 #else</code></p>
      <p><code>21  msg.msg_accrights = (caddr_t)&amp;sendfd;</code></p>
      <p><code>22  msg.msg_accrightslen = sizeof(int);</code></p>
      <p><code>23 #endif</code></p>
      <empty-line/>
      <p><code>24  msg.msg_name = NULL;</code></p>
      <p><code>25  msg.msg_namelen = 0;</code></p>
      <empty-line/>
      <p><code>26  iov[0].iov_base = ptr;</code></p>
      <p><code>27  iov[0].iov_len = nbytes;</code></p>
      <p><code>28  msg.msg_iov = iov;</code></p>
      <p><code>29  msg.msg_iovlen = 1;</code></p>
      <empty-line/>
      <p><code>30  return (sendmsg(fd, &amp;msg, 0));</code></p>
      <p><code>31 }</code></p>
      <p>Как и в случае функции <code>read_fg</code>, эта функция обрабатывает либо вспомогательные данные, либо права доступа, которые предшествовали вспомогательным данным в более ранних реализациях. В любом случае инициализируется структура <code>msghdr</code> и затем вызывается функция <code>sendmsg</code>.</p>
      <p>В разделе 28.7 мы приводим пример передачи дескриптора, в котором участвуют неродственные (unrelated) процессы, а в разделе 30.9 — пример, где задействованы родственные процессы. В них мы будем использовать функции <code>read_fd</code> и <code>write_fd</code>, которые только что описали.</p>
     </section>
    </section>
    <section>
     <title>
      <p>15.8. Получение информации об отправителе</p>
     </title>
     <section>
      <p>На рис. 14.4 мы показали другой тип информации, передаваемой через доменный сокет Unix в виде вспомогательных данных: информацию об отправителе, которая передается с помощью структуры <code>cmsgcred</code>, определяемой путем включения заголовочного файла <code>&lt;sys/socket.h&gt;</code>. Упаковка и формат данных зависят от операционной системы. Такая возможность появилась только в BSD/OS 2.1. Мы описываем FreeBSD, а прочие варианты Unix во многом подобны ей (проблема обычно состоит в выборе структуры, которую следует использовать для передачи данных). Рассказ об этой возможности мы считаем необходимым, поскольку это важное, хотя и простое дополнение доменных протоколов Unix. Когда клиент и сервер связываются с помощью этих протоколов, серверу часто бывает необходим способ точно узнать, кто является клиентом, чтобы убедиться, что клиент имеет право запрашивать определенный сервис.</p>
      <p><code>struct fcred {</code></p>
      <p><code> uid_t fc_ruid;            /* действующий идентификатор пользователя */</code></p>
      <p><code> gid_t fc_rgid;            /* действующий групповой идентификатор */</code></p>
      <p><code> char  fc_login[MAXLOGNAME]; /* имя setlogin() */</code></p>
      <p><code> uid_t fc_uid;             /* идентификатор пользователя */</code></p>
      <p><code> short fc_ngroups;         /* количество групп */</code></p>
      <p><code> gid_t fc_groups[NGROUPS]; /* дополнительные групповые идентификаторы */</code></p>
      <p><code>};</code></p>
      <p><code>#define fc_gid fc_groups[0] /* групповой идентификатор */</code></p>
      <p>Обычно <code>MAXLONGNAME</code> и <code>NGROUPS</code> имеют значение 16. Значение <code>fc_ngroups</code> равно как минимум 1, а первым элементом массива является идентификатор группы.</p>
      <p>Эта информация всегда доступна через доменный сокет Unix, хотя отправителю часто приходится принимать дополнительные меры для обеспечения ее отправки вместе с данными, и получателю также приходится выполнять некоторые действия (например, устанавливать параметры сокета). В системе FreeBSD получатель может обойтись вызовом <code>recvmsg</code> с достаточно большим буфером для вспомогательных данных, чтобы туда поместились идентифицирующие данные (листинг 15.12). Однако отправитель обязан включить структуру <code>cmsgcred</code> при отправке данных посредством <code>sendmsg</code>. Хотя включение структуры осуществляется отправителем, заполняется она ядром. Благодаря этому передача идентифицирующих данных через доменный сокет Unix является надежным способом проверки клиента.</p>
     </section>
     <section>
      <title>
       <p>Пример</p>
      </title>
      <p>В качестве примера передачи идентифицирующих данных мы изменим наш потоковый доменный сервер Unix, так чтобы он запрашивал идентифицирующие данные клиента. В листинге 15.12 показана новая функция, <code>read_cred</code>, аналогичная функции <code>read</code>, но возвращающая также структуру <code>fcred</code>, содержащую идентифицирующие данные отправителя.</p>
      <p><strong>Листинг 15.12</strong>. Функция read_cred: чтение и возвращение идентифицирующих данных отправителя</p>
      <p><code>//unixdomain/readcred.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 #define CONTROL_LEN (sizeof(struct cmsghdr) + sizeof(struct cmsgcred))</code></p>
      <empty-line/>
      <p><code> 3 ssize_t</code></p>
      <p><code> 4 read_cred(int fd, void *ptr, size_t nbytes, struct cmsgcred *cmsgcredptr)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  struct msghdr msg;</code></p>
      <p><code> 7  struct iovec iov[1];</code></p>
      <p><code> 8  char control[CONTROL_LEN];</code></p>
      <p><code> 9  int n;</code></p>
      <empty-line/>
      <p><code>10  msg.msg_name = NULL;</code></p>
      <p><code>11  msg.msg_namelen = 0;</code></p>
      <p><code>12  iov[0].iov_base = ptr;</code></p>
      <p><code>13  iov[0].iov_len = nbytes;</code></p>
      <p><code>14  msg.msg_iov = iov;</code></p>
      <p><code>15  msg.msg_iovlen = 1;</code></p>
      <p><code>16  msg.msg_control = control;</code></p>
      <p><code>17  msg.msg_controllen = sizeof(control);</code></p>
      <p><code>18  msg.msg_flags = 0;</code></p>
      <empty-line/>
      <p><code>19  if ((n = recvmsg(fd, &amp;msg, 0)) &lt; 0)</code></p>
      <p><code>20   return(n);</code></p>
      <empty-line/>
      <p><code>21  cmsgcredptr-&gt;cmcred_ngroups = 0; /* идентифицирующие данные не получены */</code></p>
      <p><code>22  if (cmsgcredptr &amp;&amp; msg.msg_controllen &gt; 0) {</code></p>
      <p><code>23   struct cmsghdr *cmptr = (struct cmsghdr*)control;</code></p>
      <empty-line/>
      <p><code>24   if (cmptr-&gt;cmsg_len &lt; CONTROL_LEN)</code></p>
      <p><code>25    err_quit("control length = %d", cmptr-&gt;cmsg_len);</code></p>
      <p><code>26   if (cmptr-&gt;cmsg_level != SOL_SOCKET)</code></p>
      <p><code>27    err_quit("control level != SOL_SOCKET");</code></p>
      <p><code>28   if (cmptr-&gt;cmsg_type != SCM_CREDS)</code></p>
      <p><code>29    err_quit("control type != SCM_CREDS");</code></p>
      <p><code>30   memcpy(cmsgcredptr, CMSG_DATA(cmptr), sizeof(struct cmsgcred));</code></p>
      <p><code>31  }</code></p>
      <empty-line/>
      <p><code>32  return(n);</code></p>
      <p><code>33 }</code></p>
      <p><code>3-4</code> Первые три аргумента идентичны аргументам функции <code>read</code>, а четвертый аргумент — это указатель на структуру <code>cmsgcred</code>, которая будет заполнена.</p>
      <p><code>22-31</code> Если данные были переданы, проверяются длина, уровень и тип вспомогательных данных, и результирующая структура копируется обратно вызывающему процессу. Если никаких идентифицирующих данных не было передано, мы обнуляем структуру. Поскольку число групп (<code>cmcred_ngroups</code>) всегда равно 1 или больше, нулевое значение указывает вызывающему процессу, что ядро не возвратило никаких идентифицирующих данных.</p>
      <p>Функция <code>main</code> для нашего эхо-сервера (см. листинг 15.3) остается неизменной. В листинге 15.13 показана новая версия функции <code>str_echo</code>, полученная путем модификации листинга 5.2. Эта функция вызывается дочерним процессом после того, как родительский процесс принял новое клиентское соединение и вызвал функцию <code>fork</code>.</p>
      <p><strong>Листинг 15.13</strong>. Функция str_echo, запрашивающая идентифицирующие данные клиента</p>
      <p><code>//unixdomain/strecho.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 ssize_t read_cred(int, void*, size_t, struct cmsgcred*);</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 str_echo(int sockfd)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  ssize_t n;</code></p>
      <p><code> 7  int i;</code></p>
      <p><code> 8  char buf[MAXLINE];</code></p>
      <p><code> 9  struct cmsgcred cred;</code></p>
      <p><code>10 again:</code></p>
      <p><code>11  while ((n = read_cred(sockfd, buf, MAXLINE, &amp;cred)) &gt; 0) {</code></p>
      <p><code>12   if (cred.cmcred_ngroups == 0) {</code></p>
      <p><code>13    printf("(no credentials returned)\n");</code></p>
      <p><code>14   } else {</code></p>
      <p><code>15    printf("PID of sender = %d\n", cred.cmcred_pid);</code></p>
      <p><code>16    printf("real user ID = %d\n", cred.cmcred_uid);</code></p>
      <p><code>17    printf("real group ID = %d\n", cred.cmcred_gid);</code></p>
      <p><code>18    printf("effective user ID = %d\n", cred.cmcred_euid);</code></p>
      <p><code>19    printf("%d groups:", cred.cmcred_ngroups - 1);</code></p>
      <p><code>20    for (i = 1; i &lt; cred.cmcred_ngroups; i++)</code></p>
      <p><code>21     printf(" %d", cred.cmcred_groups[i]);</code></p>
      <p><code>22    printf("\n");</code></p>
      <p><code>23   }</code></p>
      <p><code>24   Writen(sockfd, buf, n);</code></p>
      <p><code>25  }</code></p>
      <empty-line/>
      <p><code>26  if (n &lt; 0 &amp;&amp; errno == EINTR)</code></p>
      <p><code>27   goto again;</code></p>
      <p><code>28  else if (n &lt; 0)</code></p>
      <p><code>29   err_sys("str_echo: read error");</code></p>
      <p><code>30 }</code></p>
      <p><code>11-23</code> Если идентифицирующие данные возвращаются, они выводятся.</p>
      <p><code>24-25</code> Оставшаяся часть цикла не меняется. Этот код считывает строки от клиента и затем отправляет их обратно клиенту.</p>
      <p>Наш клиент, представленный в листинге 15.4, остается практически неизменным. Мы добавляем передачу пустой структуры <code>cmsgcred</code> при вызове <code>sendmsg</code>, которая заполняется ядром.</p>
      <p>Перед запуском клиента определим свои личные данные командой <code>id</code>:</p>
      <p><code>freebsd % <strong>id</strong></code></p>
      <p><code>uid=1007(andy) gid=1007(andy) groups=1007(andy), 0(wheel)</code></p>
      <p>Если мы запустим сервер в одном окне, а клиент в другом, то для сервера после однократного выполнения клиента получим представленный ниже вывод.</p>
      <p><code>freebsd % <strong>unixstrserv02</strong></code></p>
      <p><code>PID of sender = 26881</code></p>
      <p><code>real user ID = 1007</code></p>
      <p><code>real group ID = 1007</code></p>
      <p><code>effective user ID = 1007</code></p>
      <p><code>2 groups: 1007 0</code></p>
      <p>Информация выводится только после отправки клиентом данных серверу. Мы видим, что сведения соответствуют тем, которые были получены командой <code>id</code>.</p>
     </section>
    </section>
    <section>
     <title>
      <p>15.9. Резюме</p>
     </title>
     <p>Доменные сокеты Unix являются альтернативой IPC, когда клиент и сервер находятся на одном узле. Преимущество использования доменных сокетов Unix перед некоторой формой IPC состоит в том, что используемый API практически идентичен клиент-серверному сетевому соединению. Преимущество использования доменных сокетов Unix перед TCP, когда клиент и сервер находятся на одном узле, заключается в повышенной производительности доменных сокетов Unix относительно TCP во многих реализациях.</p>
     <p>Мы изменили наш эхо-сервер и эхо-клиент TCP и UDP для использования доменных протоколов Unix, и единственным главным отличием оказалась необходимость при помощи функции <code>bind</code> связывать полное имя с клиентским сокетом UDP так, чтобы серверу UDP было куда отправлять ответы.</p>
     <p>Передача дескрипторов между клиентами и серверами, находящимися на одном узле, — это мощная технология, которая используется при работе с доменными сокетами Unix. Мы показали пример передачи дескриптора от дочернего процесса обратно родительскому процессу в разделе 15.7. В разделе 28.7 мы покажем пример, в котором клиент и сервер не будут родственными, а в разделе 30.9 — другой пример, когда дескриптор передается от родительского процесса дочернему.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Что произойдет, если доменный сервер Unix вызовет функцию <code>unlink</code> после вызова функции <code>bind</code>?</p>
     <p>2. Что произойдет, если доменный сервер Unix при завершении не отсоединит с помощью функции <code>unlink</code> свое известное полное имя, а клиент будет пытаться с помощью функции <code>connect</code> соединиться с сервером через некоторое время после того, как тот завершит работу?</p>
     <p>3. Измените листинг 11.5 так, чтобы после того как будет выведен адрес протокола собеседника, вызывалась бы функция <code>sleep(5)</code>, а также чтобы вывести число байтов, возвращаемых функцией <code>read</code> всякий раз, когда она возвращает положительное значение. Измените листинг 11.8 так, чтобы для каждого байта результата, отправляемого клиенту, вызывалась функция <code>write</code>. (Мы обсуждаем подобные изменения в решении упражнения 1.5.) Запустите клиент и сервер на одном узле, используя TCP. Сколько байтов считывает клиент с помощью функции <code>read</code>?</p>
     <p>Запустите клиент и сервер на одном узле, используя доменный сокет Unix. Изменилось ли что-нибудь?</p>
     <p>Теперь для сервера вместо функции write вызовите функцию <code>send</code> и задайте флаг <code>MSG_EOR</code> (чтобы выполнить это упражнение, вам нужно использовать Беркли-реализацию). Запустите клиент и сервер на одном узле, используя доменный сокет Unix. Изменилось ли что-нибудь?</p>
     <p>4. Напишите программу, определяющую значения, показанные в табл. 4.6. Один из подходов — создать потоковый канал и затем с помощью функции <code>fork</code> разветвить родительский и дочерний процессы. Родительский процесс входит в цикл <code>for</code>, увеличивая на каждом шаге значение <code>backlog</code> от 0 до 14. Каждый раз при прохождении цикла родительский процесс сначала записывает значение <code>backlog</code> в потоковый канал. Дочерний процесс читает это значение, создает прослушиваемый сокет, связанный с адресом закольцовки, и присваивает <code>backlog</code> считанное значение. Затем дочерний процесс делает запись в потоковый канал просто для того, чтобы сообщить родительскому процессу о своей готовности. Затем родительский процесс пытается установить как можно больше соединений, задав предварительно аргумент функции <code>alarm</code> равным 2 с, поскольку при достижении предельного значения <code>backlog</code> вызов функции connect заблокируется, и отправляет еще раз сегмент <code>SYN</code>. Дочерний процесс никогда не вызывает функцию <code>accept</code>, что позволяет ядру установить в очередь все соединения с родительским процессом. Когда истекает время ожидания родительского процесса (аргумент функции <code>alarm</code>, в данном случае 2 с), по счетчику цикла он может определить, какая по счету функция <code>connect</code> соответствует предельному значению <code>backlog</code>. Затем родительский процесс закрывает свои сокеты и пишет следующее новое значение в потоковый канал для дочернего процесса. Когда дочерний процесс считывает новое значение, он закрывает прежний прослушиваемый сокет и создает новый, заново начиная процедуру.</p>
     <p>5. Проверьте, вызывает ли пропуск вызова функции <code>bind</code> в листинге 15.6 ошибку сервера.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 16</p>
     <p>Неблокируемый ввод-вывод</p>
    </title>
    <section>
     <title>
      <p>16.1. Введение</p>
     </title>
     <p>По умолчанию сокеты блокируют выполнение процесса. Это означает, что, когда мы вызываем на сокете функцию, которая не может выполниться немедленно, наш процесс переходит в «спящее» состояние и ждет, когда будет выполнено определенное условие. Мы можем разделить функции сокетов, способные вызвать блокирование, на четыре категории.</p>
     <p>1. Операции ввода: функции <code>read</code>, <code>readv</code>, <code>recv</code>, <code>recvfrom</code> и <code>recvmsg</code>. Если мы вызываем одну из этих функций ввода для блокируемого сокета TCP (а по умолчанию такой сокет является блокируемым) и в приемном буфере сокета отсутствуют данные, то сокет вызывает переход в спящее состояние на то время, пока не придут какие-нибудь данные. Поскольку TCP является протоколом байтового потока, из этого состояния мы выйдем, когда придет «хоть сколько- нибудь» данных: это может быть одиночный байт, а может быть и целый сегмент данных TCP. Если мы хотим ждать до тех пор, пока не будет доступно определенное фиксированное количество данных, мы вызываем нашу функцию <code>readn</code> (см. листинг 3.9) или задаем флаг <code>MSG_WAITALL</code> (см. табл. 14.1). Поскольку UDP является протоколом дейтаграмм, то если приемный буфер блокируемого сокета UDP пуст, мы переходим в состояние ожидания и находимся в нем до тех пор, пока не придет дейтаграмма UDP.</p>
     <p>В случае неблокируемого сокета при невозможности удовлетворить условию операции ввода (как минимум 1 байт данных для сокета TCP или целая дейтаграмма для сокета UDP) возврат происходит немедленно с ошибкой <code>EWOULDBLOCK</code>.</p>
     <p>2. Операции вывода: функции <code>write</code>, <code>writev</code>, <code>send</code>, <code>sendto</code>, и <code>sendmsg</code>. В отношении сокета TCP в разделе 2.9 мы сказали, что ядро копирует данные из буфера приложения в буфер отправки сокета. Если для блокируемого сокета недостаточно места в буфере отправки, процесс переходит в состояние ожидания до тех пор, пока место не освободится.</p>
     <p>В случае неблокируемого сокета TCP при недостатке места в буфере отправки завершение происходит немедленно с ошибкой <code>EWOULDBLOCK</code>. Если в буфере отправки сокета есть место, возвращаемое значение будет представлять количество байтов, которое ядро смогло скопировать в буфер (это называется <emphasis>частичным копированием</emphasis> — <emphasis>short count</emphasis>).</p>
     <p>В разделе 2.9 мы также сказали, что на самом деле буфера отправки UDP не существует. Ядро только копирует данные приложения и перемещает их вниз по стеку, добавляя к данным заголовки UDP и IP. Следовательно, операция вывода на блокируемом сокете UDP (каким он является по умолчанию) никогда не заблокируется.</p>
     <p>3. Прием входящих соединений: функция <code>accept</code>. Если функция <code>accept</code> вызывается для блокируемого сокета и новое соединение недоступно, процесс переводится в состояние ожидания.</p>
     <p>Если функция <code>accept</code> вызывается для неблокируемого сокета и новое соединение недоступно, возвращается ошибка <code>EWOULDBLOCK</code>.</p>
     <p>4. Инициирование исходящих соединений: функция <code>connect</code> для TCP. (Вспомните, что функция connect может использоваться с UDP, но она не вызывает создания «реального» соединения — она лишь заставляет ядро сохранить IP-адрес и номер порта собеседника.) В разделе 2.5 мы показали, что установление соединения TCP включает трехэтапное рукопожатие и что функция connect не возвращает управление, пока клиент не получит сегмент ACK или SYN. Это значит, что функция TCP <code>connect</code> всегда блокирует вызывающий процесс как минимум на время обращения (RTT) к серверу.</p>
     <p>Если функция <code>connect</code> вызывается для неблокируемого сокета TCP и соединение не может быть установлено немедленно, инициируется установление соединения (например, отправляется первый пакет трехэтапного рукопожатия TCP), но возвращается ошибка <code>EINPROGRESS</code>. Обратите внимание, что эта ошибка отличается от ошибки, возвращаемой в первых трех сценариях. Также отметим, что некоторые соединения могут быть установлены немедленно, когда сервер находится на том же узле, что и клиент, поэтому даже в случае неблокируемого вызова функции <code>connect</code> мы должны быть готовы к тому, что она успешно выполнится. Пример неблокируемой функции <code>connect</code> мы покажем в разделе 16.3.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Традиционно System V возвращала для неблокируемой операции ввода-вывода, которую невозможно выполнить, ошибку EAGAIN, в то время как Беркли-реализации возвращали ошибку EWOULDBLOCK. Еще больше дело запутывается тем, что согласно POSIX.1 используется EAGAIN, в то время как в POSIX.1g определено, что используется EWOULDBLOCK. К счастью, большинство систем (включая SVR4 и 4.4BSD) определяют один и тот же код для этих двух ошибок (проверьте свой системный заголовочный файл &lt;sys/errno.h&gt;), поэтому не важно, какой из них использовать. В нашем тексте мы используем ошибку EWOULDBLOCK, как определяется в POSIX.</p>
     </cite>
     <p>В разделе 6.2 мы представили различные модели ввода-вывода и сравнили неблокируемый ввод-вывод с другими моделями. В этой главе мы покажем примеры четырех типов операций и разработаем новый тип клиента, аналогичный веб-клиенту, инициирующий одновременно множество соединений TCP при помощи неблокируемой функции <code>connect</code>.</p>
    </section>
    <section>
     <title>
      <p>16.2. Неблокируемые чтение и запись: функция str_cli (продолжение)</p>
     </title>
     <section>
      <p>Мы снова возвращаемся к нашей функции <code>str_cli</code>, которую мы обсуждали в разделах 5.5 и 6.4. Последняя ее версия, задействующая функцию <code>select</code>, продолжает использовать блокируемый ввод-вывод. Например, если в стандартном устройстве ввода имеется некоторая строка, мы читаем ее с помощью функции <code>fgets</code> и затем отправляем серверу с помощью функции <code>writen</code>. Но вызов функции <code>writen</code> может вызвать блокирование процесса, если буфер отправки сокета полон. В то время как мы заблокированы в вызове функции <code>writen</code>, данные могут быть доступны для чтения из приемного буфера сокета. Аналогично, когда строка ввода доступна из сокета, мы можем заблокироваться в последующем вызове функции <code>fputs</code>, если стандартный поток вывода работает медленнее, чем сеть. Наша цель в данном разделе — создать версию этой функции, использующую неблокируемый ввод-вывод. Блокирование будет предотвращено, благодаря чему в это время мы сможем сделать еще что-то полезное.</p>
      <p>К сожалению, добавление неблокируемого ввода-вывода значительно усложняет управление буфером функции, поэтому мы будем представлять функцию частями. Мы также заменим вызовы функций из стандартной библиотеки ввода-вывода на обычные <code>read</code> и <code>write</code>. Это даст возможность отказаться от функций стандартной библиотеки ввода-вывода с неблокируемыми дескрипторами, так как их применение может привести к катастрофическим последствиям.</p>
      <p>Мы работаем с двумя буферами: буфер to содержит данные, направляющиеся из стандартного потока ввода к серверу, а буфер <code>fr</code> — данные, приходящие от сервера в стандартный поток вывода. На рис. 16.1 представлена организация буфера <code>to</code> и указателей в буфере.</p>
      <image l:href="#img_102.png"/>
      <p><strong>Рис. 16.1</strong>. Буфер, содержащий данные из стандартного потока ввода, идущие к сокету</p>
      <p>Указатель <code>toiptr</code> указывает на следующий байт, в который данные могут быть считаны из стандартного потока ввода. Указатель <code>tooptr</code> указывает на следующий байт, который должен быть записан в сокет. Число байтов, которое может быть считано из стандартного потока ввода, равно <code>&amp;to[MAXLINE]</code> минус <code>toiptr</code>. Как только значение <code>tooptr</code> достигает <code>toiptr</code>, оба указателя переустанавливаются на начало буфера.</p>
      <p>На рис. 16.2 показана соответствующая организация буфера <code>fr</code>. В листинге 16.1<a l:href="#n1" type="note">[1]</a> представлена первая часть функции.</p>
      <image l:href="#img_103.png"/>
      <p><strong>Рис. 16.2</strong>. Буфер, содержащий данные из сокета, идущие к стандартному устройству вывода</p>
      <p><strong>Листинг 16.1</strong>. Функция str_cli: первая часть, инициализация и вызов функции</p>
      <p><code>//nonblock/strclinonb.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 str_cli(FILE *fp, int sockfd)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int maxfdp1, val, stdineof;</code></p>
      <p><code> 6  ssize_t n, nwritten;</code></p>
      <p><code> 7  fd_set rset, wset;</code></p>
      <p><code> 8  char to[MAXLINE], fr[MAXLINE];</code></p>
      <p><code> 9  char *toiptr, *tooptr, *friptr, *froptr;</code></p>
      <empty-line/>
      <p><code>10  val = Fcntl(sockfd, F_GETFL, 0);</code></p>
      <p><code>11  Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);</code></p>
      <empty-line/>
      <p><code>12  val = Fcntl(STDIN_FILENO, F_SETFL, 0);</code></p>
      <p><code>13  Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);</code></p>
      <empty-line/>
      <p><code>14  val = Fcntl(STDOUT_FILENO, F_SETFL, 0);</code></p>
      <p><code>15  Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);</code></p>
      <empty-line/>
      <p><code>16  toiptr = tooptr = to; /* инициализация указателей буфера */</code></p>
      <p><code>17  friptr = froptr = fr;</code></p>
      <p><code>18  stdineof = 0;</code></p>
      <empty-line/>
      <p><code>19  maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;</code></p>
      <p><code>20  for (;;) {</code></p>
      <p><code>21   FD_ZERO(&amp;rset);</code></p>
      <p><code>22   FD_ZERO(&amp;wset);</code></p>
      <p><code>23   if (stdineof == 0 &amp;&amp; toiptr &lt; &amp;to[MAXLINE])</code></p>
      <p><code>24     FD_SET(STDIN_FILENO, &amp;rset); /* чтение из стандартного потока</code></p>
      <p><code>                                       ввода */</code></p>
      <p><code>25   if (friptr &lt; &amp;fr[MAXLINE])</code></p>
      <p><code>26    FD_SET(sockfd, &amp;rset); /* чтение из сокета */</code></p>
      <p><code>27   if (tooptr != toiptr)</code></p>
      <p><code>28    FD_SET(sockfd, &amp;wset); /* данные для записи в сокет */</code></p>
      <p><code>29   if (froptr != friptr)</code></p>
      <p><code>30    FD_SET(STDOUT_FILENO, &amp;wset); /* данные для записи в стандартный</code></p>
      <p><code>                                       поток вывода */</code></p>
      <p><code>31   Select(maxfdp1, &amp;rset, &amp;wset, NULL, NULL);</code></p>
      <subtitle>Установка неблокируемых дескрипторов</subtitle>
      <p><code>10-15</code> Все три дескриптора делаются неблокируемыми при помощи функции <code>fcntl</code>: сокет в направлении к серверу и от сервера, стандартный поток ввода и стандартный поток вывода.</p>
      <subtitle>Инициализация указателей буфера</subtitle>
      <p><code>16-19</code> Инициализируются указатели в двух буферах и вычисляется максимальный дескриптор. Это значение, увеличенное на единицу, будет использоваться в качестве первого аргумента функции <code>select</code>.</p>
      <subtitle>Основной цикл: подготовка к вызову функции select</subtitle>
      <p><code>20</code> Как и в случае первой версии этой функции, показанной в листинге 6.2, основной цикл функции содержит вызов функции <code>select</code>, за которой следуют отдельные проверки различных интересующих нас условий.</p>
      <subtitle>Подготовка интересующих нас дескрипторов</subtitle>
      <p><code>21-30</code> Оба набора дескрипторов обнуляются и затем в каждом наборе включается не более двух битов. Если мы еще не прочитали конец файла из стандартного потока ввода и есть место как минимум для 1 байта данных в буфере <code>to</code>, то в наборе флагов чтения включается бит, соответствующий стандартному потоку ввода. Если есть место как минимум для 1 байта данных в буфере <code>fr</code>, то в наборе флагов чтения включается бит, соответствующий сокету. Если есть данные для записи в сокет в буфере <code>to</code>, то в наборе флагов записи включается бит, соответствующий сокету. Наконец если в буфере <code>fr</code> есть данные для отправки в стандартный поток вывода, то в наборе флагов записи включается бит, соответствующий этому стандартному потоку.</p>
      <subtitle>Вызов функции select</subtitle>
      <p><code>31</code> Вызывается функция <code>select</code>, ожидающая, когда одно из четырех условий станет истинным. Для этой функции мы не задаем тайм-аута.</p>
      <p>Следующая часть нашей функции показана в листинге 16.2. Этот код содержит первые две проверки (из четырех возможных), выполняемые после завершения функции <code>select</code>.</p>
      <p><strong>Листинг 16.2</strong>. Функция str_cli: вторая часть, чтение из стандартного потока ввода или сокета</p>
      <p><code>//nonblock/strclinonb.c</code></p>
      <p><code>32   if (FD_ISSET(STDIN_FILENO, &amp;rset)) {</code></p>
      <p><code>33    if ((n = read(STDIN_FILENO, toiptr, &amp;to[MAXLINE] - toiptr)) &lt; 0) {</code></p>
      <p><code>34     if (errno != EWOULDBLOCK)</code></p>
      <p><code>35      err_sys("read error on stdin");</code></p>
      <p><code>36    } else if (n == 0) {</code></p>
      <p><code>37     fprintf(stderr, "%s: EOF on stdin\n", gf_time());</code></p>
      <p><code>38     stdineof = 1; /* с stdin все сделано */</code></p>
      <p><code>39     if (tooptr == toiptr)</code></p>
      <p><code>40      Shutdown(sockfd, SHUT_WR); /* отсылаем FIN */</code></p>
      <empty-line/>
      <p><code>41    } else {</code></p>
      <p><code>42     fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(),</code></p>
      <p><code>43      n);</code></p>
      <p><code>44     toiptr += n; /* только что полученное из функции read число */</code></p>
      <p><code>45     FD_SET(sockfd, &amp;wset); /* включаем бит в наборе чтения */</code></p>
      <p><code>46    }</code></p>
      <p><code>47   }</code></p>
      <p><code>48   if (FD_ISSET(sockfd, &amp;rset)) {</code></p>
      <p><code>49    if ((n = read(sockfd, friptr, &amp;fr[MAXLINE] - friptr)) &lt; 0) {</code></p>
      <p><code>50     if (errno != EWOULDBLOCK)</code></p>
      <p><code>51      err_sys("read error on socket");</code></p>
      <empty-line/>
      <p><code>52    } else if (n == 0) {</code></p>
      <p><code>53     fprintf(stderr, "%s: EOF on socket\n", gf_time());</code></p>
      <p><code>54     if (stdineof)</code></p>
      <p><code>55      return; /* нормальное завершение */</code></p>
      <p><code>56     else</code></p>
      <p><code>57      err_quit("str_cli: server terminated prematurely");</code></p>
      <empty-line/>
      <p><code>58    } else {</code></p>
      <p><code>59     fprintf(stderr, "%s: read %d bytes from socket\n",</code></p>
      <p><code>60      gf_time(), n);</code></p>
      <p><code>61     friptr += n; /* только что полученное из функции read число */</code></p>
      <p><code>62     FD_SЕТ(STDOUT_FILЕNO, &amp;wset); /* включаем бит в наборе</code></p>
      <p><code>                                        чтения */</code></p>
      <p><code>63    }</code></p>
      <p><code>64   }</code></p>
      <subtitle>Чтение из стандартного потока ввода с помощью функции read</subtitle>
      <p><code>32-33</code> Если стандартный поток ввода готов для чтения, мы вызываем функцию <code>read</code>. Третий ее аргумент — это количество свободного места в буфере <code>to</code>.</p>
      <subtitle>Обработка ошибки</subtitle>
      <p><code>34-35</code> Если происходит ошибка <code>EWOULDBLOCK</code>, мы ничего не предпринимаем. Обычно эта ситуация — когда функция <code>select</code> сообщает нам о том, что дескриптор готов для чтения, а функция read возвращает ошибку <code>EWOULDBLOCK</code> — не должна возникать, но тем не менее мы ее обрабатываем.</p>
      <subtitle>Возвращение конца файла функцией read</subtitle>
      <p><code>36-40</code> Если функция <code>read</code> возвращает нуль, мы закончили со стандартным потоком ввода. Флаг <code>stdineof</code> установлен. Если в буфере to больше нет данных для отправки (<code>tooptr</code> равно <code>toiptr</code>), функция <code>shutdown</code> отправляет серверу сегмент FIN. Если в буфере <code>to</code> еще есть данные для отправки, сегмент FIN не может быть отправлен до тех пор, пока содержимое буфера не будет записано в сокет.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Мы выводим в стандартный поток сообщений об ошибках строку, отмечающую конец файла, вместе с текущим временем. Мы покажем, как мы используем этот вывод, после описания функции. Аналогичные вызовы функции fprintf выполняются неоднократно в процессе выполнения нашей функции.</p>
      </cite>
      <subtitle>Возвращение данных функцией read</subtitle>
      <p><code>41-45</code> Когда функция read возвращает данные, мы увеличиваем на единицу <code>toiptr</code>. Мы также включаем бит, соответствующий сокету, в наборе флагов записи, чтобы позже при проверке этого бита в цикле он был включен и тем самым инициировалась бы попытка записи в сокет с помощью функции <code>write</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Это одно из непростых конструктивных решений, которые приходится принимать при написании кода. У нас есть несколько альтернатив. Вместо установки бита в наборе записи мы можем ничего не делать, и в этом случае функция select будет проверять возможность записи в сокет, когда она будет вызвана в следующий раз. Но это требует дополнительного прохода цикла и вызова функции select, когда мы уже знаем, что у нас есть данные для записи в сокет. Другой вариант — дублировать код, который записывает в сокет, но это кажется расточительным, к тому же это возможный источник ошибки (в случае, если в этой части дублируемого кода есть ошибка и мы обнаруживаем и устраняем ее только в одном месте). Наконец, мы можем создать функцию, записывающую в сокет, и вызывать эту функцию вместо дублирования кода, но эта функция должна использовать три локальные переменные совместно с функцией str_cli, что может привести к необходимости сделать эти переменные глобальными. Выбор, сделанный в нашем случае, — это результат субъективного мнения автора относительно того, какой из описанных трех вариантов предпочтительнее.</p>
      </cite>
      <subtitle>Чтение из сокета с помощью функции read</subtitle>
      <p><code>48-64</code> Эти строки кода аналогичны выражению <code>if</code>, только что описанному для случая, когда стандартный поток ввода готов для чтения. Если функция <code>read</code> возвращает ошибку <code>EWOULDBLOCK</code>, ничего не происходит. Если мы встречаем признак конца файла, присланный сервером, это нормально, когда мы уже получили признак конца файла в стандартном потоке ввода. Но иначе это будет ошибкой, означающей преждевременное завершение работы сервера (<code>Server terminated prematurely</code>). Если функция <code>read</code> возвращает некоторые данные, <code>friptr</code> увеличивается на единицу и в наборе флагов записи включается бит для стандартного потока вывода, с тем чтобы попытаться записать туда данные в следующей части функции.</p>
      <p>В листинге 16.3 показана последняя часть нашей функции.</p>
      <p><strong>Листинг 16.3</strong>. Функция str_cli: третья часть, запись в стандартный поток вывода или сокет</p>
      <p><code>//nonblock/strclinonb.c</code></p>
      <p><code>65   if (FD_ISSET(STDOUT_FILENO, &amp;wset) &amp;&amp; ((n = friptr - froptr) &gt; 0)) {</code></p>
      <p><code>66    if ((nwritten = write(STDOUT_FILENO, froptr, n)) &lt; 0) {</code></p>
      <p><code>67     if (errno != EWOULDBLOCK)</code></p>
      <p><code>68      err_sys("write error to stdout");</code></p>
      <p><code>69    } else {</code></p>
      <p><code>70     fprintf(stderr, "%s: wrote %d bytes to stdout\n",</code></p>
      <p><code>71      gf_time(), nwritten);</code></p>
      <p><code>72     froptr += nwritten; /* только что полученное из функции write</code></p>
      <p><code>                              число */</code></p>
      <p><code>73     if (froptr == friptr)</code></p>
      <p><code>74      froptr = friptr - fr; /* назад к началу буфера */</code></p>
      <p><code>75    }</code></p>
      <p><code>76   }</code></p>
      <empty-line/>
      <p><code>77   if (FD_ISSET(sockfd, &amp;wset) &amp;&amp; ((n - toiptr - tooptr) &gt; 0)) {</code></p>
      <p><code>78    if ((nwritten = write(sockfd, tooptr, n)) &lt; 0) {</code></p>
      <p><code>79     if (errno != EWOULDBLOCK)</code></p>
      <p><code>80      err_sys("write error to socket");</code></p>
      <empty-line/>
      <p><code>81    } else {</code></p>
      <p><code>82     fprintf(stderr, "%s: wrote %d bytes to socket\n",</code></p>
      <p><code>83      gf_time(), nwritten);</code></p>
      <p><code>84     tooptr += nwritten; /* только что полученное из функции write</code></p>
      <p><code>                              число */</code></p>
      <p><code>85     if (tooptr == toiptr) {</code></p>
      <p><code>86      toiptr - tooptr = to; /* назад к началу буфера */</code></p>
      <p><code>87      if (stdineof)</code></p>
      <p><code>88       Shutdown(sockfd, SHUT_WR); /* посылаем FIN */</code></p>
      <p><code>89     }</code></p>
      <p><code>90    }</code></p>
      <p><code>91   }</code></p>
      <p><code>92  }</code></p>
      <p><code>93 }</code></p>
      <subtitle>Запись в стандартный поток вывода с помощью функции write</subtitle>
      <p><code>65-68</code> Если есть возможность записи в стандартный поток вывода и число байтов для записи больше нуля, вызывается функция <code>write</code>. Если возвращается ошибка <code>EWOULDBLOCK</code>, ничего не происходит. Обратите внимание, что это условие возможно, поскольку код в конце предыдущей части функции включает бит в наборе флагов записи для стандартного потока вывода, когда не известно, успешно выполнилась функция <code>write</code> или нет.</p>
      <subtitle>Успешное выполнение функции write</subtitle>
      <p><code>68-74</code> Если функция <code>write</code> выполняется успешно, <code>froptr</code> увеличивается на число записанных байтов. Если указатель вывода стал равен указателю ввода, оба указателя переустанавливаются на начало буфера.</p>
      <subtitle>Запись в сокет с помощью функции write</subtitle>
      <p><code>76-90</code> Эта часть кода аналогична коду, только что описанному для записи в стандартный поток вывода. Единственное отличие состоит в том, что когда указатель вывода доходит до указателя ввода, не только оба указателя переустанавливаются в начало буфера, но и появляется возможность отправить серверу сегмент FIN.</p>
      <p>Теперь мы проверим работу этой функции и операций неблокируемого ввода-вывода. В листинге 16.4 показана наша функция <code>gf_time</code>, вызываемая из функции <code>str_cli</code>.</p>
      <p><strong>Листинг 16.4</strong>. Функция gf_time: возвращение указателя на строку времени</p>
      <p><code>//lib/gf_time.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;time.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 char*</code></p>
      <p><code> 4 gf_time(void)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  struct timeval tv;</code></p>
      <p><code> 7  static char str[30];</code></p>
      <p><code> 8  char *ptr;</code></p>
      <empty-line/>
      <p><code> 9  if (gettimeofday(&amp;tv, NULL) &lt; 0)</code></p>
      <p><code>10   err_sys("gettimeofday error");</code></p>
      <empty-line/>
      <p><code>11  ptr = ctime(&amp;tv.tv_sec);</code></p>
      <p><code>12  strcpy(str, &amp;ptr[11]);</code></p>
      <p><code>13  /* Fri Sep 13 00:00:00 1986\n\0 */</code></p>
      <p><code>14  /* 0123456789012345678901234 5 */</code></p>
      <p><code>15  snprintf(str + 8, sizeof(str) - 8, ".%06ld", tv.tv_usec);</code></p>
      <p><code>15  return (str);</code></p>
      <p><code>17 }</code></p>
      <p>Эта функция возвращает строку, содержащую текущее время с точностью до микросекунд, в таком формате:</p>
      <p><code>12:34:56.123456</code></p>
      <p>Здесь специально используется тот же формат, что и для отметок времени, которые выводятся программой <code>tcpdump</code>. Обратите внимание, что все вызовы функции <code>fprintf</code> в нашей функции <code>str_cli</code> записывают данные в стандартный поток сообщений об ошибках, позволяя нам отделить данные стандартного потока вывода (строки, отраженные сервером) от наших диагностических данных. Затем мы можем запустить наш клиент и функцию <code>tcpdump</code>, получить эти диагностические данные вместе с результатом функции <code>tcpdump</code> и отсортировать вместе два вида выходных данных в порядке их получения. Это позволит нам увидеть, что происходит в нашей программе, и соотнести это с действиями TCP.</p>
      <p>Например, сначала мы запускаем функцию <code>tcpdump</code> на нашем узле <code>solaris</code>, собирая только сегменты TCP, идущие к порту 7 или от него (эхо-сервер), и сохраняем выходные данные в файле, который называется <code>tcpd</code>:</p>
      <p><code>solaris % <strong>tcpdump -w tcpd tcp and port 7</strong></code></p>
      <p>Затем мы запускаем клиент TCP на этом узле и указываем сервер на узле <code>linux</code>:</p>
      <p><code>solaris % <strong>tcpcli02 192.168.1.10 &lt; 2000.lines &gt; out 2&gt; diag</strong></code></p>
      <p>Стандартный поток ввода — это файл <code>2000.lines</code>, тот же файл, что мы использовали для листинга 6.2. Стандартный поток вывода перенаправляется в файл <code>out</code>, а стандартный поток сообщений об ошибках — в файл <code>diag</code>. По завершении мы запускаем:</p>
      <p><code>solaris % <strong>diff 2000.lines out</strong></code></p>
      <p>чтобы убедиться, что отраженные строки идентичны введенным строкам. Наконец, мы прекращаем выполнение функции <code>tcpdump</code> нажатием соответствующей клавиши терминала, после чего выводим записи функции <code>tcpdump</code>, сортируя их по времени получения вместе с данными диагностики, полученными от клиента. В листинге 16.5 показана первая часть этого результата.</p>
      <p><strong>Листинг 16.5</strong>. Отсортированный вывод функции tcpdump и данных диагностики</p>
      <p><code>solaris % <strong>tcpdump -r tcpd -N | sort diag -</strong></code></p>
      <p><code>10:18:34.486392 solaris.33621 &gt; linux.echo: S 1802738644:1802738644(0) win 8760 &lt;mss 1460&gt;</code></p>
      <p><code>10:18:34.488278 linux.echo &gt; solaris.33621: S 3212986316 3212986316(0) ack 1802738645 win 8760 &lt;mss 1460&gt;</code></p>
      <p><code>10:18:34.488490 solaris.33621 &gt; linux.echo: . ack 1 win 8760</code></p>
      <p><code>10:18:34.491482: read 4096 bytes from stdin</code></p>
      <p><code>10:18:34.518663 solaris.33621 &gt; linux.echo: P 1461(1460) ack 1 win 8760</code></p>
      <p><code>10:18:34.519016: wrote 4096 bytes to socket</code></p>
      <p><code>10:18:34.528529 linux echo &gt; solaris.33621. P 1:1461(1460) ack 1461 win 8760</code></p>
      <p><code>10:18:34 528785 solaris.33621 &gt; linux.echo: . 1461 2921(1460) ack 1461 win 8760</code></p>
      <p><code>10:18:34.528900 solaris.33621 &gt; linux echo: P 2921:4097(1176) ack 1461 win 8760</code></p>
      <p><code>10:18:34.528958 solaris 33621 &gt; linux.echo: ack 1461 win 8760</code></p>
      <p><code>10:18:34.536193 linux echo: &gt; solaris.33621: . 1461:2921(1460) ack 4097 win 8760</code></p>
      <p><code>10:18:34.536697 linux.echo: &gt; solaris.33621: P 2921.3509(588) ack 4097 win 8760</code></p>
      <p><code>10:18.34.544636: read 4096 bytes from stdin 10:18:34.568505: read 3508 bytes from socket</code></p>
      <p><code>10:18:34.580373 solaris 33621 &gt; linux.echo: . ack 3509 win 8760</code></p>
      <p><code>10:18:34.582244 linux.echo &gt; solaris.33621: P 3509.4097(588) ack 4097 win 8760</code></p>
      <p><code>10:18:34.593354: wrote 3508 bytes to stdout</code></p>
      <p><code>10:18:34.617272 solaris.33621 &gt; linux.echo: P 4097.5557(1460) ack 4097 win 8760</code></p>
      <p><code>10:18:34.617610 solaris 33621 &gt; linux.echo: P 5557:7017(1460) ack 4097 win 8760</code></p>
      <p><code>10:18:34.617908 solaris.33621 &gt; linux.echo: P 7017.8193(1176) ack 4097 win 8760</code></p>
      <p><code>10:18:34.618062: wrote 4096 bytes to socket</code></p>
      <p><code>10:18:34.623310 linux.echo &gt; solaris.33621: . ack 8193 win 8760</code></p>
      <p><code>10:18:34.626129 linux.echo &gt; solaris.33621: . 4097.5557(1460) ack 8193 win 8760</code></p>
      <p><code>10:18:34.626339 solaris.33621 &gt; linux.echo: . ack 5557 win 8760</code></p>
      <p><code>10:18:34.626611 linux.echo &gt; solaris.33621: P 5557:6145(588) ack 8193 win 8760</code></p>
      <p><code>10:18:34.628396 linux.echo &gt; solaris.33621: 6145:7605(1460) ack 8193 win 8760</code></p>
      <p><code>10:18:34.643524: read 4096 bytes from stdin 10:18:34.667305. read 2636 bytes from socket</code></p>
      <p><code>10:18:34.670324 solaris.33621 &gt; linux echo: . ack 7605 win 8760</code></p>
      <p><code>10:18:34.672221 linux.echo &gt; solaris.33621: P 7605.8193(588) ack 8193 win 8760</code></p>
      <p><code>10:18:34.691039: wrote 2636 bytes to stdout</code></p>
      <p>Мы удалили записи (<code>DF</code>) из сегментов, отправленных Solaris, означающие, что устанавливается бит DF (он используется для определения величины транспортной MTU).</p>
      <p>Используя этот вывод, мы можем нарисовать временную диаграмму происходящих событий (рис. 16.3). На этой диаграмме представлены события в различные моменты времени, причем ориентация диаграммы такова, что более поздние события расположены ниже на странице.</p>
      <image l:href="#img_104.png"/>
      <p><strong>Рис. 16.3</strong>. Временная диаграмма событий для примера неблокируемого ввода</p>
      <p>На этом рисунке мы не показываем сегменты ACK. Также помните, что если программа выводит сообщение <code>wrote N bytes to stdout</code> (записано <emphasis>N</emphasis> байт в стандартное устройство вывода), это означает, что завершилась функция <code>write</code>, возможно, заставившая TCP отправить один или более сегментов данных.</p>
      <p>По этому рисунку мы можем проследить динамику обмена между клиентом и сервером. Использование неблокируемого ввода-вывода позволяет программе использовать преимущество этой динамики, считывая или записывая данные, когда операция ввода или вывода может иметь место. Ядро сообщает нам, когда может произойти операция ввода-вывода, при помощи функции <code>select</code>.</p>
      <p>Мы можем рассчитать время выполнения нашей неблокируемой версии, используя тот же файл из 2000 строк и тот же сервер (с периодом RTT, равным 175 мс), что и в разделе 6.7. Теперь время оказалось равным 6,9 с по сравнению с 12,3 с в версии из раздела 6.7. Следовательно, неблокируемый ввод-вывод сокращает общее время выполнения этого примера, в котором файл отправляется серверу.</p>
     </section>
     <section>
      <title>
       <p>Более простая версия функции str_cli</p>
      </title>
      <p>Неблокируемая версия функции <code>str_cli</code>, которую мы только что показали, нетривиальна: около 135 строк кода по сравнению с 40 строками версии, использующей функцию <code>select</code> с блокируемым вводом-выводом (см. листинг 6.2), и 20 строками начальной версии, работающей в режиме остановки и ожидания (см. листинг 5.4). Мы знаем, что эффект от удлинения кода в два раза, с 20 до 40 строк оправдывает затраченные усилия, поскольку в пакетном режиме скорость возрастает почти в 30 раз, а применение функции <code>select</code> с блокируемыми дескрипторами осуществляется не слишком сложно. Но будут ли оправданы затраченные усилия при написании приложения, использующего неблокируемый ввод-вывод, с учетом усложнения итогового кода? Нет, ответим мы. Если нам необходимо использовать неблокируемый ввод-вывод, обычно бывает проще разделить приложение либо на процессы (при помощи функции <code>fork</code>), либо на потоки (см. главу 26).</p>
      <p>В листинге 16.6 показана еще одна версия нашей функции <code>str_cli</code>, разделяемая на два процесса при помощи функции <code>fork</code>.</p>
      <p>Эта функция сразу же вызывает функцию <code>fork</code> для разделения на родительский и дочерний процессы. Дочерний процесс копирует строки от сервера в стандартный поток вывода, а родительский процесс — из стандартного потока ввода серверу, как показано на рис. 16.4.</p>
      <image l:href="#img_105.png"/>
      <p><strong>Рис. 16.4</strong>. Функция str_cli, использующая два процесса</p>
      <p>Мы показываем, что соединения TCP являются двусторонними и что родительский и дочерний процессы совместно используют один и тот же дескриптор сокета: родительский процесс записывает в сокет, а дочерний процесс читает из сокета. Есть только один сокет, один буфер приема сокета и один буфер отправки, но на этот сокет ссылаются два дескриптора: один в родительском процессе и один в дочернем.</p>
      <p><strong>Листинг 16.6</strong>. Версия функции str_cli, использующая функцию fork</p>
      <p><code>//nonblock/strclifork.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 str_cli(FILE *fp, int sockfd)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  pid_t pid;</code></p>
      <p><code> 6  char sendline[MAXLINE], recvline[MAXLINE];</code></p>
      <empty-line/>
      <p><code> 7  if ((pid = Fork()) == 0) { /* дочерний процесс: сервер -&gt; stdout */</code></p>
      <p><code> 8   while (Readline(sockfd, recvline, MAXLINE) &gt; 0)</code></p>
      <p><code> 9    Fputs(recvline, stdout);</code></p>
      <empty-line/>
      <p><code>10   kill(getppid(), SIGTERM); /* в случае, если родительский процесс</code></p>
      <p><code>                                 все еще выполняется */</code></p>
      <p><code>11   exit(0);</code></p>
      <p><code>12  }</code></p>
      <p><code>13  /* родитель: stdin -&gt; сервер */</code></p>
      <p><code>14  while (Fgets(sendline, MAXLINE, fp) != NULL)</code></p>
      <p><code>15   Writen(sockfd, sendline, strlen(sendline));</code></p>
      <p><code>16  Shutdown(sockfd, SHUT_WR); /* конец файла на stdin, посылаем FIN */</code></p>
      <p><code>17  pause();</code></p>
      <p><code>18  return;</code></p>
      <p><code>19 }</code></p>
      <p>Нам нужно снова вспомнить о последовательности завершения соединения. Обычное завершение происходит, когда в стандартном потоке ввода встречается конец файла. Родительский процесс считывает конец файла и вызывает функцию <code>shutdown</code> для отправки сегмента FIN. (Родительский процесс не может вызвать функцию <code>close</code>, см. упражнение 16.1.) Но когда это происходит, дочерний процесс должен продолжать копировать от сервера в стандартный поток вывода, пока он не получит признак конца файла на сокете.</p>
      <p>Также возможно, что процесс сервера завершится преждевременно (см. раздел 5.12), и если это происходит, дочерний процесс считывает признак конца файла на сокете. В таком случае дочерний процесс должен сообщить родительскому, что нужно прекратить копирование из стандартного потока ввода в сокет (см. упражнение 16.2). В листинге 16.6 дочерний процесс отправляет родительскому процессу сигнал <code>SIGTERM</code>, в случае, если родительский процесс еще выполняется (см. упражнение 16.3). Другим способом обработки этой ситуации было бы завершение дочернего процесса, и если родительский процесс все еще выполнялся бы к этому моменту, он получил бы сигнал <code>SIGCHLD</code>.</p>
      <p>Родительский процесс вызывает функцию <code>pause</code>, когда заканчивает копирование, что переводит его в состояние ожидания того момента, когда будет получен сигнал. Даже если родительский процесс не перехватывает никаких сигналов, он все равно переходит в состояние ожидания до получения сигнала <code>SIGTERM</code> от дочернего процесса. По умолчанию действие этого сигнала — завершение процесса, что вполне устраивает нас в этом примере. Родительский процесс ждет завершения дочернего процесса, чтобы измерить точное время для этой версии функции <code>str_cli</code>. Обычно дочерний процесс завершается после родительского, но поскольку мы измеряем время, используя команду оболочки <code>time</code>, измерение заканчивается, когда завершается родительский процесс.</p>
      <p>Отметим простоту этой версии по сравнению с неблокируемым вводом-выводом, представленным ранее в этом разделе. Наша неблокируемая версия управляла четырьмя различными потоками ввода-вывода одновременно, и поскольку все четыре были неблокируемыми, нам пришлось иметь дело с частичным чтением и частичной записью для всех четырех потоков. Но в версии с функцией <code>fork</code> каждый процесс обрабатывает только два потока ввода-вывода, копируя из одного в другой. В применении неблокируемого ввода-вывода не возникает необходимости, поскольку если нет данных для чтения из потока ввода, то и в соответствующий поток вывода записывать нечего.</p>
     </section>
     <section>
      <title>
       <p>Сравнение времени выполнения различных версий функции str_cli</p>
      </title>
      <p>Итак, мы продемонстрировали четыре различных версии функции <code>str_cli</code>. Для каждой версии мы покажем время, которое потребовалось для ее выполнения, в том числе и для версии, использующей программные потоки (см. листинг 26.1). В каждом случае было скопировано 2000 строк от клиента Solaris к серверу с периодом RTT, равным 175 мс:</p>
      <p>&#9632; 354,0 с, режим остановки и ожидания (см. листинг 5.4);</p>
      <p>&#9632; 12,3 с, функция <code>select</code> и блокируемый ввод-вывод (см. листинг 6.2);</p>
      <p>&#9632; 6,9 с, неблокируемый ввод-вывод (см. листинг 16.1);</p>
      <p>&#9632; 8,7 с, функция <code>fork</code> (см. листинг 16.6);</p>
      <p>&#9632; 8,5 с, версия с потоками (см. листинг 26.1).</p>
      <p>Наша версия с неблокируемым вводом-выводом почти вдвое быстрее версии, использующей блокируемый ввод-вывод с функцией <code>select</code>. Наша простая версия с применением функции <code>fork</code> медленнее версии с неблокируемым вводом- выводом. Тем не менее, учитывая сложность кода неблокируемого ввода-вывода по сравнению с кодом функции <code>fork</code>, мы рекомендуем более простой подход.</p>
     </section>
    </section>
    <section>
     <title>
      <p>16.3. Неблокируемая функция connect</p>
     </title>
     <p>Когда сокет TCP устанавливается как неблокируемый, а затем вызывается функция <code>connect</code>, она немедленно возвращает ошибку <code>EINPROGRESS</code>, однако трехэтапное рукопожатие TCP продолжается. Далее мы с помощью функции <code>select</code> проверяем, успешно или нет завершилось установление соединения. Неблокируемая функция connect находит применение в трех случаях:</p>
     <p>1. Трехэтапное рукопожатие может наложиться на какой-либо другой процесс. Для выполнения функции <code>connect</code> требуется один период обращения RTT (см. раздел 2.5), и это может занять от нескольких миллисекунд в локальной сети до сотен миллисекунд или нескольких секунд в глобальной сети. Это время мы можем провести с пользой, выполняя какой-либо другой процесс.</p>
     <p>2. Мы можем установить множество соединений одновременно, используя эту технологию. Этот способ уже стал популярен в применении к веб-браузерам, и такой пример мы приводим в разделе 16.5.</p>
     <p>3. Поскольку мы ждем завершения установления соединения с помощью функции <code>select</code>, мы можем задать предел времени для функции <code>select</code>, что позволит нам сократить тайм-аут для функции <code>connect</code>. Во многих реализациях тайм-аут функции connect лежит в пределах от 75 с до нескольких минут. Бывают случаи, когда приложению нужен более короткий тайм-аут, и одним из решений может стать использование неблокируемой функции <code>connect</code>. В разделе 14.2 рассматриваются другие способы помещения тайм-аута в операции с сокетами.</p>
     <p>Как бы просто ни выглядела неблокируемая функция <code>connect</code>, есть ряд моментов, которые следует учитывать.</p>
     <p>&#9632; Даже если сокет является неблокируемым, то когда сервер, с которым мы соединяемся, находится на том же узле, обычно установление соединения происходит немедленно при вызове функции <code>connect</code>.</p>
     <p>&#9632; В Беркли-реализациях (а также POSIX) имеются два следующих правила, относящихся к функции <code>select</code> и неблокируемой функции <code>connect</code>: во-первых, когда соединение устанавливается успешно, дескриптор становится готовым для записи [128, с. 531], и во-вторых, когда при установлении соединения встречается ошибка, дескриптор становится готовым как для чтения, так и для записи [128, с. 530].</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Эти два правила в отношении функции select выпадают из общего ряда наших правил из раздела 6.3 относительно условий, при которых дескриптор становится готовым для чтения или записи. В сокет TCP можно записывать, если достаточно места в буфере отправки (что всегда будет выполнено в случае присоединенного сокета, поскольку мы еще ничего не записали в сокет) и сокет является присоединенным (что выполняется, только когда завершено трехэтапное рукопожатие). При наличии ошибки, ожидающей обработки, появляется возможность читать из сокета и записывать в сокет.</p>
     </cite>
     <p>С неблокируемыми функциями <code>connect</code> связано множество проблем переносимости, которые мы отметим в последующих примерах.</p>
    </section>
    <section>
     <title>
      <p>16.4. Неблокируемая функция connect: клиент времени и даты</p>
     </title>
     <section>
      <p>В листинге 16.7 показана наша функция <code>connect_nonb</code>, вызывающая неблокируемую функцию <code>connect</code>. Мы заменяем вызов функции <code>connect</code>, имеющийся в листинге 1.1, следующим фрагментом кода:</p>
      <p><code>if (connect_nonb(sockfd, (SA*)&amp;servaddr, sizeof(servaddr), 0) &lt; 0)</code></p>
      <p><code>err_sys("connect error");</code></p>
      <p>Первые три аргумента являются обычными аргументами функции <code>connect</code>, а четвертый аргумент — это число секунд, в течение которых мы ждем завершения установления соединения. Нулевое значение подразумевает отсутствие тайм- аута для функции <code>select</code>; следовательно, для установления соединения TCP ядро будет использовать свой обычный тайм-аут.</p>
      <p><strong>Листинг 16.7</strong>. Неблокируемая функция connect</p>
      <p><code>//lib/connect_nonb.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int flags, n, error;</code></p>
      <p><code> 6  socklen_t len;</code></p>
      <p><code> 7  fd_set rset, wset;</code></p>
      <p><code> 8  struct timeval tval;</code></p>
      <empty-line/>
      <p><code> 9  flags = Fcntl(sockfd, F_GETFL, 0);</code></p>
      <p><code>10  Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);</code></p>
      <empty-line/>
      <p><code>11  error = 0;</code></p>
      <p><code>12  if ((n = connect(sockfd, saptr, salen)) &lt; 0)</code></p>
      <p><code>13   if (errno != EINPROGRESS)</code></p>
      <p><code>14    return (-1);</code></p>
      <empty-line/>
      <p><code>15  /* Пока соединение устанавливается, мы можем заняться чем-то другим */</code></p>
      <empty-line/>
      <p><code>16  if (n == 0)</code></p>
      <p><code>17   goto done; /* функция connect завершилась немедленно */</code></p>
      <empty-line/>
      <p><code>18  FD_ZERO(&amp;rset);</code></p>
      <p><code>19  FDSET(sockfd, &amp;rset);</code></p>
      <p><code>20  wset = rset;</code></p>
      <p><code>21  tval.tv_sec = nsec;</code></p>
      <p><code>22  tval.tv_usec = 0;</code></p>
      <empty-line/>
      <p><code>23  if ((n = Select(sockfd + 1, &amp;rset, &amp;wset, NULL,</code></p>
      <p><code>24   nsec ? &amp;tval : NULL)) == 0) {</code></p>
      <p><code>25   close(sockfd); /* тайм-аут */</code></p>
      <p><code>26   errno = ETIMEDOUT;</code></p>
      <p><code>27   return (-1);</code></p>
      <p><code>28  }</code></p>
      <p><code>29  if (FD_ISSET(sockfd, &amp;rset) || FD_ISSET(sockfd, &amp;wset)) {</code></p>
      <p><code>30   len = sizeof(error);</code></p>
      <p><code>31   if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &amp;error, &amp;len) &lt; 0)</code></p>
      <p><code>32    return (-1); /*в Solaris ошибка, ожидающая обработки */</code></p>
      <p><code>33  } else</code></p>
      <p><code>34   err_quit("select error: sockfd not set");</code></p>
      <empty-line/>
      <p><code>35 done:</code></p>
      <p><code>36  Fcntl(sockfd, F_SETFL, flags); /* восстанавливаем флаги, задающие статус файла */</code></p>
      <empty-line/>
      <p><code>37  if (error) {</code></p>
      <p><code>38   close(sockfd); /* на всякий случай */</code></p>
      <p><code>39   errno = error;</code></p>
      <p><code>40   return (-1);</code></p>
      <p><code>41  }</code></p>
      <p><code>42  return (0);</code></p>
      <p><code>43 }</code></p>
      <subtitle>Задание неблокируемого сокета</subtitle>
      <p><code>9-10</code> Мы вызываем функцию <code>fcntl</code>, которая делает сокет неблокируемым.</p>
      <p><code>11-14</code> Мы вызываем неблокируемую функцию <code>connect</code>. Ошибка, которую мы ожидаем (<code>EINPROGRESS</code>), указывает на то, что установление соединения началось, но еще не завершилось [128, с. 466]. Любая другая ошибка возвращается вызывающему процессу.</p>
      <subtitle>Выполнение других процессов во время установления соединения</subtitle>
      <p><code>15</code> На этом этапе мы можем делать все, что захотим, ожидая завершения установления соединения.</p>
      <subtitle>Проверка немедленного завершения</subtitle>
      <p><code>16-17</code> Если неблокируемая функция <code>connect</code> возвратила нуль, установление соединения завершилось. Как мы сказали, это может произойти, когда сервер находится на том же узле, что и клиент.</p>
      <subtitle>Вызов функции select</subtitle>
      <p><code>18-24</code> Мы вызываем функцию <code>select</code> и ждем, когда сокет будет готов либо для чтения, либо для записи. Мы обнуляем <code>rset</code>, включаем бит, соответствующий <code>sockfd</code> в этом наборе дескрипторов и затем копируем <code>rset</code> в <code>wset</code>. Это присваивание, возможно, является структурным присваиванием, поскольку обычно наборы дескрипторов представляются как структуры. Далее мы инициализируем структуру <code>timeval</code> и затем вызываем функцию <code>select</code>. Если вызывающий процесс задает четвертый аргумент нулевым (что соответствует использованию тайм-аута по умолчанию), следует задать в качестве последнего аргумента функции <code>select</code> пустой указатель, а не структуру <code>timeval</code> с нулевым значением (означающим, что мы не ждем вообще).</p>
      <subtitle>Обработка тайм-аутов</subtitle>
      <p><code>25-28</code> Если функция <code>select</code> возвращает нуль, это означает, что время таймера истекло, и мы возвращаем вызывающему процессу ошибку <code>ETIMEDOUT</code>. Мы также закрываем сокет, чтобы трехэтапное рукопожатие не продолжалось.</p>
      <subtitle>Проверка возможности чтения или записи</subtitle>
      <p><code>29-34</code> Если дескриптор готов для чтения или для записи, мы вызываем функцию <code>getsockopt</code>, чтобы получить ошибку сокета (<code>SO_ERROR</code>), ожидающую обработки. Если соединение завершилось успешно, это значение будет нулевым. Если при установлении соединения произошла ошибка, это значение является значением переменной <code>errno</code>, соответствующей ошибке соединения (например, <code>ECONNREFUSED</code>, <code>ETIMEDOUT</code> и т.д.). Мы также сталкиваемся с нашей первой проблемой переносимости. Если происходит ошибка, Беркли-реализации функции <code>getsockopt</code> возвращают нуль, а ошибка, ожидающая обработки, возвращается в нашей переменной <code>error</code>. Но в системе Solaris сама функция <code>getsockopt</code> возвращает -1, а переменная <code>errno</code> при этом принимает значение, соответствующее ошибке, ожидающей обработки. В нашем коде обрабатываются оба сценария.</p>
      <subtitle>Восстановление возможности блокировки сокета и завершение</subtitle>
      <p><code>36-42</code> Мы восстанавливаем флаги, задающие статус файла, и возвращаемся. Если наша переменная errno имеет ненулевое значение в результате выполнения функции <code>getsockopt</code>, это значение хранится в переменной <code>errno</code>, и функция возвращает -1.</p>
      <p>Как мы сказали ранее, проблемы переносимости для функции <code>connect</code> связаны с различными реализациями сокетов и отключения блокировки. Во-первых, возможно, что установление соединения завершится и придут данные для собеседника до того, как будет вызвана функция <code>select</code>. В этом случае сокет будет готов для чтения и для записи при успешном выполнении функции, как и при неудачном установленном соединении. В нашем коде, показанном в листинге 16.7, этот сценарий обрабатывается при помощи вызова функции <code>getsockopt</code> и проверки на наличие ошибки, ожидающей обработки, для сокета.</p>
      <p>Во-вторых, проблема в том, как определить, успешно завершилось установление соединения или нет, если мы не можем считать возможность записи единственным указанием на успешное установление соединения. В Usenet предлагалось множество решений этой проблемы, которые заменяют наш вызов функции <code>getsockopt</code> в листинге 16.7:</p>
      <p>1. Вызвать функцию <code>getpeername</code> вместо функции <code>getsockopt</code>. Если этот вызов окажется неудачным и возвратится ошибка <code>ENOTCONN</code>, значит, соединение не было установлено, и чтобы получить ошибку, ожидающую обработки, следует вызвать для сокета функцию <code>getsockopt</code> с <code>SO_ERROR</code>.</p>
      <p>2. Вызвать функцию <code>read</code> с нулевым значением аргумента <code>length</code>. Если выполнение функции read окажется неудачным, функция connect выполнилась неудачно, и переменная errno из функции <code>read</code> при этом указывает на причину неудачной попытки установления соединения. Если соединение успешно установлено, функция <code>read</code> возвращает нуль.</p>
      <p>3. Снова вызвать функцию <code>connect</code>. Этот вызов окажется неудачным, и если ошибка — <code>EISCONN</code>, сокет уже присоединен, а значит, первое соединение завершилось успешно.</p>
      <p>К сожалению, неблокируемая функция <code>connect</code> — это одна из самых сложных областей сетевого программирования с точки зрения переносимости. Будьте готовы к проблемам совместимости, особенно с более ранними реализациями. Более простой технологией является создание потока (см. главу 26) для обработки соединения.</p>
     </section>
     <section>
      <title>
       <p>Прерванная функция connect</p>
      </title>
      <p>Что происходит, если наш вызов функции <code>connect</code> на обычном блокируемом сокете прерывается, скажем, перехваченным сигналом, прежде чем завершится трехэтапное рукопожатие TCP? Если предположить, что функция <code>connect</code> не перезапускается автоматически, то она возвращает ошибку <code>EINTR</code>. Но мы не можем снова вызвать функцию connect, чтобы добиться завершения установления соединения. Это приведет к ошибке <code>EADDRINUSE</code>.</p>
      <p>Все, что требуется сделать в этом сценарии, — вызвать функцию <code>select</code>, так, как мы делали в этом разделе для неблокируемой функции <code>connect</code>. Тогда функция <code>select</code> завершится, если соединение успешно устанавливается (делая сокет доступным для записи) или если попытка соединения неудачна (сокет становится доступен для чтения и для записи).</p>
     </section>
    </section>
    <section>
     <title>
      <p>16.5. Неблокируемая функция connect: веб-клиент</p>
     </title>
     <section>
      <p>Первое практическое использование неблокируемой функции <code>connect</code> относится к веб-клиенту Netscape (см. раздел 13.4 [112]). Клиент устанавливает соединение HTTP с веб-сервером и попадает на домашнюю страницу. На этой странице часто присутствуют ссылки на другие веб-страницы. Вместо того чтобы получать последовательно по одной странице за один раз, клиент может получить сразу несколько страниц, используя неблокируемые функции <code>connect</code>. На рис. 16.5 показан пример установления множества параллельных соединений. Сценарий, изображенный слева, показывает все три соединения, устанавливаемые одно за другим. Мы считаем, что первое соединение занимает 10 единиц времени, второе — 15, а третье — 4, что в сумме дает 29 единиц времени.</p>
      <image l:href="#img_106.png"/>
      <p><strong>Рис. 16.5</strong>. Установление множества параллельных соединений</p>
      <p>В центре рисунка показан сценарий, при котором мы выполняем два параллельных соединения. В момент времени 0 запускаются первые два соединения, а когда первое из них устанавливается, мы запускаем третье. Общее время сократилось почти вдвое и равно 15, а не 29 единицам времени, но учтите, что это идеальный случай. Если параллельные соединения совместно используют общий канал связи (допустим, клиент использует модем для соединения с Интернетом), то каждое из этих соединений конкурирует с другими за обладание ограниченными ресурсами этого канала связи, и время установления каждого соединения может возрасти. Например, время 10 может дойти до 15, 15 — до 20, а время 4 может превратиться в 6. Тем не менее общее время будет равно 21 единице, то есть все равно меньше, чем в последовательном сценарии.</p>
      <p>В третьем сценарии мы выполняем три параллельных соединения и снова считаем, что эти три соединения не мешают друг другу (идеальный случай). Но общее время при этом такое же (15 единиц), как и во втором сценарии.</p>
      <p>При работе с веб-клиентами первое соединение устанавливается само по себе, за ним следуют соединения по ссылкам, обнаруженным в данных от первого соединения. Мы показываем это на рис. 16.6.</p>
      <image l:href="#img_107.png"/>
      <p><strong>Рис. 16.6</strong>. Установление первого соединения, а затем множества параллельных соединений</p>
      <p>Для дальнейшей оптимизации клиент может начать обработку данных, возвращаемых по первому соединению, до того, как установление первого соединения завершится, и инициировать дополнительные соединения, как только ему станет известно, что они нужны.</p>
      <p>Поскольку мы выполняем несколько неблокируемых функций <code>connect</code> одновременно, мы не можем использовать нашу функцию <code>connect_nonb</code>, показанную в листинге 16.7, так как она не завершается, пока соединение не установлено. Вместо этого мы отслеживаем множество соединений самостоятельно.</p>
      <p>Наша программа считывает около 20 строк с веб-сервера. Мы задаем в качестве аргументов командной строки максимальное число параллельных соединений, имя узла сервера, а затем каждое из имен файлов, получаемых с сервера. Типичное выполнение нашей программы выглядит так:</p>
      <p><code>solaris % <strong>web % www.foobar.com / image1.gif image2.gif \</strong></code></p>
      <p><code><strong> image3.gif image4.gif image5.gif \</strong></code></p>
      <p><code><strong> image6.gif image7.gif</strong></code></p>
      <p>Аргументы командной строки задают три одновременных соединения, имя узла сервера, имя файла домашней страницы (<code>/</code> обозначает корневой каталог сервера) и семь файлов, которые затем нужно прочитать (в нашем примере это файлы с изображениями в формате GIF). Обычно на эти семь файлов имеются ссылки с домашней страницы, и чтобы получить их имена, веб-клиент читает домашнюю страницу и обрабатывает код HTML. Чтобы не усложнять этот пример разбором кода HTML, мы просто задаем имена файлов в командной строке.</p>
      <p>Это большой пример, поэтому мы будем показывать его частями. В листинге 16.8 представлен наш заголовочный файл <code>web.h</code>, который включен во все файлы.</p>
      <p><strong>Листинг 16.8</strong>. Заголовок web.h</p>
      <p><code>//nonblock/web.h</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 #define MAXFILES 20</code></p>
      <p><code> 3 #define SERV "80" /* номер порта или имя службы */</code></p>
      <empty-line/>
      <p><code> 4 struct file {</code></p>
      <p><code> 5  char *f_name; /* имя файла */</code></p>
      <p><code> 6  char *f_host; /* имя узла или адрес IPv4/IPv6 */</code></p>
      <p><code> 7  int  f_fd;    /* дескриптор */</code></p>
      <p><code> 8  int  f_flags; /* F_xxx определены ниже */</code></p>
      <p><code> 9 } file[MAXFILES];</code></p>
      <empty-line/>
      <p><code>10 #define F_CONNECTING 1 /* connect() в процессе выполнения */</code></p>
      <p><code>11 #define F_READING 2 /* соединение установлено; происходит считывание */</code></p>
      <p><code>12 #define F_DONE 4 /* все сделано */</code></p>
      <empty-line/>
      <p><code>13 #define GET_CMD "GET %s HTTP/1.0\r\n\r\n"</code></p>
      <empty-line/>
      <p><code>14 /* глобальные переменные */</code></p>
      <p><code>15 int nconn, nfiles, nlefttoconn, nlefttoread, maxfd;</code></p>
      <p><code>16 fd_set rset, wset;</code></p>
      <empty-line/>
      <p><code>17 /* прототипы функций */</code></p>
      <p><code>18 void home_page(const char*, const char*);</code></p>
      <p><code>19 void start_connect (struct file*);</code></p>
      <p><code>20 void write_get_cmd(struct file*);</code></p>
      <subtitle>Задание структуры file</subtitle>
      <p><code>2-13</code> Программа считывает некоторое количество (не более <code>MAXFILES</code>) файлов с веб-сервера. Структура <code>file</code> содержит информацию о каждом файле: его имя (копируется из аргумента командной строки), имя узла или IP-адрес сервера, с которого читается файл, дескриптор сокета, используемый для этого файла, и набор флагов, которые указывают, что мы делаем с этим файлом (устанавливаем соединение для получения файла или считываем файл).</p>
      <subtitle>Определение глобальных переменных и прототипов функций</subtitle>
      <p><code>14-20</code> Мы определяем глобальные переменные и прототипы для наших функций, которые мы вскоре опишем.</p>
      <p><strong>Листинг 16.9</strong>. Первая часть программы одновременного выполнения функций connect: глобальные переменные и начало функции main</p>
      <p><code>//nonblock/web.c</code></p>
      <p><code> 1 #include "web.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int i, fd, n, maxnconn, flags, error;</code></p>
      <p><code> 6  char buf[MAXLINE];</code></p>
      <p><code> 7  fd_set rs, ws;</code></p>
      <empty-line/>
      <p><code> 8  if (argc &lt; 5)</code></p>
      <p><code> 9   err_quit("usage: web &lt;#conns&gt; &lt;hostname&gt; &lt;homepage&gt; &lt;file1&gt; ...");</code></p>
      <p><code>10  maxnconn = atoi(argv[1]);</code></p>
      <empty-line/>
      <p><code>11  nfiles = min(argc - 4, MAXFILES);</code></p>
      <p><code>12  for (i = 0; i &lt; nfiles; i++) {</code></p>
      <p><code>13   file[i].f_name = argv[i + 4];</code></p>
      <p><code>14   file[i].f_host = argv[2];</code></p>
      <p><code>15   file[i].f_flags = 0;</code></p>
      <p><code>16  }</code></p>
      <p><code>17  printf("nfiles = %d\n", nfiles);</code></p>
      <empty-line/>
      <p><code>18  home_page(argv[2], argv[3]);</code></p>
      <empty-line/>
      <p><code>19  FD_ZERO(&amp;rset);</code></p>
      <p><code>20  FD_ZERO(&amp;wset);</code></p>
      <p><code>21  maxfd = -1;</code></p>
      <p><code>22  nlefttoread = nlefttoconn = nfiles;</code></p>
      <p><code>23  nconn = 0;</code></p>
      <subtitle>Обработка аргументов командной строки</subtitle>
      <p><code>11-17</code> Структуры <code>file</code> заполняются соответствующей информацией из аргументов командной строки.</p>
      <subtitle>Чтение домашней страницы</subtitle>
      <p><code>18</code> Функция <code>home_page</code>, которую мы показываем в следующем листинге, создает соединение TCP, посылает команду серверу и затем читает домашнюю страницу. Это первое соединение, которое выполняется самостоятельно, до того как мы начнем устанавливать параллельные соединения.</p>
      <subtitle>Инициализация глобальных переменных</subtitle>
      <p><code>19-23</code> Инициализируются два набора дескрипторов, по одному для чтения и для записи. <code>maxfd</code> — это максимальный дескриптор для функции <code>select</code> (который мы инициализируем значением -1, поскольку дескрипторы неотрицательны), <code>nlefttoread</code> — число файлов, которые осталось прочитать (когда это значение становится нулевым, чтение заканчивается), <code>nlefttoconn</code> — это количество файлов, для которых пока еще требуется соединение TCP, a <code>nconn</code> — это число соединений, открытых в настоящий момент (оно никогда не может превышать первый аргумент командной строки).</p>
      <p>В листинге 16.10 показана функция <code>home_page</code>, вызываемая один раз, когда начинается выполнение функции main.</p>
      <p><strong>Листинг 16.10</strong>. Функция home_page</p>
      <p><code>//nonblock/home_page.c</code></p>
      <p><code> 1 #include "web.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 home_page(const char *host, const char *fname)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int fd, n;</code></p>
      <p><code> 6  char line[MAXLINE];</code></p>
      <empty-line/>
      <p><code> 7  fd = Tcp_connect(host, SERV); /* блокируемая функция connect() */</code></p>
      <empty-line/>
      <p><code> 8  n = snprintf(line, sizeof(line), GET_CMD, fname);</code></p>
      <p><code> 9  Writen(fd, line, n);</code></p>
      <empty-line/>
      <p><code>10  for (;;) {</code></p>
      <p><code>11   if ((n = Read(fd, line, MAXLINE)) == 0)</code></p>
      <p><code>12    break; /* сервер закрыл соединение */</code></p>
      <empty-line/>
      <p><code>13   printf("read %d bytes of home page\n", n);</code></p>
      <p><code>14   /* обрабатываем полученные данные */</code></p>
      <p><code>15  }</code></p>
      <p><code>16  printf("end-of-file on home page\n");</code></p>
      <p><code>17  Close(fd);</code></p>
      <p><code>18 }</code></p>
      <subtitle>Установление соединения с сервером</subtitle>
      <p><code>7</code> Наша функция <code>tcp_connect</code> устанавливает соединение с сервером.</p>
      <subtitle>Отправка команды HTTP серверу, чтение ответа</subtitle>
      <p><code>8-17</code> Запускается команда HTTP <code>GET</code> для домашней страницы (часто обозначается символом <code>/</code>). Читается ответ (с ответом мы в данном случае ничего не делаем), и соединение закрывается.</p>
      <p>Следующая функция, <code>start_connect</code>, показанная в листинге 16.11, инициирует вызов неблокируемой функции connect.</p>
      <p><strong>Листинг 16.11</strong>. Инициирование неблокируемой функции connect</p>
      <p><code>//nonblock/start_connect.c</code></p>
      <p><code> 1 #include "web.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 start_connect(struct file *fptr)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int fd, flags, n;</code></p>
      <p><code> 6  struct addrinfo *ai;</code></p>
      <empty-line/>
      <p><code> 7  ai = Host_serv(fptr-&gt;f_host, SERV, 0, SOCK_STREAM);</code></p>
      <empty-line/>
      <p><code> 8  fd = Socket(ai-&gt;ai_family; ai-&gt;ai_socktype, ai-&gt;ai_protocol);</code></p>
      <p><code> 9  fptr-&gt;f_fd = fd;</code></p>
      <p><code>10  printf("start_connect for %s, fd %d\n", fptr-&gt;f_name, fd);</code></p>
      <empty-line/>
      <p><code>11  /* отключаем блокирование сокета */</code></p>
      <p><code>12  flags = Fcntl(fd, F_GETFL, 0);</code></p>
      <p><code>13  Fcntl(fd, F_SETFL, flags | O_NONBLOCK);</code></p>
      <empty-line/>
      <p><code>14  /* инициируем неблокируемое соединение с сервером */</code></p>
      <p><code>15  if ((n = connected, ai-&gt;ai_addr, ai-&gt;ai_addrlen)) &lt; 0) {</code></p>
      <p><code>16   if (errno != EINPROGRESS)</code></p>
      <p><code>17    err_sys("nonblocking connect error");</code></p>
      <p><code>18   fptr-&gt;f_flags = F_CONNECTING;</code></p>
      <p><code>19   FD_SET(fd, &amp;rset); /* включаем дескриптор сокета в наборе чтения</code></p>
      <p><code>                           и записи */</code></p>
      <p><code>20   FD_SET(fd, &amp;wset);</code></p>
      <p><code>21   if (fd &gt; maxfd)</code></p>
      <p><code>22    maxfd = fd;</code></p>
      <empty-line/>
      <p><code>23  } else if (n &gt;= 0) /* соединение уже установлено */</code></p>
      <p><code>24   write_get_cmd(fptr); /* отправляем команду GET серверу */</code></p>
      <p><code>25 }</code></p>
      <subtitle>Создание сокета, отключение блокировки сокета</subtitle>
      <p><code>7-13</code> Мы вызываем нашу функцию <code>host_serv</code> для поиска и преобразования имени узла и имени службы. Она возвращает указатель на массив структур <code>addrinfo</code>. Мы используем только первую структуру. Создается сокет TCP, и он становится неблокируемым.</p>
      <subtitle>Вызов неблокируемой функции connect</subtitle>
      <p><code>14-22</code> Вызывается неблокируемая функция <code>connect</code>, и флагу файла присваивается значение <code>F_CONNECTING</code>. Включается дескриптор сокета и в наборе чтения, и в наборе записи, поскольку функция <code>select</code> будет ожидать любого из этих условий как указания на то, что установление соединения завершилось. При необходимости мы также обновляем значение <code>maxfd</code>.</p>
      <subtitle>Обработка завершения установления соединения</subtitle>
      <p><code>23-24</code> Если функция <code>connect</code> успешно завершается, значит, соединение уже установлено, и функция <code>write_get_cmd</code> (она показана в следующем листинге) посылает команду серверу.</p>
      <p>Мы делаем сокет неблокируемым для функции <code>connect</code>, но никогда не переустанавливаем его в блокируемый режим, заданный по умолчанию. Это нормально, поскольку мы записываем в сокет только небольшое количество данных (команда GET следующей функции) и считаем, что эти данные занимают значительно меньше места, чем имеется в буфере отправки сокета. Даже если из-за установленного флага отсутствия блокировки при вызове функции <code>write</code> происходит частичное копирование, наша функция <code>writen</code> обрабатывает эту ситуацию. Если оставить сокет неблокируемым, это не повлияет на последующее выполнение функций <code>read</code>, потому что мы всегда вызываем функцию <code>select</code> для определения того момента, когда сокет станет готов для чтения.</p>
      <p>В листинге 16.12 показана функция <code>write_get_cmd</code>, посылающая серверу команду HTTP GET.</p>
      <p><strong>Листинг 16.12</strong>. Отправка команды HTTP GET серверу</p>
      <p><code>//nonblock/write_get_cmd.c</code></p>
      <p><code> 1 #include "web.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 write_get_cmd(struct file *fptr)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int n;</code></p>
      <p><code> 6  char line[MAXLINE];</code></p>
      <empty-line/>
      <p><code> 7  n = snprintf(line, sizeof(line), GET_CMD, fptr-&gt;f_name);</code></p>
      <p><code> 8  Writen(fptr-&gt;f_fd, line, n);</code></p>
      <p><code> 9  printf("wrote %d bytes for %s\n", n, fptr-&gt;f_name);</code></p>
      <empty-line/>
      <p><code>10  fptr-&gt;f_flags = F_READING; /* сброс F_CONNECTING */</code></p>
      <p><code>11  FD_SET(fptr-&gt;f_fd, &amp;rset); /* прочитаем ответ сервера */</code></p>
      <p><code>12  if (fptr-&gt;f_fd &gt; maxfd)</code></p>
      <p><code>13   maxfd = fptr-&gt;f_fd;</code></p>
      <p><code>14 }</code></p>
      <subtitle>Создание команды и ее отправка</subtitle>
      <p><code>7-9</code> Команда создается и пишется в сокет.</p>
      <subtitle>Установка флагов</subtitle>
      <p><code>10-13</code> Устанавливается флаг <code>F_READING</code>, при этом также сбрасывается флаг <code>F_CONNECTING</code> (если он установлен). Это указывает основному циклу, что данный дескриптор готов для ввода. Также включается дескриптор в наборе чтения, и при необходимости обновляется значение <code>maxfd</code>.</p>
      <p>Теперь мы возвращаемся в функцию <code>main</code>, показанную в листинге 16.13, начиная с того места, где закончили в листинге 16.9. Это основной цикл программы: пока имеется ненулевое количество файлов для обработки (значение <code>nlefttoread</code> больше нуля), устанавливается, если это возможно, другое соединение и затем вызывается функция <code>select</code> для всех активных дескрипторов, обрабатывающая как завершение неблокируемых соединений, так и прием данных.</p>
      <subtitle>Можем ли мы инициировать другое соединение?</subtitle>
      <p><code>24-35</code> Если мы не дошли до заданного предела одновременных соединений и есть дополнительные соединения, которые нужно установить, мы ищем еще не обработанный файл (на него указывает нулевое значение <code>f_flags</code>) и вызываем функцию <code>start_connect</code> для инициирования соединения. Число активных соединений увеличивается на единицу (<code>nconn</code>), а число соединений, которые нужно установить, на единицу уменьшается (<code>nlefttoconn</code>).</p>
      <subtitle>Функция select: ожидание событий</subtitle>
      <p><code>36-37</code> Функция <code>select</code> ожидает готовности сокета либо для чтения, либо для записи. Дескрипторы, для которых в настоящий момент происходит установление соединения (неблокируемая функция <code>connect</code> находится в процессе выполнения), будут включены в обоих наборах, в то время как дескрипторы с завершенным соединением, ожидающие данных от сервера, будут включены только в наборе чтения.</p>
      <p><strong>Листинг 16.13</strong>. Основной цикл функции main</p>
      <p><code>//nonblock/web.c</code></p>
      <p><code>24  while (nlefttoread &gt; 0) {</code></p>
      <p><code>25   while (nconn &lt; maxnconn &amp;&amp; nlefttoconn &gt; 0) {</code></p>
      <p><code>26    /* find a file to read */</code></p>
      <p><code>27    for (i =0; i &lt; nfiles; i++)</code></p>
      <p><code>28     if (file[i].f_flags == 0)</code></p>
      <p><code>29      break;</code></p>
      <p><code>30    if (i == nfiles)</code></p>
      <p><code>31     err_quit("nlefttoconn = %d but nothing found", nlefttoconn);</code></p>
      <p><code>32    start_connect(&amp;file[i]);</code></p>
      <p><code>33    nconn++;</code></p>
      <p><code>34    nlefttoconn--;</code></p>
      <p><code>35   }</code></p>
      <empty-line/>
      <p><code>36   rs = rset:</code></p>
      <p><code>37   ws = wset;</code></p>
      <p><code>38   n = Select(maxfd + 1, &amp;rs, &amp;ws, NULL, NULL);</code></p>
      <p><code>39   for (i = 0; i &lt; nfiles; i++) {</code></p>
      <p><code>40    flags = file[i].f_flags;</code></p>
      <p><code>41    if (flags == 0 || flags &amp; F_DONE)</code></p>
      <p><code>42     continue;</code></p>
      <p><code>43    fd = file[i].f_fd;</code></p>
      <p><code>44    if (flags &amp; F_CONNECTING &amp;&amp;</code></p>
      <p><code>45     (FD_ISSET(fd, &amp;rs) || FD_ISSET(fd, &amp;ws))) {</code></p>
      <p><code>46     n = sizeof(error);</code></p>
      <p><code>47     if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &amp;error, &amp;n) &lt; 0 ||</code></p>
      <p><code>48      error != 0) {</code></p>
      <p><code>49      err_ret("nonblocking connect failed</code></p>
      <p><code>50       for %s", file[i].f_name);</code></p>
      <p><code>51     }</code></p>
      <p><code>52     /* соединение установлено */</code></p>
      <p><code>53     printf("connection established for %s\n", file[i].f_name);</code></p>
      <p><code>54     FD_CLR(fd, &amp;wset); /* отключаем запись в этот сокет */</code></p>
      <p><code>55     write_get_cmd(&amp;file[i]); /* передаем команду GET */</code></p>
      <empty-line/>
      <p><code>56    } else if (flags &amp; F_READING &amp;&amp; FD_ISSET(fd, &amp;rs)) {</code></p>
      <p><code>57     if ((n = Read(fd, buf, sizeof(buf))) == 0) {</code></p>
      <p><code>58      printf("end-of-file on %s\n", file[i].f_name);</code></p>
      <p><code>59      Close(fd);</code></p>
      <p><code>60      file[i].f_flags = F_DONE; /* сбрасывает флаг F_READING */</code></p>
      <p><code>61      FD_CLR(fd, &amp;rset);</code></p>
      <p><code>62      nconn--;</code></p>
      <p><code>63      nlefttoread--;</code></p>
      <p><code>64     } else {</code></p>
      <p><code>65      printf("read %d bytes from %s\n", n, file[i].f_name);</code></p>
      <p><code>66     }</code></p>
      <p><code>67    }</code></p>
      <p><code>68   }</code></p>
      <p><code>69  }</code></p>
      <p><code>70  exit(0);</code></p>
      <p><code>71 }</code></p>
      <subtitle>Обработка всех готовых дескрипторов</subtitle>
      <p><code>39-55</code> Теперь мы анализируем каждый элемент массива структур <code>file</code>, чтобы определить, какие дескрипторы нужно обрабатывать. Если установлен флаг <code>F_CONNECTING</code> и дескриптор включен либо в наборе чтения, либо в наборе записи, неблокируемая функция <code>connect</code> завершается. Как мы говорили при описании листинга 16.7, мы вызываем функцию <code>getsockopt</code>, чтобы получить ожидающую обработки ошибку для сокета. Если значение ошибки равно нулю, соединение успешно завершилось. В этом случае мы выключаем дескриптор в наборе флагов записи и вызываем функцию <code>write_get_cmd</code> для отправки запроса HTTP серверу.</p>
      <subtitle>Проверка, есть ли у дескриптора данные</subtitle>
      <p><code>56-67</code> Если установлен флаг <code>F_READING</code> и дескриптор готов для чтения, мы вызываем функцию <code>read</code>. Если соединение было закрыто другим концом, мы закрываем сокет, устанавливаем флаг <code>F_DONE</code>, выключаем дескриптор в наборе чтения и уменьшаем число активных соединений и общее число соединений, требующих обработки.</p>
      <p>Есть два способа оптимизации, которые мы не используем в этом примере (чтобы не усложнять его еще больше). Во-первых, мы можем завершить цикл for в листинге 16.13, когда мы обработали число дескрипторов, которые, по сообщению функции <code>select</code>, были готовы. Во-вторых, мы могли, где это возможно, уменьшить значение <code>maxfd</code>, чтобы функция <code>select</code> не проверяла биты дескрипторов, которые уже сброшены. Поскольку число дескрипторов, используемых в этом коде, в любой момент времени, вероятно, меньше 10, а не порядка тысяч, вряд ли какая-либо из этих оптимизаций стоит дополнительных усложнений.</p>
     </section>
     <section>
      <title>
       <p>Эффективность одновременных соединений</p>
      </title>
      <p>Каков выигрыш в эффективности при установлении множества одновременных соединений? В табл. 16.1 показано время, необходимое для выполнения определенной задачи, которая состоит в том, чтобы получить от веб-сервера домашнюю страницу и девять картинок. Время обращения RTT для данного соединения с сервером равно приблизительно 150 мс. Размер домашней страницы — 4017 байт, а средний размер девяти файлов с изображениями составил 1621 байт. Размер сегмента TCP равен 512 байт. Для сравнения мы также представляем в этой таблице значения для многопоточной версии данной программы, которую мы создаем в разделе 26.9.</p>
      <empty-line/>
      <p><strong>Таблица 16.1</strong>. Время выполнения задания для разного количества одновременных соединений в разных версиях программы</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Количество одновременных соединений</th>
        <th align="left" valign="top">Затраченное время (в секундах), отсутствие блокирования</th>
        <th align="left" valign="top">Затраченное время (в секундах), использование потоков</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">1</td>
        <td align="left" valign="top">6,0</td>
        <td align="left" valign="top">6,3</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">2</td>
        <td align="left" valign="top">4,1</td>
        <td align="left" valign="top">4,2</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">3</td>
        <td align="left" valign="top">3,0</td>
        <td align="left" valign="top">3,1</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">4</td>
        <td align="left" valign="top">2,8</td>
        <td align="left" valign="top">3,0</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">5</td>
        <td align="left" valign="top">2,5</td>
        <td align="left" valign="top">2,7</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">6</td>
        <td align="left" valign="top">2,4</td>
        <td align="left" valign="top">2,5</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">7</td>
        <td align="left" valign="top">2,3</td>
        <td align="left" valign="top">2,3</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">8</td>
        <td align="left" valign="top">2,2</td>
        <td align="left" valign="top">2,3</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">9</td>
        <td align="left" valign="top">2,0</td>
        <td align="left" valign="top">2,3</td>
       </tr>
      </table>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Мы показали пример использования одновременных соединений, поскольку он служит хорошей иллюстрацией применения неблокируемого ввода-вывода, а также потому, что в данном случае эффективность применения одновременных соединений может быть измерена. Это свойство также используется в популярном приложении — веб-браузере Netscape. В этой технологии могут появиться некоторые «подводные камни», если сеть перегружена. В главе 21 [111] подробно описываются алгоритмы TCP, называемые алгоритмами медленного старта (slow start) и предотвращения перегрузки сети (congestion avoidance). Когда от клиента к серверу устанавливается множество соединений, то взаимодействие между соединениями на уровне TCP отсутствует. То есть если на одном из соединений происходит потеря пакета, другие соединения с тем же сервером не получают соответствующего уведомления, и вполне возможно, что другие соединения вскоре также столкнутся с потерей пакетов, пока не замедлятся. По этим дополнительным соединениям будет продолжаться отправка слишком большого количества пакетов в уже перегруженную сеть. Эта технология также увеличивает нагрузку на сервер.</p>
      </cite>
      <p>Максимальное увеличение эффективности происходит при трех одновременных соединениях (время уменьшается вдвое), а при четырех и более одновременных соединениях прирост производительности значительно меньше.</p>
     </section>
    </section>
    <section>
     <title>
      <p>16.6. Неблокируемая функция accept</p>
     </title>
     <p>Как было сказано в главе 6, функция <code>select</code> сообщает, что прослушиваемый сокет готов для чтения, когда установленное соединение готово к обработке функцией <code>accept</code>. Следовательно, если мы используем функцию <code>select</code> для определения готовности входящих соединений, то нам не нужно делать прослушиваемый сокет неблокируемым, потому что когда функция <code>select</code> сообщает нам, что соединение установлено, функция <code>accept</code> обычно не является блокируемой.</p>
     <p>К сожалению, существует определенная проблема, связанная со временем, способная запутать нас [34]. Чтобы увидеть эту проблему, изменим код нашего эхо- клиента TCP (см. листинг 5.3) таким образом, чтобы после установления соединения серверу отсылался сегмент RST. В листинге 16.14 представлена новая версия.</p>
     <p><strong>Листинг 16.14</strong>. Эхо-клиент TCP, устанавливающий соединение и посылающий серверу сегмент RST</p>
     <p><code>//nonblock/tcpcli03.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd;</code></p>
     <p><code> 6  struct linger ling;</code></p>
     <p><code> 7  struct sockaddr_in servaddr;</code></p>
     <empty-line/>
     <p><code> 8  if (argc != 2)</code></p>
     <p><code> 9   err_quit("usage: tcpcli &lt;IPaddress&gt;");</code></p>
     <p><code>10  sockfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code>11  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>12  servaddr.sin_family = AF_INET;</code></p>
     <p><code>13  servaddr.sin_port = htons(SERV_PORT);</code></p>
     <p><code>14  Inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr);</code></p>
     <empty-line/>
     <p><code>15  Connect(sockfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>16  ling.l_onoff = 1; /* для отправки сегмента RST при закрытии соединения */</code></p>
     <p><code>17  ling.l_linger = 0;</code></p>
     <p><code>18  Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &amp;ling, sizeof(ling));</code></p>
     <p><code>19  Close(sockfd);</code></p>
     <empty-line/>
     <p><code>20  exit(0);</code></p>
     <p><code>21 }</code></p>
     <subtitle>Установка параметра сокета SO_LINGER</subtitle>
     <p><code>16-19</code> Как только соединение устанавливается, мы задаем параметр сокета <code>SO_LINGER</code>, устанавливая флаг <code>l_onoff</code> в единицу и обнуляя время <code>l_linger</code>. Как утверждалось в разделе 7.5, это вызывает отправку RST на сокете TCP при закрытии соединения. Затем с помощью функции <code>close</code> мы закрываем сокет.</p>
     <p>Потом мы изменяем наш сервер TCP, приведенный в листингах 6.3 и 6.4, с тем чтобы после сообщения функции <code>select</code> о готовности прослушиваемого сокета для чтения, но перед вызовом функции <code>accept</code> наступала пауза. В следующем коде, взятом из начала листинга 6.4, две добавленные строки помечены знаком <code>+</code>.</p>
     <p><code>  if (FD_ISSET(listenfd, &amp;rset)) { /* новое соединение */</code></p>
     <p><code>+  printf("listening socket readable\n");</code></p>
     <p><code>+  sleep(5);</code></p>
     <p><code>   clilen = sizeof(cliaddr);</code></p>
     <p><code>   connfd = Accept(listenfd, (SA*)&amp;cliaddr, &amp;clilen);</code></p>
     <p>Здесь мы имитируем занятый сервер, который не может вызвать функцию <code>accept</code> сразу же, как только функция <code>select</code> сообщит, что прослушиваемый сокет готов для чтения. Обычно подобное замедление со стороны сервера не вызывает проблем (на самом деле именно для этих ситуаций предусмотрена очередь полностью установленных соединений). Но поскольку после установления соединения от клиента прибыл сегмент RST, у нас возникает проблема.</p>
     <p>В разделе 5.11 мы отмечали, что когда клиент разрывает соединение до того, как сервер вызывает функцию <code>accept</code>, в Беркли-реализациях прерванное соединение не возвращается серверу, в то время как другие реализации должны возвращать ошибку <code>ECONNABORTED</code>, но часто вместо нее возвращают ошибку <code>EPROTO</code>. Рассмотрим Беркли-реализацию.</p>
     <p>&#9632; Клиент устанавливает соединение и затем прерывает его, как показано в листинге 16.14.</p>
     <p>&#9632; Функция <code>select</code> сообщает процессу сервера, что дескриптор готов для чтения, но у сервера вызов функции <code>accept</code> занимает некоторое, хотя и непродолжительное, время.</p>
     <p>&#9632; После того, как сервер получил сообщение от функции <code>select</code>, и прежде, чем была вызвана функция <code>accept</code>, прибыл сегмент RST от клиента.</p>
     <p>&#9632; Установленное соединение удаляется из очереди, и мы предполагаем, что не существует никаких других установленных соединений.</p>
     <p>&#9632; Сервер вызывает функцию <code>accept</code>, но поскольку установленных соединений нет, он оказывается заблокирован.</p>
     <p>Сервер останется блокированным в вызове функции <code>accept</code> до тех пор, пока какой-нибудь другой клиент не установит с ним соединение. Но если сервер аналогичен показанному в листинге 6.4, в это время он заблокирован в вызове функции <code>accept</code> и не может обрабатывать никакие другие готовые дескрипторы.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Проблема в некоторой степени аналогична проблеме, называемой атакой типа «отказ в обслуживании», описанной в разделе 6.8. Однако в данном случае сервер выходит из состояния блокировки, как только другой клиент установит соединение.</p>
     </cite>
     <p>Чтобы решить эту проблему, нужно соблюдать два следующих правила:</p>
     <p>1. Всегда делать прослушиваемый сокет неблокируемым, если мы используем функцию <code>select</code> для определения того, готово ли соединение к обработке функцией accept.</p>
     <p>2. Игнорировать следующие ошибки, возникающие при повторном вызове функции <code>accept</code>: <code>EWOULDBLOCK</code> (для Беркли-реализаций, когда клиент разрывает соединение), <code>ECONNABORTED</code> (для реализаций POSIX, когда клиент разрывает соединение), <code>EPROTO</code> (для реализаций SVR4, когда клиент разрывает соединение) и <code>EINTR</code> (если перехватываются сигналы).</p>
    </section>
    <section>
     <title>
      <p>16.7. Резюме</p>
     </title>
     <p>В примере неблокируемого чтения и записи в разделе 16.2 использовался наш клиент <code>str_cli</code>, который мы изменили для применения неблокируемого ввода-вывода на соединении TCP с сервером. Функция <code>select</code> обычно используется с неблокируемым вводом-выводом для определения того момента, когда дескриптор станет готов для чтения или записи. Эта версия нашего клиента является самой быстродействующей из всех показанных версией, хотя требует нетривиального изменения кода. Затем мы показали, что проще разделить процесс клиента на две части при помощи функции <code>fork</code>. Мы используем ту же технологию при создании потоков в листинге 26.1.</p>
     <p>Неблокируемая функция <code>connect</code> позволяет нам во время трехэтапного рукопожатия TCP выполнять другие задачи вместо блокирования в вызове функции <code>connect</code>. К сожалению, с этими функциями также связана проблема совместимости, так как различные реализации по-разному указывают, успешно ли установлено соединение или произошла ошибка. Мы использовали неблокируемые соединения для создания нового клиента, аналогичного веб-клиенту, открывающему одновременно множество соединений TCP для уменьшения затрат времени при получении нескольких файлов от сервера. Подобное инициирование множества соединений может сократить временные затраты, но также является «недружественным по отношению к сети», поскольку не позволяет воспользоваться алгоритмом TCP, предназначенным для предотвращения перегрузки (congestion avoidance).</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Обсуждая листинг 16.6, мы отметили, что родительский процесс должен вызвать функцию <code>shutdown</code>, а не функцию <code>close</code>. Почему?</p>
     <p>2. Что произойдет в листинге 16.6, если процесс сервера завершится преждевременно и дочерний процесс получит признак конца файла, но не уведомит об этом родительский процесс?</p>
     <p>3. Что произойдет в листинге 16.6, если родительский процесс непредвиденно завершится до завершения дочернего процесса, и дочерний процесс затем считает конец файла на сокете?</p>
     <p>4. Что произойдет в листинге 16.7, если мы удалим следующие две строки:</p>
     <p><code>if (n == 0)</code></p>
     <p><code> goto done; /* функция connect завершилась немедленно */</code></p>
     <p>5. В разделе 16.3 мы сказали, что возможна ситуация, когда данные для сокета придут раньше, чем завершится функция <code>connect</code>. Когда это может случиться?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 17</p>
     <p>Операции функции ioctl</p>
    </title>
    <section>
     <title>
      <p>17.1. Введение</p>
     </title>
     <p>Функция <code>ioctl</code> традиционно являлась системным интерфейсом, используемым для всего, что не входило в какую-либо другую четко определенную категорию. POSIX постепенно избавляется от функции <code>ioctl</code>, создавая заменяющие ее функции-обертки и стандартизуя их функциональность. Например, доступ к интерфейсу терминала Unix традиционно осуществлялся с помощью функции <code>ioctl</code>, но в POSIX были созданы 12 новых функций для терминалов: <code>tcgetattr</code> для получения атрибутов терминала, <code>tcflush</code> для опустошения буферов ввода или вывода, и т.д. Аналогичным образом POSIX заменяет одну сетевую функцию <code>ioctl</code>: новая функция <code>sockatmark</code> (см. раздел 24.3) заменяет команду <code>SIOCATMARK ioctl</code>. Тем не менее прочие сетевые команды <code>ioctl</code> остаются не стандартизованными и могут использоваться, например, для получения информации об интерфейсе и обращения к таблице маршрутизации и кэшу ARP (Address Resolution Protocol — протокол разрешения адресов).</p>
     <p>В этой главе представлен обзор команд функции <code>ioctl</code>, имеющих отношение к сетевому программированию, многие из которых зависят от реализации. Кроме того, некоторые реализации, включая системы, происходящие от 4.4BSD и Solaris 2.6, используют сокеты домена <code>AF_ROUTE</code> (маршрутизирующие сокеты) для выполнения многих из этих операций. Маршрутизирующие сокеты мы рассматриваем в главе 18.</p>
     <p>Обычно сетевые программы (как правило, серверы) используют функцию <code>ioctl</code> для получения информации обо всех интерфейсах узла при запуске программы, с тем чтобы узнать адрес интерфейса, выяснить, поддерживает ли интерфейс широковещательную передачу, многоадресную передачу и т.д. Для возвращения этой информации мы разработали нашу собственную функцию. В этой главе мы представляем ее реализацию с применением функции <code>ioctl</code>, а в главе 18 — другую реализацию, использующую маршрутизирующие сокеты.</p>
    </section>
    <section>
     <title>
      <p>17.2. Функция ioctl</p>
     </title>
     <p>Эта функция работает с открытым файлом, дескриптор которого передается через аргумент <code>fd</code>.</p>
     <p><code>#include &lt;unistd.h&gt;</code></p>
     <empty-line/>
     <p><code>int ioctl(int <emphasis>fd</emphasis>, int <emphasis>request</emphasis>, ... /* void *<emphasis>arg</emphasis> */ );</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Третий аргумент всегда является указателем, но тип указателя зависит от аргумента <code>request</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В 4.4BSD второй аргумент имеет тип unsigned long вместо int, но это не вызывает проблем, поскольку в заголовочных файлах определены константы, используемые для данного аргумента. Пока прототип функции подключен к программе, система будет обеспечивать правильную типизацию.</p>
      <p>Некоторые реализации определяют третий аргумент как неопределенный указатель (void*), а не так, как он определен в ANSI С.</p>
      <p>Не существует единого стандарта заголовочного файла, определяющего прототип функции для ioctl, поскольку он не стандартизован в POSIX. Многие системы определяют этот прототип в файле &lt;unistd.h&gt;, как это показываем мы, но традиционные системы BSD определяют его в заголовочном файле &lt;sys/ioctl.h&gt;.</p>
     </cite>
     <p>Мы можем разделить аргументы <code>request</code>, имеющие отношение к сети, на шесть категорий:</p>
     <p>&#9632; операции с сокетами;</p>
     <p>&#9632; операции с файлами;</p>
     <p>&#9632; операции с интерфейсами;</p>
     <p>&#9632; операции с кэшем ARP;</p>
     <p>&#9632; операции с таблицей маршрутизации;</p>
     <p>&#9632; операции с потоками (см. главу 31).</p>
     <p>Помимо того, что, как показывает табл. 7.9, некоторые операции <code>ioctl</code> перекрывают часть операций <code>fcntl</code> (например, установка неблокируемого сокета), существуют также некоторые операции, которые с помощью функции <code>ioctl</code> можно задать более чем одним способом (например, смена групповой принадлежности сокета).</p>
     <p>В табл. 17.1 перечислены аргументы request вместе с типами данных, на которые должен указывать адрес <code>arg</code>. В последующих разделах эти вызовы рассматриваются более подробно.</p>
     <empty-line/>
     <p><strong>Таблица 17.1</strong>. Обзор сетевых вызовов ioctl</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Категория</th>
       <th align="left" valign="top">request</th>
       <th align="left" valign="top">Описание</th>
       <th align="left" valign="top">Тип данных</th>
      </tr>
      <tr align="left">
       <td rowspan="3" align="left" valign="top">Сокет</td>
       <td align="left" valign="top">SIOCATMARK</td>
       <td align="left" valign="top">Находится ли указатель чтения сокета на отметке внеполосных данных</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCSPGRP</td>
       <td align="left" valign="top">Установка идентификатора процесса или идентификатора группы процессов для сокета</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCGPGRP</td>
       <td align="left" valign="top">Получение идентификатора процесса или идентификатора группы процессов для сокета</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td rowspan="5" align="left" valign="top">Файл</td>
       <td align="left" valign="top">FIONBIO</td>
       <td align="left" valign="top">Установка/сброс флага отсутствия блокировки</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">FIOASYNC</td>
       <td align="left" valign="top">Установка/сброс флага асинхронного ввода-вывода</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">FIONREAD</td>
       <td align="left" valign="top">Получение количества байтов в приемном буфере</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">FIOSETOWN</td>
       <td align="left" valign="top">Установка идентификатора процесса или идентификатора группы процессов для файла</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">FIOGETOWN</td>
       <td align="left" valign="top">Получение идентификатора процесса или идентификатора группы процессов для файла</td>
       <td align="left" valign="top">int</td>
      </tr>
      <tr align="left">
       <td rowspan="14" align="left" valign="top">Интерфейс</td>
       <td align="left" valign="top">SIOCGIFCONF</td>
       <td align="left" valign="top">Получение списка всех интерфейсов</td>
       <td align="left" valign="top">struct ifconf</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCSIFADDR</td>
       <td align="left" valign="top">Установка адреса интерфейса</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCGIFADDR</td>
       <td align="left" valign="top">Получение адреса интерфейса</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCSIFFLAGS</td>
       <td align="left" valign="top">Установка флагов интерфейса</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCGIFFLAGS</td>
       <td align="left" valign="top">Получение флагов интерфейса</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCSIFDSTADDR</td>
       <td align="left" valign="top">Установка адреса типа «точка-точка»</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCGIFDSTADDR</td>
       <td align="left" valign="top">Получение адреса типа «точка-точка»</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCGIFBRDADDR</td>
       <td align="left" valign="top">Получение широковещательного адреса</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCSIFBRDADDR</td>
       <td align="left" valign="top">Установка широковещательного адреса</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCGIFNETMASK</td>
       <td align="left" valign="top">Получение маски подсети</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCSIFNETMASK</td>
       <td align="left" valign="top">Установка маски подсети</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCGIFMETRIC</td>
       <td align="left" valign="top">Получение метрики интерфейса</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCSIFMETRIC</td>
       <td align="left" valign="top">Установка метрики интерфейса</td>
       <td align="left" valign="top">struct ifreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOC<emphasis>xxx</emphasis></td>
       <td align="left" valign="top">(Множество вариантов в зависимости от реализации)</td>
       <td align="left" valign="top"/>
      </tr>
      <tr align="left">
       <td rowspan="3" align="left" valign="top">ARP</td>
       <td align="left" valign="top">SIOCSARP</td>
       <td align="left" valign="top">Создание/модификация элемента ARP</td>
       <td align="left" valign="top">struct arpreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCGARP</td>
       <td align="left" valign="top">Получение элемента ARP</td>
       <td align="left" valign="top">struct arpreq</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCDARP</td>
       <td align="left" valign="top">Удаление элемента ARP</td>
       <td align="left" valign="top">struct arpreq</td>
      </tr>
      <tr align="left">
       <td rowspan="2" align="left" valign="top">Маршрутизация</td>
       <td align="left" valign="top">SIOCADDRT</td>
       <td align="left" valign="top">Добавление маршрута</td>
       <td align="left" valign="top">struct rtentry</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SIOCDELRT</td>
       <td align="left" valign="top">Удаление маршрута</td>
       <td align="left" valign="top">struct rtentry</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Потоки</td>
       <td align="left" valign="top">I_<emphasis>xxx</emphasis></td>
       <td align="left" valign="top">(См. раздел 31.5)</td>
       <td align="left" valign="top"/>
      </tr>
     </table>
    </section>
    <section>
     <title>
      <p>17.3. Операции с сокетами</p>
     </title>
     <p>Существует три типа вызова, или запроса (в зависимости от значения аргумента <code>request</code>) функции <code>ioctl</code>, предназначенные специально для сокетов [128, с. 551–553]. Все они требуют, чтобы третий аргумент функции <code>ioctl</code> был указателем на целое число.</p>
     <p>&#9632; <code>SIOCATMARK</code>. Возвращает указатель на ненулевое значение в качестве третьего аргумента (его тип, как только что было сказано, — указатель на целое число), если указатель чтения сокета в настоящий момент находится на отметке внеполосных данных (out-of-band mark), или указатель на нулевое значение, если указатель чтения сокета не находится на этой отметке. Более подробно внеполосные данные (out-of-band data) рассматриваются в главе 24. POSIX заменяет этот вызов функцией <code>sockatmark</code>, и мы рассматриваем реализацию этой новой функции с использованием функции <code>ioctl</code> в разделе 24.3.</p>
     <p>&#9632; <code>SIOCGRP</code>. Возвращает в качестве третьего аргумента указатель на целое число — идентификатор процесса или группы процессов, которым будут посылаться сигналы <code>SIGIO</code> или <code>SIGURG</code> по окончании выполнения асинхронной операции или при появлении срочных данных. Этот вызов идентичен вызову <code>F_GETOWN</code> функции <code>fcntl</code>, и в табл. 7.9 мы отмечали, что POSIX стандартизирует функцию <code>fcntl</code>.</p>
     <p>&#9632; <code>SIOCSPGRP</code>. Задает идентификатор процесса или группы процессов для отсылки им сигналов <code>SIGIO</code> или <code>SIGURG</code> как целое число, на которое указывает третий аргумент. Этот вызов идентичен вызову <code>F_SETOWN</code> функции <code>fcntl</code>, и в табл. 7.9 мы отмечали, что POSIX стандартизирует функцию <code>fcntl</code>.</p>
    </section>
    <section>
     <title>
      <p>17.4. Операции с файлами</p>
     </title>
     <p>Следующая группа вызовов начинается с <code>FIO</code> и может применяться к определенным типам файлов в дополнение к сокетам. Мы рассматриваем только вызовы, применимые к сокетам [128, с. 553].</p>
     <p>Следующие пять вызовов требуют, чтобы третий аргумент функции <code>ioctl</code> указывал на целое число.</p>
     <p>&#9632; <code>FIONBIO</code>. Флаг отключения блокировки при выполнении операций ввода-вывода сбрасывается или устанавливается в зависимости от третьего аргумента функции <code>ioctl</code>. Если этот аргумент является пустым указателем, то флаг сбрасывается (блокировка разрешена). Если же третий аргумент является указателем на единицу, то включается неблокируемый ввод-вывод. Этот вызов обладает тем же действием, что и команда <code>F_SETFL</code> функции <code>fcntl</code>, которая позволяет установить или сбросить флаг <code>O_NONBLOCK</code>, задающий статус файла.</p>
     <p>&#9632; <code>FIOASYNC</code>. Флаг, управляющий получением сигналов асинхронного ввода-вывода (<code>SIGIO</code>), устанавливается или сбрасывается для сокета в зависимости от того, является ли третий аргумент функции <code>ioctl</code> пустым указателем. Этот флаг имеет то же действие, что и флаг статуса файла <code>O_ASYNC</code>, который можно установить и сбросить с помощью команды <code>F_SETFL</code> функции <code>ioctl</code>.</p>
     <p>&#9632; <code>FIONREAD</code>. Возвращает число байтов, в настоящий момент находящихся в приемном буфере сокета, как целое число, на которое указывает третий аргумент функции <code>ioctl</code>. Это свойство работает также для файлов, каналов и терминалов. Более подробно об этом вызове мы рассказывали в разделе 14.7.</p>
     <p>&#9632; <code>FIOSETOWN</code>. Эквивалент <code>SIOCSPGRP</code> для сокета.</p>
     <p>&#9632; <code>FIOGETOWN</code>. Эквивалент <code>SIOCGPGRP</code> для сокета.</p>
    </section>
    <section>
     <title>
      <p>17.5. Конфигурация интерфейса</p>
     </title>
     <p>Один из шагов, выполняемых многими программами, работающими с сетевыми интерфейсами системы, — это получение от ядра списка всех интерфейсов, сконфигурированных в системе. Это делается с помощью вызова <code>SIOCGIFCONF</code>, использующего структуру <code>ifconf</code>, которая, в свою очередь, использует структуру <code>ifreq</code>. Обе эти структуры показаны в листинге 17.1<a l:href="#n1" type="note">[1]</a>.</p>
     <p><strong>Листинг 17.1</strong>. Структуры ifconf и ifreq, используемые в различных вызовах функции ioctl, относящихся к интерфейсам</p>
     <p><code>//&lt;net/if.h&gt; struct ifconf {</code></p>
     <p><code> int ifc_len; /* размер буфера, "значение-результат" */</code></p>
     <p><code> union {</code></p>
     <p><code>  caddr_t      ifcu_buf;  /* ввод от пользователя к ядру */</code></p>
     <p><code>  struct ifreq *ifcu_req; /* ядро возвращает пользователю */</code></p>
     <p><code> } ifc_ifcu;</code></p>
     <p><code>};</code></p>
     <p><code>#define ifc_buf ifc_ifcu.ifcu_buf /* адрес буфера */</code></p>
     <p><code>#define ifc_req ifc_ifcu.ifcu_req /* массив возвращенных структур */</code></p>
     <empty-line/>
     <p><code>#define IFNAMSIZ 16</code></p>
     <empty-line/>
     <p><code>struct ifreq {</code></p>
     <p><code> char ifr_name[IFNAMSIZ]; /* имя интерфейса, например "le0" */</code></p>
     <p><code> union {</code></p>
     <p><code>  struct sockaddr ifru_addr;</code></p>
     <p><code>  struct sockaddr ifru_dstaddr;</code></p>
     <p><code>  struct sockaddr ifru_broadaddr;</code></p>
     <p><code>  short           ifru_flags;</code></p>
     <p><code>  int             ifru_metric;</code></p>
     <p><code>  caddr_t         ifru_data;</code></p>
     <p><code> } ifr_ifru;</code></p>
     <p><code>};</code></p>
     <p><code>#define ifr_addr ifr_ifru.ifru_addr       /* адрес */</code></p>
     <p><code>#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* другой конец линии передачи, называемой</code></p>
     <p><code>                                             "точка-точка" */</code></p>
     <p><code>#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* широковещательный адрес */</code></p>
     <p><code>#define ifr_flags ifr_ifru.ifru_flags     /* флаги */</code></p>
     <p><code>#define ifr_metric ifr_ifru.ifru_metric   /* метрика */</code></p>
     <p><code>#define ifr_data ifr_ifru.ifru_data       /* с использованием интерфейсом */</code></p>
     <p>Прежде чем вызвать функцию <code>ioctl</code>, мы выделяем в памяти место для буфера и для структуры <code>ifconf</code>, а затем инициализируем эту структуру. Мы показываем это на рис. 17.1, предполагая, что наш буфер имеет размер 1024 байта. Третий аргумент функции <code>ioctl</code> — это указатель на нашу структуру <code>ifconf</code>.</p>
     <image l:href="#img_108.png"/>
     <p><strong>Рис. 17.1</strong>. Инициализация структуры ifconf перед вызовом SIOCGIFCONF</p>
     <p>Если мы предположим, что ядро возвращает две структуры <code>ifreq</code>, то при завершении функции <code>ioctl</code> мы можем получить ситуацию, представленную на рис. 17.2. Затененные области были изменены функцией <code>ioctl</code>. Буфер заполняется двумя структурами, и элемент <code>ifc_len</code> структуры <code>ifconf</code> обновляется, с тем чтобы соответствовать количеству информации, хранимой в буфере. Предполагается, что на этом рисунке каждая структура <code>ifreq</code> занимает 32 байта.</p>
     <image l:href="#img_109.png"/>
     <p><strong>Рис. 17.2</strong>. Значения, возвращаемые в результате вызова SIOCGIFCONF</p>
     <p>Указатель на структуру <code>ifreq</code> также используется в качестве аргумента оставшихся функций <code>ioctl</code> интерфейса, показанных в табл. 17.1, которые мы описываем в разделе 17.7. Отметим, что каждая структура <code>ifreq</code> содержит объединение (<code>union</code>), а директивы компилятора <code>#define</code> позволяют непосредственно обращаться к полям объединения по их именам. Помните о том, что в некоторых системах в объединение <code>ifr_ifru</code> добавлено много зависящих от реализации элементов.</p>
    </section>
    <section>
     <title>
      <p>17.6. Функция get_ifi_info</p>
     </title>
     <p>Поскольку многим программам нужно знать обо всех интерфейсах системы, мы разработаем нашу собственную функцию <code>get_ifi_info</code>, возвращающую связный список структур — по одной для каждого активного в настоящий момент интерфейса. В этом разделе мы покажем, как эта функция реализуется с помощью вызова <code>SIOCGIFCONF</code> функции <code>ioctl</code>, а в главе 18 мы создадим ее другую версию, использующую маршрутизирующие сокеты.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>BSD/OS предоставляет функцию getifaddrs, имеющую аналогичную функциональность.</p>
      <p>Поиск по всему дереву исходного кода BSD/OS 2.1 показывает, что 12 программ выполняют вызов SIOCGIFCONF функции ioctl для определения присутствующих интерфейсов.</p>
     </cite>
     <p>Сначала мы определяем структуру <code>ifi_info</code> в новом заголовочном файле, который называется <code>unpifi.h</code>, показанном в листинге 17.2.</p>
     <p><strong>Листинг 17.2</strong>. Заголовочный файл unpifi.h</p>
     <p><code>//ioctl/unpifi.h</code></p>
     <p><code> 1 /* Наш собственный заголовочный файл для программ, которым требуется</code></p>
     <p><code> 2 информация о конфигурации интерфейса. Включаем его вместо "unp.h". */</code></p>
     <empty-line/>
     <p><code> 3 #ifndef __unp_ifi_h</code></p>
     <p><code> 4 #define __unp_ifi_h</code></p>
     <empty-line/>
     <p><code> 5 #include "unp.h"</code></p>
     <p><code> 6 #include &lt;net/if.h&gt;</code></p>
     <empty-line/>
     <p><code> 7 #define IFI_NAME 16 /* то же, что и IFNAMSIZ в заголовке &lt;net/if.h&gt; */</code></p>
     <p><code> 8 #define IFI_HADDR 8 /* с учетом 64-битового интерфейса EUI-64 в будущем */</code></p>
     <empty-line/>
     <p><code> 9 struct ifi_info {</code></p>
     <p><code>10  char ifi_name[IFI_NAME];     /* имя интерфейса, заканчивается</code></p>
     <p><code>                                    символом конца строки */</code></p>
     <p><code>11  short ifi_index;             /* индекс интерфейса */</code></p>
     <p><code>12  short ifi_mtu;               /* MTU для интерфейса */</code></p>
     <p><code>13  u_char ifi_haddr[IFI_HADDR]; /* аппаратный адрес */</code></p>
     <p><code>14  u_short ifi_hlen; /* количество байтов в аппаратном адресе: 0, 6, 8 */</code></p>
     <p><code>15  short ifi_flags;  /* константы IFF_xxx из &lt;net/if.h&gt; */</code></p>
     <p><code>16  short if_myflags; /* наши флаги IFI_xxx */</code></p>
     <p><code>17  struct sockaddr *ifi_addr;    /* первичный адрес */</code></p>
     <p><code>18  struct sockaddr *ifi_brdaddr; /* широковещательный адрес */</code></p>
     <p><code>19  struct sockaddr *ifi_dstaddr; /* адрес получателя */</code></p>
     <p><code>20 s truct ifi_info *ifi_next;    /* следующая из этих структур */</code></p>
     <p><code>21 };</code></p>
     <empty-line/>
     <p><code>22 #define IFI_ALIAS 1 /* ifi_addr - это псевдоним */</code></p>
     <empty-line/>
     <p><code>23 /* прототипы функций */</code></p>
     <p><code>24 struct ifi_info *get_ifi_info((int, int);</code></p>
     <p><code>25 struct ifi_info *Get_ifi_info(int, int);</code></p>
     <p><code>26 void free_ifi_info(struct ifi_info*);</code></p>
     <empty-line/>
     <p><code>27 #endif /* _unp_ifi_h */</code></p>
     <p><code>9-21</code> Связный список этих структур возвращается нашей функцией. Элемент <code>ifi_next</code> каждой структуры указывает на следующую структуру. Мы возвращаем в этой структуре информацию, которая может быть востребована в типичном приложении: имя интерфейса, индекс интерфейса, MTU, аппаратный адрес (например, адрес Ethernet), флаги интерфейса (чтобы позволить приложению определить, поддерживает ли приложение широковещательную или многоадресную передачу и относится ли этот интерфейс к типу «точка-точка»), адрес интерфейса, широковещательный адрес, адрес получателя для связи «точка-точка». Вся память, используемая для хранения структур <code>ifi_info</code> вместе со структурами адреса сокета, содержащимися в них, выделяется динамически. Следовательно, мы также предоставляем функцию <code>free_ifi_info</code> для освобождения всей этой памяти.</p>
     <p>Перед тем как представить реализацию нашей функции <code>ifi_info</code>, мы покажем простую программу, которая вызывает эту функцию и затем выводит информацию. Эта программа, представленная в листинге 17.3, является уменьшенной версией программы <code>ifconfig</code>.</p>
     <p><strong>Листинг 17.3</strong>. Программа prifinfo, вызывающая нашу функцию ifi_info</p>
     <p><code>//ioctl/prifinfo.c</code></p>
     <p><code> 1 #include "unpifi.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  struct ifi_info *ifi, *ifihead;</code></p>
     <p><code> 6  struct sockaddr *sa;</code></p>
     <p><code> 7  u_char *ptr;</code></p>
     <p><code> 8  int i, family, doaliases;</code></p>
     <empty-line/>
     <p><code> 9  if (argc != 3)</code></p>
     <p><code>10   err_quit("usage: prifinfo &lt;inet4|inet6&gt; &lt;doaliases&gt;");</code></p>
     <empty-line/>
     <p><code>11  if (strcmp(argv[1], "inet4") == 0)</code></p>
     <p><code>12   family = AF_INET;</code></p>
     <p><code>13  else if (strcmp(argv[1], "inet6") == 0)</code></p>
     <p><code>14   family = AF_INET6;</code></p>
     <p><code>15  else</code></p>
     <p><code>16   err_quit("invalid &lt;address-family&gt;");</code></p>
     <p><code>17  doaliases = atoi(argv[2]);</code></p>
     <empty-line/>
     <p><code>18  for (ifihead = ifi = Get_ifi_info(family, doaliases);</code></p>
     <p><code>19  ifi ! = NULL; ifi = ifi-&gt;ifi_next) {</code></p>
     <p><code>20   printf("%s: &lt;", ifi-&gt;ifi_name);</code></p>
     <p><code>21   if (ifi-&gt;ifi_index != 0)</code></p>
     <p><code>22    printf("%d) ", ifi-&gt;ifi_index);</code></p>
     <p><code>23   printf("&lt;");</code></p>
     <p><code>24   if (ifi-&gt;ifi_flags &amp; IFF_UP) printf ("UP ");</code></p>
     <p><code>25   if (ifi-&gt;ifi_flags &amp; IFF_BROADCAST) printf("BCAST ");</code></p>
     <p><code>26   if (ifi-&gt;ifi_flags &amp; IFF_MULTICAST) printf("MCAST ");</code></p>
     <p><code>27   if (ifi-&gt;ifi_flags &amp; IFF_LOOPBACK) printf("LOOP ");</code></p>
     <p><code>28   if (ifi-&gt;ifi_flags &amp; IFF_POINTOPOINT) printf("P2P ");</code></p>
     <p><code>29   printf("&gt;\n");</code></p>
     <p><code>30   if ((i = ifi-&gt;ifi_hlen) &gt; 0) {</code></p>
     <p><code>31    ptr = ifi-&gt;ifi_haddr;</code></p>
     <p><code>32    do {</code></p>
     <p><code>33     printf("%s%x", (i == ifi-&gt;ifi_hlen) ? " " : ":", *ptr++);</code></p>
     <p><code>34    } while (--i &gt; 0);</code></p>
     <p><code>35    printf("\n");</code></p>
     <p><code>36   }</code></p>
     <p><code>37   if (ifi-&gt;ifi_mtu != 0)</code></p>
     <p><code>38    printf(" MTU: %d\n". ifi-&gt;ifi_mtu);</code></p>
     <p><code>39   if ((sa = ifi-&gt;ifi_addr) != NULL)</code></p>
     <p><code>40    printf(" IP addr: %s\n", Sock_ntop_host(sa, sizeof(*sa)));</code></p>
     <p><code>41   if ((sa = ifi-&gt;ifi_brdaddr) != NULL)</code></p>
     <p><code>42    printf(" broadcast addr, %s\n",</code></p>
     <p><code>43     Sock_ntop_host(sa, sizeof(*sa)));</code></p>
     <p><code>44   if ((sa = ifi-&gt;ifi_dstaddr) != NULL)</code></p>
     <p><code>45    printf(" destination addr %s\n\",</code></p>
     <p><code>46     Sock_ntop_host(sa, sizeof(*sa)));</code></p>
     <p><code>47   }</code></p>
     <p><code>48  free_ifi_info(ifihead);</code></p>
     <p><code>49  exit(0);</code></p>
     <p><code>59 }</code></p>
     <p><code>18-47</code> Программа представляет собой цикл <code>for</code>, в котором один раз вызывается функция <code>get_ifi_info</code>, а затем последовательно перебираются все возвращаемые структуры <code>ifi_info</code>.</p>
     <p><code>20-36</code> Выводятся все имена интерфейсов и флаги. Если длина аппаратного адреса больше нуля, он выводится в виде шестнадцатеричного числа (наша функция <code>get_ifi_info</code> возвращает нулевую длину <code>ifi_hlen</code>, если адрес недоступен).</p>
     <p><code>37-46</code> Выводится MTU и те IP-адреса, которые были возвращены.</p>
     <p>Если мы запустим эту программу на нашем узле <code>macosx</code> (см. рис. 1.7), то получим следующий результат:</p>
     <p><code>macosx % <strong>prifinfo inet4 0</strong></code></p>
     <p><code>lo0: &lt;UP MCAST LOOP &gt;</code></p>
     <p><code> MTU: 16384</code></p>
     <p><code> IP addr: 127.0.0.1</code></p>
     <p><code>en1: &lt;UP BCAST MCAST &gt;</code></p>
     <p><code> MTU: 1500</code></p>
     <p><code> IP addr: 172.24.37.78</code></p>
     <p><code> broadcast addr: 172.24.37.95</code></p>
     <p>Первый аргумент командной строки <code>inet4</code> задает адрес IPv4, а второй, нулевой аргумент указывает, что не должно возвращаться никаких псевдонимов, или альтернативных имен (альтернативные имена IP-адресов мы описываем в разделе А.4). Обратите внимание, что в MacOS X аппаратный адрес интерфейса Ethernet недоступен.</p>
     <p>Если мы добавим к интерфейсу Ethernet (<code>en1</code>) три альтернативных имени адреса с идентификаторами узла 79, 80 и 81 и изменим второй аргумент командной строки на 1, то получим:</p>
     <p><code>macosx % <strong>prifinfo inet4 1</strong></code></p>
     <p><code>lo0: &lt;UP MCAST LOOP &gt;</code></p>
     <p><code> MTU: 16384</code></p>
     <p><code> IP addr: 127.0.0.1</code></p>
     <p><code>en1: &lt;UP BCAST MCAST &gt;</code></p>
     <p><code> MTU: 1500</code></p>
     <p><code> IP addr: 172.24.37.78 <emphasis>первичный IP-адрес</emphasis></code></p>
     <p><code> broadcast addr: 172.24.37.95</code></p>
     <p><code>en1: &lt;UP BCAST MCAST &gt;</code></p>
     <p><code> MTU: 1500</code></p>
     <p><code> IP addr: 172.24.37.79 <emphasis>первый псевдоним</emphasis></code></p>
     <p><code> broadcast addr: 172.24.37.95</code></p>
     <p><code>en1: &lt;UP BCAST MCAST &gt;</code></p>
     <p><code> MTU: 1500</code></p>
     <p><code> IP addr: 172 24.37.80 <emphasis>второй псевдоним</emphasis></code></p>
     <p><code> broadcast addr: 172.24 37.95</code></p>
     <p><code>en1: &lt;UP BCAST MCAST &gt;</code></p>
     <p><code> MTU: 1500</code></p>
     <p><code> IP addr: 172 24.37.81 <emphasis>третий псевдоним</emphasis></code></p>
     <p><code> broadcast addr: 172.24.37 95</code></p>
     <p>Если мы запустим ту же программу под FreeBSD, используя реализацию функции <code>get_ifi_info</code>, приведенную в листинге 18.9 (которая может легко получить аппаратный адрес), то получим:</p>
     <p><code>freebsd4 % <strong>prifinfo inet4 1</strong></code></p>
     <p><code>de0: &lt;UP BCAST MCAST &gt;</code></p>
     <p><code> 0:80:c8:2b:d9:28</code></p>
     <p><code> IP addr: 135.197.17.100</code></p>
     <p><code> broadcast addr: 135.197.17.255</code></p>
     <p><code>de1: &lt;UP BCAST MCAST &gt;</code></p>
     <p><code> 0:40:5:42:d6:de</code></p>
     <p><code> IP addr: 172.24.37.94 <emphasis>основной IP-адрес</emphasis></code></p>
     <p><code> broadcast addr: 172.24.37.95</code></p>
     <p><code>ef0: &lt;UP BCAST MCAST &gt;</code></p>
     <p><code> 0:40:5:42:d6:de</code></p>
     <p><code> IP addr: 172.24.37.93 <emphasis>псевдоним</emphasis></code></p>
     <p><code> broadcast addr: 172.24.37.93</code></p>
     <p><code>lo0: &lt;UP MCAST LOOP &gt;</code></p>
     <p><code> IP addr: 127.0.0.1</code></p>
     <p>В этом примере мы указали программе выводить псевдонимы, и мы видим, что один из псевдонимов определен для второго интерфейса Ethernet (<code>de1</code>) с идентификатором узла 93.</p>
     <p>Теперь мы покажем нашу реализацию функции <code>get_ifi_info</code>, использующую вызов <code>SIOCGIFCONF</code> функции <code>ioctl</code>. В листинге 17.4 показана первая часть этой функции, получающая от ядра конфигурацию интерфейса.</p>
     <p><strong>Листинг 17.4</strong>. Выполнение вызова SIOCGIFCONF для получения конфигурации интерфейса</p>
     <p><code>//lib/get_if_info.c</code></p>
     <p><code> 1 #include "unpifi.h"</code></p>
     <empty-line/>
     <p><code> 2 struct ifi_info*</code></p>
     <p><code> 3 get_ifi_info(int family, int doaliases)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  struct ifi_info *ifi, *ifihead, **ifipnext;</code></p>
     <p><code> 6  int sockfd, len, lastlen, flags, myflags, idx = 0, hlen = 0;</code></p>
     <p><code> 7  char *ptr, *buf, lastname[IFNAMSIZ], *cptr, *haddr, *sdlname;</code></p>
     <p><code> 8  struct ifconf ifc;</code></p>
     <p><code> 9  struct ifreq *ifr, ifrcopy;</code></p>
     <p><code>10  struct sockaddr_in *sinptr;</code></p>
     <p><code>11  struct sockaddr_in6 *sin6ptr;</code></p>
     <empty-line/>
     <p><code>12  sockfd = Socket(AF_INET, SOCK_DGRAM, 0);</code></p>
     <empty-line/>
     <p><code>13  lastlen = 0;</code></p>
     <p><code>14  len = 100 * sizeof(struct ifreq); /* начальное приближение к нужному размеру буфера */</code></p>
     <p><code>15  for (;;) {</code></p>
     <p><code>16   buf = Mallос(len);</code></p>
     <p><code>17   ifc.ifc_len = len;</code></p>
     <p><code>18   ifc.ifc_buf = buf;</code></p>
     <p><code>19   if (ioctl(sockfd, SIOCGIFCONF, &amp;ifc) &lt; 0) {</code></p>
     <p><code>20    if (errno != EINVAL || lastlen != 0)</code></p>
     <p><code>21     err_sys("ioctl error");</code></p>
     <p><code>22   } else {</code></p>
     <p><code>23    if (ifc.ifc_len == lastlen)</code></p>
     <p><code>24    break; /* успех, значение len не изменилось */</code></p>
     <p><code>25    lastlen = ifc.ifc_len;</code></p>
     <p><code>26   }</code></p>
     <p><code>27   len += 10 * sizeof(struct ifreq); /* приращение */</code></p>
     <p><code>28   free(buf);</code></p>
     <p><code>29  }</code></p>
     <p><code>30  ifihead = NULL;</code></p>
     <p><code>31  ifipnext = &amp;ifihead;</code></p>
     <p><code>32  lastname[0] = 0;</code></p>
     <p><code>33  sdlname = NULL;</code></p>
     <subtitle>Создание сокета Интернета</subtitle>
     <p><code>11</code> Мы создаем сокет UDP, который будет использоваться с функциями <code>ioctl</code>. Может применяться как сокет TCP, так и сокет UDP [128, с. 163].</p>
     <subtitle>Выполнение вызова SIOCGIFCONF в цикле</subtitle>
     <p><code>12-28</code> Фундаментальной проблемой, связанной с вызовом <code>SIOCGIFCONF</code>, является то, что некоторые реализации не возвращают ошибку, если буфер слишком мал для хранения полученного результата [128, с. 118–119]. В этом случае результат просто обрезается так, чтобы поместиться в буфер, и функция <code>ioctl</code> возвращает нулевое значение, что соответствует успешному выполнению. Это означает, что единственный способ узнать, достаточно ли велик наш буфер, — сделать вызов, сохранить возвращенную длину, снова сделать вызов с большим размером буфера и сравнить полученную длину со значением, сохраненным из предыдущего вызова. Только если эти две длины одинаковы, наш буфер можно считать достаточно большим.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Беркли-реализации не возвращают ошибку, если буфер слишком мал [128, с. 118-199], и результат просто обрезается так, чтобы поместиться в существующий буфер. Solaris 2.5 возвращает ошибку EINVAL, если возвращаемая длина больше или равна длине буфера. Но мы не можем считать вызов успешным, если возвращаемая длина меньше размера буфера, поскольку Беркли-реализации могут возвращать значение, меньшее размера буфера, если часть структуры в него не помещается.</p>
      <p>В некоторых реализациях предоставляется вызов SIOCGIFNUM, который возвращает число интерфейсов. Это позволяет приложению перед выполнением вызова SIOCGIFCONF выделить в памяти место для буфера достаточного размера, но такой подход не является широко распространенным.</p>
      <p>Выделение в памяти места под буфер фиксированного размера для результата вызова SIOCGIFCONF стало проблемой с ростом Сети, поскольку большие веб-серверы используют много альтернативных адресов для одного интерфейса. Например, в Solaris 2.5 был предел в 256 альтернативных адресов для интерфейса, но в версии 2.6 этот предел вырос до 8192. Обнаружилось, что на сайтах с большим числом альтернативных адресов перестают работать программы с буферами фиксированного размера для размещения информации об интерфейсе. Хотя Solaris возвращает ошибку, если буфер слишком мал, эти программы размещают в памяти буфер фиксированного размера, запускают функцию ioctl, но затем перестают работать при возвращении ошибки.</p>
     </cite>
     <p><code>12-15</code> Мы динамически размещаем в памяти буфер начиная с размера, достаточного для 100 структур <code>ifreq</code>. Мы также отслеживаем длину, возвращаемую последним вызовом <code>SIOCGIFCONF</code> в <code>lastlen</code>, и инициализируем ее нулем.</p>
     <p><code>19-20</code> Если функция <code>ioctl</code> возвращает ошибку <code>EINVAL</code> и функция еще не возвращалась успешно (то есть <code>lastlen</code> все еще равно нулю), значит, мы еще не выделили буфер достаточного размера, поэтому мы продолжаем выполнять цикл.</p>
     <p><code>22-23</code> Если функция <code>ioctl</code> завершается успешно и возвращаемая длина равна <code>lastlen</code>, значит, длина не изменилась (наш буфер имеет достаточный размер), и мы с помощью функции <code>break</code> выходим из цикла, так как у нас имеется вся информация.</p>
     <p><code>26-27</code> В каждом проходе цикла мы увеличиваем размер буфера для хранения еще 10 структур <code>ifreq</code>.</p>
     <subtitle>Инициализация указателей связного списка</subtitle>
     <p><code>29-31</code> Поскольку мы будем возвращать указатель на начало связного списка структур <code>ifi_info</code>, мы используем две переменные <code>ifihead</code> и <code>ifipnext</code> для хранения указателей на список по мере его создания.</p>
     <p>Следующая часть нашей функции <code>get_ifi_info</code>, содержащая начало основного цикла, показана в листинге 17.5.</p>
     <p><strong>Листинг 17.5</strong>. Конфигурация интерфейса процесса</p>
     <p><code>//lib/get_ifi_info.c</code></p>
     <p><code>34 for (ptr = buf; ptr &lt; buf + ifc.ifc_len; ) {</code></p>
     <p><code>35  ifr = (struct ifreq*)ptr;</code></p>
     <empty-line/>
     <p><code>36 #ifdef HAVE_SOCKADDR_SA_LEN</code></p>
     <p><code>37  len = max(sizeof(struct sockaddr), ifr-&gt;ifr_addr.sa_len);</code></p>
     <p><code>38 #else</code></p>
     <p><code>39  switch (ifr-&gt;ifr_addr.sa_family) {</code></p>
     <p><code>40 #ifdef IPV6</code></p>
     <p><code>41  case AF_INET6:</code></p>
     <p><code>42   len = sizeof(struct sockaddr_in6);</code></p>
     <p><code>43   break;</code></p>
     <p><code>44 #endif</code></p>
     <p><code>45  case AF_INET:</code></p>
     <p><code>46  default:</code></p>
     <p><code>47   len = sizeof(struct sockaddr);</code></p>
     <p><code>48   break;</code></p>
     <p><code>49  }</code></p>
     <p><code>50 #endif /* HAVE_SOCKADDR_SA_LEN */</code></p>
     <p><code>51  ptr += sizeof(ifr-&gt;ifr_name) + len; /* для следующей строки */</code></p>
     <empty-line/>
     <p><code>52 #ifdef HAVE_SOCKADDR_DL_STRUCT</code></p>
     <p><code>53  /* предполагается, что AF_LINK идет перед AF_INET и AF_INET6 */</code></p>
     <p><code>54  if (ifr-&gt;ifr_addr.sa_family == AF_LINK) {</code></p>
     <p><code>55   struct sockaddr_dl *sdl = (struct sockaddr_dl*)&amp;ifr-&gt;ifr_addr;</code></p>
     <p><code>56   sdlname = ifr-&gt;ifr_name;</code></p>
     <p><code>57   idx = sdl-&gt;sdl_index;</code></p>
     <p><code>58   haddr = sdl-&gt;sdl_data + sdl-&gt;sdl_nlen;</code></p>
     <p><code>59   hlen = sdl-&gt;sdl_alen;</code></p>
     <p><code>60  }</code></p>
     <p><code>61 #endif</code></p>
     <empty-line/>
     <p><code>62  if (ifr-&gt;ifr_addr.sa_family != family)</code></p>
     <p><code>63   continue; /* игнорируется, если семейство адреса не то */</code></p>
     <p><code>64  myflags = 0;</code></p>
     <p><code>65  if ((cptr = strchr(ifr-&gt;ifr_name, ':')) != NULL)</code></p>
     <p><code>66   *cptr = 0; /* замена двоеточия нулем */</code></p>
     <p><code>67  if (strncmp(lastname, ifr-&gt;ifr_name, IFNAMSIZ) == 0) {</code></p>
     <p><code>68   if (doaliases == 0)</code></p>
     <p><code>69    continue; /* этот интерфейс уже обработан */</code></p>
     <p><code>70   myflags = IFI_ALIAS;</code></p>
     <p><code>71  }</code></p>
     <p><code>72  memcpy(lastname, ifr-&gt;ifr_name, IFNAMSIZ);</code></p>
     <empty-line/>
     <p><code>73  ifrcopy = *ifr;</code></p>
     <p><code>74  Ioctl(sockfd, SIOCGIFFLAGS, &amp;ifrcopy);</code></p>
     <p><code>75  flags = ifrcopy.ifr_flags;</code></p>
     <p><code>76  if ((flags &amp; IFF_UP) == 0)</code></p>
     <p><code>77   continue; /* игнорируется, если интерфейс не используется */</code></p>
     <subtitle>Переход к следующей структуре адреса сокета</subtitle>
     <p><code>35-51</code> При последовательном просмотре всех структур i<code>freq ifr</code> указывает на текущую структуру, а мы увеличиваем <code>ptr</code> на единицу, чтобы он указывал на следующую. Необходимо предусмотреть особенность более новых систем, предоставляющих поле длины для структур адреса сокета, и вместе с тем учесть, что более старые системы этого поля не предоставляют. Хотя в листинге 17.1 структура адреса сокета, содержащаяся в структуре <code>ifreq</code>, объявляется как общая структура адреса сокета, в новых системах она может относиться к произвольному типу. Действительно, в 4.4BSD структура адреса сокета канального уровня также возвращается для каждого интерфейса [128, с. 118]. Следовательно, если поддерживается элемент длины, то мы должны использовать его значение для переустановки нашего указателя на следующую структуру адреса сокета. В противном случае мы определяем длину, исходя из семейства адресов, используя размер общей структуры адреса сокета (16 байт) в качестве значения по умолчанию.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В системах, поддерживающих IPv6, не оговаривается, возвращается ли адрес IPv6 вызовом SIOCGIFCONF. Для более новых систем мы вводим оператор case, в котором предусмотрена возможность возвращения адресов IPv6. Проблема состоит в том, что объединение в структуре ifreq определяет возвращаемые адреса как общие 16-байтовые структуры sockaddr, подходящие для 16-байтовых структур sockaddr_in IPv4, но для 24-байтовых структур sockaddr_in6 IPv6 они слишком малы. В случае возвращения адресов IPv6 возможно некорректное поведение существующего кода, созданного в предположении, что в каждой структуре ifreq содержится структура sockaddr фиксированного размера. В системах, где структура sockaddr имеет поле sa_len, никаких проблем не возникает, потому что такие системы легко могут указывать размер структур sockaddr.</p>
     </cite>
     <p><code>52-60</code> Если система возвращает структуры <code>sockaddr</code> семейства <code>AF_LINK</code> в <code>SIOCGIFCONF</code>, мы копируем индекс интерфейса и данные об аппаратном адресе из таких структур.</p>
     <p><code>62-63</code> Мы игнорируем все адреса из семейств, отличных от указанного вызывающим процессом в аргументе функции <code>get_ini_info</code>.</p>
     <subtitle>Обработка альтернативных имен</subtitle>
     <p><code>64-72</code> Нам нужно обнаружить все альтернативные имена (псевдонимы), которые могут существовать для интерфейса, то есть присвоенные этому интерфейсу дополнительные адреса. Обратите внимание в наших примерах, следующих за листингом 17.3, что в Solaris псевдоним содержит двоеточие, в то время как в 4.4BSD имя интерфейса в псевдониме не изменяется. Чтобы обработать оба случая, мы сохраняем последнее имя интерфейса в <code>lastname</code> и сравниваем его только до двоеточия, если оно присутствует. Если двоеточия нет, мы игнорируем этот интерфейс в том случае, когда имя эквивалентно последнему обработанному интерфейсу.</p>
     <subtitle>Получение флагов интерфейса</subtitle>
     <p><code>73-77</code> Мы выполняем вызов <code>SIOCGIFFLAGS</code> функции <code>ioctl</code> (см. раздел 16.5), чтобы получить флаги интерфейса. Третий аргумент функции <code>ioctl</code> — это указатель на структуру <code>ifreq</code>, содержащую имя интерфейса, для которого мы хотим получить флаги. Мы создаем копию структуры <code>ifreq</code>, перед тем как запустить функцию ioctl, поскольку в противном случае этот вызов перезаписал бы IP-адрес интерфейса, потому что оба они являются элементами одного и того же объединения из листинга 17.1. Если интерфейс не активен, мы игнорируем его.</p>
     <p>В листинге 17.6 представлена третья часть нашей функции.</p>
     <p><strong>Листинг 17.6</strong>. Получение и возвращение адресов интерфейса</p>
     <p><code>//ioctl/get_ifi_infо.c</code></p>
     <p><code>78   ifi = Calloc(1, sizeof(struct ifi_info));</code></p>
     <p><code>79   *ifipnext = ifi; /* prev указывает на новую структуру */</code></p>
     <p><code>80   ifipnext = &amp;ifi-&gt;ifi_next; /* сюда указывает указатель на</code></p>
     <p><code>                                   следующую структуру */</code></p>
     <empty-line/>
     <p><code>81   ifi-&gt;ifi_flags = flags; /* значения IFF_xxx */</code></p>
     <p><code>82   ifi-&gt;ifi_myflags = myflags; /* значения IFI_xxx */</code></p>
     <p><code>83 #if defined(SIOCGIFMTU) &amp;&amp; defined(HAVE_STRUCT_IFREQ_IFR_MTU)</code></p>
     <p><code>84   Ioctl(sockfd, SIOCGIFMTU, &amp;ifrcopy);</code></p>
     <p><code>85   ifi-&gt;ifi_mtu = ifrcopy.ifr_mtu;</code></p>
     <p><code>86 #else</code></p>
     <p><code>87   ifi-&gt;ifi_mtu = 0;</code></p>
     <p><code>88 #endif</code></p>
     <p><code>89   memcpy(ifi-&gt;ifi_name, ifr-&gt;ifr_name, IFI_NAME);</code></p>
     <p><code>90   ifi-&gt;ifi_name[IFI_NAME-1] = '\0';</code></p>
     <p><code>91   /* если sockaddr_dl относится к другому интерфейсу, он игнорируется */</code></p>
     <p><code>92   if (sdlname == NULL || strcmp(sdlname, ifr-&gt;ifr_name) != 0)</code></p>
     <p><code>93    idx = hlen = 0;</code></p>
     <p><code>94   ifi-&gt;ifi_index = idx;</code></p>
     <p><code>95   ifi-&gt;ifi_hlen = hlen;</code></p>
     <p><code>96   if (ifi-&gt;ifi_hlen &gt; IFI_HADDR)</code></p>
     <p><code>97    ifi-&gt;ifi_hlen = IFI_HADDR;</code></p>
     <p><code>98   if (hlen)</code></p>
     <p><code>99    memcpy(ifi-&gt;ifi_haddr, haddr, ifi-&gt;ifi_hlen);</code></p>
     <subtitle>Выделение памяти и инициализация структуры ifi_info</subtitle>
     <p><code>78-99</code> На этом этапе мы знаем, что возвратим данный интерфейс вызывающему процессу. Мы выделяем память для нашей структуры <code>ifi_info</code> и добавляем ее в конец связного списка, который мы создаем. Мы копируем флаги и имя интерфейса в эту структуру. Далее мы проверяем, заканчивается ли имя интерфейса нулем, и поскольку функция <code>callос</code> инициализирует выделенную в памяти область нулями, мы знаем, что <code>ifi_hlen</code> инициализируется нулем, a <code>ifi_next</code> — пустым указателем.</p>
     <p>В листинге 17.7 представлена последняя часть нашей функции.</p>
     <p><strong>Листинг 17.7</strong>. Получение и возврат адреса интерфейса</p>
     <p><code>100   switch (ifr-&gt;ifr_addr.sa_family) {</code></p>
     <p><code>101   case AF_INET:</code></p>
     <p><code>102    sinptr = (struct sockaddr_in*)&amp;ifr-&gt;ifr_addr;</code></p>
     <p><code>103    ifi-&gt;ifi_addr = Calloc(1, sizeof(struct sockaddr_in));</code></p>
     <p><code>104    memcpy(ifi-&gt;ifi_addr, sinptr, sizeof(struct sockaddr_in));</code></p>
     <empty-line/>
     <p><code>105 #ifdef SIOCGIFBRDADDR</code></p>
     <p><code>106    if (flags &amp; IFF_BROADCAST) {</code></p>
     <p><code>107     Ioctl(sockfd, SIOCGIFBRDADDR, &amp;ifrcopy);</code></p>
     <p><code>108     sinptr = (struct sockaddr_in*) &amp;ifrcopy.ifr_broadaddr;</code></p>
     <p><code>109     ifi-&gt;ifi_brdaddr = Calloc(1, sizeof(struct sockaddr_in));</code></p>
     <p><code>110     memcpy(ifi-&gt;ifi_brdaddr, sinptr, sizeof(struct sockaddr_in));</code></p>
     <p><code>111    }</code></p>
     <p><code>112 #endif</code></p>
     <empty-line/>
     <p><code>113 #ifdef SIOCGIFDSTADDR</code></p>
     <p><code>114    if (flags &amp; IFF_POINTOPOINT) {</code></p>
     <p><code>115     Ioctl(sockfd, SIOCGIFDSTADDR, &amp;ifrcopy);</code></p>
     <p><code>116     sinptr = (struct sockaddr_in*) &amp;ifrcopy.ifr_dstaddr;</code></p>
     <p><code>117     ifi-&gt;ifi_dstaddr = Calloc(1, sizeof(struct sockaddr_in));</code></p>
     <p><code>118     memcpy(ifi-&gt;ifi_dstaddr, sinptr, sizeof(struct sockaddr_in));</code></p>
     <p><code>119    }</code></p>
     <p><code>120 #endif</code></p>
     <p><code>121    break;</code></p>
     <empty-line/>
     <p><code>122   case AF_INET6:</code></p>
     <p><code>123    sin6ptr = (struct sockaddr_in6*)&amp;ifr-&gt;ifr_addr;</code></p>
     <p><code>124    ifi-&gt;ifi_addr = Calloc(1, sizeof(struct sockaddr_in6));</code></p>
     <p><code>125    memcpy(ifi-&gt;ifi_addr, sin6ptr, sizeof(struct sockaddr_in6));</code></p>
     <empty-line/>
     <p><code>126 #ifdef SIOCGIFDSTADDR</code></p>
     <p><code>127    if (flags &amp; IFF_POINTOPOINT) {</code></p>
     <p><code>128     Ioctl(sockfd, SIOCGIFDSTADDR, &amp;ifrcopy);</code></p>
     <p><code>129     sin6ptr = (struct sockaddr_in6*)&amp;ifrcopy.ifr_dstaddf;</code></p>
     <p><code>130     ifi-&gt;ifi_dstaddr = Calloc(1, sizeof(struct sockaddr_in6));</code></p>
     <p><code>131     memcpy(ifi-&gt;ifi_dstaddr, sin6ptr,</code></p>
     <p><code>132     sizeof(struct sockaddr_in6));</code></p>
     <p><code>133    }</code></p>
     <p><code>134 #endif</code></p>
     <p><code>135    break;</code></p>
     <empty-line/>
     <p><code>136   default:</code></p>
     <p><code>137    break;</code></p>
     <p><code>138   }</code></p>
     <p><code>139  }</code></p>
     <p><code>140  free(buf);</code></p>
     <p><code>141  return(ifihead); /* указатель на первую структуру в связной списке */</code></p>
     <p><code>142 }</code></p>
     <p><code>102-104</code> Мы копируем IP-адрес, возвращенный из нашего начального вызова <code>SIOCGIFCONF</code> функции <code>ioctl</code>, в структуру, которую мы создаем.</p>
     <p><code>106-119</code> Если интерфейс поддерживает широковещательную передачу, мы получаем широковещательный адрес с помощью вызова <code>SIOCGIFBRDADDR</code> функции <code>ioctl</code>. Мы выделяем память для структуры адреса сокета, содержащей этот адрес, и добавляем ее к структуре <code>ifi_info</code>, которую мы создаем. Аналогично, если интерфейс является интерфейсом типа «точка-точка», вызов <code>SIOCGIFBRDADDR</code> возвращает IP-адрес другого конца связи.</p>
     <p><code>123-133</code> Обработка случая IPv6 — полная аналогия IPv4 за тем исключением, что вызов <code>SIOCGIFBRDADDR</code> не делается, потому что IPv6 не поддерживает широковещательную передачу.</p>
     <p>В листинге 17.8 показана функция <code>free_ifi_info</code>, которой передается указатель, возвращенный функцией <code>get_ifi_info</code>. Эта функция освобождает всю динамически выделенную память.</p>
     <p><strong>Листинг 17.8</strong>. Функция free_ifi_info: освобождение памяти, которая была динамически выделена функцией get_ifi_info</p>
     <p><code>//iосtl/get_ifi_info.c</code></p>
     <p><code>143 void</code></p>
     <p><code>144 free_ifi_info(struct ifi_info *ifihead)</code></p>
     <p><code>145 {</code></p>
     <p><code>146  struct ifi_info *ifi, *ifinext;</code></p>
     <empty-line/>
     <p><code>147  for (ifi = ifihead; ifi != NULL; ifi = ifinext) {</code></p>
     <p><code>148   if (ifi-&gt;ifi_addr != NULL)</code></p>
     <p><code>149    free(ifi-&gt;ifi_addr);</code></p>
     <p><code>150   if (ifi-&gt;ifi_brdaddr != NULL)</code></p>
     <p><code>151    free(ifi-&gt;ifi_brdaddr);</code></p>
     <p><code>152   if (ifi-&gt;ifi_dstaddr != NULL)</code></p>
     <p><code>153    free(ifi-&gt;ifi_dstaddr);</code></p>
     <p><code>154   ifinext = ifi-&gt;ifi_next; /* невозможно получить ifi_next</code></p>
     <p><code>                                   после вызова freed */</code></p>
     <p><code>155   free(ifi);</code></p>
     <p><code>156  }</code></p>
     <p><code>157 }</code></p>
    </section>
    <section>
     <title>
      <p>17.7. Операции с интерфейсами</p>
     </title>
     <p>Как мы показали в предыдущем разделе, запрос <code>SIOCGIFCONF</code> возвращает имя и структуру адреса сокета для каждого сконфигурированного интерфейса. Существует множество других вызовов, позволяющих установить или получить все остальные характеристики интерфейса. Версия <code>get</code> этих вызовов (<code>SIOCGxxx</code>) часто запускается программой <code>netstat</code>, а версия <code>set</code> (<code>SIOCSxxx</code>) — программой <code>ifconfig</code>. Любой пользователь может получить информацию об интерфейсе, в то время как установка этой информации требует прав привилегированного пользователя.</p>
     <p>Эти вызовы получают или возвращают структуру <code>ifreq</code>, адрес которой задается в качестве третьего аргумента функции <code>ioctl</code>. Интерфейс всегда идентифицируется по имени: <code>le0</code>, <code>lo0</code>, <code>ppp0</code>, — то есть по имени, заданному в элементе <code>ifr_name</code> структуры <code>ifreq</code>.</p>
     <p>Многие из этих запросов используют структуру адреса сокета, для того чтобы задать или возвратить IP-адрес или маску адреса. Для IPv4 адрес или маска содержится в элементе <code>sin_addr</code> из структуры адреса сокета Интернета. Для IPv6 они помещаются в элемент <code>sin6_addr</code> структуры адреса сокета IPv6.</p>
     <p>&#9632; <code>SIOCGIFADDR</code>. Возвращает адрес направленной передачи в элементе <code>ifr_addr</code>.</p>
     <p>&#9632; <code>SIOCSIFADDR</code>. Устанавливает адрес интерфейса из элемента <code>ifr_addr</code>. Также вызывается функция инициализации для интерфейса.</p>
     <p>&#9632; <code>SIOCGIFFLAGS</code>. Возвращает флаги интерфейса в элементе <code>ifr_flags</code>. Имена различных флагов определяются в виде <code>IFF_xxx</code> в заголовочном файле <code>&lt;net/if.h&gt;</code>. Флаги указывают, например, включен ли интерфейс (<code>IFF_UP</code>), является ли он интерфейсом типа «точка-точка» (<code>IFF_POINTOPOINT</code>), поддерживает ли широковещательную передачу (<code>IFF_BROADCAST</code>) и т.д.</p>
     <p>&#9632; <code>SIOCSIFFLAGS</code>. Устанавливает флаги из элемента <code>ifr_flags</code>.</p>
     <p>&#9632; <code>SIOCGIFDSTADDR</code>. Возвращает адрес типа «точка-точка» в элементе <code>ifr_dstaddr</code>.</p>
     <p>&#9632; <code>SIOCSIFDSTADDR</code>. Устанавливает адрес типа «точка-точка» из элемента <code>ifr_dstaddr</code>.</p>
     <p>&#9632; <code>SIOCGIFBRDADDR</code>. Возвращает широковещательный адрес в элементе <code>ifr_broadaddr</code>. Приложение сначала должно получить флаги интерфейса, а затем сделать корректный вызов: <code>SIOCGIFBRDADDR</code> для широковещательного интерфейса или <code>SIOCGIFDSTADDR</code> — для интерфейса типа «точка-точка».</p>
     <p>&#9632; <code>SIOCSIFBRDADDR</code>. Устанавливает широковещательный адрес из элемента <code>ifr_broadaddr</code>.</p>
     <p>&#9632; <code>SIOCGIFNETMASK</code>. Возвращает маску подсети в элементе <code>ifr_addr</code>.</p>
     <p>&#9632; <code>SIOCSIFNETMASK</code>. Устанавливает маску подсети из элемента <code>ifr_addr</code>.</p>
     <p>&#9632; <code>SIOCGIFMETRIC</code>. Возвращает метрику интерфейса в элементе <code>ifr_metric</code>. Метрика поддерживается ядром для каждого интерфейса, но используется демоном маршрутизации <code>routed</code>. Метрика интерфейса добавляется к счетчику количества переходов.</p>
     <p>&#9632; <code>SIOCSIFMETRIC</code>. Устанавливает метрику интерфейса из элемента <code>ifr_metric</code>.</p>
     <p>В этом разделе мы описали наиболее типичные операции интерфейсов. Во многих реализациях появились дополнительные операции.</p>
    </section>
    <section>
     <title>
      <p>17.8. Операции с кэшем ARP</p>
     </title>
     <section>
      <p>Операции с кэшем ARP также осуществляются с помощью функции <code>ioctl</code>. В этих запросах используется структура <code>arpreq</code>, показанная в листинге 17.9 и определяемая в заголовочном файле <code>&lt;net/if_arp.h&gt;</code>.</p>
      <p><strong>Листинг 17.9</strong>. Структура arpreq, используемая с вызовами ioctl для кэша ARP</p>
      <p><code>struct arpreq {</code></p>
      <p><code> struct sockaddr arp_pa;    /* адрес протокола */</code></p>
      <p><code> struct sockaddr arp_ha;    /* аппаратный адрес */</code></p>
      <p><code> int             arp_flags; /* флаги */</code></p>
      <p><code>};</code></p>
      <empty-line/>
      <p><code>#define ATF_INUSE 0x01 /* запись, которую нужно использовать */</code></p>
      <p><code>#define ATF_COM   0x02 /* завершенная запись */</code></p>
      <p><code>#define ATF_PERM  0x04 /* постоянная запись */</code></p>
      <p><code>#define ATF_PUBL  0x08 /* опубликованная запись (отсылается другим узлам) */</code></p>
      <p>Третий аргумент функции <code>ioctl</code> должен указывать на одну из этих структур. Поддерживаются следующие три вызова:</p>
      <p>&#9632; <code>SIOCSARP</code>. Добавляет новую запись в кэш ARP или изменяет существующую запись. <code>arp_pa</code> — это структура адреса сокета Интернета, содержащая IP-адрес, a <code>arp_ha</code> — это общая структура адреса сокета с элементом <code>ss_family</code>, равным <code>AF_UNSPEC</code>, и элементом <code>sa_data</code>, содержащим аппаратный адрес (например, 6-байтовый адрес Ethernet). Два флага <code>ATF_PERM</code> и <code>ATF_PUBL</code> могут быть заданы приложением. Два других флага, <code>ATF_INUSE</code> и <code>ATF_COM</code>, устанавливаются ядром.</p>
      <p>&#9632; <code>SIOCDARP</code>. Удаляет запись из кэша ARP. Вызывающий процесс задает интернет-адрес удаляемой записи.</p>
      <p>&#9632; <code>SIOCGARP</code>. Получает запись из кэша ARP. Вызывающий процесс задает интернет-адрес, и соответствующий адрес Ethernet возвращается вместе с флагами.</p>
      <p>Добавлять или удалять записи может только привилегированный пользователь. Эти три вызова обычно делает программа <code>arp</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Запросы функции ioctl, связанные с ARP, не поддерживаются в некоторых более новых системах, использующих для описанных операций ARP маршрутизирующие сокеты.</p>
      </cite>
      <p>Обратите внимание, что невозможно с помощью функции <code>ioctl</code> перечислить все записи кэша ARP. Большинство версий команды <code>arp</code> при использовании флага <code>-a</code> (перечисление всех записей кэша ARP) считывают память ядра (<code>/dev/kmem</code>), чтобы получить текущее содержимое кэша ARP. Мы увидим более простой (и предпочтительный) способ, основанный на применении функции <code>sysctl</code>, описанной в разделе 18.4.</p>
     </section>
     <section>
      <title>
       <p>Пример: вывод аппаратного адреса узла</p>
      </title>
      <p>Теперь мы используем нашу функцию <code>my_addrs</code> для того, чтобы возвратить все IP-адреса узла. Затем для каждого IP-адреса мы делаем вызов <code>SIOCGARP</code> функции <code>ioctl</code>, чтобы получить и вывести аппаратные адреса. Наша программа показана в листинге 17.10.</p>
      <p><strong>Листинг 17.10</strong>. Вывод аппаратного адреса узла</p>
      <p><code>//ioctl/prmac.c</code></p>
      <p><code> 1 #include "unpifi.h"</code></p>
      <p><code> 2 #include &lt;net/if_arp.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 main(int argc, char **argv)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int sockfd;</code></p>
      <p><code> 7  struct ifi_info *ifi;</code></p>
      <p><code> 8  unsigned char *ptr;</code></p>
      <p><code> 9  struct arpreq arpreq;</code></p>
      <p><code>10  struct sockaddr_in *sin;</code></p>
      <empty-line/>
      <p><code>11  sockfd = Socket(AF_INET, SOCK_DGRAM, 0);</code></p>
      <p><code>12  for (ifi = get_ifi_info(AF_INET, 0); ifi != NULL; ifi = ifi-&gt;ifi_next) {</code></p>
      <p><code>13   printf("%s: ", Sock_ntop(ifi-&gt;ifi_addr, sizeof(struct sockaddr_in)));</code></p>
      <empty-line/>
      <p><code>14   sin = (struct sockaddr_in*)&amp;arpreq.arp_pa;</code></p>
      <p><code>15   memcpy(sin, ifi-&gt;ifi_addr, sizeof(struct sockaddr_in));</code></p>
      <empty-line/>
      <p><code>16   if (ioctl(sockfd, SIOCGARP, &amp;arpreq) &lt; 0) {</code></p>
      <p><code>17    err_ret("ioctl SIOCGARP");</code></p>
      <p><code>18    continue;</code></p>
      <p><code>19   }</code></p>
      <empty-line/>
      <p><code>20   ptr = &amp;arpreq.arp_ha.sa_data[0];</code></p>
      <p><code>21   printf("%x:%x:%x:%x:%x:%x\n", *ptr, *(ptr+1),</code></p>
      <p><code>22    *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5));</code></p>
      <p><code>23  }</code></p>
      <p><code>24  exit(0);</code></p>
      <p><code>25 }</code></p>
      <subtitle>Получение списка адресов и проход в цикле по каждому из них</subtitle>
      <p><code>12</code> Мы вызываем функцию <code>get_ifi_info</code>, чтобы получить IP-адреса узла, а затем выполняем цикл по всем адресам.</p>
      <subtitle>Вывод IP-адреса</subtitle>
      <p><code>13</code> Мы выводим IP-адреса, используя функцию <code>inet_ntop</code>. Мы просим функцию <code>get_ifi_info</code> возвращать только адреса IPv4, так как ARP с IPv6 не используется.</p>
      <subtitle>Вызов функции ioctl и проверка ошибок</subtitle>
      <p><code>14-19</code> Мы заполняем структуру <code>arp_pa</code> как структуру адреса сокета IPv4, содержащую адрес IPv4. Вызывается функция <code>ioctl</code>, и если она возвращает ошибку (например, указанный адрес относится к интерфейсу, не поддерживающему ARP), мы выводим сообщение и переходим к следующему адресу.</p>
      <subtitle>Вывод аппаратного адреса</subtitle>
      <p><code>20-22</code> Выводится аппаратный адрес, возвращаемый <code>ioctl</code>.</p>
      <p>При запуске этой программы на нашем узле <code>hpux</code> мы получаем:</p>
      <p><code>hpux % <strong>prmac</strong></code></p>
      <p><code>192.6.38.100: 0:60:b0:c2:68:9b</code></p>
      <p><code>192.168.1.1: 0:60:b0:b2:28:2b</code></p>
      <p><code>127.0.0.1: ioctl SIOCGARP: Invalid argument</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>17.9. Операции с таблицей маршрутизации</p>
     </title>
     <p>Для работы с таблицей маршрутизации предназначены два вызова функции <code>ioctl</code>. Эти два вызова требуют, чтобы третий аргумент функции ioctl был указателем на структуру <code>rtentry</code>, которая определяется в заголовочном файле <code>&lt;net/route.h&gt;</code>. Обычно эти вызовы исходят от программы <code>route</code>. Их может делать только привилегированный пользователь. При наличии маршрутизирующих сокетов (глава 18) для выполнения этих запросов используются именно они, а не функция <code>ioctl</code>.</p>
     <p>&#9632; <code>SIOCADDRT</code>. Добавить запись в таблицу маршрутизации.</p>
     <p>&#9632; <code>SIOCDELRT</code>. Удалить запись из таблицы маршрутизации.</p>
     <p>Нет способа с помощью функции <code>ioctl</code> перечислить все записи таблицы маршрутизации. Эту операцию обычно выполняет программа <code>netstat</code> с флагом <code>-r</code>. Программа получает таблицу маршрутизации, считывая память ядра (<code>/dev/kmem</code>). Как и в случае с просмотром кэша ARP, в разделе 18.4 мы увидим более простой (и предпочтительный) способ, предоставляемый функцией <code>sysctl</code>.</p>
    </section>
    <section>
     <title>
      <p>17.10. Резюме</p>
     </title>
     <p>Команды функции <code>ioctl</code>, используемые в сетевых приложениях, можно разделить на шесть категорий:</p>
     <p>1. Операции с сокетами (находимся ли мы на отметке внеполосных данных?).</p>
     <p>2. Операции с файлами (установить или сбросить флаг отсутствия блокировки).</p>
     <p>3. Операции с интерфейсами (возвратить список интерфейсов, получить широковещательный адрес).</p>
     <p>4. Операции с кэшем ARP (создать, изменить, получить, удалить).</p>
     <p>5. Операции с таблицей маршрутизации (добавить или удалить).</p>
     <p>6. Операции с потоками STREAMS (см. главу 31).</p>
     <p>Мы будем использовать операции с сокетами и файлами, а получение списка интерфейсов — это настолько типичная операция, что для этой цели мы разработали собственную функцию. Мы будем применять ее много раз в оставшейся части книги. Вызовы функции <code>ioctl</code> с кэшем ARP и таблицей маршрутизации используются лишь несколькими специализированными программами.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. В разделе 17.7 мы сказали, что широковещательный адрес, возвращаемый запросом SIOCGIFBRDADDR, возвращается в элементе <code>ifr_broadaddr</code>. Но на с. 173 [128] сказано, что он возвращается в элементе <code>ifr_dstaddr</code>. Имеет ли это значение?</p>
     <p>2. Измените программу <code>get_ifi_info</code> так, чтобы она делала первый вызов <code>SIOCGIFCONF</code> для одной структуры <code>ifreq</code>, а затем каждый раз в цикле увеличивайте длину на размер одной из этих структур. Затем поместите в цикл операторы, которые выводили бы размер буфера при каждом вызове независимо от того, возвращает функция <code>ioctl</code> ошибку или нет, и при успешном выполнении выведите возвращаемую длину буфера. Запустите программу <code>prifinfo</code> и посмотрите, как ваша система обрабатывает вызов, когда размер буфера слишком мал. Выведите также семейство адресов для всех возвращаемых структур, семейство адресов которых не совпадает с указанным в первом аргументе функции <code>get_ifi_info</code>, чтобы увидеть, какие еще структуры возвращает ваша система.</p>
     <p>3. Измените функцию <code>get_ifi_info</code> так, чтобы она возвращала информацию об адресе с альтернативным именем, если дополнительный адрес находится не в той подсети, в которой находится предыдущий адрес для данного интерфейса. Таким образом, наша версия из раздела 17.6 будет игнорировать альтернативные имена в диапазоне от 206.62.226.44 до 206.62.226.46, и это вполне нормально, поскольку они находятся в той же подсети, что и первичный адрес интерфейса 206.62.226.33. Но если альтернативное имя находится в другой подсети, допустим 192.3.4.5, возвратите структуру <code>ifi_info</code> с информацией о дополнительном адресе.</p>
     <p>4. Если ваша система поддерживает вызов <code>SIOCGIGNUM</code> функции <code>ioctl</code>, измените листинг 17.4 так, чтобы запустить этот вызов, и используйте возвращаемое значение как начальный размер буфера.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 18</p>
     <p>Маршрутизирующие сокеты</p>
    </title>
    <section>
     <title>
      <p>18.1. Введение</p>
     </title>
     <p>Традиционно доступ к таблице маршрутизации Unix внутри ядра осуществлялся с помощью команд функции <code>ioctl</code>. В разделе 17.9 мы описали две операции: <code>SIOCADDRT</code> и <code>SIOCDELRT</code>, предназначенные для добавления и удаления маршрута. Мы также отметили, что не существует операции чтения всей таблицы маршрутизации — вместо этого программы, такие как <code>netstat</code>, считывают память ядра, для того чтобы получить содержимое таблицы маршрутизации. И еще одно добавление. Демонам маршрутизации, таким как <code>gated</code>, необходимо отслеживать сообщения ICMP (Internet Control Message Protocol — протокол управляющих сообщений Интернета) об изменении маршрутов, получаемых ядром, и для этого они часто создают символьный (неструктурированный) сокет ICMP (см. главу 28), а затем прослушивают на этом сокете все получаемые сообщения ICMP.</p>
     <p>В 4.3BSD Reno интерфейс подсистемы маршрутизации ядра был упрощен за счет создания семейства адресов (домена) <code>AF_ROUTE</code>. Единственный тип сокетов, поддерживаемый для этого семейства, — это символьный сокет (raw socket). Маршрутизирующие сокеты поддерживают три типа операций.</p>
     <p>1. Процесс может отправить ядру сообщение, записав его в маршрутизирующий сокет. Таким образом добавляются и удаляются маршруты.</p>
     <p>2. Процесс может прочитать сообщение от ядра через маршрутизирующий сокет. Так ядро уведомляет процесс о том, что сообщение ICMP об изменении маршрутизации было получено и обработано.</p>
     <p>Некоторые операции включают оба шага: например, процесс отправляет ядру сообщение через маршрутизирующий сокет, запрашивая всю информацию по данному маршруту, после чего через маршрутизирующий сокет считывает ответ ядра.</p>
     <p>3. Процесс может использовать функцию <code>sysctl</code> (см. раздел 18.4) либо для просмотра таблицы маршрутизации, либо для перечисления всех сконфигурированных интерфейсов.</p>
     <p>Первые две операции требуют прав привилегированного пользователя, а третью операцию может выполнить любой процесс.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Некоторые версии Unix из более новых ослабили требование к правам пользователя для операции открытия маршрутизирующего сокета и ограничивают только передачу сообщений, изменяющих таблицу маршрутизации ядра. Это позволяет любому процессу узнать маршрут при помощи команды RTM_GET, не являясь суперпользователем.</p>
      <p>Технически третья операция выполняется при помощи общей функции sysctl, а не маршрутизирующего сокета. Но мы увидим, что среди ее входных параметров есть семейство адресов (для описываемых в этой главе операций используется семейство AF_ROUTE), а результат она возвращает в том же формате, который используется ядром для маршрутизирующего сокета. Действительно, в ядре 4.4BSD обработка функции sysctl для семейства AF_ROUTE является частью кода маршрутизирующего сокета [128, с. 632–643].</p>
      <p>Функция sysctl появилась в 4.4BSD. К сожалению, не все реализации, поддерживающие маршрутизирующие сокеты, предоставляют ее. Например, AIX 4.2, Digital Unix 4.0 и Solaris 2.6 поддерживают маршрутизирующие сокеты, но ни одна из этих систем не поддерживает утилиту sysctl.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>18.2. Структура адреса сокета канального уровня</p>
     </title>
     <p>Структуры адреса сокета канального уровня будут встречаться нам как значения, содержащиеся в некоторых сообщениях, возвращаемых на маршрутизирующем сокете. В листинге 18.1<a l:href="#n1" type="note">[1]</a> показано определение структуры, задаваемой в заголовочном файле <code>&lt;net/if_dl.h&gt;</code>.</p>
     <p><strong>Листинг 18.1</strong>. Структура адреса сокета канального уровня</p>
     <p><code>struct sockaddr_dl {</code></p>
     <p><code> uint8_t     sdl_len;</code></p>
     <p><code> sa_family_t sdl_family;   /* AF_LINK */</code></p>
     <p><code> uint16_t    sdl_index;    /* индекс интерфейса, присвоенный системой,</code></p>
     <p><code>                              если &gt; 0 */</code></p>
     <p><code> uint8_t     sdl_type;     /* тип интерфейса из &lt;net/if_types.h&gt;.</code></p>
     <p><code> IFT_ETHER и т.д. */</code></p>
     <p><code> uint8_t     sdl_nlen;     /* длина имени, начинается с sdl_data[0] */</code></p>
     <p><code> uint8_t     sdl_alen;     /* длина адреса канального уровня */</code></p>
     <p><code> uint8_t     sdl_slen;     /* адрес селектора канального уровня */</code></p>
     <p><code> char        sdl_data[12]; /* минимальная рабочая область.</code></p>
     <p><code>                              может быть больше; содержит имя</code></p>
     <p><code>                              интерфейса и адрес канального уровня */</code></p>
     <p><code>};</code></p>
     <p>У каждого интерфейса имеется уникальный положительный индекс. Далее в этой главе мы увидим, каким образом он возвращается функциями <code>if_nametoindex</code> и <code>if_nameindex</code>. В главе 21 при обсуждении параметров многоадресных сокетов IPv6 и в главе 27 при обсуждении дополнительных параметров сокетов IPv6 и IPv4 мы вновь вернемся к этим функциям.</p>
     <p>Элемент <code>sdl_data</code> содержит и имя, и адрес канального уровня (например, 48-разрядный MAC-адрес интерфейса Ethernet). Имя начинается с <code>sdl_data[0]</code> и не заканчивается нулем. Начало адреса канального уровня смещено на <code>sdl_nlen</code> байтов относительно начала имени. В этом заголовочном файле для возвращения указателя на адрес канального уровня задается следующий макрос:</p>
     <p><code>#define LLADDR(s) ((caddr_t)((s)-&gt;sdl_data + (s)-&gt;sdl_nlen))</code></p>
     <p>Эти структуры адреса сокета имеют переменную длину [128, с. 89]. Если адрес канального уровня и имя превышают 12 байт, размер структуры будет больше 20 байт. В 32-разрядных системах размер обычно округляется в большую сторону, до следующего числа, кратного 4 байтам. Мы также увидим на рис. 22.1, что когда одна из этих структур возвращается параметром сокета <code>IP_RECVIF</code>, все три длины становятся нулевыми, а элемента <code>sdl_data</code> не существует.</p>
    </section>
    <section>
     <title>
      <p>18.3. Чтение и запись</p>
     </title>
     <section>
      <p>Создав маршрутизирующий сокет, процесс может отправлять ядру команды путем записи в этот сокет и считывать из него информацию от ядра. Существует 12 различных команд маршрутизации, 5 из которых могут быть запущены процессом. Они определяются в заголовочном файле <code>&lt;net/route.h&gt;</code> и показаны в табл. 18.1.</p>
      <empty-line/>
      <p><strong>Таблица 18.1</strong>. Типы сообщений, проходящих по маршрутизирующему сокету</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Тип сообщения</th>
        <th align="left" valign="top">К ядру?</th>
        <th align="left" valign="top">От ядра?</th>
        <th align="left" valign="top">Описание</th>
        <th align="left" valign="top">Тип структуры</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_ADD</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Добавить маршрут</td>
        <td align="left" valign="top">rt_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_CHANGE</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Поменять шлюз, метрику или флаги</td>
        <td align="left" valign="top">rt_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_DELADDR</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Адрес был удален из интерфейса</td>
        <td align="left" valign="top">ifa_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_DELETE</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Удалить маршрут</td>
        <td align="left" valign="top">rt_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_GET</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Сообщить о метрике и других характеристиках маршрута</td>
        <td align="left" valign="top">rt_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_IFINFO</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Находится ли интерфейс в активном состоянии</td>
        <td align="left" valign="top">if_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_LOCK</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Блокировка указанной метрики</td>
        <td align="left" valign="top">rt_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_LOSING</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Возможно, неправильный маршрут</td>
        <td align="left" valign="top">rt_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_MISS</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Поиск этого адреса завершился неудачно</td>
        <td align="left" valign="top">rt_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_NEWSDDR</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Адрес добавлен к интерфейсу</td>
        <td align="left" valign="top">ifa_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_NEWMDDR</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Групповой адрес добавлен к интерфейсу</td>
        <td align="left" valign="top">ifma_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_REDIRECT</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Ядро получило указание использовать другой маршрут</td>
        <td align="left" valign="top">rt_msghdr</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTM_RESOLVE</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">•</td>
        <td align="left" valign="top">Запрос на определение адреса канального уровня по адресу получателя</td>
        <td align="left" valign="top">rt_msghdr</td>
       </tr>
      </table>
      <p>На маршрутизирующем сокете происходит обмен пятью различными структурами, как показано в последнем столбце таблицы: <code>rt_msghdr</code>, <code>if_msghdr</code>, <code>if_announcemsghdr</code>, <code>ifma_msghdr</code> и <code>ifa_msghdr</code>. Эти структуры представлены в листинге 18.2.</p>
      <empty-line/>
      <p><strong>Листинг 18.2.</strong> Пять структур, возвращаемых с маршрутизирующими сообщениями</p>
      <p><code>struct rt_msghdr { /* из &lt;net/route.h&gt; */</code></p>
      <p><code> u_short rtm_msglen;  /* для пропуска некорректных сообщений */</code></p>
      <p><code> u_char  rtm_version; /* для обеспечения двоичной совместимости в будущем */</code></p>
      <p><code> u_char  rtm_type;    /* тип сообщения */</code></p>
      <empty-line/>
      <p><code> u_short rtm_index; /* индекс интерфейса, с которым связан адрес */</code></p>
      <p><code> int     rtm_flags; /* флаги */</code></p>
      <p><code> int     rtm_addrs; /* битовая маска, идентифицирующая sockaddr (структуру адреса</code></p>
      <p><code>                       сокета) в msg */</code></p>
      <p><code> pid_t   rtm_pid;   /* идентификация отправителя */</code></p>
      <p><code> int     rtm_seq;   /* для идентификации действия отправителем */</code></p>
      <p><code> int     rtm_errno; /* причина неудачного выполнения */</code></p>
      <p><code> int     rtm_use;   /* из rtentry */</code></p>
      <p><code> u_long  rtm_inits; /* какую метрику мы инициализируем */</code></p>
      <p><code> struct rt_metrics rtm_rmx; /* сами метрики */</code></p>
      <p><code>};</code></p>
      <empty-line/>
      <p><code>struct if_msghdr { /* из &lt;net/if.h&gt; */</code></p>
      <p><code> u_short ifm_msglen;  /* для пропуска некорректных сообщений */</code></p>
      <p><code> u_char  ifm_version; /* для обеспечения двоичной совместимости в будущем */</code></p>
      <p><code> u_char  ifm_type;    /* тип сообщения */</code></p>
      <empty-line/>
      <p><code> int     ifm_addrs;       /* как rtm_addrs */</code></p>
      <p><code> int     ifm_flags;       /* значение if_flags */</code></p>
      <p><code> u_short ifm_index;       /* индекс интерфейса, с которым связан адрес */</code></p>
      <p><code> struct if_data ifm_data; /* статистические и другие сведения */</code></p>
      <p><code>};</code></p>
      <empty-line/>
      <p><code>struct ifa_msghdr { /* из &lt;net/if.h&gt; */</code></p>
      <p><code> u_short ifam_msglen;  /* для пропуска некорректных сообщений */</code></p>
      <p><code> u_char  ifam_version; /* для обеспечения двоичной совместимости в будущем */</code></p>
      <p><code> u_char  ifam_type;    /* тип сообщения */</code></p>
      <empty-line/>
      <p><code> int     ifam_addrs;  /* как rtm_addrs */</code></p>
      <p><code> int     ifam_flags;  /* значение ifa_flags */</code></p>
      <p><code> u_short ifam_index;  /* индекс интерфейса, с которым связан адрес */</code></p>
      <p><code> int     ifam_metric; /* значение ifa_metric */</code></p>
      <p><code>};</code></p>
      <empty-line/>
      <p><code>struct ifma_msghdr { /* из &lt;net/if.h&gt; */</code></p>
      <p><code> u_short ifmam_msglen;  /* для пропуска некорректных сообщений */</code></p>
      <p><code> u_char  ifmam_version; /* для обеспечения двоичной совместимости в будущем */</code></p>
      <p><code> u_char  ifmam_type;    /* тип сообщения */</code></p>
      <p><code> int     ifmam_addrs;   /* аналог rtm_addrs */</code></p>
      <p><code> int     ifmam_flags;   /* значение ifa_flags */</code></p>
      <p><code> u_short ifmam_index;   /* индекс связанного ifp */</code></p>
      <p><code>};</code></p>
      <empty-line/>
      <p><code>struct if_announcemsghdr { /* из &lt;net/if.h&gt; */</code></p>
      <p><code> u_short ifan_msglen;  /* для пропуска некорректных сообщений */</code></p>
      <p><code> u_char  ifan_version; /* для обеспечения двоичной совместимости в будущем */</code></p>
      <p><code> u_char  ifan_type;    /* тип сообщения */</code></p>
      <p><code> u_short ifan_index;   /* индекс связанного ifp */</code></p>
      <p><code> char    ifan_name[IFNAMSIZ]; /* название интерфейса, напр. "en0" */</code></p>
      <p><code> u_short ifan_what;    /* тип объявления */</code></p>
      <p><code>};</code></p>
      <p>Первые три элемента каждой структуры одни и те же: длина, версия и тип сообщения. Тип — это одна из констант из первого столбца табл. 18.1. Элемент длины <code>xxx_msglen</code> позволяет приложению пропускать типы сообщений, которые оно не распознает.</p>
      <p>Элементы <code>rtm_addrs</code>, <code>ifm_addrs</code> и <code>ifam_addrs</code> являются битовыми масками, указывающими, какая из возможных восьми структур адреса сокета следует за сообщением. В табл. 18.2 показаны константы и значения для битовой маски, определяемые в заголовочном файле <code>&lt;net/route.h&gt;</code>.</p>
      <empty-line/>
      <p><strong>Таблица 18.2</strong>. Константы, используемые для ссылки на структуры адреса сокета в маршрутизирующих сообщениях</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Битовая маска, константа</th>
        <th align="left" valign="top">Битовая маска, значение</th>
        <th align="left" valign="top">Индекс массива, константа</th>
        <th align="left" valign="top">Индекс массива, значение</th>
        <th align="left" valign="top">Структура адреса сокета содержит</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTA_DST</td>
        <td align="left" valign="top">0x01</td>
        <td align="left" valign="top">RTAX_DST</td>
        <td align="left" valign="top">0</td>
        <td align="left" valign="top">Адрес получателя</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTA_GATEWAY</td>
        <td align="left" valign="top">0x02</td>
        <td align="left" valign="top">RTAX_GATEWAY</td>
        <td align="left" valign="top">1</td>
        <td align="left" valign="top">Адрес шлюза</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTA_NETMASK</td>
        <td align="left" valign="top">0x04</td>
        <td align="left" valign="top">RTAX_NETMASK</td>
        <td align="left" valign="top">2</td>
        <td align="left" valign="top">Маска сети</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTA_GENMASK</td>
        <td align="left" valign="top">0x08</td>
        <td align="left" valign="top">RTAX_GENMASK</td>
        <td align="left" valign="top">3</td>
        <td align="left" valign="top">Маска клонирования</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTA_IFP</td>
        <td align="left" valign="top">0x10</td>
        <td align="left" valign="top">RTAX_IFP</td>
        <td align="left" valign="top">4</td>
        <td align="left" valign="top">Имя интерфейса</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTA_IFA</td>
        <td align="left" valign="top">0x20</td>
        <td align="left" valign="top">RTAX_IFA</td>
        <td align="left" valign="top">5</td>
        <td align="left" valign="top">Адрес интерфейса</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTA_AUTHOR</td>
        <td align="left" valign="top">0x40</td>
        <td align="left" valign="top">RTAX_AUTHOR</td>
        <td align="left" valign="top">6</td>
        <td align="left" valign="top">Отправитель запроса на перенаправление</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">RTA_BRD</td>
        <td align="left" valign="top">0x80</td>
        <td align="left" valign="top">RTAX_BRD</td>
        <td align="left" valign="top">7</td>
        <td align="left" valign="top">Адрес получателя типа «точка-точка» или широковещательный</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
        <td align="left" valign="top">RTAX_MAX</td>
        <td align="left" valign="top">8</td>
        <td align="left" valign="top">Максимальное количество элементов</td>
       </tr>
      </table>
      <p>В том случае, когда имеется множество структур адреса сокета, они всегда располагаются в порядке, показанном в таблице.</p>
     </section>
     <section>
      <title>
       <p>Пример: получение и вывод записи из таблицы маршрутизации</p>
      </title>
      <p>Теперь мы покажем пример использования маршрутизирующих сокетов. Наша программа получает аргумент командной строки, состоящий из адреса IPv4 в точечно-десятичной записи, и отправляет ядру сообщение <code>RTM_GET</code> для получения этого адреса. Ядро ищет адрес в своей таблице маршрутизации IPv4 и возвращает сообщение <code>RTM_GET</code> с информацией о соответствующей записи из таблицы маршрутизации. Например, если мы выполним на нашем узле <code>freebsd</code> такой код</p>
      <p><code>freebsd # <strong>getrt 206.168.112.219</strong></code></p>
      <p><code>dest: 0.0.0.0</code></p>
      <p><code>gateway: 12.106.32.1</code></p>
      <p><code>netmask: 0.0.0.0</code></p>
      <p>мы увидим, что этот адрес получателя использует маршрут по умолчанию (который хранится в таблице маршрутизации с IP-адресом получателя 0.0.0.0 и маской 0.0.0.0). Маршрутизатор следующей ретрансляции — это интернет-шлюз нашей системы. Если мы выполним</p>
      <p><code>freebsd # <strong>getrt 192.168.42.0</strong></code></p>
      <p><code>dest: 192.168.42.0</code></p>
      <p><code>gateway: AF_LINK, index=2</code></p>
      <p><code>netmask: 255.255.255.0</code></p>
      <p>задав в качестве получателя главную сеть Ethernet, получателем будет сама сеть. Теперь шлюзом является исходящий интерфейс, возвращаемый в качестве структуры <code>sockaddr_dl</code> с индексом интерфейса 2.</p>
      <p>Перед тем как представить исходный код, мы показываем на рис. 18.1, что именно мы пишем в маршрутизирующий сокет и что возвращает ядро.</p>
      <image l:href="#img_110.png"/>
      <p><strong>Рис. 18.1</strong>. Обмен данными с ядром на маршрутизирующем сокете для команды RTM_GET</p>
      <p>Мы создаем буфер, содержащий структуру <code>rt_msghdr</code>, за которой следует структура адреса сокета, содержащая адрес получателя, информацию о котором должно найти ядро. Тип сообщения (<code>rtm_type</code>) — <code>RTM_GET</code>, а битовая маска (<code>rtm_addrs</code>) — <code>RTA_DST</code> (вспомните табл. 18.2). Эти значения указывают, что структура адреса сокета, следующая за структурой <code>rt_msghdr</code>, — это структура, содержащая адрес получателя. Эта команда может использоваться с любым семейством протоколов (предоставляющим таблицу маршрутизации), поскольку семейство адресов, в которое входит искомый адрес, указано в структуре адреса сокета.</p>
      <p>После отправки сообщения ядру мы с помощью функции <code>read</code> читаем ответ, формат которого показан на рис. 18.1 справа: структура <code>rt_msghdr</code>, за которой следует до четырех структур адреса сокета. Какая из четырех структур адреса сокета возвращается, зависит от записи в таблице маршрутизации. Мы сможем идентифицировать возвращаемую структуру адреса сокета по значению элемента <code>rtm_addrs</code> возвращаемой структуры <code>rt_msghdr</code>. Семейство каждой структуры адреса сокета указано в элементе <code>ss_family</code>, и как мы видели в наших предыдущих примерах, первый раз сообщение <code>RST_GET</code> содержало информацию о том, что адрес шлюза является структурой адреса сокета IPv4, а второй раз это была структура адреса сокета канального уровня.</p>
      <p>В листинге 18.3 показана первая часть нашей программы.</p>
      <p><strong>Листинг 18.3</strong>. Первая часть программы, запускающая команду RTM_GET на маршрутизирующем сокете</p>
      <p><code>//route/getrt.c</code></p>
      <p><code> 1 #include "unproute.h"</code></p>
      <empty-line/>
      <p><code> 2 #define BUFLEN (sizeof(struct rt_msghdr) + 512)</code></p>
      <p><code> 3 /* sizeof(struct sockaddr_in6) * 8 = 192 */</code></p>
      <p><code> 4 #define SEQ 9999</code></p>
      <empty-line/>
      <p><code> 5 int</code></p>
      <p><code> 6 main(int argc, char **argv)</code></p>
      <p><code> 7 {</code></p>
      <p><code> 8  int sockfd;</code></p>
      <p><code> 9  char *buf;</code></p>
      <p><code>10  pid_t pid;</code></p>
      <p><code>11  ssize_t n;</code></p>
      <p><code>12  struct rt_msghdr *rtm;</code></p>
      <p><code>13  struct sockaddr *sa, *rti_info[RTAX_MAX];</code></p>
      <p><code>14  struct sockaddr_in *sin;</code></p>
      <empty-line/>
      <p><code>15  if (argc != 2)</code></p>
      <p><code>16   err_quit("usage: getrt &lt;Ipaddress&gt;");</code></p>
      <empty-line/>
      <p><code>17  sockfd = Socket(AF_ROUTE, SOCK_RAW, 0); /* необходимы права</code></p>
      <p><code>                                привилегированного пользователя */</code></p>
      <empty-line/>
      <p><code>18  buf = Calloc(1, BUFLEN); /* инициализируется нулем */</code></p>
      <p><code>19  rtm = (struct rt_msghdr*)buf;</code></p>
      <p><code>20  rtm-&gt;rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in);</code></p>
      <p><code>21  rtm-&gt;rtm_version = RTM_VERSION;</code></p>
      <p><code>22  rtm-&gt;rtm_type = RTM_GET;</code></p>
      <p><code>23  rtm-&gt;rtm_addrs = RTA_DST;</code></p>
      <p><code>24  rtm-&gt;rtm_pid = pid = getpid();</code></p>
      <p><code>25  rtm-&gt;rtm_seq = SEQ;</code></p>
      <empty-line/>
      <p><code>26  sin = (struct sockaddr_in*)(rtm + 1);</code></p>
      <p><code>27  sin-&gt;sin_len = sizeof(struct sockaddr_in);</code></p>
      <p><code>28  sin-&gt;sin_family = AF_INET;</code></p>
      <p><code>29  Inet_pton(AF_INET, argv[1], &amp;sin-&gt;sin_addr);</code></p>
      <empty-line/>
      <p><code>30  Write(sockfd, rtm, rtm-&gt;rtm_msglen);</code></p>
      <p><code>31  do {</code></p>
      <p><code>32   n = Read(sockfd, rtm, BUFLEN);</code></p>
      <p><code>33  } while (rtm-&gt;rtm_type != RTM_GET || rtm-&gt;rtm_seq != SEQ ||</code></p>
      <p><code>34   rtm-&gt;rtm_pid != pid);</code></p>
      <p><code>1-3</code> Наш заголовочный файл <code>unproute.h</code> подключает некоторые необходимые файлы, а затем включает наш файл <code>unp.h</code>. Константа <code>BUFLEN</code> — это размер буфера, который мы размещаем в памяти для хранения нашего сообщения ядру вместе с ответом ядра. Нам необходимо место для одной структуры <code>rt_msghdr</code> и, возможно, восьми структур адреса сокета (максимальное число, которое может возвратиться через маршрутизирующий сокет). Поскольку структура адреса сокета IPv6 имеет размер 28 байт, то значения 512 нам более чем достаточно.</p>
      <subtitle>Создание маршрутизирующего сокета</subtitle>
      <p><code>17</code> Мы создаем символьный сокет в домене <code>AF_ROUTE</code>, что, как мы отмечали ранее, может потребовать прав привилегированного пользователя. Буфер размещается в памяти и инициализируется нулем.</p>
      <subtitle>Заполнение структуры rt_msghdr</subtitle>
      <p><code>18-25</code> Мы заполняем структуру <code>rt_msghdr</code> данными нашего запроса. В этой структуре хранится идентификатор процесса и порядковый номер, который мы выбираем. Мы сравним эти значения, когда будем искать правильный ответ.</p>
      <subtitle>Заполнение структуры адреса сокета адресом получателя</subtitle>
      <p><code>26-29</code> Следом за структурой <code>rt_msghdr</code> мы создаем структуру <code>sockaddr_in</code>, содержащую IPv4-адрес получателя, поиск которого будет проведен ядром в таблице маршрутизации. Все, что мы задаем — это длина адреса, семейство адреса и адрес.</p>
      <subtitle>Запись сообщения ядру (функция write) и чтение ответа (функция read)</subtitle>
      <p><code>30-34</code> Мы передаем сообщение ядру с помощью функции <code>write</code>, и с помощью функции read читаем ответ. Поскольку у других процессов могут быть открытые маршрутизирующие сокеты, а ядро передает копию всех маршрутизирующих сообщений всем маршрутизирующим сокетам, мы должны проверить тип сообщения, порядковый номер и идентификатор процесса, чтобы узнать, что полученное сообщение — это ожидаемое нами сообщение.</p>
      <p>Вторая часть этой программы показана в листинге 18.4. Она обрабатывает ответ.</p>
      <p><strong>Листинг 18.4</strong>. Вторая часть программы, запускающая команду RTM_GET на маршрутизирующем сокете</p>
      <p><code>//route/getrt.c</code></p>
      <p><code>35  rtm = (struct rt_msghdr*)buf;</code></p>
      <p><code>36  sa = (struct sockaddr*)(rtm + 1);</code></p>
      <p><code>37  get_rtaddrs(rtm-&gt;rtm_addrs, sa, rti_info);</code></p>
      <p><code>38  if ((sa = rti_infо[RTAX_DST]) != NULL)</code></p>
      <p><code>39   printf("dest: %s\n", Sock_ntop_host(sa, sa-&gt;sa_len));</code></p>
      <empty-line/>
      <p><code>40  if ((sa = rti_infо[RTAX_GATEWAY]) != NULL)</code></p>
      <p><code>41   printf("gateway: %s\n", Sock_ntop_host(sa, sa-&gt;sa_len));</code></p>
      <p><code>42  if ((sa = rti_info[RTAX_NETMASK]) != NULL)</code></p>
      <p><code>43   printf("netmask: %s\n", Sock_masktop(sa, sa-&gt;sa_len));</code></p>
      <empty-line/>
      <p><code>44  if ((sa = rti_info[RTAX_GENMASK]) != NULL)</code></p>
      <p><code>45   printf("genmask: %s\n", Sock_masktop(sa, sa-&gt;sa_len));</code></p>
      <empty-line/>
      <p><code>46  exit(0);</code></p>
      <p><code>47 }</code></p>
      <p><code>34-35</code> Указатель <code>rtm</code> указывает на структуру <code>rt_msghdr</code>, а указатель <code>sa</code> — на первую следующую за ней структуру адреса сокета.</p>
      <p><code>36</code> <code>rtm_addrs</code> — это битовая маска той из возможных восьми структур адреса сокета, которая следует за структурой <code>rt_msghdr</code>. Наша функция <code>get_rtaddrs</code> (она показана в следующем листинге), получив эту маску и указатель на первую структуру адреса сокета (<code>sa</code>), заполняет массив <code>rti_info</code> указателями на соответствующие структуры адреса сокета. В предположении, что ядро возвращает все четыре структуры адреса сокета, показанные на рис. 18.1, полученный в результате массив <code>rti_info</code> будет таким, как показано на рис. 18.2.</p>
      <image l:href="#img_111.png"/>
      <p><strong>Рис. 18.2</strong>. Структура rti_info, заполненная с помощью нашей функции get_rtaddrs</p>
      <p>Затем наша программа проходит массив <code>rti_info</code>, делая все, что ей нужно, с непустыми указателями массива.</p>
      <p><code>37-44</code> Каждый из присутствующих четырех возможных адресов выводится. Мы вызываем нашу функцию <code>sock_ntop_host</code> для вывода адреса получателя и адреса шлюза, но для вывода двух масок подсети вызываем нашу функцию <code>sock_masktop</code>. Эту новую функцию мы покажем далее.</p>
      <p>В листинге 18.5 показана наша функция <code>get_rtaddrs</code>, которую мы вызывали в листинге 18.4.</p>
      <p><strong>Листинг 18.5</strong>. Создание массива указателей на структуры адреса сокета в маршрутизирующем сообщении</p>
      <p><code>//libroute/get_rtaddrs.c</code></p>
      <p><code> 1 #include "unproute.h"</code></p>
      <empty-line/>
      <p><code> 2 /*</code></p>
      <p><code> 3  * Округляем 'а' до следующего значения, кратного 'size'</code></p>
      <p><code> 4  */</code></p>
      <p><code> 5 #define ROUNDUP(a, size) (((a) &amp; ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))</code></p>
      <empty-line/>
      <p><code> 6 /* Переходим к следующей структуре адреса сокета.</code></p>
      <p><code> 7  * Если sa_len равно 0, это значит, что</code></p>
      <p><code> 8  * размер выражен числом типа u_long).</code></p>
      <p><code> 9  */</code></p>
      <p><code>10 #define NEXT_SA(ap) ар = (SA*) \</code></p>
      <p><code>11  ((caddr_t)ар + (ap-&gt;sa_len ? ROUNDUP(ap-&gt;sa_len, sizeof(u_long)) : \</code></p>
      <p><code>12  sizeof(u_long)))</code></p>
      <empty-line/>
      <p><code>13 void</code></p>
      <p><code>14 get_rtaddrs(int addrs, SA *sa, SA **rti_info)</code></p>
      <p><code>15 {</code></p>
      <p><code>16  int i;</code></p>
      <empty-line/>
      <p><code>17  for (i = 0; i &lt; RTAX_MAX; i++) {</code></p>
      <p><code>18   if (addrs &amp; (1 &lt;&lt; i)) {</code></p>
      <p><code>19    rti_info[i] = sa;</code></p>
      <p><code>20    NEXT_SA(sa);</code></p>
      <p><code>21   } else</code></p>
      <p><code>22    rti_info[1] = NULL;</code></p>
      <p><code>23  }</code></p>
      <p><code>24 }</code></p>
      <subtitle>Цикл по восьми возможным указателям</subtitle>
      <p>Значение <code>RTAX_MAX</code> — максимальное число структур адреса сокета, возвращаемых от ядра в сообщении через маршрутизирующий сокет — равно 8. В цикле функции ведется поиск по каждой из восьми констант битовой маски <code>RTA_xxx</code> (см. табл. 18.2), которые могут быть присвоены элементам <code>rtm_addrs</code>, <code>ifm_addrs</code> и <code>ifam_addrs</code> структур, показанных в листинге 18.2. Если бит установлен, соответствующий элемент в массиве <code>rti_info</code> становится указателем на структуру адреса сокета; иначе элемент массива становится пустым указателем.</p>
      <subtitle>Переход к следующей структуре адреса сокета</subtitle>
      <p><code>2-12</code> Структуры адреса сокета имеют переменную длину, но в этом коде считается, что у каждой из них имеется поле <code>sa_len</code>, задающее длину структуры. Есть две сложности, с которыми придется столкнуться. Во-первых, маска подсети и маска клонирования могут возвращаться в структуре адреса сокета с нулевым значением поля <code>sa_len</code>, но на самом деле они занимают размер, представленный числом типа <code>unsigned long</code> (В главе 19 [128] обсуждается свойство клонирования таблицы маршрутизации 4.4BSD.) Это значение соответствует маске, состоящей только из нулевых битов, что мы видели в одном из приведенных выше примеров, когда для заданного по умолчанию маршрута маска подсети имела вид 0.0.0.0. Во-вторых, каждая структура адреса сокета может быть заполнена в конце таким образом, что следующая начнется на определенной границе, которая в данном случае соответствует значению типа <code>unsigned long</code> (например, 4-байтовая граница для 32-разрядной архитектуры). Хотя структуры <code>sockaddr</code>_in занимают 16 байт и не требуют заполнения, маски часто имеют в конце заполнение.</p>
      <p>Последняя функция, которую мы покажем в примере нашей программы, — это функция <code>sock_masktop</code>, представленная в листинге 18.6, возвращающая строку для одного из двух возможных значений масок. Маски хранятся в структурах адреса сокета. Элемент <code>sa_family</code> не задан, но имеется элемент <code>sa_len</code>, принимающий значения 0, 5, 6, 7 или 8 для 32-битовых масок IPv4. Когда длина больше нуля, действительная маска начинается с того же смещения от начала структуры, что и адрес IPv4 в структуре <code>sockaddr_in</code>: 4 байта от начала структуры (как показано на рис. 18.21 [128]), что соответствует элементу <code>sa_data[2]</code> общей структуры адреса сокета.</p>
      <p><strong>Листинг 18.6</strong>. Преобразование значения маски к формату представления</p>
      <p><code>//libroute/sock_masktop.c</code></p>
      <p><code> 1 #include "unproute.h"</code></p>
      <empty-line/>
      <p><code> 2 const char*</code></p>
      <p><code> 3 sock_masktop(SA *sa, socklen_t salen)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  static char str[INET6_ADDRSTRLEN];</code></p>
      <p><code> 6  unsigned char *ptr = &amp;sa-&gt;sa_data[2];</code></p>
      <empty-line/>
      <p><code> 7  if (sa-&gt;sa_len == 0)</code></p>
      <p><code> 8   return ("0.0.0.0");</code></p>
      <p><code> 9  else if (sa-&gt;sa_len == 5)</code></p>
      <p><code>10   snprintf(str, sizeof(str), '"%d.0.0.0", *ptr);</code></p>
      <p><code>11  else if (sa-&gt;sa_len == 6)</code></p>
      <p><code>12   snprintf(str, sizeof(str), "%d.%d.0.0", *ptr, *(ptr + 1));</code></p>
      <p><code>13  else if (sa-&gt;sa_len == 7)</code></p>
      <p><code>14   snprintf(str, sizeof(str), "%d.%d.%d.0", *ptr, *(ptr + 1), *(ptr + 2));</code></p>
      <p><code>15  else if (sa-&gt;sa_len == 8)</code></p>
      <p><code>16   snprintf(str, sizeof(str), "%d.%d.%d.%d",</code></p>
      <p><code>17    *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3));</code></p>
      <p><code>18  else</code></p>
      <p><code>19  snprintf(str, sizeof(str), "(unknown mask, len = %d, family = %d)",</code></p>
      <p><code>20   sa-&gt;sa_len, sa-&gt;sa_family);</code></p>
      <p><code>21  return (str);</code></p>
      <p><code>22 }</code></p>
      <p><code>7-21</code> Если длина равна нулю, то подразумевается маска 0.0.0.0. Если длина равна 5, хранится только первый байт 32-разрядной маски, а для оставшихся трех байтов подразумевается нулевое значение. Когда длина равна 8, хранятся все 4 байта маски.</p>
      <p>В этом примере мы хотим прочитать ответ ядра, поскольку он содержит информацию, которую мы ищем. Но в общем случае возвращаемое значение нашей функции <code>write</code> на маршрутизирующем сокете сообщает нам, успешно ли была выполнена команда. Если это вся необходимая нам информация, мы вызываем функцию <code>shutdown</code> со вторым аргументом <code>SHUT_RD</code>, чтобы предотвратить отправку ответа. Например, если мы удаляем маршрут, то возвращение нуля функцией <code>write</code> означает успешное выполнение, а если удалить маршрут не удалось, возвращается ошибка <code>ESRCH</code> [128, с. 608]. Аналогично, когда добавляется маршрут, возвращение ошибки <code>EEXIST</code> при выполнении функции <code>write</code> означает, что запись уже существует. В нашем примере из листинга 18.3 функция <code>write</code> возвращает ошибку <code>ESRCH</code>, если записи в таблице маршрутизации не существует (допустим, у нашего узла нет заданного по умолчанию маршрута).</p>
     </section>
    </section>
    <section>
     <title>
      <p>18.4. Операции функции sysctl</p>
     </title>
     <section>
      <p>Маршрутизирующие сокеты нужны нам главным образом для проверки таблицы маршрутизации и списка интерфейсов при помощи функции <code>sysctl</code>. В то время как создание маршрутизирующего сокета (символьного сокета в домене <code>AF_ROUTE</code>) требует прав привилегированного пользователя, проверить таблицу маршрутизации и список интерфейсов с помощью функции <code>sysctl</code> может любой процесс.</p>
      <p><code>#include &lt;sys/param.h&gt;</code></p>
      <p><code>#include &lt;sys/sysctl.h&gt;</code></p>
      <empty-line/>
      <p><code>int sysctl(int *<emphasis>name</emphasis>, u_int <emphasis>namelen</emphasis>, void *<emphasis>oldp</emphasis>, size_t *<emphasis>oldlenp</emphasis>,</code></p>
      <p><code> void *<emphasis>newp</emphasis>, size_t <emphasis>Inewlen</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: 0 в случае успешного выполнения</emphasis></code></p>
      <p>Эта функция использует имена, похожие на имена базы управляющей информации (Management Information Base, MIB) простого протокола управления сетью (Simple Network Management Protocol, SNMP). В главе 25 [111] подробно описываются SNMP и его MIB. Эти имена являются иерархическими.</p>
      <p>Аргумент <code>name</code> — это массив целых чисел, задающий имя, a <code>namelen</code> задает число элементов массива. Первый элемент массива определяет, какой подсистеме ядра направлен запрос. Второй элемент определяет некую часть этой подсистемы, и т.д. На рис. 18.3 показана иерархическая организация с некоторыми константами, используемыми на первых трех уровнях.</p>
      <image l:href="#img_112.png"/>
      <p><strong>Рис. 18.3</strong>. Иерархическая организация имен функции sysctl</p>
      <p>Для получения значений используется аргумент <code>oldp</code>. Он указывает на буфер, в котором ядро сохраняет значение. Аргумент <code>oldenp</code> имеет тип «значение-результат»: когда функция вызывается, значение, на которое указывает <code>oldenp</code>, задает размер этого буфера, а по завершении функции значением этого аргумента становится количество данных, сохраненных ядром в буфере. Если размера буфера недостаточно, возвращается ошибка <code>ENOMEM</code>. В специальном случае <code>oldp</code> может быть пустым указателем, a <code>oldenp</code> — непустым указателем, и тогда ядро определяет, сколько данных возвратилось бы при вызове, сообщая это значение через <code>oldenp</code>.</p>
      <p>Чтобы установить новое значение, используется аргумент <code>newp</code>, указывающий на буфер размера <code>newlen</code>. Если новое значение не задается, <code>newp</code> должен быть пустым указателем, a <code>newlen</code> должен быть равен нулю.</p>
      <p>В руководстве (man) по применению функции <code>sysctl</code> подробно описывается различная системная информация, которую можно получить с помощью этой функции: информация о файловых системах, виртуальной памяти, ограничениях ядра, аппаратных характеристиках и т.д. Нас интересует сетевая подсистема, на которую указывает первый элемент массива name, равный <code>CTL_NET</code> (константы <code>CTL_<emphasis>xxx</emphasis></code> определяются в заголовочном файле <code>&lt;sys/sysctl.h&gt;</code>). Тогда второй элемент может быть одним из перечисленных ниже.</p>
      <p>&#9632; <code>AF_INET</code>. Получение или установка переменных, влияющих на протоколы Интернета. Следующий уровень с помощью одной из констант <code>IPROTO_<emphasis>xxx</emphasis></code> задает протокол. BSD/OS 3.0 предоставляет на этом уровне около 30 переменных, управляющих такими свойствами, как генерация ядром переадресации ICMP, использование параметров TCP из RFC 1323, отправка контрольных сумм UDP и т.д. Пример подобного применения функции <code>sysctl</code> мы покажем в конце этого раздела.</p>
      <p>&#9632; <code>AF_LINK</code>. Получение или установка информации канального уровня, такой как число интерфейсов PPP.</p>
      <p>&#9632; <code>AF_ROUTE</code>. Возвращение информации либо о таблице маршрутизации, либо о списке интерфейсов. Мы вскоре опишем эту информацию.</p>
      <p>&#9632; <code>AF_UNSPEC</code>. Получение или установка некоторых переменных уровня сокета, таких как максимальный размер буфера отправки или приема сокета.</p>
      <p>Когда вторым элементом массива <code>name</code> является <code>AF_ROUTE</code>, третий элемент (номер протокола) всегда нулевой (поскольку протоколы внутри семейства <code>AF_ROUTE</code> отличаются от протоколов, например, в семействе <code>AF_INET</code>), четвертый элемент — это семейство адресов, а пятый и шестой элементы задают выполняемые действия. Вся эта информация обобщается в табл. 18.3.</p>
      <empty-line/>
      <p><strong>Таблица 18.3</strong>. Информация функции sysctl, возвращаемая для маршрутизирующего домена</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">name[]</th>
        <th align="left" valign="top">Возвращает таблицу</th>
        <th align="left" valign="top">Возвращает кэш APR маршрутизации</th>
        <th align="left" valign="top">Возвращает список интерфейсов</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">0</td>
        <td align="left" valign="top">CTL_NET</td>
        <td align="left" valign="top">CTL_NET</td>
        <td align="left" valign="top">CTL_NET</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">1</td>
        <td align="left" valign="top">AF_ROUTE</td>
        <td align="left" valign="top">AF_ROUTE</td>
        <td align="left" valign="top">AF_ROUTE</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">2</td>
        <td align="left" valign="top">0</td>
        <td align="left" valign="top">0</td>
        <td align="left" valign="top">0</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">3</td>
        <td align="left" valign="top">AF_INET</td>
        <td align="left" valign="top">AF_INET</td>
        <td align="left" valign="top">AF_INET</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">4</td>
        <td align="left" valign="top">NET_RT_DUMP</td>
        <td align="left" valign="top">NET_RT_FLAGS</td>
        <td align="left" valign="top">NET_RT_IFLIST</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">5</td>
        <td align="left" valign="top">0</td>
        <td align="left" valign="top">RTF_LLINFO</td>
        <td align="left" valign="top">0</td>
       </tr>
      </table>
      <p>Поддерживаются три операции, задаваемые элементом <code>name[4]</code>. (Константы <code>NET_RT_<emphasis>xxx</emphasis></code> определяются в заголовочном файле <code>&lt;sys/socket.h&gt;</code>.) Информация возвращается через указатель <code>oldp</code> при вызове функции <code>sysctl</code>. Этот буфер содержит переменное число сообщений <code>RTM_<emphasis>xxx</emphasis></code> (см. табл. 18.1).</p>
      <p>1. Операция <code>NET_RT_DUMP</code> возвращает таблицу маршрутизации для семейства адресов, заданного элементом <code>name[3]</code>. Если задано нулевое семейство адресов, возвращаются таблицы маршрутизации для всех семейств адресов.</p>
      <image l:href="#img_113.png"/>
      <p><strong>Рис. 18.4</strong>. Информация возвращаемая функцией sysctl для команд CTL_NET и NET_RT_IFLIST</p>
      <p>Таблица маршрутизации возвращается как переменное число сообщений <code>RTM_GET</code>, причем за каждым сообщением следует до четырех структур адреса сокета: получатель, шлюз, маска сети и маска клонирования записи в таблице маршрутизации. Пример такого сообщения мы показали в правой части рис. 18.1, а в нашем коде в листинге 18.4 проводится анализ одного из сообщений. В результате применения этой операции функции <code>sysctl</code> ядром возвращается одно или несколько таких сообщений.</p>
      <p>2. Операция <code>NET_RT_FLAGS</code> возвращает таблицу маршрутизации для семейства адресов, заданного элементом <code>name[3]</code>, но учитываются только те записи таблицы маршрутизации, для которых значение флага <code>RTF_<emphasis>xxx</emphasis></code> равно указанному в элементе <code>name[5]</code>. У всех записей кэша ARP в таблице маршрутизации установлен бит флага <code>RTF_LLINFO</code>.</p>
      <p>Информация возвращается в том же формате, что и в предыдущем пункте.</p>
      <p>3. Операция <code>NET_RT_IFLIST</code> возвращает информацию обо всех сконфигурированных интерфейсах. Если элемент <code>name[5]</code> ненулевой, это номер индекса интерфейса и возвращается информация только об этом интерфейсе. (Более подробно об индексах интерфейсов мы поговорим в разделе 18.6.) Все адреса, присвоенные каждому интерфейсу, также возвращаются, и если элемент <code>name[3]</code> ненулевой, возвращаются только адреса для семейства адресов, указанного в этом элементе.</p>
      <p>Для каждого интерфейса возвращается по одному сообщению <code>RTM_IFINFO</code>, за которым следует одно сообщение <code>RTM_NEWADDR</code> для каждого адреса, заданного для интерфейса. За сообщением RTM_<code>IFINFO</code> следует по одной структуре адреса сокета канального уровня, а за каждым сообщением <code>RTM_NEWADDR</code> — до трех структур адреса сокета: адрес интерфейса, маска сети и широковещательный адрес. Эти два сообщения представлены на рис. 18.4.</p>
     </section>
     <section>
      <title>
       <p>Пример: определяем, включены ли контрольные суммы UDP</p>
      </title>
      <p>Теперь мы приведем простой пример использования функции <code>sysctl</code> с протоколами Интернета для проверки, включены ли контрольные суммы UDP. Некоторые приложения UDP (например, BIND) проверяют при запуске, включены ли контрольные суммы UDP, и если нет, пытаются включить их. Для того чтобы включить подобное свойство, требуются права привилегированного пользователя, но мы сейчас просто проверим, включено это свойство или нет. В листинге 18.7 представлена наша программа.</p>
      <p><strong>Листинг 18.7</strong>. Проверка включения контрольных сумм</p>
      <p><code>//route/checkudpsum.c</code></p>
      <p><code> 1 #include "unproute.h"</code></p>
      <p><code> 2 #include &lt;netinet/udp.h&gt;</code></p>
      <p><code> 3 #include &lt;netinet/ip_var.h&gt;</code></p>
      <p><code> 4 #include &lt;netinet/udp_var.h&gt; /* для констант UDPCTL_xxx */</code></p>
      <empty-line/>
      <p><code> 5 int</code></p>
      <p><code> 6 main(int argc, char **argv)</code></p>
      <p><code> 7 {</code></p>
      <p><code> 8  int mib[4], val;</code></p>
      <p><code> 9  size_t len;</code></p>
      <empty-line/>
      <p><code>10  mib[0] = CTL_NET;</code></p>
      <p><code>11  mib[1] = AF_INET;</code></p>
      <p><code>12  mib[2] = IPPROTO_UDP;</code></p>
      <p><code>13  mib[3] = UDPCTL_CHECKSUM;</code></p>
      <empty-line/>
      <p><code>14  len = sizeof(val);</code></p>
      <p><code>15  Sysctl(mib, 4, &amp;val, &amp;len, NULL, 0);</code></p>
      <p><code>16  printf("udp checksum flag: %d\n", val);</code></p>
      <empty-line/>
      <p><code>17  exit(0);</code></p>
      <p><code>18 }</code></p>
      <subtitle>Включение системных заголовков</subtitle>
      <p><code>2-4</code> Следует включить заголовочный файл <code>&lt;netinet/udp_var.h&gt;</code>, чтобы получить определение констант UDP функции <code>sysctl</code>. Для него требуются два других заголовка.</p>
      <subtitle>Вызов функции sysctl</subtitle>
      <p><code>10-16</code> Мы размещаем в памяти массив целых чисел с четырьмя элементами и храним константы, соответствующие иерархии, показанной на рис. 18.3. Поскольку мы только получаем переменную и не присваиваем ей значение, аргумент <code>newp</code> функции <code>sysctl</code> мы задаем как пустой указатель, и поэтому аргумент <code>newp</code> этой функции имеет нулевое значение, <code>oldp</code> указывает на нашу целочисленную переменную, в которую сохраняется результат, a <code>oldenp</code> указывает на переменную типа «значение- результат», хранящую размер этого целого числа. Мы выводим либо 0 (отключено), либо 1 (включено).</p>
     </section>
    </section>
    <section>
     <title>
      <p>18.5. Функция get_ifi_info (повтор)</p>
     </title>
     <p>Вернемся к примеру из раздела 17.6 — возвращение всех активных интерфейсов в виде связного списка структур <code>ifi_info</code> (см. листинг 17.2). Программа <code>prifinfo</code> остается без изменений (см. листинг 17.3), но теперь мы покажем версию функции <code>get_ifi_info</code>, использующую функцию <code>sysctl</code> вместо вызова <code>SIOCGIFCONF</code> функции <code>ioctl</code> в листинге 17.4.</p>
     <p>Сначала в листинге 18.8 мы представим функцию <code>net_rt_iflist</code>. Эта функция вызывает функцию <code>sysctl</code> с командой <code>NET_RT_IFLIST</code>, чтобы возвратить список интерфейсов для заданного семейства адресов.</p>
     <p><strong>Листинг 18.8</strong>. Вызов функции sysctl для возвращения списка интерфейсов</p>
     <p><code>//libroute/net_rt_iflist.c</code></p>
     <p><code> 1 #include "unproute.h"</code></p>
     <empty-line/>
     <p><code> 2 char*</code></p>
     <p><code> 3 net_rt_iflist(int family, int flags, size_t *lenp)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int mib[6];</code></p>
     <p><code> 6  char *buf;</code></p>
     <empty-line/>
     <p><code> 7  mib[0] = CTL_NET;</code></p>
     <p><code> 8  mib[1] = AF_ROUTE;</code></p>
     <p><code> 9  mib[2] = 0;</code></p>
     <p><code>10  mib[3] = family; /* только адреса этого семейства */</code></p>
     <p><code>11  mib[4] = NET_RT_IFLIST;</code></p>
     <p><code>12  mib[5] = flags; /* индекс интерфейса или 0.*/</code></p>
     <p><code>13  if (sysctl(mib, 6, NULL, lenp, NULL, 0) &lt; 0)</code></p>
     <p><code>14   return (NULL);</code></p>
     <empty-line/>
     <p><code>15  if ((buf = malloc(*lenp)) == NULL)</code></p>
     <p><code>16   return (NULL);</code></p>
     <p><code>17  if (sysctl(mib, 6, buf, lenp, NULL, 0) &lt; 0) {</code></p>
     <p><code>18   free(buf);</code></p>
     <p><code>19   return (NULL);</code></p>
     <p><code>20  }</code></p>
     <p><code>21  return (buf);</code></p>
     <p><code>22 }</code></p>
     <p><code>7-14</code> Инициализируется массив <code>mib</code>, как показано в табл. 18.3, для возвращения списка интерфейсов и всех сконфигурированных адресов заданного семейства. Затем функция <code>sysctl</code> вызывается дважды. В первом вызове функции третий аргумент нулевой, в результате чего в переменной, на которую указывает <code>lenp</code>, возвращается размер буфера, требуемый для хранения всей информации об интерфейсе.</p>
     <p><code>15-21</code> Затем в памяти выделяется место для буфера, и функция <code>sysctl</code> вызывается снова, на этот раз с ненулевым третьим аргументом. При этом переменная, на которую указывает <code>lenp</code>, содержит при завершении функции число, равное количеству информации, хранимой в буфере, и эта переменная размещается в памяти вызывающим процессом. Указатель на буфер также возвращается вызывающему процессу.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Поскольку размер таблицы маршрутизации или число интерфейсов может изменяться между двумя вызовами функции sysctl, значение, возвращаемое при первом вызове, содержит поправочный множитель 10% [128, с. 639-640].</p>
     </cite>
     <p>В листинге 18.9 показана первая половина функции <code>get_ifi_info</code>.</p>
     <p><strong>Листинг 18.9</strong>. Функция get_ifi_info, первая половина</p>
     <p><code>//route/get_ifi_info.c</code></p>
     <p><code> 3 struct ifi_info *</code></p>
     <p><code> 4 get_ifi_info(int family, int doaliases)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  int flags;</code></p>
     <p><code> 7  char *buf, *next, *lim;</code></p>
     <p><code> 8  size_t len;</code></p>
     <p><code> 9  struct if_msghdr *ifm;</code></p>
     <p><code>10  struct ifa_msghdr *ifam;</code></p>
     <p><code>11  struct sockaddr *sa, *rti_info[RTAX_MAX];</code></p>
     <p><code>12  struct sockaddr_dl *sdl;</code></p>
     <p><code>13  struct ifi_info *ifi, *ifisave, *ifihead, **ifipnext;</code></p>
     <p><code>14  buf = Net_rt_iflist(family, 0, &amp;len);</code></p>
     <p><code>15  ifihead = NULL;</code></p>
     <p><code>16  ifipnext = &amp;ifihead;</code></p>
     <empty-line/>
     <p><code>17  lim = buf + len;</code></p>
     <p><code>18  for (next = buf; next &lt; lim; next += ifm-&gt;ifm_msglen) {</code></p>
     <p><code>19   ifm = (struct if_msghdr*)next;</code></p>
     <p><code>20   if (ifm-&gt;ifm_type = RTM_IFINFO) {</code></p>
     <p><code>21    if (((flags = ifm-&gt;ifm_flags) &amp; IFF_UP) == 0)</code></p>
     <p><code>22     continue; /* игнорируем, если интерфейс не активен */</code></p>
     <empty-line/>
     <p><code>23    sa = (struct sockaddr*)(ifm + 1);</code></p>
     <p><code>24    get_rtaddrs(ifm-&gt;ifm_addrs, sa, rti_info);</code></p>
     <p><code>25    if ((sa = rti_info[RTAX_IFP]) != NULL) {</code></p>
     <p><code>26     ifi = Calloc(1, sizeof(struct ifi_info));</code></p>
     <p><code>27     *ifipnext = ifi; /* предыдущий указатель указывал на эту</code></p>
     <p><code>                           структуру */</code></p>
     <p><code>28     ifipnext = &amp;ifi-&gt;ifi_next; /* указатель на следующую структуру */</code></p>
     <empty-line/>
     <p><code>29     ifi-&gt;ifi_flags = flags;</code></p>
     <p><code>30     if (sa-&gt;sa_family == AF_LINK) {</code></p>
     <p><code>31      sdl = (struct sockaddr_dl*)sa;</code></p>
     <p><code>32      ifi-&gt;ifi_index = sdl-&gt;sdl_index;</code></p>
     <p><code>33      if (sdl-&gt;sdl_nlen &gt; 0)</code></p>
     <p><code>34       snprintf(ifi-&gt;ifi_name, IFI_NAME, "%*s",</code></p>
     <p><code>35        sdl-&gt;sdl_nlen, &amp;sdl-&gt;sdl_data[0]);</code></p>
     <p><code>36      else</code></p>
     <p><code>37       snprintf(ifi-&gt;ifi_name, IFI_NAME, "index %d",</code></p>
     <p><code>38        sdl-&gt;sdl_index);</code></p>
     <empty-line/>
     <p><code>39      if ((ifi-&gt;ifi_hlen = sdl-&gt;sdl_alen) &gt; 0)</code></p>
     <p><code>40       memcpy(ifi-&gt;ifi_haddr, LLADDR(sdl),</code></p>
     <p><code>41      min(IFI_HADDR, sdl-&gt;sdl_alen));</code></p>
     <p><code>42     }</code></p>
     <p><code>43    }</code></p>
     <p><code>6-14</code> Мы объявляем локальные переменные и затем вызываем нашу функцию <code>net_rt_iflist</code>.</p>
     <p><code>17-19</code> Цикл <code>for</code> — это цикл по всем сообщениям маршрутизации, попадающим в буфер в результате выполнения функции <code>sysctl</code>. Мы предполагаем, что сообщение — это структура <code>if_msghdr</code>, и рассматриваем поле <code>ifm_type</code> (вспомните, что первые три элемента трех структур идентичны, поэтому все равно, какую из трех структур мы используем для просмотра типа элемента).</p>
     <subtitle>Проверка, включен ли интерфейс</subtitle>
     <p><code>20-22</code> Для каждого интерфейса возвращается структура <code>RTM_IFINFO</code>. Если интерфейс не активен, он игнорируется.</p>
     <subtitle>Определение, какие структуры адреса сокета присутствуют</subtitle>
     <p><code>23-24</code> <code>sa</code> указывает на первую структуру адреса сокета, следующую за структурой <code>if_msghdr</code>. Наша функция get_rtaddrs инициализирует массив <code>rti_info</code> в зависимости от того, какие структуры адреса сокета присутствуют.</p>
     <p><strong>Обработка имени интерфейса</strong></p>
     <p><code>25-42</code> Если присутствует структура адреса сокета с именем интерфейса, в памяти размещается структура <code>ifi_info</code> и хранятся флаги интерфейса. Предполагаемым семейством этой структуры адреса сокета является <code>AF_LINK</code>, что означает структуру адреса сокета канального уровня. Если элемент sdl_<code>nlen</code> ненулевой, имя интерфейса копируется в структуру <code>ifi_info</code>. В противном случае в качестве имени хранится строка, содержащая индекс интерфейса. Если элемент <code>sdl_alen</code> ненулевой, аппаратный адрес (например, адрес Ethernet) копируется в структуру <code>ifi_info</code>, а его длина также возвращается как <code>ifi_hlen</code>.</p>
     <p>В листинге 18.10 показана вторая часть нашей функции <code>get_ifi_info</code>, которая возвращает IP-адреса для интерфейса.</p>
     <p><strong>Листинг 18.10</strong>. Функция get_ifi_info, вторая часть</p>
     <p><code>//route/get_ifi_info.c</code></p>
     <p><code>44   } else if (ifm-&gt;ifm_type == RTM_NEWADDR) {</code></p>
     <p><code>45    if (ifi-&gt;ifi_addr) { /* уже имеется IP-адрес для интерфейса */</code></p>
     <p><code>46     if (doaliases == 0)</code></p>
     <p><code>47      continue;</code></p>
     <empty-line/>
     <p><code>48     /* у нас имеется новый IP-адрес для существующего интерфейса */</code></p>
     <p><code>49     ifisave = ifi;</code></p>
     <p><code>50     ifi = Calloc(1, sizeof(struct ifi_info));</code></p>
     <p><code>51     *ifipnext = ifi; /* предыдущий указатель указывал на эту</code></p>
     <p><code>                           структуру */</code></p>
     <p><code>52     ifipnext = &amp;ifi-&gt;ifi_next; /* указатель на следующую структуру */</code></p>
     <p><code>53     ifi-&gt;ifi_flags = ifi_save-&gt;ifi_flags;</code></p>
     <p><code>54     ifi-&gt;ifi_index = ifisave-&gt;ifi_index;</code></p>
     <p><code>55     ifi-&gt;ifi_hlen = ifisave-&gt;ifi_hlen;</code></p>
     <p><code>56     memcpy(ifi-&gt;ifi_name, ifisave-&gt;ifi_name, IFI_NAME);</code></p>
     <p><code>57     memcpy(ifi-&gt;ifi_haddr, ifisave-&gt;ifi_haddr, IFI_HADDR);</code></p>
     <p><code>58    }</code></p>
     <empty-line/>
     <p><code>59    ifam = (struct ifa_msghdr*)next;</code></p>
     <p><code>60    sa = (struct sockaddr*)(ifam + 1);</code></p>
     <p><code>61    get_rtaddrs(ifam-&gt;ifam_addrs, sa, rti_info);</code></p>
     <empty-line/>
     <p><code>62    if ((sa = rti_infо[RTAX_IFA]) != NULL) {</code></p>
     <p><code>63     ifi-&gt;ifi_addr = Calloc(1, sa-&gt;sa_len);</code></p>
     <p><code>64     memcpy(ifi-&gt;ifi_addr, sa, sa-&gt;sa_len);</code></p>
     <p><code>65    }</code></p>
     <empty-line/>
     <p><code>66    if ((flags &amp; IFF_BROADCAST) &amp;&amp; (sa = rti_infо[RTAX_BRD]) |= NULL) {</code></p>
     <p><code>67     ifi-&gt;ifi_brdaddr = Calloc(1, sa-&gt;sa_len);</code></p>
     <p><code>68     memcpy(ifi-&gt;ifi_brdaddr, sa, sa-&gt;sa_len);</code></p>
     <p><code>69    }</code></p>
     <p><code>70    if ((flags &amp; IFF_POINTOPOINT) &amp;&amp;</code></p>
     <p><code>71     (sa = rti_infо[RTAX_BRD]) != NULL) {</code></p>
     <p><code>72     ifi-&gt;ifi_dstaddr = Calloc(1, sa-&gt;sa_len);</code></p>
     <p><code>73     memcpy(ifi-&gt;ifi_dstaddr, sa, sa-&gt;sa_len);</code></p>
     <p><code>74    }</code></p>
     <p><code>75   } else</code></p>
     <p><code>76    err_quit("unexpected message type %d", ifm-&gt;ifm_type);</code></p>
     <p><code>77  }</code></p>
     <p><code>78  /* "ifihead" указывает на первую структуру в связном списке */</code></p>
     <p><code>79  return (ifihead); /* указатель на первую структуру в связном списке */</code></p>
     <p><code>80 }</code></p>
     <subtitle>Возвращение IP-адресов</subtitle>
     <p><code>44-65</code> Сообщение <code>RTM_NEWADDR</code> возвращается функцией <code>sysctl</code> для каждого адреса, связанного с интерфейсом: для первичного адреса и для всех альтернативных имен (псевдонимов). Если мы уже заполнили IP-адрес для этого интерфейса, то мы имеем дело с альтернативным именем. Поэтому если вызывающему процессу нужен адрес псевдонима, мы должны выделить память для другой структуры <code>ifi_info</code>, скопировать заполненные поля и затем заполнить возвращенный адрес.</p>
     <subtitle>Возвращение широковещательного адреса и адреса получателя</subtitle>
     <p><code>66-75</code> Если интерфейс поддерживает широковещательную передачу, возвращается широковещательный адрес, а если интерфейс является интерфейсом типа «точка-точка», возвращается адрес получателя.</p>
    </section>
    <section>
     <title>
      <p>18.6. Функции имени и индекса интерфейса</p>
     </title>
     <section>
      <p>Документ RFC 3493 [36] определяет четыре функции, обрабатывающие имена и индексы интерфейсов. Эти четыре функции используются во многих случаях, когда необходимо описать интерфейс. Они были предложены в процессе разработки API IPv6 (главы 21 и 27), однако индексы интерфейсов имеются и в API IPv4 (например, в вызове <code>IP_RECVIF</code> или <code>AF_LINK</code> для маршрутизирующего сокета). Основной принцип, объявляемый в этом документе, состоит в том, что каждый интерфейс имеет уникальное имя и уникальный положительный индекс (нуль в качестве индекса никогда не используется).</p>
      <p><code>#include &lt;net/if.h&gt;</code></p>
      <empty-line/>
      <p><code>unsigned int if_nametoindex(const char *<emphasis>ifname</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: положительный индекс интерфейса в случае успешного выполнения, 0 в случае ошибки</emphasis></code></p>
      <empty-line/>
      <p><code>char *if_indextoname(unsigned int <emphasis>ifindex</emphasis>, char *<emphasis>ifname</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: указатель на имя интерфейса в случае успешного выполнения, NULL в случае ошибки</emphasis></code></p>
      <empty-line/>
      <p><code>struct if_nameindex *if_nameindex(void);</code></p>
      <p><code><emphasis>Возвращает: непустой указатель в случае успешного выполнения, NULL в случае ошибки</emphasis></code></p>
      <empty-line/>
      <p><code>void if_freenameindex(struct if_nameindex *<emphasis>Iptr</emphasis>);</code></p>
      <p>Функция <code>if_nametoindex</code> возвращает индекс интерфейса, имеющего имя <code>ifname</code>. Функция <code>if_indextoname</code> возвращает указатель на имя интерфейса, если задан его индекс <code>ifindex</code>. Аргумент <code>ifname</code> указывает на буфер размера <code>IFNAMSIZ</code> (определяемый в заголовочном файле &lt;net/if.h&gt; из листинга 17.1), который вызывающий процесс должен выделить для хранения результата, и этот указатель возвращается в случае успешного выполнения функции <code>if_indextoname</code>.</p>
      <p>Функция <code>if_nameindex</code> возвращает указатель на массив структур <code>if_nameindex</code>:</p>
      <p><code>struct if_nameindex {</code></p>
      <p><code> unsigned int if_index; /* 1, 2. ... */</code></p>
      <p><code> char *if_name; /* имя, завершаемое нулем: "le0", ... */</code></p>
      <p><code>};</code></p>
      <p>Последняя запись в этом массиве содержит структуру с нулевым индексом <code>if_index</code> и с пустым указателем <code>ifname</code>. Память для этого массива, а также для имен, на которые указывают элементы массива, выделяется динамически и освобождается при вызове функции <code>if_freenameindex</code>.</p>
      <p>Теперь мы представим реализацию этих четырех функций с использованием маршрутизирующих сокетов.</p>
     </section>
     <section>
      <title>
       <p>Функция if_nametoindex</p>
      </title>
      <p>В листинге 18.11 показана функция <code>if_nametoindex</code>.</p>
      <p><strong>Листинг 18.11</strong>. Возвращение индекса интерфейса по его имени</p>
      <p><code>//libroute/if_nametoindex.c</code></p>
      <p><code> 1 #include "unpifi.h"</code></p>
      <p><code> 2 #include "unproute.h"</code></p>
      <empty-line/>
      <p><code> 3 unsigned int</code></p>
      <p><code> 4 if_nametoindex(const char *name)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  unsigned int idx, namelen;</code></p>
      <p><code> 7  char *buf, *next, *lim;</code></p>
      <p><code> 8  size_t len;</code></p>
      <p><code> 9  struct if_msghdr *ifm;</code></p>
      <p><code>10  struct sockadd *sa, *rti_info[RTAX_MAX];</code></p>
      <p><code>11  struct sockaddr_dl *sdl;</code></p>
      <empty-line/>
      <p><code>12  if ((buf = net_rt_iflist(0, 0, &amp;len)) == NULL)</code></p>
      <p><code>13   return(0);</code></p>
      <empty-line/>
      <p><code>14  namelen = strlen(name);</code></p>
      <p><code>15  lim = buf + len;</code></p>
      <p><code>16  for (next = buf; next &lt; lim; next += ifm-&gt;ifm_msglen) {</code></p>
      <p><code>17   ifm = (struct if_msghdr*)next;</code></p>
      <p><code>18   if (ifm-&gt;ifm_type == RTM_IFINFO) {</code></p>
      <p><code>19    sa = (struct sockaddr*)(ifm + 1);</code></p>
      <p><code>20    get_rtaddrs(ifm-&gt;ifm_addrs, sa, rti_info);</code></p>
      <p><code>21    if ((sa = rti_infо[RTAX_IFP]) != NULL) {</code></p>
      <p><code>22     if (sa-&gt;sa_family == AF_LINK) {</code></p>
      <p><code>23      sdl = (struct sockaddr_dl*)sa;</code></p>
      <p><code>24      if (sdl-&gt;sdl_nlen == namelen</code></p>
      <p><code>25       &amp;&amp; strncmp(&amp;sdl-&gt;sdl_data[0], name,</code></p>
      <p><code>26       sdl-&gt;sdl_nlen) == 0) {</code></p>
      <p><code>27       idx = sdl-&gt;sdl_index; /* сохранение перед</code></p>
      <p><code>                                  вызовом free */</code></p>
      <p><code>28       free(buf);</code></p>
      <p><code>29       return(idx);</code></p>
      <p><code>30      }</code></p>
      <p><code>31     }</code></p>
      <p><code>32    }</code></p>
      <empty-line/>
      <p><code>33   }</code></p>
      <p><code>34  }</code></p>
      <p><code>35  free(buf);</code></p>
      <p><code>36  return(0); /* индекс для имени не найден */</code></p>
      <p><code>37 }</code></p>
      <subtitle>Получение списка интерфейсов</subtitle>
      <p><code>12-13</code> Наша функция <code>net_rt_iflist</code> возвращает список интерфейсов.</p>
      <subtitle>Обработка только сообщений RTM_IFINFO</subtitle>
      <p><code>17-30</code> Мы обрабатываем сообщения в буфере (см. рис. 18.4) в поисках сообщений типа <code>RTM_IFINFO</code>. Найдя такое сообщение, мы вызываем нашу функцию <code>get_rtaddrs</code>, чтобы установить указатели на структуры адреса сокета, а если присутствует структура имени интерфейса (элемент <code>RTAX_IFP</code> массива <code>rti_info</code>), то имя интерфейса сравнивается с аргументом.</p>
     </section>
     <section>
      <title>
       <p>Функция if_indextoname</p>
      </title>
      <p>Следующая функция, <code>if_indextoname</code>, показана в листинге 18.12.</p>
      <p><strong>Листинг 18.12</strong>. Возвращение имени интерфейса по его индексу</p>
      <p><code>libroute/if_indextoname.c</code></p>
      <p><code> 1 #include "unpifi.h"</code></p>
      <p><code> 2 #include "unproute.h"</code></p>
      <empty-line/>
      <p><code> 3 char*</code></p>
      <p><code> 4 if_indextoname(unsigned int index, char *name)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  char *buf, *next, *lim;</code></p>
      <p><code> 7  size_t len;</code></p>
      <p><code> 8  struct if_msghdr *ifm;</code></p>
      <p><code> 9  struct sockaddr *sa, *rti_info[RTAX_MAX];</code></p>
      <p><code>10  struct sockaddr_dl *sdl;</code></p>
      <empty-line/>
      <p><code>11  if ((buf = net_rt_iflist(0, index, &amp;len)) == NULL)</code></p>
      <p><code>12   return (NULL);</code></p>
      <empty-line/>
      <p><code>13  lim = buf + len;</code></p>
      <p><code>14  for (next = buf; next &lt; lim; next += ifm-&gt;ifm_msglen) {</code></p>
      <p><code>15   ifm = (struct if_msghdr*)next;</code></p>
      <p><code>16   if (ifm-&gt;ifm_type == RTM_IFINFO) {</code></p>
      <p><code>17    sa = (struct sockaddr*)(ifm + 1);</code></p>
      <p><code>18    get_rtaddrs(ifm-&gt;ifm_addrs, sa, rti_info);</code></p>
      <p><code>19    if ((sa = rti_info[RTAX_IFP]) != NULL) {</code></p>
      <p><code>20     if (sa-&gt;sa_family == AF_LINK) {</code></p>
      <p><code>21      sdl = (struct sockaddr_dl*)sa;</code></p>
      <p><code>22      if (sdl-&gt;sdl_index == index) {</code></p>
      <p><code>23       int slen = min(IFNAMSIZ - 1, sdl-&gt;sdl_nlen);</code></p>
      <p><code>24       strncpy(name, sdl-&gt;sdl_data, slen);</code></p>
      <p><code>25       name[slen] = 0; /* завершающий нуль */</code></p>
      <p><code>26       free(buf);</code></p>
      <p><code>27       return (name);</code></p>
      <p><code>28      }</code></p>
      <p><code>29     }</code></p>
      <p><code>30    }</code></p>
      <p><code>31   }</code></p>
      <p><code>32  }</code></p>
      <p><code>33  free(buf);</code></p>
      <p><code>34  return (NULL); /* нет соответствия индексу */</code></p>
      <p><code>35 }</code></p>
      <p>Эта функция практически идентична предыдущей, но вместо поиска имени интерфейса мы сравниваем индекс интерфейса с аргументом вызывающего процесса. Кроме того, второй аргумент нашей функции <code>net_rt_iflist</code> — это заданный индекс, поэтому результат должен содержать информацию только для определенного интерфейса. Когда обнаруживается совпадение, возвращается имя интерфейса, к которому добавляется завершающий нуль.</p>
     </section>
     <section>
      <title>
       <p>Функция if_nameindex</p>
      </title>
      <p>Следующая функция, <code>if_nameindex</code>, возвращает массив структур <code>if_nameindex</code>, содержащих все имена интерфейсов и индексы. Она показана в листинге 18.13.</p>
      <p><strong>Листинг 18.13</strong>. Возвращение всех имен и индексов интерфейсов</p>
      <p>/<code>/libroute/if_nameindex.c</code></p>
      <p><code> 1 #include "unpifi.h"</code></p>
      <p><code> 2 #include "unproute.h"</code></p>
      <empty-line/>
      <p><code> 3 struct if_nameindex*</code></p>
      <p><code> 4 if_nameindex(void)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  char *buf, *next, *lim;</code></p>
      <p><code> 7  size_t len;</code></p>
      <p><code> 8  struct if_msghdr *ifm;</code></p>
      <p><code> 9  struct sockaddr *sa, *rti_info[RTAX_MAX];</code></p>
      <p><code>10  struct sockaddr_dl *sdl;</code></p>
      <p><code>11  struct if_nameindex *result, *ifptr;</code></p>
      <p><code>12  char *namptr;</code></p>
      <empty-line/>
      <p><code>13  if ((buf = net_it_iflist(0, 0, &amp;len)) == NULL)</code></p>
      <p><code>14   return (NULL);</code></p>
      <empty-line/>
      <p><code>15  if ((result = malloc(len)) == NULL) /* завышенная оценка */</code></p>
      <p><code>16   return (NULL);</code></p>
      <p><code>17  ifptr = result;</code></p>
      <p><code>18  namptr = (char*)result + len; /* имена начинаются с конца буфера */</code></p>
      <empty-line/>
      <p><code>19  lim = buf + len;</code></p>
      <p><code>20  for (next = buf; next &lt; lim; next += ifm-&gt;ifm_msglen) {</code></p>
      <p><code>21   ifm = (struct if_msghdr*)next;</code></p>
      <p><code>22   if (ifm-&gt;ifm_type == RTM_IFINFO) {</code></p>
      <p><code>23    sa = (struct sockaddr*)(ifm + 1);</code></p>
      <p><code>24    get_rtaddrs(ifm-&gt;ifm_addrs, sa, rti_info);</code></p>
      <p><code>25    if ((sa = rti_infо[RTAX_IFP]) != NULL) {</code></p>
      <p><code>26     if (sa-&gt;sa_family == AF_LINK) {</code></p>
      <p><code>27      sdl = (struct sockaddr_in*)sa;</code></p>
      <p><code>28      namptr -= sdl-&gt;sdl_nlen + 1;</code></p>
      <p><code>29      strncpy(namptr, &amp;sdl-&gt;sdl_data[0], sdl-&gt;sdl_nlen);</code></p>
      <p><code>30      namptr[sdl-&gt;sdl_nlen] = 0; /* завершающий нуль */</code></p>
      <p><code>31      ifptr-&gt;if_name = namptr;</code></p>
      <p><code>32      ifptr-&gt;if_index = sdl-&gt;sdl_index;</code></p>
      <p><code>33      ifptr++;</code></p>
      <p><code>34     }</code></p>
      <p><code>35    }</code></p>
      <p><code>36   }</code></p>
      <p><code>37  }</code></p>
      <p><code>38  ifptr-&gt;if_name = NULL; /* отмечаем конец массива структур */</code></p>
      <p><code>39  ifptr-&gt;if_index = 0;</code></p>
      <p><code>40  free(buf);</code></p>
      <p><code>41  return (result); /* вызывающий процесс должен освободить память</code></p>
      <p><code>                        с помощью free(), когда все сделано */</code></p>
      <p><code>43 }</code></p>
      <subtitle>Получение списка интерфейсов, выделение места для результата</subtitle>
      <p><code>13-18</code> Мы вызываем нашу функцию <code>net_rt_iflist</code> для возвращения списка интерфейсов. Мы также используем возвращаемый размер в качестве размера буфера, который мы размещаем в памяти для записи массива возвращаемых структур <code>if_nameindex</code>. Оценка необходимого размера буфера несколько завышена, но это проще, чем проходить список интерфейсов дважды: один раз для подсчета числа интерфейсов и общего размера имен, а второй — для записи этой информации. Мы создаем массив <code>if_nameindex</code> в начале этого буфера и записываем имена интерфейсов, начиная с конца буфера.</p>
      <subtitle>Обработка только сообщений RTM_IFINFO</subtitle>
      <p><code>22-36</code> Мы обрабатываем все сообщения, ища сообщения <code>RTM_IFINFO</code> и следующие за ними структуры адреса сокета. Имя и индекс интерфейса записываются в создаваемый нами массив.</p>
      <subtitle>Завершение массива</subtitle>
      <p><code>38-39</code> Последняя запись в массиве имеет пустой указатель <code>if_name</code> и нулевой индекс.</p>
     </section>
     <section>
      <title>
       <p>Функция if_freenameindex</p>
      </title>
      <p>Последняя функция, показанная в листинге 18.13, освобождает память, которая была выделена для массива структур <code>if_nameindex</code> и хранящихся в нем имен.</p>
      <p><strong>Листинг 18.14</strong>. Освобождение памяти, выделенной функцией if_nameindex</p>
      <p><code>43 void</code></p>
      <p><code>44 if_freenameindex(struct if_nameindex *ptr)</code></p>
      <p><code>45 {</code></p>
      <p><code>46  free(ptr);</code></p>
      <p><code>47 }</code></p>
      <p>Эта функция тривиальна, поскольку мы хранили и массив структур, и имена в одном и том же буфере. Если бы мы каждый раз вызывали функцию <code>malloc</code>, то для освобождения памяти нам бы пришлось проходить через весь массив, освобождать память, выделенную для каждого имени, а затем удалять сам массив (используя функцию <code>free</code>).</p>
     </section>
    </section>
    <section>
     <title>
      <p>18.7. Резюме</p>
     </title>
     <p>Последняя из структур адреса сокета, с которой мы встретились в книге, это <code>sockaddr_dl</code> — структура адреса сокета канального уровня, имеющая переменную длину. Ядра Беркли-реализаций связывают их с интерфейсами, возвращая в одной из этих структур индекс интерфейса, его имя и аппаратный адрес.</p>
     <p>В маршрутизирующий сокет процессом могут быть записаны 5 типов сообщений, и 12 различных сообщений могут быть асинхронно возвращены ядром через маршрутизирующий сокет. Мы привели пример, когда процесс запрашивает у ядра информацию о записи в таблице маршрутизации и ядро отвечает со всеми подробностями. Ответы ядра могут содержать до восьми структур адреса сокета, поэтому нам приходится анализировать сообщение, чтобы получить все фрагменты информации.</p>
     <p>Функция <code>sysctl</code> предоставляет общий способ получения и хранения параметров операционной системы. При выполнении функции <code>sysctl</code> нас интересует получение следующей информации:</p>
     <p>&#9632; список интерфейсов;</p>
     <p>&#9632; таблица маршрутизации;</p>
     <p>&#9632; кэш ARP.</p>
     <p>Изменения API сокетов, требуемые IPv6, включают четыре функции для сопоставления имен интерфейсов и их индексов. Каждому интерфейсу присваивается уникальный положительный индекс. В Беркли-реализациях с каждым интерфейсом уже связан индекс, поэтому нам несложно реализовать эти функции с помощью функции <code>sysctl</code>.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Что, как вы считаете, будет хранить поле <code>sdl_len</code> в структуре адреса сокета канального уровня для устройства с именем <code>eth10</code>, адрес канального уровня которого является 64-разрядным адресом IEEE EUI-64?</p>
     <p>2. В листинге 18.3 отключите параметр сокета <code>SO_USELOOPBACK</code> перед вызовом функции <code>write</code>. Что происходит?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 19</p>
     <p>Сокеты управления ключами</p>
    </title>
    <section>
     <title>
      <p>19.1. Введение</p>
     </title>
     <p>С появлением архитектуры безопасности IP (IPSec, см. RFC 2401 [64]) возникла потребность в стандартном механизме управления индивидуальными ключами шифрования и авторизации. Документ RFC 2367 [73] предлагает универсальный интерфейс управления ключами, который может использоваться с IPSec и иными криптографическими сетевыми службами. Подобно маршрутизирующим сокетам, этот интерфейс принадлежит к отдельному семейству протоколов, которое называется PF_KEY. В этом семействе поддерживаются только символьные сокеты.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Как отмечалось в разделе 4.2, на большинстве систем константа AF_KEY совпадает с PF_KEY. Однако в RFC 2367 совершенно четко утверждается, что с сокетами управления ключами должна использоваться константа PF_KEY.</p>
     </cite>
     <p>Для открытия символьного сокета управления ключами требуются определенные привилегии. В системах с сегментированными привилегиями для этого действия должна иметься специальная привилегия. В обычных Unix-системах открывать такие сокеты может только привилегированный пользователь.</p>
     <p>IPSec предоставляет криптографический сервис на базе <emphasis>соглашений о безопасности</emphasis> (<emphasis>security association</emphasis>, <emphasis>SA</emphasis>). Соглашение о безопасности представляет собой сочетание адресов отправителя и получателя (а при необходимости, транспортного протокола и портов), механизма (например, аутентификации) и ключей. К одному потоку трафика может относиться несколько соглашений (например, соглашения об аутентификации и о шифровании). Набор хранящихся в системе соглашений называется <emphasis>базой данных соглашений о безопасности</emphasis> (<emphasis>security association database</emphasis>, <emphasis>SADB</emphasis>).</p>
     <p>База SADB может использоваться не только для работы с IPSec. В ней могут иметься записи для OSPFv2, RIPv2, RSVP и Mobile-IP. По этой причине нельзя считать, что сокеты PF_KEY предназначены только для работы с IPSec.</p>
     <p>Для работы IPSec необходима также <emphasis>база политик безопасности</emphasis> (<emphasis>security policy database</emphasis>, <emphasis>SPDB</emphasis>). Политики описывают требования к трафику, например: «трафик между узлами А и В должен аутентифицироваться при помощи <emphasis>заголовков аутентификации</emphasis> IPSec (<emphasis>authentication header</emphasis>, <emphasis>АН</emphasis>); не удовлетворяющий требованию трафик должен сбрасываться». База соглашений описывает порядок выполнения требуемых для обеспечения безопасности действий, например, если трафик между узлами А и В использует заголовки аутентификации, то в SADB содержится соответствующий алгоритм и ключи. К сожалению, стандартного механизма управления SPDB не существует. Сокеты PF_KEY работают только с базой SADB, но не с SPDB. Реализация IPSec группы KAME использует для управления SPDB <emphasis>расширения</emphasis> PF_KEY, однако никаким стандартом эти расширения не описываются.</p>
     <p>Сокеты управления ключами поддерживают три типа операций:</p>
     <p>1. Процесс может отправить сообщение ядру и всем остальным процессам с открытыми сокетами, записав это сообщение в свой сокет. Таким способом добавляются и удаляются записи в базе соглашений о безопасности. Таким же способом процессы, обеспечивающие собственную безопасность самостоятельно (типа OSPFv2), могут запрашивать ключи у демона-ключника (демона, управляющего ключами).</p>
     <p>2. Процесс может считать сообщение от ядра или иного процесса через сокет управления ключами. Это позволяет ядру запросить демона-ключника о добавлении соглашения о безопасности для нового сеанса TCP, который, согласно политике, подлежит определенной защите.</p>
     <p>3. Процесс может отправить ядру запрос дампа, и ядро в ответ передаст ему дамп текущей базы SADB. Это отладочная функция, которая может быть доступна не во всех системах.</p>
    </section>
    <section>
     <title>
      <p>19.2. Чтение и запись</p>
     </title>
     <p>Все сообщения в сокете управления ключами должны иметь одинаковые заголовки, соответствующие листингу 19.1<a l:href="#n1" type="note">[1]</a>. Сообщение может сопровождаться различными расширениями в зависимости от наличия дополнительной информации или необходимости ее предоставления. Все нужные структуры определяются в заголовочном файле <code>&lt;net/pfkeyv2.h&gt;</code>. Все сообщения и расширения подвергаются 64-разрядному выравниванию и дополняются до длин, кратных 64 разрядам. Все поля длины оперируют 64-разрядными единицами, то есть значение длины 1 означает реальную длину 8 байт. Расширение, не содержащее достаточного количества данных, дополняется произвольным образом до длины, кратной 64 разрядам.</p>
     <p>Значение <code>sadb_msg_type</code> задает одну из десяти команд управления ключами. Типы сообщений перечислены в табл. 19.1. За заголовком <code>sadb_msg</code> может следовать произвольное количество расширений. Большинство сообщений имеют обязательные и необязательные расширения, которые будут описаны в соответствующих разделах. Шестнадцать типов расширений с названиями структур, их определяющих, перечислены в табл. 19.3.</p>
     <p><strong>Листинг 19.1</strong>. Заголовок сообщения управления ключами</p>
     <p><code>struct sadb_msg {</code></p>
     <p><code> u_int8_t  sadb_msg_version;  /* PF_KEY_V2 */</code></p>
     <p><code> u_int8_t  sadb_msg_type;     /* см. табл. 19.1 */</code></p>
     <p><code> u_int8_t  sadb_msg_errno;    /* код ошибки */</code></p>
     <p><code> u_int8_t  sadb_msg_satype;   /* см. табл. 19.2 */</code></p>
     <p><code> u_int16_t sadb_msg_len;      /* длина заголовка и расширений / 8 */</code></p>
     <p><code> u_int16_t sadb_msg_reserved; /* нуль при передаче, игнорируется</code></p>
     <p><code>                                 при получении */</code></p>
     <p><code> u_int32_t sadb_msg_seq;      /* порядковый номер */</code></p>
     <p><code> u_int32_t sadb_msg_pid;      /* идентификатор процесса отправителя</code></p>
     <p><code>                                 или получателя */</code></p>
     <p><code>};</code></p>
     <empty-line/>
     <p><strong>Таблица 19.1</strong>. Типы сообщений</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Тип сообщения</th>
       <th align="left" valign="top">К ядру</th>
       <th align="left" valign="top">От ядра</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_ACQUIRE</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Запрос на создание записи в SADB</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_ADD</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Добавление записи в полную базу безопасности</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_DELETE</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Удаление записи</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_DUMP</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Дамп SADB (используется для отладки)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXPIRE</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Уведомление об истечении срока действия записи</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_FLUSH</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Очистка всей базы безопасности</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_GET</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Получение записи</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_GETSPI</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Выделение SPI для создания записи SADB</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_REGISTER</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Регистрация для ответа на SADB_ACQUIRE</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_UPDATE</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Обновление записи в частичной SADB</td>
      </tr>
     </table>
     <empty-line/>
     <p><strong>Таблица 19.2</strong>. Типы соглашений о безопасности</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Тип соглашения</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_SATYPE_AH</td>
       <td align="left" valign="top">Аутентифицирующий заголовок IPSec</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_SATYPE_ESP</td>
       <td align="left" valign="top">ESP IPSec</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_SATYPE_MIP</td>
       <td align="left" valign="top">Идентификация мобильных пользователей (Mobile IP)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_SATYPE_OSPFV2</td>
       <td align="left" valign="top">Аутентификация OSPFv2</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_SATYPE_RIPV2</td>
       <td align="left" valign="top">Аутентификация RIPv2</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_SATYPE_RSVP</td>
       <td align="left" valign="top">Аутентификация RSVP</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_SATYPE_UNSPECIFIED</td>
       <td align="left" valign="top">He определен</td>
      </tr>
     </table>
     <empty-line/>
     <p><strong>Таблица 19.3</strong>. Типы расширений PF_KEY</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Тип заголовка расширения</th>
       <th align="left" valign="top">Описание</th>
       <th align="left" valign="top">Структура</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_ADDRESS_DST</td>
       <td align="left" valign="top">Адрес получателя SA</td>
       <td align="left" valign="top">sadb_address</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_ADDRESS_PROXY</td>
       <td align="left" valign="top">Адрес прокси-сервера SA</td>
       <td align="left" valign="top">sadb_address</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_ADDRESS_SRC</td>
       <td align="left" valign="top">Адрес отправителя SA</td>
       <td align="left" valign="top">sadb_address</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_IDENTITY_DST</td>
       <td align="left" valign="top">Личность получателя</td>
       <td align="left" valign="top">sadb_ident</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_IDENTITY_SRC</td>
       <td align="left" valign="top">Личность отправителя</td>
       <td align="left" valign="top">sadb_ident</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_KEY_AUTH</td>
       <td align="left" valign="top">Ключ аутентификации</td>
       <td align="left" valign="top">sadb_key</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_KEY_ENCRYPT</td>
       <td align="left" valign="top">Ключ шифрования</td>
       <td align="left" valign="top">sadb_key</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_LIFETIME_CURRENT</td>
       <td align="left" valign="top">Текущее время жизни SA</td>
       <td align="left" valign="top">sadb_lifetime</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_LIFETIME_HARD</td>
       <td align="left" valign="top">Жесткое ограничение на время жизни SA</td>
       <td align="left" valign="top">sadb_lifetime</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_LIFETIME_SOFT</td>
       <td align="left" valign="top">Гибкое ограничение на время жизни SA</td>
       <td align="left" valign="top">sadb_lifetime</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_PROPOSAL</td>
       <td align="left" valign="top">Предлагаемая ситуация</td>
       <td align="left" valign="top">sadb_prop</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_SA</td>
       <td align="left" valign="top">Соглашение о безопасности</td>
       <td align="left" valign="top">sadb_sa</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_SENSITIVITY</td>
       <td align="left" valign="top">Важность SA</td>
       <td align="left" valign="top">sadb_sens</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_SPIRANGE</td>
       <td align="left" valign="top">Диапазон допустимых значений SPI</td>
       <td align="left" valign="top">sadb_spirange</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_SUPPORTED_AUTH</td>
       <td align="left" valign="top">Поддерживаемые алгоритмы аутентификации</td>
       <td align="left" valign="top">sadb_supported</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">SADB_EXT_SUPPORTED_ENCRYPT</td>
       <td align="left" valign="top">Поддерживаемые алгоритмы шифрования</td>
       <td align="left" valign="top">sadb_supported</td>
      </tr>
     </table>
     <p>Рассмотрим несколько примеров сообщений и расширений, используемых в типичных операциях с сокетами управления ключами.</p>
    </section>
    <section>
     <title>
      <p>19.3. Дамп базы соглашений о безопасности</p>
     </title>
     <p>Для дампа текущей базы соглашений о безопасности используется сообщение <code>SADB_DUMP</code>. Это самое простое из сообщений, поскольку оно не требует никаких расширений, а состоит только из 16-байтового заголовка <code>sadb_msg</code>. Когда процесс отправляет сообщение <code>SADB_DUMP</code> ядру через сокет управления ключами, ядро отвечает последовательностью сообщений <code>SADB_DUMP</code> по тому же сокету. В каждом сообщении содержится одна запись базы SADB. Конец последовательности обозначается сообщением со значением 0 в поле <code>sadb_msg_seq</code>.</p>
     <p>Поле <code>sadb_msg_satype</code> позволяет запросить только записи определенного типа. Значения этого поля следует брать из табл. 19.2. При указании значения <code>SADB_SATYPE_UNSPEC</code> возвращаются все записи базы. Не все типы соглашений о безопасности поддерживаются всеми реализациями. Реализация KAME поддерживает только соглашения, относящиеся к IPSec (<code>SADB_SATYPE_AH</code> и <code>SADB_SATYPE_ESP</code>), поэтому при попытке получить дамп записей <code>SADB_SATYPE_RIPV2</code> будет возвращена ошибка <code>EINVAL</code>. Если же записей, относящихся к запрошенному типу, в таблице нет (но они поддерживаются), функция возвращает ошибку <code>ENOENT</code>.</p>
     <p>Программа, получающая записи из базы данных безопасности, приведена в листинге 19.2.</p>
     <p><strong>Листинг 19.2</strong>. Дамп базы соглашений о безопасности</p>
     <p><code>//key/dump.c</code></p>
     <p><code> 1 void</code></p>
     <p><code> 2 sadb_dump(int type)</code></p>
     <p><code> 3 {</code></p>
     <p><code> 4  int s;</code></p>
     <p><code> 5  char buf[4096];</code></p>
     <p><code> 6  struct sadb_msg msg;</code></p>
     <p><code> 7  int goteof;</code></p>
     <empty-line/>
     <p><code> 8  s = Socket(PF_KEY, SOCK_RAW, PF_KEY_V2);</code></p>
     <empty-line/>
     <p><code> 9  /* формирование и отправка запроса SADB_DUMP */</code></p>
     <p><code>10  bzero(&amp;msg, sizeof(msg));</code></p>
     <p><code>11  msg.sadb_msg_version = PF_KEY_V2;</code></p>
     <p><code>12  msg.sadb_msg_type = SADB_DUMP;</code></p>
     <p><code>13  msg.sadb_msg_satype = type;</code></p>
     <p><code>14  msg.sadb_msg_len = sizeof(msg) / 8;</code></p>
     <p><code>15  msg.sadb_msg_pid = getpid();</code></p>
     <p><code>16  printf("Sending dump message:\n");</code></p>
     <p><code>17  print_sadb_msg(&amp;msg, sizeof(msg));</code></p>
     <p><code>18  Write(s, &amp;msg, sizeof(msg));</code></p>
     <empty-line/>
     <p><code>19  printf("\nMessages returned:\n");</code></p>
     <p><code>20  /* считывание и вывод всех ответов SADB_DUMP */</code></p>
     <p><code>21  goteof = 0;</code></p>
     <p><code>22  while (goteof == 0) {</code></p>
     <p><code>23   int msglen;</code></p>
     <p><code>24   struct sadb_msg *msgp;</code></p>
     <empty-line/>
     <p><code>25   msglen = Read(s, &amp;buf, sizeof(buf));</code></p>
     <p><code>26   msgp = (struct sadb_msg*)&amp;buf;</code></p>
     <p><code>27   print_sadb_msg(msgp, msglen);</code></p>
     <p><code>28   if (msgp-&gt;sadb_msg_seq == 0)</code></p>
     <p><code>29    goteof = 1;</code></p>
     <p><code>30  }</code></p>
     <p><code>31  close(s);</code></p>
     <p><code>32 }</code></p>
     <empty-line/>
     <p><code>33 int</code></p>
     <p><code>34 main(int argc, char **argv)</code></p>
     <p><code>35 {</code></p>
     <p><code>36  int satype = SADB_SATYPE_UNSPEC;</code></p>
     <p><code>37  int c;</code></p>
     <empty-line/>
     <p><code>38  opterr = 0; /* отключение записи в stderr для getopt() */</code></p>
     <p><code>39  while ((c = getopt(argc, argv, "t:")) != -1) {</code></p>
     <p><code>40   switch (c) {</code></p>
     <p><code>41   case 't':</code></p>
     <p><code>42    if ((satype = getsatypebyname(optarg)) == -1)</code></p>
     <p><code>43     err_quit("invalid -t option %s", optarg);</code></p>
     <p><code>44    break;</code></p>
     <empty-line/>
     <p><code>45   default:</code></p>
     <p><code>46    err_quit("unrecognized option: %c", c);</code></p>
     <p><code>47   }</code></p>
     <p><code>48  }</code></p>
     <empty-line/>
     <p><code>49  sadb_dump(satype);</code></p>
     <p><code>50 }</code></p>
     <p>В этом листинге мы впервые встречаемся с функцией <code>getopt</code>, определяемой стандартом POSIX. Третий аргумент представляет собой строку символов, которые могут быть приняты в качестве аргументов командной строки: в нашем случае только <code>t</code>. За символом следует двоеточие, означающее, что за ключом должно быть указано численное значение. В программах, которые могут принимать несколько аргументов, эти аргументы должны объединяться. Например, в листинге 29.3 соответствующая строка имеет вид <code>0i:l:v</code>. Это означает, что ключи <code>i</code> и <code>l</code> сопровождаются дополнительными аргументами, а <code>0</code> и <code>v</code> — не сопровождаются.</p>
     <p>Эта функция работает с четырьмя глобальными переменными, определенными в заголовочном файле <code>&lt;unistd.h&gt;</code>.</p>
     <p><code>extern char *optarg;</code></p>
     <p><code>extern int optind, opterr, optopt;</code></p>
     <p>Перед вызовом <code>getopt</code> мы устанавливаем <code>opterr</code> в нуль, чтобы функция не направляла сообщений об ошибках в стандартный поток вывода этих сообщений, потому что мы хотим обрабатывать их самостоятельно. В стандарте POSIX говорится, что если первый символ третьего аргумента функции — двоеточие, то это тоже должно отключать вывод сообщений в стандартный поток сообщений об ошибках, однако не все реализации в настоящий момент выполняют данное требование.</p>
     <subtitle>Открытие сокета PF_KEY</subtitle>
     <p><code>1-8</code> Сначала мы открываем сокет <code>PF_KEY</code>. Для этого требуются определенные привилегии, поскольку сокет дает доступ к управлению ключами.</p>
     <subtitle>Формирование запроса SADB_DUMP</subtitle>
     <p><code>9-15</code> Мы начинаем с обнуления структуры <code>sadb_msg</code>, что позволяет нам не инициализировать поля, которые должны остаться нулевыми. Затем мы заполняем все интересующие нас поля по отдельности.</p>
     <p>Если при открытии сокета в качестве третьего аргумента использовалась константа <code>PF_KEY_V2</code>, все сообщения, направляемые в такой сокет, должны иметь версию <code>PF_KEY_V2</code>. Нужный нам тип сообщения — <code>SADB_DUMP</code>. Длина сообщения устанавливается равной длине заголовка без расширений, поскольку для запроса дампа расширения не нужны. Наконец, идентификатор процесса устанавливается равным идентификатору нашего процесса. Это обязательное условие для всех сообщений.</p>
     <subtitle>Отображение и отправка сообщения SADB_DUMP</subtitle>
     <p><code>16-18</code> Мы отображаем сообщение при помощи функции <code>print_sadb_msg</code>. Мы не приводим листинг этой функции, потому что он достаточно длинный и не представляет особого интереса, однако он включен в набор свободно распространяемых программ, доступный для скачивания с сайта этой книги. Функция принимает сообщение, подготовленное к отправке или полученное от ядра, и выводит всю содержащуюся в этом сообщении информацию в удобной для чтения форме.</p>
     <p>После вызова функции подготовленное сообщение записывается в сокет.</p>
     <subtitle>Чтение ответов</subtitle>
     <p><code>19-30</code> Программа считывает сообщения и выводит их в цикле при помощи функции <code>print_sadb_msg</code>. Последнее сообщение последовательности имеет порядковый номер 0, что мы трактуем как «конец файла».</p>
     <subtitle>Закрытие сокета PF_KEY</subtitle>
     <p><code>31</code> Мы закрываем открытый в начале работы сокет управления ключами.</p>
     <subtitle>Обработка аргументов командной строки</subtitle>
     <p><code>38-48</code> На долю функции <code>main</code> остается не так уж много работы. Программа принимает единственный аргумент — тип соглашений о безопасности, которые должны быть запрошены из базы. По умолчанию тип равен <code>SADB_SATYPE_UNSPEC</code>. Указав другой тип в аргументе командной строки, пользователь может выбрать интересующие его записи. Наша программа вызывает нашу же функцию <code>getsatypebyname</code>, возвращающую значение типа (константу) по его названию.</p>
     <subtitle>Вызов функции sadb_dump</subtitle>
     <p><code>49</code> Наконец, мы вызываем функцию <code>sadb_dump</code>, которая уже была описана.</p>
     <subtitle>Пробный запуск</subtitle>
     <p>Ниже приведен пример выполнения программы дампа базы данных безопасности в системе с двумя статическими соглашениями о безопасности.</p>
     <p><code>macosx % <strong>dump Sending dump message:</strong></code></p>
     <p><code>SADB Message Dump, errno 0, satype Unspecified, seq 0, pid 20623</code></p>
     <p><code>Messages returned:</code></p>
     <p><code>SADB Message Dump, errno 0. satype IPsec AH, seq 1, pid 20623</code></p>
     <p><code>SA: SPI=258 Replay Window=0 State=Mature</code></p>
     <p><code>Authentication Algorithm: HMAC-MD5</code></p>
     <p><code>Encryption Algorithm: None</code></p>
     <p><code>[unknown extension 19]</code></p>
     <p><code>Current lifetime:</code></p>
     <p><code>0 allocations, 0 bytes</code></p>
     <p><code>added at Sun May 18 16:28:11 2003, never used</code></p>
     <p><code>Source address: 2.3.4.5/128 (IP proto 255)</code></p>
     <p><code>Dest address: 6.7.8.9/128 (IP proto 255)</code></p>
     <p><code>Authentication key. 128 bits: 0x20202020202020200202020202020202</code></p>
     <p><code>SADB Message Dump, errno 0, satype IPsec AH, seq 0, pid 20623</code></p>
     <p><code>SA: SPI=257 Replay Window=0 State=Mature</code></p>
     <p><code>Authentication Algorithm: HMAC-MD5</code></p>
     <p><code>Encryption Algorithm: None</code></p>
     <p><code>[unknown extension 19]</code></p>
     <p><code>Current lifetime:</code></p>
     <p><code>0 allocations, 0 bytes</code></p>
     <p><code>added at Sun May 18 16:26:24 2003, never used</code></p>
     <p><code>Source address: 1.2.3.4/128 (IP proto 255)</code></p>
     <p><code>Dest address: 5.6.7.8/128 (IP proto 255)</code></p>
     <p><code>Authentication key, 128 bits: 0x10101010101010100101010101010101</code></p>
    </section>
    <section>
     <title>
      <p>19.4. Создание статического соглашения о безопасности</p>
     </title>
     <section>
      <p>Наиболее прямолинейным методом добавления соглашения о безопасности в базу является отправка сообщения <code>SADB_ADD</code> с заполненными параметрами, которые могут задаваться вручную. Последнее затрудняет смену ключей, которая необходима для предотвращения криптоаналитических атак, но зато упрощает настройку. Элис и Боб договариваются о ключах и алгоритмах без использования линий связи. Мы приводим последовательность действий по созданию и отправке сообщения <code>SADB_ADD</code>.</p>
      <p>Сообщение <code>SADB_ADD</code> обязано иметь три расширения: соглашение о безопасности, адрес и ключ. Оно может дополняться и другими расширениями: временем жизни, личными данными и параметром <emphasis>важности</emphasis> (<emphasis>sensitivity</emphasis>). Сначала мы опишем обязательные расширения.</p>
      <p>Расширение SA описывается структурой <code>sadb_sa</code>, представленной в листинге 19.3.</p>
      <p><strong>Листинг 19.3</strong>. Расширение SA</p>
      <p><code>struct sadb_sa {</code></p>
      <p><code> u_int16_t sadb_sa_len;     /* длина расширения / 8 */</code></p>
      <p><code> u_int16_t sadb_sa_exttype; /* SADB_EXT_SA */</code></p>
      <p><code> u_int32_t sadb_sa_spi;     /* индекс параметров безопасности (SPI) */</code></p>
      <p><code> u_int8_t  sadb_sa_replay;  /* размер окна защиты от повторов или нуль */</code></p>
      <p><code> u_int8_t  sadb_sa_state;   /* состояние SA. см. табл. 19.4 */</code></p>
      <p><code> u_int8_t  sadb_sa_auth;    /* алгоритм аутентификации, см. табл. 19.5 */</code></p>
      <p><code> u_int8_t  sadb_sa_encrypt; /* алгоритм шифрования, см. табл. 19.5 */</code></p>
      <p><code> u_int32_t sadb_sa_flags;   /* флаги */</code></p>
      <p><code>};</code></p>
      <empty-line/>
      <p><strong>Таблица 19.4</strong>. Использование расширений</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Состояние SA</th>
        <th align="left" valign="top">Описание</th>
        <th align="left" valign="top">Возможность использования</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_SASTATE_LARVAL</td>
        <td align="left" valign="top">В процессе создания</td>
        <td align="left" valign="top">Нет</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_SASTATE_MATURE</td>
        <td align="left" valign="top">Полностью сформированное</td>
        <td align="left" valign="top">Да</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_SASTATE_DYING</td>
        <td align="left" valign="top">Превышено гибкое ограничение на время жизни</td>
        <td align="left" valign="top">Да</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_SASTATE_DEAD</td>
        <td align="left" valign="top">Превышено жесткое ограничение на время жизни</td>
        <td align="left" valign="top">Нет</td>
       </tr>
      </table>
      <empty-line/>
      <p><strong>Таблица 19.5</strong>. Алгоритмы аутентификации и шифрования</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Алгоритм</th>
        <th align="left" valign="top">Описание</th>
        <th align="left" valign="top">Ссылка</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_AALG_NONE</td>
        <td align="left" valign="top">Без аутентификации</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_AALG_MD5HMAC</td>
        <td align="left" valign="top">HMAC-MD5-96</td>
        <td align="left" valign="top">RFC 2403</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_AALG_SHA1HMAC</td>
        <td align="left" valign="top">HMAC-SHA-1-96</td>
        <td align="left" valign="top">RFC 2404</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_EALG_NONE</td>
        <td align="left" valign="top">Без шифрования</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_EALG_DESCBC</td>
        <td align="left" valign="top">DES-CBC</td>
        <td align="left" valign="top">RFC 2405</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_EALG_3DESCBC</td>
        <td align="left" valign="top">3DES-CBC</td>
        <td align="left" valign="top">RFC 1851</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">SADB_EALG_NULL</td>
        <td align="left" valign="top">NULL</td>
        <td align="left" valign="top">RFC 2410</td>
       </tr>
      </table>
      <p>Поле <code>sadb_sa_spi</code> содержит <emphasis>индекс параметров безопасности</emphasis> (<emphasis>security parameters index</emphasis>, <emphasis>SPI</emphasis>). Это значение вместе с адресом получателя и используемым протоколом (например, IPSec АН) уникально идентифицирует соответствующее соглашение о безопасности. При получении пакета значение SPI используется для поиска соглашения, относящегося к пакету. При отправке пакета значение помещается в него для использования получателем. Никаких иных значений SPI не имеет, поэтому назначаться индекс может последовательно, в случайном порядке или с использованием метода, рекомендуемого собеседником. Поле <code>sadb_sa_replay</code> задает размер окна защиты от повторов. Поскольку статические соглашения о защите не дают возможности задействовать эту защиту, мы устанавливаем поле равным нулю. Значение поля <code>sadb_sa_state</code> меняется в зависимости от состояния динамически создаваемых соглашений о безопасности (см. табл. 19.4). Создаваемые вручную соглашения существуют исключительно в состоянии <code>SADB_SASTATE_MATURE</code>. С другими состояниями мы встретимся в разделе 19.5.</p>
      <p>Поля <code>sadb_sa_auth</code> и <code>sadb_sa_encrypt</code> определяют алгоритмы аутентификации и шифрования для данного соглашения. Возможные значения этих полей перечислены в табл. 19.5. Единственное значение поля <code>sadb_sa_flags</code> определено в POSIX как константа <code>SADB_SAFLAGS_PFS</code>. Этот флаг требует <emphasis>совершенной безопасности пересылки</emphasis> (<emphasis>perfect forward security</emphasis>), которая состоит в том утверждении, что значение ключа не должно зависеть от предыдущих подключений или какого-либо главного ключа. Флаг используется при запросе ключей у приложения, заведующего ими, но не при создании статических соглашений.</p>
      <p>Следующее обязательное расширение команды <code>SADB_ADD</code> должно содержать адреса отправителя и получателя, задаваемые константами <code>SADB_EXT_ADDRESS_SRC</code> и <code>SADB_EXT_ADDRESS_DST</code>. При необходимости может быть указан адрес прокси-сервера <code>SADB_EXT_ADDRESS_PROXY</code>. Подробнее об обработке адресов прокси-серверов вы можете прочесть в RFC 2367 [73]. Адреса задаются в структуре <code>sadb_address</code>, представленной в листинге 19.4. Поле <code>sadb_address_exttype</code> определяет тип адреса (отправителя, получателя или прокси-сервера). Поле <code>sadb_address_proto</code> позволяет выбрать протокол IP или произвольный протокол (значение 0). Поле <code>sadb_address_prefixlen</code> описывает значимый префикс адреса. Это позволяет использовать одно соглашение для множества адресов. За структурой <code>sadb_address</code> следует структура <code>sockaddr</code> соответствующего семейства (например, <code>sockaddr_in</code> или <code>sockaddr_in6</code>). Номер порта из структуры <code>sockaddr</code> используется только в том случае, если поле <code>sadb_address_proto</code> задает протокол, поддерживающий номера портов (например, <code>IPPROTO_TCP</code>).</p>
      <p><strong>Листинг 19.4</strong>. Структура sadb_address</p>
      <p><code>struct sadb_address {</code></p>
      <p><code> u_int16_t sadb_address_len;       /* длина расширения с адресом / 8 */</code></p>
      <p><code> u_int16_t sadb_address_exttype;   /* SADB_EXT_ADDRESS_{SRC,DST,PROXY} */</code></p>
      <p><code> u_int8_t  sadb_address_proto;     /* протокол IP или 0 (любой) */</code></p>
      <p><code> u_int8_t  sadb_address_prefixlen; /* # значащих битов адреса */</code></p>
      <p><code> u_int16_t sadb_address_reserved;  /* зарезервирован для послед. использования */</code></p>
      <p><code>};</code></p>
      <p><code>/* далее следует структура sockaddr соответствующего семейства */</code></p>
      <p>Завершают список обязательных расширений сообщения <code>SADB_ADD</code> ключи аутентификации и шифрования — расширения <code>SADB_EXT_KEY_AUTH</code> и <code>SADB_EXT_KEY_ENCRYPT</code>, описываемые структурой <code>sadb_key</code> (листинг 19.5). Поле <code>sadb_key_exttype</code> определяет тип ключа (ключ аутентификации или шифрования), поле <code>sadb_key_bits</code> задает длину ключа в битах, а сам ключ следует за структурой <code>sadb_key</code>.</p>
      <p><strong>Листинг 19.5</strong>. Структура sadb_key</p>
      <p><code>struct sadb_key {</code></p>
      <p><code> u_int16_t sadb_key_len;      /* длина расширения с ключом / 8 */</code></p>
      <p><code> u_int16_t sadb_key_exttype;  /* SADB_EXT_KEY_{AUTH,ENCRYPT} */</code></p>
      <p><code> u_int16_t sadb_key_bits;     /* # битов в ключе */</code></p>
      <p><code> u_int16_t sadb_key_reserved; /* зарезервировано для расширения */</code></p>
      <p><code>};</code></p>
      <p><code>/* далее следуют данные о самом ключе */</code></p>
      <p>Программа, добавляющая статическую запись в базу данных безопасности, представлена в листинге 19.6.</p>
      <p><strong>Листинг 19.6</strong>. Программа, использующая команду SADB_ADD</p>
      <p><code>//key/add с</code></p>
      <p><code> 33 void</code></p>
      <p><code> 34 sadb_add(struct sockaddr *src, struct sockaddr *dst, int type, int alg,</code></p>
      <p><code> 35  int spi, int keybits, unsigned char *keydata)</code></p>
      <p><code> 36  {</code></p>
      <p><code> 37  int s;</code></p>
      <p><code> 38  char buf[4096], *p; /* XXX */</code></p>
      <p><code> 39  struct sadb_msg *msg;</code></p>
      <p><code> 40  struct sadb_sa *saext;</code></p>
      <p><code> 41  struct sadb_address *addrext;</code></p>
      <p><code> 42  struct sadb_key *keyext;</code></p>
      <p><code> 43  int len;</code></p>
      <p><code> 44  int mypid;</code></p>
      <empty-line/>
      <p><code> 45  s = Socket(PF_KEY, SOCK_RAW, PF_KEY_V2);</code></p>
      <empty-line/>
      <p><code> 46  mypid = getpid();</code></p>
      <empty-line/>
      <p><code> 47  /* Формирование и запись запроса SADB_ADD */</code></p>
      <p><code> 48  bzero(&amp;buf, sizeof(buf));</code></p>
      <p><code> 49  p = buf;</code></p>
      <p><code> 50  msg = (struct sadb_msg*)p;</code></p>
      <p><code> 51  msg-&gt;sadb_msg_version = PF_KEY_V2;</code></p>
      <p><code> 52  msg-&gt;sadb_msg_type = SADB_ADD;</code></p>
      <p><code> 53  msg-&gt;sadb_msg_satype = type;</code></p>
      <p><code> 54  msg-&gt;sadb_msg_pid = getpid();</code></p>
      <p><code> 55  len = sizeof(*msg);</code></p>
      <p><code> 56  p += sizeof(*msg);</code></p>
      <empty-line/>
      <p><code> 57  saext = (struct sadb_sa*)p;</code></p>
      <p><code> 58  saext-&gt;sadb_sa_len = sizeof(*saext) / 8;</code></p>
      <p><code> 59  saext-&gt;sadb_sa_exttype = SADB_EXT_SA;</code></p>
      <p><code> 60  saext-&gt;sadb_sa_spi = htonl(spi);</code></p>
      <p><code> 61  saext-&gt;sadb_sa_replay = 0; /* статические ключи не защищают от повтора */</code></p>
      <p><code> 62  saext-&gt;sadb_sa_state = SADB_SASTATE_MATURE;</code></p>
      <p><code> 63  saext-&gt;sadb_sa_auth = alg;</code></p>
      <p><code> 64  saext-&gt;sadb_sa_encrypt = SADB_EALG_NONE;</code></p>
      <p><code> 65  saext-&gt;sadb_sa_flags = 0;</code></p>
      <p><code> 66  len += saext-&gt;sadb_sa_len * 8;</code></p>
      <p><code> 67  p += saext-&gt;sadb_sa_len * 8;</code></p>
      <empty-line/>
      <p><code> 68  addrext = (struct sadb_address*)p;</code></p>
      <p><code> 69  addrext-&gt;sadb_address_len = (sizeof(*addrext) + salen(src) + 7) / 8;</code></p>
      <p><code> 70  addrext-&gt;sadb_address_exttype = SADB_EXT_ADDRESS_SRC;</code></p>
      <p><code> 71  addrext-&gt;sadb_address_proto = 0; /* any protocol */</code></p>
      <p><code> 72  addrext-&gt;sadb_address_prefixlen = prefix_all(src);</code></p>
      <p><code> 73  addrext-&gt;sadb_address_reserved = 0;</code></p>
      <p><code> 74  memcpy(addrext + 1, src, salen(src));</code></p>
      <p><code> 75  len += addrext-&gt;sadb_address_len * 8,</code></p>
      <p><code> 76  p += addrext-&gt;sadb_address_len * 8;</code></p>
      <empty-line/>
      <p><code> 77  addrext = (struct sadb_address*)p;</code></p>
      <p><code> 78  addrext-&gt;sadb_address_len = (sizeof(*addrext) + salen(dst) + 7) / 8;</code></p>
      <p><code> 79  addrext-&gt;sadb_address_exttype = SADB_EXT_ADDRESS_DST;</code></p>
      <p><code> 80  addrext-&gt;sadb_address_proto = 0; /* any protocol */</code></p>
      <p><code> 81  addrext-&gt;sadb_address_prefixlen = prefix_all(dst);</code></p>
      <p><code> 82  addrext-&gt;sadb_address_reserved = 0;</code></p>
      <p><code> 83  memcpy(addrext + 1, dst, salen(dst));</code></p>
      <p><code> 84  len += addrext-&gt;sadb_address_len * 8;</code></p>
      <p><code> 85  p += addrext-&gt;sadb_address_len * 8;</code></p>
      <empty-line/>
      <p><code> 86  keyext = (struct sadb_key*)p;</code></p>
      <p><code> 87  /* обеспечивает выравнивание */</code></p>
      <p><code> 88  keyext-&gt;sadb_key_len = (sizeof(*keyext) + (keybits / 8) + 7) / 8;</code></p>
      <p><code> 89  keyext-&gt;sadb_key_exttype = SADB_EXT_KEY_AUTH;</code></p>
      <p><code> 90  keyext-&gt;sadb_key_bits = keybits;</code></p>
      <p><code> 91  keyext-&gt;sadb_key_reserved = 0;</code></p>
      <p><code> 92  memcpy(keyext + 1, keydata, keybits / 8);</code></p>
      <p><code> 93  len += keyext-&gt;sadb_key_len * 8;</code></p>
      <p><code> 94  p += keyext-&gt;sadb_key_len * 8;</code></p>
      <empty-line/>
      <p><code> 95  msg-&gt;sadb_msg_len = len / 8;</code></p>
      <p><code> 96  printf("Sending add message:\n");</code></p>
      <p><code> 97  print_sadb_msg(buf, len);</code></p>
      <p><code> 98  Write(s, buf, len);</code></p>
      <empty-line/>
      <p><code> 99  printf("\nReply returned:\n");</code></p>
      <p><code>100  /* считывание и вывод ответа SADB_ADD, игнорируя любые другие */</code></p>
      <p><code>101  for (;;) {</code></p>
      <p><code>102   int msglen;</code></p>
      <p><code>103   struct sadb_msg *msgp;</code></p>
      <empty-line/>
      <p><code>104   msglen = Read(s, &amp;buf, sizeof(buf));</code></p>
      <p><code>105   msgp = (struct sadb_msg*)&amp;buf;</code></p>
      <p><code>106   if (msgp-&gt;sadb_msg_pid == mypid &amp;&amp;</code></p>
      <p><code>107    msgp-&gt;sadb_msg_type == SADB_ADD) {</code></p>
      <p><code>108    print_sadb_msg(msgp, msglen);</code></p>
      <p><code>109    break;</code></p>
      <p><code>110   }</code></p>
      <p><code>111  }</code></p>
      <p><code>112  close(s);</code></p>
      <p><code>113 }</code></p>
      <subtitle>Открытие сокета PF_KEY и сохранение PID</subtitle>
      <p><code>55-56</code> Как и в предыдущей программе, мы открываем сокет PF_KEY и сохраняем идентификатор нашего процесса для последующего его использования.</p>
      <subtitle>Формирование общего заголовка сообщений</subtitle>
      <p><code>47-56</code> Мы формируем заголовок сообщения <code>SADB_ADD</code>. Поле <code>sadb_msg_len</code> устанавливается непосредственно перед отправкой сообщения, поскольку оно должно соответствовать истинной его длине. В переменной <code>len</code> хранится текущая длина сообщения, а указатель р всегда указывает на первый неиспользуемый байт буфера.</p>
      <subtitle>Добавление расширения SA</subtitle>
      <p><code>57-67</code> Мы добавляем обязательное расширение <code>SA</code> (см. листинг 19.3). Поле <code>sadb_sa_spi</code> должно иметь сетевой порядок байтов, поэтому нам приходится применять функцию <code>htonl</code> к значению в порядке байтов узла. Мы отключаем защиту от повторов и устанавливаем состояние <code>SA</code> равным <code>SADB_SASTATE_MATURE</code> (см. табл. 19.4). Алгоритм аутентификации выбирается в соответствии с аргументом командной строки, а шифрование отключается при помощи константы <code>SADB_EALG_NONE</code>.</p>
      <subtitle>Добавление адреса отправителя</subtitle>
      <p><code>68-76</code> К сообщению добавляется расширение <code>SADB_EXT_ADDRESS_SRC</code>, содержащее адрес отправителя для соглашения о безопасности.</p>
      <p>Значение протокола устанавливается равным нулю, что подразумевает действительность соглашения для всех протоколов. Длина префикса устанавливается равной соответствующей длине версии IP (то есть 32 разряда для IPv4 и 128 разрядов для IPv6). При расчете значения поля длины мы добавляем к реальному значению число 7 перед делением на 8, что гарантирует выравнивание по 64-разрядной границе, обязательное для всех расширений, передаваемых через сокеты PF_KEY. Структура <code>sockaddr</code> копируется в буфер после заголовка расширения.</p>
      <subtitle>Добавление адреса получателя</subtitle>
      <p><code>77-85</code> Адрес получателя добавляется в сообщение <code>SADB_EXT_ADDRESS_DST</code>. Процедура в точности совпадает с описанной выше.</p>
      <subtitle>Добавление ключа</subtitle>
      <p><code>86-94</code> К сообщению добавляется расширение <code>SADB_EXT_KEY_AUTH</code>, содержащее ключ авторизации. Расчет поля длины производится точно так же, как и для обоих адресов. Ключ переменной длины требует соответствующего количества дополняющих нулей. Мы устанавливаем значение количества битов и копируем ключ вслед за заголовком расширения.</p>
      <subtitle>Запись сообщения в сокет</subtitle>
      <p><code>95-98</code> Мы выводим сообщение на экран вызовом функции <code>print_sadb_msg</code>, после чего записываем его в сокет.</p>
      <subtitle>Считывание ответа</subtitle>
      <p><code>99-111</code> Мы считываем все сообщения из сокета до тех пор, пока не будет получено сообщение, адресованное нашему процессу (проверяется по PID) и имеющее тип <code>SADB_ADD</code>. Это сообщение выводится на экран функций <code>print_sadb_msg</code>, после чего программа завершает работу.</p>
     </section>
     <section>
      <title>
       <p>Пример</p>
      </title>
      <p>Мы запускаем программу, требуя от нее установки соглашения о безопасности, касающегося трафика между узлами 127.0.0.1 и 127.0.0.1 (то есть локального трафика):</p>
      <p><code>macosx % <strong>add 127.0.0.1 127.0.0.1 HMAC-SHA-1-96 160 \</strong></code></p>
      <p><code><strong> 0123456789abcdef0123456789abcdef01234567</strong></code></p>
      <p><code>Sending add message:</code></p>
      <p><code>SADB Message Add, errno 0, satype IPsec AH, seq 0, pid 6246</code></p>
      <p><code>SA: SPI=39030 Replay Window=0 State=Mature</code></p>
      <p><code>Authentication Algorithm: HMAC-SHA-1</code></p>
      <p><code>Encryption Algorithm: None</code></p>
      <p><code>Source address: 127.0.0.1/32</code></p>
      <p><code>Dest address: 127.0.0.1/32</code></p>
      <p><code>Authentication key. 160 bits: 0x0123456789abcdef0123456789abcdef01234567</code></p>
      <p><code>Reply returned:</code></p>
      <p><code>SADB Message Add, errno 0, satype IPsec AH, seq 0, pid 6246</code></p>
      <p><code>SA: SPI=39030 Replay Window=0 State=Mature</code></p>
      <p><code>Authentication Algorithm: HMAC-SHA-1</code></p>
      <p><code>Encryption Algorithm: None</code></p>
      <p><code>Source address: 127.0.0.1/32</code></p>
      <p><code>Dest address: 127.0.0.1/32</code></p>
      <p>Обратите внимание, что в ответе системы отсутствует ключ. Дело в том; что ответ направляется на все сокеты PF_KEY, которые, однако, могут принадлежать к разным доменам, а данные о ключах не должны передаваться между доменами. После добавления записи в базу данных мы даем команду <code>ping 127.0.0.1</code>, чтобы проверить, задействуется ли соглашение о безопасности, после чего запрашиваем дамп базы данных и смотрим, что в ней изменилось.</p>
      <p><code>macosx % <strong>dump</strong></code></p>
      <p><code>Sending dump message:</code></p>
      <p><code>SADB Message Dump, errno 0, satype Unspecified, seq 0, pid 6283</code></p>
      <p><code>Messages returned:</code></p>
      <p><code>SADB Message Dump, errno 0, satype IPsec AH, seq 0, pid 6283</code></p>
      <p><code>SA: SPI=39030 Replay Window=0 State=Mature</code></p>
      <p><code>Authentication Algorithm: HMAC-SHA-1</code></p>
      <p><code>Encryption Algorithm: None</code></p>
      <p><code>[unknown extension 19]</code></p>
      <p><code>Current lifetime:</code></p>
      <p><code>36 allocations. 0 bytes</code></p>
      <p><code>added at Thu Jun 5 21:01:31 2003, first used at Thu Jun 5 21:15:07 2003</code></p>
      <p><code>Source address: 127.0.0.1/128 (IP proto 255)</code></p>
      <p><code>Dest address: 127.0.0.1/128 (IP proto 255)</code></p>
      <p><code>Authentication key. 160 bits: 0x0123456789abcdef0123456789abcdef01234567</code></p>
      <p>Из этого дампа видно, что ядро изменило значение протокола с 0 на 255. Это артефакт реализации, а не общее свойство сокетов PF_KEY. Кроме того, ядро изменило длину префикса с 32 на 128. Это какая-то проблема, связанная с протоколами IPv4 и IPv6. Ядро возвращает расширение (с номером 19), которое не обрабатывается нашей программой выведения дампа. Неизвестные расширения пропускаются (их длина имеется в соответствующем поле). Наконец, возвращается расширение времени жизни (листинг 19.7), содержащее информацию о текущем времени жизни соглашения о безопасности.</p>
      <p><strong>Листинг 19.7</strong>. Структура расширения времени жизни</p>
      <p><code>struct sadb_lifetime {</code></p>
      <p><code> u_int16_t sadb_lifetime_len;     /* длина расширения / 8 */</code></p>
      <p><code> u_int16_t sadb_lifetime_exttype; /* SADB_EXT_LIFETIME_{SOFT,HARD,CURRENT} */</code></p>
      <p><code> u_int32_t sadb_lifetime_allocations; /* количество соединений, конечных</code></p>
      <p><code>                                       точек или потоков */</code></p>
      <p><code> u_int64_t sadb_lifetime_bytes;   /* количество байтов */</code></p>
      <p><code> u_int64_t sadb_lifetime_addtime; /* время создания либо время от создания</code></p>
      <p><code>                                     до устаревания */</code></p>
      <p><code> u_int64_t sadb_lifetime_usetime; /* время первого использования или время от</code></p>
      <p><code>                                     первого использования до устаревания */</code></p>
      <p><code>};</code></p>
      <p>Расширения времени жизни бывают трех типов. Расширения <code>SADB_LIFETIME_SOFT</code> и <code>SADB_LIFETIME_HARD</code> задают гибкое и жесткое ограничения на время жизни соглашения. Сообщение <code>SADB_EXPIRE</code> отправляется ядром в случае превышения гибкого ограничения на время жизни. После достижения жесткого ограничения использование соглашения прекращается. Расширение <code>SADB_LIFETIME_CURRENT</code> возвращается в ответ на <code>SADB_DUMP</code>, <code>SADB_EXPIRE</code> и <code>SADB_GET</code> и описывает соответствующие параметры текущего соглашения.</p>
     </section>
    </section>
    <section>
     <title>
      <p>19.5. Динамическое управление SA</p>
     </title>
     <section>
      <p>Для повышения безопасности требуется периодическая смена ключей. Обычно для этого используется протокол типа IKE (RFC 2409 [43]).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В момент написания этой книги рабочая группа IETF по IPSec разрабатывала замену для протокола IKE.</p>
      </cite>
      <p>Демон, обеспечивающий безопасность, регистрируется в ядре при помощи сообщения <code>SADB_REGISTER</code>, указывая в поле <code>sadb_msg_satype</code> (см. табл. 19.2) тип соглашения о безопасности, которое он умеет обрабатывать. Если демон может работать с несколькими типами соглашений, он должен отправить несколько сообщений <code>SADB_REGISTER</code>, зарегистрировав в каждом из них ровно один тип SA. В ответном сообщении <code>SADB_REGISTER</code> ядро указывает поддерживаемые алгоритмы шифрования или аутентификации (в отдельном расширении), а также длины ключей для этих алгоритмов. Расширение поддерживаемых алгоритмов описывается структурой <code>sadb_supported</code>, представленной в листинге 19.8. Структура содержит заголовок, за которым следуют описания алгоритма шифрования или аутентификации в полях <code>sadb_alg</code>.</p>
      <p><strong>Листинг 19.8</strong>. Структура, описывающая поддерживаемые алгоритмы</p>
      <p><code>struct sadb_supported {</code></p>
      <p><code> u_int16_t sadb_supported_len;      /* длина расширения и списка алгоритмов / 8 */</code></p>
      <p><code> u_int16_t sadb_supported_exttype;  /* SADB_EXT_SUPPORTED_{AUTH,ENCRYPT} */</code></p>
      <p><code> u_int32_t sadb_supported_reserved; /* зарезервировано для расширения в будущем */</code></p>
      <p><code>};</code></p>
      <empty-line/>
      <p><code>/* далее следует список алгоритмов */</code></p>
      <p><code>struct sadb_alg {</code></p>
      <p><code> u_int8_t  sadb_alg_id;       /* идентификатор алгоритма из табл. 19.5 */</code></p>
      <p><code> u_int8_t  sadb_alg_ivlen;    /* длина IV или нуль */</code></p>
      <p><code> u_int16_t sadb_alg_minbits;  /* минимальная длина ключа */</code></p>
      <p><code> u_int16_t sadb_alg_maxbits;  /* максимальная длина ключа */</code></p>
      <p><code> u_int16_t sadb_alg_reserved; /* зарезервировано для расширения в будущем */</code></p>
      <p><code>};</code></p>
      <p>После заголовка <code>sadb_supported</code> следует по одной структуре <code>sadb_alg</code> для каждого алгоритма, поддерживаемого системой. На рис. 19.1 представлен возможный ответ на сообщение, регистрирующее обработчик SA типа <code>SADB_SATYPE_ESP</code>.</p>
      <image l:href="#img_114.png"/>
      <p><strong>Рис. 19.1</strong>. Данные, возвращаемые ядром в ответ на команду SADB_REGISTER</p>
      <p>Программа, представленная в листинге 19.9, просто регистрируется в ядре в качестве обработчика заданного механизма безопасности и выводит ответ ядра, содержащий список поддерживаемых алгоритмов.</p>
      <p><strong>Листинг 19.9</strong>. Регистрация демона-обработчика</p>
      <p><code>//key/register.c</code></p>
      <p><code> 1 void</code></p>
      <p><code> 2 sadb_register(int type)</code></p>
      <p><code> 3 {</code></p>
      <p><code> 4  int s;</code></p>
      <p><code> 5  char buf[4096]; /* XXX */</code></p>
      <p><code> 6  struct sadb_msg msg;</code></p>
      <p><code> 7  int goteof;</code></p>
      <p><code> 8  int mypid;</code></p>
      <empty-line/>
      <p><code> 9  s = Socket(PF_KEY, SOCK_RAW, PF_KEY_V2);</code></p>
      <empty-line/>
      <p><code>10  mypid = getpid();</code></p>
      <empty-line/>
      <p><code>11  /* формирование и отправка запроса SADB_REGISTER */</code></p>
      <p><code>12  bzero(&amp;msg, sizeof(msg));</code></p>
      <p><code>13  msg.sadb_msg_version = PF_KEY_V2;</code></p>
      <p><code>14  msg.sadb_msg_type = SADB_REGISTER;</code></p>
      <p><code>15  msg.sadb_msg_satype = type;</code></p>
      <p><code>16  msg.sadb_msg_len = sizeof(msg) / 8;</code></p>
      <p><code>17  msg.sadb_msg_pid = mypid;</code></p>
      <p><code>18  printf("Sending register message:\n");</code></p>
      <p><code>19  print_sadb_msg(&amp;msg, sizeof(msg));</code></p>
      <p><code>20  Write(s, &amp;msg, sizeof(msg));</code></p>
      <empty-line/>
      <p><code>21  printf("\nReply returned:\n");</code></p>
      <p><code>22  /* Чтение и вывод ответа SADB_REGISTER, игнорирование всех прочих</code></p>
      <p><code>       сообщений */</code></p>
      <p><code>23  for (;;) {</code></p>
      <p><code>24   int msglen;</code></p>
      <p><code>25   struct sadb_msg *msgp;</code></p>
      <empty-line/>
      <p><code>26   msglen = Read(s, &amp;buf, sizeof(buf));</code></p>
      <p><code>27   msgp = (struct sadb_msg*)&amp;buf;</code></p>
      <p><code>28   if (msgp-&gt;sadb_msg_pid == mypid &amp;&amp;</code></p>
      <p><code>29    msgp-&gt;sadb_msg_type == SADB_REGISTER) {</code></p>
      <p><code>30    print_sadb_msg(msgp, msglen);</code></p>
      <p><code>31    break;</code></p>
      <p><code>32   }</code></p>
      <p><code>33  }</code></p>
      <p><code>34  close(s);</code></p>
      <p><code>35 }</code></p>
      <subtitle>Открытие сокета PF_KEY</subtitle>
      <p><code>1-9</code> Мы открываем сокет PF_KEY.</p>
      <subtitle>Сохранение PID</subtitle>
      <p><code>10</code> Поскольку ядро будет адресовать нам свои сообщения по идентификатору процесса, нам необходимо сохранить его, чтобы иметь возможность впоследствии отбирать интересующие нас сообщения.</p>
      <subtitle>Создание сообщения SADB_REGISTER</subtitle>
      <p><code>11-17</code> Подобно <code>SADB_DUMP</code>, сообщение <code>SADB_REGISTER</code> не требует никаких расширений. Мы обнуляем сообщение, после чего заполняем интересующие нас поля структуры.</p>
      <subtitle>Вывод и отправка сообщения</subtitle>
      <p><code>18-20</code> Мы отображаем подготовленное сообщение на экране при помощи функции <code>print_sadb_msg</code>, после чего записываем сообщение в сокет.</p>
      <subtitle>Ожидание ответа</subtitle>
      <p><code>23-30</code> Мы считываем сообщения из сокета, ожидая ответа на наше сообщение о регистрации. Ответ адресован по идентификатору процесса и представляет собой сообщение <code>SADB_REGISTER</code>. Он содержит список поддерживаемых алгоритмов, который выводится нашей функцией <code>print_sadb_msg</code>.</p>
     </section>
     <section>
      <title>
       <p>Пример</p>
      </title>
      <p>Мы запускаем программу <code>register</code> в системе, поддерживающей на несколько протоколов больше, чем описано в RFC 2367.</p>
      <p><code>macosx % <strong>register -t ah</strong></code></p>
      <p><code>Sending register message:</code></p>
      <p><code>SADB Message Register, errno 0, satype IPsec AH, seq 0, pid 20746</code></p>
      <p><code>Reply returned:</code></p>
      <p><code>SADB Message Register, errno 0, satype IPsec AH, seq 0, pid 20746</code></p>
      <p><code>Supported authentication algorithms:</code></p>
      <p><code>HMAC-MD5 ivlen 0 bits 128-128</code></p>
      <p><code>HMAC-SHA-1 ivlen 0 bits 160-160</code></p>
      <p><code>Keyed MD5 ivlen 0 bits 128-128</code></p>
      <p><code>Keyed SHA-1 ivlen 0 bits 160-160</code></p>
      <p><code>Null ivlen 0 bits 0-2048</code></p>
      <p><code>SHA2-256 ivlen 0 bits 256-256</code></p>
      <p><code>SHA2-384 ivlen 0 bits 384-384</code></p>
      <p><code>SHA2-512 ivlen 0 bits 512-512</code></p>
      <p><code>Supported encryption algorithms:</code></p>
      <p><code>DES-CBC ivlen 8 bits 64-64</code></p>
      <p><code>3DES-CBC ivlen 8 bits 192-192</code></p>
      <p><code>Null ivlen 0 bits 0-2048</code></p>
      <p><code>Blowfish-CBC ivlen 8 bits 40-448</code></p>
      <p><code>CAST128-CBC ivlen 8 bits 40-128</code></p>
      <p><code>AES ivlen 16 bits 128-256</code></p>
      <p>Если ядру требуется связаться с собеседником, а соответствующая политика требует наличия соглашения о безопасности, но соглашение таковое отсутствует, ядро отправляет на зарегистрировавшиеся для данного типа соглашения сокеты управления ключами сообщение <code>SADB_ACQUIRE</code>, в расширениях которого содержатся предлагаемые ядром алгоритмы и длины ключей. Предложение может представлять собой комбинацию поддерживаемых системой средств безопасности и политики, ограничивающей набор средств для конкретного собеседника. Алгоритмы, длины ключей и времена жизни объединяются в список в порядке предпочтительности использования. Когда демон-ключник получает сообщение <code>SADB_ACQUIRE</code>, он выполняет действия, необходимые для выбора ключа, удовлетворяющего одной из предложенных ядром комбинаций, и устанавливает этот ключ в ядро. Для выбора SPI из нужного диапазона демон отправляет ядру сообщение <code>SADB_GETSPI</code>. В ответ на это сообщение ядро создает соглашение о безопасности в состоянии <code>SADB_SASTATE_LARVAL</code>. Затем демон согласовывает параметры безопасности с удаленным собеседником, используя предоставленный ядром SPI, после чего отправляет ядру сообщение <code>SADB_UPDATE</code> для завершения создания соглашения и перевода его в рабочее состояние (<code>SADB_SASTATE_MATURE</code>). Динамически создаваемые соглашения обычно снабжаются гибким и жестким ограничениями на время жизни. Когда истекает один из этих сроков, ядро отправляет сообщение <code>SADB_EXPIRE</code>, в котором указывается, какое именно достигнуто ограничение. По достижении гибкого ограничения соглашение переходит в состояние <code>SADB_SASTATE_DYING</code>, в котором оно еще может использоваться, однако процессу следует получить новое соглашение. Если же достигнуто жесткое ограничение, соглашение переходит в состояние <code>SADB_SASTATE_DEAD</code>, в котором оно больше не может использоваться для обеспечения безопасности и должно быть удалено из базы данных.</p>
     </section>
    </section>
    <section>
     <title>
      <p>19.6. Резюме</p>
     </title>
     <p>Сокеты управления ключами используются для взаимодействия с ядром, демонами-ключниками и другими обеспечивающими безопасность сущностями (такими как маршрутизирующие демоны). Соглашения о безопасности могут создаваться статически или динамически посредством протокола согласования ключей. Динамические ключи обычно характеризуются определенным временем жизни, по истечении которого (гибкое ограничение) демон-ключник получает соответствующее уведомление. Если соглашение не обновляется до достижения жесткого ограничения, оно становится недействительным.</p>
     <p>Между процессами и ядром через сокет управления ключами могут передаваться сообщения десяти типов. Каждому типу сообщений сопоставляются обязательные и необязательные расширения. Все сообщения, отправляемые процессом, передаются на все открытые сокеты управления ключами (однако при этом из сообщений удаляются расширения, содержащие «уязвимые» данные).</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Напишите программу, открывающую сокет PF_KEY и выводящую все получаемые через этот сокет сообщения.</p>
     <p>2. Изучите сведения о новом протоколе, предложенном рабочей группой IETF по IPSec взамен IKE. Эти сведения находятся на странице <code>http://www.ietf.org/html.charters/ipsec-charter.html</code>.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 20</p>
     <p>Широковещательная передача</p>
    </title>
    <section>
     <title>
      <p>20.1. Введение</p>
     </title>
     <p>В этой главе мы расскажем о <emphasis>широковещательной передаче</emphasis> (<emphasis>brodacasting</emphasis>), а в следующей главе — о <emphasis>многоадресной передаче</emphasis> (<emphasis>multicasting</emphasis>). Во всех предыдущих примерах рассматривалась <emphasis>направленная</emphasis> (<emphasis>одноадресная</emphasis>) <emphasis>передача</emphasis> (<emphasis>unicasting</emphasis>), когда процесс общается только с одним определенным процессом. Действительно, TCP работает только с адресами направленной передачи, хотя UDP и символьные сокеты поддерживают и другие парадигмы передачи. В табл. 20.1 представлено сравнение различных видов адресации.</p>
     <empty-line/>
     <p><strong>Таблица 20.1</strong>. Различные формы адресации</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Тип</th>
       <th align="left" valign="top">IPv4</th>
       <th align="left" valign="top">Ipv6</th>
       <th align="left" valign="top">TCP</th>
       <th align="left" valign="top">UDP</th>
       <th align="left" valign="top">Количество идентифицируемых интерфейсов</th>
       <th align="left" valign="top">Количество интерфейсов, куда доставляется сообщение</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Направленная передача</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Один</td>
       <td align="left" valign="top">Один</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Передача наиболее подходящему узлу</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Пока нет</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Набор</td>
       <td align="left" valign="top">Один из набора</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Многоадресная передача</td>
       <td align="left" valign="top">Не обязательно</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Набор</td>
       <td align="left" valign="top">Все в наборе</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">Широковещательная передача</td>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top"/>
       <td align="left" valign="top"/>
       <td align="left" valign="top">•</td>
       <td align="left" valign="top">Все</td>
       <td align="left" valign="top">Все</td>
      </tr>
     </table>
     <p>С введением IPv6 к парадигмам адресации добавилась <emphasis>передача наиболее подходящему узлу</emphasis> (<emphasis>anycasting</emphasis>). Ее вариант для IPv4 не получил широкого распространения. Он описан в RFC 1546 [88]. Передача наиболее подходящему узлу для IPv6 определяется в документе RFC 3513 [44]. Этот режим позволяет обращаться к одной (обычно «ближайшей» в некоторой метрике) из множества систем, предоставляющих одинаковые сервисы. Правильная конфигурация системы маршрутизации позволяет узлам пользоваться сервисами передачи наиболее подходящему узлу по IPv4 и IPv6 путем добавления одного и того же адреса в протокол маршрутизации в нескольких местах. Однако RFC 3513 разрешает иметь адреса такого типа только маршрутизаторам; узлы не имеют права предоставлять сервисы передачи наиболее подходящему узлу. На момент написания этой книги интерфейс API для использования адресов передачи наиболее подходящему узлу еще не определен. Архитектура IPv6 в настоящий момент находится на стадии совершенствования, и в будущем узлы, вероятно, получат возможность динамически предоставлять сервисы передачи наиболее подходящему узлу.</p>
     <p>Вот наиболее важные положения из табл. 20.1:</p>
     <p>&#9632; Поддержка многоадресной передачи не обязательна для IPv4, но обязательна для IPv6.</p>
     <p>&#9632; Поддержка широковещательной передачи не обеспечивается в IPv6: любое приложение IPv4, использующее широковещательную передачу, для совместимости с IPv6 должно быть преобразовано так, чтобы использовать вместо широковещательной передачи многоадресную.</p>
     <p>&#9632; Широковещательная и многоадресная передачи требуют наличия протокола UDP или символьного IP и не работают с TCP.</p>
     <p>Одним из применений широковещательной передачи является поиск сервера в локальной подсети, когда известно, что сервер находится в этой локальной подсети, но его IP-адрес для направленной передачи неизвестен. Иногда эту процедуру называют <emphasis>обнаружением ресурса</emphasis> (<emphasis>resource discovery</emphasis>). Другое применение — минимизация сетевого трафика в локальной сети, когда несколько клиентов взаимодействуют с одним сервером. Можно привести множество примеров интернет-приложений, использующих для этой цели широковещательную передачу. Некоторые из них используют и многоадресную передачу.</p>
     <p>&#9632; Протокол разрешения адресов (Address Resolution Protocol, ARP). Это фундаментальная часть IPv4, а не пользовательское приложение. ARP отправляет широковещательный запрос в локальную подсеть, суть которого такова: «Система с IP-адресом a.b.c.d, идентифицируйте себя и сообщите свой аппаратный адрес».</p>
     <p>&#9632; Протокол начальной загрузки (Bootstrap Protocol, BOOTP). Клиент предполагает, что сервер находится в локальной подсети, и посылает запрос на широковещательный адрес (часто 255.255.255.255, поскольку клиент еще не знает IP-адреса, маски подсети и адреса ограниченной широковещательной передачи в этой подсети).</p>
     <p>&#9632; Протокол синхронизации времени (Network Time Protocol, NTP). В обычном сценарии клиент NTP конфигурируется с IP-адресом одного или более серверов, которые будут использоваться для определения времени, и опрашивает серверы с определенной частотой (с периодом 64 с или больше). Клиент обновляет свои часы, используя сложные алгоритмы, основанные на значении истинного времени (time-of-day), возвращаемом серверами, и величине периода RTT обращения к серверам. Но в широковещательной локальной сети вместо того, чтобы каждый клиент обращался к одному серверу, сервер может отправлять текущее значение времени с помощью широковещательных сообщений каждые 64 с для всех клиентов в локальной подсети, ограничивая тем самым сетевой трафик.</p>
     <p>&#9632; Демоны маршрутизации. Наиболее часто используемый демон маршрутизации <code>routed</code> распространяет по локальной сети широковещательные сообщения, содержащие таблицу маршрутизации. Это позволяет всем другим маршрутизаторам, соединенным с локальной сетью, получать объявления маршрутизации. При этом в конфигурацию каждого маршрутизатора не обязательно должны входить IP-адреса соседних маршрутизаторов. Это свойство также используется (многие могут отметить, что «используется неправильно») узлами локальной сети, прослушивающими объявления о маршрутизации и изменяющими в соответствии с этим свои таблицы маршрутизации.</p>
     <p>Следует отметить, что многоадресная передача может заменить оба варианта применения широковещательной передачи (обнаружение ресурса и ограничение сетевого трафика). Проблемы широковещательной передачи мы обсудим далее в этой главе, а также в следующей главе.</p>
    </section>
    <section>
     <title>
      <p>20.2. Широковещательные адреса</p>
     </title>
     <p>Если мы обозначим адрес IPv4 в виде <code>{subnetid, hostid}</code>, где <code>subnetid</code> означает биты, относящиеся к маске сети (или префиксу CIDR), a <code>hostid</code> — все остальные биты, мы получим два типа широковещательных адресов. Поле, целиком состоящее из единичных битов, обозначим -1.</p>
     <p>1. Широковещательный адрес подсети: <code>{subnetid, -1}</code>. Сообщение адресуется на все интерфейсы в заданной подсети. Например, в подсети 192.168.42/24 широковещательным адресом будет 192.168.42.255.</p>
     <p>Обычно маршрутизаторы не передают широковещательные сообщения дальше из подсети [128, с. 226-227]. На рис. 20.1 изображен маршрутизатор, соединенный с двумя подсетями 192.168.42/24 и 192.168.123/24.</p>
     <image l:href="#img_115.png"/>
     <p><strong>Рис. 20.1</strong>. Передает ли маршрутизатор дальше широковещательное сообщение, направленное в подсеть?</p>
     <p>Маршрутизатор получает дейтаграмму IP направленной передачи в подсети 192.168.123/24 с адресом получателя 192.168.42.255 (адрес широковещательной передачи для подсети другого интерфейса). Обычно маршрутизатор не передает дейтаграмму дальше в подсеть 192.168.42/24. У некоторых систем имеется параметр конфигурации, позволяющий передавать широковещательные сообщения, направленные в подсеть (см. приложение Е [111]).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Пересылка широковещательных сообщений, направленных в подсеть, делает возможным атаки типа «отказ в обслуживании» особого класса, получившего название «усиление» (amplification). Например, отправка эхо-запроса ICMP на широковещательный адрес подсети может привести к получению нескольких ответов. При подмене IP-адреса отправителя это дает возможность загрузить канал жертвы, снизив ее доступность. По этой причине рекомендуется отключать соответствующий параметр настройки маршрутизаторов.</p>
      <p>Учитывая вышеизложенное, не рекомендуется писать приложения, рассчитанные на пересылку широковещательных сообщений из одной подсети в другую, за исключением тех случаев, когда приложение разрабатывается для использования в полностью контролируемой сети, где включение пересылки не приведет к нарушению безопасности.</p>
     </cite>
     <p>2. Локальный широковещательный адрес: {-1,-1} или 255.255.255.255. Дейтаграммы, предназначенные для этого ограниченного адреса, никогда не должны передаваться маршрутизатором.</p>
     <p>Из четырех типов широковещательных адресов адрес широкого вещания для подсети является на сегодняшний день наиболее общим. Но более старые системы продолжают отправлять дейтаграммы, предназначенные для адреса 255.255. 255.255. Кроме того, некоторые еще более старые системы не воспринимают широковещательный адрес подсети и только отправляемые на адрес 255.255.255.255 дейтаграммы интерпретируют как широковещательные.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Адрес 255.255.255.255 предназначен для использования в качестве адреса получателя во время процесса начальной загрузки такими приложениями, как DHCP и BOOTP, которым еще не известен IP-адрес узла.</p>
      <p>Возникает вопрос: что делает узел, когда приложение посылает дейтаграмму UDP на адрес 255.255.255.255? Большинство узлов допускают это (если процесс установил параметр сокета SO_BROADCAST) и преобразуют адрес получателя в широковещательный адрес исходящего интерфейса, направленный в подсеть. Для отправки пакета на конкретный адрес 255.255.255.255 часто приходится работать непосредственно с канальным уровнем.</p>
      <p>Может появиться другой вопрос: что делает узел с несколькими сетевыми интерфейсами, когда приложение посылает дейтаграмму UDP на адрес 255.255.255.255? Некоторые системы посылают одно широковещательное сообщение с основного интерфейса (с интерфейса, который был сконфигурирован первым) с IP-адресом получателя, равным широковещательному адресу подсети этого интерфейса [128, с. 736]. Другие системы посылают по одной копии дейтаграммы с каждого интерфейса, поддерживающего широковещательную передачу. В разделе 3.3.6 RFC 1122 [10] по этому вопросу не сказано ничего. Однако если приложению нужно отправить широковещательное сообщение со всех интерфейсов, поддерживающих широковещательную передачу, то в целях переносимости оно должно получить конфигурацию интерфейсов (см. раздел 16.6) и выполнить по одному вызову sendto для каждого из них, указав в качестве адреса получателя широковещательный адрес подсети этого интерфейса.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>20.3. Направленная и широковещательная передачи</p>
     </title>
     <p>Прежде чем рассматривать широковещательную передачу, необходимо уяснить, что происходит, когда дейтаграмма UDP отправляется на адрес направленной передачи. На рис. 20.2 представлены три узла Ethernet.</p>
     <image l:href="#img_116.png"/>
     <p><strong>Рис. 20.2</strong>. Пример направленной передачи дейтаграммы UDP</p>
     <p>Адрес подсети Ethernet — 192.168.42/24. 24 разряда адреса относятся к маске сети, а 8 разрядов — к идентификатору узла. Приложение на узле, изображенном слева, вызывает функцию sendto для сокета UDP, отправляя дейтаграмму на адрес 192.168.42.3, порт 7433. Уровень UDP добавляет в начало дейтаграммы заголовок UDP и передает дейтаграмму UDP уровню IP. IP добавляет заголовок IPv4 и определяет исходящий интерфейс. В случае использования сети Ethernet активизируется протокол ARP для определения адреса Ethernet, соответствующего IP-адресу получателя: <code>08:00:20:03:f6:42</code>. Затем пакет посылается как кадр Ethernet с 48-разрядным адресом получателя Ethernet. Поле типа кадра Ethernet будет равно <code>0x0800</code>, что соответствует пакету IPv4. Тип кадра для пакета IPv6 — <code>0x86dd</code>.</p>
     <p>Интерфейс Ethernet на узле, изображенном в центре, видит проходящий кадр и сравнивает адрес получателя Ethernet со своим собственным адресом Ethernet (<code>02:60:8c:2f:4e:00</code>). Поскольку они не равны, интерфейс игнорирует кадр. Поскольку кадр является кадром направленной передачи, этот узел не тратит на его обработку никаких ресурсов. Интерфейс игнорирует кадр.</p>
     <p>Интерфейс Ethernet на узле, изображенном справа, также видит проходящий кадр, и когда он сравнивает адрес получателя Ethernet со своим собственным адресом Ethernet, они оказываются одинаковыми. Этот интерфейс считывает весь кадр, возможно, генерирует аппаратное прерывание при завершении считывания кадра и драйвер устройства читает кадр из памяти интерфейса. Поскольку тип кадра — <code>0x0800</code>, пакет помещается в очередь ввода IP.</p>
     <p>Когда уровень IP обрабатывает пакет, он сначала сравнивает IP-адрес получателя (192.168.42.3) со всеми собственными IP-адресами. (Вспомним, что узел может иметь несколько сетевых интерфейсов. Также вспомним наше обсуждение модели системы с жесткой привязкой (strong end system model) и системы с гибкой привязкой (weak end system model) в разделе 8.8.) Поскольку адрес получателя — это один из собственных IP-адресов узла, пакет принимается.</p>
     <p>Затем уровень IP проверяет поле протокола в заголовке IPv4. Его значение для UDP равно 17, поэтому далее дейтаграмма IP передается UDP.</p>
     <p>Уровень UDP проверяет порт получателя (и, возможно, также порт отправителя, если сокет UDP является присоединенным) и в нашем примере помещает дейтаграмму в соответствующий приемный буфер сокета. При необходимости процесс возобновляется для чтения вновь полученной дейтаграммы.</p>
     <p>Ключевым моментом на этом рисунке является то, что дейтаграмма IP при направленной передаче принимается только одним узлом, заданным с помощью IP-адреса получателя. Другие узлы подсети не задействуются в этом процессе.</p>
     <p>Теперь мы рассмотрим похожий пример в той же подсети, но при этом приложение будет отправлять дейтаграмму UDP на широковещательный адрес для подсети 192.168.42.255. Этот пример представлен на рис. 20.3.</p>
     <image l:href="#img_117.png"/>
     <p><strong>Рис. 20.3</strong>. Пример широковещательной дейтаграммы UDP</p>
     <p>Когда узел, изображенный слева, отправляет дейтаграмму, он замечает, что IP-адрес получателя — это широковещательный адрес подсети, и сопоставляет ему адрес Ethernet, состоящий из 48 единичных битов: <code>ff:ff:ff:ff:ff:ff</code>. Это заставляет <emphasis>каждый</emphasis> интерфейс Ethernet в подсети получить кадр. Оба узла, изображенные на правой части рисунка, работающие с IPv4, получат кадр. Поскольку тип кадра Ethernet — <code>0800</code>, оба узла передают пакет уровню IP. Так как IP-адрес получателя совпадает с широковещательным адресом для каждого из двух узлов, и поскольку поле протокола — 17 (UDP), оба узла передают пакет UDP.</p>
     <p>Узел, изображенный справа, передает дейтаграмму UDP приложению, связанному с портом UDP 520. Приложению не нужно выполнять никаких специальных действий, чтобы получить широковещательную дейтаграмму UDP — оно лишь создает сокет UDP и связывает номер приложения порта с сокетом. (Предполагается, как обычно, что связанный IP-адрес — <code>INADDR_ANY</code>.)</p>
     <p>Но на узле, изображенном в центре, с портом UDP 520 не связано никакое приложение. UDP этого узла игнорирует полученную дейтаграмму. Узел не должен отправлять сообщение ICMP о недоступности порта, поскольку это может вызвать <emphasis>лавину широковещательных сообщений</emphasis> (<emphasis>broadcast storm</emphasis>): ситуацию, в которой множество узлов сети генерируют ответы приблизительно в одно и то же время, в результате чего сеть просто невозможно использовать в течение некоторого времени. Кроме того, не совсем понятно, что должен предпринять получатель сообщения об ошибке: что, если некоторые получатели будут сообщать об ошибках, а другие — нет?</p>
     <p>В этом примере мы также показываем дейтаграмму, которую изображенный слева узел доставляет сам себе. Это свойство широковещательных сообщений: по определению широковещательное сообщение идет к каждому узлу подсети, включая отправляющий узел [128, с. 109–110]. Мы также предполагаем, что отправляющее приложение связано с портом, на который оно отправляет дейтаграммы (порт 520), поэтому оно получит копию каждой отправленной им широковещательной дейтаграммы. (Однако в общем случае не требуется, чтобы процесс связывался с портом UDP, на который он отправляет дейтаграммы.)</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В этом примере мы демонстрируем закольцовку, которая осуществляется либо на уровне IP, либо на канальном уровне, создающем копию [128, с. 109-110] и отправляющем ее вверх по стеку протоколов. Сеть могла бы использовать физическую закольцовку, но это может вызвать проблемы в случае сбоев сети (например, линия Ethernet без терминатора).</p>
     </cite>
     <p>Этот пример отражает фундаментальную проблему, связанную с широковещательной передачей: каждый узел IPv4 в подсети, даже не выполняющий соответствующего приложения, должен полностью обрабатывать широковещательную дейтаграмму UDP при ее прохождении вверх по стеку протоколов, включая уровень UDP, прежде чем сможет ее проигнорировать. (Вспомните наше обсуждение следом за листингом 8.11). Более того, каждый не-IP-узел в подсети (скажем, узел, на котором работает IPX Novell) должен также получать целый кадр на канальном уровне, перед тем как он сможет проигнорировать этот кадр (в данном случае мы предполагаем, что узел не поддерживает кадры определенного типа — для дейтаграммы IPv4 тип равен <code>0x0800</code>). Если приложение генерирует дейтаграммы IP с большой скоростью (например, аудио- или видеоданные), то такая ненужная обработка может серьезно повлиять на остальные узлы подсети. В следующей главе мы увидим, как эта проблема решается с помощью многоадресной передачи.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Для рис. 20.3 мы специально выбрали порт UDP 520. Это порт, используемый демоном routed для обмена пакетами по протоколу информации о маршрутизации (Routing Information Protocol, RIP). Все маршрутизаторы в подсети, использующие RIP, будут отправлять широковещательную дейтаграмму UDP каждые 30 секунд. Если в подсети имеется 200 узлов, в том числе два маршрутизатора, использующих RIP, то 198 узлов должны будут обрабатывать (и игнорировать) эти широковещательные дейтаграммы каждые 30 с, если ни на одном из них не запущен демон routed. Протокол RIP версии 2 использует многоадресную передачу именно для того, чтобы избавиться от этой проблемы.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>20.4. Функция dg_cli при использовании широковещательной передачи</p>
     </title>
     <section>
      <p>Мы еще раз изменим нашу функцию <code>dg_cli</code>, на этот раз дав ей возможность отправлять широковещательные сообщения стандартному серверу времени и даты UDP (см. табл. 2.1) и выводить все ответы. Единственное изменение, внесенное нами в функцию <code>main</code> (см. листинг 8.3), состоит в изменении номера порта получателя на 13:</p>
      <p><code>servaddr.sin_port = htons(13);</code></p>
      <p>Сначала мы откомпилируем измененную функцию <code>main</code> с прежней функцией <code>dg_cli</code> из листинга 8.4 и запустим ее на узле <code>freebsd</code>:</p>
      <p><code>freebsd % <strong>udpcli01 192.168.42.255</strong></code></p>
      <p><code><strong>hi</strong></code></p>
      <p><code>sendto error: Permission denied</code></p>
      <p>Аргумент командной строки — это широковещательный адрес подсети для присоединенной сети Ethernet. Мы вводим строку, программа вызывает функцию <code>sendto</code>, и возвращается ошибка <code>EACCESS</code>. Мы получаем ошибку, потому что нам не разрешается посылать дейтаграмму на широковещательный адрес получателя, если мы не указали ядру явно, что будем передавать широковещательное сообщение. Мы выполняем это условие, установив параметр сокета <code>SO_BROADCAST</code> (см. табл. 7.1).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Беркли-реализации реализуют эту «защиту от дурака» (sanity check). Однако Solaris 2.5 принимает дейтаграмму, предназначенную для широковещательного адреса, даже если мы не задаем параметр сокета SO_BROADCAST. Стандарт POSIX требует установки параметра сокета SO_BROADCAST для отправки широковещательной дейтаграммы.</p>
       <p>В 4.2BSD широковещательная передача была привилегированной операцией, и параметра сокета SO_BROADCAST не существовало. В 4.3BSD этот параметр был добавлен и каждому процессу стало разрешено его устанавливать.</p>
      </cite>
      <p>Теперь мы изменим нашу функцию <code>dg_cli</code>, как показано в листинге 20.1<a l:href="#n1" type="note">[1]</a>. Эта версия устанавливает параметр сокета <code>SO_BROADCAST</code> и выводит все ответы, полученные в течение 5 с.</p>
      <p><strong>Листинг 20.1</strong>. Функция dg_cli, осуществляющая широковещательную передачу</p>
      <p><code>//bcast/dgclibcast1.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 static void recvfrom_alarm(int);</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int n;</code></p>
      <p><code> 7  const int on = 1;</code></p>
      <p><code> 8  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
      <p><code> 9  socklen_t len;</code></p>
      <p><code>10  struct sockaddr *preply_addr;</code></p>
      <empty-line/>
      <p><code>11  preply_addr = Malloc(servlen);</code></p>
      <empty-line/>
      <p><code>12  Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &amp;on, sizeof(on));</code></p>
      <empty-line/>
      <p><code>13  Signal(SIGALRM, recvfrom_alarm);</code></p>
      <empty-line/>
      <p><code>14  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <empty-line/>
      <p><code>15   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
      <empty-line/>
      <p><code>16   alarm(5);</code></p>
      <empty-line/>
      <p><code>17   for (;;) {</code></p>
      <p><code>18    len = servlen;</code></p>
      <p><code>19    n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &amp;len);</code></p>
      <p><code>20    if (n &lt; 0) {</code></p>
      <p><code>21     if (errno == EINTR)</code></p>
      <p><code>22      break; /* окончание ожидания ответов */</code></p>
      <p><code>23     else</code></p>
      <p><code>24      err_sys("recvfrom error");</code></p>
      <p><code>25    } else {</code></p>
      <p><code>26     recvline[n] = 0; /* завершающий нуль */</code></p>
      <p><code>27     printf("from %s: %s",</code></p>
      <p><code>28      Sock_ntop_host(preply_addr, len), recvline);</code></p>
      <p><code>29    }</code></p>
      <p><code>30   }</code></p>
      <p><code>31  }</code></p>
      <p><code>32  free(preply_addr);</code></p>
      <p><code>33 }</code></p>
      <empty-line/>
      <p><code>34 static void</code></p>
      <p><code>35 recvfrom_alarm(int signo)</code></p>
      <p><code>36 {</code></p>
      <p><code>37  return; /* прерывание recvfrom() */</code></p>
      <p><code>38 }</code></p>
      <subtitle>Выделение памяти для адреса сервера, установка параметра сокета</subtitle>
      <p><code>11-13</code> Функция <code>malloc</code> выделяет в памяти пространство для адреса сервера, возвращаемого функцией <code>recvfrom</code>. Устанавливается параметр сокета <code>SO_BROADCAST</code>, устанавливается обработчик сигнала <code>SIGALRM</code>.</p>
      <subtitle>Чтение строки, отправка сокету, чтение всех ответов</subtitle>
      <p><code>14-24</code> Следующие два вызова, <code>fgets</code> и <code>sendto</code>, выполняются так же, как и в предыдущих версиях этой функции. Но поскольку мы посылаем широковещательную дейтаграмму, мы можем получить множество ответов. Мы вызываем в цикле функцию <code>recvfrom</code> и выводим все ответы, полученные в течение 5 с. По истечении 5 с генерируется сигнал <code>SIGALRM</code>, вызывается наш обработчик сигнала и функция <code>recvfrom</code> возвращает ошибку <code>EINTR</code>.</p>
      <subtitle>Вывод каждого полученного ответа</subtitle>
      <p><code>25-29</code> Для каждого полученного ответа мы вызываем функцию <code>sock_ntop_host</code>, которая в случае IPv4 возвращает строку, содержащую IP-адрес сервера в точечно-десятичной записи. Эта строка выводится вместе с ответом сервера.</p>
      <p>Если мы запустим программу, задав широковещательный адрес подсети 192. 168.42.255, мы увидим следующее:</p>
      <p><code>bsdi % <strong>udpcli01 192.168.42.255 hi</strong></code></p>
      <p><code>from 192 168.42 2: Sat Aug 2 16.42.45 2003</code></p>
      <p><code>from 192.168.42.1: Sat Aug 2 14.42.45 2003</code></p>
      <p><code>from 192.168.42.3: Sat Aug 2 14.42.45 2003</code></p>
      <p><code><strong>hello</strong></code></p>
      <p><code>from 192.168.42.3: Sat Aug 2 14.42.57 2003</code></p>
      <p><code>from 192.168.42.2: Sat Aug 2 16.42.57 2003</code></p>
      <p><code>from 192.168.42.1: Sat Aug 2 14.42.57 2003</code></p>
      <p>Каждый раз мы набираем строку ввода, чтобы сгенерировать выходную дейтаграмму UDP, и каждый раз получаем три ответа, причем отвечает и отправляющий узел. Как мы отмечали ранее, получателями широковещательной дейтаграммы являются все узлы в сети, включая отправляющий. Каждый ответ является направленным, поскольку адрес отправителя запроса, используемый каждым сервером в качестве адреса получателя ответа, — это адрес направленной передачи.</p>
      <p>Все системы сообщают одно и то же время, поскольку на них используется NTP (Network Time Protocol — протокол синхронизации времени).</p>
     </section>
     <section>
      <title>
       <p>Фрагментация IP-пакетов и широковещательная передача</p>
      </title>
      <p>В Беркли-ядрах фрагментация широковещательных дейтаграмм запрещена. Если размер IP-дейтаграммы, посылаемой на широковещательный адрес, превышает размер MTU исходящего интерфейса, возвращается ошибка <code>EMSGSIZE</code> [128, с. 233–234]. Эта стратегия впервые появилась в 4.2BSD. На самом деле нет никаких технических препятствий для фрагментирования широковещательных дейтаграмм, но широковещательная передача сама по себе связана со значительной нагрузкой на сеть, поэтому не стоит дополнительно увеличивать эту нагрузку, используя фрагментацию.</p>
      <p>Можно наблюдать этот сценарий с нашей программой из листинга 20.1. Мы перенаправляем стандартный поток ввода для чтения из файла, содержащего 2000-байтовую строку, которая потребует фрагментации в Ethernet:</p>
      <p><code>bsdi % <strong>udpcli01 192.168.42.255 &lt; 2000line</strong></code></p>
      <p><code>sendto error: Message too long</code></p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Это ограничение реализовано в AIX, FreeBSD и MacOS. Linux, Solaris и HP-UX фрагментируют дейтаграммы, отправленные на широковещательный адрес. Однако в целях переносимости приложение, которому нужно сделать широковещательный запрос, должно определять MTU для интерфейса, через который будет отправлено сообщение, при помощи параметра SIOCGIPMTU функции ioctl, после чего вычесть размер заголовков IP и транспортного протокола. Альтернативный подход: выбрать типичное значение MTU (например, 1500 для Ethernet) и использовать его в качестве константы.</p>
      </cite>
     </section>
    </section>
    <section>
     <title>
      <p>20.5. Ситуация гонок</p>
     </title>
     <section>
      <p><emphasis>Ситуация гонок</emphasis> (<emphasis>race condition</emphasis>) обычно возникает, когда множество процессов получают доступ к общим для них данным, но корректность результата зависит от порядка выполнения процессов. Поскольку порядок выполнения процессов в типичных системах Unix зависит от множества факторов, которые могут меняться от запуска к запуску, иногда результат корректен, а иногда — нет. Наиболее сложным для отладки типом гонок является такой, когда результат получается некорректным только изредка. Более подробно о ситуациях гонок мы поговорим в главе 26, когда будем обсуждать взаимные исключения (mutex) и условные переменные (condition variables). При программировании потоков всегда возникают проблемы с ситуациями гонок, поскольку значительное количество данных является общим для всех потоков (например, все глобальные переменные).</p>
      <p>Ситуации гонок другого типа часто возникают при работе с сигналами. Проблемы возникают, потому что сигнал, как правило, может быть доставлен в любой момент во время выполнения нашей программы. POSIX позволяет нам <emphasis>блокировать</emphasis> доставку сигнала, но при выполнении операций ввода-вывода это часто не дает эффекта.</p>
      <p>Чтобы понять эту проблему, рассмотрим пример. Ситуация гонок возникает при выполнении программы из листинга 20.1. Потратьте несколько минут и посмотрите, сможете ли вы ее обнаружить. (<emphasis>Подсказка</emphasis>: в каком месте программы мы можем находиться, когда доставляется сигнал?) Вы можете также инициировать ситуацию гонок следующим образом: изменить аргумент функции <code>alarm</code> с 5 на 1 и добавить вызов <code>sleep(1)</code> сразу же после <code>printf</code>.</p>
      <p>Когда мы после внесения этих изменений наберем первую строку ввода, эта строка будет отправлена как широковещательное сообщение, а мы установим аргумент функции <code>alarm</code> равным 1 с. Мы блокируемся в вызове функции <code>recvfrom</code>, а затем для нашего сокета приходит первый ответ, вероятно, в течение нескольких миллисекунд. Ответ возвращается функцией <code>recvfrom</code>, но затем мы входим в спящее состояние на одну секунду. Принимаются остальные ответы и помещаются в приемный буфер сокета. Но пока мы находимся в спящем состоянии, время таймера <code>alarm</code> истекает и генерируется сигнал <code>SIGALRM</code>. При этом вызывается наш обработчик сигнала, затем он возвращает управление и прерывает функцию <code>sleep</code>, в которой мы блокированы. Далее мы повторяем цикл и читаем установленные в очередь ответы с паузой в одну секунду каждый раз, когда выводится ответ. Прочитав все ответы, мы снова блокируемся в вызове функции <code>recvfrom</code>, однако таймер уже не работает. Мы окажемся навсегда заблокированы в вызове функции <code>recvfrom</code>. Фундаментальная проблема здесь в том, что наша цель — обеспечить прерывание блокирования в функции <code>recvfrom</code> обработчиком сигнала, однако сигнал может быть доставлен в любое время, и наша программа в момент доставки сигнала может находиться в любом месте бесконечного цикла <code>for</code>.</p>
      <p>Теперь мы проанализируем четыре различных варианта решения этой проблемы: одно некорректное и три различных корректных решения.</p>
      <subtitle>Блокирование и разблокирование сигнала</subtitle>
      <p>Наше первое (некорректное) решение снижает вероятность появления ошибки, блокируя сигнал и предотвращая его доставку, пока наша программа выполняет оставшуюся часть цикла <code>for</code>. Эта версия представлена в листинге 20.2.</p>
      <p><strong>Листинг 20.2</strong>. Блокирование сигналов при выполнении в цикле for (некорректное решение)</p>
      <p><code>//bcast/dgclibcast3.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 static void recvfrom_alarm(int);</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int n;</code></p>
      <p><code> 7  const int on = 1;</code></p>
      <p><code> 8  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
      <p><code> 9  sigset_t sigset_alrm;</code></p>
      <p><code>10  socklen_t len;</code></p>
      <p><code>11  struct sockaddr *preply_addr;</code></p>
      <empty-line/>
      <p><code>12  preply_addr = Malloc(servlen);</code></p>
      <empty-line/>
      <p><code>13  Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &amp;on, sizeof(on));</code></p>
      <empty-line/>
      <p><code>14  Sigemptyset(&amp;sigset_alrm);</code></p>
      <p><code>15  Sigaddset(&amp;sigset_alrm, SIGALRM);</code></p>
      <empty-line/>
      <p><code>16  Signal(SIGALRM, recvfrom_alarm);</code></p>
      <empty-line/>
      <p><code>17  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <p><code>18   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
      <p><code>19   alarm(5);</code></p>
      <p><code>20   for (;;) {</code></p>
      <p><code>21    len = servlen;</code></p>
      <p><code>22    Sigprocmask(SIG_UNBLOCK, &amp;sigset_alrm, NULL);</code></p>
      <p><code>23    n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &amp;len);</code></p>
      <p><code>24    Sigprocmask(SIG_BLOCK, &amp;sigset_alrm, NULL);</code></p>
      <p><code>25    if (n &lt; 0) {</code></p>
      <p><code>26     if (errno == EINTR)</code></p>
      <p><code>27      break; /* окончание ожидания ответа */</code></p>
      <p><code>28     else</code></p>
      <p><code>29      err_sys("recvfrom error");</code></p>
      <p><code>30    } else {</code></p>
      <p><code>31     recvline[n] = 0; /* завершающий нуль */</code></p>
      <p><code>32     printf("from %s: %s",</code></p>
      <p><code>33     Sock_ntop_host(preply_addr, len), recvline);</code></p>
      <p><code>34    }</code></p>
      <p><code>35   }</code></p>
      <p><code>36  }</code></p>
      <p><code>37  free(preply_addr);</code></p>
      <p><code>38 }</code></p>
      <empty-line/>
      <p><code>39 static void</code></p>
      <p><code>40 recvfrom_alarm(int signo)</code></p>
      <p><code>41 {</code></p>
      <p><code>42  return; /* выход из recvfrom() */</code></p>
      <p><code>43 }</code></p>
      <subtitle>Объявление набора сигналов и инициализация</subtitle>
      <p><code>14-15</code> Мы объявляем набор сигналов, инициализируем его как пустой набор (<code>sigemptyset</code>) и включаем бит, соответствующий сигналу <code>SIGALRM</code> (<code>sigaddset</code>).</p>
      <subtitle>Разблокирование и блокирование сигнала</subtitle>
      <p><code>21-24</code> Перед вызовом функции <code>recvfrom</code> мы разблокируем сигнал (с тем, чтобы он мог быть доставлен, пока наша программа блокирована), а затем блокируем его, как только завершается функция <code>recvfrom</code>. Если сигнал генерируется (истекает время таймера), когда сигнал блокирован, то ядро запоминает этот факт, но доставить сигнал (то есть вызвать наш обработчик) не может, пока сигнал не будет разблокирован. В этом состоит принципиальная разница между <emphasis>генерацией</emphasis> сигнала и его <emphasis>доставкой</emphasis>. В главе 10 [110] предоставлена более подробная информация обо всех аспектах обработки сигналов POSIX.</p>
      <p>Если мы откомпилируем и запустим эту программу, нам будет казаться, что она работает нормально, но все программы, порождающие ситуацию гонок, большую часть времени работают без каких-либо проблем! Проблема остается: разблокирование сигнала, вызов функции <code>recvfrom</code> и блокирование сигнала — все эти действия являются независимыми системными вызовами. Будем считать, что функция <code>recvfrom</code> возвращает последний ответ на нашу дейтаграмму, а сигнал доставляется между вызовом функции <code>recvfrom</code> и блокированием сигнала. Следующий вызов функции <code>recvfrom</code> заблокируется навсегда. Мы ограничили размер окна, но проблема осталась.</p>
      <p>Вариантом решения может быть установка глобального флага при доставке сигнала его обработчиком:</p>
      <p><code>recvfrom_alarm(int signo) {</code></p>
      <p><code> had_alarm = 1;</code></p>
      <p><code> return;</code></p>
      <p><code>}</code></p>
      <p>Флаг сбрасывается в 0 каждый раз, когда вызывается функция <code>alarm</code>. Наша функция <code>dg_cli</code> проверяет этот флаг перед вызовом функции <code>recvfrom</code> и не вызывает ее, если флаг ненулевой.</p>
      <p><code>for (;;) {</code></p>
      <p><code> len = servlen;</code></p>
      <p><code> Sigprocmask(SIG_UNBLOCK, &amp;sigset_alrm, NULL);</code></p>
      <p><code> if (had_alarm == 1)</code></p>
      <p><code>  break;</code></p>
      <p><code> n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &amp;len);</code></p>
      <p>Если сигнал был сгенерирован во время его блокирования (после предыдущего возвращения из функции <code>recvfrom</code>), то после разблокирования в этой части кода он будет доставлен перед завершением функции <code>sigprocmask</code>, устанавливающей наш флаг. Однако между проверкой флага и вызовом функции <code>recvfrom</code> существует промежуток времени, в течение которого сигнал может быть сгенерирован и доставлен, и если это произойдет, вызов функции <code>recvfrom</code> заблокируется навсегда (разумеется, мы считаем при этом, что не приходит никаких дополнительных ответов).</p>
     </section>
     <section>
      <title>
       <p>Блокирование и разблокирование сигнала с помощью функции pselect</p>
      </title>
      <p>Одним из корректных решений будет использование функции <code>pselect</code> (см. раздел 6.9), как показано в листинге 20.3.</p>
      <p><strong>Листинг 20.3</strong>. Блокирование и разблокирование сигналов с помощью функции pselect</p>
      <p><code>//bcast/dgclibcast4.с</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 static void recvfrom_alarm(int);</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int n;</code></p>
      <p><code> 7  const int on = 1;</code></p>
      <p><code> 8  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
      <p><code> 9  fd_set rset;</code></p>
      <p><code>10  sigset_t sigset_alrm, sigset_empty;</code></p>
      <p><code>11  socklen_t len;</code></p>
      <p><code>12  struct sockaddr *preply_addr;</code></p>
      <empty-line/>
      <p><code>13  preply_addr = Malloc(servlen);</code></p>
      <empty-line/>
      <p><code>14  Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &amp;on, sizeof(on));</code></p>
      <empty-line/>
      <p><code>15  FD_ZERO(&amp;rset);</code></p>
      <empty-line/>
      <p><code>16  Sigemptyset(&amp;sigset_empty);</code></p>
      <p><code>17  Sigemptyset(&amp;sigset_alrm);</code></p>
      <p><code>18  Sigaddset(&amp;sigset_alrm, SIGALRM);</code></p>
      <empty-line/>
      <p><code>19  Signal(SIGALRM, recvfrom_alarm);</code></p>
      <empty-line/>
      <p><code>20  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <p><code>21   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
      <empty-line/>
      <p><code>22   Sigprocmask(SIG_BLOCK, &amp;sigset_alrm, NULL);</code></p>
      <p><code>23   alarm(5);</code></p>
      <p><code>24   for (;;) {</code></p>
      <p><code>25    FD_SET(sockfd, &amp;rset);</code></p>
      <p><code>26    n = pselect(sockfd + 1, &amp;rset, NULL, NULL, NULL, &amp;sigset_empty);</code></p>
      <p><code>27    if (n &lt; 0) {</code></p>
      <p><code>28     if (errno == EINTR)</code></p>
      <p><code>29      break;</code></p>
      <p><code>30     else</code></p>
      <p><code>31      err_sys("pselect error");</code></p>
      <p><code>32    } else if (n != 1)</code></p>
      <p><code>33    err_sys("pselect error; returned %d", n);</code></p>
      <empty-line/>
      <p><code>34    len = servlen;</code></p>
      <p><code>35    n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &amp;len);</code></p>
      <p><code>36    recvline[n] = 0; /* завершающий нуль */</code></p>
      <p><code>37    printf("from %s: %s",</code></p>
      <p><code>38    Sock_ntop_host(preply_addr, len), recvline);</code></p>
      <p><code>39   }</code></p>
      <p><code>40  }</code></p>
      <p><code>41  free(preply_addr);</code></p>
      <p><code>42 }</code></p>
      <empty-line/>
      <p><code>43 static void</code></p>
      <p><code>44 recvfrom_alarm(int signo)</code></p>
      <p><code>45 {</code></p>
      <p><code>46  return; /* просто прерываем recvfrom() */</code></p>
      <p><code>47 }</code></p>
      <p><code>22-23</code> Мы блокируем сигнал <code>SIGALRM</code> и вызываем функцию <code>pselect</code>. Последний аргумент этой функции — указатель на нашу переменную <code>sigset_empty</code>, являющуюся набором сигналов, в котором нет блокированных сигналов (все сигналы разблокированы). Функция <code>pselect</code> сохранит текущую маску сигналов (которая блокирует <code>SIGALRM</code>), проверит заданные дескрипторы, заблокируется при необходимости с маской сигналов, установленной в пустой набор, но перед завершением функции маска сигналов процесса будет переустановлена в исходное значение, которое она имела при вызове функции <code>pselect</code>. Ключ к пониманию функции <code>pselect</code> в том, что установка маски сигналов, проверка дескрипторов и переустановка маски сигнала — это атомарные операции по отношению к вызывающему процессу.</p>
      <p><code>34-38</code> Если наш сокет готов для чтения, мы вызываем функцию <code>recvfrom</code>, зная, что она не заблокируется.</p>
      <p>Как мы упоминали в разделе 6.9, функция <code>pselect</code> — относительно новая среди других, описываемых спецификацией POSIX. Из всех систем, показанных на рис. 1.7, эту функцию поддерживают только FreeBSD и Linux. Тем не менее в листинге 20.4 представлена простая, хотя и некорректная ее реализация. Мы приводим ее здесь, несмотря на некорректность, чтобы продемонстрировать три стадии решения: установку маски сигнала в значение, заданное вызывающей функцией, с сохранением текущей маски, проверку дескрипторов и переустановку маски сигнала.</p>
      <p><strong>Листинг 20.4</strong>. Простая некорректная реализация функции pselect</p>
      <p><code>//lib/pselect.c</code></p>
      <p><code> 9 #include "unp.h"</code></p>
      <empty-line/>
      <p><code>10 int</code></p>
      <p><code>11 pselect(int nfds, fd_set *rset, fd_set *wset, fd_set *xset,</code></p>
      <p><code>12  const struct timespec *ts, const sigset_t *sigmask)</code></p>
      <p><code>13  {</code></p>
      <p><code>14  int n;</code></p>
      <p><code>15  struct timeval tv;</code></p>
      <p><code>16  sigset_t savemask;</code></p>
      <empty-line/>
      <p><code>17  if (ts != NULL) {</code></p>
      <p><code>18   tv.tv_sec = ts-&gt;tv_sec;</code></p>
      <p><code>19   tv.tv_usec = ts-&gt;tv_nsec / 1000; /* наносекунды -&gt; микросекунды */</code></p>
      <p><code>20  }</code></p>
      <p><code>21  sigprocmask(SIG_SETMASK, sigmask, &amp;savemask); /* маска вызывающего</code></p>
      <p><code>                                                     процесса */</code></p>
      <p><code>22  n = select(nfds, rset, wset, xset., (ts == NULL) ? NULL : &amp;tv);</code></p>
      <p><code>23  sigprocmask(SIG_SETMASK, &amp;savemask, NULL); /* восстанавливаем</code></p>
      <p><code>                                                  исходную маску */</code></p>
      <empty-line/>
      <p><code>24  return (n);</code></p>
      <p><code>25 }</code></p>
     </section>
     <section>
      <title>
       <p>Использование функций sigsetjmp и siglongjmp</p>
      </title>
      <p>Нашу проблему можно решить корректно, если отказаться от прерывания блокированного системного вызова обработчиком сигнала, вместо этого вызвав из обработчика сигнала функцию <code>siglongjmp</code>. Этот метод называется <emphasis>нелокальным оператором goto</emphasis> (<emphasis>nonlocal goto</emphasis>), поскольку мы можем использовать его для перехода из одной функции в другую. В листинге 20.5 проиллюстрирована эта технология.</p>
      <p><strong>Листинг 20.5</strong>. Вызов функций sigsetjmp и siglongjmp из обработчика сигнала</p>
      <p><code>//bcast/dgclibcast5.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;setjmp.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 static void recvfrom_alarm(int);</code></p>
      <p><code> 4 static sigjmp_buf jmpbuf;</code></p>
      <empty-line/>
      <p><code> 5 void</code></p>
      <p><code> 6 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
      <p><code> 7 {</code> </p>
      <p><code> 8  int n;</code></p>
      <p><code> 9  const int on = 1;</code></p>
      <p><code>10  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
      <p><code>11  socklen_t len;</code></p>
      <p><code>12  struct sockaddr *preply_addr;</code></p>
      <empty-line/>
      <p><code>13  preply_addr = Malloc(servlen);</code></p>
      <empty-line/>
      <p><code>14  Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &amp;on, sizeof(on));</code></p>
      <empty-line/>
      <p><code>15  Signal(SIGALRM, recvfrom_alarm);</code></p>
      <empty-line/>
      <p><code>16  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <empty-line/>
      <p><code>17   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
      <empty-line/>
      <p><code>18   alarm(5);</code></p>
      <p><code>19   for (;;) {</code></p>
      <p><code>20    if (sigsetjmp(jmpbuf, 1) != 0)</code></p>
      <p><code>21     break;</code></p>
      <p><code>22    len = servlen;</code></p>
      <p><code>23    n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &amp;len);</code></p>
      <p><code>24    recvline[n] = 0; /* null terminate */</code></p>
      <p><code>25    printf("from %s: %s",</code></p>
      <p><code>26     Sock_ntop_host(preply_addr, len), recvline);</code></p>
      <p><code>27   }</code></p>
      <p><code>28  }</code></p>
      <p><code>29  free(preply_addr);</code></p>
      <p><code>30 }</code></p>
      <empty-line/>
      <p><code>31 static void</code></p>
      <p><code>32 recvfrom_alarm(int signo)</code></p>
      <p><code>33 {</code></p>
      <p><code>34  siglongjmp(jmpbuf, 1);</code></p>
      <p><code>35 }</code></p>
      <subtitle>Размещение буфера перехода в памяти</subtitle>
      <p><code>4</code> Мы выделяем буфер перехода, который будет использовать наша функция и ее обработчик сигнала.</p>
      <subtitle>Вызов функции sigsetjmp</subtitle>
      <p><code>20-23</code> Когда мы вызываем функцию <code>sigsetjmp</code> непосредственно из нашей функции <code>dg_cli</code>, она устанавливает буфер перехода и возвращает нуль. Мы продолжаем работать дальше и вызываем функцию <code>recvfrom</code>.</p>
      <subtitle>Обработка сигнала SIGALRM и вызов функции siglongjmp</subtitle>
      <p><code>31-35</code> Когда сигнал доставлен, мы вызываем функцию <code>siglongjmp</code>. Это заставляет <code>sigsetjmp</code> в функции <code>dg_cli</code> возвратить значение, равное второму аргументу (1), который должен быть ненулевым. Это приведет к завершению цикла <code>for</code> в функции <code>dg_cli</code>.</p>
      <p>Использование функций <code>sigsetjmp</code> и <code>siglongjmp</code> подобным образом гарантирует, что мы не останемся навсегда блокированы в вызове функции <code>recvfrom</code> из-за доставки сигнала в неподходящее время. Однако такое решение создает иную потенциальную проблему. Если сигнал доставляется в тот момент, когда функция <code>printf</code> осуществляет вывод данных, управление будет передано из <code>printf</code> обратно на <code>sigsetjmp</code>. При этом в структурах данных <code>printf</code> могут возникнуть противоречия. Чтобы предотвратить эту проблему, следует объединить блокирование и разблокирование сигналов, показанное в листинге 20.2, с помощью нелокального оператора <code>goto</code>.</p>
     </section>
     <section>
      <title>
       <p>Применение IPC в обработчике сигнала функции</p>
      </title>
      <p>Существует еще один корректный путь решения нашей проблемы. Вместо того чтобы просто возвращать управление и, как мы надеемся, прерывать блокированную функцию <code>recvfrom</code>, наш обработчик сигнала при помощи средств IPC (Interprocess Communications — взаимодействие процессов) может сообщить функции <code>dg_cli</code> о том, что время таймера истекло. Это аналогично предложению, сделанному нами раньше, когда обработчик сигнала устанавливал глобальную переменную <code>had_alarm</code> по истечении времени таймера. Глобальная переменная использовалась как некая разновидность IPC (поскольку она была доступна и нашей функции, и обработчику сигнала). Однако при таком решении наша функция должна была проверять эту переменную, что могло привести к проблемам синхронизации в том случае, когда сигнал доставлялся приблизительно в это же время.</p>
      <p>Листинг 20.6 демонстрирует использование канала внутри процесса. Обработчик сигналов записывает в канал 1 байт, когда истекает время таймера, а наша функция <code>dg_cli</code> считывает этот байт, чтобы определить, когда завершить свой цикл <code>for</code>. Что замечательно в этом решении — проверка готовности канала осуществляется функцией <code>select</code>. С ее помощью мы проверяем, готов ли к считыванию сокет или канал.</p>
      <p><strong>Листинг 20.6</strong>. Использование канала в качестве IPC между обработчиком сигнала и нашей функцией</p>
      <p><code>//bcast/dgclibcast6.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 static void recvfrom_alarm(int);</code></p>
      <p><code> 3 static int pipefd[2];</code></p>
      <p><code> 4 void</code></p>
      <p><code> 5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  int n, maxfdp1;</code></p>
      <p><code> 8  const int on = 1;</code></p>
      <p><code> 9  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
      <p><code>10  fd_set rset;</code></p>
      <p><code>11  socklen_t len;</code></p>
      <p><code>12  struct sockaddr *preply_addr;</code></p>
      <empty-line/>
      <p><code>13  preply_addr = Malloc(servlen);</code></p>
      <empty-line/>
      <p><code>14  Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &amp;on, sizeof(on));</code></p>
      <empty-line/>
      <p><code>15  Pipe(pipefd);</code></p>
      <p><code>16  maxfdp1 = max(sockfd, pipefd[0]) + 1;</code></p>
      <empty-line/>
      <p><code>17  FD_ZERO(&amp;rset);</code></p>
      <empty-line/>
      <p><code>18  Signal(SIGALRM, recvfrom_alarm);</code></p>
      <empty-line/>
      <p><code>19  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <p><code>20   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
      <empty-line/>
      <p><code>21   alarm(5);</code></p>
      <p><code>22   for (;;) {</code></p>
      <p><code>23    FD_SET(sockfd, &amp;rset);</code></p>
      <p><code>24    FD_SET(pipefd[0], &amp;rset);</code></p>
      <p><code>25    if ((n = select(maxfdp1, &amp;rset, NULL, NULL, NULL)) &lt; 0) {</code></p>
      <p><code>26     if (errno == EINTR)</code></p>
      <p><code>27      continue;</code></p>
      <p><code>28     else</code></p>
      <p><code>29      err_sys("select error");</code></p>
      <p><code>30    }</code></p>
      <empty-line/>
      <p><code>31    if (FD_ISSET(sockfd, &amp;rset)) {</code></p>
      <p><code>32     len = servlen;</code></p>
      <p><code>33     n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr,</code></p>
      <p><code>34      &amp;len);</code></p>
      <p><code>35     recvline[n] = 0; /* null terminate */</code></p>
      <p><code>36     printf("from %s: %s",</code></p>
      <p><code>37     Sock_ntop_host(preply_addr, len), recvline);</code></p>
      <p><code>38    }</code></p>
      <empty-line/>
      <p><code>39    if (FD_ISSET(pipefd[0], &amp;rset)) {</code></p>
      <p><code>40     Read(pipefd[0], &amp;n, 1); /* истекшее время */</code></p>
      <p><code>41     break;</code></p>
      <p><code>42    }</code></p>
      <p><code>43   }</code></p>
      <p><code>44  }</code></p>
      <p><code>45  free(preply_addr);</code></p>
      <p><code>46 }</code></p>
      <empty-line/>
      <p><code>47 static void</code></p>
      <p><code>48 recvfrom_alarm(int signo)</code></p>
      <p><code>49 {</code></p>
      <p><code>50  Write(pipefd[1], "", 1); /* в канал пишется один нулевой байт */</code></p>
      <p><code>51  return;</code></p>
      <p><code>52 }</code></p>
      <subtitle>Создание канала</subtitle>
      <p><code>15</code> Мы создаем обычный канал Unix. Возвращаются два дескриптора: <code>pipefd[0]</code> доступен для чтения, а <code>pipefd[0]</code> — для записи.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Мы могли бы использовать функцию socketpair и получить двусторонний канал. В некоторых системах, особенно SVR4, обычный канал Unix всегда является двусторонним, и мы можем и читать, и записывать на любом конце этого канала.</p>
      </cite>
      <subtitle>Функция select на сокете и считывающем конце канала</subtitle>
      <p><code>23-30</code> Мы вызываем функцию <code>select</code> и на сокете, и на считывающем конце канала.</p>
      <p><code>47-52</code> Когда доставляется сигнал <code>SIGALRM</code>, наш обработчик сигналов записывает в канал 1 байт, в результате чего считывающий конец канала становится готовым для чтения. Наш обработчик сигнала также возвращает управление, возможно, прерывая функцию <code>select</code>. Следовательно, если функция <code>select</code> возвращает ошибку <code>EINTR</code>, мы игнорируем эту ошибку, зная, что считывающий конец канала также готов для чтения, что завершит цикл <code>for</code>.</p>
      <subtitle>Чтение из канала</subtitle>
      <p><code>38-41</code> Когда считывающий конец канала готов для чтения, мы с помощью функции read считываем нулевой байт, записанный обработчиком сигнала, и игнорируем его. Но прибытие этого нулевого байта указывает нам на то, что истекло время таймера, и мы с помощью функции <code>break</code> выходим из бесконечного цикла <code>for</code>.</p>
     </section>
    </section>
    <section>
     <title>
      <p>20.6. Резюме</p>
     </title>
     <p>При широковещательной передаче посылается дейтаграмма, которую получают все узлы. Недостатком широковещательной передачи является то, что каждый узел в подсети должен обрабатывать дейтаграмму, вплоть до уровня UDP в случае дейтаграммы UDP, даже если на узле не выполняется приложение-адресат. Для приложений с большими потоками данных, таких как аудио- и видео-приложения, это может привести к повышенной нагрузке на все узлы. В следующей главе мы увидим, что многоадресная передача решает эту проблему, поскольку позволяет не получать дейтаграмму узлам, не заинтересованным в этом.</p>
     <p>Использование версии нашего эхо-клиента UDP, который отправляет серверу времени и даты широковещательные дейтаграммы и затем выводит все его ответы, полученные в течение 5 с, позволяет нам рассмотреть ситуацию гонок, возникающую при применении сигнала <code>SIGALRM</code>. Общим способом помещения тайм-аута в операцию чтения является использование функции <code>alarm</code> и сигнала <code>SIGALRM</code>, но он несет в себе неявную ошибку, типичную для сетевых приложений. Мы показали один некорректный и три корректных способа решения этой проблемы:</p>
     <p>&#9632; использование функции <code>pselect</code>,</p>
     <p>&#9632; использование функций <code>sigsetjmp</code> и <code>siglongjmp</code>,</p>
     <p>&#9632; использование средств IPC (обычно канала) между обработчиком сигнала и главным циклом.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Запустите клиент UDP, используя функцию <code>dg_cli</code>, выполняющую широковещательную передачу (см. листинг 20.1). Сколько ответов вы получаете? Всегда ли ответы приходят в одном и том же порядке? Синхронизированы ли часы у узлов в вашей подсети?</p>
     <p>2. Поместите несколько функций <code>printf</code> в листинг 20.6 после завершения функции <code>select</code>, чтобы увидеть, возвращает ли она ошибку или указание на готовность к чтению одного из двух дескрипторов. Возвращает ли ваша система ошибку <code>EINTR</code> или сообщение о готовности канала к чтению, когда истекает время таймера <code>alarm</code>?</p>
     <p>3. Запустите такую программу, как <code>tcpdump</code>, если это возможно, и просмотрите широковещательные пакеты в вашей локальной сети (команда <code>tcpdump ether broadcast</code>). К каким наборам протоколов относятся эти широковещательные пакеты?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 21</p>
     <p>Многоадресная передача</p>
    </title>
    <section>
     <title>
      <p>21.1. Введение</p>
     </title>
     <p>Как показано в табл. 20.1, адрес направленной передачи идентифицирует <emphasis>одиночный</emphasis> интерфейс, широковещательный адрес идентифицирует <emphasis>все</emphasis> интерфейсы в подсети, а адрес многоадресной передачи — <emphasis>набор</emphasis> (<emphasis>множество</emphasis>) интерфейсов. Направленная и широковещательная передача — это конечные точки спектра адресации (один интерфейс или все), а цель многоадресной передачи — обеспечить возможность адресации на участок спектра между этими конечными точками. Дейтаграмму многоадресной передачи должны получать только заинтересованные в ней интерфейсы, то есть интерфейсы на тех узлах, на которых запущены приложения, желающие принять участие в сеансе многоадресной передачи. Кроме того, широковещательная передача обычно ограничена локальными сетями, в то время как многоадресная передача может использоваться как в локальной, так и в глобальной сети. Существуют приложения, которые ежедневно участвуют в многоадресной передаче через всю сеть Интернет.</p>
     <p>Дополнения к API сокетов, необходимые для поддержки многоадресной передачи, — это девять параметров сокетов. Три из них влияют на отправку дейтаграмм UDP на адрес, а шесть — на получение узлом дейтаграмм многоадресной передачи.</p>
    </section>
    <section>
     <title>
      <p>21.2. Адрес многоадресной передачи</p>
     </title>
     <section>
      <p>При описании адресов многоадресной передачи необходимо провести различия между IPv4 и IPv6.</p>
     </section>
     <section>
      <title>
       <p>Адреса IPv4 класса D</p>
      </title>
      <p>Адреса класса D, лежащие в диапазоне от 224.0.0.0 до 239.255.255.255, в IPv4 являются адресами многоадресной передачи (см. табл. А.1). Младшие 28 бит адреса класса D образуют <emphasis>идентификатор группы многоадресной передачи</emphasis> (<emphasis>multicast group ID</emphasis>), а 32-разрядный адрес называется <emphasis>адресом группы</emphasis> (<emphasis>group address</emphasis>).</p>
      <p>На рис. 21.1 показано, как адреса многоадресной передачи сопоставляются адресам Ethernet. Сопоставление адресов групп IPv4 для сетей Ethernet описывается в RFC 1112 [26], для сетей FDDI — в RFC 1390 [59], а для сетей типа Token Ring — в RFC 1469 [97]. Чтобы обеспечить возможность сравнения полученных в результате адресов Ethernet, мы также показываем сопоставление для адресов групп Ipv6.</p>
      <image l:href="#img_118.png"/>
      <p><strong>Рис. 21.1</strong>. Сопоставление адресам Ethernet адресов многоадресной передачи IPv4 и IPv6</p>
      <p>Если рассматривать лишь сопоставление адресов IPv4, то в 24 старших битах адреса Ethernet всегда будет <code>01:00:5е</code>. Следующий бит всегда нулевой, а 23 младших бита копируются из 23 младших битов группового адреса. Старшие 5 бит группового адреса при сопоставлении игнорируются. Это значит, что 32 групповых адреса сопоставляются одиночному адресу Ethernet, то есть соответствие не является взаимнооднозначным.</p>
      <p>Младшие 2 бита первого байта адреса Ethernet идентифицируют адрес как универсально управляемый групповой адрес. «Универсально управляемый» означает то, что 24 старших бита были присвоены IEEE (Institute of Electrical and Electronics Engineers — Институт инженеров по электротехнике и электронике), а групповые адреса многоадресной передачи распознаются и обрабатываются получающими интерфейсами специальным образом.</p>
      <p>Существует несколько специальных адресов многоадресной передачи IPv4:</p>
      <p>&#9632; 224.0.0.1 — это группа <emphasis>всех узлов</emphasis> (<emphasis>all-hosts group</emphasis>). Все узлы в подсети, имеющие возможность многоадресной передачи, должны присоединиться к этой группе интерфейсами, поддерживающими многоадресную передачу. (Мы поговорим о том, что значит присоединиться к группе, несколько позже.)</p>
      <p>&#9632; 224.0.0.2 — это группа <emphasis>всех маршрутизаторов</emphasis> (<emphasis>all-routers group</emphasis>). Все маршрутизаторы многоадресной передачи в подсети должны присоединиться к этой группе интерфейсами, поддерживающими многоадресную передачу.</p>
      <p>Диапазон адресов от 224.0.0.0 до 224.0.0.255 (который мы можем также записать в виде 224.0.0.0/24), называется <emphasis>локальным на канальном уровне</emphasis> (<emphasis>link local</emphasis>). Эти адреса предназначены для низкоуровневого определения топологии и служебных протоколов, и дейтаграммы, предназначенные для любого из этих адресов, никогда не передаются маршрутизатором многоадресной передачи дальше. Более подробно об области действия различных групповых адресов IPv4 мы поговорим после того, как рассмотрим адреса многоадресной передачи IPv6.</p>
     </section>
     <section>
      <title>
       <p>Адреса многоадресной передачи IPv6</p>
      </title>
      <p>Старший байт адреса многоадресной передачи IPv6 имеет значение <code>ff</code>. На рис. 21.1 показано сопоставление 16-байтового адреса многоадресной передачи IPv6 6-байтовому адресу Ethernet. Младшие 32 бита группового адреса копируются в младшие 32 бита адреса Ethernet. Старшие 2 байта адреса Ethernet имеют значение <code>33:33</code>. Это сопоставление для сетей Ethernet описано в RFC 2464 [23], то же сопоставление для FDDI — в RFC 2467 [24], а сопоставление для сетей типа Token Ring — в RFC 2470 [25].</p>
      <p>Младшие два бита первого байта адреса Ethernet определяют адрес как локально администрируемый групповой адрес. «Локально администрируемый» — это значит, что нет гарантий, что адрес уникален по отношению к IPv6. В этой сети кроме IPv6 могут быть и другие наборы протоколов, использующие те же два старших байта адреса Ethernet. Как мы отмечали ранее, групповые адреса распознаются и обрабатываются получающими интерфейсами специальным образом.</p>
      <p>Имеется два формата адресов многоадресной передачи IPv6 (рис. 21.2). Когда флаг P имеет значение 0, флаг T интерпретируется как обозначение принадлежности адреса к группе <emphasis>заранее известных</emphasis> (<emphasis>well-known</emphasis> — значение 0) или к группе <emphasis>временных</emphasis> (<emphasis>transient</emphasis> — значение 1). Если флаг <code>P</code> равен 1, адрес считается назначенным на основе одноадресного префикса (см. RFC 3306 [40]). При этом флаг <code>T</code> также должен иметь значение 1 (многоадресные адреса на основе одноадресных всегда являются временными), а поля <code>plen</code> и prefix устанавливаются равными длине и значению префикса соответственно. Верхние два бита этого поля зарезервированы. Адреса многоадресной передачи IPv6 имеют также 4-разрядное поле области действия (scope), которое будет описано ниже. Документ RFC 3307 [39] описывает механизм выделения младших 32 разрядов группового адреса IPv6 (идентификатора группы) в зависимости от значения флага <code>P</code>.</p>
      <image l:href="#img_119.png"/>
      <p><strong>Рис. 21.2</strong>. Формат адресов многоадресной передачи IPv6</p>
      <p>Существует несколько специальных адресов многоадресной передачи Ipv6:</p>
      <p>&#9632; <code>ff02:1</code> — это группа <emphasis>всех узлов</emphasis> (<emphasis>all-nodes group</emphasis>). Все узлы подсети (компьютеры, маршрутизаторы, принтеры и т.д.), имеющие возможность многоадресной передачи, должны присоединиться к этой группе всеми своими интерфейсами, поддерживающими многоадресную передачу. Этот адрес аналогичен адресу многоадресной передачи IPv4 224.0.0.1. Однако поскольку многоадресная передача является неотъемлемой частью IPv6, присоединение к группе является обязательным (в отличие от IPv4).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Хотя группа IPv4 называется all-hosts, а группа IPv6 — all-nodes, назначение у них одно и то же. Группа IPv6 была переименована, чтобы подчеркнуть, что в нее должны входить маршрутизаторы, принтеры и любые другие IP-устройства подсети, а не только компьютеры (hosts).</p>
      </cite>
      <p>&#9632; <code>ff02:2</code> — группа <emphasis>всех маршрутизаторов</emphasis> (<emphasis>all-routers group</emphasis>). Все маршрутизаторы многоадресной передачи в подсети должны присоединиться к этой группе интерфейсами, поддерживающими многоадресную передачу. Он аналогичен адресу многоадресной передачи IPv4 224.0.0.2.</p>
     </section>
     <section>
      <title>
       <p>Область действия адресов многоадресной передачи</p>
      </title>
      <p>Адреса многоадресной передачи IPv6 имеют собственное 4-разрядное поле <emphasis>области действия</emphasis> (<emphasis>scope</emphasis>), определяющее, насколько «далеко» будет передаваться пакет многоадресной передачи. Пакеты IPv6 вообще имеют поле предела количества транзитных узлов, которое ограничивает количество передач через маршрутизаторы (hop limit field). Поле области действия может принимать следующие значения:</p>
      <p>&#9632; 1: локальная в пределах узла (node-local);</p>
      <p>&#9632; 2: локальная в пределах физической сети (подсети) (link-local);</p>
      <p>&#9632; 4: локальная в пределах области администрирования (admin-local);</p>
      <p>&#9632; 5: локальная в пределах сайта (site-local);</p>
      <p>&#9632; 8: локальная в пределах организации (organization-local);</p>
      <p>&#9632; 14: глобальная (global).</p>
      <p>Оставшиеся значения — это еще не присвоенные либо зарезервированные значения. Дейтаграмма, локальная в пределах узла, не должна выводиться интерфейсом, а дейтаграмма, локальная в пределах сети, никогда не должна передаваться в другую сеть маршрутизатором. Что понимается под областью администрирования, сайтом или организацией, зависит от администраторов маршрутизаторов многоадресной передачи. Адреса многоадресной передачи IPv6, различающиеся только областью действия, считаются относящимися к разным группам.</p>
      <p>В IPv4 нет отдельного поля области действия для многоадресных пакетов. Исторически поле TTL IPv4 в заголовке IP выполняло также роль поля области действия многоадресной передачи: TTL, равное нулю, означает адрес, локальный в пределах узла, 1 — локальный в пределах сети, значения до 32 — локальный в пределах сайта, до 64 — локальный в пределах региона, до 128 — локальный в пределах континента (это означает, что пакеты не передаются по низкоскоростным и загруженным каналам, даже если они проложены в пределах одного континента) и до 255 — неограниченная область действия (глобальный). Двойное использование поля TTL привело к ряду сложностей, подробно описанных в документе RFC 2365 [75].</p>
      <p>Хотя использование поля TTL IPv4 для области действия является принятой и рекомендуемой практикой, предпочтительнее административное управление областями действия, если оно возможно. При этом диапазон адресов от 239.0.0.0 до 239.255.255.255 определяется как <emphasis>пространство многоадресной передачи IPv4 с административным ограничением области действия</emphasis> (administratively scoped IPv4 multicast space) [75]. Это верхняя граница пространства адресов многоадресной передачи. Адреса в этом диапазоне задаются организацией локально, но их уникальность за пределами организации не гарантируется. Организация должна настроить свои пограничные маршрутизаторы многоадресной передачи таким образом, чтобы пакеты многоадресной передачи, предназначенные для любого из этих адресов, не передавались вовне.</p>
      <p>Административно управляемые адреса многоадресной передачи IPv4 затем делятся на локальную область действия и локальную в пределах организации область действия, первая из которых аналогична (но не является семантическим эквивалентом) области действия IPv6, локальной в пределах сайта. Различные правила определения области действия мы приводим в табл. 21.1.</p>
      <empty-line/>
      <p><strong>Таблица 21.1</strong>. Область действия адресов многоадресной передачи IPv4 и IPv6</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Область действия</th>
        <th align="left" valign="top">Значение поля области действия в IPv6</th>
        <th align="left" valign="top">Значение поля TTL в IPv4</th>
        <th align="left" valign="top">Административное управление областью действия в IPv4</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Локальная в пределах узла</td>
        <td align="left" valign="top">1</td>
        <td align="left" valign="top">0</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Локальная в пределах сети</td>
        <td align="left" valign="top">2</td>
        <td align="left" valign="top">1</td>
        <td align="left" valign="top">от 224.0.0.0 до 224.0.0.255</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Локальная в пределах сайта</td>
        <td align="left" valign="top">5</td>
        <td align="left" valign="top">&lt;32</td>
        <td align="left" valign="top">от 239.255.0.0 до 239.255.255.255</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Локальная в пределах организации</td>
        <td align="left" valign="top">8</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">от 239.192.0.0 до 239.195.255.255</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Глобальная</td>
        <td align="left" valign="top">14</td>
        <td align="left" valign="top">&lt;255</td>
        <td align="left" valign="top">от 224.0.1.0 до 238.255.255.255</td>
       </tr>
      </table>
     </section>
     <section>
      <title>
       <p>Сеансы многоадресной передачи</p>
      </title>
      <p>Сочетание адреса многоадресной передачи IPv4 или IPv6 и порта транспортного уровня часто называется <emphasis>сеансом</emphasis> (<emphasis>session</emphasis>), особенно если речь идет о передаче потокового мультимедиа. Например, телеконференция может объединять два сеанса: один аудио- и один видео-. Практически во всех случаях сеансы используют разные порты, а иногда и разные группы, что обеспечивает определенную гибкость для получателей. Например, один клиент может получать только аудиопоток, тогда как другой — аудио- и видео-. Если бы сеансы использовали один и тот же групповой адрес, это было бы невозможно.</p>
     </section>
    </section>
    <section>
     <title>
      <p>21.3. Сравнение многоадресной и широковещательной передачи в локальной сети</p>
     </title>
     <p>Вернемся к примерам, представленным на рис. 20.2 и 20.3, чтобы показать, что происходит в случае многоадресной передачи. В примере, показанном на рис. 21.3, мы будем использовать IPv4, хотя для IPv6 последовательность операций будет такой же.</p>
     <image l:href="#img_120.png"/>
     <p><strong>Рис. 21.3</strong>. Пример многоадресной передачи дейтаграммы UDP</p>
     <p>Принимающее приложение на узле, изображенном справа, запускается и создает сокет UDP, связывает порт 123 с сокетом и затем присоединяется к группе 224.0.1.1. Мы вскоре увидим, что операция «присоединения» выполняется при помощи вызова функции <code>setsockopt</code>. Когда это происходит, уровень IPv4 сохраняет внутри себя информацию и затем сообщает соответствующему канальному уровню, что нужно получить кадры Ethernet, предназначенные адресу <code>01:00:5e:00:01:01</code> (см. раздел 12.11 [128]). Это соответствующий IP-адресу многоадресной передачи адрес Ethernet, к которому приложение только что присоединилось (с учетом сопоставления адресов, показанного на рис. 21.1).</p>
     <p>Следующий шаг для отправляющего приложения на узле, изображенном слева, — создание сокета UDP и отправка дейтаграммы на адрес 224.0.1.1, порт 123. Для отправки дейтаграммы многоадресной передачи не требуется никаких специальных действий — приложению не нужно присоединяться к группе. Отправляющий узел преобразует IP-адрес в соответствующий адрес получателя Ethernet, и кадр отправляется. Обратите внимание, что кадр содержит и адрес получателя Ethernet (проверяемый интерфейсами), и IP-адрес получателя (проверяемый уровнями IP).</p>
     <p>Мы предполагаем, что узел, изображенный в центре рисунка, не поддерживает многоадресную передачу IPv4 (поскольку поддержка многоадресной передачи IPv4 не обязательна). Узел полностью игнорирует кадр, поскольку, во-первых, адрес получателя Ethernet не совпадает с адресом интерфейса; во-вторых, адрес получателя Ethernet не является широковещательным адресом Ethernet, и в-третьих, интерфейс не получал указания принимать сообщения с адресами многоадресной передачи (то есть адресами, у которых младший бит старшего байта равен 1, как на рис. 21.1).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Когда интерфейс получает указание принимать кадры, предназначенные для определенного группового адреса Ethernet, многие современные сетевые адаптеры Ethernet применяют к адресу хэш-функцию, вычисляя значение от 0 до 511. Затем один из 512 бит массива устанавливается равным 1. Когда кадр проходит по кабелю, предназначенному для группового адреса, та же хэш-функция применяется интерфейсом к адресу получателя (первое поле в кадре), и снова вычисляется значение от 0 до 511. Если соответствующий бит в массиве установлен, кадр будет получен интерфейсом; иначе он игнорируется. Старые сетевые адаптеры использовали массив размером 64 бита, поэтому вероятность получения ненужных кадров была выше. С течением времени, поскольку все больше и больше приложений используют многоадресную передачу, этот размер, возможно, еще возрастет. Некоторые сетевые карты уже сейчас осуществляют совершенную фильтрацию (perfect filtering). У других карт возможность фильтрации многоадресной передачи отсутствует вовсе, и получая указание принять определенный групповой адрес, они должны принимать все кадры многоадресных передач (иногда это называется режимом смешанной многоадресной передачи). Одна популярная сетевая карта выполняет совершенную фильтрацию для 16 групповых адресов, а также имеет 512-битовую хэш-таблицу. Другая выполняет совершенную фильтрацию для 80 адресов, а остальные обрабатывает в смешанном режиме. Даже если интерфейс выполняет совершенную фильтрацию, все равно требуется совершенная программная фильтрация в пределах IP, поскольку сопоставление групповых адресов IP с аппаратными адресами не является взаимнооднозначным.</p>
     </cite>
     <p>Канальный уровень, изображенный справа, получает кадр на основе так называемой <emphasis>несовершенной фильтрации</emphasis> (<emphasis>imperfect filtering</emphasis>), которая выполняется интерфейсом с использованием адреса получателя Ethernet. Мы говорим, что эта фильтрация несовершенна, потому что если интерфейс получает указание принимать кадры, предназначенные для одного определенного группового адреса Ethernet, может случиться так, что он будет получать кадры, предназначенные также для других групповых адресов Ethernet.</p>
     <p>Если предположить, что канальный уровень, изображенный справа, получает кадр, то поскольку тип кадра Ethernet — IPv4, пакет передается уровню IP. Поскольку полученный пакет был предназначен IP-адресу многоадресной передачи, уровень IP сравнивает этот адрес со всеми адресами многоадресной передачи, к которым присоединились приложения на узле. Мы называем это <emphasis>совершенной фильтрацией</emphasis>, так как она основана на полном 32-разрядном адресе класса D в заголовке IPv4. В этом примере пакет принимается уровнем IP и передается уровню UDP, который, в свою очередь, передает дейтаграмму сокету, связанному с портом 123.</p>
     <p>Существует еще три сценария, не показанных нами на рис. 21.3.</p>
     <p>1. На узле запущено приложение, присоединившееся к адресу многоадресной передачи 225.0.1.1. Поскольку 5 верхних битов группового адреса игнорируются при сопоставлении с адресом Ethernet, этот интерфейс узла будет также получать кадры с адресом получателя Ethernet <code>01:00:5e:00:01:01</code>. В этом случае пакет будет проигнорирован при осуществлении совершенной фильтрации на уровне IP.</p>
     <p>2. На узле запущено приложение, присоединившееся к некоторой группе. Соответствующий адрес Ethernet этой группы является одним из тех, которые интерфейс может получить случайно, поскольку он запрограммирован на получение сообщений на адрес <code>01:00:5e:00:01:01</code> (то есть сетевая карта выполняет несовершенную фильтрацию). Этот кадр будет проигнорирован либо канальным уровнем, либо уровнем IP.</p>
     <p>3. Пакет предназначен для той же группы 224.0.1.1, но для другого порта, скажем 4000. Узел, изображенный справа на рис. 21.3, получает пакет, далее этот пакет принимается уровнем IP, но если не существует сокета, связанного с портом 4000, пакет будет проигнорирован уровнем UDP.</p>
     <cite>
      <subtitle>ВНИМАНИЕ</subtitle>
      <p>Эти сценарии показывают нам, что для того чтобы процесс мог получать дейтаграммы многоадресной передачи, он должен присоединиться к группе и связаться с портом.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>21.4. Многоадресная передача в глобальной сети</p>
     </title>
     <p>Многоадресная передача внутри локальной сети, описанная нами в предыдущем разделе, проста. Один узел посылает пакет многоадресной передачи, и любой заинтересованный узел получает этот пакет. Преимущество многоадресной передачи перед широковещательной состоит в сокращении нагрузки на все узлы, не заинтересованные в получении пакетов многоадресной передачи.</p>
     <p>Многоадресная передача имеет преимущества и при работе в глобальных сетях. Рассмотрим глобальную сеть, изображенную на рис. 21.4.</p>
     <image l:href="#img_121.png"/>
     <p><strong>Рис. 21.4</strong>. Пять локальных сетей с пятью маршрутизаторами многоадресной передачи </p>
     <p>Здесь изображены пять локальных сетей, соединенных пятью маршрутизаторами многоадресной передачи.</p>
     <p>Будем считать, что некая программа запущена на пяти из показанных узлов (скажем, программа прослушивания группового аудиосеанса), и эти пять программ присоединяются к данной группе. Тогда каждый из пяти узлов присоединяется к группе. Мы также считаем, что каждый маршрутизатор многоадресной передачи общается с соседними маршрутизаторами многоадресной передачи при помощи <emphasis>протокола маршрутизации многоадресной передачи</emphasis> (<emphasis>multicast routing protocol</emphasis>), который мы обозначим просто MRP. Это показано на рис. 21.5.</p>
     <image l:href="#img_122.png"/>
     <p><strong>Рис. 21.5</strong>. Присоединение пяти узлов к группе многоадресной передачи в глобальной сети</p>
     <p>Когда процесс на узле присоединяется к группе, этот узел отправляет всем присоединенным к той же сети маршрутизаторам многоадресной передачи сообщение IGMP, информирующее их о том, что узел только что присоединился к группе. Затем маршрутизаторы обмениваются этой информацией по MRP, так что каждый маршрутизатор знает, что делать, если он получит пакет, предназначенный для конкретного адреса многоадресной передачи.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Адресация многоадресной передачи — не до конца исследованная тема, и ее описание может легко составить отдельную книгу.</p>
     </cite>
     <p>Теперь будем считать, что процесс на узле, изображенном слева вверху, начинает отправлять пакеты на адрес многоадресной передачи. Допустим, этот процесс отправляет аудиопакеты, ожидаемые получателями многоадресной передачи. Эти пакеты показаны на рис. 21.6.</p>
     <image l:href="#img_123.png"/>
     <p><strong>Рис. 21.6</strong>. Отправка пакетов на адрес многоадресной передачи в глобальной сети</p>
     <p>Проследим шаги, которые проходит пакет от отправителя до получателей.</p>
     <p>&#9632; Пакеты многоадресной передачи рассылаются отправителем в левой верхней локальной сети. Получатель H1 получает их (так как он присоединился к группе), как и MR1 (поскольку маршрутизатор многоадресной передачи должен получать все пакеты многоадресного вещания).</p>
     <p>&#9632; MR1 передает пакет многоадресной передачи дальше маршрутизатору MR2, поскольку протокол маршрутизации многоадресной передачи сообщил MR1, что MR2 должен получить пакеты, предназначенные для этой группы.</p>
     <p>&#9632; MR2 передает этот пакет присоединенной локальной сети, поскольку узлы H2 и H3 входят в группу. Он также создает копию пакета и отправляет ее MR3.</p>
     <p>Создание копии пакета маршрутизатором свойственно только многоадресной передаче. Пакет направленной передачи никогда не дублируется при передаче маршрутизаторами.</p>
     <p>&#9632; MR3 с отправляет пакет многоадресной передачи маршрутизатору MR4, но не передает копию в свою локальную сеть, потому что ни один из узлов в этой сети не присоединился к группе.</p>
     <p>&#9632; MR4 передает пакет на присоединенную локальную сеть, поскольку узлы H4 и H5 входят в группу. Он не создает копии пакета и не отправляет пакет маршрутизатору MR, поскольку ни один из узлов присоединенной к MR локальной сети не входит в группу, и MR4 знает об этом из информации о маршрутизации многоадресной передачи, которой он обменялся с MR.</p>
     <p>Две менее желательные альтернативы многоадресной передаче в глобальной сети — <emphasis>лавинная адресация</emphasis> (<emphasis>broadcast flooding</emphasis>) и отправка индивидуальных копий каждому получателю. В первом случае отправитель будет передавать широковещательные пакеты, а каждый маршрутизатор будет передавать пакет с каждого из своих интерфейсов, кроме принимающего. Ясно, что это увеличит число незаинтересованных узлов и маршрутизаторов, которым придется получать этот пакет.</p>
     <p>Во втором случае отправитель должен знать IP-адреса всех получателей и отослать каждому по копии пакета. В случае с пятью пакетами, который представлен на рис. 21.6, это потребует пяти пакетов в локальной сети отправителя, четырех пакетов, идущих от MR1 к MR2, и двух пакетов, идущих от MR2 к MR3 и к MR4. А если получателей будет миллион?!</p>
    </section>
    <section>
     <title>
      <p>21.5. Многоадресная передача от отправителя</p>
     </title>
     <p>Внедрение многоадресной передачи в глобальные сети было затруднено несколькими обстоятельствами. Главная проблема заключается в том, что протокол маршрутизации MRP, описанный в разделе 21.4, должен обеспечивать доставку данных от всех отправителей (которые могут располагаться в сети совершенно произвольным образом) всем получателям (которые также могут быть размещены произвольно). Еще одна проблема связана с выделением адресов: адресов многоадресной передачи IPv4 недостаточно для того, чтобы можно было статически назначать их всем, кому они нужны, как это делается с адресами направленной передачи. Чтобы передавать многоадресные сообщения в глобальной сети, не конфликтуя с другими отправителями, нужно иметь уникальный адрес, однако механизма глобального выделения адресов еще не существует.</p>
     <p><emphasis>Многоадресная передача от отправителя</emphasis> (<emphasis>source-specific multicast</emphasis>, <emphasis>SSM</emphasis>) [47] представляет собой эффективное решение этих проблем. Она состоит в соединении адреса группы с адресом отправителя.</p>
     <p>&#9632; При подключении к группе получатели предоставляют маршрутизаторам не только адрес группы, но и адрес отправителя. Это устраняет проблему поиска, потому что теперь маршрутизатор точно знает, где находится отправитель. Однако при этом сохраняется удобство масштабирования приложений, потому что отправителю все так же не нужно знать адреса всех своих получателей. Такое решение очень сильно упрощает протоколы маршрутизации многоадресной передачи.</p>
     <p>&#9632; Идентификатор группы перестает быть групповым адресом и становится комбинацией адреса отправителя (адреса направленной передачи) и адреса группы (адреса многоадресной передачи). Такая комбинация называется в SSM <emphasis>каналом</emphasis> (<emphasis>channel</emphasis>). Благодаря этому отправитель может выбрать любой адрес многоадресной передачи, так как уникальность канала обеспечивается уже уникальностью адреса отправителя. Сеанс SSM представляет собой комбинацию адреса отправителя, адреса группы и порта.</p>
     <p>SSM обеспечивает некоторую защиту от подмены адреса, потому что отправителю 2 становится значительно труднее передавать сообщения по каналу отправителя 1, так как идентификатор этого канала включает в себя адрес отправителя 1. Подмена все еще остается возможной, однако серьезно усложняется.</p>
    </section>
    <section>
     <title>
      <p>21.6. Параметры сокетов многоадресной передачи</p>
     </title>
     <p>Для поддержки многоадресной передачи программным интерфейсом приложений (API) требуется только пять новых параметров сокетов. Поддержка фильтрации отправителей, необходимая для SSM, требует еще четырех параметров. В табл. 21.2 показаны три параметра, не имеющих отношения к членству в группах, а также тип данных аргумента, который предполагается использовать в вызове функций <code>getsockopt</code> или <code>setsockopt</code> для IPv4 и IPv6. В табл. 21.3 представлены оставшиеся шесть параметров сокетов для IPv4, IPv6 и не зависящего от IP-версии API. Во втором столбце показан тип данных переменной, указатель на которую является четвертым аргументом функций <code>getsockopt</code> и <code>setsockopt</code>. Все девять параметров действительны с функцией <code>setsockopt</code>, но шесть предназначенных для входа и выхода из группы не могут быть использованы в вызове функции <code>getsockopt</code>.</p>
     <empty-line/>
     <p><strong>Таблица 21.2</strong>. Параметры сокетов многоадресной передачи</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Параметр</th>
       <th align="left" valign="top">Тип данных</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_MULTICAST_IF</td>
       <td align="left" valign="top">struct in_addr</td>
       <td align="left" valign="top">Интерфейс по умолчанию для исходящих многоадресных пакетов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_MULTICAST_TTL</td>
       <td align="left" valign="top">u_char</td>
       <td align="left" valign="top">TTL для исходящих многоадресных пакетов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_MULTICAST_LOOP</td>
       <td align="left" valign="top">u_char</td>
       <td align="left" valign="top">Включение и отключение закольцовки для исходящих многоадресных пакетов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_MULTICAST_IF</td>
       <td align="left" valign="top">u_int</td>
       <td align="left" valign="top">Интерфейс по умолчанию для исходящих многоадресных пакетов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_MULTICAST_HOPS</td>
       <td align="left" valign="top">int</td>
       <td align="left" valign="top">Предел количества прыжков для и сходящих многоадресных пакетов</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_MULTICAST_LOOP</td>
       <td align="left" valign="top">u_int</td>
       <td align="left" valign="top">Включение и отключение закольцовки для исходящих многоадресных пакетов</td>
      </tr>
     </table>
     <empty-line/>
     <p><strong>Таблица 21.3</strong>. Параметры сокета, определяющие членство в группах многоадресной передачи</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Параметр</th>
       <th align="left" valign="top">Тип данных</th>
       <th align="left" valign="top">Описание</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_ADD_MEMBERSHIP</td>
       <td align="left" valign="top">struct ip_mreq</td>
       <td align="left" valign="top">Присоединение к группе многоадресной передачи</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_DROP_MEMBERSHIP</td>
       <td align="left" valign="top">struct ip_mreq</td>
       <td align="left" valign="top">Отсоединение от группы многоадресной передачи</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_BLOCK_SOURCE</td>
       <td align="left" valign="top">struct ip_mreq_source</td>
       <td align="left" valign="top">Блокирование источника из группы, к которой выполнено присоединение</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_UNBLOCK_SOURCE</td>
       <td align="left" valign="top">struct ip_mreq_source</td>
       <td align="left" valign="top">Разблокирование ранее заблокированного источника</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_ADD_SOURCE_MEMBERSHIP</td>
       <td align="left" valign="top">struct ip_mreq_source</td>
       <td align="left" valign="top">Присоединение к группе источника</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IP_DROP_SOURCE_MEMBERSHIP</td>
       <td align="left" valign="top">struct ip_mreq_source</td>
       <td align="left" valign="top">Отсоединение от группы источника</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_JOIN_GROUP</td>
       <td align="left" valign="top">struct ipv6_mreq</td>
       <td align="left" valign="top">Присоединение к группе многоадресной передачи</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">IPV6_LEAVE_GROUP</td>
       <td align="left" valign="top">struct ipv6_mreq</td>
       <td align="left" valign="top">Отсоединение от группы многоадресной передачи</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_JOIN_GROUP</td>
       <td align="left" valign="top">struct group_req</td>
       <td align="left" valign="top">Присоединение к группе многоадресной передачи</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_LEAVE_GROUP</td>
       <td align="left" valign="top">struct group_req</td>
       <td align="left" valign="top">Отсоединение от группы многоадресной передачи</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_BLOCK_SOURCE</td>
       <td align="left" valign="top">struct group_source_req</td>
       <td align="left" valign="top">Блокирование источника из группы, к которой выполнено присоединение</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_UNBLOCK_SOURCE</td>
       <td align="left" valign="top">struct group_source_req</td>
       <td align="left" valign="top">Разблокирование ранее заблокированного источника</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_JOIN_SOURCE_GROUP</td>
       <td align="left" valign="top">struct group_source_req</td>
       <td align="left" valign="top">Присоединение к группе источника</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">MCAST_LEAVE_SOURCE_GROUP</td>
       <td align="left" valign="top">struct group_source_req</td>
       <td align="left" valign="top">Отсоединение от группы источника</td>
      </tr>
     </table>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Параметры IPv4 TTL и закольцовки получают аргумент типа u_char, в то время как IPv6-параметры предела транзитных узлов и закольцовки получают аргументы соответственно типа int и u_int. Распространенная ошибка программирования с параметрами многоадресной передачи IPv4 — вызов функции setsockopt с аргументом типа int для задания TTL или закольцовки (что не разрешается [128, с. 354–355]), поскольку большинство других параметров сокетов, представленных в табл. 7.1, имеют целочисленные аргументы. Изменения, внесенные в IPv6, должны уменьшить вероятность ошибок.</p>
     </cite>
     <p>Теперь мы опишем каждый из девяти параметров сокетов более подробно. Обратите внимание, что эти девять параметров концептуально идентичны в IPv4 и IPv6 — различаются только их названия и типы аргументов.</p>
     <p>&#9632; <code>IP_ADD_MEMBERSHIP</code>, <code>IPV6_JOIN_GROUP</code>, <code>MCAST_JOIN_GROUP</code>. Назначение этих параметров — присоединение к группе на заданном локальном интерфейсе. Мы задаем локальный интерфейс одним из его направленных адресов для IPv4 или индексом интерфейса для IPv6. Следующие три структуры используются при присоединении к группе или при отсоединении от нее:</p>
     <p><code>struct ip_mreq {</code></p>
     <p><code> struct in_addr imr_multiaddr; /* IPv4-адрес многоадресной</code></p>
     <p><code>                                  передачи класса D */</code></p>
     <p><code> struct in_addr imr_interface; /* IPv4-адрес локального</code></p>
     <p><code>                                  интерфейса */</code></p>
     <p><code>};</code></p>
     <empty-line/>
     <p><code>struct ipv6_mreq {</code></p>
     <p><code> struct in6_addr ipv6mr_multiaddr; /* IPv6-адрес многоадресной</code></p>
     <p><code>                                      передачи */</code></p>
     <p><code> unsigned int ipv6mr_interface;    /* индекс интерфейса или 0 */</code></p>
     <p><code>};</code></p>
     <empty-line/>
     <p><code>struct group_req {</code></p>
     <p><code> unsigned int gr_interface;        /* индекс интерфейса или 0 */</code></p>
     <p><code> struct sockaddr_storage gr_group; /* адрес многоадресной передачи</code></p>
     <p><code>                                      IPv4 или IPv6 */</code></p>
     <p><code>};</code></p>
     <p>Если локальный интерфейс задается как универсальный адрес (<code>INADDR_ANY</code> для IPv4) или как нулевой индекс IPv6, то конкретный локальный интерфейс выбирается ядром.</p>
     <p>Мы говорим, что узел принадлежит к данной группе на данном интерфейсе, если один или более процессов в настоящий момент принадлежат к этой группе на этом интерфейсе.</p>
     <p>Сокет может быть присоединен к нескольким группам, но к каждой группе должен быть присоединен уникальный адрес или уникальный интерфейс. Это свойство можно использовать на узле с несколькими сетевыми интерфейсами: создается один сокет, которому присваивается один адрес многоадресной передачи, но благодаря наличию разных интерфейсов этот сокет может быть присоединен к разным группам.</p>
     <p>Вспомните из табл. 21.1, что частью адреса многоадресной передачи IPv6 является поле области действия. Как мы отмечали, адреса многоадресной передачи IPv6, отличающиеся только областью действия, являются различными. Следовательно, если реализация протокола синхронизации времени (network time protocol, NTP) хочет получать все пакеты NTP независимо от их области действия, она должна будет присоединиться к адресу <code>ff01:101</code> (локальный в пределах узла), <code>ff02:101</code> (локальный в пределах физической сети), <code>ff05:101</code> (локальный в пределах сайта), <code>ff08:101</code> (локальный в пределах организации) и <code>ff0e:101</code> (глобальный). Все присоединения могут выполняться на одном сокете. Можно установить параметр сокета <code>IPV6_PKTINFO</code> (см. раздел 22.8), чтобы функция recvmsg возвращала адрес получателя каждой дейтаграммы.</p>
     <p>Независимый от версии IP параметр сокета (<code>MCAST_JOIN_GROUP</code>) аналогичен соответствующему параметру IPv6 за тем исключением, что он использует структуру <code>sockaddr_storage</code> вместо <code>in6_addr</code> для передачи адреса ядру. Структура <code>sockaddr_storage</code> (см. листинг 3.4) достаточно велика для хранения адреса любой версии, поддерживаемой системой.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В большинстве реализаций число присоединений, допустимых для одного сокета, ограничено. Предел определяется константой IP_MAX_MEMBERSHIPS (для Беркли-реализаций ее значение равно 20). В некоторых реализациях это ограничение снято, в других оно значительно превышает значение для Беркли-реализаций.</p>
      <p>Когда интерфейс, на котором будет происходить присоединение, не задан, Беркли-ядра ищут адрес многоадресной передачи в обычной таблице маршрутизации IP и используют полученный в результате интерфейс [128, с. 357]. Некоторые системы для обработки этой ситуации устанавливают маршрут для всех адресов многоадресной передачи (то есть маршрут с адресом получателя 224.0.0.0/8 для IPv4) в процессе инициализации.</p>
      <p>Для IPv6 сделано изменение — при задании интерфейса используется индекс, а не локальный адрес направленной передачи, как было в IPv4. Это позволяет выполнять присоединение на ненумерованных интерфейсах и конечных точках туннелей.</p>
      <p>Изначально в API многоадресной передачи IPv6 использовалась константа IPV6_ADD_MEMBERSHIP, а не IPV6_JOIN_GROUP. Во всех остальных отношениях интерфейс программирования не изменился. Описанная далее функция mcast_join скрывает это отличие.</p>
     </cite>
     <p>&#9632; <code>IP_DROP_MEMBERSHIP</code>, <code>IPV6_LEAVE_GROUP</code> и <code>MCAST_LEAVE_GROUP</code>. Назначение этих параметров — выход из группы на заданном локальном интерфейсе. С этими параметрами сокета применяются те же структуры, которые мы только что показали для присоединения к группе. Если локальный интерфейс не задан (то есть его значение равно <code>INADDR_ANY</code> для IPv4 или индекс интерфейса равен нулю для IPv6), удаляется первое совпадающее с искомым вхождение в группу.</p>
     <p>Если процесс присоединился к группе, но не выходил из группы явно, то при закрытии сокета (либо явном, либо по завершении процесса) вхождение в группу прекращается автоматически. Возможна ситуация, когда несколько процессов на узле присоединились к одной и той же группе, и в этом случае узел остается членом группы, пока последний процесс не выйдет из группы.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Изначально в API многоадресной передачи IPv6 использовалась константа IPV6_DROP_MEMBERSHIP, а не IPV6_LEAVE_GROUP. Во всех остальных отношениях интерфейс программирования не изменился. Описанная далее функция mcast_leave скрывает это отличие.</p>
     </cite>
     <p>&#9632; <code>IP_BLOCK_SOURCE</code>, <code>MCAST_BLOCK_SOURCE</code>. Блокируют получение трафика через данный сокет от конкретного источника для конкретной группы и интерфейса. Если все сокеты, присоединенные к группе, заблокировали один и тот же источник, система может проинформировать маршрутизаторы о нежелательности трафика, что может повлиять на маршрутизацию многоадресного трафика в сети. Локальный интерфейс задается одним из его направленных адресов для IPv4 или индексом для независимого от версии API. Для блокирования и разблокирования источника используются две приведенные ниже структуры:</p>
     <p><code>struct ip_mreq_source {</code></p>
     <p><code> struct in_addr imr_multiaddr;  /* IPv4-адрес многоадресной</code></p>
     <p><code>                                   передачи класса D */</code></p>
     <p><code> struct in_addr imr_sourceaddr; /* IPv4-адрес источника */</code></p>
     <p><code> struct in_addr imr_interface;  /* IPv4-адрес локального</code></p>
     <p><code>                                   интерфейса */</code></p>
     <p><code>};</code></p>
     <empty-line/>
     <p><code>struct group_source_req {</code></p>
     <p><code> unsigned int gsr_interface;         /* индекс интерфейса или 0 */</code></p>
     <p><code> struct sockaddr_storage gsr_group;  /* адрес многоадресной</code></p>
     <p><code>                                        передачи IPv4 или IPv6 */</code></p>
     <p><code> struct sockaddr_storage gsr_source; /* адрес источника IPv4</code></p>
     <p><code>                                        или IPv6 */</code></p>
     <p><code>};</code></p>
     <p>Если локальный интерфейс задается как универсальный адрес (<code>INADDR_ANY</code> для IPv4) или как нулевой индекс IPv6, то конкретный локальный интерфейс выбирается ядром.</p>
     <p>Запрос на блокирование источника действует только для присоединенных групп, то есть таких, которые уже были присоединены к указанному интерфейсу параметром <code>IP_ADD_MEMBERSHIP</code>, <code>IPV6_JOIN_GROUP</code> или <code>MCAST_JOIN_GROUP</code>.</p>
     <p>&#9632; <code>IP_UNBLOCK_SOURCE</code>, <code>MCAST_UNBLOCK_SOURCE</code>. Разблокирование заблокированного ранее источника. Аргументы должны быть в точности те же, что и у предшествовавшего запроса <code>IP_BLOCK_SOURCE</code> или <code>MCAST_BLOCK_SOURCE</code>.</p>
     <p>Если локальный интерфейс задается как универсальный адрес (<code>INADDR_ANY</code> для IPv4) или как нулевой индекс IPv6, то конкретный локальный интерфейс выбирается ядром.</p>
     <p>&#9632; <code>IP_ADD_SOURCE_MEMBERSHIР</code>, <code>MCAST_JOIN_SOURCE_GROUP</code>. Присоединение к группе конкретного источника на заданном локальном интерфейсе. С этим параметром используются те же структуры, что и с параметрами блокирования и разблокирования источника. Сокет не должен быть присоединен к той же группе без указания источника (параметры <code>IP_ADD_MEMBERSHIP</code>, <code>IPV6_JOIN_GROUP</code>, <code>MCAST_JOIN_GROUP</code>).</p>
     <p>Если локальный интерфейс задается как универсальный адрес (<code>INADDR_ANY</code> для IPv4) или как нулевой индекс IPv6, то конкретный локальный интерфейс выбирается ядром.</p>
     <p>&#9632; <code>IP_DROP_SOURCE_MEMBERSHIP</code>, <code>MCAST_LEAVE_SOURCE_GROUP</code>. Отключение от группы источника конкретного локального интерфейса. Используются те же структуры, что и с предыдущими параметрами сокетов. Если локальный интерфейс не указан (значение <code>INADDR_ANY</code> для IPv4 или 0 для независимого от версии API), отключается первая группа, удовлетворяющая заданным значениям.</p>
     <p>Если процесс присоединяется к группе источника, но не отключается от нее явно, отсоединение производится автоматически при закрытии сокета (явном или также автоматическом, при завершении процесса). Несколько процессов узла могут присоединиться к одной и той же группе источника, в случае чего узел остается в группе до тех пор, пока из нее не выйдет последний процесс.</p>
     <p>&#9632; <code>IP_MULTICAST_IF</code> и <code>IPV6_MULTICAST_IF</code>. Назначение этих параметров - задание интерфейса для исходящих дейтаграмм многоадресной передачи, отправленных на этом сокете. Этот интерфейс задается либо структурой <code>in_addr</code> для IPv4, либо индексом интерфейса для IPv6. Если задано значение <code>INADDR_ANY</code> для IPv4 или нулевой индекс интерфейса для IPv6, то удаляется любой интерфейс, ранее заданный этим параметром сокета, и система будет выбирать интерфейс каждый раз при отправке дейтаграммы.</p>
     <p>Будьте внимательны, четко различая локальный интерфейс, заданный (или выбранный), когда процесс присоединяется к группе (интерфейс для получения приходящих дейтаграмм многоадресной передачи), и локальный интерфейс, заданный (или выбранный) для исходящих дейтаграмм.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Беркли-ядра выбирают интерфейс для исходящих дейтаграмм многоадресной передачи по умолчанию при помощи обычной таблицы маршрутизации IP. В ней выполняется поиск маршрута к групповому адресу получателя, после чего используется соответствующий интерфейс. Это та же технология, что используется для выбора принимающего интерфейса, если процесс не задает его в процессе присоединения к группе. При этом считается, что если для данного адреса многоадресной передачи существует маршрут (возможно, маршрут, заданный по умолчанию в таблице маршрутизации), то соответствующий интерфейс должен использоваться для ввода и вывода.</p>
     </cite>
     <p>&#9632; <code>IP_MULTICAST_TTL</code> и <code>IPV6_MULTICAST_HOPS</code>. Назначение этих параметров - установка значения поля TTL в случае IPv4 или предельного количества транзитных узлов в случае IPv6 для исходящих дейтаграмм многоадресной передачи. По умолчанию значение обоих параметров равно 1, что ограничивает дейтаграмму локальной подсетью.</p>
     <p>&#9632; <code>IP_MULTICAST_LOOP</code> и <code>IPV6_MULTICAST_LOOP</code>. Назначение этих параметров - включение или отключение локальной закольцовки для дейтаграмм многоадресной передачи. По умолчанию закольцовка включена: копия каждой дейтаграммы многоадресной передачи, посылаемой процессом на узле, будет отправлена обратно на этот узел и обработана им, как любая другая полученная дейтаграмма, если узел принадлежит данной группе на исходящем интерфейсе.</p>
     <p>Это аналогично широковещательной передаче, где мы видели, что широковещательные сообщения, посылаемые на узле, также обрабатываются на нем, как полученные дейтаграммы (см. рис. 20.3). (Но в случае широковещательной передачи нет возможности отключить закольцовку.) Это значит, что если процесс входит в ту группу, которой он отправляет дейтаграммы, он будет получать свои собственные передачи.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Описываемая здесь закольцовка является внутренней и выполняется на уровне IP или выше. Если интерфейс получает копии своих передач, RFC 1112 [26] требует, чтобы драйвер игнорировал эти копии. В этом документе также утверждается, что параметр закольцовки по умолчанию включен «в целях оптимизации производительности для протоколов верхнего уровня, которые ограничивают членство в группе до одного процесса на узел (например, маршрутизирующих протоколов)».</p>
     </cite>
     <p>Первые шесть пар параметров сокетов (<code>ADD_MEMBERSHIP/JOIN_GROUP</code>, <code>DROP_MEMBERSHIP/LEAVE_GROUP</code>, <code>BLOCK_SOURCE</code>, <code>UNBLOCK_SOURCE</code>, <code>ADD_SOURCE_MEMBERSHIP/JOIN_SOURCE_GROUP</code>, <code>DROP_SOURCE_MEMBERSHIP/LEAVE_SOURCE_GROUP</code>) влияют на получение дейтаграмм многоадресной передачи, в то время как последние три пары параметров влияют на отправку дейтаграмм многоадресной передачи (интерфейс для исходящих сообщений, TTL или предел количества транзитных узлов, закольцовка). Ранее мы отмечали, что для отправки дейтаграммы многоадресной передачи ничего особенного не требуется. Если ни один параметр сокетов многоадресной передачи не задан перед отправкой дейтаграммы, интерфейс для исходящей дейтаграммы будет выбран ядром, TTL или предел количества транзитных узлов будут равны 1, а копия отправленной дейтаграммы будет посылаться обратно (то есть будет включена закольцовка).</p>
     <p>Чтобы получить дейтаграмму многоадресной передачи, процесс должен присоединиться к группе, а также связать при помощи функции <code>bind</code> сокет UDP с номером порта, который будет использоваться как номер порта получателя для дейтаграмм, отсылаемых данной группе. Это две отдельные операции, и обе они являются обязательными. Присоединение к группе указывает уровню IP узла и канальному уровню, что необходимо получать дейтаграммы многоадресной передачи, отправленные этой группе. Связывая порт, приложение указывает UDP, что требуется получать отправляемые на этот порт дейтаграммы. Некоторые приложения в дополнение к связыванию порта также связывают при помощи функции <code>bind</code> адрес многоадресной передачи с сокетом. Это предотвращает доставку сокету любых других дейтаграмм, которые могли быть получены для этого порта.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Исторически Беркли-реализации требуют только, чтобы некоторый сокет на узле присоединился к группе — это не обязательно тот сокет, который связывается с портом и затем получает дейтаграммы многоадресной передачи. Однако есть вероятность, что эти реализации могут доставлять дейтаграммы многоадресной передачи приложениям, не знающим о многоадресной передаче. Более новые ядра требуют, чтобы процесс связывался с портом и устанавливал какой-нибудь параметр сокета многоадресной передачи для сокета как указатель того, что приложение знает о многоадресной передаче. Самый обычный параметр сокета многоадресной передачи — признак присоединения к группе. Для Solaris 2.5 характерны некоторые отличия: дейтаграммы многоадресной передачи доставляются только на те сокеты, которые присоединились к группе и связались с портом. В целях переносимости все приложения многоадресной передачи должны присоединиться к группе и связаться с портом.</p>
      <p>Более новый интерфейс многоадресного сервиса требует, чтобы уровень IP доставлял многоадресные пакеты сокету только в том случае, если этот сокет присоединился к группе или источнику. Такое требование было введено с IGMPv3 (RFC 3376 [16]), чтобы разрешить фильтрацию источников и многоадресную передачу от источника. Таким образом ужесточается требование на присоединение к группе, но зато ослабляется требование на связывание группового адреса. Однако для наибольшей переносимости со старыми и новыми интерфейсами приложения должны присоединяться к группам и связывать сокеты с групповыми адресами.</p>
      <p>Некоторые более старые узлы, имеющие возможность многоадресной передачи, не позволяют связывать адрес многоадресной передачи с сокетом при помощи функции bind. В целях переносимости приложение может игнорировать ошибку функции bind при связывании адреса многоадресной передачи с сокетом и делать повторную попытку с адресом INADDR_ANY или in6addr_any.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>21.7. Функция mcast_join и родственные функции</p>
     </title>
     <section>
      <p>Несмотря на то что параметры сокетов многоадресной передачи для IPv4 аналогичны параметрам сокетов многоадресной передачи для IPv6, есть достаточно много различий, из-за которых не зависящий от протокола код, использующий многоадресную передачу, усложняется и содержит множество директив <code>#ifdef</code>. Наилучшим решением будет использование приведенных ниже восьми функций, позволяющих скрыть различия реализаций:</p>
      <p><code>#include "unp.h"</code></p>
      <empty-line/>
      <p><code>int mcast_join(int <emphasis>sockfd</emphasis>, const struct sockaddr *<emphasis>grp</emphasis>,</code></p>
      <p><code> socklen_t <emphasis>grplen</emphasis>, const char *<emphasis>ifname</emphasis>, u_int <emphasis>ifindex</emphasis>);</code></p>
      <p><code>int mcast_leave(int <emphasis>sockfd</emphasis>, const struct sockaddr *<emphasis>grp</emphasis>,</code></p>
      <p><code> socklen_t <emphasis>grplen</emphasis>);</code></p>
      <p><code>int mcast_block_source(int <emphasis>sockfd</emphasis>,</code></p>
      <p><code> const struct sockaddr *<emphasis>src</emphasis>, socklen_t <emphasis>srclen</emphasis>,</code></p>
      <p><code> const struct sockaddr *<emphasis>grp</emphasis>, socklen_t <emphasis>grplen</emphasis>);</code></p>
      <p><code>int mcast_unblock_source(int <emphasis>sockfd</emphasis>,</code></p>
      <p><code> const struct sockaddr *<emphasis>src</emphasis>, socklen_t <emphasis>srclen</emphasis>,</code></p>
      <p><code> const struct sockaddr *<emphasis>grp</emphasis>, socklen_t <emphasis>grplen</emphasis>);</code></p>
      <p><code>int mcast_join_source_group(int <emphasis>sockfd</emphasis>,</code></p>
      <p><code> const struct sockaddr *<emphasis>src</emphasis>, socklen_t <emphasis>srclen</emphasis>,</code></p>
      <p><code> const struct sockaddr *<emphasis>grp</emphasis>, socklen_t <emphasis>grplen</emphasis>,</code></p>
      <p><code> const char *<emphasis>ifname</emphasis>, u_int <emphasis>ifindex</emphasis>);</code></p>
      <p><code>int mcast_leave_source_group(int <emphasis>sockfd</emphasis>,</code></p>
      <p><code> const struct sockaddr *<emphasis>src</emphasis>, socklen_t <emphasis>srclen</emphasis>,</code></p>
      <p><code> const struct sockaddr *<emphasis>grp</emphasis>, socklen_t <emphasis>grplen</emphasis>);</code></p>
      <p><code>int mcast_set_if(int <emphasis>sockfd</emphasis>, const char *<emphasis>ifname</emphasis>, u_int <emphasis>ifindex</emphasis>);</code></p>
      <p><code> int mcast_set_loop(int <emphasis>sockfd</emphasis>, int <emphasis>flag</emphasis>);</code></p>
      <p><code>int mcast_set_ttl(int <emphasis>sockfd</emphasis>, int <emphasis>ttl</emphasis>);</code></p>
      <p><code><emphasis>Все перечисленные выше функции возвращают: 0 в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
      <empty-line/>
      <p><code>int mcast_get_if(int <emphasis>sockfd</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: неотрицательный индекс интерфейса в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
      <empty-line/>
      <p><code>int mcast_get_loop(int <emphasis>sockfd</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: текущий флаг закольцовки в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
      <empty-line/>
      <p><code>int mcast_get_ttl(int <emphasis>sockfd</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: текущее значение TTL или предельное количество транзитных узлов в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
      <p>Функция <code>mcast_join</code> присоединяет узел к группе. IP-адрес этой группы содержится в структуре адреса сокета, на которую указывает аргумент <code>grp</code>, а длина этой структуры задается аргументом <code>grplen</code>. Мы можем задать интерфейс, на котором должно происходить присоединение к группе, либо через имя интерфейса (непустой аргумент <code>ifname</code>), либо через ненулевой индекс интерфейса (непустой аргумент <code>ifindex</code>). Если ни одно из этих значений не задано, ядро самостоятельно выбирает интерфейс, на котором происходит присоединение к группе. Вспомните, что в случае IPv6 для работы с параметрами сокета интерфейс задается по его индексу. Если для сокета IPv6 известно имя интерфейса, нужно вызвать функцию <code>if_nametoindex</code>, чтобы получить индекс интерфейса. В случае параметра сокета IPv4 мы задаем интерфейс по его IP-адресу направленной передачи. Если для сокета IPv4 интерфейс задан по имени, нужно вызвать функцию <code>ioctl</code> с запросом <code>SIOCGIFADDR</code> для получения IP-адреса направленной передачи для этого интерфейса. Если для сокета IPv4 задан индекс интерфейса, мы сначала вызываем функцию <code>if_indextoname</code>, чтобы получить имя интерфейса, а затем обрабатываем имя так, как только что было сказано.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Пользователи обычно задают имя интерфейса le0 или ether0, а IP-адрес и индекс интерфейса не используются. Например, tcpdump является одной из немногих программ, позволяющих пользователю задавать интерфейс, а ее параметр -i принимает имя интерфейса в качестве аргумента.</p>
      </cite>
      <p>Функция <code>mcast_leave</code> выводит узел из группы с IP-адресом, содержащимся в структуре адреса сокета, на которую указывает аргумент <code>grp</code>.</p>
      <p>Функция <code>mcast_block_source</code> блокирует получение через конкретный сокет пакетов, относящихся к определенной группе и исходящих от определенного источника. IP-адреса группы и источника хранятся в структурах адреса сокета, на которые указывают аргументы <code>grp</code> и <code>src</code> соответственно. Длины структур задаются параметрами <code>srclen</code> и <code>grplen</code>. Для успешного завершения функции необходимо, чтобы до ее вызова уже была вызвана функция <code>mcast_join</code> для того же сокета и той же группы.</p>
      <p>Функция <code>mcast_unblock_source</code> разблокирует получение трафика от источника из заданной группы. Аргументы <code>src</code>, <code>srclen</code>, <code>grp</code> и <code>grplen</code> имеют тот же смысл, что и аргументы предыдущей функции, и должны совпадать с ними по значениям.</p>
      <p>Функция <code>mcast_join_source_group</code> выполняет присоединение к группе источника. Адрес источника и адрес группы содержатся в структурах адреса сокета, на которые указывают аргументы <code>src</code> и <code>grp</code>. Длины структур задаются параметрами <code>srclen</code> и <code>grplen</code>. Интерфейс, присоединяемый к группе, может быть задан именем (ненулевой аргумент <code>ifname</code>) или индексом (<code>ifindex</code>). Если интерфейс не задан явно, ядро выбирает его самостоятельно.</p>
      <p>Функция <code>mcast_leave_source_group</code> выполняет отсоединение от группы источника. Адреса источника и группы содержатся в структурах адреса сокета, на которые указывают аргументы <code>src</code> и <code>grp</code>. Длины структур задаются параметрами <code>srclen</code> и <code>grplen</code>. Подобно <code>mcast_leave</code>, <code>mcast_leave_source_group</code> не требует указания интерфейса: она всегда отсоединяет от группы первый интерфейс, удовлетворяющий условиям.</p>
      <p>Функция <code>mcast_set_if</code> устанавливает индекс интерфейса по умолчанию для исходящих дейтаграмм многоадресной передачи. Если аргумент <code>ifname</code> непустой, он задает имя интерфейса. Иначе положительное значение аргумента <code>ifindex</code> будет задавать индекс интерфейса. В случае IPv6 имя сопоставляется индексу с использованием функции <code>if_nametoindex</code>. В случае IPv4 сопоставление имени или индекса IP-адресу направленной передачи интерфейса происходит так же, как для функции <code>mcast_join</code>.</p>
      <p>Функция <code>mcast_set_loop</code> устанавливает параметр закольцовки либо в 0, либо в 1, а функция <code>mcast_set_ttl</code> TTL в случае IPv4 или предел количества транзитных узлов в случае IPv6. Функции <code>mcast_get_XXX</code> возвращают соответствующие значения.</p>
     </section>
     <section>
      <title>
       <p>Пример: функция mcast_join</p>
      </title>
      <p>В листинге 21.1<a l:href="#n1" type="note">[1]</a> показана первая часть функции <code>mcast_join</code>. Эта часть демонстрирует простоту интерфейса программирования, не зависящего от протокола.</p>
      <p><strong>Листинг 21.1</strong>. Присоединение к группе: сокет IPv4</p>
      <p><code>//lib/mcast_join.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;net/if.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 mcast_join(int sockfd, const SA *grp, socklen_t grplen,</code></p>
      <p><code> 5 const char *ifname, u_int ifindex)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7 #ifdef MCAST_JOIN_GROUP</code></p>
      <p><code> 8  struct group_req req;</code></p>
      <p><code> 9  if (ifindex &gt; 0) {</code></p>
      <p><code>10   req.gr_interface = ifindex;</code></p>
      <p><code>11  } else if (ifname != NULL) {</code></p>
      <p><code>12   if ((req.gr_interface = if_nametoindex(ifname)) == 0) {</code></p>
      <p><code>13    errno = ENXIO; /* интерфейс не найден */</code></p>
      <p><code>14    return(-1);</code></p>
      <p><code>15   }</code></p>
      <p><code>16  } else</code></p>
      <p><code>17  req.gr_interface = 0;</code></p>
      <p><code>18  if (grplen &gt; sizeof(req.gr_group)) {</code></p>
      <p><code>19   errno = EINVAL;</code></p>
      <p><code>20   return -1;</code></p>
      <p><code>21  }</code></p>
      <p><code>22  memcpy(&amp;req.gr_group, grp, grplen);</code></p>
      <p><code>23  return (setsockopt(sockfd, family_to_level(grp-&gt;sa_family),</code></p>
      <p><code>24  MCAST_JOIN_GROUP, &amp;req, sizeof(req)));</code></p>
      <p><code>25 #else</code></p>
      <subtitle>Обработка индекса</subtitle>
      <p><code>9-17</code> Если при вызове был указан индекс интерфейса, функция использует его непосредственно. В противном случае (при указании имени интерфейса), имя преобразуется в индекс вызовом <code>if_nametoindex</code>. Если ни имя, ни индекс не заданы, интерфейс выбирается ядром.</p>
      <subtitle>Копирование адреса и вызов setsockopt</subtitle>
      <p><code>18-22</code> Адрес сокета копируется непосредственно в поле группы. Вспомните, что поле это имеет тип <code>sockaddr_storage</code>, а потому достаточно велико для хранения адреса любого типа, поддерживаемого системой. Для предотвращения переполнения буфера (при ошибках в программе) мы проверяем размер <code>sockaddr</code> и возвращаем <code>EINVAL</code>, если он слишком велик.</p>
      <p><code>23-24</code> Присоединение к группе выполняется вызовом <code>setsockopt</code>. Аргумент <code>level</code> определяется на основании семейства группового адреса вызовом нашей собственной функции <code>family_to_level</code>. Некоторые системы допускают несоответствие аргумента level семейству адреса сокета, например использование <code>IPPROTO_IP</code> с <code>MCAST_JOIN_GROUP</code>, даже если сокет относится к семейству <code>AF_INET6</code>, но это верно не для всех систем, поэтому мы и должны выполнить преобразование семейства к нужному значению <code>level</code>. Листинг этой тривиальной функции в книге мы не приводим, но исходный код этой функции вы можете скачать вместе со всеми остальными программами.</p>
      <p>В листинге 21.2 представлена вторая часть функции <code>mcast_join</code>, обрабатывающая сокеты IPv4.</p>
      <p><strong>Листинг 21.2</strong>. Присоединение к группе: обработка сокета IPv4</p>
      <p><code>26  switch (grp-&gt;sa_family) {</code></p>
      <p><code>27  case AF_INET: {</code></p>
      <p><code>28   struct ip_mreq mreq;</code></p>
      <p><code>29   struct ifreq ifreq;</code></p>
      <empty-line/>
      <p><code>30   memcpy(&amp;mreq.imr_multiaddr,</code></p>
      <p><code>31    &amp;((const struct sockaddr_in*)grp)-&gt;sin_addr,</code></p>
      <p><code>32    sizeof(struct in_addr));</code></p>
      <empty-line/>
      <p><code>33    if (ifindex &gt; 0) {</code></p>
      <p><code>34     if (if_indextoname(ifindex, ifreq.ifr_name) == NULL) {</code></p>
      <p><code>35      errno = ENXIO; /* i/f index not found */</code></p>
      <p><code>36      return(-1);</code></p>
      <p><code>37     }</code></p>
      <p><code>38     goto doioctl;</code></p>
      <p><code>39    } else if (ifname != NULL) {</code></p>
      <p><code>40     strncpy(ifreq.ifr_name, ifname, IFNAMSIZ);</code></p>
      <p><code>41 doioctl:</code></p>
      <p><code>42     if (ioctl(sockfd, SIOCGIFADDR, &amp;ifreq) &lt; 0)</code></p>
      <p><code>43      return(-1);</code></p>
      <p><code>44     memcpy(&amp;mreq.imr_interface,</code></p>
      <p><code>45      &amp;((struct sockaddr_in*)&amp;ifreq.ifr_addr)-&gt;sin_addr,</code></p>
      <p><code>46      sizeof(struct in_addr));</code></p>
      <p><code>47    } else</code></p>
      <p><code>48     mreq.imr_interface.s_addr = htonl(INADDR_ANY);</code></p>
      <empty-line/>
      <p><code>49    return(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,</code></p>
      <p><code>50     &amp;mreq, sizeof(mreq)));</code></p>
      <p><code>51   }</code></p>
      <subtitle>Обработка индекса</subtitle>
      <p><code>33-38</code> Адрес многоадресной передачи IPv4 в структуре адреса сокета копируется в структуру <code>ip_mreq</code>. Если индекс был задан, вызывается функция <code>if_indextoname</code>, сохраняющая имя в нашей структуре <code>ip_mreq</code>. Если это выполняется успешно, мы переходим на точку вызова <code>ioctl</code>.</p>
      <subtitle>Обработка имени</subtitle>
      <p><code>39-46</code> Имя вызывающего процесса копируется в структуру <code>ip_mreq</code>, а вызов <code>SIOCGIFADDR</code> функции <code>ioctl</code> возвращает адрес многоадресной передачи, связанный с этим именем. При успешном выполнении адрес IPv4 копируется в элемент <code>imr_interface</code> структуры <code>ip_mreq</code>.</p>
      <subtitle>Значения по умолчанию</subtitle>
      <p><code>47-48</code> Если ни индекс, ни имя не заданы, используется универсальный адрес, что указывает ядру на необходимость выбрать интерфейс.</p>
      <p><code>49-50</code> Функция <code>setsockopt</code> выполняет присоединение к группе.</p>
      <p>Третья, и последняя, часть функции, обрабатывающая сокеты IPv6, приведена в листинге 21.3.</p>
      <p><strong>Листинг 21.3</strong>. Присоединение к группе: обработка сокета IPv6</p>
      <p><code>52 #ifdef IPV6</code></p>
      <p><code>53  case AF_INET6: {</code></p>
      <p><code>54   struct ipv6_mreq mreq6;</code></p>
      <empty-line/>
      <p><code>55   memcpy(&amp;mreq6.ipv6mr_multiaddr,</code></p>
      <p><code>56    &amp;((const struct sockaddr_in6*) grp)-&gt;sin6_addr,</code></p>
      <p><code>57    sizeof(struct in6_addr));</code></p>
      <empty-line/>
      <p><code>58    if (ifindex &gt; 0) {</code></p>
      <p><code>59     mreq6.ipv6mr_interface = ifindex;</code></p>
      <p><code>60    } else if (ifname != NULL) {</code></p>
      <p><code>61     if ((mreq6.ipv6mr_interface = if_nametoindex(ifname)) == 0) {</code></p>
      <p><code>62      errno = ENXIO; /* интерфейс не найден */</code></p>
      <p><code>63      return(-1);</code></p>
      <p><code>64     }</code></p>
      <p><code>65    } else</code></p>
      <p><code>66     mreq6.ipv6mr_interface = 0;</code></p>
      <empty-line/>
      <p><code>67    return(setsockopt(sockfd, IPPROTO_IPV6, IPV6_JOIN_GROUP,</code></p>
      <p><code>68     &amp;mreq6, sizeof(mreq6)));</code></p>
      <p><code>69   }</code></p>
      <p><code>70 #endif</code></p>
      <empty-line/>
      <p><code>71  default:</code></p>
      <p><code>72   errno = EAFNOSUPPORT;</code></p>
      <p><code>73   return(-1);</code></p>
      <p><code>74  }</code></p>
      <p><code>75 #endif</code></p>
      <p><code>76 }</code></p>
      <subtitle>Копирование адреса</subtitle>
      <p><code>55-57</code> Сначала адрес IPv6 копируется из структуры адреса сокета в структуру <code>ipv6_mreq</code>.</p>
      <subtitle>Обработка индекса или имени интерфейса или выбор интерфейса по умолчанию</subtitle>
      <p><code>58-66</code> Если был задан индекс, он записывается в элемент <code>ipv6mr_interface</code>. Если индекс не задан, но задано имя, то для получения индекса вызывается функция <code>if_nametoindex</code>. В противном случае для функции <code>setsockopt</code> индекс устанавливается в 0, что указывает ядру на необходимость выбрать интерфейс.</p>
      <p><code>67-68</code> Выполняется присоединение к группе.</p>
     </section>
     <section>
      <title>
       <p>Пример: функция mcast_set_loop</p>
      </title>
      <p>В листинге 21.4 показана наша функция <code>mcast_set_loop</code>.</p>
      <p>Поскольку аргументом является дескриптор сокета, а не структура адреса сокета, мы вызываем нашу функцию <code>sockfd_to_family</code>, чтобы получить семейство адресов сокета. Устанавливается соответствующий параметр сокета.</p>
      <p>Мы не показываем исходный код для всех остальных функций <code>mcast_<emphasis>XXX</emphasis></code>, так как он свободно доступен в Интернете (см. предисловие).</p>
      <p><strong>Листинг 21.4</strong>. Установка параметра закольцовки для многоадресной передачи</p>
      <p><code>//lib/mcast_set_loop.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 mcast_set_loop(int sockfd, int onoff)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  switch (sockfd_to_family(sockfd)) {</code></p>
      <p><code> 6  case AF_INET:{</code></p>
      <p><code> 7   u_char flag;</code></p>
      <empty-line/>
      <p><code> 8   flag = onoff;</code></p>
      <p><code> 9   return (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP,</code></p>
      <p><code>10    &amp;flag, sizeof(flag)));</code></p>
      <p><code>11  }</code></p>
      <empty-line/>
      <p><code>12 #ifdef IPV6</code></p>
      <p><code>13  case AF_INET6:{</code></p>
      <p><code>14   u_int flag;</code></p>
      <empty-line/>
      <p><code>15   flag = onoff;</code></p>
      <p><code>16   return (setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,</code></p>
      <p><code>17    &amp;flag, sizeof(flag)));</code></p>
      <p><code>18  }</code></p>
      <p><code>19 #endif</code></p>
      <empty-line/>
      <p><code>20  default:</code></p>
      <p><code>21   errno = EPROTONOSUPPORT;</code></p>
      <p><code>22   return (-1);</code></p>
      <p><code>23  }</code></p>
      <p><code>24 }</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>21.8 Функция dg_cli, использующая многоадресную передачу</p>
     </title>
     <section>
      <p>Мы изменяем нашу функцию <code>dg_cli</code>, показанную в листинге 20.1, просто удаляя вызов функции <code>setsockopt</code>. Как мы сказали ранее, для отправки дейтаграмм многоадресной передачи не нужно устанавливать ни одного параметра сокета многоадресной передачи, если нас устраивают заданные по умолчанию настройки интерфейса исходящих пакетов, значения TTL и параметра закольцовки. Мы запускаем нашу программу, задавая в качестве адреса получателя группу всех узлов (all-hosts group):</p>
      <p><code>macosx % <strong>udpcli01 224.0.1.1</strong></code></p>
      <p><code><strong>hi there</strong></code></p>
      <p><code>from 172.24.37.78: hi there <emphasis>MacOS X</emphasis></code></p>
      <p><code>from 172.24.37.94: hi there <emphasis>FreeBSD</emphasis></code></p>
      <p>Отвечают оба узла, находящиеся в подсети. На обоих работают многоадресные эхо-серверы. Каждый ответ является направленным, поскольку адрес отправителя запроса, используемый сервером в качестве адреса получателя ответа, является адресом направленной передачи.</p>
     </section>
     <section>
      <title>
       <p>Фрагментация IP и многоадресная передача</p>
      </title>
      <p>В конце раздела 20.4 мы отмечали, что в большинстве систем фрагментация широковещательной дейтаграммы не допускается по стратегическим соображениям. Фрагментация допускается при многоадресной передаче, что мы можем легко проверить, используя тот же файл с 2000-байтовой строкой:</p>
      <p><code>macosx % <strong>udpcli01 224.0.1.1 &lt; 2000line</strong></code></p>
      <p><code>from 172.24.37.78: xxxxxxx[...]</code></p>
      <p><code>from 172.24.37.94: xxxxxxx[...]</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>21.9. Получение анонсов сеансов многоадресной передачи</p>
     </title>
     <p>Многоадресная инфраструктура представляет собой часть Интернета, в которой разрешена многоадресная передача между доменами. Во всем Интернете многоадресная передача не разрешена. Многоадресная инфраструктура Интернета начала свое существование в 1992 году. Тогда она называлась MBone и была оверлейной сетью. В 1998 году MBone была признана частью инфраструктуры Интернета. Внутри предприятий многоадресная передача используется достаточно широко, но междоменная передача поддерживается гораздо меньшим числом серверов.</p>
     <p>Для участия в мультимедиа-конференции по сети многоадресной передачи достаточно того, чтобы сайту был известен групповой адрес конференции и порты UDP для потоков данных (например, аудио и видео). <emphasis>Протокол анонсирования сеансов</emphasis> (<emphasis>Session Announcement Protocol</emphasis>, <emphasis>SAP</emphasis>) определяет эту процедуру, описывая заголовки пакетов и частоту, с которой эти анонсы при помощи многоадресной передачи передаются по инфраструктуре многоадресной передачи. Этот протокол описан в RFC 2974 [42]. Протокол описания сеанса (Session Description Protocol, SDP) [41] описывает технические параметры сеанса связи (в частности, он определяет, как задаются адреса многоадресной передачи и номера портов UDP). Сайт, желающий анонсировать сеанс, периодически посылает пакет многоадресной передачи, содержащий описание сеанса, для известной группы на известный порт UDP. Для получения этих анонсов сайты запускают программу под названием <code>sdr</code>. Эта программа не только получает объявления сеансов, но и предоставляет интерактивный интерфейс пользователя, позволяющий пользователю отправлять свои собственные анонсы.</p>
     <p>В этом разделе мы продемонстрируем прием пакетов многоадресной передачи, создав пример простой программы, лишь получающей анонсы сеансов. В данном случае мы стремимся показать простоту устройства получателя пакетов при многоадресной передаче, а не исследовать подробности конкретного приложения.</p>
     <p>В листинге 21.5 показана наша программа main, получающая периодические анонсы SAP/SDP.</p>
     <p><strong>Листинг 21.5</strong>. Программа main, получающая периодические анонсы SAP/SDP</p>
     <p><code>//mysdr/main.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 #define SAP_NAME "sap.mcast.net" /* имя группы и порт по умолчанию */</code></p>
     <p><code> 3 #define SAP_PORT "9875"</code></p>
     <empty-line/>
     <p><code> 4 void loop(int, socklen_t);</code></p>
     <empty-line/>
     <p><code> 5 int</code></p>
     <p><code> 6 main(int argc, char **argv)</code></p>
     <p><code> 7 {</code></p>
     <p><code> 8  int sockfd;</code></p>
     <p><code> 9  const int on = 1;</code></p>
     <p><code>10  socklen_t salen;</code></p>
     <p><code>11  struct sockaddr *sa;</code></p>
     <empty-line/>
     <p><code>12  if (argc == 1)</code></p>
     <p><code>13   sockfd = Udp_client(SAP_NAME, SAP_PORT, (void**)&amp;sa, &amp;salen);</code></p>
     <p><code>14  else if (argc == 4)</code></p>
     <p><code>15   sockfd = Udp_client(argv[1], argv[2], (void**)&amp;sa, &amp;salen);</code></p>
     <p><code>16  else</code></p>
     <p><code>17   err_quit("usage: mysdr &lt;mcast-addr&gt; &lt;port#&gt; &lt;interface-name&gt;");</code></p>
     <empty-line/>
     <p><code>18  Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
     <p><code>19  Bind(sockfd, sa, salen);</code></p>
     <empty-line/>
     <p><code>20  Mcast_join(sockfd, sa, salen, (argc == 4) ? argv[3], NULL, 0);</code></p>
     <empty-line/>
     <p><code>21  loop(sockfd, salen); /* получение и вывод */</code></p>
     <empty-line/>
     <p><code>22  exit(0);</code></p>
     <p><code>23 }</code></p>
     <subtitle>Заранее известные имя и порт</subtitle>
     <p><code>2-3</code> Адрес многоадресной передачи, заданный для анонсов SAP — 224.2.127.254, а его имя — <code>sap.mcast.net</code>. Все заранее известные адреса многоадресной передачи (см. <code>http://www.iana.org/assignments/multicast-addresses</code>) появляются в DNS в иерархии <code>mcast.net</code>. Заранее известный порт UDP — это порт 9875.</p>
     <subtitle>Создание сокета UDP</subtitle>
     <p><code>12-17</code> Мы вызываем нашу функцию <code>udp_client</code>, чтобы просмотреть имя и порт, и она заполняет соответствующую структуру адреса сокета. Если не заданы аргументы командной строки, мы используем значения по умолчанию. В противном случае мы получаем адрес многоадресной передачи, порт и имя интерфейса из аргументов командной строки.</p>
     <subtitle>Связывание порта с помощью функции bind</subtitle>
     <p><code>18-19</code> Мы устанавливаем параметр сокета <code>SO_REUSEADDR</code>, чтобы позволить множеству экземпляров этой программы запуститься на узле, и с помощью функции bind связываем порт с сокетом. Связывая адрес многоадресной передачи с сокетом, мы запрещаем сокету получать какие-либо другие дейтаграммы UDP, которые могут быть получены для этого порта. Связывание этого адреса многоадресной передачи не является обязательным, но оно обеспечивает возможность фильтрации, благодаря чему ядро может не принимать пакеты, которые его не интересуют.</p>
     <subtitle>Присоединение к группе</subtitle>
     <p><code>20</code> Мы вызываем нашу функцию <code>mcast_join</code>, чтобы присоединиться к группе. Если имя интерфейса было задано в качестве аргумента командной строки, оно передается нашей функции, иначе мы позволяем ядру выбрать интерфейс, на котором будет происходить присоединение к группе.</p>
     <p><code>21</code> Мы вызываем нашу функцию <code>loop</code>, показанную в листинге 21.6, чтобы прочитать и вывести все анонсы.</p>
     <p><strong>Листинг 21.6</strong>. Цикл, получающий и выводящий анонсы SAP/SDP</p>
     <p><code>//mysdr/loop.c</code></p>
     <p><code> 1 #include "mysdr.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 loop(int sockfd, socklen_t salen)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  socklen_t len;</code></p>
     <p><code> 6  ssize_t   n;</code></p>
     <p><code> 7  char      *p;</code></p>
     <p><code> 8  struct sockaddr *sa;</code></p>
     <p><code> 9  struct sap_packet {</code></p>
     <p><code>10   uint32_t sap_header;</code></p>
     <p><code>11   uint32_t sap_src;</code></p>
     <p><code>12   char     sap_data[BUFFSIZE];</code></p>
     <p><code>13  } buf;</code></p>
     <empty-line/>
     <p><code>14  sa = Malloc(salen);</code></p>
     <empty-line/>
     <p><code>15  for (;;) {</code></p>
     <p><code>15   len = salen;</code></p>
     <p><code>17   n = Recvfrom(sockfd, &amp;buf, sizeof(buf) - 1, 0, sa, &amp;len);</code></p>
     <p><code>18   ((char *)&amp;buf)[n] = 0; /* завершающий нуль */</code></p>
     <p><code>19   buf.sap_header = ntohl(buf.sap_header);</code></p>
     <p><code>20   printf("From %s hash 0х%0х\n" Sock_ntop(sa, len),</code></p>
     <p><code>21    buf.sap_header &amp; SAP_HASH_MASK);</code></p>
     <p><code>22   if (((buf.sap_header &amp; SAP_VERSION_MASK) &gt;&gt; SAP_VERSION_SHIFT) &gt; 1) {</code></p>
     <p><code>23    err_msg("... version field not 1 (0x%08x)", buf.sap_header);</code></p>
     <p><code>24    continue;</code></p>
     <p><code>25   }</code></p>
     <p><code>26   if (buf.sap_header &amp; SAP_IPV6) {</code></p>
     <p><code>27    err_msg("... IPv6");</code></p>
     <p><code>28    continue;</code></p>
     <p><code>29   }</code></p>
     <p><code>30   if (buf.sap_header &amp; (SAP_DELETE|SAP_ENCRYPTED|SAP_COMPRESSED)) {</code></p>
     <p><code>31    err_msg("... can't parse this packet type (0x%80x)",</code></p>
     <p><code>32    buf.sap_header);</code></p>
     <p><code>33    continue;</code></p>
     <p><code>34   }</code></p>
     <p><code>35   p = buf.sap_data + ((buf.sap_header &amp; SAP AUTHLEN_MASK)</code></p>
     <p><code>36    &gt;&gt; SAP_AUTHLEN_SHIFT);</code></p>
     <p><code>37   if (strcmp(p.,"application/sdp") == 0)</code></p>
     <p><code>38    p += 16;</code></p>
     <p><code>39   printf(%s\n", p);</code></p>
     <p><code>40  }</code></p>
     <p><code>41 }</code></p>
     <subtitle>Формат пакета</subtitle>
     <p><code>9-13</code> Структура <code>sap_packet</code> описывает пакет SDP: 32-разрядный заголовок SAP, за которым следует 32-разрядный адрес отправителя и сам анонс. Анонс представляет собой строки текста в стандарте ISO 8859-1 и не может превышать 1024 байта. В каждой дейтаграмме UDP допускается только один анонс сеанса.</p>
     <subtitle>Чтение дейтаграммы UDP, вывод параметров отправителя и содержимого</subtitle>
     <p><code>15-21</code> Функция <code>recvfrom</code> ждет следующую дейтаграмму UDP, предназначенную нашему сокету. Когда она приходит, мы помещаем в конец буфера пустой байт, исправляем порядок байтов заголовка и выводим адрес отправителя пакета и хэш SAP.</p>
     <subtitle>Проверка заголовка SAP</subtitle>
     <p><code>22-34</code> Мы проверяем заголовок SAP, чтобы убедиться, что он относится к одному из тех типов, с которыми мы умеем работать. Пакеты SAP с адресами IPv6 в заголовках, а также сжатые и зашифрованные пакеты мы не обрабатываем.</p>
     <subtitle>Поиск начала и вывод анонса</subtitle>
     <p><code>35-39</code> Мы пропускаем аутентифицирующие данные и тип пакета, после чего выводим содержимое оставшейся части.</p>
     <p>В листинге 21.7 показано несколько типичных примеров результата выполнения нашей программы.</p>
     <p><strong>Листинг 21.7</strong>. Типичный анонс SAP/SDP</p>
     <p><code>freebsd % <strong>mysdr</strong></code></p>
     <p><code>From 128.223.83.33:1028 hash 0x0000 v=0</code></p>
     <p><code>o=- 60345 0 IN IP4 128.223.214.198</code></p>
     <p><code>s=UO Broadcast - NASA Videos - 25 Years of Progress</code></p>
     <p><code>i=25 Years of Progress, parts 1-13. Broadcast with Cisco System's</code></p>
     <p><code>IP/TV using MPEG1 codec (6 hours 5 Minutes; repeats) More information</code></p>
     <p><code>about IP/TV and the client needed to view this program is available</code></p>
     <p><code>from http://videolab.uoregon.edu/download.html</code></p>
     <p><code>u=http://videolab.uoregon.edu/</code></p>
     <p><code>e=Hans Kuhn &lt;multicast@lists.uoregon.edu&gt;</code></p>
     <p><code>p=Hans Kuhn &lt;541/346-1758&gt;</code></p>
     <p><code>b=AS:1000</code></p>
     <p><code>t=0 0</code></p>
     <p><code>a=type:broadcast</code></p>
     <p><code>a=tool:IP/TV Content Manager 3.2.24</code></p>
     <p><code>a=x-iptv-file:1 name y:25yop1234567890123.mpg</code></p>
     <p><code>m=video 63096 RTP/AVP 32 31 96</code></p>
     <p><code>c=IN IP4 224.2.245.25/127</code></p>
     <p><code>a=framerate:30</code></p>
     <p><code>a=rtpmap:96 WBIH/90000</code></p>
     <p><code>a=x-iptv-svr:video blaster2.uoregon.edu file 1 loop</code></p>
     <p><code>m=audio 31954 RTP/AVP 14 96 0 3 5 97 98 99 100 101 102 10 11 103 104 105 106</code></p>
     <p><code>c=IN IP4 224.2.216.85/127</code></p>
     <p><code>a=rtpmap:96 X-WAVE/8000</code></p>
     <p><code>a=rtpmap:97 L8/8000/2</code></p>
     <p><code>a=rtpmap:98 L8/8000</code></p>
     <p><code>a=rtpmap:99 L8/22050/2</code></p>
     <p><code>a=rtpmap:100 L8/22050</code></p>
     <p><code>a=rtpmap:101 L8/11025/2</code></p>
     <p><code>a=rtpmap:102 L8/11025</code></p>
     <p><code>a=rtpmap:103 L16/22050/2</code></p>
     <p><code>a=rtpmap:104 L16/22050</code></p>
     <p><code>a=rtpmap:105 L16/11025/2</code></p>
     <p><code>a=rtpmap:106 L16/11025</code></p>
     <p><code>a=x-iptv-svr:audio blaster2.uoregon.edu file 1 loop</code></p>
     <p>Этот анонс описывает рассылки, посвященные истории NASA (National Aeronautics and Space Administration — НАСА, государственная организация США, занимающаяся исследованием космоса). Описание сеанса SDP состоит из множества строк следующего формата:</p>
     <p><code>type=value</code></p>
     <p>где <code>type</code> всегда является одним символом, значение которого зависит от регистра, a <code>value</code> — это структурированная текстовая строка, зависящая от значения <code>type</code>. Пробелы справа и слева от знака равенства недопустимы. <code>v=0</code> (в нашем случае) обозначает версию (version).</p>
     <p>&#9632; <code>o=</code> обозначает источник (origin). В данном случае имя пользователя не указано, 60345 — идентификатор сеанса, 0 — номер версии этого сеанса, <code>IN</code> — тип сети, <code>IР4</code> — тип адреса, <code>128.223.214.198</code> — адрес. В результате объединения этих пяти элементов — имя пользователя, идентификатор сеанса, тип сети, тип адреса и адрес — образуется глобально уникальный идентификатор сеанса.</p>
     <p>&#9632; <code>s=</code> задает имя сеанса (session name), а <code>i=</code> — это информация о сеансе (information). <code>u=</code> указывает URI (Uniform Resource Identifier — уникальный идентификатор ресурса), по которому можно найти более подробную информацию по тематике данного сеанса, а <code>р=</code> и <code>e=</code> задают номер телефона (phone number) и адрес электронной почты (e-mail) ответственного за данную конференцию.</p>
     <p>&#9632; <code>b=</code> позволяет оценить пропускную способность, необходимую для приема данного сеанса.</p>
     <p>&#9632; <code>t=</code> задает время начала и время окончания сеанса в единицах NTP (Network Time Protocol — синхронизирующий сетевой протокол), то есть число секунд, прошедшее с 1 января 1900 года, измеренное в соответствии с UTC (Universal Time Coordinated — универсальное скоординированное время). Данный сеанс является постоянным и не имеет конкретных моментов начала и окончания, поэтому соответствующие времена полагаются нулевыми.</p>
     <p>&#9632; Строки <code>a=</code> представляют собой атрибуты, либо сеанса, если они помещены до первой строки <code>m=</code>, либо мультимедиа, если они помещены после первой строки <code>m=</code>.</p>
     <p>&#9632; Строки <code>m=</code> — это анонсы мультимедиа. Первая строка говорит нам о том, что видео передается на порт 63 096 в формате RTP с использованием профиля аудио и видео (Audio/Video Profile, AVP) с возможными типами данных 32, 31 и 96 (то есть MPEG, H.261 и WBIH соответственно). Строка <code>c=</code> сообщает о соединении. В данном случае используется протокол IPv4 с групповым адресом 224.2.245.25 и TTL = 127. Хотя между этими числами стоит символ «косая черта», как в формате CIDR, они ни в коем случае не должны трактоваться как префикс и маска.</p>
     <p>Следующая строка <code>m=</code> говорит, что аудиопоток передается на порт 31 954 и может иметь один из типов RTP/AVP, некоторые из которых являются стандартными, в то время как другие указаны ниже в виде атрибутов <code>a=rtpmap:</code>. Строка <code>с=</code> сообщает нам сведения об аудиосоединении: IPv4 с групповым адресом 224.2.216.85 и TTL = 127.</p>
    </section>
    <section>
     <title>
      <p>21.10. Отправка и получение</p>
     </title>
     <section>
      <p>Программа для получения анонсов сеанса многоадресной передачи, показанная в предыдущем разделе, могла только получать дейтаграммы многоадресной передачи. Теперь мы создадим простую программу, способную и отправлять, и получать дейтаграммы многоадресной передачи. Наша программа состоит из двух частей. Первая часть отправляет дейтаграмму многоадресной передачи определённой группе каждые 5 с. Эта дейтаграмма содержит имя узла отправителя и идентификатор процесса. Вторая часть программы — это бесконечный цикл, присоединяющийся к той группе, которой первая часть программы отправляет данные. В этом цикле выводится каждая полученная дейтаграмма (содержащая имя узла и идентификатор процесса отправителя). Это позволяет нам запустить программу на множестве узлов в локальной сети и посмотреть, какой узел получает дейтаграммы от каких отправителей.</p>
      <p>В листинге 21.8 показана функция <code>main</code> нашей программы.</p>
      <p><strong>Листинг 21.8</strong>. Создание сокетов, вызов функции fork и запуск отправителя и получателя</p>
      <p><code>//mcast/main.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void recv_all(int, socklen_t);</code></p>
      <p><code> 3 void send_all(int. SA *, socklen_t);</code></p>
      <empty-line/>
      <p><code> 4 int</code></p>
      <p><code> 5 main(int argc, char **argv)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  int sendfd, recvfd;</code></p>
      <p><code> 8  const int on = 1;</code></p>
      <p><code> 9  socklen_t salen;</code></p>
      <p><code>10  struct sockaddr *sasend, *sarecv;</code></p>
      <empty-line/>
      <p><code>11  if (argc != 3)</code></p>
      <p><code>12   err_quit("usage: sendrecv &lt;IP-multicast-address&gt; &lt;port#&gt;");</code></p>
      <empty-line/>
      <p><code>13  sendfd = Udp_client(argv[1], argv[2], (void**)&amp;sasend, &amp;salen);</code></p>
      <empty-line/>
      <p><code>14  recvfd = Socket(sasend-&gt;sa_family, SOCK_DGRAM, 0);</code></p>
      <empty-line/>
      <p><code>15  Setsockopt(recvfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
      <empty-line/>
      <p><code>16  sarecv = Malloc(salen);</code></p>
      <p><code>17  memcpy(sarecv, sasend, salen);</code></p>
      <p><code>18  Bind(recvfd, sarecv, salen);</code></p>
      <empty-line/>
      <p><code>19  Mcast_join(recvfd, sasend, salen, NULL, 0);</code></p>
      <p><code>20  Mcast_set_loop(sendfd, 0);</code></p>
      <empty-line/>
      <p><code>21  if (Fork() == 0)</code></p>
      <p><code>22   recv_all(recvfd, salen); /* дочерний процесс -&gt; получение */</code></p>
      <empty-line/>
      <p><code>23  send_all(sendfd, sasend, salen); /* родитель -&gt; отправка */</code></p>
      <p><code>24 }</code></p>
      <p>Мы создаем два сокета, один для отправки и один для получения. Нам нужно, чтобы принимающий сокет связался при помощи функции <code>bind</code> с группой и портом, допустим 239.255.1.2, порт 8888. (Вспомните, что мы могли просто связать универсальный IP-адрес и порт 8888, но связывание с определенным адресом многоадресной передачи предотвращает получение сокетом других дейтаграмм, которые могут прийти на порт получателя 8888.) Далее, нам нужно, чтобы принимающий сокет присоединился к группе. Отправляющий сокет будет отправлять дейтаграммы на этот же адрес многоадресной передачи и этот же порт, то есть на 239.255.1.2, порт 8888. Но если мы попытаемся использовать один сокет и для отправки, и для получения, то адресом отправителя для функции <code>bind</code> будет 239.255.1.2.8888 (здесь используется нотация <code>netstat</code>), а адресом получателя для функции <code>sendto</code> — также 239.255.1.2.8888. Но адрес отправителя, связанный с сокетом, становится IP-адресом отправителя дейтаграммы UDP, a RFC 1122 [10] запрещает дейтаграмме IP иметь IP-адрес отправителя, являющийся адресом многоадресной или широковещательной передачи. (См. также упражнение 21.2.) Следовательно, мы создаем два сокета: один для отправки, другой для получения.</p>
      <subtitle>Создание отправляющего сокета</subtitle>
      <p><code>13</code> Наша функция <code>udp_client</code> создает отправляющий сокет, обрабатывая два аргумента командной строки, которые задают адрес многоадресной передачи и номер порта. Эта функция также возвращает структуру адреса сокета, готовую к вызовам функции <code>sendto</code>, и длину этой структуры.</p>
      <subtitle>Создание принимающего сокета и связывание (при помощи функции bind) с адресом многоадресной передачи и портом</subtitle>
      <p><code>14-18</code> Мы создаем принимающий сокет, используя то же семейство адресов, что и при создании отправляющего сокета, и устанавливаем параметр сокета <code>SO_REUSEADDR</code>, чтобы разрешить множеству экземпляров этой программы одновременно запускаться на узле. Затем мы выделяем в памяти пространство для структуры адреса этого сокета, копируем ее содержимое из структуры адреса отправляющего сокета (адрес и порт которого взяты из аргументов командной строки) и при помощи функции <code>bind</code> связываем адрес многоадресной передачи и порт с принимающим сокетом.</p>
      <subtitle>Присоединение к группе и выключение закольцовки</subtitle>
      <p><code>19-20</code> Мы вызываем нашу функцию <code>mcast_join</code>, чтобы присоединиться к группе на получающем сокете, а также нашу функцию <code>mcast_set_loop</code>, чтобы отключить закольцовку на отправляющем сокете. Для присоединения задаем имя интерфейса в виде пустого указателя и нулевой индекс интерфейса, что указывает ядру на необходимость выбрать интерфейс самостоятельно.</p>
      <subtitle>Функция fork и вызов соответствующих функций</subtitle>
      <p><code>21-23</code> Мы вызываем функцию <code>fork</code>, после чего дочерним процессом становится получающий цикл, а родительским — отправляющий.</p>
      <p>Наша функция <code>sendmail</code>, отправляющая по одной дейтаграмме многоадресной передачи каждые 5 с, показана в листинге 21.9. Функция <code>main</code> передает в качестве аргументов дескриптор сокета, указатель на структуру адреса сокета, содержащую адрес получателя многоадресной передачи и порт, и длину структуры.</p>
      <p><strong>Листинг 21.9</strong>. Отправка дейтаграммы многоадресной передачи каждые 5 с</p>
      <p><code>//mcast/send.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;sys/utsname.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 #define SENDRATE 5 /* отправка дейтаграмм каждые 5 с */</code></p>
      <empty-line/>
      <p><code> 4 void</code></p>
      <p><code> 5 send_all(int sendfd, SA *sadest, socklen_t salen)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  static char line[MAXLINE]; /* имя узла и идентификатор процесса */</code></p>
      <p><code> 8  struct utsname myname;</code></p>
      <empty-line/>
      <p><code> 9  if (uname(&amp;myname) &lt; 0)</code></p>
      <p><code>10   err_sys("uname error");</code></p>
      <p><code>11  snprintf(line, sizeof(line), "%s, %d\n", myname, nodename, getpid());</code></p>
      <empty-line/>
      <p><code>12  for (;;) {</code></p>
      <p><code>13   Sendto(sendfd, line, strlen(line), 0, sadest, salen);</code></p>
      <p><code>14   sleep(SENDRATE);</code></p>
      <p><code>15  }</code></p>
      <p><code>16 }</code></p>
      <subtitle>Получение имени узла и формирование содержимого дейтаграммы</subtitle>
      <p><code>9-11</code> Мы получаем имя узла из функции <code>uname</code> и создаем строку вывода, содержащую это имя и идентификатор процесса.</p>
      <subtitle>Отправка дейтаграммы, переход в режим ожидания</subtitle>
      <p><code>12-15</code> Мы отправляем дейтаграмму и с помощью функции <code>sleep</code> переходим в состояние ожидания на 5 с.</p>
      <p>Функция <code>recv_all</code>, содержащая бесконечный цикл получения, показана в листинге 21.10.</p>
      <p><strong>Листинг 21.10</strong>. Получение всех дейтаграмм многоадресной передачи для группы, к которой мы присоединились</p>
      <p><code>//mcast/recv.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 recv_all(int recvfd, socklen_t salen)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int n;</code></p>
      <p><code> 6  char line[MAXLINE + 1];</code></p>
      <p><code> 7  socklen_t len;</code></p>
      <p><code> 8  struct sockaddr *safrom;</code></p>
      <empty-line/>
      <p><code> 9  safrom = Malloc(salen);</code></p>
      <empty-line/>
      <p><code>10  for (;;) {</code></p>
      <p><code>11   len = salen;</code></p>
      <p><code>12   n = Recvfrom(recvfd, line, MAXLINE, 0, safrom, &amp;len);</code></p>
      <empty-line/>
      <p><code>13   line[n] = 0; /* завершающий нуль */</code></p>
      <p><code>14   printf("from %s: %s", Sock_ntop(safrom, len), line);</code></p>
      <p><code>15  }</code></p>
      <p><code>16 }</code></p>
      <subtitle>Размещение в памяти структуры адреса сокета</subtitle>
      <p><code>9</code> При каждом вызове функции <code>recvfrom</code> в памяти выделяется пространство для структуры адреса сокета, в которую записывается адрес отправителя.</p>
      <subtitle>Чтение и вывод дейтаграмм</subtitle>
      <p><code>10-15</code> Каждая дейтаграмма считывается функцией <code>recvfrom</code>, дополняется символом конца строки (то есть нулем) и выводится.</p>
     </section>
     <section>
      <title>
       <p>Пример</p>
      </title>
      <p>Мы запускаем программу в двух системах: <code>freebsd4</code> и <code>macosx</code>. Каждая система видит пакеты, отправляемые другой.</p>
      <p><code>freebsd4 % <strong>sendrecv 239.255.1.2 8888</strong></code></p>
      <p><code>from 172.24.37.78:51297: macosx, 21891</code></p>
      <p><code>from 172.24.37.78:51297: macosx, 21891</code></p>
      <p><code>from 172.24.37.78:51297: macosx, 21891</code></p>
      <p><code>from 172.24.37.78:51297: macosx, 21891</code></p>
      <empty-line/>
      <p><code>macosx % <strong>sendrecv 239.255.1.2 8888</strong></code></p>
      <p><code>from 172.24.37.94.1215: freebsd4, 55372</code></p>
      <p><code>from 172.24.37.94.1215: freebsd4, 55372</code></p>
      <p><code>from 172.24.37.94.1215: freebsd4, 55372</code></p>
      <p><code>from 172.24.37.94.1215: freebsd4, 55372</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>21.11. SNTP: простой синхронизирующий сетевой протокол</p>
     </title>
     <p>Синхронизирующий сетевой протокол (Network Time Protocol, NTP) — это сложный протокол синхронизации часов в глобальной или локальной сети. Его точность часто может достигать миллисекунд. В RFC 1305 [76] этот протокол подробно описан, а в RFC 2030 [77] рассматривается протокол SNTP — упрощенная версия NTP, предназначенная для узлов, которым не требуется функциональность полной реализации NTP. Типичной является ситуация, когда несколько узлов в локальной сети синхронизируют свои часы через Интернет с другими узлами NTP, а затем распространяют полученное значение времени в локальной сети с использованием либо широковещательной, либо многоадресной передачи.</p>
     <p>В этом разделе мы создадим клиент SNTP, который прослушивает широковещательные или групповые сообщения NTP на всех присоединенных сетях, а затем выводит разницу во времени между пакетом NTP и текущим истинным временем узла. Мы не пытаемся изменить это время, поскольку для этого необходимы права привилегированного пользователя.</p>
     <p>Файл <code>ntp.h</code>, показанный в листинге 21.11, содержит некоторые из основных определений формата пакета NTP.</p>
     <p><strong>Листинг 21.11</strong>. Заголовок ntp.h: формат пакета NTP и определения</p>
     <p><code>//ssntp/ntp.h</code></p>
     <p><code> 1 #define JAN_1970 2208988800UL /* 1970 - 1900 в секундах */</code></p>
     <empty-line/>
     <p><code> 2 struct l_fixedpt { /* 64-разрядное число с фиксированной точкой */</code></p>
     <p><code> 3  uint32_t int_part;</code></p>
     <p><code> 4  uint32_t fraction;</code></p>
     <p><code> 5 };</code></p>
     <empty-line/>
     <p><code> 6 struct s_fixedpt { /* 32-разрядное число с фиксированной точкой */</code></p>
     <p><code> 7  u_short int_part;</code></p>
     <p><code> 8  u_short fraction;</code></p>
     <p><code> 9 };</code></p>
     <empty-line/>
     <p><code>10 struct ntpdata { /* заголовок NTP */</code></p>
     <p><code>11  u_char status;</code></p>
     <p><code>12  u_char stratum;</code></p>
     <p><code>13  u_char ppoll;</code></p>
     <p><code>14  int    precision:8;</code></p>
     <p><code>15  struct s_fixedpt distance;</code></p>
     <p><code>16  struct s_fixedpt dispersion;</code></p>
     <p><code>17  uint32_t refid;</code></p>
     <p><code>18  struct l_fixedpt reftime;</code></p>
     <p><code>19  struct l_fixedpt org;</code></p>
     <p><code>20  struct 1_fixedpt rec;</code></p>
     <p><code>21  struct l_fixedpt xmt;</code></p>
     <p><code>22 };</code></p>
     <empty-line/>
     <p><code>23 #define VERSION_MASK 0x38</code></p>
     <p><code>24 #define MODE_MASK 0x07</code></p>
     <empty-line/>
     <p><code>25 #define MODE CLIENT 3</code></p>
     <p><code>26 #define MODE_SERVER 4</code></p>
     <p><code>27 #define MODE_BROADCAST 5</code></p>
     <p><code>2-22 l_fixedpt</code> задает 64-разрядные числа с фиксированной точкой, используемые NTP для отметок времени, a <code>s_fixedpt</code> — 32-разрядные значения с фиксированной точкой, также используемые NTP. Структура <code>ntpdata</code> представляет 48-байтовый формат пакета NTP.</p>
     <p>В листинге 21.12 пpeдcтaвлeнa функция <code>main</code>.</p>
     <p><strong>Листинг 21.12</strong>. Функция main</p>
     <p><code>//ssntp/main.c</code></p>
     <p><code> 1 #include "sntp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd;</code></p>
     <p><code> 6  char buf[MAXLINE];</code></p>
     <p><code> 7  ssize_t n;</code></p>
     <p><code> 8  socklen_t salen, len;</code></p>
     <p><code> 9  struct ifi_info *ifi;</code></p>
     <p><code>10  struct sockaddr *mcastsa, *wild, *from;</code></p>
     <p><code>11  struct timeval now;</code></p>
     <empty-line/>
     <p><code>12  if (argc != 2)</code></p>
     <p><code>13   err_quit("usage: ssntp &lt;Ipaddress&gt;");</code></p>
     <empty-line/>
     <p><code>14  sockfd = Udp_client(argv[1], "ntp", (void**)&amp;mcastsa, &amp;salen);</code></p>
     <empty-line/>
     <p><code>15  wild = Malloc(salen);</code></p>
     <p><code>16  memcpy(wild, mcastsa. salen); /* копируем семейство и порт */</code></p>
     <p><code>17  sock_set_wild(wild, salen);</code></p>
     <p><code>18  Bind(sockfd, wild, salen); /* связываем сокет с универсальным</code><a l:href="#n21" type="note">[3]</a></p>
     <p><code>                                  адресом */</code></p>
     <empty-line/>
     <p><code>19 #ifdef MCAST</code></p>
     <p><code>20  /* получаем список интерфейсов и обрабатываем каждый интерфейс */</code></p>
     <p><code>21  for (ifi = Get_ifi_info(mcastsa-&gt;sa_family, 1); ifi != NULL;</code></p>
     <p><code>22   ifi = ifi-&gt;ifi_next) {</code></p>
     <p><code>23   if (ifi-&gt;ifi_flags &amp; IFF_MULTICAST) {</code></p>
     <p><code>24    Mcast_join(sockfd, mcastsa, salen, ifi-&gt;ififname, 0);</code></p>
     <p><code>25    printf("joined %s on %s\n",</code></p>
     <p><code>26     Sock_ntop(mcastsa, salen), ifi-&gt;ifi_name);</code></p>
     <p><code>27   }</code></p>
     <p><code>28  }</code></p>
     <p><code>29 #endif</code></p>
     <empty-line/>
     <p><code>30  from = Malloc(salen);</code></p>
     <p><code>31  for (;;) {</code></p>
     <p><code>32   len = salen;</code></p>
     <p><code>33   n = Recvfrom(sockfd, buf, sizeof(buf), 0, from, &amp;len);</code></p>
     <p><code>34   Gettimeofday(&amp;now, NULL);</code></p>
     <p><code>35   sntp_proc(buf, n, &amp;now);</code></p>
     <p><code>36  }</code></p>
     <p><code>37 }</code></p>
     <subtitle>Получение IP-адреса многоадресной передачи</subtitle>
     <p><code>12-14</code> При выполнении программы пользователь должен задать в качестве аргумента командной строки адрес многоадресной передачи, к которому он будет присоединяться. В случае IPv4 это будет 224.0.1.1 или имя <code>ntp.mcast.net</code>. В случае IPv6 это будет <code>ff05::101</code> для области действия NTP, локальной в пределах сайта. Наша функция <code>udp_client</code> выделяет в памяти пространство для структуры адреса сокета корректного типа (либо IPv4, либо IPv6) и записывает адрес многоадресной передачи и порт в эту структуру. Если эта программа выполняется на узле, не поддерживающем многоадресную передачу, может быть задан любой IP-адрес, так как в этой структуре задействуются только семейство адресов и порт. Обратите внимание, что наша функция <code>udp_client</code> не связывает адрес с сокетом (то есть не вызывает функцию <code>bind</code>) — она лишь создает сокет и заполняет структуру адреса сокета.</p>
     <subtitle>Связывание универсального адреса с сокетом</subtitle>
     <p><code>15-18</code> Мы выделяем в памяти пространство для другой структуры адреса сокета и заполняем ее, копируя структуру, заполненную функцией <code>udp_client</code>. При этом задаются семейство адреса и порт. Мы вызываем нашу функцию sock_set_wild, чтобы присвоить IP-адресу универсальный адрес, а затем вызываем функцию bind.</p>
     <subtitle>Получение списка интерфейсов</subtitle>
     <p><code>20-22</code> Наша функция <code>get_ifi_info</code> возвращает информацию обо всех интерфейсах и адресах. Запрашиваемое нами семейство адреса берется из структуры адреса сокета, заполненной функцией <code>udp_client</code> на основе аргумента командной строки.</p>
     <subtitle>Присоединение к группе</subtitle>
     <p><code>23-27</code> Мы вызываем нашу функцию <code>mcast_join</code>, чтобы присоединиться к группе, заданной аргументом командной строки для каждого интерфейса, поддерживающего многоадресную передачу. Все эти присоединения происходят на одном сокете, который использует эта программа. Как отмечалось ранее, количество присоединений на одном сокете ограничено константой <code>IP_MAX_MEMBERSHIPS</code> (которая обычно имеет значение 20), но лишь немногие многоинтерфейсные узлы используют столько интерфейсов.</p>
     <subtitle>Чтение и обработка всех пакетов NTP</subtitle>
     <p><code>30-36</code> В памяти размещается другая структура адреса сокета для хранения адреса, возвращаемого функцией <code>recvfrom</code>, и программа входит в бесконечный цикл, считывая все пакеты NTP, которые получает узел, и вызывая нашу функцию <code>sntp_proc</code> (описывается далее) для обработки пакета. Поскольку сокет был связан с универсальным адресом и присоединение к группе произошло на всех интерфейсах, поддерживающих многоадресную передачу, сокет должен получить любой пакет NTP направленной, широковещательной или многоадресной передачи, получаемый узлом. Перед вызовом функции <code>sntp_proc</code> мы вызываем функцию <code>gettimeofday</code>, чтобы получить текущее время, потому что функция <code>sntp_proc</code> вычисляет разницу между временем пакета и текущим временем.</p>
     <p>Наша функция <code>sntp_proc</code>, показанная в листинге 21.13, обрабатывает пакет NTP.</p>
     <p><strong>Листинг 21.13</strong>. Функция sntp_proc: обработка пакета NTР</p>
     <p><code>//ssntp/sntp_proc.c</code></p>
     <p><code> 1 #include "sntp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 sntp proc(char *buf, ssize_t n, struct timeval *nowptr)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int version, mode;</code></p>
     <p><code> 6  uint32_t nsec, useci;</code></p>
     <p><code> 7  double usecf;</code></p>
     <p><code> 8  struct timeval diff;</code></p>
     <p><code> 9  struct ntpdata *ntp;</code></p>
     <empty-line/>
     <p><code>10  if (n &lt; (ssize_t)sizeof(struct ntpdata)) {</code></p>
     <p><code>11   printf("\npacket too small: %d bytes\n", n);</code></p>
     <p><code>12   return;</code></p>
     <p><code>13  }</code></p>
     <empty-line/>
     <p><code>14  ntp = (struct ntpdata*)buf;</code></p>
     <p><code>15  version = (ntp-&gt;status &amp; VERSION_MASK) &gt;&gt; 3;</code></p>
     <p><code>16  mode = ntp-&gt;status &amp; MODE_MASK;</code></p>
     <p><code>17  printf("\nv%d, mode %d, strat %d, ", version, mode, ntp-&gt;stratum);</code></p>
     <p><code>18  if (mode == MODE_CLIENT) {</code></p>
     <p><code>19   printf("client\n");</code></p>
     <p><code>20   return;</code></p>
     <p><code>21  }</code></p>
     <empty-line/>
     <p><code>22  nsec = ntohl(ntp-&gt;xmt.int_part) - JAN_1970;</code></p>
     <p><code>23  useci = ntohl(ntp-&gt;xmt.fraction); /* 32-разрядная дробь */</code></p>
     <p><code>24  usecf = useci; /* дробь в double */</code></p>
     <p><code>25  usecf /= 4294967296.0; /* деление на 2**32 -&gt; [0, 1.0) */</code></p>
     <p><code>26  useci = usecf * 1000000.0; /* дробь в миллионную часть */</code></p>
     <empty-line/>
     <p><code>27  diff.tv_sec = nowptr-&gt;tv_sec - nsec;</code></p>
     <p><code>28  if ((diff.tv_usec = nowptr-&gt;tv_usec - useci) &lt; 0) {</code></p>
     <p><code>29   diff.tv_usec += 1000000;</code></p>
     <p><code>30   diff.tv_sec--;</code></p>
     <p><code>31  }</code></p>
     <p><code>32  useci = (diff.tv_sec * 1000000) + diff.tv_usec; /* diff в мс */</code></p>
     <p><code>33  printf("clock difference = %d usec\n", useci);</code></p>
     <p><code>34 }</code></p>
     <subtitle>Ратификация пакета</subtitle>
     <p><code>10-21</code> Сначала мы проверяем размер пакета, затем выводим его версию, режим и слой (stratum) сервера. Если режимом является <code>MODE_CLIENT</code>, пакет является запросом клиента, а не ответом сервера, и мы игнорируем его.</p>
     <subtitle>Получение времени передачи из пакета NTP</subtitle>
     <p><code>22-34</code> В пакете NTP нас интересует поле <code>xmt</code> — отметка времени. Это 64-разрядное значение с фиксированной точкой, определяющее момент отправки пакета сервером. Поскольку отметки времени NTP отсчитывают секунды начиная с 1 января 1900 года, а отметки времени Unix — с 1 января 1970 года, сначала мы вычитаем <code>JAN_1970</code> (число секунд в 70 годах) из целой части.</p>
     <p>Дробная часть — это 32-разрядное целое без знака, которое может принимать значение от 0 до 4 294 967 295 включительно. Оно копируется из 32-разрядного целого (<code>usecf</code>) в переменную с плавающей точкой двойной точности (<code>usecf</code>) и делится на 4 294 967 296 (2<sup>32</sup>). Результат больше либо равен 0.0 и меньше 1.0. Мы умножаем это число на 1 000 000 — число микросекунд в секунде, записывая результат в переменную <code>useci</code> как 32-разрядное целое без знака.</p>
     <p>Число микросекунд лежит в интервале от 0 до 999 999 (см. упражнение 21.5). Мы преобразуем значение в микросекунды, поскольку отметка времени Unix, возвращаемая функцией <code>gettimeofday</code>, возвращается как два целых числа: число секунд и число микросекунд, прошедшее с 1 января 1970 года (UTC). Затем мы вычисляем и выводим разницу между истинным временем узла и истинным временем сервера NTP в микросекундах.</p>
     <p>Один из факторов, не учитываемых нашей программой, — это задержка в сети между клиентом и сервером. Но мы считаем, что пакеты NTP обычно приходят как широковещательные или многоадресные пакеты в локальной сети, а в этом случае задержка в сети составит всего несколько миллисекунд.</p>
     <p>Если мы запустим эту программу на узле <code>macosx</code> с сервером NTP на узле <code>freebsd4</code>, который с помощью многоадресной передачи отправляет пакеты NTP в сеть Ethernet каждые 64 с, то получим следующий результат:</p>
     <p><code>macosx # <strong>ssntp 224.0.1.1</strong></code></p>
     <p><code>joined 224.0.1.1.123 on lo0</code></p>
     <p><code>joined 224.0.1.1.123 on en1</code></p>
     <p><code>v4, mode 5, strat 3, clock difference = 661 usec</code></p>
     <p><code>v4, mode 5, strat 3, clock difference = -1789 usec</code></p>
     <p><code>v4, mode 5, strat 3, clock difference = -2945 usec</code></p>
     <p><code>v4, mode 5, strat 3, clock difference = -3689 usec</code></p>
     <p><code>v4, mode 5, strat 3, clock difference = -5425 usec</code></p>
     <p><code>v4, mode 5, strat 3, clock difference = -6700 usec</code></p>
     <p><code>v4, mode 5, strat 3, clock difference = -8520 usec</code></p>
     <p>Перед запуском нашей программы мы завершили на узле работу NTP-сервера, поэтому когда наша программа запускается, время очень близко к времени сервера. Мы видим, что этот узел отстал на 9181 мс за 384 с работы программы, то есть за 24 ч он отстанет на 2 с.</p>
    </section>
    <section>
     <title>
      <p>21.12. Резюме</p>
     </title>
     <p>Для запуска приложения многоадресной передачи в первую очередь требуется присоединиться к группе, заданной для этого приложения. Тем самым уровень IP получает указание присоединиться к группе, что, в свою очередь, указывает канальному уровню на необходимость получать кадры многоадресной передачи, отправляемые на соответствующий адрес многоадресной передачи аппаратного уровня. Многоадресная передача использует преимущество аппаратной фильтрации, имеющееся у большинства интерфейсных карт, и чем качественнее фильтрация, тем меньше число нежелательных получаемых пакетов. Использование аппаратной фильтрации сокращает нагрузку на все узлы, не задействованные в приложении.</p>
     <p>Многоадресная передача в глобальной сети требует наличия маршрутизаторов, поддерживающих многоадресную передачу, и протокола маршрутизации многоадресной передачи. Поскольку не все маршрутизаторы в Интернете имеют возможность многоадресной передачи, для этой цели используется IP-инфраструктура многоадресной передачи.</p>
     <p>API для многоадресной передачи обеспечивают девять параметров сокетов:</p>
     <p>&#9632; присоединение к группе на интерфейсе;</p>
     <p>&#9632; выход из группы;</p>
     <p>&#9632; блокирование передачи от источника;</p>
     <p>&#9632; разблокирование заблокированного источника;</p>
     <p>&#9632; присоединение интерфейса к группе многоадресной передачи от источника;</p>
     <p>&#9632; выход из группы многоадресной передачи от источника;</p>
     <p>&#9632; установка интерфейса по умолчанию для исходящих пакетов многоадресной передачи;</p>
     <p>&#9632; установка значения TTL или предельного количества транзитных узлов для исходящих пакетов многоадресной передачи;</p>
     <p>&#9632; включение или отключение закольцовки для пакетов многоадресной передачи.</p>
     <p>Первые шесть параметров предназначены для получения пакетов многоадресной передачи, последние три — для отправки. Существует достаточно большая разница между указанными параметрами сокетов IPv4 и IPv6. Вследствие этого код многоадресной передачи, зависящий от протокола, очень быстро становится «замусорен» директивами <code>#ifdef</code>. Мы разработали 12 наших собственных функций с именами, начинающимися с <code>mcast_</code>, для упрощения написания приложений многоадресной передачи, работающих как с IPv4, так и с IPv6.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Скомпилируйте программу, показанную в листинге 20.5, и запустите ее, задав в командной строке IP-адрес 224.0.0.1. Что произойдет?</p>
     <p>2. Измените программу из предыдущего примера, чтобы связать IP-адрес 224.0.0.1 и порт 0 с сокетом. Запустите ее. Разрешается ли вам связывать адрес многоадресной передачи с сокетом при помощи функции <code>bind</code>? Если у вас есть такая программа, как <code>tcpdump</code>, понаблюдайте за пакетами в сети. Каков IP-адрес отправителя посылаемой вами дейтаграммы?</p>
     <p>3. Один из способов определить, какие узлы в вашей подсети имеют возможность многоадресной передачи, заключается в запуске утилиты <code>ping</code> для группы всех узлов, то есть для адреса 224.0.0.1. Попробуйте это сделать.</p>
     <p>4. Одним из способов обнаружения маршрутизаторов многоадресной передачи в вашей подсети является запуск утилиты ping для группы всех маршрутизаторов — 224.0.0.2. Попробуйте это сделать.</p>
     <p>5. Один из способов узнать, соединен ли ваш узел с многоадресной IP-инфраструктурой — запустить нашу программу из раздела 21.9, подождать несколько минут и посмотреть, появляются ли анонсы сеанса. Попробуйте сделать это и посмотрите, получите ли вы какие-нибудь анонсы.</p>
     <p>6. Выполните вычисления в листинге 21.12 при условии, что дробная часть отметки времени NTP равна 1 073 741 824 (одна четвертая от 2<sup>32</sup>).</p>
     <p>Выполните еще раз эти же вычисления для максимально возможной дробной части (2<sup>32</sup> - 1).</p>
     <p>Измените реализацию функции <code>mcast_set_if</code> для IPv4 так, чтобы запоминать имя каждого интерфейса, для которого она получает IP-адрес. Это позволит избежать нового вызова функции <code>ioctl</code> для данного интерфейса.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 22</p>
     <p>Дополнительные сведения о сокетах udp</p>
    </title>
    <section>
     <title>
      <p>22.1. Введение</p>
     </title>
     <p>Эта глава объединяет различные темы, касающиеся приложений, использующих сокеты UDP. Для начала нас интересует, как определяется адрес получателя дейтаграммы UDP и интерфейс, на котором дейтаграмма была получена, поскольку сокет, связанный с портом UDP и универсальным адресом, может получать дейтаграммы направленной, широковещательной и многоадресной передачи на любом интерфейсе.</p>
     <p>TCP — это потоковый протокол, использующий <emphasis>окно переменной величины</emphasis> (<emphasis>sliding window</emphasis>), поэтому в TCP отсутствует такое понятие, как граница записи, и невозможно переполнение буфера получателя отправителем в результате передачи слишком большого количества данных. Однако в случае UDP каждой операции ввода соответствует одна дейтаграмма UDP (запись), поэтому возникает вопрос: что произойдет, когда полученная дейтаграмма окажется больше приемного буфера приложения?</p>
     <p>UDP — это ненадежный протокол, однако существуют приложения, в которых UDP использовать целесообразнее, чем TCP. Мы рассмотрим факторы, под влиянием которых UDP оказывается предпочтительнее TCP. В UDP-приложения необходимо включать ряд функций, в некоторой степени компенсирующих ненадежность UDP: тайм-аут и повторную передачу, обработку потерянных дейтаграмм и порядковые номера для сопоставления ответов запросам. Мы разработаем набор функций, которые сможем вызывать из наших приложений UDP.</p>
     <p>Если реализация не поддерживает параметр сокета <code>IP_RECVDSTADDR</code>, один из способов определить IP-адрес получателя UDP-дейтаграммы заключается в связывании всех интерфейсных адресов и использовании функции <code>select</code>.</p>
     <p>Большинство серверов UDP являются последовательными, но существуют приложения, обменивающиеся множеством дейтаграмм UDP между клиентом и сервером, что требует параллельной обработки. Примером может служить TFTP (Trivial File Transfer Protocol — упрощенный протокол передачи файлов). Мы рассмотрим два варианта подобного согласования — с использованием суперсервера <code>inetd</code> и без него.</p>
     <p>В завершение этой главы мы рассмотрим информацию о пакете, которая может быть передана во вспомогательных данных дейтаграммы IPv6: IP-адрес отправителя, отправляющий интерфейс, предельное количество транзитных узлов исходящих дейтаграмм и адрес следующего транзитного узла. Аналогичная информация — IP-адрес получателя, принимающий интерфейс и предельное количество транзитных узлов — может быть получена вместе с дейтаграммой IPv6.</p>
    </section>
    <section>
     <title>
      <p>22.2. Получение флагов, IP-адреса получателя и индекса интерфейса</p>
     </title>
     <section>
      <p>Исторически функции <code>sendmsg</code> и <code>recvmsg</code> использовались только для передачи дескрипторов через доменные сокеты Unix (см. раздел 15.7), но даже это происходило сравнительно редко. Однако в настоящее время популярность этих двух функций растет по двум причинам:</p>
      <p>1. Элемент <code>msg_flags</code>, добавленный в структуру <code>msghdr</code> в реализации 4.3BSD Reno, возвращает приложению флаги сообщения. Эти флаги мы перечислили в табл. 14.2.</p>
      <p>2. Вспомогательные данные используются для передачи все большего количества информации между приложением и ядром. В главе 27 мы увидим, что IPv6 продолжает эту тенденцию.</p>
      <p>В качестве примера использования функции <code>recvmsg</code> мы напишем функцию <code>recvfrom_flags</code>, аналогичную функции recvfrom, но дополнительно позволяющую получить:</p>
      <p>&#9632; возвращаемое значение <code>msg_flags</code>;</p>
      <p>&#9632; адрес получателя полученной дейтаграммы (из параметра сокета <code>IP_RECVDSTADDR</code>);</p>
      <p>&#9632; индекс интерфейса, на котором была получена дейтаграмма (параметр сокета <code>IP_RECVIF</code>).</p>
      <p>Чтобы можно было получить два последних элемента, мы определяем в нашем заголовке <code>unp.h</code> следующую структуру:</p>
      <p><code>struct in_pktinfo {</code></p>
      <p><code> struct in_addr ipi_addr;    /* IPv4-адрес получателя */</code></p>
      <p><code> int            ipi_ifindex; /* индекс интерфейса, на котором была</code></p>
      <p><code>                                получена дейтаграмма */</code></p>
      <p><code>};</code></p>
      <p>Мы выбрали имена структуры и ее элементов так, чтобы получить определенное сходство со структурой IPv6 <code>in6_pktinfo</code>, возвращающей те же два элемента для сокета IPv6 (см. раздел 22.8). Наша функция <code>recvfrom_flags</code> будет получать в качестве аргумента указатель на структуру <code>in_pktinfo</code>, и если этот указатель не нулевой, возвращать структуру через указатель.</p>
      <p>Проблема построения этой структуры состоит в том, что неясно, что возвращать, если недоступна информация, которая должна быть получена из параметра сокета <code>IP_RECVDSTADDR</code> (то есть реализация не поддерживает данный параметр сокета). Обработать индекс интерфейса легко, поскольку нулевое значение может использоваться как указание на то, что индекс неизвестен. Но для IP-адреса все 32-разрядные значения являются действительными. Мы выбрали такое решение: адрес получателя 0.0.0.0 возвращается в том случае, когда действительное значение недоступно. Хотя это реальный IP-адрес, использовать его в качестве IP-адреса получателя не разрешается (RFC 1122 [10]). Он будет действителен только в качестве IP-адреса отправителя во время начальной загрузки узла, когда узел еще не знает своего IP-адреса.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>К сожалению, Беркли-ядра принимают дейтаграммы, предназначенные для адреса 0.0.0.0 [128, с. 218-219]. Это устаревшие адреса широковещательной передачи, генерируемые ядрами 4.2BSD.</p>
      </cite>
      <p>Первая часть нашей функции <code>recvfrom_flags</code> представлена в листинге 22.1<a l:href="#n1" type="note">[1]</a>. Эта функция предназначена для использования с сокетом UDP.</p>
      <p><strong>Листинг 22.1</strong>. Функция recvfrom_flags: вызов функции recvmsg</p>
      <p><code>//adviо/recvfromflags.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;sys/param.h&gt; /* макрос ALIGN для макроса CMSG_NXTHDR() */</code></p>
      <empty-line/>
      <p><code> 3 ssize_t</code></p>
      <p><code> 4 recvfrom_flags(int fd, void *ptr, size_t nbytes, int *flagsp,</code></p>
      <p><code> 5  SA *sa, socklen_t *salenptr, struct unp_in_pktinfo *pktp)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  struct msghdr msg;</code></p>
      <p><code> 8  struct iovec iov[1];</code></p>
      <p><code> 9  ssize_t n;</code></p>
      <empty-line/>
      <p><code>10 #ifdef HAVE_MSGHDR_MSG_CONTROL</code></p>
      <p><code>11  struct cmsghdr *cmptr;</code></p>
      <p><code>12  union {</code></p>
      <p><code>13   struct cmsghdr cm;</code></p>
      <p><code>14   char control[CMSG_SPACE(sizeof(struct in_addr)) +</code></p>
      <p><code>15    CMSG_SPACE(sizeof(struct unp_in_pktinfo))];</code></p>
      <p><code>16  } control_un;</code></p>
      <empty-line/>
      <p><code>17  msg.msg_control = control_un.control;</code></p>
      <p><code>18  msg.msg_controllen = sizeof(control_un.control);</code></p>
      <p><code>19  msg.msg_flags = 0;</code></p>
      <p><code>20 #else</code></p>
      <p><code>21  bzero(&amp;msg, sizeof(msg)); /* обнуление msg_accrightslen = 0 */</code></p>
      <p><code>22 #endif</code></p>
      <empty-line/>
      <p><code>23  msg.msg_name = sa;</code></p>
      <p><code>24  msg.msg_namelen = *salenptr;</code></p>
      <p><code>25  iov[0].iov_base = ptr;</code></p>
      <p><code>26  iov[0].iov_len = nbytes;</code></p>
      <p><code>27  msg.msg_iov = iov;</code></p>
      <p><code>28  msg.msg_iovlen = 1;</code></p>
      <empty-line/>
      <p><code>29  if ((n = recvmsg(fd, &amp;msg, *flagsp)) &lt; 0)</code></p>
      <p><code>30   return(n);</code></p>
      <p><code>31  *salenptr = msg.msg_namelen; /* возвращение результатов */</code></p>
      <p><code>32  if (pktp)</code></p>
      <p><code>33   bzero(pktp, sizeof(struct unp_in_pktinfo)); /* 0.0.0.0. интерфейс = 0 */</code></p>
      <subtitle>Подключаемые файлы</subtitle>
      <p><code>1-2</code> Использование макроопределения <code>CMSG_NXTHDR</code> требует подключения заголовочного файла <code>&lt;sys/param.h&gt;</code>.</p>
      <subtitle>Аргументы функции</subtitle>
      <p><code>3-5</code> Аргументы функции аналогичны аргументам функции <code>recvfrom</code> за исключением того, что четвертый аргумент является указателем на целочисленный флаг (так что мы можем возвратить флаги, возвращаемые функцией <code>recvmsg</code>), а седьмой аргумент новый: это указатель на структуру <code>unp_in_pktinfo</code>, содержащую IPv4-адрес получателя пришедшей дейтаграммы и индекс интерфейса, на котором дейтаграмма была получена.</p>
      <subtitle>Различия реализаций</subtitle>
      <p><code>10-22</code> При работе со структурой <code>msghdr</code> и различными константами <code>MSG_<emphasis>XXX</emphasis></code> мы встречаемся со множеством различий в реализациях. Одним из вариантов обработки таких различий может быть использование имеющейся в языке С возможности условного подключения (директива <code>#ifdef</code>). Если реализация поддерживает элемент <code>msg_control</code>, то выделяется пространство для хранения значений, возвращаемых параметрами сокета <code>IP_RECVDSTADDR</code> и <code>IP_RECVIF</code>, и соответствующие элементы инициализируются.</p>
      <subtitle>Заполнение структуры msghdr и вызов функции recvmsg</subtitle>
      <p><code>23-33</code> Заполняется структура <code>msghdr</code> и вызывается функция <code>recvmsg</code>. Значения элементов <code>msg_namelen</code> и <code>msg_flags</code> должны быть переданы обратно вызывающему процессу. Они являются аргументами типа «значение-результат». Мы также инициализируем структуру вызывающего процесса <code>unp_in_pktinfo</code>, устанавливая IP-адрес 0.0.0.0 и индекс интерфейса 0.</p>
      <p>В листинге 22.2 показана вторая часть нашей функции.</p>
      <p><strong>Листинг 22.2</strong>. Функция recvfrom_flags: возвращаемые флаги и адрес получателя</p>
      <p><code>//advio/recvfromflags.c</code></p>
      <p><code>34 #ifndef HAVE_MSGHDR_MSG_CONTROL</code></p>
      <p><code>35  *flagsp = 0; /* возвращение результатов */</code></p>
      <p><code>36  return(n);</code></p>
      <p><code>37 #else</code></p>
      <empty-line/>
      <p><code>38  *flagsp = msg.msg_flags; /* возвращение результатов */</code></p>
      <p><code>39  if (msg.msg_controllen &lt; sizeof(struct cmsghdr) ||</code></p>
      <p><code>40   (msg.msg_flags &amp; MSG_CTRUNC) || pktp == NULL)</code></p>
      <p><code>41    return(n);</code></p>
      <empty-line/>
      <p><code>42   for (cmptr = CMSG_FIRSTHDR(&amp;msg); cmptr != NULL;</code></p>
      <p><code>43    cmptr = CMSG_NXTHDR(&amp;msg, cmptr)) {</code></p>
      <empty-line/>
      <p><code>44 #ifdef IP_RECVDSTADDR</code></p>
      <p><code>45    if (cmptr-&gt;cmsg_level == IPPROTO_IP &amp;&amp;</code></p>
      <p><code>46     cmptr-&gt;cmsg_type == IP_RECVDSTADDR) {</code></p>
      <empty-line/>
      <p><code>47     memcpy(&amp;pktp-&gt;ipi_addr, CMSG_DATA(cmptr),</code></p>
      <p><code>48      sizeof(struct in_addr));</code></p>
      <p><code>49     continue;</code></p>
      <p><code>50    }</code></p>
      <p><code>51 #endif</code></p>
      <empty-line/>
      <p><code>52 #ifdef IP_RECVIF</code></p>
      <p><code>53    if (cmptr-&gt;cmsg_level == IPPROTO_IP &amp;&amp; cmptr-&gt;cmsg_type == IP_RECVIF) {</code></p>
      <p><code>54     struct sockaddr_dl *sdl;</code></p>
      <empty-line/>
      <p><code>55    sdl = (struct sockaddr_dl*)CMSG_DATA(cmptr);</code></p>
      <p><code>56    pktp-&gt;ipi_ifindex = sdl-&gt;sdl_index;</code></p>
      <p><code>57    continue;</code></p>
      <p><code>58   }</code></p>
      <p><code>59 #endif</code></p>
      <p><code>60   err_quit("unknown ancillary data, len = %d, </code><code>level = %d, type = %d",</code></p>
      <p><code>61    cmptr-&gt;cmsg_len, cmptr-&gt;cmsg_level, cmptr-&gt;cmsg_type);</code></p>
      <p><code>62  }</code></p>
      <p><code>63  return(n);</code></p>
      <p><code>64 #endif /* HAVE_MSGHDR_MSG_CONTROL */</code></p>
      <p><code>65 }</code></p>
      <p><code>34-37</code> Если реализация не поддерживает элемента <code>msg_control</code>, мы просто обнуляем возвращаемые флаги и завершаем функцию. Оставшаяся часть функции обрабатывает информацию, содержащуюся в структуре <code>msg_control</code>.</p>
      <subtitle>Возвращение при отсутствии управляющей информации</subtitle>
      <p><code>38-41</code> Мы возвращаем значение <code>msg_flags</code> и передаем управление вызывающей функции в том случае, если нет никакой управляющей информации, управляющая информация была обрезана или вызывающий процесс не требует возвращения структуры <code>unp_in_pktinfo</code>.</p>
      <subtitle>Обработка вспомогательных данных</subtitle>
      <p><code>42-43</code> Мы обрабатываем произвольное количество объектов вспомогательных данных с помощью макросов <code>CMSG_FIRSTHDR</code> и <code>CMSG_NEXTHDR</code>.</p>
      <subtitle>Обработка параметра сокета IP_RECVDSTADDR</subtitle>
      <p><code>47-54</code> Если в составе управляющей информации был возвращен IP-адрес получателя (см. рис. 14.2), он возвращается вызывающему процессу.</p>
      <subtitle>Обработка параметра сокета IP_RECVIF</subtitle>
      <p><code>55-63</code> Если в составе управляющей информации был возвращен индекс интерфейса, он возвращается вызывающему процессу. На рис. 22.1 показано содержимое возвращенного объекта вспомогательных данных.</p>
      <image l:href="#img_124.png"/>
      <p><strong>Рис. 22.1</strong>. Объект вспомогательных данных, возвращаемый для параметра IP_RECVIF</p>
      <p>Вспомните структуру адреса сокета канального уровня (см. листинг 18.1). Данные, возвращаемые в объекте вспомогательных данных, представлены в одной из этих структур, но длины трех элементов являются нулевыми (длина имени, адреса и селектора). Следовательно, нет никакой необходимости указывать эти значения, и таким образом структура имеет размер 8 байт, а не 20, как было в листинге 18.1. Возвращаемая нами информация — это индекс интерфейса.</p>
     </section>
     <section>
      <title>
       <p>Пример: вывод IP-адреса получателя и флага обрезки дейтаграммы</p>
      </title>
      <p>Для проверки нашей функции мы изменим функцию <code>dg_echo</code> (см. листинг 8.2) так, чтобы она вызывала функцию <code>recvfrom_flags</code> вместо функции recvfrom. Новая версия функции <code>dg_echo</code> показана в листинге 22.3.</p>
      <p><strong>Листинг 22.3</strong>. Функция dg_echo, вызывающая нашу функцию recvfrom_flags</p>
      <p><code>//advio/dgechoaddr.c</code></p>
      <p><code> 1 #include "unpifi.h"</code></p>
      <empty-line/>
      <p><code> 2 #undef MAXLINE</code></p>
      <p><code> 3 #define MAXLINE 20 /* устанавливаем новое значение, чтобы</code></p>
      <p><code>                         пронаблюдать обрезку дейтаграмм */</code></p>
      <empty-line/>
      <p><code> 4 void</code></p>
      <p><code> 5 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  int flags;</code></p>
      <p><code> 8  const int on = 1;</code></p>
      <p><code> 9  socklen_t len;</code></p>
      <p><code>10  ssize_t n;</code></p>
      <p><code>11  char</code> <code>mesg[MAXLINE], str[INET6_ADDRSTRLEN], ifname[IFNAMSIZ];</code></p>
      <p><code>12  struct in_addr in_zero;</code></p>
      <p><code>13  struct in_pktinfo pktinfo;</code></p>
      <empty-line/>
      <p><code>14 #ifdef IP_RECVDSTADDR</code></p>
      <p><code>15  if (setsockopt(sockfd, IPPROTO_IP, IP_RECVDSTADDR, &amp;on, sizeof(on)) &lt; 0)</code></p>
      <p><code>16   err_ret("setsockopt of IP_RECVDSTADDR");</code></p>
      <p><code>17 #endif</code></p>
      <p><code>18 #ifdef IP_RECVIF</code></p>
      <p><code>19  if (setsockopt(sockfd, IPPROTO_IP, IP_RECVIF, &amp;on, sizeof(on)) &lt; 0)</code></p>
      <p><code>20   err_ret("setsockopt of IP_RECVIF");</code></p>
      <p><code>21 #endif</code></p>
      <p><code>22  bzero(&amp;in_zero, sizeof(struct in_addr)); /* IPv4-адрес, состоящий</code></p>
      <p><code>                                                из одних нулей */</code></p>
      <empty-line/>
      <p><code>23  for (;;) {</code></p>
      <p><code>24   len = clilen;</code></p>
      <p><code>25   flags = 0;</code></p>
      <p><code>26   n = Recvfrom_flags(sockfd, mesg, MAXLINE, &amp;flags,</code></p>
      <p><code>27    pcliaddr, &amp;len, &amp;pktinfo);</code></p>
      <p><code>28   printf("%d-byte datagram from %s", n, Sock_ntop(pcliaddr, len));</code></p>
      <p><code>29   if (memcmp(&amp;pktinfo.ipi_addr, &amp;in_zero, sizeof(in_zero)) != 0)</code></p>
      <p><code>30    printf(", to %s", Inet_ntop(AF_INET, &amp;pktinfo.ipi_addr,</code></p>
      <p><code>31     str, sizeof(str)));</code></p>
      <p><code>32   if (pktinfo.ipi_ifindex &gt; 0)</code></p>
      <p><code>33    printf(", recv i/f = %s",</code></p>
      <p><code>34    If_indextoname(pktinfо.ipi_ifindex, ifname));</code></p>
      <p><code>35 #ifdef MSG_TRUNC</code></p>
      <p><code>36   if (flags &amp; MSG_TRUNC)</code></p>
      <p><code>37    printf(" (datagram truncated)");</code></p>
      <p><code>38 #endif</code></p>
      <p><code>39 #ifdef MSG_CTRUNC</code></p>
      <p><code>40   if (flags &amp; MSG_CTRUNC)</code></p>
      <p><code>41    printf(" (control info truncated)");</code></p>
      <p><code>42 #endif</code></p>
      <p><code>43 #ifdef MSG_BCAST</code></p>
      <p><code>44   if (flags &amp; MSG_BCAST)</code></p>
      <p><code>45    printf(" (broadcast)");</code></p>
      <p><code>46 #endif</code></p>
      <p><code>47 #ifdef MSG_MCAST</code></p>
      <p><code>48   if (flags &amp; MSG_MCAST)</code></p>
      <p><code>49    printf(" (multicast)");</code></p>
      <p><code>50 #endif</code></p>
      <p><code>51   printf("\n");</code></p>
      <empty-line/>
      <p><code>52   Sendto(sockfd, mesg, n, 0, pcliaddr, len);</code></p>
      <p><code>53  }</code></p>
      <p><code>54 }</code></p>
      <subtitle>Изменение MAXLINE</subtitle>
      <p><code>2-3</code> Мы удаляем существующее определение <code>MAXLINE</code>, имеющееся в нашем заголовочном файле <code>unp.h</code>, и задаем новое значение — 20. Это позволит нам увидеть, что произойдет, когда мы получим дейтаграмму UDP, превосходящую размер буфера, переданного функции (в данном случае функции <code>recvmsg</code>).</p>
      <subtitle>Установка параметров сокета IP_RECVDSTADDR и IP_RECVIF</subtitle>
      <p><code>14-21</code> Если параметр сокета <code>IP_RECVDSTADDR</code> определен, мы включаем его. Аналогично включается параметр сокета <code>IP_RECVIF</code>.</p>
      <subtitle>Чтение дейтаграммы, вывод IP-адреса отправителя и порта</subtitle>
      <p><code>24-28</code> Дейтаграмма читается с помощью вызова функции <code>recvfrom_flags</code>. IP-адрес отправителя и порт ответа сервера преобразуются в формат представления функцией <code>sock_ntop</code>.</p>
      <subtitle>Вывод IP-адреса получателя</subtitle>
      <p><code>29-31</code> Если возвращаемый IP-адрес ненулевой, он преобразуется в формат представления функцией <code>inet_ntop</code> и выводится.</p>
      <subtitle>Вывод имени интерфейса, на котором была получена дейтаграмма</subtitle>
      <p><code>32-34</code> Если индекс интерфейса ненулевой, его имя будет возвращено функцией <code>if_indextoname</code>. Это имя наша функция печатает на экране.</p>
      <subtitle>Проверка различных флагов</subtitle>
      <p><code>35-51</code> Мы проверяем четыре дополнительных флага и выводим сообщение, если какие-либо из них установлены.</p>
     </section>
    </section>
    <section>
     <title>
      <p>22.3. Обрезанные дейтаграммы</p>
     </title>
     <p>В системах, происходящих от BSD, при получении UDP-дейтаграммы, размер которой больше буфера приложения, функция recvmsg устанавливает флаг <code>MSG_TRUNC</code> в элементе <code>msg_flags</code> структуры <code>msghdr</code> (см. табл. 14.2). Все Беркли-реализации, поддерживающие структуру <code>msghdr</code> с элементом <code>msg_flags</code>, обеспечивают это уведомление.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Это пример флага, который должен быть возвращен процессу ядром. В разделе 14.3 мы упомянули о проблеме разработки функций recv и recvfrom: их аргумент flags является целым числом, что позволяет передавать флаги от процесса к ядру, но не наоборот.</p>
     </cite>
     <p>К сожалению, не все реализации подобным образом обрабатывают ситуацию, когда размер дейтаграммы UDP оказывается больше, чем предполагалось. Возможны три сценария:</p>
     <p>1. Лишние байты игнорируются, и приложение получает флаг <code>MSG_TRUNC</code>, что требует вызова функции <code>recvmsg</code>.</p>
     <p>2. Игнорирование лишних байтов без уведомления приложения.</p>
     <p>3. Сохранение лишних байтов и возвращение их в последующих операциях чтения на сокете.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>POSIX задает первый тип поведения: игнорирование лишних байтов и установку флага MSG_TRUNC. Ранние реализации SVR4 действуют по третьему сценарию.</p>
     </cite>
     <p>Поскольку способ обработки дейтаграмм, превышающих размер приемного буфера приложения, зависит от реализации, одним из решений, позволяющий обнаружить ошибку, будет всегда использовать буфер приложения на 1 байт больше самой большой дейтаграммы, которую приложение предположительно может получить. Если все же будет получена дейтаграмма, длина которой равна размеру буфера, это явно будет свидетельствовать об ошибке.</p>
    </section>
    <section>
     <title>
      <p>22.4. Когда UDP оказывается предпочтительнее TCP</p>
     </title>
     <p>В разделах 2.3 и 2.4 мы описали основные различия между UDP и TCP. Поскольку мы знаем, что TCP надежен, a UDP — нет, возникает вопрос: когда следует использовать UDP вместо TCP и почему? Сначала перечислим преимущества UDP:</p>
     <p>&#9632; Как видно из табл. 20.1, UDP поддерживает широковещательную и направленную передачу. Действительно, использование UDP обязательно, если приложению требуется широковещательная или многоадресная передача. Эти два режима адресации мы рассматривали в главах 20 и 21.</p>
     <p>&#9632; UDP не требует установки и разрыва соединения. В соответствии с рис. 2.5 UDP позволяет осуществить обмен запросом и ответом в двух пакетах (если предположить, что размеры запроса и ответа меньше минимального размера MTU между двумя оконечными системами). В случае TCP требуется около 10 пакетов, если считать, что для каждого обмена «запрос-ответ» устанавливается новое соединение TCP.</p>
     <p>Для анализа количества передаваемых пакетов важным фактором является также число циклов обращения пакетов, необходимых для получения ответа. Это становится важно, если время ожидания превышает пропускную способность, как показано в приложении А [112]. В этом тексте сказано, что минимальное <emphasis>время транзакции</emphasis> для запроса-ответа UDP равно RTT + SPT, где RTT — это время обращения между клиентом и сервером, a SPT — время обработки запроса сервером. Однако в случае TCP, если для осуществления каждой последовательности «запрос-ответ» используется новое соединение TCP, минимальное время транзакции будет равно 2&#215;RTT+SPT, то есть на один период RTT больше, чем для UDP.</p>
     <p>В отношении второго пункта очевидно, что если соединение TCP используется для множества обменов «запрос-ответ», то стоимость установления и разрыва соединения амортизируется во всех запросах и ответах. Обычно это решение предпочтительнее, чем использование нового соединения для каждого обмена «запрос- ответ». Тем не менее существуют приложения, использующие новое соединение для каждого цикла «запрос-ответ» (например, старые версии HTTP). Кроме того, существуют приложения, в которых клиент и сервер обмениваются в одном цикле «запрос-ответ» (например, DNS), а затем могут не обращаться друг к другу в течение часов или дней.</p>
     <p>Теперь мы перечислим функции TCP, отсутствующие в UDP. Это означает, что приложение должно само реализовывать эти функции, если они ему необходимы. Мы говорим «необходимы», потому что не все свойства требуются всем приложениям. Например, может не возникнуть необходимости повторно передавать потерянные сегменты для аудиоприложений реального времени, если приемник способен интерполировать недостающие данные. Также для простых транзакций «запрос-ответ» может не потребоваться управление потоком, если два конца соединения заранее договорятся о размерах наибольшего запроса и ответа.</p>
     <p>&#9632; <emphasis>Положительные подтверждения, повторная передача потерянных пакетов, обнаружение дубликатов и упорядочивание пакетов, порядок следования которых был изменен сетью</emphasis>. TCP подтверждает получение всех данных, позволяя обнаруживать потерянные пакеты. Реализация этих двух свойств требует, чтобы каждый сегмент данных TCP содержал порядковый номер, по которому можно впоследствии проверить получение данного сегмента. Требуется также, чтобы TCP прогнозировал значение тайм-аута повторной передачи для соединения и чтобы это значение последовательно обновлялось по мере изменения сетевого трафика между конечными точками.</p>
     <p>&#9632; <emphasis>Оконное управление потоком</emphasis>. Принимающий TCP сообщает отправляющему, какое буферное пространство он выделил для приема данных, и отправляющий не может превышать этого ограничения. То есть количество неподтвержденных данных отправителя никогда не может стать больше объявленного размера окна принимающего.</p>
     <p>&#9632; <emphasis>Медленный старт и предотвращение перегрузки</emphasis>. Это форма управления потоком, осуществляемого отправителем, служащая для определения текущей пропускной способности сети и позволяющая контролировать ситуацию во время переполнения сети. Все современные TCP-приложения должны поддерживать эти два свойства, и опыт (накопленный еще до того, как эти алгоритмы были реализованы в конце 80-х) показывает, что протоколы, не снижающие скорость передачи при перегрузке сети, лишь усугубляют эту перегрузку (см., например, [52]).</p>
     <p>Суммируя вышесказанное, мы можем сформулировать следующие рекомендации:</p>
     <p>&#9632; UDP <emphasis>должен</emphasis> использоваться для приложений широковещательной и многоадресной передачи. Если требуется какая-либо форма защиты от ошибок, то соответствующая функциональность должна быть добавлена клиентам и серверам. Однако приложения часто используют широковещательную и многоадресную передачу, когда некоторое (предположительно небольшое) количество ошибок вполне допустимо (например, потеря аудио- или видеопакетов). Имеются приложения многоадресной передачи, требующие надежной доставки (например, пересылка файлов при помощи многоадресной передачи), но в каждом конкретном случае мы должны решить, компенсируется ли выигрышем в производительности, получаемым за счет использования многоадресной передачи (отправка одного пакета <emphasis>N</emphasis> получателям вместо отправки <emphasis>N</emphasis> копий пакета через <emphasis>N</emphasis> соединений TCP), дополнительное усложнение приложения для обеспечения надежности соединений.</p>
     <p>&#9632; UDP <emphasis>может</emphasis> использоваться для простых приложений «запрос-ответ», но тогда обнаружение ошибок должно быть встроено в приложение. Минимально это означает включение подтверждений, тайм-аутов и повторных передач. Управление потоком часто не является существенным для обеспечения надежности, если запросы и ответы имеют достаточно разумный размер. Мы приводим пример реализации этой функциональности в приложении UDP, представленном в разделе 22.5. Факторы, которые нужно учитывать, — это частота соединения клиента и сервера (нужно решить, можно ли не разрывать установленное соединение TCP между транзакциями) и количество данных, которыми обмениваются клиент и сервер (если в большинстве случаев при работе данного приложения требуется много пакетов, стоимость установления и разрыва соединения TCP становится менее значимым фактором).</p>
     <p>&#9632; UDP <emphasis>не следует</emphasis> использовать для передачи большого количества данных (например, при передаче файлов). Причина в том, что оконное управление потоком, предотвращение переполнения и медленный старт должны быть встроены в приложение вместе с функциями, перечисленными в предыдущем пункте. Это означает, что мы фактически заново изобретаем TCP для одного конкретного приложения. Нам следует оставить производителям заботу об улучшении производительности TCP и сконцентрировать свои усилия на самом приложении.</p>
     <p>Из этих правил есть исключения, в особенности для существующих приложений. Например, TFTP использует UDP для передачи большого количества данных. Для TFTP был выбран UDP, поскольку, во-первых, его реализация проще в отношении кода начальной загрузки (800 строк кода С для UDP в сравнении с 4500 строками для TCP, например в [128]), а во-вторых, TFTP используется только для начальной загрузки систем в локальной сети, а не для передачи большого количества данных через глобальные сети. Однако при этом требуется, чтобы в TFTP были предусмотрены такие свойства, как собственное поле порядкового номера (для подтверждений), тайм-аут и возможность повторной передачи.</p>
     <p>NFS (Network File System — сетевая файловая система) является другим исключением из правила: она также использует UDP для передачи большого количества данных (хотя некоторые могут возразить, что в действительности это приложение типа «запрос-ответ», использующее запросы и ответы больших размеров). Отчасти это можно объяснить исторически сложившимися обстоятельствами: в середине 80-х, когда была разработана эта система, реализации UDP были быстрее, чем TCP, и система NFS использовалась только в локальных сетях, где потеря пакетов, как правило, происходит на несколько порядков реже, чем в глобальных сетях. Но как только в начале 90-х NFS начала использоваться в глобальных сетях, а реализации TCP стали обгонять UDP в отношении производительности при передаче большого количества данных, была разработана версия 3 системы NFS для поддержки TCP. Теперь большинство производителей предоставляют NFS как для и TCP, так и для UDP. Аналогичные причины (большая скорость по сравнению с TCP в начале 80-х плюс преобладание локальных сетей над глобальными) привели к тому, что в Apollo NCS (предшественник DCE RPC) сначала использовали UDP, а не TCP, хотя современные реализации поддерживают и UDP, и TCP.</p>
     <p>Мы могли бы сказать, что применение UDP сокращается, поскольку сегодня хорошие реализации TCP не уступают в скорости сетям и все меньше разработчиков готовы встраивать в приложения UDP функциональность, свойственную TCP. Но предсказываемое увеличение количества мультимедиа-приложений в будущем десятилетии должно привести к возрастанию популярности UDP, поскольку их работа обычно подразумевает использование многоадресной передачи, требующей наличия UDP.</p>
    </section>
    <section>
     <title>
      <p>22.5. Добавление надежности приложению UDP</p>
     </title>
     <section>
      <p>Если мы хотим использовать UDP для приложения типа «запрос-ответ», как было отмечено в предыдущем разделе, мы <emphasis>должны</emphasis> добавить нашему клиенту две функции:</p>
      <p>&#9632; тайм-аут и повторную передачу, которые позволяют решать проблемы, возникающие в случае потери дейтаграмм;</p>
      <p>&#9632; порядковые номера, позволяющие клиенту проверить, что ответ приходит на определенный запрос.</p>
      <p>Эти два свойства предусмотрены в большинстве существующих приложений UDP, использующих простую модель «запрос-ответ»: например, распознаватели DNS, агенты SNMP, TFTP и RPC. Мы не пытаемся использовать UDP для передачи большого количества данных: наша цель — приложение, посылающее запрос и ожидающее ответа на этот запрос.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Использование дейтаграмм по определению не может быть надежным, следовательно, мы специально не называем данный сервис «надежным сервисом дейтаграмм». Действительно, термин «надежная дейтаграмма» — это оксюморон. Речь идет лишь о том, что приложение до некоторой степени обеспечивает надежность, добавляя соответствующие функциональные возможности «поверх» ненадежного сервиса дейтаграмм (UDP).</p>
      </cite>
      <p>Добавление порядковых номеров осуществляется легко. Клиент подготавливает порядковый номер для каждого запроса, а сервер должен отразить этот номер обратно в своем ответе клиенту. Это позволяет клиенту проверить, что данный ответ пришел на соответствующий запрос.</p>
      <p>Более старый метод реализации тайм-аутов и повторной передачи заключался в отправке запроса и ожидании в течение <emphasis>N</emphasis> секунд. Если ответ не приходил, осуществлялась повторная передача и снова на ожидание ответа отводилось <emphasis>N</emphasis> секунд. Если это повторялось несколько раз, отправка запроса прекращалась. Это так называемый <emphasis>линейный</emphasis> таймер повторной передачи (на рис. 6.8 [111] показан пример клиента TFTP, использующего эту технологию. Многие клиенты TFTP до сих пор пользуются этим методом).</p>
      <p>Проблема при использовании этой технологии состоит в том, что количество времени, в течение которого дейтаграмма совершает цикл в объединенной сети, может варьироваться от долей секунд в локальной сети до нескольких секунд в глобальной. Факторами, влияющими на время обращения (RTT), являются расстояние, скорость сети и переполнение. Кроме того, RTT между клиентом и сервером может быстро меняться со временем при изменении условий в сети. Нам придется использовать тайм-ауты и алгоритм повторной передачи, который учитывает действительное (измеряемое) значение периода RTT и изменения RTT с течением времени. В этой области ведется большая исследовательская работа, в основном направленная на TCP, но некоторые идеи применимы к любым сетевым приложениям.</p>
      <p>Мы хотим вычислить тайм-аут повторной передачи (RTO), чтобы использовать его при отправке каждого пакета. Для того чтобы выполнить это вычисление, мы измеряем RTT — действительное время обращения для пакета. Каждый раз, измеряя RTT, мы обновляем два статистических показателя: <code>srtt</code> — сглаженную оценку RTT, и <code>rttvar</code> — сглаженную оценку среднего отклонения. Последняя является хорошей приближенной оценкой стандартного отклонения, но ее легче вычислять, поскольку для этого не требуется извлечения квадратного корня. Имея эти два показателя, мы вычисляем RTO как сумму <code>srtt</code> и <code>rttvar</code>, умноженного на четыре. В [52] даются все необходимые подробности этих вычислений, которые мы можем свести к четырем следующим уравнениям:</p>
      <p><code>delta = measuredRTT - srtt</code></p>
      <p><code>srtt &#8592; srtt + g &#215; delta</code></p>
      <p><code>rttvar &#8592; rttvar + h (|delta| - rttvar)</code></p>
      <p><code>RTO = srtt + 4 &#215; rttvar</code></p>
      <p><code>delta</code> — это разность между измеренным RTT и текущим сглаженным показателем RTT (<code>srtt</code>). <code>g</code> — это приращение, применяемое к показателю RTT, равное 1/8. <code>h</code> — это приращение, применяемое к сглаженному показателю среднего отклонения, равное &#188;.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Два приращения и множитель 4 в вычислении RTO специально выражены степенями числа 2 и могут быть вычислены с использованием операций сдвига вместо деления и умножения. На самом деле реализация TCP в ядре (см. раздел 25.7 [128]) для ускорения вычислений обычно использует арифметику с фиксированной точкой, но мы для простоты используем в нашем коде вычисления с плавающей точкой.</p>
      </cite>
      <p>Другой важный момент, отмеченный в [52], заключается в том, что по истечении времени таймера повторной передачи для следующего RTO должно использоваться <emphasis>экспоненциальное смещение</emphasis> (<emphasis>exponential backoff</emphasis>). Например, если наше первое значение RTO равно 2 с и за это время ответа не получено, следующее значение RTO будет равно 4 с. Если ответ все еще не последовал, следующее значение RTO будет 8 с, затем 16 и т.д.</p>
      <p>Алгоритмы Джекобсона (Jacobson) реализуют вычисление RTO при измерении RTT и увеличение RTO при повторной передаче. Однако, когда клиент выполняет повторную передачу и получает ответ, возникает <emphasis>проблема неопределенности повторной передачи</emphasis> (<emphasis>retransmission ambiguity problem</emphasis>). На рис. 22.2 показаны три возможных сценария, при которых истекает время ожидания повторной передачи:</p>
      <p>&#9632; запрос потерян;</p>
      <p>&#9632; ответ потерян;</p>
      <p>&#9632; значение RTO слишком мало.</p>
      <image l:href="#img_125.png"/>
      <p><strong>Рис. 22.2</strong>. Три сценария, возможные при истечении времени таймера повторной передачи</p>
      <p>Когда клиент получает ответ на запрос, отправленный повторно, он не может сказать, какому из запросов соответствует ответ. На рисунке, изображенном справа, ответ соответствует начальному запросу, в то время как на двух других рисунках ответ соответствуют второму запросу.</p>
      <p>Алгоритм Карна (Karn) [58] обрабатывает этот сценарий в соответствии со следующими правилами, применяемыми в любом случае, когда ответ получен на запрос, отправленный более одного раза:</p>
      <p>&#9632; Если для запроса и ответа было измерено значение RTT, не следует использовать его для обновления оценочных значений, так как мы не знаем, какому запросу соответствует ответ.</p>
      <p>&#9632; Поскольку ответ пришел до того, как истекло время нашего таймера повторной передачи, используйте для следующего пакета текущее значение RTO. Только когда мы получим ответ на запрос, который не был передан повторно, мы изменяем значение RTT и снова вычисляем RTO.</p>
      <p>При написании наших функций RTT применить алгоритм Карна несложно, но оказывается, что существует и более изящное решение. Оно используется в расширениях TCP для сетей с высокой пропускной способностью, то есть сетей, обладающих либо широкой полосой пропускания, либо большим значением RTT, либо обоими этими свойствами (RFC 1323 [53]). Кроме добавления порядкового номера к началу каждого запроса, который сервер должен отразить, мы добавляем <emphasis>отметку времени</emphasis>, которую сервер также должен отразить. Каждый раз, отправляя запрос, мы сохраняем в этой отметке значение текущего времени. Когда приходит ответ, мы вычисляем величину RTT для этого пакета как текущее время минус значение отметки времени, отраженной сервером в своем ответе. Поскольку каждый запрос несет отметку времени, отражаемую сервером, мы можем вычислить RTT для <emphasis>каждого</emphasis> ответа, который мы получаем. Теперь нет никакой неопределенности. Более того, поскольку сервер только отражает отметку времени клиента, клиент может использовать для отметок времени любые удобные единицы, и при этом не требуется, чтобы клиент и сервер синхронизировали часы.</p>
     </section>
     <section>
      <title>
       <p>Пример</p>
      </title>
      <p>Свяжем теперь всю эту информацию воедино в примере. Мы начнем с функции <code>main</code> нашего клиента UDP, представленного в листинге 8.3, и изменим в ней только номер порта с <code>SERV_PORT</code> на 7 (стандартный эхо-сервер, см. табл. 2.1).</p>
      <p>В листинге 22.4 показана функция <code>dg_cli</code>. Единственное изменение по сравнению с листингом 8.4 состоит в замене вызовов функций <code>sendto</code> и <code>recvfrom</code> вызовом нашей новой функции <code>dg_send_recv</code>.</p>
      <p>Перед тем как представить функцию <code>dg_send_recv</code> и наши функции RTT, которые она вызывает, мы показываем в листинге 22.5 нашу схему реализации функциональных свойств, повышающих надежность клиента UDP. Все функции, имена которых начинаются с <code>rtt_</code>, описаны далее.</p>
      <p><strong>Листинг 22.4</strong>. Функция dg_cli, вызывающая нашу функцию dg_send_recv</p>
      <p><code>//rtt/dg_cli.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 ssize_t Dg_send_recv(int, const void*, size_t, void*, size_t,</code></p>
      <p><code> 3  const SA*, socklen_t);</code></p>
      <empty-line/>
      <p><code> 4 void</code></p>
      <p><code> 5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  ssize_t n;</code></p>
      <p><code> 8  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
      <empty-line/>
      <p><code> 9  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <empty-line/>
      <p><code>10   n = Dg_send_recv(sockfd, sendline, strlen(sendline),</code></p>
      <p><code>11    recvline, MAXLINE, pservaddr, servlen);</code></p>
      <empty-line/>
      <p><code>12   recvline[n] = 0; /* завершающий нуль */</code></p>
      <p><code>13   Fputs(recvline, stdout);</code></p>
      <p><code>14  }</code></p>
      <p><code>15 }</code></p>
      <p><strong>Листинг 22.5</strong>. Схема функций RTT и последовательность их вызова</p>
      <p><code>static sigjmp_buf jmpbuf;</code></p>
      <empty-line/>
      <p><code>{</code></p>
      <empty-line/>
      <p><code><emphasis> формирование запроса</emphasis></code></p>
      <empty-line/>
      <p><code> signal(SIGALRM, sig_alrm); /* устанавливаем обработчик сигнала */</code></p>
      <p><code> rtt_newpack(); /* инициализируем значение счетчика rexmt нулем */</code></p>
      <p><code>sendagain:</code></p>
      <p><code> sendto();</code></p>
      <empty-line/>
      <p><code> alarm(rtt_start()); /* задаем аргумент функции alarm равным RTO */</code></p>
      <p><code> if (sigsetjmp(jmpbuf, 1) != 0) {</code></p>
      <p><code>  if (rtt_timeout()) /* удваиваем RTO, обновляем оценочные значения */</code></p>
      <p><code><emphasis>   отказываемся от дальнейших попыток</emphasis></code></p>
      <p><code>  goto sendagain; /* повторная передача */</code></p>
      <p><code> }</code></p>
      <p><code> do {</code></p>
      <p><code>  recvfrom();</code></p>
      <p><code> } while (неправильный порядковый номер);</code></p>
      <p><code> alarm(0); /* отключаем сигнал alarm */</code></p>
      <p><code> rtt_stop(); /* вычисляем RTT и обновляем оценочные значения */</code></p>
      <empty-line/>
      <p><code><emphasis> обрабатываем ответ</emphasis></code></p>
      <empty-line/>
      <p><code>}</code></p>
      <empty-line/>
      <p><code>void sig_alrm(int signo) {</code></p>
      <p><code> siglongjmp(jmpbuf, 1);</code></p>
      <p><code>}</code></p>
      <p>Если приходит ответ, но его порядковый номер отличается от предполагаемого, мы снова вызываем функцию <code>recvfrom</code>, но не отправляем снова тот же запрос и не перезапускаем работающий таймер повторной передачи. Обратите внимание, что в крайнем правом случае на рис. 22.2 последний ответ, полученный на отправленный повторно запрос, будет находиться в приемном буфере сокета до тех пор, пока клиент не решит отправить следующий запрос (и получить на него ответ). Это нормально, поскольку клиент прочитает этот ответ, отметит, что порядковый номер отличается от предполагаемого, проигнорирует ответ и снова вызовет функцию <code>recvfrom</code>.</p>
      <p>Мы вызываем функции <code>sigsetjmp</code> и <code>siglongjmp</code>, чтобы предотвратить возникновение ситуации гонок с сигналом <code>SIGALRM</code>, который мы описали в разделе 20.5. В листинге 22.6 показана первая часть нашей функции <code>dg_send_recv</code>.</p>
      <p><strong>Листинг 22.6</strong>. Функция dg_send_recv: первая половина</p>
      <p><code>//rtt/dg_send_recv.c</code></p>
      <p><code> 1 #include "unprtt.h"</code></p>
      <p><code> 2 #include &lt;setjmp.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 #define RTT_DEBUG</code></p>
      <empty-line/>
      <p><code> 4 static struct rtt_info rttinfo;</code></p>
      <p><code> 5 static int rttinit = 0;</code></p>
      <p><code> 6 static struct msghdr msgsend, msgrecv;</code></p>
      <p><code>   /* предполагается, что обе структуры инициализированы нулем */</code></p>
      <p><code> 7 static struct hdr {</code></p>
      <p><code> 8  uint32_t seq; /* порядковый номер */</code></p>
      <p><code> 9  uint32_t ts;  /* отметка времени при отправке */</code></p>
      <p><code>10 } sendhdr, recvhdr;</code></p>
      <empty-line/>
      <p><code>11 static void signalrm(int signo);</code></p>
      <p><code>12 static sigjmp_buf jmpbuf;</code></p>
      <empty-line/>
      <p><code>13 ssize_t</code></p>
      <p><code>14 dg_send_recv(int fd, const void *outbuff, size_t outbytes,</code></p>
      <p><code>15  void *inbuff, size_t inbytes,</code></p>
      <p><code>16  const SA *destaddr, socklen_t destlen)</code></p>
      <p><code>17 {</code></p>
      <p><code>18  ssize_t n;</code></p>
      <p><code>19  struct iovec iovsend[2], iovrecv[2];</code></p>
      <p><code>20  if (rttinit == 0) {</code></p>
      <p><code>21   rtt_init(&amp;rttinfo); /* первый вызов */</code></p>
      <p><code>22   rttinit = 1;</code></p>
      <p><code>23   rtt_d_flag = 1;</code></p>
      <p><code>24  }</code></p>
      <p><code>25  sendhdr.seq++;</code></p>
      <p><code>26  msgsend.msg_name = destaddr;</code></p>
      <p><code>27  msgsend.msg_namelen = destlen;</code></p>
      <p><code>28  msgsend.msg_iov = iovsend;</code></p>
      <p><code>29  msgsend.msg_iovlen = 2;</code></p>
      <p><code>30  iovsend[0].iov_base = &amp;sendhdr;</code></p>
      <p><code>31  iovsend[0].iov_len = sizeof(struct hdr);</code></p>
      <p><code>32  iovsend[1].iov_base = outbuff;</code></p>
      <p><code>33  iovsend[1].iov_len = outbytes;</code></p>
      <p><code>34  msgrecv.msg_name = NULL;</code></p>
      <p><code>35  msgrecv.msg_namelen = 0;</code></p>
      <p><code>36  msgrecv.msg_iov = iovrecv;</code></p>
      <p><code>37  msgrecv.msg_iovlen = 2;</code></p>
      <p><code>38  iovrecv[0].iov_base = &amp;recvhdr;</code></p>
      <p><code>39  iovrecv[0].iov_len = sizeof(struct hdr);</code></p>
      <p><code>40  iovrecv[l].iov_base = inbuff;</code></p>
      <p><code>41  iovrecv[l].iov_len = inbytes;</code></p>
      <p><code>1-5</code> Мы включаем новый заголовочный файл <code>unprtt.h</code>, показанный в листинге 22.8, который определяет структуру <code>rtt_info</code>, содержащую информацию RTT для клиента. Мы определяем одну из этих структур и ряд других переменных.</p>
      <subtitle>Определение структур msghdr и структуры hdr</subtitle>
      <p><code>6-10</code> Мы хотим скрыть от вызывающего процесса добавление порядкового номера и отметки времени в начало каждого пакета. Проще всего использовать для этого функцию <code>writev</code>, записав свой заголовок (структура <code>hdr</code>), за которым следуют данные вызывающего процесса, в виде одной дейтаграммы UDP. Вспомните, что результатом выполнения функции <code>writev</code> на дейтаграммном сокете является отправка одной дейтаграммы. Это проще, чем заставлять вызывающий процесс выделять для нас место в начале буфера, а также быстрее, чем копировать наш заголовок и данные вызывающего процесса в один буфер (под который мы должны выделить память) для каждой функции <code>sendto</code>. Но поскольку мы работаем с UDP и нам необходимо задать адрес получателя, следует использовать возможности, предоставляемые структурой <code>iovec</code> функций <code>sendmsg</code> и <code>recvmsg</code> и отсутствующие в функциях <code>sendto</code> и <code>recvfrom</code>. Вспомните из раздела 14.5, что в некоторых системах доступна более новая структура <code>msghdr</code>, включающая вспомогательные данные (<code>msg_control</code>), тогда как в более старых системах вместо них применяются элементы <code>msg_accright</code> (так называемые права доступа — access rights), расположенные в конце структуры. Чтобы избежать усложнения кода директивами <code>#ifdef</code> для обработки этих различий, мы объявляем две структуры <code>msghdr</code> как <code>static</code>. При этом они инициализируются только нулевыми битами, а затем неиспользованные элементы в конце структур просто игнорируются.</p>
      <subtitle>Инициализация при первом вызове</subtitle>
      <p><code>20-24</code> При первом вызове нашей функции мы вызываем функцию <code>rtt_init</code>.</p>
      <subtitle>Заполнение структур msghdr</subtitle>
      <p><code>25-41</code> Мы заполняем две структуры <code>msghdr</code>, используемые для ввода и вывода. Для данного пакета мы увеличиваем на единицу порядковый номер отправки, но не устанавливаем отметку времени отправки, пока пакет не будет отправлен (поскольку он может отправляться повторно, а для каждой повторной передачи требуется текущая отметка времени).</p>
      <p>Вторая часть функции вместе с обработчиком сигнала <code>sig_alarm</code> показана в листинге 22.7.</p>
      <p><strong>Листинг 22.7</strong>. Функция dg_send_recv: вторая половина</p>
      <p><code>//rtt/dg_send_rеcv.c</code></p>
      <p><code>42  Signal(SIGALRM, sig_alrm);</code></p>
      <p><code>43  rtt_newpack(&amp;rttinfo); /* инициализируем для этого пакета */</code></p>
      <empty-line/>
      <p><code>44 sendagain:</code></p>
      <p><code>45  sendhdr.ts = rtt_ts(&amp;rttinfo);</code></p>
      <p><code>46  Sendmsg(fd, &amp;msgsend, 0);</code></p>
      <empty-line/>
      <p><code>47  alarm(rtt_start(&amp;rttinfo)); /* вычисляем тайм-аут. запускаем таймер */</code></p>
      <p><code>48  if (sigsetjmp(jmpbuf, 1) != 0) {</code></p>
      <p><code>49   if (rtt_timeout(&amp;rttinfо) &lt; 0) {</code></p>
      <p><code>50    err_msg("dg_send_recv: no response from server, giving up");</code></p>
      <p><code>51    rttinit = 0; /* повторная инициализация для следующего вызова */</code></p>
      <p><code>52    errno = ETIMEDOUT;</code></p>
      <p><code>53    return (-1);</code></p>
      <p><code>54   }</code></p>
      <p><code>55   goto sendagain;</code></p>
      <p><code>56  }</code></p>
      <p><code>57  do {</code></p>
      <p><code>58   n = Recvmsg(fd, &amp;msgrecv, 0);</code></p>
      <p><code>59  } while (n &lt; sizeof(struct hdr) || recvhdr.seq != sendhdr.seq);</code></p>
      <empty-line/>
      <p><code>60  alarm(0); /* останавливаем таймер SIGALRM */</code></p>
      <p><code>61  /* вычисляем и записываем новое значение оценки RTT */</code></p>
      <p><code>62  rtt_stop(&amp;rttinfo, rtt_ts(&amp;rttinfo) — recvhdr.ts);</code></p>
      <empty-line/>
      <p><code>63  return (n - sizeof(struct hdr)); /* возвращаем размер полученной</code></p>
      <p><code>                                        дейтаграммы */</code></p>
      <p><code>64 }</code></p>
      <empty-line/>
      <p><code>65 static void</code></p>
      <p><code>66 sig_alrm(int signo)</code></p>
      <p><code>67 {</code></p>
      <p><code>68  siglongjmp(jmpbuf, 1);</code></p>
      <p><code>69 }</code></p>
      <subtitle>Установка обработчика сигналов</subtitle>
      <p><code>42-43</code> Для сигнала <code>SIGALRM</code> устанавливается обработчик сигналов, а функция <code>rtt_newpack</code> устанавливает счетчик повторных передач в нуль.</p>
      <subtitle>Отправка дейтаграммы</subtitle>
      <p><code>45-47</code> Функция <code>rtt_ts</code> получает текущую отметку времени. Отметка времени хранится в структуре <code>hdr</code>, которая добавляется к данным пользователя. Одиночная дейтаграмма UDP отправляется функцией <code>sendmsg</code>. Функция <code>rtt_start</code> возвращает количество секунд для этого тайм-аута, а сигнал <code>SIGALRM</code> контролируется функцией <code>alarm</code>.</p>
      <subtitle>Установка буфера перехода</subtitle>
      <p><code>48</code> Мы устанавливаем буфер перехода для нашего обработчика сигналов с помощью функции sigsetjmp. Мы ждем прихода следующей дейтаграммы, вызывая функцию <code>recvmsg</code>. (Совместное использование функций <code>sigsetjmp</code> и <code>siglongjmp</code> вместе с сигналом <code>SIGALRM</code> мы обсуждали применительно к листингу 20.5.) Если время таймера истекает, функция <code>sigsetjmp</code> возвращает 1.</p>
      <subtitle>Обработка тайм-аута</subtitle>
      <p><code>49-55</code> Когда возникает тайм-аут, функция <code>rtt_timeout</code> вычисляет следующее значение RTO (используя экспоненциальное смещение) и возвращает -1, если нужно прекратить попытки передачи дейтаграммы, или 0, если нужно выполнить очередную повторную передачу. Когда мы прекращаем попытки, мы присваиваем переменной <code>errno</code> значение <code>ETIMEDOUT</code> и возвращаемся в вызывающую функцию.</p>
      <subtitle>Вызов функции recvmsg, сравнение порядковых номеров</subtitle>
      <p><code>57-59</code> Мы ждем прихода дейтаграммы, вызывая функцию <code>recvmsg</code>. Длина полученной дейтаграммы не должна быть меньше размера структуры <code>hdr</code>, а ее порядковый номер должен совпадать с порядковым номером запроса, ответом на который предположительно является эта дейтаграмма. Если при сравнении хотя бы одно из этих условий не выполняется, функция <code>recvmsg</code> вызывается снова.</p>
      <subtitle>Выключение таймера и обновление показателей RTT</subtitle>
      <p><code>60-62</code> Когда приходит ожидаемый ответ, функция <code>alarm</code> отключается, а функция <code>rtt_stop</code> обновляет оценочное значение RTT. Функция <code>rtt_ts</code> возвращает текущую отметку времени, и отметка времени из полученной дейтаграммы вычитается из текущей отметки, что дает в результате RTT.</p>
      <subtitle>Обработчик сигнала SIGALRM</subtitle>
      <p><code>65-69</code> Вызывается функция <code>siglongjmp</code>, результатом выполнения которой является то, что функция <code>sigsetjmp</code> в <code>dg_send_recv</code> возвращает 1.</p>
      <p>Теперь мы рассмотрим различные функции RTT, которые вызывались нашей функцией <code>dg_send_recv</code>. В листинге 22.8 показан заголовочный файл <code>unprtt.h</code>.</p>
      <p><strong>Листинг 22.8</strong>. Заголовочный файл unprtt.h</p>
      <p><code>//lib/unprtt.h</code></p>
      <p><code> 1 #ifndef __unp_rtt_h</code></p>
      <p><code> 2 #define __unp_rtt_h</code></p>
      <empty-line/>
      <p><code> 3 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 4 struct rtt_info {</code></p>
      <p><code> 5  float    rtt_rtt;    /* последнее измеренное значение RTT в секундах */</code></p>
      <p><code> 6  float    rtt_srtt;   /* сглаженная оценка RTT в секундах */</code></p>
      <p><code> 7  float    rtt_rttvar; /* сглаженные средние значения отклонений</code></p>
      <p><code>                            в секундах */</code></p>
      <p><code> 8  float    rtt_rto;    /* текущее используемое значение RTO, в секундах */</code></p>
      <p><code> 9  int      rtt_nrexmt; /* количество повторных передач: 0, 1, 2, ... */</code></p>
      <p><code>10  uint32_t rtt_base;   /* число секунд, прошедшее после 1.1.1970 в начале */</code></p>
      <p><code>11 };</code></p>
      <empty-line/>
      <p><code>12 #define RTT_RXTMIN    2 /* минимальное значение тайм-аута для</code></p>
      <p><code>                              повторной передачи, в секундах */</code></p>
      <p><code>13 #define RTT_RXTMAX   60 /* максимальное значение тайм-аута для</code></p>
      <p><code>                              повторной передачи, в секундах */</code></p>
      <p><code>14 #define RTT_MAXNREXMT 3 /* максимально допустимое количество</code></p>
      <p><code>                              повторных передач одной дейтаграммы */</code></p>
      <empty-line/>
      <p><code>15 /* прототипы функций */</code></p>
      <p><code>16 void     rtt_debug(struct rtt_info*);</code></p>
      <p><code>17 void     rtt_init(struct rtt_info*);</code></p>
      <p><code>18 void     rtt_newpack(struct rtt_info*);</code></p>
      <p><code>19 int      rtt_start(struct rtt_info*);</code></p>
      <p><code>20 void     rtt_stop(struct rtt_info*, uint32_t);</code></p>
      <p><code>21 int      rtt_timeout(struct rtt_info*);</code></p>
      <p><code>22 uint32_t rtt_ts(struct rtt_info*);</code></p>
      <p><code>23 extern int rtt_d_flag; /* может быть ненулевым при наличии</code></p>
      <p><code>                             дополнительной информации */</code></p>
      <p><code>24 #endif /* _unp_rtt_h */</code></p>
      <subtitle>Структура rtt_info</subtitle>
      <p><code>4-11</code> Эта структура содержит переменные, необходимые для того, чтобы определить время передачи пакетов между клиентом и сервером. Первые четыре переменных взяты из уравнений, приведенных в начале этого раздела.</p>
      <p><code>12-14</code> Эти константы определяют минимальный и максимальный тайм-ауты повторной передачи и максимальное число возможных повторных передач.</p>
      <p>В листинге 22.9 показан макрос <code>RTT_RTOCALC</code> и первые две из четырех функций RTT.</p>
      <p><strong>Листинг 22.9</strong>. Макрос RTT_RTOCALC, функции rtt_minmax и rtt_init</p>
      <p><code>//lib/rtt.c</code></p>
      <p><code> 1 #include "unprtt.h"</code></p>
      <empty-line/>
      <p><code> 2 int rtt_d_flag = 0; /* отладочный флаг; может быть установлен в</code></p>
      <p><code>                          ненулевое значение вызывающим процессом */</code></p>
      <p><code> 3 /* Вычисление значения RTO на основе текущих значений:</code></p>
      <p><code> 4  * сглаженное оценочное значение RTT + четырежды сглаженная</code></p>
      <p><code> 5  * величина отклонения.</code></p>
      <p><code> 6  */</code></p>
      <p><code> 7 #define RTI_RTOCALC(ptr) ((ptr)-&gt;rtt_srtt + (4.0 * (ptr)-&gt;rtt_rttvar))</code></p>
      <empty-line/>
      <p><code> 8 static float</code></p>
      <p><code> 9 rtt_minmax(float rto)</code></p>
      <p><code>10 {</code></p>
      <p><code>11  if (rto &lt; RTT_RXTMIN)</code></p>
      <p><code>12   rto = RTT_RXTMIN;</code></p>
      <p><code>13  else if (rto &gt; RTT_RXTMAX)</code></p>
      <p><code>14   rto = RTT_RXTMAX;</code></p>
      <p><code>15  return (rto);</code></p>
      <p><code>16 }</code></p>
      <empty-line/>
      <p><code>17 void</code></p>
      <p><code>18 rtt_init(struct rtt_info *ptr)</code></p>
      <p><code>19 {</code></p>
      <p><code>20  struct timeval tv;</code></p>
      <empty-line/>
      <p><code>21  Gettimeofday(&amp;tv, NULL);</code></p>
      <p><code>22  ptr-&gt;rtt_base = tv.tv_sec; /* количество секунд, прошедших с 1.1.1970 */</code></p>
      <empty-line/>
      <p><code>23  ptr-&gt;rtt_rtt = 0;</code></p>
      <p><code>24  ptr-&gt;rtt_srtt = 0;</code></p>
      <p><code>25  ptr-&gt;rtt_rttvar = 0.75;</code></p>
      <p><code>26  ptr-&gt;rtt_rto = rtt_minmax(RTT_RTOCALC(ptr));</code></p>
      <p><code>27  /* первое RTO (srtt + (4 * rttvar)) = 3 с */</code></p>
      <p><code>28 }</code></p>
      <p><code>3-7</code> Макрос вычисляет RTO как сумму оценочной величины RTT и оценочной величины среднего отклонения, умноженной на четыре.</p>
      <p><code>8-16</code> Функция <code>rtt_minmax</code> проверяет, что RTO находится между верхним и нижним пределами, заданными в заголовочном файле <code>unprtt.h</code>.</p>
      <p><code>17-28</code> Функция <code>rtt_init</code> вызывается функцией <code>dg_send_recv</code> при первой отправке пакета. Функция <code>gettimeofday</code> возвращает текущее время и дату в той же структуре <code>timeval</code>, которую мы видели в функции <code>select</code> (см. раздел 6.3). Мы сохраняем только текущее количество секунд с момента начала эпохи Unix, то есть с 00:00:00 1 января 1970 года (UTC). Измеряемое значение RTT обнуляется, а сглаженная оценка RTT и среднее отклонение принимают соответственно значение 0 и 0,75, в результате чего начальное RTO равно 3 с (4&#215;0,75).</p>
      <p>В листинге 22.10 показаны следующие три функции RTT.</p>
      <p><strong>Листинг 22.10</strong>. Функции rtt_ts, rtt_newpack и rtt_start</p>
      <p><code>//lib/rtt.c</code></p>
      <p><code>34 uint32_t</code></p>
      <p><code>35 rtt_ts(struct rtt_info *ptr)</code></p>
      <p><code>36 {</code></p>
      <p><code>37  uint32_t ts;</code></p>
      <p><code>38  struct timeval tv;</code></p>
      <empty-line/>
      <p><code>39  Gettimeofday(&amp;tv, NULL);</code></p>
      <p><code>40  ts = ((tv.tv_sec - ptr-&gt;rtt_base) * 1000) + (tv.tv_usec / 1000);</code></p>
      <p><code>41  return (ts);</code></p>
      <p><code>42 }</code></p>
      <empty-line/>
      <p><code>43 void</code></p>
      <p><code>44 rtt_newpack(struct rtt_info *ptr)</code></p>
      <p><code>45 {</code></p>
      <p><code>46  ptr-&gt;rtt_nrexmt = 0;</code></p>
      <p><code>47 }</code></p>
      <empty-line/>
      <p><code>48 int</code></p>
      <p><code>49 rtt_start(struct rtt_info *ptr)</code></p>
      <p><code>50 {</code></p>
      <p><code>51  return ((int)(ptr-&gt;rtt_rto + 0.5)); /* округляем float до int */</code></p>
      <p><code>52  /* возвращенное значение может быть использовано как аргумент</code></p>
      <p><code>       alarm(rtt_start(&amp;fоо)) */</code></p>
      <p><code>53 }</code></p>
      <p><code>34-42</code> Функция <code>rtt_ts</code> возвращает текущую отметку времени для вызывающего процесса, которая должна содержаться в отправляемой дейтаграмме в виде 32-разрядного целого числа без знака. Мы получаем текущее время и дату из функции <code>gettimeofday</code> и затем вычитаем число секунд в момент вызова функции <code>rtt_init</code> (значение, хранящееся в элементе <code>rtt_base</code> структуры <code>rtt_info</code>). Мы преобразуем это значение в миллисекунды, а также преобразуем в миллисекунды значение, возвращаемое функцией <code>gettimeofday</code> в микросекундах. Тогда отметка времени является суммой этих двух значений в миллисекундах.</p>
      <p>Разница во времени между двумя вызовами функции <code>rtt_ts</code> представляется количеством миллисекунд между этими двумя вызовами. Но мы храним отметки времени в 32-разрядном целом числе без знака, а не в структуре <code>timeval</code>.</p>
      <p><code>43-47</code> Функция <code>rtt_newpack</code> просто обнуляет счетчик повторных передач. Эта функция должна вызываться всегда, когда новый пакет отправляется в первый раз.</p>
      <p><code>48-53</code> Функция <code>rtt_start</code> возвращает текущее значение RTO в миллисекундах. Возвращаемое значение затем может использоваться в качестве аргумента функции <code>alarm</code>.</p>
      <p>Функция <code>rtt_stop</code>, показанная в листинге 22.11, вызывается после получения ответа для обновления оценочного значения RTT и вычисления нового значения RTO.</p>
      <p><strong>Листинг 22.11</strong>. Функция rtt_stop: обновление показателей RTT и вычисление нового</p>
      <p><code>//lib/rtt.c</code></p>
      <p><code>62 void</code></p>
      <p><code>63 rtt_stop(struct rtt_info *ptr, uint32_t ms)</code></p>
      <p><code>64 {</code></p>
      <p><code>65  double delta;</code></p>
      <empty-line/>
      <p><code>66  ptr-&gt;rtt_rtt = ms / 1000.0; /* измеренное значение RTT в секундах */</code></p>
      <empty-line/>
      <p><code>67  /*</code></p>
      <p><code>68   * Обновляем оценочные значения RTT среднего отклонения RTT.</code></p>
      <p><code>69   * (См. статью Джекобсона (Jacobson). SIGCOMM'88. Приложение А.)</code></p>
      <p><code>70   * Здесь мы для простоты используем числа с плавающей точкой.</code></p>
      <p><code>71   */</code></p>
      <empty-line/>
      <p><code>72  delta = ptr-&gt;rtt_rtt - ptr-&gt;rtt_srtt;</code></p>
      <p><code>73  ptr-&gt;rtt_srtt += delta / 8; /* g - 1/8 */</code></p>
      <empty-line/>
      <p><code>74  if (delta &lt; 0.0)</code></p>
      <p><code>75   delta = -delta; /* |delta| */</code></p>
      <empty-line/>
      <p><code>76  ptr-&gt;rtt_rttvar += (delta - ptr-&gt;rtt_rttvar) / 4; /* h - 1/4 */</code></p>
      <empty-line/>
      <p><code>77  ptr-&gt;rtt_rto = rtt_minmax(RTT_RTOCALC(ptr));</code></p>
      <p><code>78 }</code></p>
      <p><code>62-78</code> Вторым аргументом является измеренное RTT, полученное вызывающим процессом при вычитании полученной в ответе отметки времени из текущей (функция <code>rtt_ts</code>). Затем применяются уравнения, приведенные в начале этого раздела, и записываются новые значения переменных <code>rtt_srtt</code>, <code>rtt_rttvar</code> и <code>rtt_rto</code>.</p>
      <p>Последняя функция, <code>rtt_timeout</code> показана в листинге 22.12. Эта функция вызывается, когда истекает время таймера повторных передач.</p>
      <p><strong>Листинг 22.12</strong>. Функция rtt_timeout: применение экспоненциального смещения</p>
      <p><code>//lib/rtt.c</code></p>
      <p><code>83 int</code></p>
      <p><code>84 rtt_timeout(struct rtt_info *ptr)</code></p>
      <p><code>85 {</code></p>
      <p><code>86  ptr-&gt;rtt_rto *= 2; /* следующее значение RTO */</code></p>
      <empty-line/>
      <p><code>87  if (++ptr-&gt;rtt_nrexmt &gt; RTT_MAXNREXMT)</code></p>
      <p><code>88   return (-1); /* закончилось время, отпущенное на попытки отправить</code></p>
      <p><code>                     этот пакет */</code></p>
      <p><code>89  return (0);</code></p>
      <p><code>90 }</code></p>
      <p><code>86</code> Текущее значение RTO удваивается — в этом и заключается экспоненциальное смещение.</p>
      <p><code>87-89</code> Если мы достигли максимально возможного количества повторных передач, возвращается значение -1, указывающее вызывающему процессу, что дальнейшие попытки передачи должны прекратиться. В противном случае возвращается 0.</p>
      <p>В нашем примере клиент соединялся дважды с двумя различными эхо-серверами в Интернете утром рабочего дня. Каждому серверу было отправлено по 500 строк. По пути к первому серверу было потеряно 8 пакетов, по пути ко второму — 16. Один из потерянных шестнадцати пакетов, предназначенных второму серверу, был потерян дважды, то есть пакет пришлось дважды передавать повторно, прежде чем был получен ответ. Все остальные потерянные пакеты пришлось передать повторно только один раз. Мы могли убедиться, что эти пакеты были действительно потеряны, посмотрев на выведенные порядковые номера каждого из полученных пакетов. Если пакет лишь опоздал, но не был потерян, после повторной передачи клиент получает два ответа: соответствующий запоздавшему первому пакету и повторно переданному. Обратите внимание, что у нас нет возможности определить, что именно было потеряно (и привело к необходимости повторной передачи клиентского запроса) — сам клиентский запрос или же ответ сервера, высланный после получения такого запроса.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Для первого издания этой книги автор написал для проверки этого клиента сервер UDP, который случайным образом игнорировал пакеты. Теперь он не используется. Нужно только соединить клиент с сервером через Интернет, и тогда нам почти гарантирована потеря некоторых пакетов!</p>
      </cite>
     </section>
    </section>
    <section>
     <title>
      <p>22.6. Связывание с адресами интерфейсов</p>
     </title>
     <p>Одно из типичных применений функции <code>get_ifi_info</code> связано с приложениями UDP, которым нужно выполнять мониторинг всех интерфейсов на узле, чтобы знать, когда и на какой интерфейс приходит дейтаграмма. Это позволяет получающей программе узнавать адрес получателя дейтаграммы UDP, так как именно по этому адресу определяется сокет, на который доставляется дейтаграмма, даже если узел не поддерживает параметр сокета <code>IP_RECVDSTADDR</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Вспомните наше обсуждение в конце раздела 22.2. Если узел использует более распространенную модель системы с гибкой привязкой (см. раздел 8.8), IP-адрес получателя может отличаться от IP-адреса принимающего интерфейса. В этом случае мы можем определить только адрес получателя дейтаграммы, который не обязательно должен быть адресом, присвоенным принимающему интерфейсу. Чтобы определить принимающий интерфейс, требуется параметр сокета IP_RECVIF или IPV6_PKTINFO.</p>
     </cite>
     <p>В листинге 22.13 показана первая часть примера применения этой технологии к эхо-серверу UDP, который связывается со всеми адресами направленной передачи, широковещательной передачи и, наконец, с универсальными адресами.</p>
     <p><strong>Листинг 22.13</strong>. Первая часть сервера UDP, который с помощью функции bind связывается со всеми адресами</p>
     <p><code>//advio/udpserv03.c</code></p>
     <p><code> 1 #include "unpifi.h"</code></p>
     <empty-line/>
     <p><code> 2 void mydg_echo(int, SA*, socklen_t, SA*);</code></p>
     <empty-line/>
     <p><code> 3 int</code></p>
     <p><code> 4 main(int argc, char **argv)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  int sockfd;</code></p>
     <p><code> 7  const int on = 1;</code></p>
     <p><code> 8  pid_t pid;</code></p>
     <p><code> 9  struct ifi_info *ifi, *ifihead;</code></p>
     <p><code>10  struct sockaddr_in *sa, cliaddr, wildaddr;</code></p>
     <empty-line/>
     <p><code>11  for (ifihead = ifi = Get_ifi_info(AF_INET, 1);</code></p>
     <p><code>12   ifi != NULL; ifi = ifi-&gt;ifi_next) {</code></p>
     <empty-line/>
     <p><code>13   /* связываем направленный адрес */</code></p>
     <p><code>14   sockfd = Socket(AF_INET, SOCK_DGRAM, 0);</code></p>
     <empty-line/>
     <p><code>15   Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
     <empty-line/>
     <p><code>16   sa = (struct sockaddr_in*)ifi-&gt;ifi_addr;</code></p>
     <p><code>17   sa-&gt;sin_family = AF_INET;</code></p>
     <p><code>18   sa-&gt;sin_port = htons(SERV_PORT);</code></p>
     <p><code>19   Bind(sockfd, (SA*)sa, sizeof(*sa));</code></p>
     <p><code>20   printf("bound %s\n", Sock_ntop((SA*)sa, sizeof(*sa)));</code></p>
     <empty-line/>
     <p><code>21   if ((pid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>22    mydg_echo(sockfd, (SA*)&amp;cliaddr, sizeof(cliaddr), (SA*)sa);</code></p>
     <p><code>23    exit(0); /* не выполняется */</code></p>
     <p><code>24   }</code></p>
     <subtitle>Вызов функции get_ifi_info для получения информации об интерфейсе</subtitle>
     <p><code>11-12</code> Функция <code>get_ifi_info</code> получает все адреса IPv4, включая дополнительные (псевдонимы), для всех интерфейсов. Затем программа перебирает все структуры <code>ifi_info</code>.</p>
     <subtitle>Создание сокета UDP и связывание адреса направленной передачи</subtitle>
     <p><code>13-20</code> Создается сокет UDP, и с ним связывается адрес направленной передачи. Мы также устанавливаем параметр сокета <code>SO_REUSEADDR</code>, поскольку мы связываем один и тот же порт (параметр <code>SERV_PORT</code>) для всех IP-адресов.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Не все реализации требуют, чтобы был установлен этот параметр сокета. Например, Беркли-реализации не требуют этого параметра и позволяют с помощью функции bind связать уже связанный порт, если новый связываемый IP-адрес не является универсальным адресом и отличается от всех IP-адресов, уже связанных с портом. Однако Solaris 2.5 для успешного связывания с одним и тем же портом второго адреса направленной передачи требует установки этого параметра.</p>
     </cite>
     <subtitle>Порождение дочернего процесса для данного адреса</subtitle>
     <p><code>21-24</code> Вызывается функция <code>fork</code>, порождающая дочерний процесс. В этом дочернем процессе вызывается функция <code>mydg_echo</code>, которая ждет прибытия любой дейтаграммы на сокет и отсылает ее обратно отправителю.</p>
     <p>В листинге 22.14 показана следующая часть функции <code>main</code>, которая обрабатывает широковещательные адреса.</p>
     <p><strong>Листинг 22.14</strong>. Вторая часть сервера UDP, который с помощью функции bind связывается со всеми адресами</p>
     <p><code>//advio/udpserv03.c</code></p>
     <p><code>25   if (ifi-&gt;ifi_flags &amp; IFF_BROADCAST) {</code></p>
     <p><code>26    /* пытаемся связать широковещательный адрес */</code></p>
     <p><code>27    sockfd = Socket(AF_INET, SOCK_DGRAM, 0);</code></p>
     <p><code>28    Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
     <empty-line/>
     <p><code>29    sa = (struct sockaddr_in*)ifi-&gt;ifi_brdaddr;</code></p>
     <p><code>30    sa-&gt;sin_family = AF_INET;</code></p>
     <p><code>31    sa-&gt;sin_port = htons(SERV_PORT);</code></p>
     <p><code>32    if (bind(sockfd, (SA*)sa, sizeof(*sa)) &lt; 0) {</code></p>
     <p><code>33     if (errno == EADDRINUSE) {</code></p>
     <p><code>34      printf("EADDRINUSE: %s\n",</code></p>
     <p><code>35       Sock_ntop((SA*)sa, sizeof(*sa)));</code></p>
     <p><code>36      Close(sockfd);</code></p>
     <p><code>37      continue;</code></p>
     <p><code>38     } else</code></p>
     <p><code>39      err_sys("bind error for %s",</code></p>
     <p><code>40       Sock_ntop((SA*)sa, sizeof(*sa)));</code></p>
     <p><code>41    }</code></p>
     <p><code>42    printf("bound %s\n", Sock_ntop((SA*)sa, sizeof(*sa)));</code></p>
     <empty-line/>
     <p><code>43    if ((pid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>44     mydg_echo(sockfd, (SA*)&amp;cliaddr, sizeof(cliaddr),</code></p>
     <p><code>45      (SA*)sa);</code></p>
     <p><code>46     exit(0); /* не выполняется */</code></p>
     <p><code>47    }</code></p>
     <p><code>48   }</code></p>
     <p><code>49  }</code></p>
     <subtitle>Связывание с широковещательными адресами</subtitle>
     <p><code>25-42</code> Если интерфейс поддерживает широковещательную передачу, создается сокет UDP и с ним связывается широковещательный адрес. На этот раз мы позволим функции <code>bind</code> завершиться с ошибкой <code>EADDRINUSE</code>, поскольку если у интерфейса имеется несколько дополнительных адресов (псевдонимов) в одной подсети, то каждый из различных адресов направленной передачи будет иметь один и тот же широковещательный адрес. Подобный пример приведен после листинга 17.3. В этом сценарии мы предполагаем, что успешно выполнится только первая функция <code>bind</code>.</p>
     <subtitle>Порождение дочернего процесса</subtitle>
     <p><code>43-47</code> Порождается дочерний процесс, и он вызывает функцию <code>mydg_echo</code>.</p>
     <p>Заключительная часть функции <code>main</code> показана в листинге 22.15. В этом коде при помощи функции <code>bind</code> происходит связывание с универсальным адресом для обработки любого адреса получателя, отличного от адресов направленной и широковещательной передачи, которые уже связаны. На этот сокет будут приходить только дейтаграммы, предназначенные для ограниченного широковещательного адреса (255.255.255.255).</p>
     <p><strong>Листинг 22.15</strong>. Заключительная часть сервера UDP, связывающегося со всеми адресами</p>
     <p><code>//advio/udpserv03.c</code></p>
     <p><code>50  /* связываем универсальный адрес */</code></p>
     <p><code>51  sockfd = Socket(AF_INET, SOCK_DGRAM, 0);</code></p>
     <p><code>52  Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
     <empty-line/>
     <p><code>53  bzero(&amp;wildaddr, sizeof(wildaddr));</code></p>
     <p><code>54  wildaddr.sin_family = AF_INET;</code></p>
     <p><code>55  wildaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>56  wildaddr.sin_port = htons(SERV_PORT);</code></p>
     <p><code>57  Bind(sockfd, (SA*)&amp;wildaddr, sizeof(wildaddr));</code></p>
     <p><code>58  printf("bound %s\n", Sock_ntop((SA*)&amp;wildaddr, sizeof(wildaddr)));</code></p>
     <empty-line/>
     <p><code>59  if ((pid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>60   mydg_echo(sockfd, (SA*)&amp;cliaddr, sizeof(cliaddr), (SA*)sa);</code></p>
     <p><code>61   exit(0); /* не выполняется */</code></p>
     <p><code>62  }</code></p>
     <p><code>63  exit(0);</code></p>
     <p><code>64 }</code></p>
     <subtitle>Создание сокета и связывание с универсальным адресом</subtitle>
     <p><code>50-62</code> Создается сокет UDP, устанавливается параметр сокета <code>SO_REUSEADDR</code> и происходит связывание с универсальным IP-адресом. Порождается дочерний процесс, вызывающий функцию <code>mydg_echo</code>.</p>
     <subtitle>Завершение работы функции main</subtitle>
     <p><code>63</code> Функция <code>main</code> завершается, и сервер продолжает выполнять работу, как и все порожденные дочерние процессы.</p>
     <p>Функция <code>mydg_echo</code>, которая выполняется всеми дочерними процессами, показана в листинге 22.16.</p>
     <p><strong>Листинг 22.16</strong>. Функция mydg_echo</p>
     <p><code>//advio/udpserv03.c</code></p>
     <p><code>65 void</code></p>
     <p><code>66 mydg_echo(int sockfd, SA *pcliaddr, socklen_t clilen, SA *myaddr)</code></p>
     <p><code>67 {</code></p>
     <p><code>68  int n;</code></p>
     <p><code>69  char mesg[MAXLINE];</code></p>
     <p><code>70  socklen_t len;</code></p>
     <empty-line/>
     <p><code>71  for (;;) {</code></p>
     <p><code>72   len = clilen;</code></p>
     <p><code>73   n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &amp;len);</code></p>
     <p><code>74   printf("child %d, datagram from %s", getpid(),</code></p>
     <p><code>75   Sock_ntop(pcliaddr, len));</code></p>
     <p><code>76   printf(", to %s\n", Sock_ntop(myaddr, clilen));</code></p>
     <empty-line/>
     <p><code>77   Sendto(sockfd, mesg, n, 0, pcliaddr, len);</code></p>
     <p><code>78  }</code></p>
     <p><code>79 }</code></p>
     <subtitle>Новый аргумент</subtitle>
     <p><code>65-66</code> Четвертым аргументом этой функции является IP-адрес, связанный с сокетом. Этот сокет должен получать только дейтаграммы, предназначенные для данного IP-адреса. Если IP-адрес является универсальным, сокет должен получать только те дейтаграммы, которые не подходят ни для какого другого сокета, связанного с тем же портом.</p>
     <subtitle>Чтение дейтаграммы и отражение ответа</subtitle>
     <p><code>71-78</code> Дейтаграмма читается с помощью функции <code>recvfrom</code> и отправляется клиенту обратно с помощью функции <code>sendto</code>. Эта функция также выводит IP-адрес клиента и IP-адрес, который был связан с сокетом.</p>
     <p>Запустим эту программу на нашем узле <code>solaris</code> после установки псевдонима для интерфейса <code>hme0</code> Ethernet. Адрес псевдонима: узел 200 в сети 10.0.0/24.</p>
     <p><code>solaris % <strong>udpserv03</strong></code></p>
     <p><code>bound 127.0.0.1:9877     <emphasis>интерфейс закольцовки</emphasis></code></p>
     <p><code>bound 10.0.0.200:9877    <emphasis>направленный адрес интерфейса hme0:1</emphasis></code></p>
     <p><code>bound 10.0.0.255:9877    <emphasis>широковещательный адрес интерфейса hme0:1</emphasis></code></p>
     <p><code>bound 192.168.1.20:9877  <emphasis>направленный адрес интерфейса hme0</emphasis></code></p>
     <p><code>bound 192.168.1.255:9877 <emphasis>широковещательный адрес интерфейса hme0</emphasis></code></p>
     <p><code>bound 0.0.0.0.9877       <emphasis>универсальный адрес</emphasis></code></p>
     <p>При помощи утилиты <code>netstat</code> мы можем проверить, что все сокеты связаны с указанными IP-адресами и портом:</p>
     <p><code>solaris % <strong>netstat -na | grep 9877</strong></code></p>
     <p><code>127.0.0.1.9877       Idle</code></p>
     <p><code>10.0.0.200.9877      Idle</code></p>
     <p><code>    *.9877           Idle</code></p>
     <p><code>192.129.100.100.9877 Idle</code></p>
     <p><code>    *.9877           Idle</code></p>
     <p><code>    *.9877           Idle</code></p>
     <p>Следует отметить, что для простоты мы создаем по одному дочернему процессу на сокет, хотя возможны другие варианты. Например, чтобы ограничить число процессов, программа может управлять всеми дескрипторами сама, используя функцию <code>select</code> и не вызывая функцию <code>fork</code>. Проблема в данном случае будет заключаться в усложнении кода. Хотя использовать функцию <code>select</code> для всех дескрипторов несложно, нам придется осуществить некоторое сопоставление каждого дескриптора связанному с ним IP-адресу (вероятно, с помощью массива структур), чтобы иметь возможность вывести IP-адрес получателя после того, как на определенном сокете получена дейтаграмма. Часто бывает проще использовать отдельный процесс или поток для каждой операции или дескриптора вместо мультиплексирования множества различных операций или дескрипторов одним процессом.</p>
    </section>
    <section>
     <title>
      <p>22.7. Параллельные серверы UDP</p>
     </title>
     <p>Большинство серверов UDP являются последовательными (iterative): сервер ждет запрос клиента, считывает запрос, обрабатывает его, отправляет обратно ответ и затем ждет следующий клиентский запрос. Но когда обработка запроса клиента занимает длительное время, желательно так или иначе совместить во времени обработку различных запросов.</p>
     <p>Определение «длительное время» означает, что другой клиент вынужден ждать в течение некоторого заметного для него промежутка времени, пока обслуживается текущий клиент. Например, если два клиентских запроса приходят в течение 10 мс и предоставление сервиса каждому клиенту занимает в среднем 5 с, то второй клиент будет вынужден ждать ответа около 10 с вместо 5 с (если бы запрос был принят в обработку сразу же по прибытии).</p>
     <p>В случае TCP проблема решается просто — требуется лишь породить дочерний процесс с помощью функции <code>fork</code> (или создать новый поток, что мы увидим в главе 23) и дать возможность дочернему процессу выполнять обработку нового клиента. При использовании TCP ситуация существенно упрощается за счет того, что каждое клиентское соединение уникально: пара сокетов TCP уникальна для каждого соединения. Но в случае с UDP мы вынуждены рассматривать два различных типа серверов.</p>
     <p>1. Первый тип — простой сервер UDP, который читает клиентский запрос, посылает ответ и затем завершает работу с клиентом. В этом сценарии сервер, читающий запрос клиента, может с помощью функции <code>fork</code> породить дочерний процесс и дать ему возможность обработать запрос. «Запрос», то есть содержимое дейтаграммы и структура адреса сокета, содержащая адрес протокола клиента, передаются дочернему процессу в виде копии содержимого области памяти из функции <code>fork</code>. Затем дочерний процесс посылает свой ответ непосредственно клиенту.</p>
     <p>2. Второй тип — сервер UDP, обменивающийся множеством дейтаграмм с клиентом. Проблема здесь в том, что единственный номер порта сервера, известный клиенту, — это номер заранее известного порта. Клиент посылает первую дейтаграмму своего запроса на этот порт, но как сервер сможет отличить последующие дейтаграммы этого клиента от запросов новых клиентов? Типичным решением этой проблемы для сервера будет создание нового сокета для каждого клиента, связывание при помощи функции <code>bind</code> динамически назначаемого порта с этим сокетом и использование этого сокета для всех своих ответов. При этом требуется, чтобы клиент запомнил номер порта, с которого был отправлен первый ответ сервера, и отправлял последующие дейтаграммы уже на этот порт.</p>
     <p>Примером второго типа сервера UDP является сервер TFTP (Trivial File Transfer Protocol — упрощенный протокол передачи файлов). Передача файла с помощью TFTP обычно требует большого числа дейтаграмм (сотен или тысяч, в зависимости от размера файла), поскольку этот протокол отправляет в одной дейтаграмме только 512 байт. Клиент отправляет дейтаграмму на известный порт сервера (69), указывая, какой файл нужно отправить или получить. Сервер читает запрос, но отправляет ответ с другого сокета, который он создает и связывает с динамически назначаемым портом. Все последующие дейтаграммы между клиентом и сервером используют для передачи этого файла новый сокет. Это позволяет главному серверу TFTP продолжать обработку других клиентских запросов, приходящих на порт 69, в то время как происходит передача файла (возможно, в течение нескольких секунд или даже минут).</p>
     <p>Если мы рассмотрим автономный сервер TFTP (то есть случай, когда не используется демон <code>inetd</code>), то получим сценарий, показанный на рис. 22.3. Мы считаем, что динамически назначаемый порт, связанный дочерним процессом с его новым сокетом, — это порт 2134.</p>
     <image l:href="#img_126.png"/>
     <p><strong>Рис. 22.3</strong>. Процессы, происходящие на автономном параллельном UDP-сервере</p>
     <p>Если используется демон <code>inetd</code>, сценарий включает еще один шаг. Вспомните из табл. 13.4, что большинство серверов UDP задают аргумент <code>wait-flag</code> как <code>wait</code>. В описании, которое следовало за рис. 13.4, мы сказали, что при указанном значении этого флага демон <code>inetd</code> приостанавливает выполнение функции <code>select</code> на сокете до завершения дочернего процесса, давая возможность этому дочернему процессу считать дейтаграмму, доставленную на сокет. На рис. 22.4 показаны все шаги.</p>
     <image l:href="#img_127.png"/>
     <p><strong>Рис. 22.4</strong>. Параллельный сервер UDP, запущенный демоном inetd</p>
     <p>Сервер TFTP, являясь дочерним процессом функции <code>inetd</code>, вызывает функцию <code>recvfrom</code> и считывает клиентский запрос. Затем он с помощью функции <code>fork</code> порождает собственный дочерний процесс, и этот дочерний процесс будет обрабатывать клиентский запрос. Затем сервер TFTP вызывает функцию <code>exit</code>, отправляя демону <code>inetd</code> сигнал <code>SIGCHLD</code>, который, как мы сказали, указывает демону <code>inetd</code> снова вызвать функцию <code>select</code> на сокете, связанном с портом UDP 69.</p>
    </section>
    <section>
     <title>
      <p>22.8. Информация о пакетах IPv6</p>
     </title>
     <section>
      <p>IPv6 позволяет приложению определять до пяти характеристик исходящей дейтаграммы:</p>
      <p>&#9632; IPv6-адрес отправителя;</p>
      <p>&#9632; индекс интерфейса для исходящих дейтаграмм;</p>
      <p>&#9632; предельное количество транзитных узлов для исходящих дейтаграмм;</p>
      <p>&#9632; адрес следующего транзитного узла;</p>
      <p>&#9632; класс исходящего трафика.</p>
      <p>Эта информация отправляется в виде вспомогательных данных с функцией <code>sendmsg</code>. Для сокета можно задать постоянные параметры, которые будут действовать на все отправляемые пакеты (раздел 27.7).</p>
      <p>Для полученного пакета могут быть возвращены четыре аналогичных характеристики. Они возвращаются в виде вспомогательных данных с функцией <code>recvmsg</code>:</p>
      <p>&#9632; IPv6-адрес получателя;</p>
      <p>&#9632; индекс интерфейса для входящих дейтаграмм;</p>
      <p>&#9632; предельное количество транзитных узлов для входящих дейтаграмм.</p>
      <p>&#9632; класс входящего трафика.</p>
      <p>На рис. 22.5 показано содержимое вспомогательных данных, о которых рассказывается далее.</p>
      <image l:href="#img_128.png"/>
      <p><strong>Рис. 22.5</strong>. Вспомогательные данные для информации о пакете IPv6</p>
      <p>Структура <code>in6_pktinfo</code> содержит либо IPv6-адрес отправителя и индекс интерфейса для исходящей дейтаграммы, либо IPv6-адрес получателя и индекс интерфейса для получаемой дейтаграммы:</p>
      <p><code>struct in6_pktinfo {</code></p>
      <p><code> struct in6_addr ipi6_addr; /* IPv6-адрес отправителя/получателя */</code></p>
      <p><code> int ipi6_ifindex; /* индекс интерфейса для исходящей/получаемой дейтаграммы */</code></p>
      <p><code>};</code></p>
      <p>Эта структура определяется в заголовочном файле <code>&lt;netinet/in.h&gt;</code>, подключение которого позволяет ее использовать. В структуре <code>cmsghdr</code>, содержащей вспомогательные данные, элемент <code>cmsg_level</code> будет иметь значение <code>IPPROTO_IPV6</code>, элемент <code>cmsg_type</code> будет равен <code>IPV6_PKTINFO</code> и первый байт данных будет первым байтом структуры <code>in6_pktinfo</code>. В примере, приведенном на рис. 22.5, мы считаем, что между структурой <code>cmsghdr</code> и данными нет заполнения и целое число занимает 4 байта.</p>
      <p>Чтобы отправить эту информацию, никаких специальных действий не требуется — нужно только задать управляющую информацию во вспомогательных данных функции <code>sendmsg</code>. Чтобы информация добавлялась ко всем отправляемым через сокет пакетам, необходимо установить параметр сокета <code>IPV6_PKTINFO</code> со значением <code>in6_pktinfo</code>. Возвращать эту информацию функция <code>recvmsg</code> будет, только если приложение включит параметр сокета <code>IPV6_RECVPKTINFO</code>.</p>
     </section>
     <section>
      <title>
       <p>Исходящий и входящий интерфейсы</p>
      </title>
      <p>Интерфейсы на узле IPv6 идентифицируются небольшими целыми положительными числами, как мы сказали в разделе 18.6. Вспомните, что ни одному интерфейсу не может быть присвоен нулевой индекс. При задании исходящего интерфейса ядро само выберет исходящий интерфейс, если значение <code>ipi6_ifindex</code> нулевое. Если приложение задает исходящий интерфейс для пакета многоадресной передачи, то любой интерфейс, заданный параметром сокета <code>IPV6_MULTICAST_IF</code>, заменяется на интерфейс, заданный вспомогательными данными (но только для данной дейтаграммы).</p>
     </section>
     <section>
      <title>
       <p>Адрес отправителя и адрес получателя IPv6</p>
      </title>
      <p>IPv6-адрес отправителя обычно определяется при помощи функции <code>bind</code>. Но если адрес отправителя поставляется вместе с данными, это может снизить непроизводительные затраты. Этот параметр также позволяет серверу гарантировать, что адрес отправителя ответа совпадает с адресом получателя клиентского запроса — некоторым клиентам требуется такое условие, которое сложно выполнить в случае IPv4 (см. упражнение 22.4).</p>
      <p>Когда IPv6-адрес отправителя задан в качестве вспомогательных данных и элемент <code>ipi6_addr</code> структуры <code>in6_pktinfo</code> имеет значение <code>IN6ADDR_ANY_INIT</code>, возможны следующие сценарии: если адрес в настоящий момент связан с сокетом, он используется в качестве адреса отправителя; если в настоящий момент никакой адрес не связан с сокетом, ядро выбирает адрес отправителя. Если же элемент <code>ipi6_addr</code> не является неопределенным адресом, но сокет уже связался с адресом отправителя, то значением элемента <code>ipi6_addr</code> перекрывается уже связанный адрес, но только для данной операции вывода. Затем ядро проверяет, действительно ли запрашиваемый адрес отправителя является адресом направленной передачи, присвоенным узлу.</p>
      <p>Когда структура in6_<code>pktinfo</code> возвращается в качестве вспомогательных данных функцией <code>recvmsg</code>, элемент <code>ipi6_addr</code> содержит IPv6-адрес получателя из полученного пакета. По сути, это аналог параметра сокета <code>IP_RECVDSTADDR</code> для IPv4.</p>
     </section>
     <section>
      <title>
       <p>Задание и получение предельного количества транзитных узлов</p>
      </title>
      <p>Предельное количество транзитных узлов обычно задается параметром сокета <code>IPV6_UNICAST_HOPS</code> для дейтаграмм направленной передачи (см. раздел 7.8) или параметром сокета <code>IPV6_MULTICAST_HOPS</code> для дейтаграмм многоадресной передачи (см. раздел 21.6). Задавая предельное количество транзитных узлов в составе вспомогательных данных, мы можем заменить как значение этого предела, задаваемое ядром по умолчанию, так и ранее заданное значение — и для направленной, и для многоадресной передачи, но только для одной операции вывода. Предел количества транзитных узлов полученного пакета используется в таких программах, как <code>traceroute</code>, и в некоторых приложениях IPv6, которым нужно проверять, что полученное значение равно 255 (то есть что пакет не пересылался маршрутизаторами).</p>
      <p>Полученное предельное количество транзитных узлов возвращается в виде вспомогательных данных функцией <code>recvmsg</code>, только если приложение включает параметр сокета <code>IPV6_RECVHOPLIMIT</code>. В структуре <code>cmsghdr</code>, содержащей эти вспомогательные данные, элемент <code>cmsg_level</code> будет иметь значение <code>IPPROTO_IPV6</code>, элемент <code>cmsg_type</code> — значение <code>IPV6_HOPLIMIT</code>, а первый байт данных будет первым байтом целочисленного предела повторных передач. Мы показали это на рис. 22.5. Нужно понимать, что значение, возвращаемое в качестве вспомогательных данных, — это действительное значение из полученной дейтаграммы, в то время как значение, возвращаемое функцией <code>getsockopt</code> с параметром <code>IPV6_UNICAST_HOPS</code>, является значением по умолчанию, которое ядро будет использовать для исходящих дейтаграмм на сокете.</p>
      <p>Чтобы задать предельное количество транзитных узлов для исходящих пакетов, никаких специальных действий не требуется — нам нужно только указать управляющую информацию в виде вспомогательных данных для функции <code>sendmsg</code>. Обычные значения для предельного количества транзитных узлов лежат в диапазоне от 0 до 255 включительно, но если целочисленное значение равно -1, это указывает ядру, что следует использовать значение по умолчанию.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Предельное количество транзитных узлов не содержится в структуре in6_pktinfo — некоторые серверы UDP хотят отвечать на запросы клиентов, посылая ответы на том же интерфейсе, на котором был получен запрос, с совпадением IPv6-адреса отправителя ответа и IPv6-адреса получателя запроса. Для этого приложение может включить параметр сокета IPV6_RECVPKTINFO, а затем использовать полученную управляющую информацию из функции recvmsg в качестве управляющей информации для функции sendmsg при отправке ответа. Приложению вообще никак не нужно проверять или изменять структуру in6_pktinfo. Но если в этой структуре содержался бы предел количества транзитных узлов, приложение должно было бы проанализировать полученную управляющую информацию и изменить значение этого предела, поскольку полученный предел не является желательным значением для исходящего пакета.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Задание адреса следующего транзитного узла</p>
      </title>
      <p>Объект вспомогательных данных <code>IPV6_NEXTHOP</code> задает адрес следующего транзитного узла дейтаграммы в виде структуры адреса сокета. В структуре <code>cmsghdr</code>, содержащей эти вспомогательные данные, элемент <code>cmsg_level</code> будет иметь значение <code>IPPROTO_IPV6</code>, элемент <code>cmsg_type</code> — значение <code>IPV6_NEXTHOP</code>, а первый байт данных будет первым байтом структуры адреса сокета.</p>
      <p>На рис. 22.5 мы показали пример такого объекта вспомогательных данных, считая, что структура адреса сокета — это 24-байтовая структура <code>sockaddr_in6</code>. В этом случае узел, идентифицируемый данным адресом, должен быть соседним для отправляющего узла. Если этот адрес совпадает с адресом получателя IPv6-дейтаграммы, мы получаем эквивалент параметра сокета <code>SO_DONTROUTE</code>. Установка этого параметра требует прав привилегированного пользователя. Адрес следующего транзитного узла можно устанавливать для всех пакетов на сокете, если включить параметр сокета <code>IPV6_NEXTHOP</code> со значением <code>sockaddr_in6</code> (раздел 27.7). Для этого необходимо обладать правами привилегированного пользователя.</p>
     </section>
     <section>
      <title>
       <p>Задание и получение класса трафика</p>
      </title>
      <p>Объект вспомогательных данных <code>IPV6_TCLASS</code> задает класс трафика для дейтаграммы. Элемент <code>cmsg_level</code> структуры <code>cmsghdr</code>, содержащей эти данные, будет равен <code>IPPROTO_IPV6</code>, элемент <code>cmsg_type</code> будет равен <code>IPV6_TCLASS</code>, а первый байт данных будет первым байтом целочисленного (4-байтового) значения класса трафика (см. рис. 22.5). Согласно разделу А.3, класс трафика состоит из полей <code>DSCP</code> и <code>ECN</code>. Эти поля должны устанавливаться одновременно. Ядро может маскировать или игнорировать указанное пользователем значение, если ему это нужно (например, если ядро реализует <code>ECN</code>, оно может установить биты <code>ECN</code> равными какому-либо значению, игнорируя два бита, указанных с параметром <code>IPV6_TCLASS</code>). Класс трафика обычно лежит в диапазоне 0–255. Значение -1 говорит ядру о необходимости использовать значение по умолчанию.</p>
      <p>Чтобы задать класс трафика для пакета, нужно отправить вспомогательные данные вместе с этим пакетом. Чтобы задать класс трафика для всех пакетов, отправляемых через сокет, необходимо использовать параметр сокета <code>IPV6_TCLASS</code> (раздел 27.7). Класс трафика для принятого пакета возвращается функцией <code>recvmsg</code> во вспомогательных данных, только если приложение включило параметр сокета <code>IPV6_RECVTCLASS</code>.</p>
     </section>
    </section>
    <section>
     <title>
      <p>22.9. Управление транспортной MTU IPv6</p>
     </title>
     <section>
      <p>IPv6 предоставляет приложениям средства для управления механизмом обнаружения транспортной MTU (раздел 2.11). Значения по умолчанию пригодны для подавляющего большинства приложений, однако специальные программы могут настраивать процедуру обнаружения транспортной MTU так, как им нужно. Для этого имеется четыре параметра сокета.</p>
     </section>
     <section>
      <title>
       <p>Отправка с минимальной MTU</p>
      </title>
      <p>При работе в режиме детектирования транспортной MTU пакеты фрагментируются по MTU исходящего интерфейса или по транспортной MTU в зависимости от того, какое значение оказывается меньше. IPv6 требует минимального значения MTU 1280 байт. Это значение должно поддерживаться любой линией передачи. Фрагментация сообщений по этому минимальному значению позволяет не тратить ресурсы на обнаружение транспортной MTU (потерянные пакеты и задержки в процессе обнаружения), но зато не дает возможности отправлять большие пакеты (что более эффективно).</p>
      <p>Минимальная MTU может использоваться приложениями двух типов. Во- первых, это приложения многоадресной передачи, которым нужно избегать порождения множества ICMP-сообщений «Message too big». Во-вторых, это приложения, выполняющие небольшие по объему транзакции с большим количеством адресатов (например, DNS). Обнаружение MTU для многоадресного сеанса может быть недостаточно выгодным, чтобы компенсировать затраты на получение и обработку миллионов ICMP-сообщений, а приложения типа DNS обычно связываются с серверами недостаточно часто, чтобы можно было рисковать утратой пакетов.</p>
      <p>Использование минимальной MTU обеспечивается параметром сокета <code>IPV6_USE_MIN_MTU</code>. Для него определено три значения: -1 (по умолчанию) соответствует использованию минимальной MTU для многоадресных передач и обнаруженной транспортной MTU для направленных передач; 0 соответствует обнаружению транспортной MTU для всех передач; 1 означает использование минимальной MTU для всех адресатов.</p>
      <p>Параметр <code>IPV6_USE_MIN_MTU</code> может быть передан и во вспомогательных данных. В этом случае элемент <code>cmsg_level</code> структуры <code>cmsghdr</code> должен иметь значение <code>IPPROTO_IPV6</code>, элемент <code>cmsg_type</code> должен иметь значение <code>IPV6_USE_MIN_MTU</code>, а первый байт данных должен быть первым байтом четырехбайтового целочисленного значения параметра.</p>
     </section>
     <section>
      <title>
       <p>Получение сообщений об изменении транспортной MTU</p>
      </title>
      <p>Для получения уведомлений об изменении транспортной MTU приложение может включить параметр сокета <code>IPV6_RECVPATHMTU</code>. Этот флаг разрешает доставку транспортной MTU во вспомогательных данных каждый раз, когда эта величина меняется. Функция recvmsg в этом случае возвратит дейтаграмму нулевой длины, но со вспомогательными данными, в которых будет помещена транспортная MTU. Элемент <code>cmsg_level</code> структуры <code>cmsghdr</code> будет иметь значение <code>IPPROTO_IPV6</code>, элемент <code>cmsg_type</code> будет <code>IPV6_PATHMTU</code>, а первый байт данных будет первым байтом структуры <code>iр6_mtuinfo</code>. Эта структура содержит адрес узла, для которого изменилась транспортная MTU, и новое значение этой величины в байтах.</p>
      <p><code>struct ip6_mtuinfo {</code></p>
      <p><code> struct sockaddr_in6 ip6m_addr; /* адрес узла */</code></p>
      <p><code> uint32_t            ip6m_mtu;  /* транспортная MTU</code></p>
      <p><code>                                   в порядке байтов узла */</code></p>
      <p><code>};</code></p>
      <p>Эта структура определяется включением заголовочного файла <code>&lt;netinet/in.h&gt;</code>.</p>
     </section>
     <section>
      <title>
       <p>Определение текущей транспортной MTU</p>
      </title>
      <p>Если приложение не отслеживало изменения MTU при помощи параметра <code>IPV6_RECVPATHMTU</code>, оно может определить текущее значение транспортной MTU <emphasis>присоединенного</emphasis> сокета при помощи параметра <code>IPV6_PATHMTU</code>. Этот параметр доступен только для чтения и возвращает он структуру <code>ip6_mtuinfo</code> (см. выше), в которой хранится текущее значение MTU. Если значение еще не было определено, возвращается значение MTU по умолчанию для исходящего интерфейса. Значение адреса из структуры <code>ip6_mtuinfo</code> в данном случае не определено.</p>
     </section>
     <section>
      <title>
       <p>Отключение фрагментации</p>
      </title>
      <p>По умолчанию стек IPv6 фрагментирует исходящие пакеты по транспортной MTU. Приложениям типа <code>traceroute</code> автоматическая фрагментация не нужна, потому что им нужно иметь возможность самостоятельно определять транспортную MTU. Параметр сокета <code>IPV6_DONTFRAG</code> используется для отключения автоматической фрагментации: значение 0 (по умолчанию) разрешает фрагментацию, тогда как значение 1 отключает ее.</p>
      <p>Когда автоматическая фрагментация отключена, вызов send со слишком большим пакетом может возвратить ошибку <code>EMSGSIZE</code>, но это не является обязательным. Единственным способом определить необходимость фрагментации пакета является использование параметра сокета <code>IPV6_RECVPATHMTU</code>, который мы описали выше.</p>
      <p>Параметр <code>IPV6_DONTFRAG</code> может передаваться и во вспомогательных данных. При этом элемент <code>cmsg_level</code> структуры <code>cmsghdr</code> должен иметь значение <code>IPPROTO_IPV6</code>, а элемент <code>cmsg_type</code> должен иметь значение <code>IPV6_DONTFRAG</code>. Первый байт данных должен быть первым байтом четырехбайтового целого.</p>
     </section>
    </section>
    <section>
     <title>
      <p>22.10. Резюме</p>
     </title>
     <p>Существуют приложения, которым требуется знать IP-адрес получателя дейтаграммы UDP и интерфейс, на котором была получена эта дейтаграмма. Чтобы получать эту информацию в виде вспомогательных данных для каждой дейтаграммы, можно установить параметры сокета <code>IP_RECVDSTADDR</code> и <code>IP_RFCVIF</code>. Аналогичная информация вместе с предельным значением количества транзитных узлов полученной дейтаграммы для сокетов IPv6 становится доступна при включении параметра сокета <code>IPV6_PKTINFO</code>.</p>
     <p>Несмотря на множество полезных свойств, предоставляемых протоколом TCP и отсутствующих в UDP, существуют ситуации, когда следует отдать предпочтение UDP. UDP <emphasis>должен</emphasis> использоваться для широковещательной или многоадресной передачи. UDP <emphasis>может</emphasis> использоваться в простых сценариях «запрос-ответ», но тогда приложение должно само обеспечить некоторую функциональность, повышающую надежность протокола UDP. UDP <emphasis>не следует</emphasis> использовать для передачи большого количества данных.</p>
     <p>В разделе 22.5 мы добавили нашему клиенту UDP определенные функциональные возможности, повышающие его надежность за счет обнаружения факта потери пакетов, для чего используются тайм-аут и повторная передача. Мы изменяли тайм-аут повторной передачи динамически, снабжая каждый пакет отметкой времени и отслеживая два параметра: период обращения RTT и его среднее отклонение. Мы также добавили порядковые номера, чтобы проверять, что данный ответ — это ожидаемый нами ответ на определенный запрос. Наш клиент продолжал использовать простой протокол остановки и ожидания (stop-and-wait), а приложения такого типа допускают применение UDP.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Почему в листинге 22.16 функция <code>printf</code> вызывается дважды?</p>
     <p>2. Может ли когда-нибудь функция <code>dg_send_recv</code> (см. листинги 22.6 и 22.7) возвратить нуль?</p>
     <p>3. Перепишите функцию <code>dg_send_recv</code> с использованием функции <code>select</code> и ее таймера вместо <code>alarm</code>, <code>SIGALRM</code>, <code>sigsetjmp</code> и <code>siglongjmp</code>.</p>
     <p>4. Как может сервер IPv4 гарантировать, что адрес отправителя в его ответе совпадает с адресом получателя клиентского запроса? (Аналогичную функциональность предоставляет параметр сокета <code>IPV6_PKTINFO</code>.)</p>
     <p>5. Функция <code>main</code> в разделе 22.6 является зависящей от протокола (IPv4). Перепишите ее, чтобы она стала не зависящей от протокола. Потребуйте, чтобы пользователь задал один или два аргумента командной строки, первый из которых — необязательный IP-адрес (например, 0.0.0.0 или 0::0), а второй — обязательный номер порта. Затем вызовите функцию <code>udp_client</code>, чтобы получить семейство адресов, номер порта и длину структуры адреса сокета.</p>
     <p>Что произойдет, если вы вызовете функцию <code>udp_client</code>, как было предложено, не задавая аргумент <code>hostname</code>, поскольку функция <code>udp_client</code> не задает значение <code>AI_PASSIVE</code> функции <code>getaddrinfo</code>?</p>
     <p>6. Соедините клиент, показанный в листинге 22.4, с эхо-сервером через Интернет, изменив функции <code>rtt_</code> так, чтобы выводилось каждое значение RTT. Также измените функцию <code>dg_send_recv</code>, чтобы она выводила каждый полученный порядковый номер. Изобразите на графике полученные в результате значения RTT вместе с оценочными значениями RTT и среднего отклонения.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 23</p>
     <p>Дополнительные сведения о сокетах SCTP</p>
    </title>
    <section>
     <title>
      <p>23.1. Введение</p>
     </title>
     <p>В этой главе мы займемся углубленным рассмотрением SCTP, изучим особенности этого протокола и параметры сокетов, при помощи которых он управляется. Мы обсудим некоторые специальные вопросы, в частности, управление обнаружением отказов, доставку неупорядоченных данных, а также уведомления. Мы будем щедро иллюстрировать наши утверждения примерами программ, которые помогут читателю получить представление об использовании расширенных функций SCTP.</p>
     <p>SCTP — протокол, ориентированный на передачу сообщений. Он способен доставлять сообщения конечному пользователю как целиком, так и по частям. Доставка по частям включается только в том случае, если приложение отправляет собеседнику большие сообщения (то есть такие, размер которых превышает половину размера буфера). Части разных сообщений никогда не смешиваются друг с другом. Приложение получает сообщение либо одним вызовом функции чтения, либо несколькими последовательными вызовами. Метод работы с механизмом частичной доставки мы продемонстрируем на примере вспомогательной функции.</p>
     <p>Серверы SCTP могут быть как последовательными, так и параллельными в зависимости от того, какой тип интерфейса выберет разработчик приложения. SCTP предоставляет средства извлечения ассоциации из сокета типа «один-ко-многим» в отдельный сокет типа «один-к-одному». Благодаря этому появляется возможность создания последовательно-параллельных серверов.</p>
    </section>
    <section>
     <title>
      <p>23.2. Сервер типа «один-ко-многим» с автоматическим закрытием</p>
     </title>
     <p>Вспомните программу-сервер, которую мы написали в главе 10. Эта программа не отслеживала ассоциации. Сервер рассчитывал, что клиент самостоятельно закроет ассоциацию, удалив тем самым данные о ее состоянии. Однако такой подход делает сервер уязвимым: что если клиент откроет ассоциацию, но никаких данных не пришлет? Для такого клиента будут выделены ресурсы, которые он не использует. Неудачное стечение обстоятельств может привести к DoS-атаке на нашу реализацию SCTP со стороны неактивных клиентов. Для предотвращения подобных ситуаций в SCTP была добавлена функция автоматического закрытия ассоциаций (autoclose).</p>
     <p>Автоматическое закрытие позволяет конечной точке SCTP задавать максимальную длительность бездействия ассоциации. Ассоциация считается бездействующей, если по ней не передаются никакие данные (ни в одном направлении). Если длительность бездействия превышает установленное ограничение, ассоциация автоматически закрывается реализацией SCTP.</p>
     <p>Особое внимание следует уделить выбору ограничения на время бездействия. Значение не должно быть слишком маленьким, иначе сервер может в какой-то момент обнаружить, что ему требуется передать данные по уже закрытой ассоциации. На повторное открытие ассоциации будут затрачены ресурсы, да и вообще маловероятно, что клиент будет готов принять входящую ассоциацию. В листинге 23.1<a l:href="#n1" type="note">[1]</a> приведена новая версия кода нашего сервера, в которую добавлены вызовы, защищающие этот сервер от неактивных клиентов. Как отмечалось в разделе 7.10, функция автоматического закрытия по умолчанию отключена и должна быть включена явным образом при помощи параметра сокета <code>SCTP_AUTOCLOSE</code>.</p>
     <p><strong>Листинг 23.1</strong>. Включение автоматического закрытия сокета на сервере</p>
     <p><code>//sctp/sctpserv04.c</code></p>
     <p><code>14 if (argc == 2)</code></p>
     <p><code>15  stream_increment = atoi(argv[1]);</code></p>
     <p><code>16 sock_fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);</code></p>
     <p><code>17 close_time = 120;</code></p>
     <p><code>18 Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_AUTOCLOSE,</code></p>
     <p><code>19  &amp;close_time, sizeof(close_time));</code></p>
     <empty-line/>
     <p><code>20 bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>21 servaddr.sin_family = AF_INET;</code></p>
     <p><code>22 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>23 servaddr.sin_report = htons(SERV_PORT);</code></p>
     <subtitle>Установка автоматического закрытия</subtitle>
     <p><code>17-19</code> Сервер устанавливает ограничение на простой ассоциаций равным 120 с и помещает это значение в переменную <code>close_time</code>. Затем сервер вызывает функцию <code>setsockopt</code> с параметром <code>SCTP_AUTOCLOSE</code>, устанавливающим выбранное ограничение. В остальном код сервера остается прежним.</p>
     <p>Теперь SCTP будет автоматически закрывать ассоциации, простаивающие более двух минут. Автоматическое закрытие ассоциаций уменьшает расходы ресурсов сервера на неактивных клиентов.</p>
    </section>
    <section>
     <title>
      <p>23.3. Частичная доставка</p>
     </title>
     <p>Механизм частичной доставки (partial delivery) используется стеком SCTP каждый раз, когда требуется доставить приложению большое сообщение. Сообщение считается «большим», если SCTP решает, что у него недостаточно ресурсов на его обработку. Частичная доставка накладывает на работу SCTP определенные ограничения:</p>
     <p>&#9632; объем памяти, занимаемой сообщением в буфере, должен превосходить некоторое пороговое значение;</p>
     <p>&#9632; доставка может выполняться только последовательно от начала сообщения до первого отсутствующего блока;</p>
     <p>&#9632; после включения механизма частичной доставки приложение не может получить никакие другие сообщения до тех пор, пока «большое» сообщение не будет им полностью считано из буфера. Таким образом, большое сообщение блокирует все остальные, которые в противном случае могли бы быть доставлены (в том числе и по другим потокам).</p>
     <p>В реализации SCTP, выполненной группой KAME, используется пороговое значение, равное половине объема приемного буфера сокета. На момент написания этой книги объем приемного буфера по умолчанию составляет 131 072 байта. Если параметр сокета <code>SO_RCVBUF</code> не меняется, механизм частичной доставки будет включаться только для сообщений, превышающих 65 536 байт. Мы продолжим расширение новой версии сервера из раздела 10.2, написав функцию-обертку для вызова <code>sctp_recvmsg</code>. Затем мы создадим новый сервер, который будет использовать эту функцию. В листинге 23.2 представлена функция-обертка, способная работать с механизмом частичной доставки.</p>
     <p><strong>Листинг 23.2</strong>. Работа с API частичной доставки</p>
     <p><code>//sctp/sctp_pdapirev.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 static uint8_t *sctp_pdapi_readbuf=NULL;</code></p>
     <p><code> 3 static int sctp_pdapi_rdbuf_sz=0;</code></p>
     <empty-line/>
     <p><code> 4 uint8_t*</code></p>
     <p><code> 5 pdapi_recvmsg(int sock_fd,</code></p>
     <p><code> 6  int *rdlen,</code></p>
     <p><code> 7  SA *from,</code></p>
     <p><code> 8  int *from_len, struct sctp_sndrcvinfo *sri, int *msg_flags)</code></p>
     <p><code> 9 {</code></p>
     <p><code>10  int rdsz, left, at_in_buf;</code></p>
     <p><code>11  int frmlen=0;</code></p>
     <empty-line/>
     <p><code>12  if (sctp_pdapi_readbuf == NULL) {</code></p>
     <p><code>13   sctp_pdapi_readbuf = (uint8_t*)Malloc(SCTP_PDAPI_INCR_SZ);</code></p>
     <p><code>14   sctp_pdapi_rdbuf_sz = SCTP_PDAPI_INCR_SZ;</code></p>
     <p><code>15  }</code></p>
     <p><code>16  at_in_buf = Sctp_recvmsg(sock_fd, sctp_pdapi_readbuf, sctp_pdapi_rdbuf_sz,</code></p>
     <p><code>17   from, from_len,</code></p>
     <p><code>18   sri.msg_flags);</code></p>
     <p><code>19  if (at_in_buf &lt; 1) {</code></p>
     <p><code>20   *rdlen = at_in_buf;</code></p>
     <p><code>21   return(NULL);</code></p>
     <p><code>22  }</code></p>
     <p><code>23  while ((*msg_flags &amp; MSG_EOR) == 0) {</code></p>
     <p><code>24   left = sctp_pdapi_rdbuf_sz = at_in_buf;</code></p>
     <p><code>25   if (left &lt; SCTP_PDAPI_NEED_MORE_THRESHOLD) {</code></p>
     <p><code>26    sctp_pdapi_readbuf =</code></p>
     <p><code>27     realloc(sctp_pdapi_readbuf,</code></p>
     <p><code>28      setp_pdapi_rdbuf_sz + SCTP_PDAPI_INCR_SZ);</code></p>
     <p><code>29    if (sctp_pdapi_readbuf == NULL) {</code></p>
     <p><code>30     err_quit("sctp_pdapi ran out of memory");</code></p>
     <p><code>31    }</code></p>
     <p><code>32    sctp_pdapi_rdbuf_sz += SCTP_PDAPI_INCR_SZ;</code></p>
     <p><code>33     left = sctp_pdapi_rdbuf_sz - at_in_buf;</code></p>
     <p><code>34   </code></p>
     <p><code>35   rdsz = Sctp_recvmsg(sock_fd, &amp;sctp_pdapi_readbuf[at_in_buf],</code></p>
     <p><code>36    left, NULL, &amp;frmlen, NULL, msg_flags);</code></p>
     <p><code>37   at_in_buf += rdsz;</code></p>
     <p><code>38  }</code></p>
     <p><code>39  *rdlen = at_in_buf;</code></p>
     <p><code>40  return(sctp_pdapi_readbuf);</code></p>
     <p><code>41 }</code></p>
     <subtitle>Подготовка статического буфера</subtitle>
     <p><code>12-15</code> Если статический буфер не выделен, функция выделяет его и инициализирует переменную, хранящую информацию о состоянии этого буфера.</p>
     <subtitle>Чтение сообщения</subtitle>
     <p><code>16-18</code> Первое сообщение считывается из сокета вызовом <code>sctp_recvmsg</code>.</p>
     <subtitle>Обработка ошибки чтения</subtitle>
     <p><code>19-22</code> Если <code>sctp_recvmsg</code> возвращает ошибку или признак конца файла EOF, соответствующий код возвращается вызвавшему нашу функцию процессу без всяких изменений.</p>
     <subtitle>Если сообщение считано не полностью</subtitle>
     <p><code>23-24</code> Если флаги сообщения показывают, что оно было считано не полностью, мы вызываем функцию <code>sctp_recvmsg</code> снова. Предварительно мы вычисляем объем свободного места в буфере.</p>
     <subtitle>Проверка необходимости увеличения статического буфера</subtitle>
     <p><code>25-34</code> Если остаток приемного буфера оказался меньше некоторого минимального значения, этот буфер необходимо увеличить. С этой целью мы вызываем функцию <code>realloc</code>, выделяющую буфер большего размера, после чего копируем в новый буфер данные из старого буфера. Если по какой-то причине размер буфера не может быть увеличен, функция завершает свою работу с выводом сообщения об ошибке.</p>
     <subtitle>Получение данных</subtitle>
     <p><code>35-36</code> Новые данные считываются из буфера вызовом <code>sctp_recvmsg</code>.</p>
     <subtitle>Шаг вперед</subtitle>
     <p><code>37-38</code> Функция увеличивает индекс буфера, после чего возвращается на проверку полного считывания сообщения.</p>
     <subtitle>После завершения цикла</subtitle>
     <p><code>39-40</code> После завершения цикла функция копирует количество считанных байтов в буфер, указатель на который передается ей вызвавшим процессом, и возвращает этому процессу указатель на собственный буфер.</p>
     <p>Теперь мы можем изменить сервер SCTP таким образом, чтобы он использовал нашу новую функцию. Новый код представлен в листинге 23.3.</p>
     <p><strong>Листинг 23.3</strong>. Сервер SCTP, использующий API частичной доставки</p>
     <p><code>//sctp/sctpserv05.c</code></p>
     <p><code>26 for (;;) {</code></p>
     <p><code>27  len = sizeof(struct sockaddr_in);</code></p>
     <p><code>28  bzero(&amp;sri,.sizeof(sri));</code></p>
     <p><code>29  readbuf = pdapi_recvmsg(sock_fd, &amp;rd_sz,</code></p>
     <p><code>30   (SA*)&amp;cliaddr, &amp;len, &amp;sri, &amp;msg_flags);</code></p>
     <p><code>31  if (readbuf == NULL)</code></p>
     <p><code>32   continue;</code></p>
     <subtitle>Чтение сообщения</subtitle>
     <p><code>29-30</code> Сервер вызывает новую функцию-обертку интерфейса частичной доставки. Предварительно обнуляется переменная <code>sri</code>.</p>
     <subtitle>Проверка наличия считанных данных</subtitle>
     <p><code>31-32</code> Обратите внимание, что теперь серверу приходится проверять объем буфера, чтобы убедиться, что чтение было успешным. Если буфер оказывается нулевым, программа переходит на начало цикла.</p>
    </section>
    <section>
     <title>
      <p>23.4. Уведомления</p>
     </title>
     <section>
      <p>В разделе 9.14 уже отмечалось, что приложение может подписаться на уведомления, общее количество которых составляет 7 штук. Пока что наше приложение игнорировало все события, за исключением прихода новых данных. В этом разделе приводятся примеры приема и интерпретации уведомлений SCTP о других событиях транспортного уровня. В листинге 23.4 представлена функция, отображающая все получаемые уведомления. Нам придется изменить и код сервера, чтобы разрешить доставку уведомлений обо всех происходящих событиях. Однако сервер не будет использовать получаемые уведомления для чего-либо конкретного.</p>
      <p><strong>Листинг 23.4</strong>. Функция вывода уведомлений</p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 print_notification(char *notify_buf)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  union sctp_notification *snp;</code></p>
      <p><code> 6  struct sctp_assoc_change *sac;</code></p>
      <p><code> 7  struct sctp_paddr_change *spc;</code></p>
      <p><code> 8  struct sctp_remote_error *sre;</code></p>
      <p><code> 9  struct sctp_send_failed *ssf;</code></p>
      <p><code>10  struct sctp_shutdown_event *sse;</code></p>
      <p><code>11  struct sctp_adaption_event *ae;</code></p>
      <p><code>12  struct sctp_pdapi_event *pdapi,</code></p>
      <p><code>13  const char *str;</code></p>
      <empty-line/>
      <p><code>14  snp = (union sctp_notification*)notify_buf;</code></p>
      <p><code>15  switch (snp-&gt;sn_header.sn_type) {</code></p>
      <p><code>16  case SCTP_ASSOC_CHANGE:</code></p>
      <p><code>17   sac = &amp;snp-&gt;sn_assoc_change;</code></p>
      <p><code>18   switch (sac-&gt;sac_state) {</code></p>
      <p><code>19    case SCTP_COMM_UP:</code></p>
      <p><code>20    str = "COMMUNICATION UP";</code></p>
      <p><code>21    break;</code></p>
      <p><code>22   case SCTP_COMM_LOST:</code></p>
      <p><code>23    str = "COMMUNICATION LOST";</code></p>
      <p><code>24    break;</code></p>
      <p><code>25   case SCTP_RESTART:</code></p>
      <p><code>26    str = "RESTART";</code></p>
      <p><code>27    break;</code></p>
      <p><code>28   case SCTP_SHUTDOWN_COMP:</code></p>
      <p><code>29    str = "SHUTDOWN COMPLETE";</code></p>
      <p><code>30    break;</code></p>
      <p><code>31   case SCTP_CANT_STR_ASSOC:</code></p>
      <p><code>32    str = "CAN'T START ASSOC";</code></p>
      <p><code>33    break;</code></p>
      <p><code>34   default:</code></p>
      <p><code>35    str = "UNKNOWN";</code></p>
      <p><code>36    break;</code></p>
      <p><code>37   } /* конец ветвления switch (sac-&gt;sac_state) */</code></p>
      <p><code>38   printf("SCTP_ASSOC_CHANGE %s, assoc=0x%x\n", str,</code></p>
      <p><code>39    (uint32_t)sac-&gt;sac_assoc_id);</code></p>
      <p><code>40   break;</code></p>
      <p><code>41  case SCTP_PEER_ADDR_CHANGE:</code></p>
      <p><code>42   spc = &amp;snp-&gt;sn_paddr_change;</code></p>
      <p><code>43   switch (spc-&gt;spc_state) {</code></p>
      <p><code>44   case SCTP_ADDR_AVAILABLE:</code></p>
      <p><code>45    str = "ADDRESS AVAILABLE";</code></p>
      <p><code>46    break;</code></p>
      <p><code>47   case SCTP_ADDR_UNREACHABLE:</code></p>
      <p><code>48    str = "ADDRESS UNREACHABLE";</code></p>
      <p><code>49    break;</code></p>
      <p><code>50   case SCTP_ADDR_REMOVED:</code></p>
      <p><code>51    str = "ADDRESS REMOVED";</code></p>
      <p><code>52    break;</code></p>
      <p><code>53   case SCTP_ADDR_ADDED:</code></p>
      <p><code>54    str = "ADDRESS ADDED";</code></p>
      <p><code>55    break;</code></p>
      <p><code>56   case SCTP_ADDR_MADE_PRIM:</code></p>
      <p><code>57    str = "ADDRESS MADE PRIMARY";</code></p>
      <p><code>58    break;</code></p>
      <p><code>59   default:</code></p>
      <p><code>60    str = "UNKNOWN";</code></p>
      <p><code>61    break;</code></p>
      <p><code>62   } /* конец ветвления switch (spc-&gt;spc_state) */</code></p>
      <p><code>63   printf("SCTP_PEER_ADDR_CHANGE %s, addr=%s, assoc=0x%x\n", str,</code></p>
      <p><code>64    Sock_ntop((SA*)&amp;spc-&gt;spc_aaddr, sizeof(spc-&gt;spc_aaddr)),</code></p>
      <p><code>65    (uint32_t)spc-&gt;spc_assoc_id);</code></p>
      <p><code>66   break;</code></p>
      <p><code>67  case SCTP_REMOTE_ERROR:</code></p>
      <p><code>68   sre = &amp;snp-&gt;sn_remote_error;</code></p>
      <p><code>69   printf("SCTP_REMOTE_ERROR: assoc=0x%x error=%d\n",</code></p>
      <p><code>70    (uint32_t)sre-&gt;sre_assoc_id, sre-&gt;sre_error);</code></p>
      <p><code>71   break;</code></p>
      <p><code>72  case SCTP_SEND_FAILED:</code></p>
      <p><code>73   ssf = &amp;snp-&gt;sn_send_failed;</code></p>
      <p><code>74   printf("SCTP_SEND_FAILED: assoc=0x%x error=%d\n",</code></p>
      <p><code>75    (uint32_t)ssf-&gt;ssf_assoc_id, ssf-&gt;ssf_error);</code></p>
      <p><code>76   break;</code></p>
      <p><code>77  case SCTP_ADAPTION_INDICATION:</code></p>
      <p><code>78   ae = &amp;snp-&gt;sn_adaption_event;</code></p>
      <p><code>79   printf("SCTP_ADAPTION_INDICATION: 0x%x\n",</code></p>
      <p><code>80    (u_int)ae-&gt;sai_adaption_ind);</code></p>
      <p><code>81   break;</code></p>
      <p><code>82  case SCTP_PARTIAL_DELIVERY_EVENT:</code></p>
      <p><code>83   pdapi = &amp;snp-&gt;sn_pdapi_event;</code></p>
      <p><code>84   if (pdapi-&gt;pdapi_indication == SCTP_PARTIAL_DELIVERY_ABORTED)</code></p>
      <p><code>85    printf("SCTP_PARTIAL_DELIEVERY_ABORTED\n");</code></p>
      <p><code>86   else</code></p>
      <p><code>87    printf("Unknown SCTP_PARTIAL_DELIVERY_EVENT 0x%x\n",</code></p>
      <p><code>88     pdapi-&gt;pdapi_indication);</code></p>
      <p><code>89   break;</code></p>
      <p><code>90  case SCTP_SHUTDOWN_EVENT:</code></p>
      <p><code>91   sse = &amp;snp-&gt;sn_shutdown_event;</code></p>
      <p><code>92   printf("SCTP_SHUTDOWN_EVENT: assoc=0x%x\n",</code></p>
      <p><code>93    (uint32_t)sse-&gt;sse_assoc_id);</code></p>
      <p><code>94   break;</code></p>
      <p><code>95  default:</code></p>
      <p><code>96   printf("Unknown notification event type=0x%x\n",</code></p>
      <p><code>97    snp-&gt;sn_header.sn_type);</code></p>
      <p><code>98  }</code></p>
      <p><code>99 }</code></p>
      <subtitle>Преобразование буфера и начало ветвления</subtitle>
      <p><code>14-15</code> Функция преобразует буфер вызова к типу union, после чего разыменовывает структуру <code>sn_header</code> и тип <code>sn_type</code> и выполняет ветвление по значению соответствующего поля.</p>
      <subtitle>Обработка изменения состояния ассоциации</subtitle>
      <p><code>16-40</code> Если функция обнаруживает в буфере уведомление об изменении ассоциации, она выводит тип происшедшего изменения.</p>
      <subtitle>Изменение адреса собеседника</subtitle>
      <p><code>16-40</code> Если получено уведомление об изменении адреса собеседника, функция распечатывает событие и новый адрес.</p>
      <subtitle>Ошибка на удаленном узле</subtitle>
      <p><code>67-71</code> Если получено уведомление об ошибке на удаленном узле, функция отображает сообщение об этом вместе с идентификатором ассоциации, для которой получено уведомление. Мы не пытаемся декодировать и отобразить сообщение об ошибке, присланное собеседником. При необходимости эти сведения можно получить из поля <code>sre_data</code> структуры <code>sctp_remote_error</code>.</p>
      <subtitle>Ошибка отправки сообщения</subtitle>
      <p><code>72-76</code> Если получено уведомление об ошибке отправки сообщения, мы можем сделать вывод, что сообщение не было отправлено собеседнику. Это означает, что либо ассоциация завершает работу и вскоре будет получено уведомление об изменении ее состояния (если оно еще не было получено) или же сервер использует расширение частичной надежности и отправка сообщения оказалась неудачной из-за наложенных ограничений. Данные, которые все-таки были переданы, помещаются в поле <code>ssf_data</code>, которая наша функция не использует.</p>
      <subtitle>Индикация уровня адаптера</subtitle>
      <p><code>77-81</code> Если получено уведомление об уровне адаптера, функция отображает соответствующее 32-разрядное значение, полученное в сообщении INIT или INIT-ACK.</p>
      <subtitle>Уведомление механизма частичной доставки</subtitle>
      <p><code>82-89</code> Если получено уведомление механизма частичной доставки, функция выводит на экран соответствующее сообщение. Единственное определенное на момент написания этой книги событие, связанное с частичной доставкой, состоит в ее аварийном завершении.</p>
      <subtitle>Уведомление о завершении ассоциации</subtitle>
      <p><code>90-94</code> Если получено уведомление о завершении ассоциации, мы можем сделать вывод, что собеседник выполняет корректное закрытие. За этим уведомлением обычно следует уведомление об изменении состояния ассоциации, которое приходит б момент окончания последовательности пакетов, завершающих ассоциацию. Код сервера, использующего нашу новую функцию, приведен в листинге 23.5.</p>
      <p><strong>Листинг 23.5</strong>. Сервер, обрабатывающий уведомления о событиях</p>
      <p><code>//sctp/sctpserv06.c</code></p>
      <p><code>21 bzero(&amp;evnts, sizeof(evnts));</code></p>
      <p><code>22 evnts.sctp_data_io_event = 1;</code></p>
      <p><code>23 evnts.sctp_association_event = 1;</code></p>
      <p><code>24 evnts.sctp_address_event = 1;</code></p>
      <p><code>25 evnts.sctp_send_failure_event = 1;</code></p>
      <p><code>26 evnts.sctp_peer_error_event = 1;</code></p>
      <p><code>27 evnts.sctp_shutdown_event = 1;</code></p>
      <p><code>28 evnts.sctp_partial_delivery_event = 1;</code></p>
      <p><code>29 evnts.sctp_adaption_layer_event = 1;</code></p>
      <p><code>30 Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &amp;evnts, sizeof(evnts));</code></p>
      <empty-line/>
      <p><code>31 Listen(sock_fd, LISTENQ);</code></p>
      <p><code>32 for (;;) {</code></p>
      <p><code>33  len = sizeof(struct sockaddr_in);</code></p>
      <p><code>34  rd_sz = Sctp_recvmsg(sock_fd, readbuf, sizeof(readbuf),</code></p>
      <p><code>35   (SA*)&amp;cliaddr, &amp;len, &amp;sri, &amp;msg_flags);</code></p>
      <p><code>36  if (msg_f1ags &amp; MSG_NOTIFICATION) {</code></p>
      <p><code>37   print_notification(readbuf);</code></p>
      <p><code>38   continue;</code></p>
      <p><code>39  }</code></p>
      <subtitle>Подписка на уведомления</subtitle>
      <p><code>21-30</code> Сервер изменяет параметры подписки на события таким образом, чтобы получать все возможные уведомления.</p>
      <subtitle>Получение данных</subtitle>
      <p><code>31-35</code> Эта часть кода сервера осталась неизменной.</p>
      <subtitle>Обработка уведомлений</subtitle>
      <p><code>36-39</code> Сервер проверяет поле <code>msg_flags</code>. Если сообщение представляет собой уведомление, сервер вызывает рассмотренную ранее функцию <code>sctp_print_notification</code> и переходит к обработке следующего сообщения.</p>
     </section>
     <section>
      <title>
       <p>Запуск программы</p>
      </title>
      <p>Мы запускаем клиент и отправляем одно сообщение.</p>
      <p><code>FreeBSD-lap: <strong>./sctpclient01 10.1.1.5</strong></code></p>
      <p><code><strong>[0]Hello</strong></code></p>
      <p><code>From str:1 seq:0 (assoc:c99e15a0):[0]Hello</code></p>
      <p><code><strong>Control-D</strong></code></p>
      <p><code>FreeBSD-lap:</code></p>
      <p>Сервер отображает сообщения обо всех происходящих событиях (приеме входящего соединения, получении сообщения, завершении соединения).</p>
      <p><code>FreeBSD-lap:<strong>./sctpserv06</strong></code></p>
      <p><code>SCTP_ADAPTION_INDICATION:0x504c5253</code></p>
      <p><code>SCTP_ASSOC_CHANGE: COMMUNICATION UP, assoc=c99e2680h</code></p>
      <p><code>SCTP_SHUTDOWN_EVENT; assoc=c99e2680h</code></p>
      <p><code>SCTP_ASSOC_CHANGE: SHUTDOWN COMPLETE, assoc=c99e2680h</code></p>
      <p><code><strong>Control-C</strong></code></p>
      <p>Как видите, сервер действительно выводит сообщения обо всех происходящих событиях транспортного уровня.</p>
     </section>
    </section>
    <section>
     <title>
      <p>23.5. Неупорядоченные данные</p>
     </title>
     <p>В обычном режиме SCTP обеспечивает надежную упорядоченную доставку данных. Кроме того, SCTP предоставляет и сервис надежной неупорядоченной доставки. Сообщение с флагом <code>MSG_UNORDERED</code> отправляется вне очереди и делается доступным для чтения сразу же после приема на удаленном узле. Такое сообщение может быть отправлено по любому потоку. Ему не присваивается порядковый номер внутри какого-либо потока. В листинге 23.6 представлены изменения кода клиента, позволяющие ему отправлять внеочередные запросы серверу.</p>
     <p><strong>Листинг 23.6</strong>. Функция sctp_strcli, отправляющая внеочередные данные</p>
     <p><code>//sctp/sctp_strcli_un.c</code></p>
     <p><code>18 out_sz = strlen(sendline);</code></p>
     <p><code>19 Sctp_sendmsg(sock_fd, sendline, out_sz,</code></p>
     <p><code>20  to, tolen, 0, MSG_UNORDERED, sri.sinfo_stream, 0, 0);</code></p>
     <subtitle>Отправка внеочередных данных</subtitle>
     <p><code>18-20</code> Функция <code>sctp_str_cli</code> практически не отличается от той, которую мы разработали в разделе 10.4. Единственное изменение произошло в строке 21: клиент передает флаг <code>MSG_UNORDERED</code>, включающий механизм частичной доставки. Обычно все сообщения внутри потока упорядочиваются по номерам. Флаг <code>MSG_UNORDERED</code> позволяет отправить сообщение без порядкового номера. Такое сообщение доставляется адресату сразу после получения его стеком SCTP, даже если другие внеочередные сообщения, отправленные ранее по тому же потоку, еще не были приняты.</p>
    </section>
    <section>
     <title>
      <p>23.6. Связывание с подмножеством адресов</p>
     </title>
     <p>Некоторым приложениям требуется связывать один сокет с некоторым конкретным подмножеством всех адресов узла. Протоколы TCP и UDP не позволяют выделить подмножество адресов. Системный вызов <code>bind</code> позволяет приложению связать сокет с единственным адресом или сразу со всеми адресами узла (то есть с универсальным адресом). Поэтому в SCTP был добавлен новый системный вызов <code>sctp_bindx</code>, который позволяет приложению связываться с произвольным количеством адресов. Все адреса должны иметь один и тот же номер порта, а если ранее вызывалась функция <code>bind</code>, то номер порта должен быть таким, как в вызове <code>bind</code>. Если указать не тот порт, вызов <code>sctp_bindx</code> завершится с ошибкой. В листинге 23.7 представлена функция, которую мы добавим к нашему серверу, чтобы получить возможность связывать сокет с адресами, передаваемыми в качестве аргументов командной строки.</p>
     <p><strong>Листинг 23.7</strong>. Функция, связывающая сокет с набором адресов</p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 sctp_bind_arg_list(int sock_fd, char **argv, int argc)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  struct addrinfo *addr;</code></p>
     <p><code> 6  char *bindbuf, *p, portbuf[10];</code></p>
     <p><code> 7  int addrcnt=0;</code></p>
     <p><code> 8  int i;</code></p>
     <empty-line/>
     <p><code> 9  bindbuf = (char*)Calloc(argc, sizeof(struct sockaddr_storage));</code></p>
     <p><code>10  p = bindbuf;</code></p>
     <p><code>11  sprintf(portbuf, "%d", SERV_PORT);</code></p>
     <p><code>12  for (i=0; i&lt;argc; i++ ) {</code></p>
     <p><code>13   addr = Host_serv(argv[i], portbuf, AF_UNSPEC, SOCK_SEQPACKET);</code></p>
     <p><code>14   memcpy(p, addr-&gt;ai_addr, addr-&gt;ai_addrlen);</code></p>
     <p><code>15   freeaddrinfo(addr);</code></p>
     <p><code>16   addrcnt++;</code></p>
     <p><code>17   p += addr-&gt;ai_addrlen;</code></p>
     <p><code>18  }</code></p>
     <p><code>19  Sctp_bindx(sock_fd, (SA*)bindbuf, addrent, SCTP_BINDX_ADD_ADDR);</code></p>
     <p><code>20  free(bindbuf);</code></p>
     <p><code>21  return(0);</code></p>
     <p><code>22 }</code></p>
     <subtitle>Выделение памяти под аргументы bind</subtitle>
     <p><code>9-10</code> Наша новая функция начинает работу с выделения памяти под аргументы функции <code>sctp_bindx</code>. Обратите внимание, что функция <code>sctp_bindx</code> может принимать в качестве аргументов адреса IPv4 и IPv6 в произвольных комбинациях. Для каждого адреса мы выделяем место под структуру <code>sockaddr_storage</code> несмотря на то, что соответствующий аргумент <code>sctp_bindx</code> представляет собой упакованный список адресов (см. рис. 9.3). В результате мы расходуем зря некоторый объем памяти, но зато функция работает быстрее, потому что ей не приходится вычислять точный объем памяти и лишний раз обрабатывать список аргументов.</p>
     <subtitle>Обработка аргументов</subtitle>
     <p><code>11-18</code> Мы подготавливаем <code>portbuf</code> к хранению номера порта в ASCII-представлении, имея в виду вызов нашей обертки для <code>getaddrinfo</code>, которая называется <code>host_serv</code>. Каждый адрес с номером порта мы передаем <code>host_serv</code>, указывая константы <code>AF_UNSPEC</code> (протоколы IPv4 и IPv6) и <code>SOCK_SEQPACKET</code> (протокол SCTP). Мы копируем первую возвращаемую структуру <code>sockaddr</code>, игнорируя все остальные. Поскольку аргументами этой функции должны быть адреса в строковом представлении, а не имена, с каждым из которых может быть связано несколько адресов, это не вызывает проблем. Мы освобождаем буфер, увеличиваем количество адресов на единицу и перемещаем указатель на следующий элемент в упакованном массиве структур <code>sockaddr</code>.</p>
     <subtitle>Вызов связывающей функции</subtitle>
     <p><code>19</code> Указатель устанавливается на начало буфера адресов, после чего вызывается функция <code>sctp_bindx</code>, в качестве аргументов которой используется раскодированный ранее набор адресов.</p>
     <subtitle>Успешное завершение</subtitle>
     <p><code>20-21</code> Если мы добрались до этого места, можно считать, что выполнение прошло успешно, поэтому мы освобождаем память и возвращаем управление вызвавшему процессу.</p>
     <p>В листинге 23.8 представлен модифицированный эхо-сервер, связывающий сокет с набором адресов, передаваемых в командной строке. Мы слегка изменили код сервера, чтобы он отправлял эхо-сообщения по тем потокам, по которым были приняты исходные сообщения.</p>
     <p><strong>Листинг 23.8</strong>. Сервер, работающий с произвольным набором адресов</p>
     <p><code>if (argc &lt; 2)</code></p>
     <p><code> err_quit("Error, use %s [list of addresses to bind]\n", argv[0]);</code></p>
     <p><code>sock_fd = Socket(AF_INET6, SOCK_SEQPACKET, IPPROTO_SCTP);</code></p>
     <empty-line/>
     <p><code>if (sctp_bind_arg_list(sock_fd, argv + 1, argc — 1))</code></p>
     <p><code> err_sys("Can't bind the address set");</code></p>
     <empty-line/>
     <p><code>bzero(&amp;evnts, sizeof(evnts));</code></p>
     <p><code>evnts sctp_data_io_event = 1;</code></p>
     <subtitle>Работа с IPv6</subtitle>
     <p><code>14</code> Это тот же сервер, с которым мы работали во всех предыдущих разделах этой главы, но с незначительным изменением. Сервер создает сокет <code>AF_INET6</code>, что позволяет ему работать с протоколом IP обеих версий.</p>
     <subtitle>Вызов sctp_bind_arg_list</subtitle>
     <p><code>15-16</code> Сервер вызывает новую функцию <code>sctp_bind_arg_list</code> и передает ей список аргументов для обработки.</p>
    </section>
    <section>
     <title>
      <p>23.7. Получение адресов</p>
     </title>
     <section>
      <p>Поскольку протокол SCTP ориентирован на многоинтерфейсные узлы, для определения адресов локального и удаленного узла не могут использоваться те же механизмы, что и в TCP. В этом разделе мы изменим код клиента, добавив в него подписку на уведомление о событии COMMUNICATION UP. В этом уведомлении клиент будет получать сведения об адресах, между которыми установлена ассоциация. В листингах 23.9 и 23.10 представлены изменения в коде клиента. Листинги 23.11 и 23.12 содержат добавления к коду клиента.</p>
      <p><strong>Листинг 23.9</strong>. Клиент включает уведомления</p>
      <p><code>16 bzero(&amp;evnts, sizeof(evnts));</code></p>
      <p><code>17 evnts.sctp_data_io_event = 1;</code></p>
      <p><code>18 evnts.sctp_association_event = 1;</code></p>
      <p><code>19 Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &amp;evnts, sizeof(evnts));</code></p>
      <empty-line/>
      <p><code>20 sctpstr_cli(stdin, sock_fd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
      <subtitle>Включение событий и вызов функции отправки сообщения</subtitle>
      <p><code>16-20</code> Функция <code>main</code> клиента претерпевает не слишком значительные изменения. Клиент явным образом подписывается на уведомления об изменении состояния ассоциации.</p>
      <p>Посмотрим, что нам придется изменить в функции <code>sctpstr_cli</code>, чтобы она смогла вызывать нашу новую функцию обработки уведомлений.</p>
      <p><strong>Листинг 23.10</strong>. Функция sctp_strcli, способная работать с уведомлениями</p>
      <p><code>21 do {</code></p>
      <p><code>22  len = sizeof(peeraddr);</code></p>
      <p><code>23  rd_sz = Sctp_recvmsg(sock_fd, recvline, sizeof(recvline),</code></p>
      <p><code>24   (SA*)&amp;peeraddr, &amp;len, &amp;sri, &amp;msg_flags);</code></p>
      <p><code>25  if (msg_flags &amp; MSG_NOTIFICATION)</code></p>
      <p><code>26   check_notification(sock_fd, recvline, rd_sz);</code></p>
      <p><code>27 } while (msg_flags &amp; MSG_NOTIFICATION);</code></p>
      <p><code>28 printf("From str:%d seq:%d (assoc.0x%x) ",</code></p>
      <p><code>29 sri.sinfo_stream, sri.sinfo_ssn, (u_int)sri.sinfo_assoc_id);</code></p>
      <p><code>30 printf("%.*s", rd_sz.recvline);</code></p>
      <subtitle>Цикл ожидания сообщения</subtitle>
      <p><code>21-24</code> Клиент устанавливает переменную, в которой хранится длина адреса, и вызывает функцию <code>sctp_recvmsg</code> для получения эхо-ответа сервера на свое сообщение.</p>
      <subtitle>Проверка уведомлений</subtitle>
      <p><code>25-26</code> Клиент проверяет, не является ли полученное сообщение уведомлением. В последнем случае он вызывает функцию обработки уведомлений, представленную в листинге 23.11.</p>
      <subtitle>Переход на начало цикла</subtitle>
      <p><code>27</code> Если сообщение действительно было уведомлением, происходит переход на начало цикла ожидания сообщений.</p>
      <subtitle>Отображение сообщения</subtitle>
      <p><code>28-30</code> Клиент отображает сообщение и переходит к ожиданию пользовательского ввода.</p>
      <p>Теперь мы можем изучить новую функцию <code>sctp_check_notification</code>, которая будет отображать адреса обоих конечных точек при получении уведомления об изменении состояния ассоциации.</p>
      <p><strong>Листинг 23.11</strong>. Обработка уведомлений</p>
      <p><code>//sctp/sctp_check_notify.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 check_notification(int sock_fd, char *recvline, int rd_len)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  union sctp_notification *snp;</code></p>
      <p><code> 6  struct sctp_assoc_change *sac;</code></p>
      <p><code> 7  struct sockaddr_storage *sal, *sar;</code></p>
      <p><code> 8  int num_rem, num_loc;</code></p>
      <empty-line/>
      <p><code> 9  snp = (union sctp_notification*)recvline;</code></p>
      <p><code>10  if (snp-&gt;sn_header.sn_type == SCTP_ASSOC_CHANGE) {</code></p>
      <p><code>11   sac = &amp;snp-&gt;sn_assoc_change;</code></p>
      <p><code>12   if ((sac-&gt;sac_state == SCTP_COMM_UP) ||</code></p>
      <p><code>13    (sac-&gt;sac_state == SCTP_RESTART)) {</code></p>
      <p><code>14    num_rem = sctp_getpaddrs(sock_fd, sac-&gt;sac_assoc_id, &amp;sar);</code></p>
      <p><code>15    printf("There are %d remote addresses and they are:\n", num_rem);</code></p>
      <p><code>16    sctp_print_addresses(sar, num_rem);</code></p>
      <p><code>17    sctp_freepaddrs(sar);</code></p>
      <empty-line/>
      <p><code>18    num_loc = sctp_getladdrs(sock_fd.sac-&gt;sac_assoc_id, &amp;sal);</code></p>
      <p><code>19    printf("There are %d local addresses and they are:\n", num_loc);</code></p>
      <p><code>20    sctp_print_addresses(sal, num_loc);</code></p>
      <p><code>21    sctp_freeladdrs(sal);</code></p>
      <p><code>22   }</code></p>
      <p><code>23  }</code></p>
      <p><code>24 }</code></p>
      <subtitle>Проверка типа уведомления</subtitle>
      <p><code>9-13</code> Функция преобразует буфер приема к типу универсального указателя на уведомления, чтобы определить тип полученного уведомления. Из всех уведомлений нас интересуют только уведомления об изменении ассоциации, а из них — уведомления о создании или перезапуске ассоциации (<code>SCTP_COMM_UP</code> и <code>SCTP_RESTART</code>). Все прочие уведомления нас не интересуют.</p>
      <subtitle>Получение и вывод адресов собеседника</subtitle>
      <p><code>14-17</code> Функция <code>sctp_getpaddrs</code> возвращает нам список удаленных адресов, которые мы выводим при помощи функции <code>sctp_print_addresses</code>, представленной в листинге 23.12. После работы с ней мы освобождаем ресурсы, выделенные <code>sctp_getpaddrs</code>, вызывая функцию <code>sctp_freepaddrs</code>.</p>
      <subtitle>Получение и вывод локальных адресов</subtitle>
      <p><code>18-21</code> Функция <code>sctp_getladdrs</code> возвращает нам список локальных адресов, которые мы выводим на экран вместе с их общим количеством. После завершения работы с адресами мы освобождаем память вызовом <code>sctp_freeladdrs</code>.</p>
      <p>Последняя из новых функций называется <code>sctp_print_addresses</code>. Она выводит на экран адреса из списка, возвращаемого функциями <code>sctp_getpaddrs</code> и <code>sctp_getladdrs</code>. Текст функции представлен в листинге 23.12.</p>
      <p><strong>Листинг 23.12</strong>. Вывод списка адресов</p>
      <p><code>//sctp/sctp_print_addrs.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 sctp_print_addresses(struct sockaddr_storage *addrs, int num)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  struct sockaddr_storage *ss;</code></p>
      <p><code> 6  int i, salen;</code></p>
      <empty-line/>
      <p><code> 7  ss = addrs;</code></p>
      <p><code> 8  for (i=0; i&lt;num; i++){</code></p>
      <p><code> 9   printf("%s\n", Sock_ntop((SA*)ss, salen));</code></p>
      <p><code>10 #ifdef HAVE_SOCKADDR_SA_LEN</code></p>
      <p><code>11   salen = ss-&gt;ss_len;</code></p>
      <p><code>12 #else</code></p>
      <p><code>13   swilch (ss-&gt;ss_family) {</code></p>
      <p><code>14   case AF_INET:</code></p>
      <p><code>15    salen = sizeof(struct sockaddr_in);</code></p>
      <p><code>16    break;</code></p>
      <p><code>17 #ifdef IPV6</code></p>
      <p><code>18   case AF_INET6:</code></p>
      <p><code>19    salen = sizeof(struct sockaddr_in6);</code></p>
      <p><code>20    break;</code></p>
      <p><code>21 #endif</code></p>
      <p><code>22   default:</code></p>
      <p><code>23    err_auit("sctp_print_addresses: unknown AF");</code></p>
      <p><code>24    break;</code></p>
      <p><code>25   }</code></p>
      <p><code>26 #endif</code></p>
      <p><code>27   ss = (struct sockaddr_storage*)((char*)ss + salen);</code></p>
      <p><code>28  }</code></p>
      <p><code>29 }</code></p>
      <subtitle>Последовательная обработка адресов</subtitle>
      <p><code>7-8</code> Функция перебирает адреса в цикле. Общее количество адресов указывается вызывающим процессом.</p>
      <subtitle>Вывод адреса</subtitle>
      <p><code>9</code> Адрес преобразуется к удобочитаемому виду функцией <code>sock_ntop</code>, которая, как вы помните, должна работать со структурами адреса сокета всех форматов, поддерживаемых системой.</p>
      <subtitle>Определение размера адреса</subtitle>
      <p><code>10-26</code> Список адресов передается в упакованном формате. Это не просто массив структур s<code>ockaddr_storage</code>. Дело в том, что структура <code>sockaddr_storage</code> достаточно велика, и ее нецелесообразно использовать при передаче адресов между ядром и пользовательскими процессами. В системах, где эта структура содержит внутреннее поле длины, обработка списка является делом тривиальным: достаточно извлекать длину из текущей структуры <code>sockaddr_storage</code>. В прочих системах длина определяется на основании семейства адреса. Если семейство не определено, функция завершает работу с сообщением об ошибке.</p>
      <subtitle>Перемещение указателя</subtitle>
      <p><code>27</code> К указателю на элемент списка прибавляется размер адреса. Таким образом осуществляется перемещение по списку адресов.</p>
     </section>
     <section>
      <title>
       <p>Выполнение программы</p>
      </title>
      <p>Результат взаимодействия модифицированного клиента с сервером представлен ниже.</p>
      <p><code>FreeBSD-lap: <strong>./sctpclient01 10.1.1.5</strong></code></p>
      <p><code><strong>[0]Hi</strong></code></p>
      <p><code>There are 2 remote addresses and they are:</code></p>
      <p><code>10.1.1.5:9877</code></p>
      <p><code>127.0.0.1:9877</code></p>
      <p><code>There are 2 local addresses and they are:</code></p>
      <p><code>10.1.1.5:1025</code></p>
      <p><code>127.0.0.1:1025</code></p>
      <p><code>From str:0 seq:0 (assoc:c99e2680):[0]Hi</code></p>
      <p><code><strong>Control-D</strong></code></p>
      <p><code>FreeBSD-lap:</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>23.8. Определение идентификатора ассоциации по IP-адресу</p>
     </title>
     <p>Модифицированный клиент из раздела 23.7 использовал уведомления в качестве сигнала для получения списков адресов. Это было достаточно удобно, поскольку идентификатор ассоциации, для которой требовалось получить адреса, содержался в уведомлении в поле <code>sac_assoc_id</code>. Но что если приложение не отслеживает идентификаторы ассоциаций, а ему вдруг понадобилось определить какой- либо идентификатор по адресу собеседника? В листинге 23.13 представлена простая функция, преобразующая адрес собеседника в идентификатор ассоциации. Эта функция будет использоваться сервером из раздела 23.10.</p>
     <p><strong>Листинг 23.13</strong>. Преобразование адреса в идентификатор ассоциации</p>
     <p><code>//sctp/sctp_addr_to_associd.с</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 sctp_assoc_t</code></p>
     <p><code> 3 sctp_address_to_associd(int sock_fd, struct sockaddr *sa, socklen_t salen)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  struct sctp_paddrparams sp;</code></p>
     <p><code> 6  int siz;</code></p>
     <empty-line/>
     <p><code> 7  siz = sizeof(struct sctp_paddrparams);</code></p>
     <p><code> 8  bzero(&amp;sp, siz);</code></p>
     <p><code> 9  memcpy(&amp;sp, spp_address, sa.salen);</code></p>
     <p><code>10  sctp_opt_info(sock_fd, 0, SCTP_PEER_ADDR_PARAMS, &amp;sp, &amp;siz);</code></p>
     <p><code>11  return(sp.spp_assoc_id);</code></p>
     <p><code>12 }</code></p>
     <subtitle>Инициализация</subtitle>
     <p><code>7-8</code> Функция начинает работу с инициализации структуры <code>sctp_paddrparams</code>.</p>
     <subtitle>Копирование адреса</subtitle>
     <p><code>9</code> Мы копируем адрес в структуру <code>sctp_paddrparams</code>, используя переданную нам вызвавшим процессом информацию о длине этого адреса.</p>
     <subtitle>Вызов параметра сокета</subtitle>
     <p><code>10</code> При помощи параметра сокета <code>SCTP_PEER_ADDR_PARAMS</code> наша функция запрашивает параметры адреса собеседника. Обратите внимание, что мы используем <code>sctp_opt_info</code> вместо <code>getsockopt</code>, потому что параметр <code>SCTP_PEER_ADDR_PARAMS</code> требует копирования аргументов как в ядро, так и из ядра. Вызов, который мы делаем, возвратит нам текущий интервал проверки работоспособности соединения, максимальное количество попыток повторной передачи перед принятием решения о признании адреса собеседника отказавшим, и, что самое важное, идентификатор ассоциации. Мы не проверяем возвращаемое значение, потому что если вызов оказывается неудачным, мы хотим вернуть 0.</p>
     <p><code>11</code> Функция возвращает идентификатор ассоциации. Если вызов <code>sctp_opt_info</code> оказался неудачным, обнуление структуры гарантирует, что вызвавший нашу функцию процесс получит 0. Идентификатор ассоциации нулевым быть не может. Это значение используется реализацией SCTP для указания на отсутствие ассоциации.</p>
    </section>
    <section>
     <title>
      <p>23.9. Проверка соединения и ошибки доступа</p>
     </title>
     <p>Механизм периодической проверки соединения, предоставляемый протоколом SCTP, основан на той же концепции, что и параметр поддержания соединения TCP keep-alive. Однако в SCTP этот механизм по умолчанию включен, тогда как в TCP он выключен. Приложение может устанавливать пороговое значение количества неудачных проверок при помощи того же параметра сокета, который использовался в разделе 23.8. Порог ошибок — это количество пропущенных проверочных пакетов и тайм-аутов повторной передачи, после которого адрес получателя считается недоступным. Когда доступность адреса восстанавливается (о чем сообщают все те же проверочные пакеты), он снова становится активным.</p>
     <p>Приложение может отключить проверку соединения, но без нее SCTP не сможет узнать о доступности адреса собеседника, который ранее был признан недоступным. Без вмешательства пользователя такой адрес не сможет стать активным.</p>
     <p>Параметр проверки соединения задается полем <code>spp_hbinterval</code> структуры <code>sctp_paddrparams</code>. Если приложение устанавливает это поле равным <code>SCTP_NO_HB</code> (эта константа имеет значение 0), проверка соединения отключается. Ненулевое значение устанавливает задержку проверки соединения в миллисекундах. К фиксированной задержке прибавляется текущее значение таймера повторной передачи и некоторое случайное число, в результате чего получается реальный промежуток времени между проверками соединения. В листинге 23.14 приводится небольшая функция, которая позволяет устанавливать задержку проверки соединения, или вовсе отключать этот механизм протокола SCTP для конкретного адресата. Обратите внимание, что если поле <code>spp_pathmaxrxr</code> структуры <code>sctp_paddrparams</code> оставить равным нулю, текущее значение задержки останется неизменным.</p>
     <p><strong>Листинг 23.14</strong>. Управление периодической проверкой соединения</p>
     <p><code>//sctp/sctp_modify_hb.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 heartbeat_action(int sock_fd, struct sockaddr *sa, socklen_t salen,</code></p>
     <p><code> 4 u_int value)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  struct sctp_paddrparams sp;</code></p>
     <p><code> 7  int siz;</code></p>
     <empty-line/>
     <p><code> 8  bzero(&amp;sp, sizeof(sp));</code></p>
     <p><code> 9  sp.spp_hbinterval = value;</code></p>
     <p><code>10  memcpy((caddr_t)&amp;sp, spp_address, sa.salen);</code></p>
     <p><code>11  Setsockopt(sock_fd, IPPROTO_SCTP,</code></p>
     <p><code>12   SCTP_PEER_ADDR_PARAMS, &amp;sp, sizeof(sp));</code></p>
     <p><code>13  return(0);</code></p>
     <p><code>14 }</code></p>
     <subtitle>Обнуление структуры sctp_paddrparams и копирование аргумента</subtitle>
     <p><code>8-9</code> Мы обнуляем структуру <code>sctp_paddrparams</code>, чтобы случайно не изменить какой-нибудь параметр, который нас не интересует. Затем мы копируем в нее переданное пользователем значение задержки: <code>SCTP_ISSUE_HB</code>, <code>SCTP_NO_HB</code> или конкретное число.</p>
     <subtitle>Установка адреса</subtitle>
     <p><code>10</code> Функция подготавливает адрес и копирует его в структуру <code>sctp_paddrparams</code>, чтобы реализация SCTP знала, к какому адресу относятся устанавливаемые нами параметры периодической проверки соединения.</p>
     <subtitle>Выполнение действия</subtitle>
     <p><code>11-12</code> Наконец, функция делает вызов параметра сокета, чтобы выполнить запрошенную пользователем операцию.</p>
    </section>
    <section>
     <title>
      <p>23.10. Выделение ассоциации</p>
     </title>
     <p>Пока что мы занимались исключительно интерфейсом типа «один-ко-многим». Этот интерфейс имеет несколько преимуществ перед традиционным интерфейсом «один-к-одному»:</p>
     <p>&#9632; программа работает с единственным дескриптором;</p>
     <p>&#9632; программисту достаточно написать простой последовательный сервер;</p>
     <p>&#9632; приложение может передавать данные в третьем и четвертом пакетах четырехэтажного рукопожатия, если для неявной установки соединения используются функции <code>sendmsg</code> и <code>sctp_sendmsg</code>;</p>
     <p>&#9632; отсутствует необходимость в отслеживании состояния на транспортном уровне. Другими словами, приложение просто запрашивает данные из дескриптора сокета, не вызывая традиционных функций <code>connect</code> и <code>accept</code> для получения сообщений.</p>
     <p>Есть у этого интерфейса и недостатки. Самый существенный из них состоит в том, что интерфейс типа «один-ко-многим» затрудняет написание параллельного сервера (многопоточного или порождающего процессы). Для устранения этого недостатка была придумана функция <code>sctp_peeloff</code>. Она принимает в качестве аргумента дескриптор сокета типа «один-ко-многим» и идентификатор ассоциации, а возвращает новый дескриптор сокета типа «один-к-одному» с единственной ассоциацией (сохраняя все уведомления и данные, помещенные в очередь этой ассоциации). Исходный сокет остается открытым, причем все остальные ассоциации проведенной операцией извлечения никак не затрагиваются.</p>
     <p>Выделенный сокет может быть передан потоку или дочернему процессу для обработки запросов клиента. Листинг 23.15 демонстрирует новую модифицированную версию сервера, который обрабатывает первое сообщение клиента, выделяет ассоциацию при помощи <code>sctp_peeloff</code>, порождает дочерний процесс и вызывает функцию <code>str_echo</code> для TCP, которая была написана в разделе 5.3. Адрес из полученного сообщения мы передаем нашей функции из раздела 23.8, которая по этому адресу определяет идентификатор ассоциации. Идентификатор хранится также в поле <code>sri</code>, <code>sinfo_assoc_id</code>. Наша функция служит лишь иллюстрацией использования альтернативного метода. Породив процесс, сервер переходит к обработке следующего сообщения.</p>
     <p><strong>Листинг 23.15</strong>. Параллельный сервер SCTP</p>
     <p><code>//sctp/sctpserv_fork.c</code></p>
     <p><code>23 for (;;) {</code></p>
     <p><code>24  len = sizeof(struct sockaddr_in);</code></p>
     <p><code>25  rd_sz = Sctp_recvmsg(sock_fd, readbuf, sizeof(readbuf),</code></p>
     <p><code>26   (SA*)&amp;cliaddr, &amp;len, &amp;sri, &amp;msg_flags);</code></p>
     <p><code>27  Sctp_sendmsg(sock_fd, readbuf, rd_sz,</code></p>
     <p><code>28   (SA*)&amp;cliaddr, len,</code></p>
     <p><code>29   sri.sinfo_ppid,</code></p>
     <p><code>30   sri.sinfo_flags, sn.sinfo_stream, 0, 0);</code></p>
     <p><code>31  assoc = sctp_address_to_associd(sock_fd, (SA*)&amp;cliaddr, len);</code></p>
     <p><code>32  if ((int)assoc == 0) {</code></p>
     <p><code>33   err_ret("Can't get association id");</code></p>
     <p><code>34   continue;</code></p>
     <p><code>35  }</code></p>
     <p><code>36  connfd = sctp_peeloff(sock_fd, assoc);</code></p>
     <p><code>37  if (connfd == -1) {</code></p>
     <p><code>38   err_ret("sctp_peeloff fails");</code></p>
     <p><code>39   continue;</code></p>
     <p><code>40  }</code></p>
     <p><code>41  if ((childpid = fork()) == 0) {</code></p>
     <p><code>42   Close(sock_fd);</code></p>
     <p><code>43   str_echo(connfd);</code></p>
     <p><code>44   exit(0);</code></p>
     <p><code>45  } else {</code></p>
     <p><code>46   Close(connfd);</code></p>
     <p><code>47  }</code></p>
     <p><code>48 }</code></p>
     <subtitle>Получение и обработка первого сообщения</subtitle>
     <p><code>26-30</code> Сервер получает и обрабатывает первое сообщение клиента.</p>
     <subtitle>Преобразование адреса в идентификатор ассоциации</subtitle>
     <p><code>31-35</code> Сервер вызывает функцию из листинга 23.13 для получения идентификатора ассоциации по ее адресу. Если что-то мешает серверу получить идентификатор, он не делает попыток породить дочерний процесс, а просто переходит к обработке следующего сообщения.</p>
     <subtitle>Выделение ассоциации</subtitle>
     <p><code>36-40</code> Сервер выделяет ассоциацию в отдельный дескриптор сокета при помощи <code>sctp_peeloff</code>. Полученный сокет типа «один-к-одному» может быть без проблем передан написанной ранее для TCP функции <code>str_echo</code>.</p>
     <subtitle>Передача работы дочернему процессу</subtitle>
     <p><code>41-47</code> Сервер порождает дочерний процесс, который и выполняет всю обработку по конкретному дескриптору.</p>
    </section>
    <section>
     <title>
      <p>23.11. Управление таймерами</p>
     </title>
     <p>Протокол SCTP имеет множество численных пользовательских параметров. Все они устанавливаются через параметры сокетов, рассмотренные в разделе 7.10. Далее мы займемся рассмотрением нескольких параметров, определяющих задержку перед объявлением об отказе ассоциации или адреса собеседника.</p>
     <p>Время обнаружения отказа в SCTP определяется семью переменными (табл. 23.1).</p>
     <empty-line/>
     <p><strong>Таблица 23.1</strong>. Поля таймеров SCTP</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Поле</th>
       <th align="left" valign="top">Описание</th>
       <th align="left" valign="top">По умолчанию</th>
       <th align="left" valign="top">Единицы</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">srto_min</td>
       <td align="left" valign="top">Минимальный тайм-аут повторной передачи</td>
       <td align="left" valign="top">1000</td>
       <td align="left" valign="top">Мс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">srto_max</td>
       <td align="left" valign="top">Максимальный тайм-аут повторной передачи</td>
       <td align="left" valign="top">60000</td>
       <td align="left" valign="top">Мс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">srto_initial</td>
       <td align="left" valign="top">Начальный тайм-аут повторной передачи</td>
       <td align="left" valign="top">3000</td>
       <td align="left" valign="top">Мс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">sinit_max_init_timeo</td>
       <td align="left" valign="top">Максимальный тайм-аут повторной передачи сегмента INIT</td>
       <td align="left" valign="top">3000</td>
       <td align="left" valign="top">Мс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">sinit_max_attempts</td>
       <td align="left" valign="top">Максимальное количество повторных передач сегмента INIT</td>
       <td align="left" valign="top">8</td>
       <td align="left" valign="top">попыток</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">spp_pathmaxrxt</td>
       <td align="left" valign="top">Максимальное количество повторных передач по адресу</td>
       <td align="left" valign="top">5</td>
       <td align="left" valign="top">попыток</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">sasoc_asocmaxrxt</td>
       <td align="left" valign="top">Максимальное количество повторных передач на ассоциацию</td>
       <td align="left" valign="top">10</td>
       <td align="left" valign="top">попыток</td>
      </tr>
     </table>
     <p>Эти параметры можно воспринимать как регуляторы, укорачивающие и удлиняющие время обнаружения отказа. Рассмотрим два сценария.</p>
     <p>1. Конечная точка SCTP пытается открыть ассоциацию с собеседником, отключившимся от сети.</p>
     <p>2. Две многоинтерфейсные конечные точки SCTP обмениваются данными. Одна из них отключается от сети питания в момент передачи данных. Сообщения ICMP фильтруются защитными экранами и потому не достигают второй конечной точки.</p>
     <p>В сценарии 1 система, пытающаяся открыть соединение, устанавливает таймер RTO равным <code>srto_initial</code> (3000 мс). После первой повторной передачи пакета INIT таймер устанавливается на значение 6000 мс. Это продолжается до тех пор, пока не будет сделано <code>sinit_max_attempts</code> попыток (9 штук), между которыми пройдут семь тайм-аутов. Удвоение таймера закончится на величине <code>sinit_max_init_timeo</code>, равной 60 000 мс. Таким образом, через 3 + 6 + 12 + 24 + 48 + 60 + 60 + 60 = 273 с стек SCTP объявит потенциального собеседника недоступным.</p>
     <p>Вращением нескольких «ручек» мы можем удлинять и укорачивать это время. Начнем с двух параметров, позволяющих уменьшить общую задержку. Сократим количество повторных передач, изменив переменную <code>sinit_max_attempts</code>. Альтернативное изменение может состоять в уменьшении максимального тайм- аута для пакета INIT (переменная <code>srto_max_init_timeo</code>). Если количество попыток снизить до 4, время детектирования резко упадет до 45 с (одна шестая первоначального значения). Однако у этого метода есть недостаток: из-за проблем в сети или перегруженности собеседника мы можем объявить его недоступным, даже если это состояние является лишь временным.</p>
     <p>Другой подход состоит в уменьшении <code>srto_max_init_timeo</code> до 20 с. При этом задержка до обнаружения недоступности сократится до 121 с — менее половины исходной величины. Однако и это решение является компромиссным. Если мы выберем слишком низкое значение тайм-аута, при большой сетевой задержке мы будем отправлять гораздо больше пакетов INIT, чем это требуется на самом деле.</p>
     <p>Перейдем теперь к сценарию 2, описывающему взаимодействие двух многоинтерфейсных узлов. Одна конечная точка имеет адреса IP-A и IP-B, другая IP-X и IP-Y. Если одна из них становится недоступна, а вторая отправляет какие-то данные, последней приходится делать повторные передачи по каждому из адресов с задержкой, начинающейся с <code>srto_min</code> (по умолчанию 1 с) и последовательно удваивающейся до значения <code>srto_max</code> (по умолчанию 60 с). Повторные передачи будут продолжаться до тех пор, пока не будет достигнуто ограничение на их количество <code>sasoc_asocmaxrxt</code> (по умолчанию 10 повторных передач).</p>
     <p>В нашем сценарии последовательность тайм-аутов будет иметь вид 1(IP-A) + 1(IP-B) + 2(IP-A) + 2(IP-B) + 4(IP-A) + 4(IP-B) + 8(IP-A) + 8(IP-B) + 16(IP-A) + 16(IP-B), что в общей сложности составит 62 с. Параметр srto_max не влияет на работу многоинтерфейсного узла, если его значение совпадает с установленным по умолчанию, потому что ограничение на количество передач для ассоциации <code>sasoc_asocmaxrxt</code> действует раньше, чем <code>srto_max</code>. Опять-таки, у нас есть два параметра влияющих на длительность тайм-аутов и эффективность обнаружения отказов. Мы можем уменьшить количество попыток, изменив значение <code>sasoc_asocmaxrxt</code> (по умолчанию 10), или снизить максимальное значение тайм-аута, изменив значение <code>srto_max</code> (по умолчанию 60 с). Если мы сделаем <code>srto_max</code> равным 10 с, время обнаружения отказа собеседника снизится на 12 с и станет равным 50 с. Альтернативой может быть уменьшение количества повторных передач до 8; при этом время обнаружения снизится до 30 с. Изложенные ранее соображения относятся и к этому сценарию: кратковременные неполадки в сети и перегрузка удаленной системы могут привести к обрыву работоспособного соединения.</p>
     <p>Одну из множества альтернатив мы не рассматриваем в качестве рекомендуемой. Это снижение минимального тайм-аута (<code>srto_min</code>). При передаче данных через Интернет снижение этого значения приведет к неприятным последствиям: наш узел будет передавать повторные пакеты слишком часто, перегружая инфраструктуру Интернета. В частной сети снижение этого значения допустимо, но для большинства приложений в этом просто нет необходимости.</p>
     <p>Для каждого приложения выбор конкретных значений параметров повторной передачи должен определяться несколькими факторами:</p>
     <p>&#9632; Насколько быстро нужно приложению обнаруживать отказы?</p>
     <p>&#9632; Будет ли приложение выполняться в частных сетях, где условия передачи заранее известны и меняются не так резко, как в Интернете?</p>
     <p>&#9632; Каковы последствия неправильного обнаружения отказа?</p>
     <p>Только внимательно подумав над ответами на эти вопросы, программист может правильно настроить параметры тайм-аутов SCTP.</p>
    </section>
    <section>
     <title>
      <p>23.12. Когда SCTP оказывается предпочтительнее TCP</p>
     </title>
     <p>Изначально протокол SCTP разрабатывался для управления сигналами и реализации интернет-телефонии. Однако в процессе разработки область применения этого протокола значительно расширилась. Фактически он превратился в общецелевой транспортный протокол. SCTP поддерживает почти все функции TCP и значительно расширяет их новыми сервисами транспортного уровня. Маловероятно, чтобы сетевое приложение ничего не выиграло от перехода на SCTP. Так в каких же случаях следует использовать этот протокол? Начнем с перечисления его достоинств.</p>
     <p>1. Протокол SCTP обеспечивает явную поддержку многоинтерфейсных узлов. Конечная точка может передавать данные по нескольким сетям для повышения надежности. Никаких особых действий, кроме перехода на SCTP, для использования новых сервисов SCTP предпринимать не требуется. Подробнее об SCTP для многоинтерфейсных узлов читайте в [117, раздел 7.4].</p>
     <p>2. Протокол SCTP устраняет блокирование очереди. Приложение может передавать данные параллельно по нескольким потокам одной ассоциации. Потеря пакета в одном потоке не приведет к задержке передачи по другим потокам той же ассоциации (см. раздел 10.5 настоящей книги).</p>
     <p>3. Границы сообщений уровня приложения сохраняются протоколом SCTP. Многие приложения не нуждаются в отправке потока байтов. Им удобнее работать с сообщениями. SCTP сохраняет границы сообщений и тем самым упрощает задачу программисту-разработчику, которому больше не приходится отмечать границы сообщений внутри потока байтов и писать специальные функции для реконструкции сообщений из этого потока.</p>
     <p>4. SCTP предоставляет сервис неупорядоченной доставки. Некоторые приложения не нуждаются в сохранении порядка сообщений при передаче их по сети. Раньше такому приложению, использующему TCP для обеспечения надежности, приходилось мириться с задержками, вызванными блокированием очереди и необходимостью упорядоченной доставки (хотя само приложение в ней не нуждалось). SCTP предоставляет таким приложениям именно тот тип сервиса, который им нужен.</p>
     <p>5. Некоторые реализации SCTP предоставляют сервис частичной надежности. Отправитель получает возможность указывать время жизни каждого сообщения в поле <code>sinfo_timetolive</code> структуры <code>sctp_sndrcvinfo</code>. (Это время жизни отличается от TTL IPv4 и ограничения на количество прыжков IPv6 тем, что оно на самом деле измеряется в единицах времени.) Если частичная надежность поддерживается обоими узлами, не доставленные вовремя данные могут сбрасываться транспортным уровнем, а не приложением, даже если они были переданы и утеряны. Таким образом оптимизируется передача данных в условиях загруженных линий.</p>
     <p>6. Легкость перехода с TCP на SCTP обеспечивается сокетами типа «один-к-одному». Сокеты этого типа предоставляют типичный для TCP интерфейс, так что приложение может быть перенесено на новый протокол с самыми незначительными изменениями.</p>
     <p>7. Многие функции TCP поддерживаются и SCTP: уведомление о приеме, повторная передача утерянных данных, сохранение последовательности данных, оконное управление передачей, медленное начало и алгоритмы предотвращения перегрузки линий, а также выборочные уведомления. Есть и два исключения: состояние неполного закрытия и срочные данные.</p>
     <p>8. SCTP позволяет приложению настраивать транспортный уровень по своим потребностям, причем настройка выполняется для каждой ассоциации в отдельности. Эта гибкость в сочетании с универсальным набором значений по умолчанию (для приложений, не нуждающихся в тонкой настройке транспортного уровня) дает приложению нечто большее, нежели оно могло получить при работе с TCP.</p>
     <p>SCTP лишен двух особенностей TCP. Одной из них является состояние неполного (половинного) закрытия соединения. Это состояние возникает, когда приложение закрывает свой конец соединения, но разрешает собеседнику отправлять данные, а само принимает их (мы обсуждали это состояние в разделе 6.6). Приложение входит в это состояние для того, чтобы сообщить собеседнику, что отправка данных завершена. Приложения очень редко используют эту возможность, поэтому при разработке SCTP решено было не заботиться об ее поддержке. Приложениям, которым нужна эта функция, с переходом на SCTP придется изменять протокол уровня приложения, чтобы отправлять сигнал в потоке данных. В некоторых случаях изменения могут быть далеко не тривиальными.</p>
     <p>SCTP не поддерживает и такую функцию TCP, как обработка внеочередных данных (urgent data). Для доставки срочных данных в SCTP можно использовать отдельный поток, однако это не позволяет в точности воспроизвести поведение TCP.</p>
     <p>Для приложений, ориентированных на передачу потока байтов, переход на SCTP может оказаться невыгодным. К таким приложениям относятся <code>telnet</code>, <code>rlogin</code>, <code>rsh</code> и <code>ssh</code>. TCP сегментирует поток байтов на пакеты IP более эффективно, чем SCPT, который пытается сохранять границы сообщений, из-за чего могут получаться блоки, не помещающиеся целиком в IP-дейтаграммы и вызывающие избыточные накладные расходы на передачу.</p>
     <p>В заключение следует сказать, что многим программистам стоит задуматься о переносе своих приложений на SCTP, когда этот протокол станет доступен на их Unix-платформе. Однако чтобы эффективно использовать специальные функции SCTP, нужно хорошо разбираться в них. Пока этот протокол не будет распространен повсеместно, вам может быть выгоднее не уходить от TCP.</p>
    </section>
    <section>
     <title>
      <p>23.13. Резюме</p>
     </title>
     <p>В этой главе мы изучили функцию автоматического закрытия ассоциации SCTP и исследовали, каким образом она может быть использована для ограничения неактивных соединений через сокет типа «один-ко-многим». Мы написали простую функцию, при помощи которой приложение может получать большие сообщения, используя механизм частичной доставки. Мы узнали, каким образом приложение может декодировать уведомления о событиях, происходящих на транспортном уровне. Мы достаточно коротко рассказали о том, как процесс может отправлять неупорядоченные данные, связывать сокет с подмножеством адресов, получать адреса собеседника и свои собственные, а также преобразовывать IP-адрес в идентификатор ассоциации.</p>
     <p>Периодическая проверка соединения для ассоциаций SCTP включена по умолчанию. Мы научились управлять этой функцией посредством простой подпрограммы, которую сами же написали. Мы научились отделять ассоциацию при помощи системного вызова <code>sctp_peeloff</code> и написали параллельно-последовательный сервер, использующий эту возможность. Мы обсудили проблему настройки тайм-аутов повторной передачи SCTP, а также раскрыли преимущества и недостатки перехода на SCTP.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Напишите клиент для тестирования интерфейса частичной доставки из раздела 23.3.</p>
     <p>2. Каким образом можно задействовать механизм частичной доставки, если не отправлять очень больших сообщений?</p>
     <p>3. Перепишите сервер, использующий механизм частичной доставки, таким образом, чтобы он умел обрабатывать соответствующие уведомления.</p>
     <p>4. Каким приложениям пригодится механизм передачи неупорядоченных данных? А каким он не нужен? Поясните.</p>
     <p>5. Каким образом можно протестировать сервер, связывающийся с подмножеством IP-адресов узла?</p>
     <p>6. Предположим, ваше приложение работает в частной сети, причем конечные точки находятся в одной локальной сети. Все серверы и клиенты являются многоинтерфейсными узлами. Каким образом следует настроить параметры повторной передачи, чтобы обнаруживать отказ узла не более, чем за 2 с?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 24</p>
     <p>Внеполосные данные</p>
    </title>
    <section>
     <title>
      <p>24.1. Введение</p>
     </title>
     <p>Ко многим транспортным уровням применима концепция <emphasis>внеполосных данных</emphasis> (<emphasis>out-of-band data</emphasis>), которые иногда называются <emphasis>срочными данными</emphasis> (<emphasis>expedited data</emphasis>). Суть этой концепции заключается в том, что если на одном конце соединения происходит какое-либо важное событие, то требуется быстро сообщить об этом собеседнику. В данном случае «быстро» означает, что сообщение должно быть послано прежде, чем будут посланы какие-либо обычные данные (называемые иногда <emphasis>данными из полосы пропускания</emphasis>), которые уже помещены в очередь для отправки, то есть внеполосные данные имеют более высокий приоритет, чем обычные данные. Для передачи внеполосных данных не создается новое соединение, а используется уже существующее.</p>
     <p>К сожалению, когда мы переходим от общих концепций к реальной ситуации, почти в каждом транспортном протоколе имеется своя реализация внеполосных данных. В качестве крайнего примера можно привести UDP, где внеполосных данных нет вовсе. В этой главе мы уделим основное внимание модели внеполосных данных TCP. Мы приведем различные примеры обработки внеполосных данных в API сокетов и опишем, каким образом внеполосные данные используются приложениями Telnet, Rlogin и FTP. За пределами очерченного круга удаленных интерактивных приложений найти применение внеполосным данным довольно сложно.</p>
    </section>
    <section>
     <title>
      <p>24.2. Внеполосные данные протокола TCP</p>
     </title>
     <section>
      <p>В протоколе TCP нет настоящих <emphasis>внеполосных данных</emphasis>. Вместо этого в TCP предусмотрен так называемый <emphasis>срочный режим</emphasis><a l:href="#n24" type="note">[4]</a> (<emphasis>urgent mode</emphasis>), к рассмотрению которого мы сейчас и приступим. Предположим, процесс записал <emphasis>N</emphasis> байт данных в сокет протокола TCP, и эти данные образуют очередь в буфере отправки сокета и ожидают отправки собеседнику. Ситуацию иллюстрирует рис. 24.1. Байты данных пронумерованы от 1 до <emphasis>N</emphasis>.</p>
      <image l:href="#img_129.png"/>
      <p><strong>Рис. 24.1</strong>. Буфер отправки сокета, содержащий данные для отправки</p>
      <p>Теперь процесс отправляет один байт внеполосных данных, содержащий символ ASCII <code>а</code>, используя функцию <code>send</code> с флагом <code>MSG_OOB</code>:</p>
      <p><code>send(fd, "a", 1, MSG_OOB);</code></p>
      <p>TCP помещает данные в следующую свободную позицию буфера отправки сокета и устанавливает указатель на срочные данные (или просто <emphasis>срочный указатель</emphasis><a l:href="#n25" type="note">[5]</a> — <emphasis>urgent pointer</emphasis>) для этого соединения на первую свободную позицию. Этот буфер показан на рис. 24.2, а байт, содержащий внеполосные данные, помечен буквами <code>OOB</code>.</p>
      <image l:href="#img_130.png"/>
      <p><strong>Рис. 24.2</strong>. Буфер отправки сокета, в который добавлен один байт внеполосных данных</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Срочный указатель TCP указывает на байт данных, который следует за последним байтом внеполосных данных (то есть данных, снабженных флагом MSG_OOB). В книге [111] на с. 292-296 говорится, что это исторически сложившаяся особенность, которая теперь эмулируется во всех реализациях. Если посылающий и принимающий протоколы TCP одинаково интерпретируют срочный указатель TCP, беспокоиться не о чем.</p>
      </cite>
      <p>Если состояние буфера таково, как показано на рис. 24.2, то в заголовке TCP следующего отправленного сегмента будет установлен флаг URG, а поле смещения срочных данных (или просто <emphasis>поле срочного смещения</emphasis><a l:href="#n26" type="note">[6]</a>) будет указывать на байт, следующий за байтом с внеполосными данными. Но этот сегмент может содержать байт, помеченный как OOB, а может и не содержать его. Будет ли послан этот байт, зависит от количества предшествующих ему байтов в буфере отправки сокета, от размера сегмента, который TCP пересылает собеседнику, и от текущего размера окна, объявленного собеседником.</p>
      <p>Выше мы использовали термины «срочный указатель» (urgent pointer) и «срочное смещение» (urgent offset). На уровне TCP эти термины имеют различные значения. Величина, представленная 16 битами в заголовке TCP, называется срочным смещением и должна быть прибавлена к полю последовательного номера в заголовке TCP для получения 32-разрядного последовательного номера последнего байта срочных данных (то есть срочного указателя). TCP использует срочное смещение, только если в заголовке установлен другой бит, называемый флагом URG. Программисту можно не заботиться об этом различии и работать только со срочным указателем TCP.</p>
      <p>Важная характеристика срочного режима TCP заключается в следующем: заголовок TCP указывает на то, что отправитель вошел в срочный режим (то есть флаг URG установлен вместе со срочным смещением), но фактической отправки байта данных, на который указывает срочный указатель, не требуется. Действительно, если поток данных TCP остановлен функциями управления потоком (когда буфер приема сокета получателя заполнен и TCP получателя объявил нулевое окно для отправляющего TCP), то срочное уведомление отправляется без каких-либо данных [128, с. 1016–1017], как показано в листингах 24.8 и 24.9. Это одна из причин, по которой в приложениях используется срочный режим TCP (то есть внеполосные данные): срочное уведомление всегда отсылается собеседнику, даже если поток данных остановлен функциями управления потоком TCP.</p>
      <p>Что произойдет, если мы отправим несколько байтов внеполосных данных, как в следующем примере?</p>
      <p><code>send(fd, "abc", 3, MSG_OOB);</code></p>
      <p>В этом примере срочный указатель TCP указывает на байт, следующий за последним байтом, и таким образом, последний байт (<code>с</code>) считается байтом внеполосных данных.</p>
      <p>Посмотрим теперь, как выглядит процесс отправки внеполосных данных с точки зрения принимающей стороны.</p>
      <p>1. Когда TCP получает сегмент, в котором установлен флаг URG, срочный указатель проверяется для выяснения того, указывает ли он на <emphasis>новые</emphasis> внеполосные данные. Иначе говоря, проверяется, впервые ли этот конкретный байт передается в срочном режиме TCP. Дело в том, что часто отправляющий TCP посылает несколько сегментов (обычно в течение короткого промежутка времени), содержащих флаг URG, в которых срочный указатель указывает на один и тот же байт данных. Только первый из этих сегментов фактически уведомляет принимающий процесс о прибытии новых внеполосных данных.</p>
      <p>2. Принимающий процесс извещается о том, что прибыли новые внеполосные данные. Сначала владельцу сокета посылается сигнал <code>SIGURG</code>. При этом предполагается, что для установления владельца сокета была вызвана функция <code>fcntl</code> или <code>ioctl</code> (см. табл. 7.9) и что для данного сигнала процессом был установлен обработчик сигнала. Затем, если процесс блокирован в вызове функции <code>select</code>, которая ждет возникновения исключительной ситуации для дескриптора сокета, происходит возврат из этой функции.</p>
      <p>Эти два уведомления действуют в том случае, когда прибывает новый срочный указатель, вне зависимости от того, принят ли байт, на который он указывает.</p>
      <p>В потоке данных может быть только одна отметка OOB (один срочный указатель). Если новый срочный байт отправляется до того, как будет принят старый, последний просто сбрасывается и перестает быть срочным.</p>
      <p>3. Когда байт данных, на который указывает срочный указатель, фактически прибывает на принимающий TCP, этот байт может быть помещен отдельно или оставлен вместе с другими данными. По умолчанию параметр сокета <code>SO_OOBINLINE</code> <emphasis>не</emphasis> установлен, поэтому внеполосный байт не размещается в приемном буфере сокета. Вместо этого содержащиеся в нем данные помещаются в отдельный внеполосный буфер размером в один байт, предназначенный специально для этого соединения [128, с. 986–988]. Для процесса единственным способом прочесть данные из этого специального однобайтового буфера является вызов функции <code>recv</code>, <code>recvfrom</code> или <code>recvmsg</code> с заданием флага <code>MSG_OOB</code>. Если новый срочный байт прибывает до того, как будет считан старый, новое значение записывается в буфер поверх прежнего.</p>
      <p>Однако если процесс устанавливает параметр сокета <code>SO_OOBINLINE</code>, то байт данных, на который указывает срочный указатель TCP, остается в обычном буфере приема сокета. В этом случае процесс не может задать флаг <code>MSG_OOB</code> для считывания данных, содержащихся во внеполосном байте. Процесс сможет распознать этот байт, только когда дойдет до него и проверит <emphasis>отметку внеполосных данных</emphasis> (<emphasis>out-of-band mark</emphasis>) для данного соединения, как показано в разделе 24.3. Возможны следующие ошибки:</p>
      <p>1. Если процесс запрашивает внеполосные данные (то есть устанавливает флаг <code>MSG_OOB</code>), но собеседник таких данных не послал, возвращается <code>EINVAL</code>.</p>
      <p>2. Если процесс был уведомлен о том, что собеседник послал содержащий внеполосные данные байт (например, с помощью функции <code>select</code> или сигнала <code>SIGURG</code>), и пытается считать эти данные, когда указанный байт еще не прибыл, возвращается ошибка <code>EWOULDBLOCK</code>. В такой ситуации все, что может сделать процесс, — это считать данные из приемного буфера сокета (возможно, сбрасывая данные, если отсутствует свободное место для их хранения), чтобы освободить место в буфере для приема байта внеполосных данных, посылаемых собеседником.</p>
      <p>3. Если процесс пытается считать одни и те же внеполосные данные несколько раз, возвращается ошибка <code>EINVAL</code>.</p>
      <p>4. Если процесс установил параметр сокета <code>SO_OOBINLINE</code>, а затем пытается считать внеполосные данные, задавая флаг <code>MSG_OOB</code>, возвращается <code>EINVAL</code>.</p>
     </section>
     <section>
      <title>
       <p>Простой пример использования сигнала SIGURG</p>
      </title>
      <p>Теперь мы рассмотрим тривиальный пример отправки и получения внеполосных данных. В листинге 24.1<a l:href="#n27" type="note">[1]</a> показана программа отправки этих данных.</p>
      <p id="___temp_view_cursor_for_clear_format__1"><strong>Листинг 24.1</strong>. Простая программа отправки внеполосных данных</p>
      <p><code>//oob/tcpsend01.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd;</code></p>
      <empty-line/>
      <p><code> 6  if (argc != 3)</code></p>
      <p><code> 7   err_quit("usage: tcpsend01 &lt;host&gt; &lt;port#&gt;");</code></p>
      <p><code> 8  sockfd = Tcp_connect(argv[1], argv[2]);</code></p>
      <p><code> 9  Write(sockfd, "123", 3);</code></p>
      <p><code>10  printf("wrote 3 bytes of normal data\n");</code></p>
      <p><code>11  sleep(1);</code></p>
      <empty-line/>
      <p><code>12  Send(sockfd, "4", 1, MSG_OOB);</code></p>
      <p><code>13  printf("wrote 1 byte of OOB data\n");</code></p>
      <p><code>14  sleep(1);</code></p>
      <empty-line/>
      <p><code>15  Write(sockfd, "56", 2);</code></p>
      <p><code>16  printf("wrote 2 bytes of normal data\n");</code></p>
      <p><code>17  sleep(1);</code></p>
      <empty-line/>
      <p><code>18  Send(sockfd, "7", 1, MSG_OOB);</code></p>
      <p><code>19  printf("wrote 1 byte of OOB data\n");</code></p>
      <p><code>20  sleep(1);</code></p>
      <empty-line/>
      <p><code>21  Write(sockfd, "89", 2);</code></p>
      <p><code>22  printf("wrote 2 bytes of normal data\n");</code></p>
      <p><code>23  sleep(1);</code></p>
      <empty-line/>
      <p><code>24  exit(0);</code></p>
      <p><code>25 }</code></p>
      <p>Отправлены 9 байт, промежуток между операциями по отправке установлен с помощью функции <code>sleep</code> равным одной секунде. Назначение этого промежутка в том, чтобы данные каждой из функций <code>write</code> или <code>send</code> были переданы и получены на другом конце как отдельный сегмент TCP. Несколько позже мы обсудим некоторые вопросы согласования во времени при пересылке внеполосных данных. После выполнения данной программы мы видим вполне предсказуемый результат:</p>
      <p><code>macosx % <strong>tcpsend01 freebsd 9999</strong></code></p>
      <p><code>wrote 3 bytes of normal data</code></p>
      <p><code>wrote 1 byte of OOB data</code></p>
      <p><code>wrote 2 bytes of normal data</code></p>
      <p><code>wrote 1 byte of OOB data</code></p>
      <p><code>wrote 2 bytes of normal data</code></p>
      <p>В листинге 24.2 показана принимающая программа.</p>
      <p><strong>Листинг 24.2</strong>. Простая программа для получения внеполосных данных</p>
      <p><code>//oob/tcprecv01.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int listenfd, connfd;</code></p>
      <empty-line/>
      <p><code> 3 void sig_urg(int);</code></p>
      <empty-line/>
      <p><code> 4 int</code></p>
      <p><code> 5 main(int argc, char **argv)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  int n;</code></p>
      <p><code> 8  char buff[100];</code></p>
      <empty-line/>
      <p><code> 9  if (argc == 2)</code></p>
      <p><code>10   listenfd = Tcp_listen(NULL, argv[1], NULL);</code></p>
      <p><code>11  else if (argc == 3)</code></p>
      <p><code>12   listenfd = Tcp_listen(argv[1], argv[2], NULL);</code></p>
      <p><code>13  else</code></p>
      <p><code>14   err_quit("usage: tcprecv01 [ &lt;host&gt; ] &lt;port#&gt;");</code></p>
      <empty-line/>
      <p><code>15  connfd = Accept(listenfd, NULL, NULL);</code></p>
      <empty-line/>
      <p><code>16  Signal(SIGURG, sig_urg);</code></p>
      <p><code>17  Fcntl(connfd, F_SETOWN, getpid());</code></p>
      <empty-line/>
      <p><code>18  for (;;) {</code></p>
      <p><code>19   if ((n = Read(connfd, buff, sizeof(buff) - 1)) == 0) {</code></p>
      <p><code>20    printf("received EOF\n");</code></p>
      <p><code>21    exit(0);</code></p>
      <p><code>22   }</code></p>
      <p><code>23   buff[n] = 0; /* завершающий нуль */</code></p>
      <p><code>24   printf("read bytes: %s\n", n, buff);</code></p>
      <p><code>25  }</code></p>
      <p><code>26 }</code></p>
      <empty-line/>
      <p><code>27 void</code></p>
      <p><code>28 sig_urg(int signo)</code></p>
      <p><code>29 {</code></p>
      <p><code>30  int n;</code></p>
      <p><code>31  char buff[100];</code></p>
      <empty-line/>
      <p><code>32  printf("SIGURG received\n");</code></p>
      <p><code>33  n = Recv(connfd, buff, sizeof(buff) - 1, MSG_OOB);</code></p>
      <p><code>34  buff[n] = 0; /* завершающий нуль */</code></p>
      <p><code>35  printf("read OOB byte: %s\n", n, buff);</code></p>
      <p><code>36 }</code></p>
      <subtitle>Установка обработчика сигнала и владельца сокета</subtitle>
      <p><code>16-17</code> Устанавливается обработчик сигнала <code>SIGURG</code> и функция <code>fcntl</code> задает владельца сокета для данного соединения.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Обратите внимание, что мы не задаем обработчик сигнала, пока не завершается функция accept. Существует небольшая вероятность того, что внеполосные данные могут прибыть после того, как TCP завершит трехэтапное рукопожатие, но до завершения функции accept. Внеполосные данные мы в этом случае потеряем. Допустим, что мы установили обработчик сигнала перед вызовом функции accept, а также задали владельца прослушиваемого сокета (который затем стал бы владельцем присоединенного сокета). Тогда, если внеполосные данные прибудут до завершения функции accept, наш обработчик сигналов еще не получит значения для дескриптора connfd. Если данный сценарий важен для приложения, следует инициализировать connfd, «вручную» присвоив этому дескриптору значение -1, добавить в обработчик проверку равенства connfd ==-1 и при истинности этого условия просто установить флаг, который будет проверяться в главном цикле после вызова accept. За счет этого главный цикл сможет узнать о поступлении внеполосных данных и считать их. Можно заблокировать сигнал на время вызова accept, но при этом программа будет страдать от всех возможных ситуаций гонок, описанных в разделе 20.5.</p>
      </cite>
      <p><code>18-25</code> Процесс считывает данные из сокета и выводит каждую строку, которая возвращается функцией <code>read</code>. После того как отправитель разрывает соединение, то же самое делает и получатель.</p>
      <subtitle>Обработчик сигнала SIGURG</subtitle>
      <p><code>27-36</code> Наш обработчик сигнала вызывает функцию <code>printf</code>, считывает внеполосные данные, устанавливая флаг <code>MSG_OOB</code>, а затем выводит полученные данные. Обратите внимание, что при вызове функции recv мы запрашиваем до 100 байт, но, как мы вскоре увидим, всегда возвращается только один байт внеполосных данных.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Как сказано ранее, вызов ненадежной функции printf из обработчика сигнала не рекомендуется. Мы делаем это просто для того, чтобы увидеть, что произойдет с нашей программой.</p>
      </cite>
      <p>Ниже приведен результат, который получается, когда мы запускаем эту программу, а затем — программу для отправки внеполосных данных, приведенную в листинге 24.1.</p>
      <p><code>freebsd % <strong>tcprecv01 9999</strong></code></p>
      <p><code>read 3 bytes: 123</code></p>
      <p><code>SIGURG received</code></p>
      <p><code>read 1 OOB byte: 4</code></p>
      <p><code>read 2 bytes: 56</code></p>
      <p><code>SIGURG received</code></p>
      <p><code>read 1 OOB byte: 7</code></p>
      <p><code>read 2 bytes: 89</code></p>
      <p><code>received EOF</code></p>
      <p>Результаты оказались такими, как мы и ожидали. Каждый раз, когда отправитель посылает внеполосные данные, для получателя генерируется сигнал <code>SIGURG</code>, после чего получатель считывает один байт, содержащий внеполосные данные.</p>
     </section>
     <section>
      <title>
       <p>Простой пример использования функции select</p>
      </title>
      <p>Теперь мы переделаем код нашего получателя внеполосных данных и вместо сигнала <code>SIGURG</code> будем использовать функцию <code>select</code>. В листинге 24.3 показана принимающая программа.</p>
      <p><strong>Листинг 24.3</strong>. Принимающая программа, в которой (ошибочно) используется функция select для уведомления о получении внеполосных данных</p>
      <p><code>//oob/tcprecv02.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int listenfd, connfd, n;</code></p>
      <p><code> 6  char buff[100];</code></p>
      <p><code> 7  fd_set rset, xset;</code></p>
      <empty-line/>
      <p><code> 8  if (argc == 2)</code></p>
      <p><code> 9   listenfd = Tcp_listen(NULL, argv[1], NULL);</code></p>
      <p><code>10  else if (argc ==3)</code></p>
      <p><code>11   listenfd = Tcp_listen(argv[1], argv[2], NULL);</code></p>
      <p><code>12  else</code></p>
      <p><code>13   err_quit("usage: tcprecv02 [ &lt;host&gt; ] &lt;port#&gt;");</code></p>
      <empty-line/>
      <p><code>14  connfd = Accept(listenfd, NULL, NULL);</code></p>
      <empty-line/>
      <p><code>15  FD_ZERO(&amp;rset);</code></p>
      <p><code>16  FD_ZERO(&amp;xset);</code></p>
      <p><code>17  for (;;) {</code></p>
      <p><code>18   FD_SET(connfd, &amp;rset);</code></p>
      <p><code>19   FD_SET(connfd, &amp;xset);</code></p>
      <empty-line/>
      <p><code>20   Select(connfd + 1, &amp;rset, NULL, &amp;xset, NULL);</code></p>
      <empty-line/>
      <p><code>21   if (FD_ISSET(connfd, &amp;xset)) {</code></p>
      <p><code>22    n = Recv(connfd, buff, sizeof(buff) - 1, MSG_OOB);</code></p>
      <p><code>23    buff[n] =0; /* завершающий нуль */</code></p>
      <p><code>24    printf("read OOB byte: %s\n", n, buff);</code></p>
      <p><code>25   }</code></p>
      <p><code>26   if (FD_ISSET(connfd, &amp;rset)) {</code></p>
      <p><code>27    if ((n = Read(connfd, buff, sizeof(buff) - 1)) == 0) {</code></p>
      <p><code>28     printf("received EOF\n");</code></p>
      <p><code>29     exit(0);</code></p>
      <p><code>30    }</code></p>
      <p><code>31    buff[n] = 0; /* завершающий нуль */</code></p>
      <p><code>32    printf("read bytes: %s\n", n, buff);</code></p>
      <p><code>33   }</code></p>
      <p><code>34  }</code></p>
      <p><code>35 }</code></p>
      <p><code>15-20</code> Процесс вызывает функцию <code>select</code>, которая ожидает получения либо обычных данных (набор дескрипторов для чтения, <code>rset</code>), либо внеполосных (набор дескрипторов для обработки исключений, <code>xset</code>). В обоих случаях полученные данные выводятся.</p>
      <p>Если мы запустим эту программу, а затем — программу для отправки, которая приведена в листинге 24.1, то столкнемся со следующей ошибкой:</p>
      <p><code>freebsd4 % <strong>tcprecv02 9999</strong></code></p>
      <p><code>read 3 bytes: 123</code></p>
      <p><code>read 1 OOB byte: 4</code></p>
      <p><code>recv error: Invalid argument</code></p>
      <p>Проблема заключается в том, что функция <code>select</code> будет сообщать об исключительной ситуации, пока процесс не считает данные, находящиеся за отметкой внеполосных данных (то есть после них [128, с. 530-531]). Мы не можем считывать внеполосные данные больше одного раза, так как после первого же их считывания ядро очищает буфер, содержащий один байт внеполосных данных. Когда мы вызываем функцию <code>recv</code>, устанавливая флаг <code>MSG_OOB</code> во второй раз, она возвращает ошибку <code>EINVAL</code>.</p>
      <p>Чтобы решить эту проблему, нужно вызывать функцию <code>select</code> для проверки на наличие исключительной ситуации только после того, как будут приняты все обычные данные. В листинге 24.4 показана модифицированная версия принимающей программы из листинга 24.3. В этой версии описанный сценарий обрабатывается корректно.</p>
      <p><strong>Листинг 24.4</strong>. Модификация программы, приведенной в листинге 24.3. Функция select применяется для проверки исключительной ситуации корректным образом</p>
      <p><code>//oob/tcprecv03.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int listenfd, connfd, n, justreadoob = 0;</code></p>
      <p><code> 6  char buff[100];</code></p>
      <p><code> 7  fd_set rset, xset;</code></p>
      <empty-line/>
      <p><code> 8  if (argc == 2)</code></p>
      <p><code> 9   listenfd = Tcp_listen(NULL, argv[1], NULL);</code></p>
      <p><code>10  else if (argc == 3)</code></p>
      <p><code>11   listenfd = Tcp_1isten(argv[1], argv[2], NULL);</code></p>
      <p><code>12  else</code></p>
      <p><code>13   err_quit("usage: tcprecv03 [ &lt;host&gt; ] &lt;port#&gt;");</code></p>
      <empty-line/>
      <p><code>14  connfd = Accept(listenfd, NULL, NULL);</code></p>
      <empty-line/>
      <p><code>15  FD_ZERO(&amp;rset);</code></p>
      <p><code>16  FD_ZERO(&amp;xset);</code></p>
      <p><code>17  for (;;) {</code></p>
      <p><code>18   FD_SET(connfd, &amp;rset);</code></p>
      <p><code>19   if (justreadoob == 0)</code></p>
      <p><code>20    FD_SET(connfd, &amp;xset);</code></p>
      <p><code>21   Select(connfd + 1, &amp;rset, NULL, &amp;xset, NULL);</code></p>
      <empty-line/>
      <p><code>22   if (FD_ISSET(connfd, &amp;xset)) {</code></p>
      <p><code>23    n = Recv(connfd, buff, sizeof(buff) - 1, MSG_OOB);</code></p>
      <p><code>24    buff[n] = 0; /* завершающий нуль */</code></p>
      <p><code>25    printf("read %d OOB byte: %s\n", n, buff);</code></p>
      <p><code>26    justreadoob = 1;</code></p>
      <p><code>27    FD_CLR(connfd, &amp;xset);</code></p>
      <p><code>28   }</code></p>
      <p><code>29   if (FD_ISSET(connfd, &amp;rset)) {</code></p>
      <p><code>30    if ((n = Read(connfd, buff, sizeof(buff) - 1)) == 0) {</code></p>
      <p><code>31     printf("received EOF\n");</code></p>
      <p><code>32     exit(0);</code></p>
      <p><code>33    }</code></p>
      <p><code>34    buff[n] = 0; /* завершающий нуль */</code></p>
      <p><code>35    printf("read %d bytes: %s\n", n, buff);</code></p>
      <p><code>36    justreadoob = 0;</code></p>
      <p><code>37   }</code></p>
      <p><code>38  }</code></p>
      <p><code>39 }</code></p>
      <p><code>5</code> Мы объявляем новую переменную с именем <code>justreadoob</code>, которая указывает, какие данные мы считываем — внеполосные или обычные. Этот флаг определяет, нужно ли вызывать функцию <code>select</code> для проверки на наличие исключительной ситуации.</p>
      <p><code>26-27</code> Когда мы устанавливаем флаг <code>justreadoob</code>, мы также должны выключить бит соответствующего дескриптора в наборе для проверки исключительных ситуаций.</p>
      <p>Теперь программа работает так, как мы ожидали.</p>
     </section>
    </section>
    <section>
     <title>
      <p>24.3. Функция sockatmark</p>
     </title>
     <section>
      <p>С приемом внеполосных данных всегда связана так называемая <emphasis>отметка внеполосных данных</emphasis> (<emphasis>out-of-bandmark</emphasis>). Это позиция в потоке обычных данных <emphasis>на стороне отправителя</emphasis>, соответствующая тому моменту; когда посылающий процесс отправляет байт, содержащий внеполосные данные. Считывая данные из сокета, принимающий процесс путем вызова функции <code>sockatmark</code> определяет, находится ли он в данный момент на этой отметке.</p>
      <p><code>#include &lt;sys/socket.h&gt;</code></p>
      <empty-line/>
      <p><code>int sockatmark(int <emphasis>sockfd</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: 1, если находится на отметке внеполосных данных: 0, если не на отметке; -1 в случае ошибки</emphasis></code></p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Эта функция появилась в POSIX. Разработчики стандарта POSIX стремятся заменить отдельными функциями все вызовы ioctl с различными параметрами.</p>
      </cite>
      <p>В листинге 24.5 показана реализация этой функции с помощью поддерживаемого в большинстве систем параметра <code>SIOCATMARK</code> функции <code>ioctl</code>.</p>
      <p><strong>Листинг 24.5</strong>. Функция sockatmark реализована с использованием функции ioctl</p>
      <p><code>//lib/sockatmark.c</code></p>
      <p><code>1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code>2 int</code></p>
      <p><code>3 sockatmark(int fd)</code></p>
      <p><code>4 {</code></p>
      <p><code>5  int flag;</code></p>
      <empty-line/>
      <p><code>6  if (ioctl(fd, SIOCATMARK, &amp;flag) &lt; 0)</code></p>
      <p><code>7   return (-1);</code></p>
      <p><code>8  return (flag != 0 ? 1 : 0);</code></p>
      <p><code>9 }</code></p>
      <p>Отметка внеполосных данных применима независимо от того, как принимающий процесс получает внеполосные данные: вместе с обычными данными (параметр сокета <code>SO_OOBINLINE</code>) или отдельно (флаг <code>MSG_OOB</code>). Отметка внеполосных данных часто используется для того, чтобы принимающий процесс мог интерпретировать получаемые данные специальным образом до тех пор, пока он не дойдет до этой отметки.</p>
     </section>
     <section>
      <title>
       <p>Пример: особенности отметки внеполосных данных</p>
      </title>
      <p>Далее мы приводим простой пример, иллюстрирующий следующие две особенности отметки внеполосных данных:</p>
      <p>1. Отметка внеполосных данных всегда указывает на один байт дальше конечного байта обычных данных. Это означает, что, когда внеполосные данные получены вместе с обычными, функция <code>sockatmark</code> возвращает 1, если следующий считываемый байт был послан с флагом <code>MSG_OOB</code>. Если параметр <code>SO_OOBINLINE</code> не включен (состояние по умолчанию), то функция <code>sockatmark</code> возвращает 1, когда следующий байт данных является первым байтом, посланным следом за внеполосными данными.</p>
      <p>2. Операция считывания всегда останавливается на отметке внеполосных данных [128, с. 519–520]. Это означает, что если в приемном буфере сокета 100 байт, но только 5 из них расположены перед отметкой внеполосных данных, то когда процесс выполнит функцию <code>read</code>, запрашивая 100 байт, возвратятся только 5 байт, расположенные до этой отметки. Эта вынужденная остановка на отметке позволяет процессу вызвать функцию <code>sockatmark</code>, которая определит, находится ли указатель буфера на отметке внеполосных данных.</p>
      <p>В листинге 24.6 показана наша программа отправки. Она посылает три байта обычных данных, один байт внеполосных данных, а затем еще один байт обычных данных. Паузы между этими операциями отсутствуют.</p>
      <p>В листинге 24.7 показана принимающая программа. В ней не используется ни функция <code>select</code>, ни сигнал <code>SIGURG</code>. Вместо этого в ней вызывается функция <code>sokatmark</code>, определяющая положение байта внеполосных данных.</p>
      <p><strong>Листинг 24.6</strong>. Программа отправки</p>
      <p><code>//oob/tcpsen04.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd;</code></p>
      <empty-line/>
      <p><code> 6  if (argc != 3)</code></p>
      <p><code> 7   err_quit("usage: tcpsend04 &lt;host&gt; &lt;port#&gt;");</code></p>
      <empty-line/>
      <p><code> 8  sockfd = Tcp_connect(argv[1], argv[2]);</code></p>
      <empty-line/>
      <p><code> 9  Write(sockfd, "123", 3);</code></p>
      <p><code>10  printf("wrote 3 bytes of normal data\n");</code></p>
      <empty-line/>
      <p><code>11  Send(sockfd, "4", 1, MSG_OOB);</code></p>
      <p><code>12  printf("wrote 1 byte of OOB data\n");</code></p>
      <empty-line/>
      <p><code>13  Write(sockfd, "5", 1);</code></p>
      <p><code>14  printf("wrote 1 byte of normal data\n");</code></p>
      <empty-line/>
      <p><code>15  exit(0);</code></p>
      <p><code>16 }</code></p>
      <p><strong>Листинг 24.7</strong>. Принимающая программа, в которой вызывается функция sokatmark</p>
      <p><code>//oob/tcprecv04.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int listenfd, connfd, n, on = 1;</code></p>
      <p><code> 6  char buff[100];</code></p>
      <empty-line/>
      <p><code> 7  if (argc == 2)</code></p>
      <p><code> 8   listenfd = Tcp_listen(NULL, argv[1], NULL);</code></p>
      <p><code> 9  else if (argc == 3)</code></p>
      <p><code>10   listenfd = Tcp_listen(argv[1], argv[2], NULL);</code></p>
      <p><code>11  else</code></p>
      <p><code>12   err_quit("usage- tcprecv04 [ &lt;host&gt; ] &lt;port#&gt;");</code></p>
      <empty-line/>
      <p><code>13  Setsockopt(listenfd, SOL_SOCKET, SO_OOBINLINE, &amp;on, sizeof(on));</code></p>
      <empty-line/>
      <p><code>14  connfd = Accept(listenfd, NULL, NULL);</code></p>
      <p><code>15  sleep(5);</code></p>
      <empty-line/>
      <p><code>16  for (;;) {</code></p>
      <p><code>17   if (Sockatmark(connfd))</code></p>
      <p><code>18    printf("at OOB mark\n");</code></p>
      <empty-line/>
      <p><code>19   if ((n = Read(connfd, buff, sizeof(buff) - 1)) == 0) {</code></p>
      <p><code>20    printf("received EOF\n");</code></p>
      <p><code>21    exit(0);</code></p>
      <p><code>22   }</code></p>
      <p><code>23   buff[n] = 0; /* завершающий нуль */</code></p>
      <p><code>24   printf("read %d bytes: %s\n", n; buff);</code></p>
      <p><code>25  }</code></p>
      <p><code>26 }</code></p>
      <subtitle>Включение параметра сокета SO_OOBINLINE</subtitle>
      <p><code>13</code> Мы хотим принимать внеполосные данные вместе с обычными данными, поэтому нам нужно включить параметр <code>SO_OOBINLINE</code>. Но если мы будем ждать, когда выполнится функция accept и установит этот параметр для присоединенного сокета, трехэтапное рукопожатие завершится и внеполосные данные могут уже прибыть. Поэтому нам нужно установить этот параметр еще для прослушиваемого сокета, помня о том, что все параметры прослушиваемого сокета наследуются присоединенным сокетом (см. раздел 7.4).</p>
      <subtitle>Вызов функции sleep после вызова функции accept</subtitle>
      <p><code>14-15</code> После того как выполнена функция <code>accept</code>, получатель переходит в спящее состояние, что позволяет получить все данные, посланные отправителем. Это позволяет нам продемонстрировать, что функция read останавливается на отметке внеполосных данных, даже если в приемном буфере сокета имеются дополнительные данные.</p>
      <subtitle>Считывание всех отправленных данных</subtitle>
      <p><code>16-25</code> В программе имеется цикл, в котором вызывается функция <code>read</code> и выводятся полученные данные. Но перед вызовом функции <code>read</code> функция <code>sockatmark</code> проверяет, находится ли указатель буфера на отметке внеполосных данных.</p>
      <p>После выполнения этой программы мы получаем следующий результат:</p>
      <p><code>freebsd4 % <strong>tcprecv04 6666</strong></code></p>
      <p><code>read 3 bytes: 123</code></p>
      <p><code>at OOB mark</code></p>
      <p><code>read 2 bytes: 45</code></p>
      <p><code>received EOF</code></p>
      <p>Хотя принимающий TCP получил все посланные данные, первый вызов функции <code>read</code> возвращает только три байта, так как была обнаружена отметка внеполосных данных. Следующий считанный байт — это байт, содержащий внеполосные данные (его значение равно 4), так как мы дали ядру указание поместить внеполосные данные вместе с обычными.</p>
     </section>
     <section>
      <title>
       <p>Пример: дополнительные свойства внеполосных данных</p>
      </title>
      <p>Теперь мы покажем другой столь же простой пример, иллюстрирующий две дополнительные особенности внеполосных данных, о которых мы уже упоминали ранее.</p>
      <p>1. TCP посылает уведомление об отправке внеполосных данных (их срочный указатель), даже если поток данных остановлен функциями управления потоком.</p>
      <p>2. Принимающий процесс может получить уведомление о том, что отправитель отослал внеполосные данные (с помощью сигнала <code>SIGURG</code> или функции <code>select</code>) <emphasis>до того</emphasis>, как эти данные фактически прибудут. Если после получения этого уведомления процесс вызывает функцию <code>recv</code>, задавая флаг <code>MSG_OOB</code>, а внеполосные данные еще не прибыли, то будет возвращена ошибка <code>EWOULDBLOCK</code>.</p>
      <p>В листинге 24.8 приведена программа отправки.</p>
      <p><strong>Листинг 24.8</strong>. Программа отправки</p>
      <p><code>//oob/tcpsend05.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd, size;</code></p>
      <p><code> 6  char buff[16384];</code></p>
      <empty-line/>
      <p><code> 7  if (argc != 3)</code></p>
      <p><code> 8   err_quit("usage: tcpsend04 &lt;host&gt; &lt;port#&gt;");</code></p>
      <empty-line/>
      <p><code> 9  sockfd = Tcp_connect(argv[1], argv[2]);</code></p>
      <empty-line/>
      <p><code>10  size = 32768;</code></p>
      <p><code>11  Setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &amp;size, sizeof(size));</code></p>
      <empty-line/>
      <p><code>12  Write(sockfd, buff, 16384);</code></p>
      <p><code>13  printf("wrote 16384 bytes of normal data\n");</code></p>
      <p><code>14  sleep(5);</code></p>
      <empty-line/>
      <p><code>15  Send(sockfd, "a", 1, MSG_OOB);</code></p>
      <p><code>16  printf("wrote 1 byte of OOB data\n");</code></p>
      <empty-line/>
      <p><code>17  Write(sockfd, buff, 1024);</code></p>
      <p><code>18  printf("wrote 1024 bytes of normal data\n");</code></p>
      <empty-line/>
      <p><code>19  exit(0);</code></p>
      <p><code>20 }</code></p>
      <p><code>9-19</code> Этот процесс устанавливает размер буфера отправки сокета равным 32 768 байт, записывает 16 384 байт обычных данных, а затем на 5 с переходит в спящее состояние. Чуть ниже мы увидим, что приемник устанавливает размер приемного буфера сокета равным 4096 байт, поэтому данные, отправленные отсылающим TCP, с гарантией заполнят приемный буфер сокета получателя. Затем отправитель посылает один байт внеполосных данных, за которым следуют 1024 байт обычных данных, и, наконец, закрывает соединение.</p>
      <p>В листинге 24.9 представлена принимающая программа.</p>
      <p><strong>Листинг 24.9</strong>. Принимающая программа</p>
      <p><code>//oob/tcprecv05.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int listenfd, connfd;</code></p>
      <empty-line/>
      <p><code> 3 void sig_urg(int);</code></p>
      <empty-line/>
      <p><code> 4 int</code></p>
      <p><code> 5 main(int argc, char **argv)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  int size;</code></p>
      <empty-line/>
      <p><code> 8  if (argc == 2)</code></p>
      <p><code> 9   listenfd = Tcp_listen(NULL, argv[1], NULL);</code></p>
      <p><code>10  else if (argc == 3)</code></p>
      <p><code>11   listenfd = Tcp_listen(argv[1], argv[2], NULL);</code></p>
      <p><code>12  else</code></p>
      <p><code>13   err_quit("usage: tcprecv05 [ &lt;host&gt; ] &lt;port#&gt;");</code></p>
      <empty-line/>
      <p><code>14  size = 4096;</code></p>
      <p><code>15  Setsockopt(listenfd, SOL_SOCKET, SO_RCVBUF, &amp;size, sizeof(size));</code></p>
      <empty-line/>
      <p><code>16  connfd = Accept(listenfd, NULL, NULL);</code></p>
      <empty-line/>
      <p><code>17  Signal(SIGURG, sig_urg);</code></p>
      <p><code>18  Fcntl(connfd, F_SETOWN, getpid());</code></p>
      <empty-line/>
      <p><code>19  for (;;)</code></p>
      <p><code>20   pause();</code></p>
      <p><code>21 }</code></p>
      <empty-line/>
      <p><code>22 void</code></p>
      <p><code>23 sig_urg(int signo)</code></p>
      <p><code>24 {</code></p>
      <p><code>25  int n;</code></p>
      <p><code>26  char buff[2048];</code></p>
      <empty-line/>
      <p><code>27  printf("SIGURG received\n");</code></p>
      <p><code>28  n = Recv(connfd, buff, sizeof(buff) - 1, MSG_OOB);</code></p>
      <p><code>29  buff[n] = 0; /* завершающий пустой байт */</code></p>
      <p><code>30  printf("read %d OOB byte\n", n);</code></p>
      <p><code>31 }</code></p>
      <p><code>14-20</code> Принимающий процесс устанавливает размер приемного буфера сокета приемника равным 4096 байт. Этот размер наследуется присоединенным сокетом после установления соединения. Затем процесс вызывает функцию <code>accept</code>, задает обработчик для сигнала <code>SIGURG</code> и задает владельца сокета. В главном цикле (бесконечном) вызывается функция <code>pause</code>.</p>
      <p><code>22-31</code> Обработчик сигнала вызывает функцию <code>recv</code> для считывания внеполосных данных.</p>
      <p>Если мы запускаем сначала принимающую программу, а затем программу отправки, то получаем следующий результат выполнения программы отправки:</p>
      <p><code>macosx % <strong>tcpsend05 freebsd 5555</strong></code></p>
      <p><code>wrote 16384 bytes of normal data</code></p>
      <p><code>wrote 1 byte of OOB data</code></p>
      <p><code>wrote 1024 bytes of normal data</code></p>
      <p>Как и ожидалось, все данные помещаются в буфер отправки сокета отправителя, и программа завершается. Ниже приведен результат работы принимающей программы:</p>
      <p><code>freebsd4 % <strong>tcprecv05 5555</strong></code></p>
      <p><code>SIGURG received</code></p>
      <p><code>recv error: Resource temporarily unavailable</code></p>
      <p>Сообщение об ошибке, которое выдает наша функция <code>err_sys</code>, соответствует ошибке <code>EAGAIN</code>, которая в FreeBSD аналогична ошибке <code>EWOULDBLOCK</code>. TCP посылает уведомление об отправке внеполосных данных принимающему TCP, который в результате генерирует сигнал <code>SIGURG</code> для принимающего процесса. Но когда вызывается функция <code>recv</code> и задается флаг <code>MSG_OOB</code>, байт с внеполосными данными не может быть прочитан.</p>
      <p>Для решения этой проблемы необходимо, чтобы получатель освобождал место в своем приемном буфере, считывая поступившие обычные данные. В результате TCP объявит для отправителя окно ненулевого размера, что в конечном счете позволит отправителю передать байт, содержащий внеполосные данные.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В реализациях, происходящих от Беркли [128, с. 1016-1017], можно отметить две близких проблемы. Во-первых, даже если приемный буфер сокета заполнен, ядро всегда принимает от процесса внеполосные данные для отправки собеседнику. Во-вторых, когда отправитель посылает байт с внеполосными данными, немедленно посылается сегмент TCP, содержащий срочное уведомление. Все обычные проверки вывода TCP (алгоритм Нагла, предотвращение синдрома «глупого окна») при этом блокируются.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Пример: единственность отметки внеполосных данных в TCP</p>
      </title>
      <p>Нашим очередным примером мы иллюстрируем тот факт, что для данного соединения TCP существует всего одна отметка внеполосных данных, и если новые внеполосные данные прибудут прежде, чем принимающий процесс начнет считывать пришедшие ранее внеполосные данные, то предыдущая отметка будет утеряна.</p>
      <p>В листинге 24.10 показана посылающая программа, аналогичная программе, приведенной в листинге 24.6. Отличие заключается в том, что сейчас мы добавили еще одну функцию <code>send</code> для отправки внеполосных данных и еще одну функцию <code>write</code> для записи обычных данных.</p>
      <p><strong>Листинг 24.10</strong>. Отправка двух байтов внеполосных данных друг за другом</p>
      <p><code>//oob/tcpsend06.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int sockfd;</code></p>
      <empty-line/>
      <p><code> 6  if (argc != 3)</code></p>
      <p><code> 7   err_quit("usage: tcpsend04 &lt;host&gt; &lt;port#&gt;");</code></p>
      <empty-line/>
      <p><code> 8  sockfd = Tcp_connect(argv[1], argv[2]);</code></p>
      <empty-line/>
      <p><code> 9  Write(sockfd, "123", 3);</code></p>
      <p><code>10  printf("wrote 3 bytes of normal data\n");</code></p>
      <empty-line/>
      <p><code>11  Send(sockfd, "4", 1, MSG_OOB);</code></p>
      <p><code>12  printf("wrote 1 byte of OOB data\n");</code></p>
      <empty-line/>
      <p><code>13  Write(sockfd, "5", 1);</code></p>
      <p><code>14  printf("wrote 1 byte of normal data\n");</code></p>
      <empty-line/>
      <p><code>15  Send(sockfd,. "6", 1, MSG_OOB);</code></p>
      <p><code>16  printf("wrote 1 byte of OOB data\n");</code></p>
      <empty-line/>
      <p><code>17  Write(sockfd, "7", 1);</code></p>
      <p><code>18  printf("wrote 1 byte of normal data\n");</code></p>
      <empty-line/>
      <p><code>19  exit(0);</code></p>
      <p><code>20 }</code></p>
      <p>В данном случае отправка данных происходит без пауз, что позволяет быстро переслать данные собеседнику.</p>
      <p>Принимающая программа идентична программе, приведенной в листинге 24.7, где вызывается функция <code>sleep</code>, которая после установления соединения переводит получателя в спящее состояние на 5 с, чтобы позволить данным прибыть на принимающий TCP. Ниже приводится результат выполнения этой программы:</p>
      <p><code>freebsd4 % <strong>tcprecv06 5555</strong></code></p>
      <p><code>read 5 bytes: 12345</code></p>
      <p><code>at OOB mark</code></p>
      <p><code>read 2 bytes: 67</code></p>
      <p><code>received EOF</code></p>
      <p>Прибытие второго байта внеполосных данных (<code>6</code>) изменяет отметку, которая ассоциировалась с первым прибывшим байтом внеполосных данных (<code>4</code>). Как мы сказали, для конкретного соединения TCP допускается только одна отметка внеполосных данных.</p>
     </section>
    </section>
    <section>
     <title>
      <p>24.4. Резюме по теме внеполосных данных TCP</p>
     </title>
     <p>Все приведенные до сих пор примеры, иллюстрирующие использование внеполосных данных, были весьма тривиальны. К сожалению, когда мы начинаем учитывать возможные проблемы, связанные с согласованием во времени при пересылке внеполосных данных, ситуация заметно усложняется. В первую очередь, нужно осознать, что концепция внеполосных данных подразумевает передачу получателю трех различных фрагментов информации:</p>
     <p>1. Сам факт того, что отправитель вошел в срочный режим. Принимающий процесс получает уведомление об этом либо с помощью сигнала <code>SIGURG</code>, либо с помощью функции <code>select</code>. Это <emphasis>уведомление</emphasis> передается сразу же после того, как отправитель посылает байт внеполосных данных, поскольку, как показано в листинге 24.9, TCP посылает уведомление, даже если поток каких-либо данных от сервера к клиенту остановлен функциями управления потоком. В результате получения такого уведомления получатель может входить в определенный специальный режим обработки последующих данных.</p>
     <p>2. <emphasis>Позиция</emphasis> байта, содержащего внеполосные данные, то есть расположение этого байта по отношению к остальным данным, посланным отправителем, иначе говоря, отметка внеполосных данных.</p>
     <p>3. Фактическое <emphasis>значение</emphasis> внеполосного байта. Поскольку TCP является потоковым протоколом, который не интерпретирует данные, посланные приложением, это может быть любое 8-разрядное значение.</p>
     <p>Говоря о срочном режиме TCP, мы можем рассматривать флаг URG как уведомление, а срочный указатель как внеполосную отметку.</p>
     <p>Проблемы, связанные с концепцией внеполосных данных, сформулированы в следующих пунктах:</p>
     <p>1. Для каждого соединения имеется только один срочный указатель.</p>
     <p>2. Для каждого соединения допускается только одна отметка внеполосных данных.</p>
     <p>3. Для каждого соединения имеется только один однобайтовый буфер, предназначенный для внеполосных данных (это имеет значение, только если внеполосные данные не считываются вместе с обычными данными).</p>
     <p>В листинге 24.10 показано, что вновь прибывшая отметка внеполосных данных отменяет все предыдущие отметки, до которых принимающий процесс еще не дошел. Если внеполосные данные считываются вместе с обычными данными, то в случае прибытия новых внеполосных данных предыдущие не теряются, но теряются их отметки.</p>
     <p>Типичный пример использования внеполосных данных — протокол Rlogin, задействующий эту концепцию в ситуации, когда клиент прерывает программу, выполняемую на стороне сервера [111, с. 393–394]. Сервер должен сообщить клиенту, что нужно сбросить все данные, принятые от сервера, буферизованные и предназначенные для вывода на терминал. Сервер посылает клиенту специальный байт внеполосных данных, указывая тем самым, что необходимо сбросить все полученные данные. Когда клиент получает сигнал <code>SIGURG</code>, он просто считывает данные из сокета, пока не встречает отметку внеполосных данных, после чего он сбрасывает все данные вплоть до этой отметки. (В [111, с. 398–401] показан пример подобного использования внеполосных данных вместе с выводом программы <code>tcpdump</code>.) Если в этом сценарии сервер посылает несколько внеполосных байтов, следующих с небольшими промежутками друг за другом, то такая последовательность не оказывает влияния на клиента, поскольку тот просто сбрасывает все данные, расположенные до последней отметки внеполосных данных.</p>
     <p>В итоге можно сказать, что польза применения внеполосных данных зависит от того, для каких целей они служат в приложении. Если их назначение в том, чтобы сообщить собеседнику о необходимости сбросить все обычные данные, расположенные до отметки, то утрата промежуточных внеполосных данных и их отметок не повлечет никаких последствий. Но если потеря внеполосных данных недопустима, то эти данные следует получать вместе с обычными данными. Более того, байты, посланные как внеполосные данные, требуется каким-то образом отличать от обычных данных, так как промежуточные отметки могут быть перезаписаны при получении новых внеполосных данных. Telnet, например, посылает свои собственные команды в потоке обычных данных между клиентом и сервером, но ставит перед этими командами байт, содержащий 255 (поэтому для отправки этого значения требуется послать последовательно два байта, содержащих 255). Эти байты позволяют отличить команды сервера от обычных пользовательских данных, но при этом для обнаружения команд сервера требуется, чтобы клиент и сервер обрабатывали каждый байт данных.</p>
    </section>
    <section>
     <title>
      <p>24.5. Резюме</p>
     </title>
     <p>В TCP не существует настоящих внеполосных данных. Вместо этого при переходе отправителя в срочный режим собеседнику отсылается в TCP-заголовке срочный указатель. Получение этого указателя на другом конце соединения служит уведомлением для процесса о том, что отправитель вошел в срочный режим, а указатель указывает на последний байт внеполосных (срочных) данных. Но эти данные отсылаются через то же соединение и подчиняются обычным функциям управления потоком данных TCP.</p>
     <p>В API сокетов срочный режим TCP сопоставляется внеполосным данным. Отправитель входит в срочный режим, задавая флаг <code>MSG_OOB</code> при вызове функции send. Последний байт данных, переданных с помощью этой функции, считается внеполосным байтом. Приемник получает уведомление о том, что его TCP получил новый срочный указатель. Это происходит либо с помощью сигнала <code>SIGURG</code>, либо с помощью функции <code>select</code>, которая указывает, что на сокете возникла исключительная ситуация. По умолчанию TCP извлекает байт с внеполосными данными и помещает его в специальный однобайтовый буфер для внеполосных данных, откуда принимающий процесс считывает его с помощью вызова функции <code>recv</code> с флагом <code>MSG_OOB</code>. Имеется другой вариант — получатель может включить параметр сокета <code>SO_OOBINLINE</code>, и тогда внеполосный байт остается в потоке обычных данных. Независимо от того, какой метод используется принимающей стороной, уровень сокета поддерживает отметку внеполосных данных в потоке данных, и операция считывания остановится, когда дойдет до этой отметки. Чтобы определить, достигнута ли эта отметка, принимающий процесс использует функцию <code>sockatmark</code>.</p>
     <p>Внеполосные данные применяются не очень широко. Они используются в протоколах Telnet и Rlogin, а также FTP. Во всех случаях внеполосные данные уведомляют собеседника об исключительной ситуации (например, прерывании на стороне клиента), после чего собеседник сбрасывает все принятые данные до отметки внеполосных данных.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Есть ли разница между одним вызовом функции</p>
     <p><code>send(fd, "ab", 2, MSG_OOB);</code></p>
     <p>и двумя последовательными вызовами</p>
     <p><code>send(fd, "a", 1, MSG_OOB);</code></p>
     <p><code>send(fd, "b", 1, MSG_OOB);</code></p>
     <p>?</p>
     <p>2. Переделайте программу, приведенную в листинге 24.4, так, чтобы использовать функцию <code>poll</code> вместо функции <code>select</code>.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 25</p>
     <p>Управляемый сигналом ввод-вывод</p>
    </title>
    <section>
     <title>
      <p>25.1. Введение</p>
     </title>
     <p>Ввод-вывод, управляемый сигналом, подразумевает, что мы указываем ядру проинформировать нас сигналом, если что-либо произойдет с дескриптором. Исторически такой ввод-вывод назвали <emphasis>асинхронным вводом-выводом</emphasis>, но в действительности описанный далее управляемый сигналом ввод-вывод асинхронным не является. Последний обычно определяется как операция ввода-вывода с немедленным возвратом управления процессу после инициирования операции в ядре. Процесс продолжает выполняться во время того, как производится ввод-вывод. Когда операция ввода-вывода завершается или обнаруживается некоторая ошибка, процесс некоторым образом оповещается. В разделе 6.2 проводилось сравнение всех возможных типов ввода-вывода и было показано различие между вводом-выводом, управляемым сигналом, и асинхронным вводом-выводом.</p>
     <p>Следует отметить, что неблокируемый ввод-вывод, описанный в главе 16, также не является асинхронным. При неблокируемом вводе-выводе ядро не возвращает управление после инициирования операции ввода-вывода. Управление возвращается немедленно, только если операция не может быть выполнена без блокирования процесса.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Стандарт POSIX обеспечивает истинный асинхронный ввод-вывод с помощью функций aio_XXX. Эти функции позволяют процессу решить, генерировать ли при завершении ввода-вывода сигнал, и какой именно.</p>
     </cite>
     <p>Беркли-реализации поддерживают ввод-вывод, управляемый сигналом, для сокетов и устройств вывода с помощью сигнала <code>SIGIO</code>. SVR4 поддерживает ввод- вывод, управляемый сигналом, для устройств STREAMS с помощью сигнала <code>SIGPOLL</code>, который в данном случае приравнивается к <code>SIGIO</code>.</p>
    </section>
    <section>
     <title>
      <p>25.2. Управляемый сигналом ввод-вывод для сокетов</p>
     </title>
     <section>
      <p>Для использования управляемого сигналом ввода-вывода с сокетом (<code>SIGIO</code>) необходимо, чтобы процесс выполнил три следующих действия:</p>
      <p>1. Установил обработчик сигнала <code>SIGIO</code>.</p>
      <p>2. Задал владельца сокета. Обычно это выполняется с помощью команды <code>F_SETOWN</code> функции <code>fcntl</code> (см. табл. 7.9).</p>
      <p>3. Разрешил управляемый сигналом ввод-вывод для данного сокета, что обычно выполняется с помощью команды <code>F_SETFL</code> функции <code>fcntl</code> или путем включения флага <code>O_ASYNC</code> (см. табл. 7.9).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Флаг O_ASYNC был добавлен в POSIX относительно поздно. Его поддержка пока реализована в небольшом количестве систем. Для разрешения управляемого сигналом ввода-вывода в листинге 25.2 вместо этого флага мы используем функцию ioctl с флагом FIOASYNC. Следует отметить, что разработчики POSIX выбрали не самое удачное имя для нового флага: ему больше подходит имя O_SIGIO.</p>
       <p>Обработчик сигнала должен быть установлен до того, как будет задан владелец сокета. В Беркли-реализациях порядок вызова этих функций не имеет значения, поскольку по умолчанию сигнал SIGIO игнорируется. Поэтому если изменить порядок вызова функций на противоположный, появится небольшая вероятность того, что сигнал будет сгенерирован после вызова функции fcntl, но перед вызовом функции signal. Однако если это произойдет, то сигнал просто не будет учитываться. В SVR4 SIGIO определяется в заголовочном файле &lt;sys/signal.h&gt; как SIGPOLL, а действием по умолчанию для SIGPOLL является прерывание процесса. Таким образом, в SVR4 желательно быть уверенным в том, что обработчик сигнала установлен до задания владельца сокета.</p>
      </cite>
      <p>Перевести сокет в режим ввода-вывода, управляемого сигналом, несложно. Сложнее определить условия, которые должны приводить к генерации сигнала <code>SIGIO</code> для владельца сокета. Это зависит от транспортного протокола.</p>
     </section>
     <section>
      <title>
       <p>Сигнал SIGIO и сокеты UDP</p>
      </title>
      <p>Использовать ввод-вывод, управляемый сигналом, с сокетами UDP довольно легко. Сигнал генерируется в следующих случаях:</p>
      <p>&#9632; на сокет прибывает дейтаграмма;</p>
      <p>&#9632; на сокете возникает асинхронная ошибка.</p>
      <p>Таким образом, когда мы перехватываем сигнал <code>SIGIO</code> для сокета UDP, вызывается функция <code>recvfrom</code> как для чтения дейтаграммы, так и для получения асинхронной ошибки. Асинхронные ошибки, касающиеся UDP-сокетов, обсуждались в разделе 8.9. Напомним, что эти сигналы генерируются, только если сокет UDP является присоединенным (создан с помощью вызова функции <code>connect</code>).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Сигнал SIGIO генерируется для этих двух условий путем вызова макроса sorwakeup, описываемого в книге [128, с. 775, с. 779, с. 784].</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Сигнал SIGIO и сокеты TCP</p>
      </title>
      <p>К сожалению, использовать управляемый сигналом ввод-вывод для сокетов TCP почти бесполезно. Проблема состоит в том, что сигнал генерируется слишком часто, а само по себе возникновение сигнала не позволяет выяснить, что произошло. Как отмечается в [128, с. 439], генерацию сигнала <code>SIGIO</code> для TCP-сокета вызывают все нижеперечисленные ситуации (при условии, что управляемый сигналом ввод-вывод разрешен):</p>
      <p>&#9632; на прослушиваемом сокете выполнен запрос на соединение;</p>
      <p>&#9632; инициирован запрос на отключение;</p>
      <p>&#9632; запрос на отключение выполнен;</p>
      <p>&#9632; половина соединения закрыта;</p>
      <p>&#9632; данные доставлены на сокет;</p>
      <p>&#9632; данные отправлены с сокета (то есть в буфере отправки имеется свободное место);</p>
      <p>&#9632; произошла асинхронная ошибка.</p>
      <p>Например, если одновременно осуществляются и чтение, и запись в TCP-сокет, то сигнал <code>SIGIO</code> генерируется, когда поступают новые данные и когда подтверждается прием ранее записанных данных, а обработчик сигнала не имеет возможности различить эти сигналы. Если используется сигнал <code>SIGIO</code>, то для предотвращения блокирования при выполнении функции <code>read</code> или <code>write</code> TCP-сокет должен находиться в режиме неблокируемого ввода-вывода. Следует использовать сигнал <code>SIGIO</code> лишь с прослушиваемым сокетом TCP, поскольку для прослушиваемого сокета этот сигнал генерируется только при завершении установления нового соединения.</p>
      <p>Единственное реальное применение управляемого сигналом ввода-вывода с сокетами, которое удалось обнаружить автору, — это сервер NTP (Network Time Protocol — сетевой протокол синхронизации времени), использующий протокол UDP. Основной цикл этого сервера получает дейтаграмму от клиента и посылает ответ. Но обработка клиентского запроса на этом сервере требует некоторого ненулевого количества времени (больше, чем для нашего тривиального эхо-сервеpa). Серверу важно записать точные отметки времени для каждой принимаемой дейтаграммы, поскольку это значение возвращается клиенту и используется им для вычисления времени обращения к серверу (RTT). На рис. 25.1 показаны два варианта построения такого UDP-сервера.</p>
      <image l:href="#img_131.png"/>
      <p><strong>Рис. 25.1</strong>. Два варианта построения UDP-сервера</p>
      <p>Большинство UDP-серверов (включая наш эхо-сервер, описанный в главе 8) построены так, как показано на рисунке слева. Однако NTP-сервер использует способ, показанный справа: когда прибывает новая дейтаграмма, она читается обработчиком сигнала <code>SIGIO</code>, который также записывает время прибытия дейтаграммы. Далее дейтаграмма помещается в другую очередь внутри процесса, из которой она будет извлечена, а затем обработана основным циклом сервера. Это усложняет код сервера, но зато обеспечивает точные отметки времени прибытия дейтаграмм.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Вспомните листинг 22.3: процесс может установить параметр сокета IP_RECVDSTADDR, чтобы получить адрес получателя пришедшей UDP-дейтаграммы. Можно возразить, что вместе с полученной дейтаграммой UDP должны быть возвращены два дополнительных фрагмента информации — интерфейс, на котором была получена дейтаграмма (этот интерфейс может отличаться от адреса получателя, если узел использует более типичную модель системы с гибкой привязкой), и время прибытия дейтаграммы.</p>
       <p>Для IPv6 интерфейс, на котором была получена дейтаграмма, можно получить, если включен параметр сокета IPV6_PKTINFO (см. раздел 22.8). Аналогичный параметр сокета IP_RECVIF для IPv4 описывался в разделе 22.2.</p>
       <p>В FreeBSD также предусмотрен параметр сокета SO_TIMESTAMP, возвращающий время получения дейтаграммы как вспомогательные данные в структуре timeval. В Linux существует флаг SIOCGSTAMP для функции ioctl, которая возвращает структуру timeval, содержащую время прибытия дейтаграммы.</p>
      </cite>
     </section>
    </section>
    <section>
     <title>
      <p>25.3. Эхо-сервер UDP с использованием сигнала SIGIO</p>
     </title>
     <p>В этом разделе мы приведем пример, аналогичный правой части рис. 25.1: UDP-сервер, использующий сигнал <code>SIGIO</code> для получения приходящих дейтаграмм. Этот пример также иллюстрирует использование надежных сигналов стандарта POSIX.</p>
     <p>В данном случае клиент совсем не изменен по сравнению с листингами 8.3 и 8.4, а функция сервера main не изменилась по сравнению с листингом 8.1. Единственные внесенные изменения касаются функции <code>dg_echo</code>, которая будет приведена в следующих четырех листингах. В листинге 25.1<a l:href="#n1" type="note">[1]</a> представлены глобальные объявления.</p>
     <p><strong>Листинг 25.1</strong>. Глобальные объявления</p>
     <p><code>//sigio/dgecho01.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 static int sockfd;</code></p>
     <empty-line/>
     <p><code> 3 #define QSIZE    8 /* размер входной очереди */</code></p>
     <p><code> 4 #define MAXDG 4096 /* максимальный размер дейтаграммы */</code></p>
     <empty-line/>
     <p><code> 5 typedef struct {</code></p>
     <p><code> 6  void *dg_data;          /* указатель на текущую дейтаграмму */</code></p>
     <p><code> 7  size_t dg_len;          /* длина дейтаграммы */</code></p>
     <p><code> 8  struct sockaddr *dg_sa; /* указатель на sockaddr{} с адресом клиента */</code></p>
     <p><code> 9  socklen_t dg_salen;     /* длина sockaddr{} */</code></p>
     <p><code>10 } DG;</code></p>
     <p><code>11 static DG dg[QSIZE]; /* очередь дейтаграмм для обработки */</code></p>
     <p><code>12 static long cntread[QSIZE +1]; /* диагностический счетчик */</code></p>
     <p><code>13 static int iget; /* следующий элемент для обработки в основном цикле */</code></p>
     <p><code>14 static int iput; /* следующий элемент для считывания обработчиком</code></p>
     <p><code>                       сигналов */</code></p>
     <p><code>15 static int nqueue; /* количество дейтаграмм в очереди на обработку</code></p>
     <p><code>                         в основном цикле */</code></p>
     <p><code>16 static socklen_t clilen; /* максимальная длина sockaddr{} */</code></p>
     <p><code>17 static void sig_io(int);</code></p>
     <p><code>18 static void sig_hup(int);</code></p>
     <subtitle>Очередь принимаемых дейтаграмм</subtitle>
     <p><code>3-12</code> Обработчик сигнала <code>SIGIO</code> помещает приходящие дейтаграммы в очередь. Эта очередь является массивом структур <code>DG</code>, который интерпретируется как кольцевой буфер. Каждая структура содержит указатель на принятую дейтаграмму, ее длину и указатель на структуру адреса сокета, содержащую адрес протокола клиента и размер адреса протокола. В памяти размещается столько этих структур, сколько указано в <code>QSIZE</code> (в данном случае 8), и в листинге 25.2 будет видно, что функция <code>dg_echo</code> для размещения в памяти всех структур дейтаграмм и адресов сокетов вызывает функцию <code>malloc</code>. Также происходит выделение памяти под диагностический счетчик <code>cntread</code>, который будет рассмотрен чуть ниже. На рис. 25.2 приведен массив структур, при этом предполагается, что первый элемент указывает на 150-байтовую дейтаграмму, а длина связанного с ней адреса сокета равна 16.</p>
     <image l:href="#img_132.png"/>
     <p><strong>Рис. 25.2</strong>. Структуры данных, используемые для хранения прибывающих дейтаграмм и структур адресов их сокетов</p>
     <subtitle>Индексы массивов</subtitle>
     <p><code>13-15</code> Переменная <code>iget</code> является индексом следующего элемента массива для обработки в основном цикле, а переменная <code>iput</code> — это индекс следующего элемента массива, в котором сохраняется результат действия обработчика сигнала. Переменная <code>nqueue</code> обозначает полное количество дейтаграмм, предназначенных для обработки в основном цикле.</p>
     <p>В листинге 25.2 показан основной цикл сервера — функция <code>dg_echo</code>.</p>
     <p><strong>Листинг 25.2</strong>. Функция dg_echo: основной обрабатывающий цикл сервера</p>
     <p><code>//sigio/dgecho01.c</code></p>
     <p><code>19 void</code></p>
     <p><code>20 dg_echo(int sockfd_arg, SA *pcliaddr, socklen_t clilen_arg)</code></p>
     <p><code>21 {</code></p>
     <p><code>22  int i;</code></p>
     <p><code>23  const int on = 1;</code></p>
     <p><code>24  sigset_t zeromask, newmask, oldmask;</code></p>
     <empty-line/>
     <p><code>25  sockfd = sockfd_arg;</code></p>
     <p><code>26  clilen = clilen_arg;</code></p>
     <empty-line/>
     <p><code>27  for (i = 0; i &lt; QSIZE; i++) { /* инициализация очереди */</code></p>
     <p><code>28   dg[i].dg_data = Malloc(MAXDG);</code></p>
     <p><code>29   dg[i].dg_sa = Malloc(clilen);</code></p>
     <p><code>30   dg[i].dg_salen = clilen;</code></p>
     <p><code>31  }</code></p>
     <p><code>32  iget = iput = nqueue = 0;</code></p>
     <empty-line/>
     <p><code>33  Signal(SIGHUP, sig_hup);</code></p>
     <p><code>34  Signal(SIGIO, sig_io);</code></p>
     <p><code>35  Fcntl(sockfd, F_SETOWN, getpid());</code></p>
     <p><code>36  Ioctl(sockfd, FIOASYNC, &amp;on);</code></p>
     <p><code>37  Ioctl(sockfd. FIONBIO, &amp;on);</code></p>
     <empty-line/>
     <p><code>38  Sigemptyset(&amp;zeromask); /* инициализация трех наборов сигналов */</code></p>
     <p><code>39  Sigemptyset(&amp;oldmask);</code></p>
     <p><code>40  Sigemptyset(&amp;newmask);</code></p>
     <p><code>41  Sigaddset(&amp;newmask, SIGIO); /* сигнал, который хотим блокировать*/</code></p>
     <empty-line/>
     <p><code>42  Sigprocmask(SIG_BLOCK, &amp;newmask, &amp;oldmask);</code></p>
     <p><code>43  for (;;) {</code></p>
     <p><code>44   while (nqueue == 0)</code></p>
     <p><code>45    sigsuspend(&amp;zeromask); /* ждем дейтаграмму для обработки */</code></p>
     <empty-line/>
     <p><code>46   /* разблокирование SIGIO */</code></p>
     <p><code>47   Sigprocmask(SIG_SETMASK, &amp;oldmask, NULL);</code></p>
     <empty-line/>
     <p><code>48   Sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0,</code></p>
     <p><code>49    dg[iget].dg_sa, dg[iget].dg_salen);</code></p>
     <empty-line/>
     <p><code>50   if (++iget &gt;= QSIZE)</code></p>
     <p><code>51    iget = 0;</code></p>
     <empty-line/>
     <p><code>52   /* блокировка SIGIO */</code></p>
     <p><code>53   Sigprocmask(SIG_BLOCK, &amp;newmask, &amp;oldmask);</code></p>
     <p><code>54   nqueue--;</code></p>
     <p><code>55  }</code></p>
     <p><code>56 }</code></p>
     <subtitle>Инициализация очереди принятых дейтаграмм</subtitle>
     <p><code>27-32</code> Дескриптор сокета сохраняется в глобальной переменной, поскольку он необходим обработчику сигналов. Происходит инициализация очереди принятых дейтаграмм.</p>
     <subtitle>Установка обработчиков сигналов и флагов сокетов</subtitle>
     <p><code>33-37</code> Для сигналов <code>SIGHUP</code> (он используется для диагностических целей) и <code>SIGIO</code> устанавливаются обработчики. С помощью функции <code>fcntl</code> задается владелец сокета, а с помощью функции <code>ioctl</code> устанавливаются флаги ввода-вывода, управляемого сигналом, и неблокируемого ввода-вывода.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Ранее отмечалось, что для разрешения ввода-вывода, управляемого сигналом, в POSIX применяется флаг O_ASYNC функции fcntl, но поскольку большинство систем пока его не поддерживают, мы используем функцию ioctl. Поскольку большинство систем не поддерживают флаг O_NONBLOCK для включения неблокируемого ввода-вывода, здесь также рассмотрен вариант использования функции ioctl.</p>
     </cite>
     <subtitle>Инициализация наборов сигналов</subtitle>
     <p><code>38-41</code> Инициализируется три набора сигналов: <code>zeromask</code> (никогда не изменяется), <code>oldmask</code> (хранит старую маску сигнала, когда <code>SIGIO</code> блокируется) и <code>newmask</code>. Функция <code>sigaddset</code> включает в набор <code>newmask</code> бит, соответствующий <code>SIGIO</code>.</p>
     <subtitle>Блокирование SIGIO и ожидание дальнейших действий</subtitle>
     <p><code>42-45</code> Функция <code>sigprocmask</code> сохраняет текущую маску сигналов процесса в <code>oldmask</code>, а затем выполняет логическое сложение, сравнивая <code>newmask</code> с текущей маской сигналов. Такие действия блокируют сигнал <code>SIGIO</code> и возвращают текущую маску сигналов. Далее мы заходим в цикл <code>for</code> и проверяем счетчик <code>nqueue</code>. Пока этот счетчик равен нулю, ничего делать не нужно, и мы вызываем функцию <code>sigsuspend</code>. Эта функция POSIX, сохранив в одной из локальных переменных текущую маску сигналов, присваивает текущей маске значение аргумента <code>zeromask</code>. Так как <code>zeromask</code> является пустым набором сигналов, то разрешается доставка любых сигналов. Как только перехватывается сигнал и завершается обработчик, функция <code>sigsuspend</code> также завершается. (Это необычная функция, поскольку она всегда возвращает ошибку <code>EINTR</code>.) Прежде чем завершиться, функция <code>sigsuspend</code> всегда устанавливает такое значение маски сигналов, которое предшествовало ее вызову (в данном случае <code>newmask</code>). Таким образом гарантируется, что, когда функция <code>sigsuspend</code> возвращает значение, сигнал <code>SIGIO</code> блокирован. Именно поэтому можно проверять счетчик <code>nqueue</code>, поскольку известно, что пока он проверяется, сигнал <code>SIGIO</code> не может быть доставлен.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>А что произойдет, если сигнал SIGIO не будет блокирован во время проверки переменной nqueue, используемой совместно основным циклом и обработчиком сигналов? Может случиться так, что проверка nqueue покажет нулевое значение, а сразу после проверки возникнет сигнал и nqueue станет равна 1. Далее мы вызовем функцию sigsuspend и перейдем в режим ожидания, в результате чего пропустим сигнал. После вызова функции sigsuspend мы не выйдем из режима ожидания, пока не поступит другой сигнал. Это похоже на ситуацию гонок, описанную в разделе 20.5</p>
     </cite>
     <subtitle>Разблокирование SIGIO и отправка ответа</subtitle>
     <p><code>46-51</code> Разблокируем сигнал SIGIO с помощью вызова <code>sigprocmask</code>, чтобы вернуть маске сигналов процесса значение, сохраненное ранее (<code>oldmask</code>). В этом случае ответ посылается с помощью функции <code>sendto</code>. Индекс <code>iget</code> увеличился на 1, и если его значение совпадает с количеством элементов массива, он снова обнуляется. Массив используется как кольцевой буфер. Обратите внимание, что нет необходимости блокировать сигнал <code>SIGIO</code> во время изменения переменной <code>iget</code>, поскольку этот индекс используется только в основном цикле и никогда не изменяется обработчиком сигнала.</p>
     <subtitle>Блокирование SIGIO</subtitle>
     <p><code>52-54</code> Сигнал <code>SIGIO</code> блокируется, а значение переменной <code>nqueue</code> уменьшается на 1. Во время изменения данной переменной необходимо заблокировать сигнал, поскольку она используется совместно основным циклом и обработчиком сигнала. Также необходимо, чтобы сигнал <code>SIGIO</code> был заблокирован, когда в начале цикла происходит проверка переменной <code>nqueue</code>.</p>
     <p>Альтернативным способом является удаление обоих вызовов функции <code>sigprocmask</code>, находящихся внутри цикла <code>for</code>, что предотвращает разблокирование сигнала и его последующее блокирование. Однако проблема состоит в следующем: в такой ситуации весь цикл выполняется при блокированном сигнале, что уменьшает быстроту реагирования обработчика сигнала. При этом дейтаграммы не будут теряться (если, конечно, буфер приема сокета достаточно велик), но выдача сигнала процессу будет задерживаться на время блокирования сигнала. Одной из задач при создании приложений, производящих обработку сигналов, должна быть минимизация времени блокирования сигнала.</p>
     <p><strong>Листинг 25.3</strong>. Обработчик сигнала SIGIO</p>
     <p><code>//sigio/dgecho01.c</code></p>
     <p><code>57 static void</code></p>
     <p><code>58 sig_io(int signo)</code></p>
     <p><code>59 {</code></p>
     <p><code>60  ssize_t len;</code></p>
     <p><code>61  int nread;</code></p>
     <p><code>62  DG *ptr;</code></p>
     <p><code>63  for (nread = 0;;) {</code></p>
     <p><code>64   if (nqueue &gt;= QSIZE)</code></p>
     <p><code>65    err_quit("receive overflow");</code></p>
     <empty-line/>
     <p><code>66   ptr = &amp;dg[iput];</code></p>
     <p><code>67   ptr-&gt;dg_salen = clilen;</code></p>
     <p><code>68   len = recvfrom(sockfd, ptr-&gt;dg_data, MAXDG, 0,</code></p>
     <p><code>69    ptr-&gt;dg_sa, &amp;ptr-&gt;dg_salen);</code></p>
     <p><code>70   if (len &lt; 0) {</code></p>
     <p><code>71    if (errno == EWOULDBLOCK)</code></p>
     <p><code>72     break; /* все сделано; очередь на чтение отсутствует */</code></p>
     <p><code>73    else</code></p>
     <p><code>74     err_sys("recvfrom error");</code></p>
     <p><code>75   }</code></p>
     <p><code>76   ptr-&gt;dg_len = len;</code></p>
     <empty-line/>
     <p><code>77   nread++;</code></p>
     <p><code>78   nqueue++;</code></p>
     <p><code>79   if (++iput &gt;= QSIZE)</code></p>
     <p><code>80    iput = 0;</code></p>
     <empty-line/>
     <p><code>81  }</code></p>
     <p><code>82  cntread[nread]++; /* гистограмма количества дейтаграмм.</code></p>
     <p><code>                         считанных для каждого сигнала */</code></p>
     <p><code>83 }</code></p>
     <p>Во время создания этих обработчиков сигналов была обнаружена следующая проблема: в стандарте POSIX сигналы обычно <emphasis>не</emphasis> помещаются в очередь. Это означает, что если во время пребывания внутри обработчика сигналов (при этом сигнал заведомо заблокирован) возникает еще два сигнала, то сигнал доставляется еще <emphasis>один</emphasis> раз.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В стандарте POSIX предусмотрено несколько сигналов реального времени, для которых обеспечивается буферизация, однако ряд других сигналов, в том числе и SIGIO, обычно не буферизуются, то есть не помещаются в очередь на доставку.</p>
     </cite>
     <p>Рассмотрим следующий сценарий. Прибывает дейтаграмма и выдается сигнал. Обработчик сигнала считывает дейтаграмму и помещает ее в очередь к основному циклу. Но во время работы обработчика сигнала приходят еще две дейтаграммы, вызывая генерацию сигнала еще дважды. Поскольку сигнал блокирован, то когда обработчик сигналов возвращает управление после обработки первого сигнала, он запустится снова всего лишь один раз. После второго запуска обработчик считывает вторую дейтаграмму, а третья будет оставлена в очереди приходящих дейтаграмм сокета. Эта третья дейтаграмма будет прочитана, только если (и только когда) придет четвертая. Когда придет четвертая дейтаграмма, считана и поставлена в очередь на обработку основным циклом будет именно третья, а не четвертая дейтаграмма.</p>
     <p>Поскольку сигналы не помещаются в очередь, дескриптор, установленный для управляемого сигналом ввода-вывода, обычно переводится в неблокируемый режим. Обработчик сигнала <code>SIGIO</code> мы кодируем таким образом, чтобы он считывал дейтаграммы в цикле, который прерывается, только когда при считывании возвращается ошибка <code>EWOULDBLOCK</code>.</p>
     <subtitle>Проверка переполнения очереди</subtitle>
     <p><code>64-65</code> Если очередь переполняется, происходит завершение работы. Для обработки такой ситуации существуют и другие способы (например, можно размещать в памяти дополнительные буферы), но для данного примера достаточно простого завершения.</p>
     <subtitle>Чтение дейтаграммы</subtitle>
     <p><code>66-76</code> На неблокируемом сокете вызывается функция <code>recvfrom</code>. Элемент массива, обозначенный индексом <code>iput</code>, — это то место, куда записывается дейтаграмма. Если нет дейтаграмм, которые нужно считывать, мы выходим из цикла <code>for</code> с помощью оператора <code>break</code>.</p>
     <subtitle>Увеличение счетчиков и индекса на единицу</subtitle>
     <p><code>77-80</code> Переменная <code>nread</code> является диагностическим счетчиком количества дейтаграмм, читаемых на один сигнал. Переменная <code>nqueue</code> — это количество дейтаграмм для обработки основным циклом.</p>
     <p><code>82</code> Прежде чем обработчик сигналов возвращает управление, он увеличивает счетчик на единицу в соответствии с количеством дейтаграмм, прочитанных за один сигнал. Этот массив распечатывается программой в листинге 25.4 и представляет собой диагностическую информацию для обработки сигнала <code>SIGHUP</code>.</p>
     <p>Последняя функция (листинг 25.4) представляет собой обработчик сигнала <code>SIGHUP</code>, который выводит массив <code>cntread</code>. Он считает количество дейтаграмм, прочитанных за один сигнал.</p>
     <p><strong>Листинг 25.4</strong>. Обработчик сигнала SIGHUP</p>
     <p><code>//sigio/dgecho01.c</code></p>
     <p><code>84 static void</code></p>
     <p><code>85 sig_hup(int signo)</code></p>
     <p><code>86 {</code></p>
     <p><code>87  int i;</code></p>
     <empty-line/>
     <p><code>88  for (i = 0; i &lt;= QSIZE; i++)</code></p>
     <p><code>89   printf("cntread[%d] = %ld\n", i, cntread[i]);</code></p>
     <p><code>90 }</code></p>
     <p>Чтобы проиллюстрировать, что сигналы не буферизуются и что в дополнение к установке флага, указывающего на управляемый сигналом ввод-вывод, необходимо перевести сокет в неблокируемый режим, запустим этот сервер с шестью клиентами одновременно. Каждый клиент посылает серверу 3645 строк (для отражения). При этом каждый клиент запускается из сценария интерпретатора в фоновом режиме, так что все клиенты стартуют приблизительно одновременно. Когда все клиенты завершены, серверу посылается сигнал <code>SIGHUP</code>, в результате чего сервер выводит получившийся массив <code>cntread</code>:</p>
     <p><code>linux % <strong>udpserv01</strong></code></p>
     <p><code>cntread[0] = 2</code></p>
     <p><code>cntread[1] = 21838</code></p>
     <p><code>cntread[2] = 12</code></p>
     <p><code>cntread[3] = 1</code></p>
     <p><code>cntread[4] = 0</code></p>
     <p><code>cntread[5] = 1</code></p>
     <p><code>cntread[6] = 0</code></p>
     <p><code>cntread[7] = 0</code></p>
     <p><code>cntread[8] = 0</code></p>
     <p>Большую часть времени обработчик сигналов читает только одну дейтаграмму, но бывает, что готово больше одной дейтаграммы. Ненулевое значение счетчика <code>cntread[0]</code> получается потому, что сигнал генерируется в процессе выполнения клиента. Мы считываем дейтаграммы в цикле обработчика сигнала. Дейтаграмма, прибывшая во время считывания других дейтаграмм, будет считана вместе с этими дейтаграммами (в том же вызове обработчика), а сигнал об ее прибытии будет отложен и доставлен процессу после завершения обработчика. Это приведет к повторному вызову обработчика, но считывать ему будет нечего (отсюда <code>cntread[0]&gt;0</code>). Наконец, можно проверить, что взвешенная сумма элементов массива (21 838&#215;1 + 12&#215;2 + 1&#215;3+1&#215;5=21 870) равна 6&#215;3645 (количество клиентов &#215; количество строк клиента).</p>
    </section>
    <section>
     <title>
      <p>25.4. Резюме</p>
     </title>
     <p>При управляемом сигналом вводе-выводе ядро уведомляет процесс сигналом <code>SIGIO</code>, если «что-нибудь» происходит на сокете.</p>
     <p>&#9632; Для присоединенного TCP-сокета существует множество ситуаций, которые вызывают такое уведомление, что делает эту возможность практически бесполезной.</p>
     <p>&#9632; Для прослушиваемого TCP-сокета уведомление приходит процессу только в случае готовности принятия нового соединения.</p>
     <p>&#9632; Для UDP такое уведомление означает, что либо пришла дейтаграмма, либо произошла асинхронная ошибка: в обоих случаях вызывается <code>recvfrom</code>.</p>
     <p>С помощью метода, аналогичного применяемому для сервера NTP, был изменен эхо-сервер UDP для работы с вводом-выводом, управляемым сигналом: мы стремимся выполнить чтение дейтаграммы как можно быстрее после ее прибытия, чтобы получить точную отметку времени прибытия и поставить дейтаграмму в очередь для дальнейшей обработки.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Далее приведен альтернативный вариант цикла, рассмотренного в листинге 25.2:</p>
     <p><code>for (;;) {</code></p>
     <p><code> Sigprocmask(SIG_BLOCK, &amp;newmask, &amp;oldmask);</code></p>
     <p><code> while (nqueue == 0)</code></p>
     <p><code>  sigsuspend(&amp;zeromask); /* ожидание дейтаграммы для обработки */</code></p>
     <p><code> nqueue--;</code></p>
     <empty-line/>
     <p><code> /* разблокирование SIGIO */</code></p>
     <p><code> Sigprocmask(SIG_SETMASK, &amp;oldmask, NULL);</code></p>
     <empty-line/>
     <p><code> Sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0,</code></p>
     <p><code>  dg[iget].dg_sa, dg[iget].dg_salen);</code></p>
     <p><code> if (++iget &gt;= QSIZE)</code></p>
     <p><code>  iget = 0;</code></p>
     <p><code>}</code></p>
     <p>Верна ли такая модификация?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 26</p>
     <p>Программные потоки</p>
    </title>
    <section>
     <title>
      <p>26.1. Введение</p>
     </title>
     <p>Согласно традиционной модели Unix, когда процессу требуется, чтобы некое действие было выполнено каким-либо другим объектом, он порождает дочерний процесс, используя функцию <code>fork</code>, и этим порожденным процессом выполняется необходимое действие. Большинство сетевых серверов под Unix устроены именно таким образом, как мы видели при рассмотрении примера параллельного (concurrent) сервера: родительский процесс осуществляет соединение с помощью функции <code>accept</code> и порождает дочерний процесс, используя функцию <code>fork</code>, а затем дочерний процесс занимается обработкой клиентского запроса.</p>
     <p>Хотя эта концепция с успехом использовалась на протяжении многих лет, с функцией <code>fork</code> связаны определенные неудобства.</p>
     <p>&#9632; Стоимость функции <code>fork</code> довольно высока, так как при ее использовании требуется скопировать все содержимое памяти из родительского процесса в дочерний, продублировать все дескрипторы и т.д. Текущие реализации используют технологию, называемую <emphasis>копированием при записи</emphasis> (<emphasis>copy-on-write</emphasis>), при которой копирование пространства данных из родительского процесса в дочерний происходит лишь тогда, когда дочернему процессу требуется своя собственная копия. Но несмотря на эту оптимизацию, стоимость функции <code>fork</code> остается высокой.</p>
     <p>&#9632; Для передачи данных между родительским и дочерним процессами <emphasis>после</emphasis> вызова функции <code>fork</code> требуется использовать средства взаимодействия процессов (IPC). Передача информации перед вызовом <code>fork</code> не вызывает затруднений, так как при запуске дочерний процесс получает от родительского копию пространства данных и копии всех родительских дескрипторов. Но возвращение информации из дочернего процесса в родительский требует большей работы.</p>
     <p>Обе проблемы могут быть разрешены путем использования <emphasis>программных потоков</emphasis> (<emphasis>threads</emphasis>). Программные потоки иногда называются <emphasis>облегченными процессами</emphasis> (<emphasis>lightweight processes</emphasis>), так как поток проще, чем процесс. В частности, создание потока требует в 10–100 раз меньше времени, чем создание процесса.</p>
     <p>Все потоки одного процесса совместно используют его глобальные переменные, поэтому им легко обмениваться информацией, но это приводит к необходимости синхронизации.</p>
     <p>Однако общими становятся не только глобальные переменные. Все потоки одного процесса разделяют:</p>
     <p>&#9632; инструкции процесса;</p>
     <p>&#9632; большую часть данных;</p>
     <p>&#9632; открытые файлы (например, дескрипторы);</p>
     <p>&#9632; обработчики сигналов и вообще настройки для работы с сигналами (действие сигнала);</p>
     <p>&#9632; текущий рабочий каталог;</p>
     <p>&#9632; идентификаторы пользователя и группы пользователей.</p>
     <p>У каждого потока имеются собственные:</p>
     <p>&#9632; идентификатор потока;</p>
     <p>&#9632; набор регистров, включая счетчик команд и указатель стека;</p>
     <p>&#9632; стек (для локальных переменных и адресов возврата);</p>
     <p>&#9632; переменная <code>errno</code>;</p>
     <p>&#9632; маска сигналов;</p>
     <p>&#9632; приоритет.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Как сказано в разделе 11.18, можно рассматривать обработчик сигнала как некую разновидность потока. В традиционной модели Unix у нас имеется основной поток выполнения и обработчик сигнала (другой поток). Если в основном потоке в момент возникновения сигнала происходит корректировка связного списка и обработчик сигнала также пытается изменить связный список, обычно начинается путаница. Основной поток и обработчик сигнала совместно используют одни и те же глобальные переменные, но у каждого из них имеется свой собственный стек.</p>
     </cite>
     <p>В этой книге мы рассматриваем потоки POSIX, которые также называются <emphasis>Pthreads</emphasis> (POSIX threads). Они были стандартизованы в 1995 году как часть POSIX.1c и будут поддерживаться большинством версий Unix. Мы увидим, что все названия функций Pthreads начинаются с символов <code>pthread_</code>. Эта глава является введением в концепцию потоков, необходимым для того, чтобы в дальнейшем мы могли использовать потоки в наших сетевых приложениях. Более подробную информацию вы можете найти в [15].</p>
    </section>
    <section>
     <title>
      <p>26.2. Основные функции для работы с потоками: создание и завершение потоков</p>
     </title>
     <section>
      <p>В этом разделе мы рассматриваем пять основных функций для работы с потоками, а в следующих двух разделах мы используем эти функции для написания потоковой модификации клиента и сервера TCP.</p>
     </section>
     <section>
      <title>
       <p>Функция pthread_create</p>
      </title>
      <p>Когда программа запускается с помощью функции <code>exec</code>, создается один поток, называемый <emphasis>начальным</emphasis> (<emphasis>initial</emphasis>) или <emphasis>главным</emphasis> (<emphasis>main</emphasis>). Дополнительные потоки создаются функцией <code>pthread_create</code>.</p>
      <p><code>#include &lt;pthread.h&gt;</code></p>
      <empty-line/>
      <p><code>int pthread_create(pthread_t* <emphasis>tid</emphasis>, const pthread_attr_t *<emphasis>attr</emphasis>,</code></p>
      <p><code> void *(*<emphasis>func</emphasis>)(void*), void *<emphasis>arg</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</emphasis></code></p>
      <p>Каждый поток процесса обладает собственным <emphasis>идентификатором потока</emphasis> (<emphasis>thread ID</emphasis>), относящимся к типу данных <code>pthread_t</code> (как правило, это <code>unsigned int</code>). При успешном создании нового потока его идентификатор возвращается через указатель <code>tid</code>.</p>
      <p>У каждого потока имеется несколько <emphasis>атрибутов</emphasis>: его приоритет, исходный размер стека, указание на то, должен ли этот поток являться демоном или нет, и т.д. При создании потока мы можем задать эти атрибуты, инициализируя переменную типа <code>pthread_attr_t</code>, что позволяет заменить значение, заданное по умолчанию. Обычно мы используем значение по умолчанию, в этом случае мы задаем аргумент <code>attr</code> равным пустому указателю.</p>
      <p>Наконец, при создании потока мы должны указать, какую функцию будет выполнять этот поток. Выполнение потока начинается с вызова заданной функции, а завершается либо явно (вызовом <code>pthread_exit</code>), либо неявно (когда вызванная функция возвращает управление). Адрес функции задается аргументом <code>func</code>, и она вызывается с единственным аргументом-указателем <code>arg</code>. Если этой функции необходимо передать несколько аргументов, следует поместить их в некоторую структуру и передать адрес этой структуры как единственный аргумент функции.</p>
      <p>Обратите внимание на объявления <code>func</code> и <code>arg</code>. Функции передается один аргумент — универсальный указатель <code>void*</code>. Это позволяет нам передавать потоку с помощью единственного указателя все, что требуется, и точно так же поток возвращает любые данные, используя этот указатель.</p>
      <p>Возвращаемое значение функций Pthreads — это обычно 0 в случае успешного выполнения или ненулевая величина в случае ошибки. Но в отличие от функций сокетов и большинства системных вызовов, для которых в случае ошибки возвращается -1 и переменной <code>errno</code> присваивается некоторое положительное значение (код ошибки), функции Pthreads возвращают сам код ошибки. Например, если функция <code>pthread_create</code> не может создать новый поток, так как мы превысили допустимый системный предел количества потоков, функция возвратит значение <code>EAGAIN</code>. Функции Pthreads не присваивают переменной <code>errno</code> никаких значений. Соглашение о том, что 0 является индикатором успешного выполнения, а ненулевое значение — индикатором ошибки, не приводит к противоречию, так как все значения <code>Exxx</code>, определенные в заголовочном файле <code>&lt;sys/errno.h&gt;</code>, являются положительными. Ни одному из имен ошибок Exxx не сопоставлено нулевое значение.</p>
     </section>
     <section>
      <title>
       <p>Функция pthread_join</p>
      </title>
      <p>Мы можем приостановить выполнение текущего потока и ждать завершения выполнения какого-либо другого потока, используя функцию <code>pthread_join</code>. Сравнивая потоки и процессы Unix, можно сказать, что функция <code>pthread_create</code> аналогична функции <code>fork</code>, а функция <code>pthread_join</code> — функции <code>waitpid</code>.</p>
      <p><code>#include &lt;pthread.h&gt;</code></p>
      <empty-line/>
      <p><code>int pthread_join(pthread_t <emphasis>tid</emphasis>, void **<emphasis>status</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</emphasis></code></p>
      <p>Следует указать идентификатор <code>tid</code> того потока, завершения которого мы ждем. К сожалению, нет способа указать, что мы ждем завершения любого потока данного процесса (тогда как при работе с процессами мы могли с помощью функции <code>waitpid</code> ждать завершения любого процесса, задав аргумент идентификатора процесса, равный -1). Мы вернемся к этой проблеме при обсуждении листинга 26.11.</p>
      <p>Если указатель <code>status</code> непустой, то значение, возвращаемое потоком (указатель на некоторый объект), хранится в ячейке памяти, на которую указывает <code>status</code>.</p>
     </section>
     <section>
      <title>
       <p>Функция pthread_self</p>
      </title>
      <p>Каждый поток снабжен идентификатором, уникальным в пределах данного процесса. Идентификатор потока возвращается функцией <code>pthread_create</code> и, как мы видели, используется функцией <code>pthread_join</code>. Поток может узнать свой собственный идентификатор с помощью вызова <code>pthread_self</code>.</p>
      <p><code>#include &lt;pthread.h&gt;</code></p>
      <empty-line/>
      <p><code>pthread_t pthread_self(void);</code></p>
      <p><code><emphasis>Возвращает: идентификатор вызывающего потока</emphasis></code></p>
      <p>Сравнивая потоки и процессы Unix, можно отметить, что функция <code>pthread_self</code> аналогична функции <code>getpid</code>.</p>
     </section>
     <section>
      <title>
       <p>Функция pthread_detach</p>
      </title>
      <p>Поток может быть либо <emphasis>присоединяемым</emphasis> (<emphasis>joinable</emphasis>), каким он является по умолчанию, либо <emphasis>отсоединенным</emphasis> (<emphasis>detached</emphasis>). Когда присоединяемый поток завершает свое выполнение, его статус завершения и идентификатор сохраняются, пока другой поток данного процесса не вызовет функцию <code>pthread_join</code>. В свою очередь, отсоединенный поток напоминает процесс-демон: когда он завершается, все занимаемые им ресурсы освобождаются и мы не можем отслеживать его завершение. Если один поток должен знать, когда завершится выполнение другого потока, нам следует оставить последний присоединяемым.</p>
      <p>Функция <code>pthread_detach</code> изменяет состояние потока, превращая его из присоединяемого в отсоединенный.</p>
      <p><code>#include &lt;pthread.h&gt;</code></p>
      <empty-line/>
      <p><code>int pthread_detach(pthread_t <emphasis>tid</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</emphasis></code></p>
      <p>Эта функция обычно вызывается потоком при необходимости изменить собственный статус в следующем формате:</p>
      <p><code>pthread_detach(pthread_self());</code></p>
     </section>
     <section>
      <title>
       <p>Функция pthread_exit</p>
      </title>
      <p>Одним из способов завершения потока является вызов функции <code>pthread_exit</code>.</p>
      <p><code>#include &lt;pthread.h&gt;</code></p>
      <empty-line/>
      <p><code>void pthread_exit(void *<emphasis>status</emphasis>);</code></p>
      <p><code><emphasis>Ничего не возвращает вызвавшему потоку</emphasis></code></p>
      <p>Если поток не является отсоединенным, идентификатор потока и статус завершения сохраняются до того момента, пока какой-либо другой поток данного процесса не вызовет функцию <code>pthread_join</code>.</p>
      <p>Указатель <code>status</code> не должен указывать на объект, локальный по отношению к вызывающему потоку, так как этот объект будет уничтожен при завершении потока.</p>
      <p>Существуют и другие способы завершения потока.</p>
      <p>&#9632; Функция, которая была вызвана потоком (третий аргумент функции <code>pthread_create</code>), может возвратить управление в вызывающий процесс. Поскольку, согласно своему объявлению, эта функция возвращает указатель <code>void</code>, возвращаемое ею значение играет роль статуса завершения данного потока.</p>
      <p>&#9632; Если функция <code>main</code> данного процесса возвращает управление или любой поток вызывает функцию <code>exit</code>, процесс завершается вместе со всеми своими потоками.</p>
     </section>
    </section>
    <section>
     <title>
      <p>26.3. Использование потоков в функции str_cli</p>
     </title>
     <p>В качестве первого примера использования потоков мы перепишем нашу функцию <code>str_cli</code>. В листинге 16.6 была представлена версия этой функции, в которой использовалась функция <code>fork</code>. Напомним, что были также представлены и некоторые другие версии этой функции: изначально в листинге 5.4 функция блокировалась в ожидании ответа и была, как мы показали, далека от оптимальной в случае пакетного ввода; в листинге 6.2 применяется блокируемый ввод-вывод и функция <code>select</code>; версии, показанные в листинге 16.1 и далее, используют неблокируемый ввод-вывод.</p>
     <p>На рис. 26.1 показана структура очередной версии функции str_cli, на этот раз использующей потоки, а в листинге 26.1<a l:href="#n1" type="note">[1]</a> представлен код этой функции.</p>
     <image l:href="#img_133.png"/>
     <p><strong>Рис. 26.1</strong>. Измененная функция str_cli, использующая потоки</p>
     <p><strong>Листинг 26.1</strong>. Функция str_cli, использующая потоки</p>
     <p><code>//threads/strclithread.c</code></p>
     <p><code> 1 #include "unpthread.h"</code></p>
     <empty-line/>
     <p><code> 2 void *copyto(void*);</code></p>
     <empty-line/>
     <p><code> 3 static int sockfd; /* глобальная переменная, доступная обоим потокам */</code></p>
     <p><code> 4 static FILE *fp;</code></p>
     <empty-line/>
     <p><code> 5 void</code></p>
     <p><code> 6 str_cli(FILE *fp_arg, int sockfd_arg)</code></p>
     <p><code> 7 {</code></p>
     <p><code> 8  char recvline[MAXLINE];</code></p>
     <p><code> 9  pthread_t tid;</code></p>
     <empty-line/>
     <p><code>10  sockfd = sockfd_arg; /* копирование аргументов во внешние переменные */</code></p>
     <p><code>11  fp = fp_arg;</code></p>
     <empty-line/>
     <p><code>12  Pthread_create(&amp;tid, NULL, copyto, NULL);</code></p>
     <empty-line/>
     <p><code>13  while (Readline(sockfd, recvline. MAXLINE) &gt; 0)</code></p>
     <p><code>14   Fputs(recvline, stdout);</code></p>
     <p><code>15 }</code></p>
     <empty-line/>
     <p><code>16 void*</code></p>
     <p><code>17 copyto(void *arg)</code></p>
     <p><code>18 {</code></p>
     <p><code>19  char sendline[MAXLINE];</code></p>
     <empty-line/>
     <p><code>20  while (Fgets(sendline, MAXLINE, fp) != NULL)</code></p>
     <p><code>21   Writen(sockfd, sendline, strlen(sendline));</code></p>
     <empty-line/>
     <p><code>22  Shutdown(sockfd, SHUT_WR); /* признак конца файла в стандартном</code></p>
     <p><code>                                  потоке ввода, отправка сегмента FIN */</code></p>
     <p><code>23  return (NULL);</code></p>
     <p><code>24  /* завершение потока происходит, когда в стандартном потоке ввода</code></p>
     <p><code>       встречается признак конца файла */</code></p>
     <p><code>25 }</code></p>
     <subtitle>Заголовочный файл unpthread.h</subtitle>
     <p><code>1</code> Мы впервые встречаемся с заголовочным файлом <code>unpthread.h</code>. Он включает наш обычный заголовочный файл <code>unp.h</code>, затем — заголовочный файл POSIX <code>&lt;pthread.h&gt;</code>, и далее определяет прототипы наших потоковых функций-оберток для <code>pthread_XXX</code> (см. раздел 1.4), название каждой из которых начинается с <code>Pthread_</code>.</p>
     <subtitle>Сохранение аргументов во внешних переменных</subtitle>
     <p><code>10-11</code> Для потока, который мы собираемся создать, требуются значения двух аргументов функции <code>str_cli</code>: <code>fp</code> — указатель на структуру <code>FILE</code> для входного файла, и <code>sockfd</code> — сокет TCP, связанный с сервером. Для простоты мы храним эти два значения во внешних переменных. Альтернативой является запись этих двух значений в структуру, указатель на которую затем передается в качестве аргумента создаваемому потоку.</p>
     <subtitle>Создание нового потока</subtitle>
     <p><code>12</code> Создается поток, и значение нового идентификатора потока сохраняется в <code>tid</code>. Функция, выполняемая новым потоком, — это <code>copyto</code>. Никакие аргументы потоку не передаются.</p>
     <subtitle>Главный цикл потока: копирование из сокета в стандартный поток вывода</subtitle>
     <p><code>13-14</code> В основном цикле вызываются функции <code>readline</code> и <code>fputs</code>, которые осуществляют копирование из сокета в стандартный поток вывода.</p>
     <subtitle>Завершение</subtitle>
     <p><code>15</code> Когда функция <code>str_cli</code> возвращает управление, функция main завершается при помощи вызова функции <code>exit</code> (см. раздел 5.4). При этом завершаются все потоки данного процесса. В обычном сценарии второй поток уже должен завершиться в результате считывания признака конца файла из стандартного потока ввода. Но в случае, когда сервер преждевременно завершил свою работу (см. раздел 5.12), при вызове функции <code>exit</code> завершается также и второй поток, чего мы и добиваемся.</p>
     <subtitle>Поток copyto</subtitle>
     <p><code>16-25</code> Этот поток осуществляет копирование из стандартного потока ввода в сокет. Когда он считывает признак конца файла из стандартного потока ввода, на сокете вызывается функция <code>shutdown</code> и отсылается сегмент FIN, после чего поток возвращает управление. При выполнении оператора <code>return</code> (то есть когда функция, запустившая поток, возвращает управление) поток также завершается.</p>
     <p>В конце раздела 16.2 мы привели результаты измерений времени выполнения для пяти различных реализаций функции <code>str_cli</code>. Мы отметили, что многопоточная версия выполняется всего 8,5 с — немногим быстрее, чем версия, использующая функцию <code>fork</code> (как мы и ожидали), но медленнее, чем версия с неблокируемым вводом-выводом. Тем не менее, сравнивая устройство версии с неблокируемым вводом-выводом (см. раздел 16.2) и версии с использованием потоков, мы заметили, что первая гораздо сложнее. Поэтому мы рекомендуем использовать именно версию с потоками, а не с неблокируемым вводом-выводом.</p>
    </section>
    <section>
     <title>
      <p>26.4. Использование потоков в эхо-сервере TCP</p>
     </title>
     <section>
      <p>Теперь мы перепишем эхо-сервер TCP, приведенный в листинге 5.1, используя для каждого клиента по одному потоку вместо одного процесса. Кроме того, с помощью нашей функции <code>tcp_listen</code> мы сделаем эту версию не зависящей от протокола. В листинге 26.2 показан код сервера.</p>
      <p><strong>Листинг 26.2</strong>. Эхо-сервер TCP, использующий потоки</p>
      <p><code>//threads/tcpserv01.с</code></p>
      <p><code> 1 #include "unpthread.h"</code></p>
      <empty-line/>
      <p><code> 2 static void *doit(void*); /* каждый поток выполняет эту функцию */</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 main(int argc, char **argv)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int listenfd, connfd;</code></p>
      <p><code> 7  pthread_t tid;</code></p>
      <p><code> 8  socklen_t addrlen, len;</code></p>
      <p><code> 9  struct sockaddr *cliaddr;</code></p>
      <empty-line/>
      <p><code>10  if (argc == 2)</code></p>
      <p><code>11   listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
      <p><code>12  else if (argc == 3)</code></p>
      <p><code>13   listenfd = Tcp_listen(argv[1], argv[2], &amp;addrlen);</code></p>
      <p><code>14  else</code></p>
      <p><code>15   err_quit("usage: tcpserv01 [ &lt;host&gt; ] &lt;service or port&gt;");</code></p>
      <empty-line/>
      <p><code>16  cliaddr = Malloc(addrlen);</code></p>
      <empty-line/>
      <p><code>17  for (;;) {</code></p>
      <p><code>18   len = addrlen;</code></p>
      <p><code>19   connfd = Accept(listenfd, cliaddr, &amp;len);</code></p>
      <p><code>20   Pthread_create(&amp;tid, NULL, &amp;doit, (void*)connfd);</code></p>
      <p><code>21  }</code></p>
      <p><code>22 }</code></p>
      <empty-line/>
      <p><code>23 static void*</code></p>
      <p><code>24 doit(void *arg)</code></p>
      <p><code>25 {</code></p>
      <p><code>26  Pthread_detach(pthread_self());</code></p>
      <p><code>27  str_echo((int)arg); /* та же функция, что и раньше */</code></p>
      <p><code>28  Close((int)arg); /* мы закончили с присоединенным сокетом */</code></p>
      <p><code>29  return (NULL);</code></p>
      <p><code>30 }</code></p>
      <subtitle>Создание потока</subtitle>
      <p><code>17-21</code> Когда функция <code>accept</code> возвращает управление, мы вызываем функцию <code>pthread_create</code> вместо функции <code>fork</code>. Мы передаем функции <code>doit</code> единственный аргумент — дескриптор присоединенного сокета <code>connfd</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Мы преобразуем целочисленный дескриптор сокета к универсальному указателю (void). В ANSI С не гарантируется, что такое преобразование будет выполнено корректно, — мы можем быть уверены лишь в том, что оно сработает в тех системах, в которых размер целого числа не превышает размера указателя. К счастью, большинство реализаций Unix обладают этим свойством (см. табл. 1.5). Далее мы поговорим об этом подробнее.</p>
      </cite>
      <subtitle>Функция потока</subtitle>
      <p><code>23-30</code> <code>doit</code> — это функция, выполняемая потоком. Поток отделяет себя с помощью функции <code>pthread_detach</code>, так как нет причины, по которой главному потоку имело бы смысл ждать завершения каждого созданного им потока. Функция <code>str_echo</code> не изменилась и осталась такой же, как в листинге 5.2. Когда эта функция завершается, следует вызвать функцию <code>close</code> для того, чтобы закрыть присоединенный сокет, поскольку этот поток использует все дескрипторы совместно с главным потоком. При использовании функции <code>fork</code> дочерний процесс не должен специально закрывать присоединенный сокет, так как при завершении дочернего процесса все открытые дескрипторы закрываются (см. упражнение 26.2).</p>
      <p>Обратите также внимание на то, что главный поток не закрывает присоединенный сокет, что всегда происходило, когда параллельный сервер вызывал функцию <code>fork</code>. Это объясняется тем, что все потоки внутри процесса совместно используют все дескрипторы, поэтому если главному потоку потребуется вызвать функцию <code>close</code>, это приведет к закрытию соединения. Создание нового потока не влияет на счетчики ссылок для открытых дескрипторов, в отличие от того, что происходит при вызове функции <code>fork</code>.</p>
      <p>В этой программе имеется одна неявная ошибка, о которой рассказывается в разделе 26.5. Можете ли вы ее обнаружить? (См. упражнение 26.5.)</p>
     </section>
     <section>
      <title>
       <p>Передача аргументов новым потокам</p>
      </title>
      <p>Мы уже упомянули, что в листинге 26.2 мы преобразуем целочисленную переменную <code>connfd</code> к указателю на неопределенный тип (<code>void</code>), но этот способ не работает в некоторых системах. Для корректной обработки данной ситуации требуются дополнительные усилия.</p>
      <p>В первую очередь, заметим, что мы не можем просто передать адрес <code>connfd</code> нового потока, то есть следующий код не будет работать:</p>
      <p><code>int main(int argc, char **argv) {</code></p>
      <p><code> int listenfd, connfd;</code></p>
      <p><code> ...</code></p>
      <empty-line/>
      <p><code> for (;;) {</code></p>
      <p><code>  len = addrlen;</code></p>
      <p><code>  connfd = Accept(listenfd, cliaddr, &amp;len);</code></p>
      <empty-line/>
      <p><code>  Pthread_create(&amp;tid, NULL, &amp;doit, &amp;connfd);</code></p>
      <p><code> }</code></p>
      <p><code>}</code></p>
      <empty-line/>
      <p><code>static void* doit(void *arg) {</code></p>
      <p><code> int connfd;</code></p>
      <empty-line/>
      <p><code> connfd = *((int*)arg);</code></p>
      <p><code> Pthread_detach(pthread_self());</code></p>
      <p><code> str_echo(connfd); /* та же функция, что и прежде */</code></p>
      <p><code> Close(connfd);    /* мы закончили с присоединенным сокетом */</code></p>
      <p><code> return(NULL);</code></p>
      <p><code>}</code></p>
      <p>С точки зрения ANSI С здесь все в порядке: мы гарантированно можем преобразовать целочисленный указатель к типу <code>void*</code> и затем обратно преобразовать получившийся указатель на неопределенный тип к целочисленному указателю. Проблема заключается в другом — на что именно он будет указывать?</p>
      <p>В главном потоке имеется одна целочисленная переменная <code>connfd</code>, и при каждом вызове функции <code>accept</code> значение этой переменной меняется на новое (в соответствии с новым присоединенным сокетом). Может сложиться следующая ситуация:</p>
      <p>&#9632; Функция <code>accept</code> возвращает управление, записывается новое значение переменной <code>connfd</code> (допустим, новый дескриптор равен 5) и в главном потоке вызывается функция <code>pthread_create</code>. Указатель на <code>connfd</code> (а не фактическое его значение!) является последним аргументом функции <code>pthread_create</code>.</p>
      <p>&#9632; Создается новый поток, и начинает выполняться функция <code>doit</code>.</p>
      <p>&#9632; Готово другое соединение, и главный поток снова начинает выполняться (прежде, чем начнется выполнение вновь созданного потока). Завершается функция <code>accept</code>, записывается новое значение переменной <code>connfd</code> (например, значение нового дескриптора равно 6) и главный поток вновь вызывает функцию <code>pthread_create</code>.</p>
      <p>Хотя созданы два новых потока, оба они будут работать с одним и тем же последним значением переменной <code>connfd</code>, которое, согласно нашему предположению, равно 6. Проблема заключается в том, что несколько потоков получают доступ к совместно используемой переменной (целочисленному значению, хранящемуся в <code>connfd</code>) при отсутствии синхронизации. В листинге 26.2 мы решаем эту проблему, передавая значение переменной <code>connfd</code> функции <code>pthread_create</code>, вместо того чтобы передавать указатель на это значение. Этот метод работает благодаря тому способу, которым целочисленные значения в С передаются вызываемой функции (копия значения помещается в стек вызванной функции).</p>
      <p>В листинге 26.3 показано более удачное решение описанной проблемы.</p>
      <p><strong>Листинг 26.3</strong>. Эхо-сервер TCP, использующий потоки с более переносимой передачей аргументов</p>
      <p><code>//threads/tcpserv02.c</code></p>
      <p><code> 1 #include "unpthread.h"</code></p>
      <empty-line/>
      <p><code> 2 static void *doit(void*); /* каждый поток выполняет эту функцию */</code></p>
      <empty-line/>
      <p><code> 3 int</code></p>
      <p><code> 4 main(int argc, char **argv)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int listenfd, *iptr;</code></p>
      <p><code> 7  thread_t tid;</code></p>
      <p><code> 8  socklen_t addrlen, len;</code></p>
      <p><code> 9  struct sockaddr *cliaddr;</code></p>
      <empty-line/>
      <p><code>10  if (argc == 2)</code></p>
      <p><code>11   listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
      <p><code>12  else if (argc == 3)</code></p>
      <p><code>13   listenfd = Tcp_listen(argv[1], argv[2], &amp;addrlen);</code></p>
      <p><code>14  else</code></p>
      <p><code>15   err_quit("usage: tcpserv01 [ &lt;host&gt; ] &lt;service or port&gt;");</code></p>
      <empty-line/>
      <p><code>16  cliaddr = Malloc(addrlen);</code></p>
      <empty-line/>
      <p><code>17  for (;;) {</code></p>
      <p><code>18   len = addrlen;</code></p>
      <p><code>19   iptr = Malloc(sizeof(int));</code></p>
      <p><code>20   *iptr = Accept(listenfd, cliaddr, &amp;len);</code></p>
      <p><code>21   Pthread_create(&amp;tid, NULL, &amp;doit, iptr);</code></p>
      <p><code>22  }</code></p>
      <p><code>23 }</code></p>
      <empty-line/>
      <p><code>24 static void*</code></p>
      <p><code>25 doit(void *arg)</code></p>
      <p><code>26 {</code></p>
      <p><code>27  int connfd;</code></p>
      <empty-line/>
      <p><code>28  connfd = *((int*)arg);</code></p>
      <p><code>29  free(arg);</code></p>
      <empty-line/>
      <p><code>30  Pthread_detach(pthread_self());</code></p>
      <p><code>31  str_echo(connfd); /* та же функция, что и раньше */</code></p>
      <p><code>32  Close(connfd); /* мы закончили с присоединенным сокетом */</code></p>
      <p><code>33  return (NULL);</code></p>
      <p><code>34 }</code></p>
      <p><code>17-22</code> Каждый раз перед вызовом функции <code>accept</code> мы вызываем функцию <code>malloc</code> и выделяем в памяти пространство для целочисленной переменной (дескриптора присоединенного сокета). Таким образом каждый поток получает свою собственную копию этого дескриптора.</p>
      <p><code>28-29</code> Поток получает значение дескриптора присоединенного сокета, а затем освобождает занимаемую им память с помощью функции <code>free</code>.</p>
      <p>Исторически функции <code>malloc</code> и <code>free</code> не допускали повторного вхождения. Это означает, что при вызове той или иной функции из обработчика сигнала в то время, когда главный поток выполняет одну из них, возникает большая путаница, так как эти функции оперируют статическими структурами данных. Как же мы можем вызывать эти две функции в листинге 26.3? Дело в том, что в POSIX требуется, чтобы эти две функции, так же как и многие другие, были <emphasis>безопасными в многопоточной среде</emphasis> (<emphasis>thread-safe</emphasis>). Обычно это достигается с помощью некоторой разновидности синхронизации, осуществляемой внутри библиотечных функций и являющейся для нас прозрачной (то есть незаметной).</p>
     </section>
     <section>
      <title>
       <p>Функции, безопасные в многопоточной среде</p>
      </title>
      <p>Стандарт POSIX.1 требует, чтобы все определенные в нем функции, а также функции, определенные в стандарте ANSI С, были безопасными в многопоточной среде. Исключения из этого правила приведены в табл. 26.1.</p>
      <p>К сожалению, в POSIX.1 ничего не сказано о безопасности в многопоточной среде по отношению к функциям сетевого API. Последние пять строк в этой таблице появились благодаря Unix 98. В разделе 11.18 мы говорили о том, что функции <code>gethostbyname</code> и <code>gethostbyaddr</code> не допускают повторного вхождения. Как уже отмечалось, некоторые производители определяют версии этих функций, обладающие свойством безопасности в многопоточной среде (их названия заканчиваются на <code>_r</code>), но поскольку они не стандартизованы, лучше от них отказаться. Все функции <code>getXXX</code>, не допускающие повторного вхождения, были приведены в табл. 11.5.</p>
      <empty-line/>
      <p><strong>Таблица 26.1</strong>. Функции, безопасные в многопоточной среде</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Могут не быть безопасными в многопоточной среде</th>
        <th align="left" valign="top">Должны быть безопасными в многопоточной среде</th>
        <th align="left" valign="top">Комментарии</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Asctime</td>
        <td align="left" valign="top">asctime_r</td>
        <td align="left" valign="top">Безопасна в многопоточной среде только в случае непустого аргумента</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top"/>
        <td align="left" valign="top">ctermid</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Ctime</td>
        <td align="left" valign="top">ctime_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">getc_unlocked</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">getchar_unlocked</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Getgrid</td>
        <td align="left" valign="top">getgrid_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Getgrnam</td>
        <td align="left" valign="top">getgrnam_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Getlogin</td>
        <td align="left" valign="top">getlogin_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Getpwnam</td>
        <td align="left" valign="top">getpwnam_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Getpwuid</td>
        <td align="left" valign="top">getpwuid_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Gmtime</td>
        <td align="left" valign="top">gmtime_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Localtime</td>
        <td align="left" valign="top">localtime_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">putc_unlocked</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">putchar_unlocked</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Rand</td>
        <td align="left" valign="top">rand_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Readdir</td>
        <td align="left" valign="top">readdir_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Strtock</td>
        <td align="left" valign="top">strtock_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top"/>
        <td align="left" valign="top">tmpnam</td>
        <td align="left" valign="top">Безопасна в многопоточной среде только в случае непустого аргумента</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Ttyname</td>
        <td align="left" valign="top">ttyname_r</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">GethostXXX</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">GetnetXXX</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">GetprotoXXX</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">GetservXXX</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">inet_ntoa</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top"/>
       </tr>
      </table>
      <p>Приведенная таблица позволяет заключить, что общим способом сделать функцию допускающей повторное вхождение является определение новой функции с названием, оканчивающимся на <code>_r</code>. Обе функции будут безопасными в многопоточной среде, только если вызывающий процесс выделяет в памяти место для результата и передает соответствующий указатель как аргумент функции.</p>
     </section>
    </section>
    <section>
     <title>
      <p>26.5. Собственные данные потоков</p>
     </title>
     <section>
      <p>При преобразовании существующих функций для использования в многопоточной среде часто возникают проблемы, связанные со статическими переменными. Функция, сохраняющая состояние в собственном буфере или возвращающая результат в виде указателя на статический буфер, не является безопасной в многопоточной среде, поскольку несколько потоков не могут использовать один и тот же буфер для хранения разных данных. Такая проблема имеет несколько решений.</p>
      <p>1. Использование собственных данных потоков (thread-specific data). Это нетривиальная задача, и функция при этом преобразуется к такому виду, что может использоваться только в системах, поддерживающих потоки. Преимущество этого подхода заключается в том, что не меняется вызывающая последовательность, и все изменения связаны с библиотечной функцией, а не с приложениями, которые вызывают эту функцию. Позже в этом разделе мы покажем безопасную в многопоточной среде версию функции <code>readline</code>, созданную с применением собственных данных потоков.</p>
      <p>2. Изменение вызывающей последовательности таким образом, чтобы вызывающий процесс упаковывал все аргументы в некую структуру, а также записывал в нее статические переменные из листинга 3.12. Это также было сделано, и в листинге 26.4 показана новая структура и новые прототипы функций.</p>
      <p><strong>Листинг 26.4</strong>. Структура данных и прототип функции для версии функции readline, допускающей повторное вхождение</p>
      <p><code>typedef struct {</code></p>
      <p><code> int    read_fd;    /* дескриптор, указывающий, откуда считываются данные */</code></p>
      <p><code> char   *read_ptr;   /* буфер, куда передаются данные */</code></p>
      <p><code> size_t read_maxlen; /* максимальное количество байтов, которое может быть считано */</code></p>
      <p><code> /* следующие три элемента для внутреннего использования функцией */</code></p>
      <p><code> int    rl_cnt;      /* инициализируется нулем */</code></p>
      <p><code> char   *rl_bufptr;  /* инициализируется значением rl_buf */</code></p>
      <p><code> char   rl_buf[MAXLINE];</code></p>
      <p><code>} Rline;</code></p>
      <empty-line/>
      <p><code>void readline_rinit(int, void*, size_t, Rline*);</code></p>
      <p><code>ssize_t readline_r(Rline*);</code></p>
      <p><code>ssize_t Readline_r(Rline*);</code></p>
      <p>Эти новые функции могут использоваться как в системах с поддержкой потоков, так и в тех, где потоки не поддерживаются, но все приложения, вызывающие функцию <code>readline</code>, должны быть изменены.</p>
      <p>3. Реструктуризация интерфейса для исключения статических переменных и обеспечения безопасности функции в многопоточной среде. Для <code>readline</code> это будет означать отказ от увеличения быстродействия, достигнутого в листинге 3.12, и возвращение к более старой версии, представленной в листинге 3.11. Поскольку мы назвали старую версию «ужасно медленной», это решение не всегда пригодно на практике.</p>
      <p>Использование собственных данных потоков — это распространенный способ сделать существующую функцию безопасной в многопоточной среде. Прежде чем описывать функции Pthread, работающие с такими данными, мы опишем саму концепцию и возможный способ реализации, так как эти функции кажутся более сложными, чем являются на самом деле.</p>
      <p>Частично осложнения возникают по той причине, что во всех книгах, где идет речь о потоках, описание собственных данных потоков дается по образцу стандарта Pthreads. Пары ключ-значение и ключи рассматриваются в них как непрозрачные объекты. Мы описываем собственные данные потоков в терминах индексов и указателей, так как обычно в реализациях в качестве ключей используются небольшие положительные целые числа (индексы), а значение, ассоциированное с ключом, — это просто указатель на область памяти, выделяемую потоку с помощью функции <code>malloc</code>.</p>
      <p>В каждой системе поддерживается ограниченное количество объектов собственных данных потоков. В POSIX требуется, чтобы этот предел не превышал 128 (на каждый процесс), и в следующем примере мы используем именно это значение. Система (вероятно, библиотека потоков) поддерживает один массив структур (которые мы называем структурами <code>Key</code>) для каждого процесса, как показано на рис. 26.2.</p>
      <image l:href="#img_134.png"/>
      <p><strong>Рис. 26.2</strong>. Возможная реализация собственных данных потока</p>
      <p>Флаг в структуре <code>Key</code> указывает, используется ли в настоящий момент данный элемент массива. Все флаги инициализируются как указывающие на то, что элемент не используется. Когда поток вызывает функцию <code>pthread_key_create</code> для создания нового элемента собственных данных потока, система отыскивает в массиве структур <code>Key</code> первую структуру, не используемую в настоящий момент. Индекс этой структуры, который может иметь значение от 0 до 127, называется ключом и возвращается вызывающему потоку как результат выполнения функции. О втором элементе структуры <code>Key</code>, так называемом <emphasis>указателе-деструкторе</emphasis>, мы поговорим чуть позже.</p>
      <p>В дополнение к массиву структур <code>Key</code>, общему для всего процесса, система хранит набор сведений о каждом потоке процесса в структуре <code>Pthread</code>. Частью этой структуры является массив указателей, состоящий из 128 элементов, который мы называем <code>pkey</code>. Это показано на рис. 26.3.</p>
      <image l:href="#img_135.png"/>
      <p><strong>Рис. 26.3</strong>. Информация, хранящаяся в системе для каждого потока</p>
      <p>Все элементы массива <code>pkey</code> инициализируются пустыми указателями. Эти 128 указателей являются «значениями», ассоциированными с каждым из 128 «ключей» процесса.</p>
      <p>Когда мы с помощью функции <code>pthread_key_create</code> создаем ключ, система сообщает нам фактическое значение ключа (индекс). Затем каждый поток может сохранить значение (указатель), связанное с этим ключом, и, как правило, каждый поток получает этот указатель в виде возвращаемого значения функции <code>malloc</code>. Частично путаница с собственными данными потока обусловлена тем, что указатель в паре ключ-значение играет роль значения, но сами собственные данные потока — это то, на что указывает данный указатель.</p>
      <p>Теперь мы перейдем к примеру применения собственных данных потока, предполагая, что наша функция <code>readline</code> использует их для хранения информации о состоянии каждого потока при последовательных обращениях к ней. Вскоре мы покажем код, выполняющий эту задачу, в котором функция <code>readline</code> модифицирована так, чтобы реализовать представленную далее последовательность шагов.</p>
      <p>1. Запускается процесс, и создается несколько потоков.</p>
      <p>2. Один из потоков вызовет функцию <code>readline</code> первой, а та, в свою очередь, вызовет функцию <code>phtread_key_create</code>. Система отыщет первую неиспользуемую структуру <code>Key</code> (см. рис. 26.2) и возвратит вызывающему процессу ее индекс. В данном примере мы предполагаем, что индекс равен 1.</p>
      <p>Мы будем использовать функцию <code>pthread_once</code>, чтобы гарантировать, что функция <code>pthread_key_create</code> вызывается только первым потоком, вызвавшим функцию <code>readline</code>.</p>
      <p>3. Функция <code>readline</code> вызывает функцию <code>pthread_getspecific</code>, чтобы получить значение <code>pkey[1]</code> («указатель» на рис. 26.3 для ключа, имеющего значение 1) для данного потока, но эта функция возвращает пустой указатель. Тогда функция <code>readline</code> вызывает функцию <code>malloc</code> для выделения памяти, которая необходима для хранения информации о каждом потоке при последовательных вызовах функции <code>readline</code>. Функция <code>readline</code> инициализирует эти области памяти по мере надобности и вызывает функцию <code>pthread_setspecific</code>, чтобы установить указатель собственных данных потока (<code>pkey[1]</code>), соответствующий данному ключу, на только что выделенную область памяти. Мы показываем этот процесс на рис. 26.4, предполагая, что вызывающий поток — это поток с номером 0 в данном процессе.</p>
      <image l:href="#img_136.png"/>
      <p><strong>Рис. 26.4</strong>. Соответствие между областью памяти, выделенной функцией malloc, и указателем собственных данных потока</p>
      <p>На этом рисунке мы отмечаем, что структура Pthread поддерживается системой (вероятно, библиотекой потоков), но фактически собственные данные потока, которые мы размещаем в памяти с помощью функции <code>malloc</code>, поддерживаются нашей функцией (в данном случае <code>readline</code>). Все, что делает функция <code>pthread_setspecific</code>, — это установка указателя для данного ключа в структуре Pthread на выделенную область памяти. Аналогично, действие функции <code>pthread_getspecific</code> сводится к возвращению этого указателя.</p>
      <p>4. Другой поток, например поток с номером <code>n</code>, вызывает функцию <code>readline</code>, возможно, в тот момент, когда поток с номером 0 все еще находится в стадии выполнения функции <code>readline</code>.</p>
      <p>Функция <code>readline</code> вызывает функцию <code>pthread_once</code>, чтобы инициализировать ключ этого элемента собственных данных, но так как эта функция уже была однажды вызвана, то больше она не выполняется.</p>
      <p>5. Функция <code>readline</code> вызывает функцию <code>pthread_getspecific</code> для получения значения указателя <code>pkey[1]</code> для данного потока, но возвращается пустой указатель. Тогда поток вызывает функцию <code>malloc</code> и функцию <code>pthread_setspecific</code>, как и в случае с потоком номер 0, инициализируя элемент собственных данных потока, соответствующий этому ключу (1). Этот процесс иллюстрирует рис. 26.5.</p>
      <image l:href="#img_137.png"/>
      <p><strong>Рис. 26.5</strong>. Структуры данных после того, как поток n инициализировал свои собственные данные</p>
      <p>6. Поток номер <emphasis>n</emphasis> продолжает выполнять функцию <code>readline</code>, используя и модифицируя свои собственные данные.</p>
      <p>Один вопрос, который мы пока не рассмотрели, заключается в следующем: что происходит, когда поток завершает свое выполнение? Если поток вызвал функцию <code>readline</code>, эта функция выделила в памяти область, которая должна быть освобождена по завершении выполнения потока. Для этого используется <emphasis>указатель-деструктор</emphasis>, показанный на рис. 26.2. Когда поток, создающий элемент собственных данных, вызывает функцию <code>pthread_key_create</code>, одним из аргументов этой функции является указатель на <emphasis>функцию-деструктор</emphasis>. Когда выполнение потока завершается, система перебирает массив <code>pkey</code> для данного потока, вызывая соответствующую функцию-деструктор для каждого непустого указателя <code>pkey</code>. Под «соответствующим деструктором» мы понимаем указатель на функцию, хранящийся в массиве <code>Key</code> с рис. 26.2. Таким образом осуществляется освобождение памяти, занимаемой собственными данными потока, когда выполнение потока завершается.</p>
      <p>Первые две функции, которые обычно вызываются при работе с собственными данными потока, — это <code>pthread_once</code> и <code>pthread_key_create</code>.</p>
      <p><code>#include &lt;pthread.h&gt;</code></p>
      <empty-line/>
      <p><code>int pthread_once(pthread_once_t *<emphasis>onceptr</emphasis>, void (*<emphasis>init</emphasis>)(void));</code></p>
      <p><code>int pthread_key_create(pthread_key_t *<emphasis>keyptr</emphasis>, void (*<emphasis>destructor</emphasis>)(void *<emphasis>value</emphasis>));</code></p>
      <p><code><emphasis>Обе функции возвращают: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</emphasis></code></p>
      <p>Функция <code>pthread_once</code> обычно вызывается при вызове функции, манипулирующей собственными данными потока, но <code>pthread_once</code> использует значение переменной, на которую указывает <code>onceptr</code>, чтобы гарантировать, что функция <code>init</code> вызывается для каждого процесса только один раз.</p>
      <p>Функция <code>pthread_key_create</code> должна вызываться только один раз для данного ключа в пределах одного процесса. Значение ключа возвращается с помощью указателя <code>keyptr</code>, а функция<emphasis>-деструктор</emphasis> (если аргумент является непустым указателем) будет вызываться каждым потоком по завершении его выполнения, если этот поток записывал какое-либо значение, соответствующее этому ключу.</p>
      <p>Обычно эти две функции используются следующим образом (если игнорировать возвращение ошибок):</p>
      <p><code>pthread_key_t rl_key;</code></p>
      <p><code>pthread_once_t rl_once = PTHREAD_ONCE_INIT;</code></p>
      <empty-line/>
      <p><code>void readline_destructor(void *ptr) {</code></p>
      <p><code> free(ptr);</code></p>
      <p><code>}</code></p>
      <empty-line/>
      <p><code>void readline_once(void) {</code></p>
      <p><code> pthread_key_create(&amp;rl_key, readline_destructor);</code></p>
      <p><code>}</code></p>
      <empty-line/>
      <p><code>ssize_t readline(...) {</code></p>
      <p><code> ...</code></p>
      <empty-line/>
      <p><code> pthread_once(&amp;rl_once, readline_once);</code></p>
      <empty-line/>
      <p><code> if ((ptr = pthread_getspecific(rl_key)) == NULL) {</code></p>
      <p><code>  ptr = Malloc(...);</code></p>
      <p><code>  pthread_setspecifiс(rl_key, ptr);</code></p>
      <p><code>  /* инициализация области памяти, на которую указывает ptr */</code></p>
      <p><code> }</code></p>
      <p><code> ...</code></p>
      <p><code> /* используются значения, на которые указывает ptr */</code></p>
      <p><code>}</code></p>
      <p>Каждый раз, когда вызывается функция <code>readline</code>, она вызывает функцию <code>pthread_once</code>. Эта функция использует значение, на которое указывает ее аргумент-указатель <code>onceptr</code> (содержащийся в переменной <code>rl_once</code>), чтобы удостовериться, что функция <code>init</code> вызывается только один раз. Функция инициализации <code>readline_once</code> создает ключ для собственных данных потока, который хранится в <code>rl_key</code> и который функция <code>readline</code> затем использует в вызовах функций <code>pthread_getspecific</code> и <code>pthread_setspecific</code>.</p>
      <p>Функции <code>pthread_getspecific</code> и <code>pthread_setspecific</code> используются для того, чтобы получать и задавать значение, ассоциированное с данным ключом. Это значение представляет собой тот указатель, который показан на рис. 26.3. На что указывает этот указатель — зависит от приложения, но обычно он указывает на динамически выделяемый участок памяти.</p>
      <p><code>#include &lt;pthread.h&gt;</code></p>
      <empty-line/>
      <p><code>void *pthread_getspecific(pthread_key_t <emphasis>key</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: указатель на собственные данные потока (возможно, пустой указатель)</emphasis></code></p>
      <empty-line/>
      <p><code>int pthread_setspecific(pthread_key_t <emphasis>key</emphasis>, const void *<emphasis>value</emphasis>);</code></p>
      <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</emphasis></code></p>
      <p>Обратите внимание на то, что аргументом функции <code>pthread_key_create</code> является указатель на ключ (поскольку эта функция хранит значение, присвоенное ключу), в то время как аргументами функций <code>get</code> и <code>set</code> являются сами ключи (которые, скорее всего, представляют собой небольшие целые числа, как уже говорилось).</p>
     </section>
     <section>
      <title>
       <p>Пример: функция readline, использующая собственные данные потока</p>
      </title>
      <p>В этом разделе мы приводим полный пример использования собственных данных потока, преобразуя оптимизированную версию функции <code>readline</code> из листинга 3.12 к виду, безопасному в многопоточной среде, не изменяя последовательность вызовов.</p>
      <p>В листинге 26.5 показана первая часть функции: переменные <code>pthread_key_t</code> и <code>pthread_once_t</code>, функции <code>readline_destructor</code> и <code>readline_once</code> и наша структура <code>Rline</code>, которая содержит всю информацию, нужную нам для каждого потока.</p>
      <p><strong>Листинг 26.5</strong>. Первая часть функции readline, безопасной в многопоточной среде</p>
      <p><code>//threads/readline.c</code></p>
      <p><code> 1 #include "unpthread.h"</code></p>
      <empty-line/>
      <p><code> 2 static pthread_key_t rl_key;</code></p>
      <p><code> 3 static pthread_once_t rl_once = PTHREAD_ONCE_INIT;</code></p>
      <empty-line/>
      <p><code> 4 static void</code></p>
      <p><code> 5 readline_destructor(void *ptr)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  free(ptr);</code></p>
      <p><code> 8 }</code></p>
      <empty-line/>
      <p><code>9 static void</code></p>
      <p><code>10 readline_once(void)</code></p>
      <p><code>11 {</code></p>
      <p><code>12  Pthread_key_create(&amp;rl_key, readline_destructor);</code></p>
      <p><code>13 }</code></p>
      <empty-line/>
      <p><code>14 typedef struct {</code></p>
      <p><code>15  int rl_cnt;      /* инициализируется нулем */</code></p>
      <p><code>16  char *rl_bufptr; /* инициализируется значением rl_buf */</code></p>
      <p><code>17  char rl_buf[MAXLINE];</code></p>
      <p><code>18 } Rline;</code></p>
      <subtitle>Деструктор</subtitle>
      <p><code>4-8</code> Наша функция-деструктор просто освобождает всю память, которая была выделена для данного потока.</p>
      <subtitle>«Одноразовая» функция</subtitle>
      <p><code>9-13</code> Мы увидим, что наша «одноразовая» (то есть вызываемая только один раз) функция вызывается однократно из функции <code>pthread_once</code> и создает ключ, который затем используется в функции <code>readline</code>.</p>
      <subtitle>Структура Rline</subtitle>
      <p><code>14-18</code> Наша структура <code>Rline</code> содержит три переменные, которые, будучи объявленными как статические (<code>static</code>) в листинге 3.12, привели к возникновению описанных далее проблем. Такая структура динамически выделяется в памяти для каждого потока, а по завершении выполнения этого потока она освобождается функцией-деструктором.</p>
      <p>В листинге 26.6 показана сама функция <code>readline</code>, а также функция <code>my_read</code>, которую она вызывает. Этот листинг является модификацией листинга 3.12.</p>
      <p><strong>Листинг 26.6</strong>. Вторая часть функции readline, безопасной в многопоточной среде</p>
      <p><code>//threads/readline.c</code></p>
      <p><code>19 static ssize_t</code></p>
      <p><code>20 my_read(Rline *tsd, int fd, char *ptr)</code></p>
      <p><code>21 {</code></p>
      <p><code>22  if (tsd-&gt;rl_cnt &lt;= 0) {</code></p>
      <p><code>23 again:</code></p>
      <p><code>24   if ((tsd-&gt;rl_cnt = read(fd, tsd-&gt;rl_buf, MAXLINE)) &lt; 0) {</code></p>
      <p><code>25    if (errno == EINTR)</code></p>
      <p><code>26     goto again;</code></p>
      <p><code>27    return (-1);</code></p>
      <p><code>28   } else if (tsd-&gt;rl_cnt == 0)</code></p>
      <p><code>29    return (0);</code></p>
      <p><code>30   tsd-&gt;rl_bufptr = tsd-&gt;rl_buf;</code></p>
      <p><code>31  }</code></p>
      <p><code>32  tsd-&gt;rl_cnt--;</code></p>
      <p><code>33  *ptr = *tsd-&gt;rl_bufptr++;</code></p>
      <p><code>34  return (1);</code></p>
      <p><code>35 }</code></p>
      <empty-line/>
      <p><code>36 ssize_t</code></p>
      <p><code>37 readline(int fd, void *vptr, size_t maxlen)</code></p>
      <p><code>38 {</code></p>
      <p><code>39  int n, rc;</code></p>
      <p><code>40  char c, *ptr;</code></p>
      <p><code>41  Rline *tsd;</code></p>
      <empty-line/>
      <p><code>42  Pthread_once(&amp;rl_once, readline_once);</code></p>
      <p><code>43  if ((tsd = pthread_getspecific(rl_key)) == NULL) {</code></p>
      <p><code>44   tsd = Calloc(1, sizeof(Rline)); /* инициализируется нулем */</code></p>
      <p><code>45   Pthread_setspecifiс(rl_key, tsd);</code></p>
      <p><code>46  }</code></p>
      <empty-line/>
      <p><code>47  ptr = vptr;</code></p>
      <p><code>48  for (n = 1; n &lt; maxlen; n++) {</code></p>
      <p><code>49   if ((rc = my_read(tsd, fd, &amp;c)) == 1) {</code></p>
      <p><code>50    *ptr++ = c;</code></p>
      <p><code>51    if (c == '\n')</code></p>
      <p><code>52     break;</code></p>
      <p><code>53   } else if (rc == 0) {</code></p>
      <p><code>54    *ptr = 0;</code></p>
      <p><code>55    return (n-1); /* EOF, данные не были считаны */</code></p>
      <p><code>56   } else</code></p>
      <p><code>57    return (-1); /* ошибка, errno устанавливается функцией read() */</code></p>
      <p><code>58   }</code></p>
      <empty-line/>
      <p><code>59  *ptr = 0;</code></p>
      <p><code>60  return (n);</code></p>
      <p><code>61 }</code></p>
      <subtitle>Функция my_read</subtitle>
      <p><code>19-35</code> Первым аргументом функции теперь является указатель на структуру <code>Rline</code>, которая была размещена в памяти для данного потока (и содержит собственные данные этого потока).</p>
      <subtitle>Размещение собственных данных потока в памяти</subtitle>
      <p><code>42</code> Сначала мы вызываем функцию <code>pthread_once</code>, так чтобы первый поток, вызывающий функцию <code>readline</code> в этом процессе, вызвал бы функцию <code>readline_once</code> для создания ключа собственных данных потока.</p>
      <subtitle>Получение указателя на собственные данные потока</subtitle>
      <p><code>43-46</code> Функция <code>pthread_getspecific</code> возвращает указатель на структуру <code>Rline</code> для данного потока. Но если это первый вызов функции <code>readline</code> данным потоком, то возвращаемым значением будет пустой указатель. В таком случае мы выделяем в памяти место для структуры <code>Rline</code>, а элемент <code>rl_cnt</code> этой структуры инициализируется нулем с помощью функции <code>calloc</code>. Затем мы записываем этот указатель для данного потока, вызывая функцию <code>pthread_setspecific</code>. Когда этот поток вызовет функцию <code>readline</code> в следующий раз, функция <code>pthread_getspecific</code> возвратит этот указатель, который был только что записан.</p>
     </section>
    </section>
    <section>
     <title>
      <p>26.6. Веб-клиент и одновременное соединение (продолжение)</p>
     </title>
     <p>Вернемся к нашему примеру с веб-клиентом из раздела 16.5 и перепишем его с использованием потоков вместо неблокируемой функции <code>connect</code>. Мы можем оставить сокеты в их заданном по умолчанию виде — блокируемыми, и создать один поток на каждое соединение. Каждый поток может блокироваться в вызове функции <code>connect</code>, так как ядро будет просто выполнять какой-либо другой поток, готовый к работе.</p>
     <p>В листинге 26.7 показана первая часть нашей программы, глобальные переменные и начало функции <code>main</code>.</p>
     <p><strong>Листинг 26.7</strong>. Глобальные переменные и начало функции main</p>
     <p><code>//threads/web01.c</code></p>
     <p><code> 1 #include "unpthread.h"</code></p>
     <p><code> 2 #include &lt;thread.h&gt; /* потоки Solaris */</code></p>
     <empty-line/>
     <p><code> 3 #define MAXFILES 20</code></p>
     <p><code> 4 #define SERV    "80" /* номер порта или имя службы */</code></p>
     <empty-line/>
     <p><code> 5 struct file {</code></p>
     <p><code> 6  char      *f_name; /* имя файла */</code></p>
     <p><code> 7  char      *f_host; /* имя узла или IP-адрес */</code></p>
     <p><code> 8  int       f_fd;    /* дескриптор */</code></p>
     <p><code> 9  int       f_flags; /* F_xxx ниже */</code></p>
     <p><code>10  pthread_t f_tid;   /* идентификатор потока */</code></p>
     <p><code>11 } file[MAXFILES];</code></p>
     <p><code>12 #define F_CONNECTING 1 /* функция connect () в процессе</code></p>
     <p><code>                             выполнения */</code></p>
     <p><code>13 #define F_READING 2    /* функция connect() завершена;</code></p>
     <p><code>                             выполняется считывание */</code></p>
     <p><code>14 #define F_DONE 4       /* все сделано */</code></p>
     <empty-line/>
     <p><code>15 #define GET_CMD "GET %s HTTP/1.0\r\n\r\n"</code></p>
     <empty-line/>
     <p><code>16 int nconn, nfiles, nlefttoconn, nlefttoread;</code></p>
     <empty-line/>
     <p><code>17 void *do_get_read(void*);</code></p>
     <p><code>18 void home_page(const char*, const char*);</code></p>
     <p><code>19 void write_get_cmd(struct file*);</code></p>
     <empty-line/>
     <p><code>20 int</code></p>
     <p><code>21 main(int argc, char **argv)</code></p>
     <p><code>22 {</code></p>
     <p><code>23  int i, n, maxnconn;</code></p>
     <p><code>24  pthread_t tid;</code></p>
     <p><code>25  struct file *fptr;</code></p>
     <empty-line/>
     <p><code>26  if (argc &lt; 5)</code></p>
     <p><code>27   err_quit("usage: web &lt;#conns&gt; &lt;IPaddr&gt; &lt;homepage&gt; file1 ...");</code></p>
     <p><code>28  maxnconn = atoi(argv[1]);</code></p>
     <empty-line/>
     <p><code>29  nfiles = min(argc - 4, MAXFILES);</code></p>
     <p><code>30  for (i = 0; i &lt; nfiles; i++) {</code></p>
     <p><code>31   file[i].f_name = argv[i + 4];</code></p>
     <p><code>32   file[i].f_host = argv[2];</code></p>
     <p><code>33   file[i].f_flags = 0;</code></p>
     <p><code>34  }</code></p>
     <p><code>35  printf("nfiles = %d\n", nfiles);</code></p>
     <empty-line/>
     <p><code>36  home_page(argv[2], argv[3]);</code></p>
     <empty-line/>
     <p><code>37  nlefttoread = nlefttoconn = nfiles;</code></p>
     <p><code>38  nconn = 0;</code></p>
     <subtitle>Глобальные переменные</subtitle>
     <p><code>1-16</code> Мы подключаем заголовочный файл <code>&lt;thread.h&gt;</code> вдобавок к обычному <code>&lt;pthread.h&gt;</code>, так как нам требуется использовать потоки Solaris в дополнение к потокам Pthreads, как мы вскоре покажем.</p>
     <p><code>10</code> Мы добавили к структуре <code>file</code> один элемент — идентификатор потока f<code>_tid</code>. Остальная часть этого кода аналогична коду в листинге 16.9. В этой версии нам не нужно использовать функцию <code>select</code>, а следовательно, не нужны наборы дескрипторов и переменная <code>maxfd</code>.</p>
     <p><code>36</code> Функция <code>home_page</code> не изменилась относительно листинга 16.10. В листинге 26.8 показан основной рабочий цикл потока main.</p>
     <p><strong>Листинг 26.8</strong>. Основной рабочий цикл потока main</p>
     <p><code>//threads/web01.c</code></p>
     <p><code>39  while (nlefttoread &gt; 0) {</code></p>
     <p><code>40   while (nconn &lt; maxnconn &amp;&amp; nlefttoconn &gt; 0) {</code></p>
     <p><code>41    /* находим файл для считывания */</code></p>
     <p><code>42    for (i = 0; i &lt; nfiles; i++)</code></p>
     <p><code>43     if (file[i].f_flags == 0)</code></p>
     <p><code>44      break;</code></p>
     <p><code>45    if (i == nfiles)</code></p>
     <p><code>46     err_quit("nlefttoconn = %d but nothing found", nlefttoconn);</code></p>
     <empty-line/>
     <p><code>47    file[i].f_flags = F_CONNECTING;</code></p>
     <p><code>48    Pthread_create(&amp;tid, NULL, &amp;do_get_read, &amp;file[i]);</code></p>
     <p><code>49    file[i].f_tid = tid;</code></p>
     <p><code>50    nconn++;</code></p>
     <p><code>51    nlefttoconn--;</code></p>
     <p><code>52   }</code></p>
     <empty-line/>
     <p><code>53   if ((n = thr_join(0, &amp;tid, (void**)&amp;fptr)) != 0)</code></p>
     <p><code>54    errno = n, err_sys("thr_join error");</code></p>
     <empty-line/>
     <p><code>55   nconn--;</code></p>
     <p><code>56   nlefttoread--;</code></p>
     <p><code>57   printf("thread id %d for %s done\n", tid, fptr-&gt;f_name);</code></p>
     <p><code>58  }</code></p>
     <empty-line/>
     <p><code>59  exit(0);</code></p>
     <p><code>60 }</code></p>
     <subtitle>По возможности создаем другой поток</subtitle>
     <p><code>40-52</code> Если имеется возможность создать другой поток (<code>nconn</code> меньше, чем <code>maxconn</code>), мы так и делаем. Функция, которую выполняет каждый новый поток, — это <code>do_get_read</code>, а ее аргументом является указатель на структуру <code>file</code>.</p>
     <subtitle>Ждем, когда завершится выполнение какого-либо потока</subtitle>
     <p><code>53-54</code> Мы вызываем функцию потоков <code>thr_join</code> Solaris с нулевым первым аргументом, чтобы дождаться завершения выполнения какого-либо из наших потоков. К сожалению, в Pthreads не предусмотрен способ, с помощью которого мы могли бы ждать завершения выполнения любого потока, и функция <code>pthread_join</code> требует, чтобы мы точно указали, завершения какого потока мы ждем. В разделе 26.9 мы увидим, что решение этой проблемы в случае применения технологии Pthreads оказывается сложнее и требует использования условной переменной для сообщения главному потоку о завершении выполнения дополнительного потока.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Показанное здесь решение, в котором используется функция потоков thr_join Solaris, не является, вообще говоря, совместимым со всеми системами. Тем не менее мы приводим здесь эту версию веб-клиента, использующую потоки, чтобы не осложнять обсуждение рассмотрением условных переменных и взаимных исключений (mutex). К счастью, в Solaris допустимо смешивать потоки Pthreads и потоки Solaris.</p>
     </cite>
     <p>В листинге 26.9 показана функция <code>do_get_read</code>, которая выполняется каждым потоком. Эта функция устанавливает соединение TCP, посылает серверу команду HTTP <code>GET</code> и считывает ответ сервера.</p>
     <p><strong>Листинг 26.9</strong>. Функция do_get_read</p>
     <p><code>//threads/web01.c</code></p>
     <p><code>61 void*</code></p>
     <p><code>62 do_get_read(void *vptr)</code></p>
     <p><code>63 {</code></p>
     <p><code>64  int fd, n;</code></p>
     <p><code>65  char line[MAXLINE];</code></p>
     <p><code>66  struct file *fptr;</code></p>
     <empty-line/>
     <p><code>67  fptr = (struct file*)vptr;</code></p>
     <empty-line/>
     <p><code>68  fd = Tcp_connect(fptr-&gt;f_host, SERV);</code></p>
     <p><code>69  fptr-&gt;f_fd = fd;</code></p>
     <p><code>70  printf("do_get_read for %s, fd %d, thread %d\n",</code></p>
     <p><code>71   fptr-&gt;f_name, fd, fptr-&gt;f_tid);</code></p>
     <p><code>72  write_get_cmd(fptr);</code></p>
     <empty-line/>
     <p><code>73  /* Чтение ответа сервера */</code></p>
     <p><code>74  for (;;) {</code></p>
     <p><code>75   if ((n = Read(fd, line, MAXLINE)) == 0)</code></p>
     <p><code>76    break; /* сервер закрывает соединение */</code></p>
     <p><code>77   printf ("read %d bytes from %s\n", n, fptr-&gt;f_name);</code></p>
     <p><code>78  }</code></p>
     <p><code>79  printf("end-of-file on %s\n\", fptr-&gt;f_name);</code></p>
     <p><code>80  Close(fd);</code></p>
     <p><code>81  fptr-&gt;f_flags = F_DONE; /* сбрасываем F_READING */</code></p>
     <empty-line/>
     <p><code>82  return (fptr); /* завершение потока */</code></p>
     <p><code>83 }</code></p>
     <subtitle>Создание сокета TCP, установление соединения</subtitle>
     <p><code>68-71</code> Создается сокет TCP, и с помощью функции <code>tcp_connect</code> устанавливается соединение. В данном случае используется обычный блокируемый сокет, поэтому поток будет блокирован при вызове функции <code>connect</code>, пока не будет установлено соединение.</p>
     <subtitle>Отправка запроса серверу</subtitle>
     <p><code>72</code> Функция <code>write_get_cmd</code> формирует команду HTTP <code>GET</code> и отсылает ее серверу. Мы не показываем эту функцию заново, так как единственным отличием от листинга 16.12 является то, что в версии, использующей потоки, не вызывается макрос <code>FD_SET</code> и не используется <code>maxfd</code>.</p>
     <subtitle>Чтение ответа сервера</subtitle>
     <p><code>73-82</code> Затем считывается ответ сервера. Когда соединение закрывается сервером, устанавливается флаг <code>F_DONE</code> и функция возвращает управление, завершая выполнение потока.</p>
     <p>Мы также не показываем функцию <code>home_page</code>, так как она полностью повторяет версию, приведенную в листинге 16.10.</p>
     <p>Мы вернемся к этому примеру, заменив функцию Solaris <code>thr_join</code> на более переносимую функцию семейства Pthreads, но сначала нам необходимо обсудить взаимные исключения и условные переменные.</p>
    </section>
    <section>
     <title>
      <p>26.7. Взаимные исключения</p>
     </title>
     <p>Обратите внимание на то, что в листинге 26.8 при завершении выполнения очередного потока в главном цикле уменьшаются на единицу и <code>nconn</code>, и <code>nlefttoread</code>. Мы могли бы поместить оба эти оператора уменьшения в одну функцию <code>do_get_read</code>, что позволило бы каждому потоку уменьшать эти счетчики непосредственно перед тем, как выполнение потока завершается. Но это привело бы к возникновению трудноуловимой серьезной ошибки параллельного программирования.</p>
     <p>Проблема, возникающая при помещении определенного кода в функцию, которая выполняется каждым потоком, заключается в том, что обе эти переменные являются глобальными, а не собственными переменными потока. Если один поток в данный момент уменьшает значение переменной и это действие приостанавливается, чтобы выполнился другой поток, который также станет уменьшать на единицу эту переменную, может произойти ошибка. Предположим, например, что компилятор С осуществляет уменьшение переменной на единицу в три этапа: загружает информацию из памяти в регистр, уменьшает значение регистра, а затем сохраняет значение регистра в памяти. Рассмотрим возможный сценарий.</p>
     <p>1. Выполняется поток А, который загружает в регистр значение переменной <code>nconn</code> (равное 3).</p>
     <p>2. Система переключается с выполнения потока А на выполнение потока В. Регистры потока А сохранены, регистры потока В восстановлены.</p>
     <p>3. Поток В выполняет три действия, составляющие оператор декремента в языке С (<code>nconn--</code>), сохраняя новое значение переменной <code>nconn</code>, равное 2.</p>
     <p>4. Впоследствии в некоторый момент времени система переключается на выполнение потока А. Восстанавливаются регистры потока А, и он продолжает выполняться с того места, на котором остановился, а именно начиная со второго этапа из трех, составляющих оператор декремента. Значение регистра уменьшается с 3 до 2, и значение 2 записывается в переменную <code>nconn</code>.</p>
     <p>Окончательный результат таков: значение <code>nconn</code> равно 2, в то время как оно должно быть равным 1. Это ошибка.</p>
     <p>Подобные ошибки параллельного программирования трудно обнаружить по многим причинам. Во-первых, они возникают нечасто. Тем не менее это ошибки, которые по закону Мэрфи вызывают сбои в работе программ. Во-вторых, ошибки такого типа возникают не систематически, так как зависят от недетерминированного совпадения нескольких событий. Наконец, в некоторых системах аппаратные команды могут быть атомарными. Это значит, что имеется аппаратная команда уменьшения значения целого числа на единицу (вместо трехступенчатой последовательности, которую мы предположили выше), а аппаратная команда не может быть прервана до окончания своего выполнения. Но это не гарантировано для всех систем, так что код может работать в одной системе и не работать в другой.</p>
     <p>Программирование с использованием потоков является <emphasis>параллельным</emphasis> (<emphasis>parallel</emphasis>), или <emphasis>одновременным</emphasis> (<emphasis>concurrent</emphasis>), программированием, так как несколько потоков могут выполняться параллельно (одновременно), получая доступ к одним и тем же переменным. Хотя ошибочный сценарий, рассмотренный нами далее, предполагает систему с одним центральным процессором, вероятность ошибки также присутствует, если потоки А и В выполняются в одно и то же время на разных процессорах в многопроцессорной системе. В обычном программировании под Unix мы не сталкиваемся с подобными ошибками, так как при использовании функции <code>fork</code> родительский и дочерний процессы не используют совместно ничего, кроме дескрипторов. Тем не менее мы столкнемся с ошибками этого типа при обсуждении совместного использовании памяти несколькими процессами.</p>
     <p>Эту проблему можно с легкостью продемонстрировать на примере потоков. В листинге 26.11 показана программа, которая создает два потока, после чего каждый поток увеличивает некоторую глобальную переменную 5000 раз.</p>
     <p>Мы повысили вероятность ошибки за счет того, что потребовали от программы получить текущее значение переменной <code>counter</code>, вывести это значение и записать его. Если мы запустим эту программу, то получим результат, представленный в листинге 26.10.</p>
     <p><strong>Листинг 26.10</strong>. Результат выполнения программы, приведенной в листинге 26.11</p>
     <p><code>4: 1</code></p>
     <p><code>4: 2</code></p>
     <p><code>4: 3</code></p>
     <p><code>4: 4</code></p>
     <p><code><emphasis> продолжение выполнения потока номер 4</emphasis></code></p>
     <p><code>4: 517</code></p>
     <p><code>4: 518</code></p>
     <p><code>5: 518 <emphasis>теперь выполняется поток номер 5</emphasis></code></p>
     <p><code>5: 519</code></p>
     <p><code>5: 520</code></p>
     <p><code><emphasis> продолжение выполнения потока номер 5</emphasis></code></p>
     <p><code>5: 926</code></p>
     <p><code>5: 927</code></p>
     <p><code>4: 519 <emphasis>теперь выполняется поток номер 4, записывая неверные значения</emphasis></code></p>
     <p><code>4: 520</code></p>
     <p><strong>Листинг 26.11</strong>. Два потока, которые неверно увеличивают значение глобальной переменной</p>
     <p><code>//threads/example01.c</code></p>
     <p><code> 1 #include "unpthread.h"</code></p>
     <empty-line/>
     <p><code> 2 #define NLOOP 5000</code></p>
     <empty-line/>
     <p><code> 3 int counter; /* потоки должны увеличивать значение этой переменной */</code></p>
     <empty-line/>
     <p><code> 4 void *doit(void*);</code></p>
     <empty-line/>
     <p><code> 5 int</code></p>
     <p><code> 6 main(int argc, char **argv)</code></p>
     <p><code> 7 {</code></p>
     <p><code> 8  pthread_t tidA, tidB;</code></p>
     <empty-line/>
     <p><code> 9  Pthread_create(&amp;tidA, NULL, &amp;doit, NULL);</code></p>
     <p><code>10  Pthread_create(&amp;tidB, NULL, &amp;doit, NULL);</code></p>
     <empty-line/>
     <p><code>11  /* ожидание завершения обоих потоков */</code></p>
     <p><code>12  Pthread_join(tidA, NULL);</code></p>
     <p><code>13  Pthread_join(tidB, NULL);</code></p>
     <empty-line/>
     <p><code>14  exit(0);</code></p>
     <p><code>15 }</code></p>
     <empty-line/>
     <p><code>16 void*</code></p>
     <p><code>17 doit(void *vptr)</code></p>
     <p><code>18 {</code></p>
     <p><code>19  int i, val;</code></p>
     <empty-line/>
     <p><code>20  /* Каждый поток получает, выводит и увеличивает на</code></p>
     <p><code>21   * единицу переменную counter NLOOP раз. Значение</code></p>
     <p><code>22   * переменной должно увеличиваться монотонно.</code></p>
     <p><code>23   */</code></p>
     <empty-line/>
     <p><code>24  for (i = 0; i &lt; NLOOP; i++) {</code></p>
     <p><code>25   val = counter;</code></p>
     <p><code>26   printf("%d: %d\n", pthread_self(), val + 1);</code></p>
     <p><code>27   counter = val + 1;</code></p>
     <p><code>28  }</code></p>
     <empty-line/>
     <p><code>29  return (NULL);</code></p>
     <p><code>30 }</code></p>
     <p>Обратите внимание на то, что в первый раз ошибка происходит при переключении системы с выполнения потока номер 4 на выполнение потока номер 5: каждый поток в итоге записывает значение 518. Это происходит множество раз на протяжении 10 000 строк вывода.</p>
     <p>Недетерминированная природа ошибок такого типа также будет очевидна, если мы запустим программу несколько раз: каждый раз результат выполнения программы будет отличаться от предыдущего. Также, если мы переадресуем вывод результатов в файл на диске, эта ошибка иногда не будет возникать, так как программа станет работать быстрее, что приведет к уменьшению вероятности переключения системы между потоками. Наибольшее количество ошибок возникнет в случае, если программа будет работать интерактивно, записывая результат на медленный терминал, но при этом также сохраняя результат в файл при помощи программы Unix <code>script</code> (которая описана в главе 19 книги [110]).</p>
     <p>Только что описанная проблема, возникающая, когда несколько потоков изменяют значение одной переменной, является самой простой из проблем параллельного программирования. Для решения этой проблемы используются так называемые <emphasis>взаимные исключения</emphasis> (<emphasis>mutex — mutual exclusion</emphasis>), с помощью которых контролируется доступ к переменной. В терминах Pthreads взаимное исключение — это переменная типа <code>pthread_mutex_t</code>, которая может быть заблокирована и разблокирована с помощью следующих двух функций:</p>
     <p><code>#include &lt;pthread.h&gt;</code></p>
     <empty-line/>
     <p><code>int pthread_mutex_lock(pthread_mutex_t *<emphasis>mptr</emphasis>);</code></p>
     <p><code>int pthread_mutex_unlock(pthread_mutex_t *<emphasis>mptr</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</emphasis></code></p>
     <p>Если некоторый поток попытается блокировать взаимное исключение, которое уже блокировано каким-либо другим потоком (то есть принадлежит ему в данный момент времени), этот поток окажется заблокированным до освобождения взаимного исключения.</p>
     <p>Если переменная-исключение размещена в памяти статически, следует инициализировать ее константой <code>PTHREAD_MUTEX_INITIALIZER</code>. В разделе 30.8 мы увидим, что если мы размещаем исключение в совместно используемой (разделяемой) памяти, мы должны инициализировать его во время выполнения программы путем вызова функции <code>pthread_mutex_init</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Некоторые системы (например, Solaris) определяют константу PTHREAD_MUTEX_INITIALIZER как 0. Если данная инициализация будет опущена, это ни на что не повлияет, так как статически размещаемые переменные все равно автоматически инициализируются нулем. Но для других систем такой гарантии дать нельзя — например, в Digital Unix константа инициализации ненулевая.</p>
     </cite>
     <p>В листинге 26.12 приведена исправленная версия листинга 26.11, в которой используется одно взаимное исключение для блокирования счетчика при работе с двумя потоками.</p>
     <p><strong>Листинг 26.12</strong>. Исправленная версия листинга 26.11, использующая взаимное исключение для защиты совместно используемой переменной</p>
     <p><code>//threads/examplе01.с</code></p>
     <p><code> 1 #include "unpthread.h"</code></p>
     <empty-line/>
     <p><code> 2 #define NLOOP 5000</code></p>
     <empty-line/>
     <p><code> 3 int counter; /* увеличивается потоками */</code></p>
     <p><code> 4 pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;</code></p>
     <empty-line/>
     <p><code> 5 void *doit(void*);</code></p>
     <empty-line/>
     <p><code> 6 int</code></p>
     <p><code> 7 main(int argc, char **argv)</code></p>
     <p><code> 8 {</code></p>
     <p><code> 9  pthread_t tidA, tidB;</code></p>
     <empty-line/>
     <p><code>10  Pthread_create(&amp;tidA, NULL, &amp;doit, NULL);</code></p>
     <p><code>11  Pthread_create(&amp;tidB, NULL, &amp;doit, NULL);</code></p>
     <empty-line/>
     <p><code>12  /* ожидание завершения обоих потоков */</code></p>
     <p><code>13  Pthread_join(tidA, NULL);</code></p>
     <p><code>14  Pthread_join(tidB, NULL);</code></p>
     <empty-line/>
     <p><code>15  exit(0);</code></p>
     <p><code>16 }</code></p>
     <empty-line/>
     <p><code>17 void*</code></p>
     <p><code>18 doit(void *vptr)</code></p>
     <p><code>19 {</code></p>
     <p><code>20  int i, val;</code></p>
     <empty-line/>
     <p><code>21  /*</code></p>
     <p><code>22   * Каждый поток считывает, выводит и увеличивает счетчик NLOOP раз.</code></p>
     <p><code>23   * Значение счетчика должно возрастать монотонно.</code></p>
     <p><code>24   */</code></p>
     <empty-line/>
     <p><code>25  for (i = 0; i &lt; NLOOP; i++) {</code></p>
     <p><code>26   Pthread_mutex_lock(&amp;counter_mutex);</code></p>
     <empty-line/>
     <p><code>27   val = counter;</code></p>
     <p><code>28   printf(%d: %d\n", pthread_self(), val + 1);</code></p>
     <p><code>29   counter = val + 1;</code></p>
     <empty-line/>
     <p><code>30   Pthread_mutex_unlock(&amp;counter_mutex);</code></p>
     <p><code>31  }</code></p>
     <empty-line/>
     <p><code>32  return(NULL);</code></p>
     <p><code>33 }</code></p>
     <p>Мы объявляем взаимное исключение с именем <code>counter_mutex</code>. Это исключение должно быть заблокировано потоком на то время, когда он манипулирует переменной counter. Когда мы запускали эту программу, результат всегда был правильным: значение переменной увеличивалось монотонно, а ее окончательное значение всегда оказывалось равным 10 000.</p>
     <p>Насколько серьезной является дополнительная нагрузка, связанная с использованием взаимных исключений? Мы изменили программы, приведенные в листингах 26.11 и 26.12, заменив значение <code>NLOOP</code> на 50 000 (вместо исходного значения 5000), и засекли время, направив вывод на устройство <code>/dev/null</code>. Время работы центрального процессора в случае корректной версии, использующей взаимное исключение, увеличилось относительно времени работы некорректной версии без взаимного исключения на 10 %. Это означает, что использование взаимного исключения не связано со значительными издержками.</p>
    </section>
    <section>
     <title>
      <p>26.8. Условные переменные</p>
     </title>
     <p>Взаимное исключение позволяет предотвратить одновременный доступ к совместно используемой (разделяемой) переменной, но для того чтобы перевести поток в состояние ожидания (спящее состояние) до момента выполнения некоторого условия, необходим другой механизм. Продемонстрируем сказанное на следующем примере. Вернемся к нашему веб-клиенту из раздела 26.6 и заменим функцию Solaris <code>thr_join</code> на <code>pthread_join</code>. Но мы не можем вызвать функцию <code>pthread_join</code> до тех пор, пока не будем знать, что выполнение потока завершилось. Сначала мы объявляем глобальную переменную, которая служит счетчиком количества завершившихся потоков, и организуем управление доступом к ней с помощью взаимного исключения.</p>
     <p><code>int ndone; /* количество потоков, завершивших выполнение */</code></p>
     <p><code>pthread_mutex_t ndone_mutex = PTHREAD_MUTEX_INITIALIZER;</code></p>
     <p>Затем мы требуем, чтобы каждый поток по завершении своего выполнения увеличивал этот счетчик на единицу, используя соответствующее взаимное исключение.</p>
     <p><code>void* do_get_read(void *vptr) {</code></p>
     <p><code> ...</code></p>
     <empty-line/>
     <p><code> Pthread_mutex_lock(&amp;ndone_mutex);</code></p>
     <p><code> ndone++;</code></p>
     <p><code> Pthread_mutex_unlock(&amp;ndone_mutex);</code></p>
     <empty-line/>
     <p><code> return(fptr); /* завершение выполнения потока */</code></p>
     <p><code>}</code></p>
     <p>Но каким при этом получается основной цикл? Взаимное исключение должно быть постоянно блокировано основным циклом, который проверяет, какие потоки завершили свое выполнение.</p>
     <p><code>while (nlefttoread &gt; 0) {</code></p>
     <p><code> while (nconn &lt; maxnconn &amp;&amp; nlefttoconn &gt; 0) {</code></p>
     <p><code>  /* находим файл для чтения */</code></p>
     <p><code>  ...</code></p>
     <p><code> }</code></p>
     <p><code> /* Проверяем, не завершен ли поток */</code></p>
     <p><code> Pthread_mutex_lock(&amp;ndone_mutex);</code></p>
     <p><code> if (ndone &gt; 0) {</code></p>
     <p><code>  for (i =0; i &lt; nfiles; i++) {</code></p>
     <p><code>   if (file[i].f_flags &amp; F_DONE) {</code></p>
     <p><code>    Pthread_join(file[i].f_tid, (void**)&amp;fptr);</code></p>
     <p><code>    /* обновляем file[i] для завершенного потока */</code></p>
     <p><code>    ...</code></p>
     <p><code>   }</code></p>
     <p><code>  }</code></p>
     <p><code> }</code></p>
     <p><code> Pthread_mutex_unlock(&amp;ndone_mutex);</code></p>
     <p><code>}</code></p>
     <p>Это означает, что главный поток <emphasis>никогда</emphasis> не переходит в спящее состояние, а просто входит в цикл, проверяя каждый раз значение переменной <code>ndone</code>. Этот процесс называется <emphasis>опросом</emphasis> (<emphasis>polling</emphasis>) и рассматривается как пустая трата времени центрального процессора.</p>
     <p>Нам нужен метод, с помощью которого главный цикл мог бы входить в состояние ожидания, пока один из потоков не оповестит его о том, что какая-либо задача выполнена. Эта возможность обеспечивается использованием <emphasis>условной переменной</emphasis> (<emphasis>conditional variable</emphasis>) вместе со взаимным исключением. Взаимное исключение используется для реализации блокирования, а условная переменная обеспечивает сигнальный механизм.</p>
     <p>В терминах Pthreads условная переменная — это переменная типа <code>pthread_cond_t</code>. Такие переменные используются в следующих двух функциях:</p>
     <p><code>#include &lt;pthread.h&gt;</code></p>
     <empty-line/>
     <p><code>int pthread_cond_wait(pthread_cond_t *<emphasis>cptr</emphasis>, pthread_mutex_t *<emphasis>mptr</emphasis>);</code></p>
     <p><code>int pthread_cond_signal(pthread_cond_t *<emphasis>cptr</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</emphasis></code></p>
     <p>Слово <code>signal</code> в названии второй функции не имеет отношения к сигналам Unix <code>SIGxxx</code>.</p>
     <p>Проще всего объяснить действие этих функций на примере. Вернемся к нашему примеру веб-клиента. Счетчик <code>ndone</code> теперь ассоциируется и с условной переменной, и с взаимным исключением:</p>
     <p><code>int ndone;</code></p>
     <p><code>pthread_mutex_t ndone_mutex = PTHREAD_MUTEX_INITIALIZER;</code></p>
     <p><code>pthread_cond_t ndone_cond = PTHREAD_COND_INITIALIZER;</code></p>
     <p>Поток оповещает главный цикл о своем завершении, увеличивая значение счетчика, пока взаимное исключение принадлежит данному потоку (блокировано им), и используя условную переменную для сигнализации.</p>
     <p><code>Pthread_mutex_lock(&amp;ndone_mutex);</code></p>
     <p><code>ndone++;</code></p>
     <p><code>Pthread_cond_signal(&amp;ndone_cond);</code></p>
     <p><code>Pthread_mutex_unlock(&amp;ndone_mutex);</code></p>
     <p>Затем основной цикл блокируется в вызове функции <code>pthread_cond_wait</code>, ожидая оповещения о завершении выполнения потока:</p>
     <p><code>while (nlefttoread &gt; 0) {</code></p>
     <p><code> while (nconn &lt; maxnconn &amp;&amp; nlefttoconn &gt; 0) {</code></p>
     <p><code>  /* находим файл для чтения */</code></p>
     <p><code>  ...</code></p>
     <p><code> }</code></p>
     <empty-line/>
     <p><code> /* Ждем завершения выполнения какого-либо потока */</code></p>
     <p><code> Pthread_mutex_lock(&amp;ndone_mutex);</code></p>
     <p><code> while (ndone == 0)</code></p>
     <p><code>  Pthread_cond_wait(&amp;ndone_cond, &amp;ndone_mutex);</code></p>
     <empty-line/>
     <p><code> for (i = 0; i &lt; nfiles; i++) {</code></p>
     <p><code>  if (file[i].f_flags &amp; F_DONE) {</code></p>
     <p><code>   Pthread_join(file[i].f_tid, (void**)&amp;fptr);</code></p>
     <empty-line/>
     <p><code>   /* обновляем file[i] для завершенного потока */</code></p>
     <p><code>   ...</code></p>
     <p><code>  }</code></p>
     <p><code> }</code></p>
     <p><code> Pthread_mutex_unlock(&amp;ndone_mutex);</code></p>
     <p><code>}</code></p>
     <p>Обратите внимание на то, что переменная <code>ndone</code> по-прежнему проверяется, только если потоку принадлежит взаимное исключение. Тогда, если не требуется выполнять какое-либо действие, вызывается функция <code>pthread_cond_wait</code>. Таким образом, вызывающий поток переходит в состояние ожидания, <emphasis>и</emphasis> разблокируется взаимное исключение, которое принадлежало этому потоку. Кроме того, когда управление возвращается потоку функцией <code>pthread_cond_wait</code> (после того как поступил сигнал от какого-либо другого потока), он снова блокирует взаимное исключение.</p>
     <p>Почему взаимное исключение всегда связано с условной переменной? «Условие» обычно представляет собой значение некоторой переменной, используемой совместно несколькими потоками. Взаимное исключение требуется для того, чтобы различные потоки могли задавать и проверять значение условной переменной. Например, если в примере кода, приведенном ранее, отсутствовало бы взаимное исключение, то проверка в главном цикле выглядела бы следующим образом:</p>
     <p><code>/* Ждем завершения выполнения одного или нескольких потоков */</code></p>
     <p><code>while (ndone == 0)</code></p>
     <p><code> Pthread_cond_wait(&amp;ndone_cond, &amp;ndone_mutex);</code></p>
     <p>Но при этом существует вероятность, что последний поток увеличивает значение переменной <code>ndone</code> после проверки главным потоком условия <code>ndone == 0</code>, но перед вызовом функции <code>pthread_cond_wait</code>. Если это происходит, то последний «сигнал» теряется, и основной цикл оказывается заблокированным навсегда, так как он будет ждать события, которое никогда не произойдет.</p>
     <p>По этой же причине при вызове функции <code>pthread_cond_wait</code> поток должен блокировать соответствующее взаимное исключение, после чего эта функция разблокирует взаимное исключение и помещает вызывающий поток в состояние ожидания, выполняя эти действия как одну атомарную операцию. Если бы эта функция не разблокировала взаимное исключение и не заблокировала его снова после своего завершения, то выполнять эти операции пришлось бы потоку, как показано в следующем фрагменте кода:</p>
     <p><code>/* Ждем завершения выполнения одного или нескольких потоков */</code></p>
     <p><code>Pthread_mutex_lock(&amp;ndone_mutex);</code></p>
     <p><code>while (ndone == 0) {</code></p>
     <p><code> Pthread_mutex_unlock(&amp;ndone_mutex);</code></p>
     <p><code> Pthread_cond_wait(&amp;ndone_cond, &amp;ndone_mutex);</code></p>
     <p><code> Pthread_mutex_lock(&amp;ndone_mutex);</code></p>
     <p><code>}</code></p>
     <p>Существует вероятность того, что по завершении выполнения поток увеличит на единицу значение переменной <code>ndone</code> и это произойдет между вызовом функций <code>pthread_mutex_unlock</code> и <code>pthread_cond_wait</code>.</p>
     <p>Обычно функция <code>pthread_cond_signal</code> выводит из состояния ожидания один поток, на который указывает условная переменная. Существуют ситуации, когда некоторый поток знает, что из состояния ожидания должны быть выведены несколько потоков. В таком случае используется функция <code>pthread_cond_broadcast</code>, выводящая из состояния ожидания все потоки, которые блокированы условной переменной.</p>
     <p><code>#include &lt;pthread.h&gt;</code></p>
     <empty-line/>
     <p><code>int pthread_cond_broadcast(pthread_cond_t *<emphasis>cptr</emphasis>);</code></p>
     <p><code>int pthread_cond_timedwait(pthread_cond_t *<emphasis>cptr</emphasis>, pthread_mutex_t *<emphasis>mptr</emphasis>,</code></p>
     <p><code> const struct timespec *<emphasis>abstime</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</emphasis></code></p>
     <p>Функция <code>pthread_cond_timedwait</code> позволяет потоку задать предельное время блокирования. Аргумент <code>abstime</code> представляет собой структуру <code>timespec</code> (определенную в разделе 6.9 при рассмотрении функции <code>pselect</code>), которая задает системное время для момента, когда функция должна возвратить управление, даже если к этому моменту условная переменная не подала сигнал. Если возникает такая ситуация, возвращается ошибка <code>ETIME</code>.</p>
     <p>В данном случае значение времени является <emphasis>абсолютным значением времени</emphasis>, в отличие от относительного значения <emphasis>разницы во времени</emphasis> (<emphasis>time delta</emphasis>) между некоторыми событиями. Иными словами, <code>abstime</code> — это системное время, то есть количество секунд и наносекунд, прошедших с 1 января 1970 года (UTC) до того момента, когда эта функция должна вернуть управление. Здесь имеется различие как с функцией <code>pselect</code>, так и с функцией <code>select</code>, задающими количество секунд (и наносекунд в случае <code>pselect</code>) до некоторого момента в будущем, когда функция должна вернуть управление. Обычно для этого вызывается функция <code>gettimeofday</code>, которая выдает текущее время (в виде структуры <code>timeval</code>), а затем оно копируется в структуру <code>timespec</code> и к нему добавляется требуемое значение:</p>
     <p><code>struct timeval tv;</code></p>
     <p><code>struct timespec ts;</code></p>
     <empty-line/>
     <p><code>if (gettimeofday(&amp;tv, NULL) &lt; 0)</code></p>
     <p><code> err_sys("gettimeofday error");</code></p>
     <p><code>ts.tv_sec = tv.tv_sec + 5; /* 5 с в будущем */</code></p>
     <p><code>ts.tv_nsec = tv.tv_usec * 1000; /* микросекунды переводим в наносекунды */</code></p>
     <empty-line/>
     <p><code>pthread_cond_timedwait( , &amp;ts);</code></p>
     <p>Преимущество использования абсолютного времени (в противоположность относительному) заключается в том, что функция может завершиться раньше (возможно, из-за перехваченного сигнала). Тогда функцию можно вызвать снова, не меняя содержимое структуры <code>timespec</code>. Недостаток этого способа заключается в необходимости вызывать дополнительно функцию <code>gettimeofday</code> перед тем, как в первый раз вызывать функцию <code>pthread_cond_timedwait</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В POSIX определена новая функция clock_gettime, возвращающая текущее время в виде структуры timespec.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>26.9. Веб-клиент и одновременный доступ</p>
     </title>
     <p>Изменим код нашего веб-клиента из раздела 26.6: уберем вызов функции Solaris <code>thr_join</code> и заменим его вызовом функции <code>pthread_join</code>. Как сказано в разделе 26.6, теперь нам нужно точно указать, завершения какого потока мы ждем. Для этого мы используем условную переменную, описанную в разделе 26.8.</p>
     <p>Единственным изменением в отношении глобальных переменных (см. листинг 26.7) является добавление нового флага и условной переменной:</p>
     <p><code>#define F_JOINED 8 /* количество потоков */</code></p>
     <empty-line/>
     <p><code>int ndone; /* количество завершившихся потоков */</code></p>
     <p><code>pthread_mutex_t ndone_mutex = PTHREAD_MUTEX_INITIALIZER;</code></p>
     <p><code>pthread_cond_t ndone_cond = PTHREAD_COND_IINITIALIZER;</code></p>
     <p>Единственным изменением функции <code>do_get_read</code> (см. листинг 26.9) будет увеличение на единицу значения переменной <code>ndone</code> и оповещение главного цикла о завершении выполнения потока:</p>
     <p><code> printf("end-of-file on %s\n", fptr-&gt;f_name);</code></p>
     <p><code> Close(fd);</code></p>
     <empty-line/>
     <p><code> Pthread_mutex_lock(&amp;ndone_mutex);</code></p>
     <p><code> fptr-&gt;f_flags = F_DONE; /* сбрасывает флаг F_READING */</code></p>
     <p><code> ndone++;</code></p>
     <p><code> Pthread_cond_signal(&amp;ndone_cond);</code></p>
     <p><code> Pthread_mutex_unlock(&amp;ndone_mutex);</code></p>
     <empty-line/>
     <p><code> return(fptr); /* завершение выполнения потока */</code></p>
     <p><code>}</code></p>
     <p>Большинство изменений касается главного цикла, представленного в листинге 26.8. Новая версия показана в листинге 26.13.</p>
     <p><strong>Листинг 26.13</strong>. Основной рабочий цикл функции main</p>
     <p><code>//threads/web03.c</code></p>
     <p><code>43  while (nlefttoread &gt; 0) {</code></p>
     <p><code>44   while (nconn &lt; maxnconn &amp;&amp; nlefttoconn &gt; 0) {</code></p>
     <p><code>45    /* находим файл для считывания */</code></p>
     <p><code>46    for (i = 0; i &lt; nfiles; i++)</code></p>
     <p><code>47     if (file[i].f_flags == 0)</code></p>
     <p><code>48      break;</code></p>
     <p><code>49    if (i == nfiles)</code></p>
     <p><code>50     err_quit("nlefttoconn = %d but nothing found", nlefttoconn);</code></p>
     <empty-line/>
     <p><code>51    file[i].f_flags = F_CONNECTING;</code></p>
     <p><code>52    Pthread_create(&amp;tid, NULL, &amp;do_get_read, &amp;file[i]);</code></p>
     <p><code>53    file[i].f_tid = tid;</code></p>
     <p><code>54    nconn++;</code></p>
     <p><code>55    nlefttoconn--;</code></p>
     <p><code>56   }</code></p>
     <empty-line/>
     <p><code>57   /* Ждем завершения выполнения одного из потоков */</code></p>
     <p><code>58   Pthread_mutex_lock(&amp;ndone_mutex);</code></p>
     <p><code>59   while (ndone == 0)</code></p>
     <p><code>60    Pthread_cond_wait(&amp;ndone_cond, &amp;ndone_mutex);</code></p>
     <empty-line/>
     <p><code>61   for (i = 0; i &lt; nfiles; i++) {</code></p>
     <p><code>62    if (file[i].f_flags &amp; F_DONE) {</code></p>
     <p><code>63     Pthread_join(file[i].f_tid, (void**)&amp;fptr);</code></p>
     <empty-line/>
     <p><code>64     if (&amp;file[i] != fptr)</code></p>
     <p><code>65      err_quit("file[i] != fptr");</code></p>
     <p><code>66     fptr-&gt;f_flags = F_JOINED; /* clears F_DONE */</code></p>
     <p><code>67     ndone--;</code></p>
     <p><code>68     nconn--;</code></p>
     <p><code>69     nlefttoread--;</code></p>
     <p><code>70     printf("thread %d for %s done\n", fptr-&gt;f_tid, fptr-&gt;f_name);</code></p>
     <p><code>71    }</code></p>
     <p><code>72   }</code></p>
     <p><code>73   Pthread_mutex_unlock(&amp;ndone_mutex);</code></p>
     <p><code>74  }</code></p>
     <empty-line/>
     <p><code>75  exit(0);</code></p>
     <p><code>76 }</code></p>
     <subtitle>По возможности создаем новый поток</subtitle>
     <p><code>44-56</code> Эта часть кода не изменилась.</p>
     <subtitle>Ждем завершения выполнения потока</subtitle>
     <p><code>57-60</code> Мы ждем завершения выполнения потоков, отслеживая, когда значение <code>ndone</code> станет равно нулю. Как сказано в разделе 26.8, эта проверка должна быть проведена перед тем, как взаимное исключение будет блокировано, а переход потока в состояние ожидания осуществляется функцией <code>pthread_cond_wait</code>.</p>
     <subtitle>Обработка завершенного потока</subtitle>
     <p><code>61-73</code> Когда выполнение потока завершилось, мы перебираем все структуры <code>file</code>, отыскивая соответствующий поток, вызываем <code>pthread_join</code>, а затем устанавливаем новый флаг <code>F_JOINED</code>.</p>
     <p>В табл. 16.1 показано, сколько времени требует выполнение этой версии веб-клиента, а также версии, использующей неблокируемую функцию <code>connect</code>.</p>
    </section>
    <section>
     <title>
      <p>26.10. Резюме</p>
     </title>
     <p>Создание нового потока обычно требует меньше времени, чем порождение нового процесса с помощью функции <code>fork</code>. Одно это уже является большим преимуществом использования потоков на активно работающих сетевых серверах. Многопоточное программирование, однако, представляет собой отдельную технологию, требующую большей аккуратности при использовании.</p>
     <p>Все потоки одного процесса совместно используют глобальные переменные и дескрипторы, тем самым эта информация становится доступной всем потокам процесса. Но совместное использование информации вносит проблемы, связанные с синхронизацией доступа к разделяемым переменным, и поэтому нам следует использовать примитивы синхронизации технологии Pthreads — взаимные исключения и условные переменные. Синхронизация доступа к совместно используемым данным — необходимое условие почти для любого приложения, работающего с потоками.</p>
     <p>При разработке функций, которые могут быть вызваны таким приложением, нужно учитывать требование безопасности в многопоточной среде. Это требование выполнимо при использовании собственных данных потоков (thread-specific data), пример которых мы показали при рассмотрении функции <code>readline</code> в этой главе.</p>
     <p>К модели потоков мы вернемся в главе 30, где сервер при запуске создает пул потоков. Для обслуживания очередного клиентского запроса используется любой свободный поток.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Сравните использование дескриптора в случае, когда в коде сервера применяется функция <code>fork</code>, и в случае, когда используются потоки. Предполагается, что одновременно обслуживается 100 клиентов.</p>
     <p>2. Что произойдет в листинге 26.2, если поток при завершении функции <code>str_echo</code> не вызовет функцию <code>close</code> для закрытия сокета?</p>
     <p>3. В листингах 5.4 и 6.2 мы выводили сообщение <code>Server terminated prematurely</code> (Сервер завершил работу преждевременно), когда мы ждали от сервера прибытия отраженной строки, а вместо этого получали признак конца файла (см. раздел 5.12). Модифицируйте листинг 26.1 таким образом, чтобы в соответствующих случаях также выдавалось аналогичное сообщение.</p>
     <p>4. Модифицируйте листинги 26.5 и 26.6 таким образом, чтобы программы можно было компилировать в системах, не поддерживающих потоки.</p>
     <p>5. Чтобы увидеть ошибку в функции <code>readline</code>, приведенной в листинге 26.2, запустите эту программу на стороне сервера. Затем измените эхо-клиент TCP из листинга 6.2, корректно работающий в пакетном режиме. Возьмите какой- либо большой текстовый файл в своей системе и трижды запустите клиент в пакетном режиме, чтобы он считывал текст из этого файла и записывал результат во временный файл. Если есть возможность, запустите клиенты на другом узле (не на том, на котором запущен сервер). Если все три клиента выполнят работу правильно (часто они зависают), посмотрите на файлы с результатом и сравните их с исходным файлом.</p>
     <p>Теперь создайте версию сервера, используя корректную версию функции <code>readline</code> из раздела 26.5. Повторите тест, используя три эхо-клиента. Теперь все три клиента должны работать исправно. Также поместите функцию <code>printf</code> в функции <code>readline_destructor</code>, <code>readline_once</code> и в вызов функции <code>malloc</code> в <code>readline</code>. Это даст вам возможность увидеть, что ключ создается только один раз, но для каждого потока выделяется область памяти и вызывается функция-деструктор.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 27</p>
     <p>Параметры IP</p>
    </title>
    <section>
     <title>
      <p>27.1. Введение</p>
     </title>
     <p>В IPv4 допускается, чтобы после фиксированного 20-байтового заголовка шли до 40 байт, отведенных под различные параметры. Хотя всего определено десять параметров, чаще всего используется параметр маршрута от отправителя (source route option). Доступ к этим параметрам осуществляется через параметр сокета <code>IP_OPTIONS</code>, что мы покажем именно на примере использования маршрутизации от отправителя.</p>
     <p>В IPv6 допускается наличие расширяющих заголовков (extension headers) между фиксированным 40-байтовым заголовком IPv6 и заголовком транспортного уровня (например, ICMPv6, TCP или UDP). В настоящее время определены 6 различных расширяющих заголовков. В отличие от подхода, использованного в IPv4, доступ к расширяющим заголовкам IPv6 осуществляется через функциональный интерфейс, что не требует от пользователя понимания фактических деталей того, как именно эти заголовки расположены в пакете IPv6.</p>
    </section>
    <section>
     <title>
      <p>27.2. Параметры IPv4</p>
     </title>
     <p>На рис. А.1 мы показываем параметры, расположенные после 20-байтового заголовка IPv4. Как отмечено при рассмотрении этого рисунка, 4-разрядное поле длины ограничивает общий размер заголовка IPv4 до 15 32-разрядных слов (что составляет 60 байт), так что на параметры IPv4 остается 40 байт. Для IPv4 определено 10 различных параметров.</p>
     <p>1. NOP (no-operation — нет действий). Этот однобайтовый параметр используется для выравнивания очередного параметра по 4-байтовой границе.</p>
     <p>2. EOL (end-of-list — конец списка параметров). Этот однобайтовый параметр обозначает конец списка параметров. Поскольку суммарный размер параметров IP должен быть кратным 4 байтам, после последнего параметра добавляются байты EOL.</p>
     <p>3. LSRR (Loose Source and Record Route — гибкая маршрутизация от отправителя с записью) (см. раздел 8.5 [111]). Пример использования этого параметра мы вскоре продемонстрируем.</p>
     <p>4. SSRR (Strict Source and Record Route — жесткая маршрутизация от отправителя с записью) (см. раздел 8.5 [111]). Пример использования этого параметра мы также вскоре продемонстрируем.</p>
     <p>5. Отметка времени (timestamp) (см. раздел 7.4 [111]).</p>
     <p>6. Запись маршрута (record route) (см. раздел 7.3 [111]).</p>
     <p>7. Основной параметр обеспечения безопасности (устаревший параметр) (basic security).</p>
     <p>8. Расширенный параметр обеспечения безопасности (устаревший параметр) (extended security).</p>
     <p>9. Идентификатор потока (устаревший параметр) (stream identifier).</p>
     <p>10. Извещение маршрутизатора (router alert). Этот параметр описан в RFC 2113 [60]. Он включается в дейтаграмму IP, для того чтобы все пересылающие эту дейтаграмму маршрутизаторы обрабатывали ее содержимое.</p>
     <p>В главе 9 книги [128] приводится более подробное рассмотрение первых шести параметров, а в указанных далее разделах [111] имеются примеры их использования.</p>
     <p>Функции <code>getsockopt</code> и <code>setsockopt</code> (с аргументом level, равным <code>IPPROTO_IP</code>, а аргументом <code>optname</code> — <code>IP_OPTIONS</code>) предназначены соответственно для получения и установки параметров IP. Четвертый аргумент функций <code>getsockopt</code> и <code>setsockopt</code> — это указатель на буфер (размер которого не превосходит 44 байт), а пятый аргумент — это размер буфера. Причина, по которой размер буфера может на 4 байт превосходить максимальный суммарный размер параметров, заключается в способе обработки параметров маршрута от отправителя, как мы вскоре увидим. Все остальные параметры помещаются в буфер именно в том виде, в котором они потом упаковываются в заголовок дейтаграммы.</p>
     <p>Когда параметры IP задаются с использованием функции <code>setsockopt</code>, указанные параметры включаются во все дейтаграммы, отсылаемые с данного сокета. Этот принцип работает для сокетов TCP, UDP и для символьных сокетов. Для отмены какого-либо параметра следует вызвать функцию <code>setsockopt</code> и задать либо пустой указатель в качестве четвертого аргумента, либо нулевое значение в качестве пятого аргумента (длина).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Установка параметров IP для символьного сокета IP работает не во всех реализациях, если уже установлен параметр IP_HDRINCL (который мы обсудим в последующих главах). Многие Беркли-реализации не отсылают параметры, установленные с помощью IP_OPTIONS, если включен параметр IP_HDRINCL, так как приложение может устанавливать собственные параметры в формируемом им заголовке IP [128, с. 1056-1057]. В других системах (например, в FreeBSD) приложение может задавать свои параметры IP, либо используя параметр сокета IP_OPTIONS, либо установив параметр IP_HDRINCL и включив требуемые параметры в создаваемый им заголовок IP, но одновременное применение обоих этих способов не допускается.</p>
     </cite>
     <p>При вызове функции <code>getsockopt</code> для получения параметров IP присоединенного сокета TCP, созданного функцией <code>accept</code>, возвращается лишь обращенный параметр маршрута от отправителя, полученный вместе с клиентским сегментом SYN на прослушиваемом сокете [128, с. 931]. TCP автоматически обращает маршрут от отправителя, поскольку маршрут, указанный клиентом, — это маршрут от клиента к серверу, а сервер должен использовать для отсылаемых им дейтаграмм обратный маршрут. Если вместе с сегментом SYN не был получен маршрут от отправителя, то значение пятого аргумента (этот аргумент типа «значение-результат», как было указано ранее, задает длину буфера) при завершении функции <code>getsockopt</code> будет равно нулю. Для прочих сокетов TCP, всех сокетов UDP и всех символьных сокетов IP при вызове функции <code>getsockopt</code> вы просто получите копию тех параметров IP, которые были установлены для этих сокетов с помощью функции <code>setsockopt</code>. Заметим, что для символьных сокетов IP полученный заголовок IP, включая все параметры IP, всегда возвращается всеми входными функциями, поэтому полученные параметры IP всегда доступны.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В Беркли-ядрах полученный маршрут от отправителя, так же как и другие параметры IP, никогда не возвращается для сокетов UDP. Показанный на с. 775 [128] код, предназначенный для получения параметров IP, существовал со времен 4.3BSD Reno, но так как он не работал, его всегда приходилось превращать в комментарий. Таким образом, для сокетов UDP невозможно использовать обращенный маршрут от отправителя полученной дейтаграммы, чтобы отослать ответ.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>27.3. Параметры маршрута от отправителя IPv4</p>
     </title>
     <section>
      <p><emphasis>Маршрут от отправителя</emphasis> (<emphasis>source route</emphasis>) — это список IP-адресов, указанных отправителем дейтаграммы IP. Если маршрут является <emphasis>жестким</emphasis> (<emphasis>строгим</emphasis>, strict), то дейтаграмма должна передаваться только между указанными узлами и пройти их все. Иными словами, все узлы, перечисленные в маршруте от отправителя, должны быть соседними друг для друга. Но если маршрут является <emphasis>свободным</emphasis>, или <emphasis>гибким</emphasis> (<emphasis>loose</emphasis>), дейтаграмма должна пройти все перечисленные в нем узлы, но может при этом пройти и еще какие-то узлы, не входящие в список.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Маршрутизация от отправителя (source routing) в IPv4 является предметом споров и сомнений. В [20] пропагандируется отказ от поддержки этой функции на всех маршрутизаторах, и многие организации и провайдеры действительно следуют этому принципу. Один из наиболее разумных способов использования маршрутизации от отправителя — это обнаружение с помощью программы traceroute асимметричных маршрутов, как показано на с. 108–109 [111], но в настоящее время даже этот способ становится непопулярен. Тем не менее определение и получение маршрута от отправителя — это часть API сокетов, и поэтому заслуживает описания.</p>
      </cite>
      <p>Параметры IPv4, связанные с маршрутизацией от отправителя, называются <emphasis>параметрами маршрутизации от отправителя с записью</emphasis> (Loose Source and Record Routes — LSRR в случае свободной маршрутизации и Strict Source and Record Routes — SSRR в случае жесткой маршрутизации), так как при проходе дейтаграммы через каждый из перечисленных в списке узлов происходит замена указанного адреса на адрес интерфейса для исходящих дейтаграмм. Это позволяет получателю дейтаграммы обратить полученный список, превратив его в маршрут, по которому будет послан ответ отправителю. Примеры этих двух маршрутов от отправителя вместе с соответствующим выводом программы <code>tcpdump</code>, приведены в разделе 8.5 книги [111].</p>
      <p>Маршрут от отправителя мы определяем как массив адресов IPv4, которому предшествуют три однобайтовых поля, как показано на рис. 27.1. Это формат буфера, который передается функции <code>setsockopt</code>.</p>
      <image l:href="#img_138.png"/>
      <p><strong>Рис. 27.1</strong>. Передача маршрута от отправителя ядру</p>
      <p>Перед параметром маршрута от отправителя мы поместили параметр NOP (нет действий), чтобы все IP-адреса были выровнены по 4-байтовой границе. Это не обязательно, но желательно, поскольку в результате мы выравниваем адреса, не расходуя дополнительно лишней памяти (все IP-параметры обычно выравниваются, чтобы в итоге занимать место, кратное 4 байтам).</p>
      <p>На рис. 27.1 показано, что маршрут состоит из 10 адресов, но первый приведенный адрес удаляется из параметра маршрута от отправителя и становится адресом получателя, когда дейтаграмма IP покидает узел отправителя. Хотя в 40-байтовом пространстве, отведенном под данный параметр IP, хватает места только для 9 адресов (не забудьте о 3-байтовом заголовке параметра, который мы вскоре опишем), фактически в заголовке IPv4 у нас имеется 10 IP-адресов, так как к 9 адресам узлов добавляется адрес получателя.</p>
      <p>Поле <code>code</code> — это либо <code>0x83</code> для параметра LSRR, либо <code>0x89</code> для параметра SSRR. Задаваемое нами значение поля <code>len</code> — это размер параметра в байтах, включая 3-байтовый заголовок и дополнительный адрес получателя, приведенный в конце списка. Для маршрута, состоящего из одного IP-адреса, это значение будет равно 11, для двух адресов — 15, и т.д. вплоть до максимального значения 43. Параметр NOP не является частью обсуждаемого параметра, и его длина не включается в значение поля len, но она входит в размер буфера, который мы сообщаем функции <code>setsockopt</code>. Когда первый адрес в списке удаляется из параметра маршрута от отправителя и добавляется в поле адреса получателя в заголовок IP, значение поля <code>len</code> уменьшается на 4 (см. рис. 9.32 и 9.33 [128]). Поле <code>ptr</code> — это указатель, или сдвиг, задающий положение следующего IP-адреса из списка, который должен быть обработан. Мы инициализируем это поле значением 4, что соответствует первому адресу IP. Значение этого поля увеличивается на 4 каждый раз, когда дейтаграмма обрабатывается одним из перечисленных в маршруте узлов.</p>
      <p>Теперь мы переходим к определению трех функций, с помощью которых мы инициализируем, создаем и обрабатываем параметр маршрута от отправителя. Наши функции предназначены для работы только с этим параметром. Хотя в принципе возможно объединить параметр маршрута от отправителя с другими параметрами IP (такими как параметр извещения маршрутизатора), но на практике параметры редко комбинируются. В листинге 27.1<a l:href="#n1" type="note">[1]</a> приведена функция <code>inet_srcrt_init</code>, а также некоторые статические переменные, используемые при составлении параметра.</p>
      <p><strong>Листинг 27.1</strong>. Функция inet_srcrt_init: инициализация перед записью маршрута от отправителя</p>
      <p><code>//ipopts/sourceroute.с</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;netinet/in_systm.h&gt;</code></p>
      <p><code> 3 #include &lt;netinet/ip.h&gt;</code></p>
      <empty-line/>
      <p><code> 4 static u_char *optr;   /* указатель на формируемые параметры */</code></p>
      <p><code> 5 static u_char *lenptr; /* указатель на длину параметра SRR */</code></p>
      <p><code> 6 static int    ocnt;    /* количество адресов */</code></p>
      <empty-line/>
      <p><code> 7 u_char*</code></p>
      <p><code> 8 inet_srcrt_init(int type)</code></p>
      <p><code> 9 {</code></p>
      <p><code>10  optr = Malloc(44); /* NOP, код параметра. len, указатель + до 10</code></p>
      <p><code>                          адресов */</code></p>
      <p><code>11  bzero(optr, 44); /* гарантирует наличие EOL на конце */</code></p>
      <p><code>12  ocnt = 0;</code></p>
      <p><code>13  *optr++ = IPOPT_NOP; /* выравнивающие NOP */</code></p>
      <p><code>14  *optr++ = type ? IPOPT_SSRR : IPOPT_LSRR;</code></p>
      <p><code>15  lenptr = optr++; /* поле длины заполняется позже */</code></p>
      <p><code>16  *optr++ = 4; /* сдвиг на первый адрес */</code></p>
      <empty-line/>
      <p><code>17  return(optr - 4); /* указатель для setsockopt() */</code></p>
      <p><code>18 }</code></p>
      <subtitle>Инициализация</subtitle>
      <p><code>10-17</code> Мы выделяем в памяти буфер, максимальный размер которого — 44 байт, и обнуляем его содержимое. Значение параметра EOL равно нулю, так что тем самым параметр инициализируется байтами EOL. Затем мы подготавливаем заголовок для маршрутизации от источника. Как показано на рис. 27.1, сначала мы обеспечиваем выравнивание при помощи параметра NOP, после чего указываем тип маршрута (гибкий, жесткий), длину и значение указателя. Мы сохраняем указатель в поле <code>len</code>. Это значение мы будем записывать при поочередном добавлении адресов к списку. Указатель на параметр возвращается вызывающему процессу, а затем передается как четвертый аргумент функции <code>setsockopt</code>.</p>
      <p>Следующая функция, <code>inet_srcrt_add</code>, добавляет один IPv4-адрес к создаваемому маршруту от отправителя.</p>
      <p><strong>Листинг 27.2</strong>. Функция inet_srcrt_add: добавление одного IPv4-адреса к маршруту от отправителя</p>
      <p><code>//ipopts/sourceroute.с</code></p>
      <p><code>19 int</code></p>
      <p><code>20 inet_srcrt_add(char *hostptr)</code></p>
      <p><code>21 {</code></p>
      <p><code>22  int len;</code></p>
      <p><code>23  struct addrinfo *ai;</code></p>
      <p><code>24  struct sockaddr_in *sin;</code></p>
      <empty-line/>
      <p><code>25  if (ocnt &gt; 9)</code></p>
      <p><code>26   err_quit("too many source routes with: %s", hostptr);</code></p>
      <empty-line/>
      <p><code>27  ai = Host_serv(hostptr, NULL, AF_INET, 0);</code></p>
      <p><code>28  sin = (struct sockaddr_in*)ai-&gt;ai_addr;</code></p>
      <p><code>29  memcpy(optr, &amp;sin-&gt;sin_addr, sizeof(struct in_addr));</code></p>
      <p><code>30  freeaddrinfo(ai);</code></p>
      <empty-line/>
      <p><code>31  optr += sizeof(struct in_addr);</code></p>
      <p><code>32  ocnt++;</code></p>
      <p><code>33  len = 3 + (ocnt * sizeof(struct in_addr));</code></p>
      <p><code>34  *lenptr = len;</code></p>
      <p><code>35  return(len + 1); /* размер для setsockopt() */</code></p>
      <p><code>36 }</code></p>
      <subtitle>Аргумент</subtitle>
      <p><code>19-20</code> Аргумент функции указывает либо на имя узла, либо на адрес IP в точечно- десятичной записи.</p>
      <subtitle>Проверка переполнения</subtitle>
      <p><code>25-26</code> Мы проверяем количество переданных адресов и выполняем инициализацию, если обрабатывается первый адрес.</p>
      <subtitle>Получение двоичного IP-адреса и запись маршрута</subtitle>
      <p><code>29-37</code> Функция <code>host_serv</code> обрабатывает имя узла или его IP-адрес, а возвращаемый ей адрес в двоичной форме мы помещаем в список. Мы обновляем поле <code>len</code> и возвращаем полный размер буфера (с учетом параметров NOP), который вызывающий процесс затем передаст функции <code>setsockopt</code>.</p>
      <p>Когда полученный маршрут от отправителя возвращается приложению функцией <code>getsockopt</code>, формат этого параметра отличается от того, что было показано на рис. 27.1. Формат полученного параметра маршрута от отправителя показан на рис. 27.2.</p>
      <image l:href="#img_139.png"/>
      <p><strong>Рис. 27.2</strong>. Формат параметра маршрута от отправителя, возвращаемого функцией getsockopt</p>
      <p>В первую очередь, мы можем отметить, что порядок следования адресов изменен ядром на противоположный относительно полученного маршрута от отправителя. Имеется в виду следующее: если в полученном маршруте содержались адреса А, В, С и D в указанном порядке, то под противоположным порядком подразумевается следующий: D, С, В, А. Первые 4 байта содержат первый IP-адрес из списка, затем следует однобайтовый параметр NOP (для выравнивания), затем — 3-байтовый заголовок параметра маршрута от отправителя, и далее остальные IP-адреса. После 3-байтового заголовка может следовать до 9 IP-адресов, и максимальное значение поля <code>len</code> в возвращенном заголовке равно 39. Поскольку параметр NOP всегда присутствует, длина буфера, возвращаемая функцией <code>getsockopt</code>, всегда будет равна значению, кратному 4 байтам.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Формат, приведенный на рис. 27.2, определен в заголовочном файле &lt;netinet/ip_var.h&gt; в виде следующей структуры:</p>
       <p><code>#define MAX_IPOPTLEN 40</code></p>
       <empty-line/>
       <p><code>struct ipoption {</code></p>
       <p><code> struct in_addr ipopt_dst; /* адрес первого получателя */</code></p>
       <p><code> char   ipopt_list[MAX_IPOPTLEN]; /* соответствующие параметры */</code></p>
       <p><code>};</code></p>
       <p>В листинге 27.3 мы анализируем эти данные, не используя указанную структуру.</p>
      </cite>
      <p>Возвращаемый формат отличается от того, который был передан функции <code>setsockopt</code>. Если нам было бы нужно преобразовать формат, показанный на рис. 27.2, к формату, показанному на рис. 27.1, нам следовало бы поменять местами первые и вторые 4 байта и изменить значение поля <code>len</code>, добавив к имеющемуся значению 4. К счастью, нам не нужно этого делать, так как Беркли-реализации автоматически используют обращенный маршрут от получателя для сокета TCP. Иными словами, данные, возвращаемые функцией <code>getsockopt</code> (представленные на рис. 27.2), носят чисто информативный характер. Нам не нужно вызывать функцию <code>setsockopt</code>, чтобы указать ядру на необходимость использования данного маршрута для дейтаграмм IP, отсылаемых по соединению TCP, — ядро сделает это само. Подобный пример с нашим сервером TCP мы вскоре увидим.</p>
      <p>Следующей из рассматриваемых нами функций, связанных с параметром маршрутизации, полученный маршрут от отправителя передается в формате, показанном на рис. 27.2. Затем она выводит соответствующую информацию. Эту функцию <code>inet_srtcrt_print</code> мы показываем в листинге 27.3.</p>
      <p><strong>Листинг 27.3</strong>. Функция inet_srtcrt_print: вывод полученного маршрута от отправителя</p>
      <p><code>//ipopts/sourceroute.c</code></p>
      <p><code>37 void</code></p>
      <p><code>38 inet_srcrt_print(u_char *ptr, int len)</code></p>
      <p><code>39 {</code></p>
      <p><code>40  u_char c;</code></p>
      <p><code>41  char str[INET_ADDRSTRLEN];</code></p>
      <p><code>42  struct in_addr hop1;</code></p>
      <empty-line/>
      <p><code>43  memcpy(&amp;hop1, ptr, sizeof(struct in_addr));</code></p>
      <p><code>44  ptr += sizeof(struct in_addr);</code></p>
      <empty-line/>
      <p><code>45  while ((c = *ptr++) == IPOPT_NOP); /* пропуск параметров NOP */</code></p>
      <empty-line/>
      <p><code>46  if (с == IPOPT_LSRR)</code></p>
      <p><code>47   printf("received LSRR: ");</code></p>
      <p><code>48  else if (c == IPOPT_SSRR)</code></p>
      <p><code>49   printf("received SSRR: ");</code></p>
      <p><code>50  else {</code></p>
      <p><code>51   printf("received option type %d\n", c);</code></p>
      <p><code>52   return;</code></p>
      <p><code>53  }</code></p>
      <p><code>54  printf("%s ", Inet_ntop(AF_INET, &amp;hop1, str, sizeof(str)));</code></p>
      <empty-line/>
      <p><code>55  len = *ptr++ - sizeof(struct in_addr); /* вычитаем адрес получателя */</code></p>
      <p><code>56  ptr++; /* пропуск указателя */</code></p>
      <p><code>57  while (len &gt; 0) {</code></p>
      <p><code>58   printf("%s ", Inet_ntop(AF_INET, ptr, str, sizeof(str)));</code></p>
      <p><code>59   ptr += sizeof(struct in_addr);</code></p>
      <p><code>60   len -= sizeof(struct in_addr);</code></p>
      <p><code>61  }</code></p>
      <p><code>62  printf("\n");</code></p>
      <p><code>63 }</code></p>
      <subtitle>Сохраняем первый адрес IP, пропускаем все параметры NOP</subtitle>
      <p><code>43-45</code> Первый IP-адрес в буфере сохраняется, а все следующие за ним параметры NOP мы пропускаем.</p>
      <subtitle>Проверяем параметр маршрута от отправителя</subtitle>
      <p><code>46-62</code> Мы выводим информацию о маршруте и проверяем значение поля <code>code</code>, содержащегося в 3-байтовом заголовке, получаем значение поля <code>len</code> и пропускаем указатель <code>ptr</code>. Затем мы выводим все IP-адреса, следующие за 3-байтовым заголовком, кроме IP-адреса получателя.</p>
     </section>
     <section>
      <title>
       <p>Пример</p>
      </title>
      <p>Теперь мы модифицируем наш эхо-сервер TCP таким образом, чтобы выводить полученный маршрут от отправителя, а эхо-клиент TCP — так, чтобы маршрут от отправителя можно было задавать. В листинге 27.4 показан код эхо-клиента TCP.</p>
      <p><strong>Листинг 27.4</strong>. Эхо-клиент TCP, задающий маршрут от отправителя</p>
      <p><code>//ipopts/tcpcli01.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int c, sockfd, len = 0;</code></p>
      <p><code> 6  u_char *ptr = NULL;</code></p>
      <p><code> 7  struct addrinfo *ai;</code></p>
      <empty-line/>
      <p><code> 8  if (argc &lt; 2)</code></p>
      <p><code> 9   err_quit("usage: tcpcli01 [ -[gG] &lt;hostname&gt; ... ] &lt;hostname&gt;");</code></p>
      <empty-line/>
      <p><code>10  opterr = 0; /* отключаем запись сообщений getopt() в stderr */</code></p>
      <p><code>11  while ((с = getopt(argc, argv, "gG")) != -1) {</code></p>
      <p><code>12   switch (c) {</code></p>
      <p><code>13   case 'g': /* свободный маршрут от отправителя */</code></p>
      <p><code>14    if (ptr)</code></p>
      <p><code>15     err_quit("can't use both -g and -G");</code></p>
      <p><code>16    ptr = inet_srcrt_init(0); </code></p>
      <p><code>17    break;</code></p>
      <empty-line/>
      <p><code>18   case 'G': /* жесткий маршрут от отправителя */</code></p>
      <p><code>19    if (ptr)</code></p>
      <p><code>20     err_qint("can't use both -g and -G");</code></p>
      <p><code>21    ptr = inet_srcrt_init(1);</code></p>
      <p><code>22    break;</code></p>
      <empty-line/>
      <p><code>23   case '?':</code></p>
      <p><code>24    err_quit("unrecognized option: %c", c);</code></p>
      <p><code>25   }</code></p>
      <p><code>26  }</code></p>
      <empty-line/>
      <p><code>27  if (ptr)</code></p>
      <p><code>28   while (optind &lt; argc-1)</code></p>
      <p><code>29    len = inet_srcrt_add(argv[optind++]);</code></p>
      <p><code>30  else if (optind &lt; argc-1)</code></p>
      <p><code>31   err_quit("need -g or -G to specify route");</code></p>
      <empty-line/>
      <p><code>32  if (optind != argc-1)</code></p>
      <p><code>33   err_quit("missing &lt;hostname&gt;");</code></p>
      <empty-line/>
      <p><code>34  ai = Host_serv(argv[optind], SERV_PORT_STR, AF_INET, SOCK_STREAM);</code></p>
      <empty-line/>
      <p><code>35  sockfd = Socket(ai-&gt;ai_family, ai-&gt;ai_socktype, ai-&gt;ai_protocol);</code></p>
      <empty-line/>
      <p><code>36  if (ptr) {</code></p>
      <p><code>37   len = inet_srcrt_add(argv[optind]); /* получатель в конце */</code></p>
      <p><code>38   Setsockopt(sockfd, IPPROTO_IP, IP_OPTIONS, ptr, len);</code></p>
      <p><code>39   free(ptr);</code></p>
      <p><code>40  }</code></p>
      <empty-line/>
      <p><code>41  Connect(sockfd, ai-&gt;ai_addr, ai-&gt;ai_addrlen);</code></p>
      <empty-line/>
      <p><code>42  str_cli(stdin, sockfd); /* вызов рабочей функции */</code></p>
      <empty-line/>
      <p><code>43  exit(0);</code></p>
      <p><code>44 }</code></p>
      <subtitle>Обработка аргументов командной строки</subtitle>
      <p><code>12-26</code> Мы вызываем нашу функцию <code>inet_srcrt_init</code>, чтобы инициализировать маршрут от отправителя. Тип маршрутизации указывается при помощи параметра <code>-g</code> (свободная) или <code>-G</code> (жесткая).</p>
      <p><code>27-33</code> Если указатель <code>ptr</code> установлен, значит, был указан параметр маршрутизации от отправителя, и все указанные промежуточные узлы добавляются к маршруту, подготовленному на предыдущем этапе функцией <code>inet_srcrt_add</code>. Если же <code>ptr</code> не установлен, но в командной строке еще есть аргументы, значит, пользователь задал маршрут, но не указал его тип. В этом случае программа завершает работу с сообщением об ошибке.</p>
      <subtitle>Обработка адреса получателя и создание сокета</subtitle>
      <p><code>34-35</code> Последний аргумент командной строки — это имя узла или адрес сервера в точечно-десятичной записи, который обрабатывается нашей функцией <code>host_serv</code>. Мы не можем вызвать функцию <code>tcp_connect</code>, так как должны задать маршрут от отправителя между вызовом функций <code>socket</code> и <code>connect</code>. Последняя инициирует трехэтапное рукопожатие, а нам нужно, чтобы сегмент SYN отправителя и все последующие пакеты проходили по одному и тому же маршруту.</p>
      <p><code>36-42</code> Если маршрут от отправителя задан, следует добавить IP-адрес сервера в конец списка адресов (см. рис. 27.1). Функция <code>setsockopt</code> устанавливает маршрут от отправителя для данного сокета. Затем мы вызываем функцию connect, а потом — нашу функцию <code>str_cli</code> (см. листинг 5.4).</p>
      <p>Наш TCP-сервер имеет много общего с кодом, показанным в листинге 5.9, но содержит следующие изменения.</p>
      <p>Во-первых, мы выделяем место для параметров:</p>
      <p><code>int len;</code></p>
      <p><code>u_char *opts;</code></p>
      <empty-line/>
      <p><code>opts = Malloc(44);</code></p>
      <p>Во-вторых, мы получаем параметры IP после вызова функции <code>accept</code>, но перед вызовом функции <code>fork</code>:</p>
      <p><code>len = 44;</code></p>
      <p><code>Getsockopt(connfd, IPPROTO_IP, IP_OPTIONS, opts, &amp;len);</code></p>
      <p><code>if (len &gt; 0) {</code></p>
      <p><code> printf("received IP options, len = %d\n", len);</code></p>
      <p><code> inet_srcrt_print(opts, len);</code></p>
      <p><code>}</code></p>
      <p>Если сегмент SYN, полученный от клиента, не содержит никаких параметров IP, переменная <code>len</code> по завершении функции <code>getsockopt</code> будет иметь нулевое значение (эта переменная относится к типу «значение-результат»). Как уже упоминалось, нам не нужно предпринимать какие-либо шаги для того, чтобы на стороне сервера использовался обращенный маршрут от отправителя: это делается автоматически без нашего участия [128, с. 931]. Вызывая функцию <code>getsockopt</code>, мы просто получаем копию обращенного маршрута от отправителя. Если мы не хотим, чтобы TCP использовал этот маршрут, то после завершения функции accept следует вызвать функцию <code>setsockopt</code> и задать нулевую длину (последний аргумент), тем самым удалив все используемые в текущий момент параметры IP. Но маршрут от отправителя тем не менее уже был использован в процессе трехэтапного рукопожатия при пересылке второго сегмента. Если мы уберем параметры маршрутизации, IP составит и будет использовать для пересылки последующих пакетов какой-либо другой маршрут.</p>
      <p>Теперь мы покажем пример клиент-серверного взаимодействия при заданном маршруте от отправителя. Мы запускаем наш клиент на узле <code>freebsd</code> следующим образом:</p>
      <p><code>freebsd4 % <strong>tcpcli01 -g macosx freebsd4 macosx</strong></code></p>
      <p>Тем самым дейтаграммы IP отсылаются с узла <code>freebsd</code> на узел <code>macosx</code>, обратно на узел <code>freebsd4</code>, и наконец, на <code>macosx</code>, где запущен наш сервер. Две промежуточные системы <code>freebsd4</code> и <code>macosx</code> должны переправлять дейтаграммы и принимать дейтаграммы с маршрутизацией от отправителя, чтобы этот пример работал.</p>
      <p>Когда соединение устанавливается, на стороне сервера выдается следующий результат:</p>
      <p><code>macosx % <strong>tcpserv01</strong></code></p>
      <p><code>received IP options, len = 16</code></p>
      <p><code>received LSRR, 172.24.37.94 172.24.37.78 172.24.37.94</code></p>
      <p>Первый выведенный IP-адрес — это первый узел обратного маршрута (<code>freebsd4</code>, как показано на рис. 27.2), а следующие два адреса идут в том порядке, который используется сервером для отправки дейтаграмм назад клиенту. Если мы понаблюдаем за процессом взаимодействия клиента и сервера с помощью программы <code>tcpdump</code>, мы увидим, как используется параметр маршрутизации для каждой дейтаграммы в обоих направлениях.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>К сожалению, действие параметра сокета IP_OPTIONS никогда не было документировано, поэтому вы можете увидеть различные вариации поведения в системах, не происходящих от исходного кода Беркли. Например, в системе Solaris 2.5 первый адрес, возвращаемый функцией getsockopt (см. рис. 27.2) — это не первый адрес в обращенном маршруте, а адрес собеседника. Тем не менее обратный маршрут, используемый TCP, будет корректен. Кроме того, в Solaris 2.5 всем параметрам маршрутизации предшествует четыре параметра NOP, что ограничивает параметр маршрутизации восемью IP-адресами, а не девятью, которые реально могли бы поместиться.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Уничтожение полученного маршрута от отправителя</p>
      </title>
      <p>К сожалению, использование параметра маршрутизации образует брешь в системе обеспечения безопасности программ, выполняющих аутентификацию по IP-адресам (сейчас такая проверка считается недостаточной). Если хакер отправляет пакеты, используя один из доверенных адресов в качестве адреса отправителя, но указывая в качестве одного из промежуточных адресов маршрута от отправителя свой собственный адрес, возвращаемые по обратному маршруту пакеты будут попадать к хакеру, а «отправитель», чьим адресом хакер прикрывался, никогда не узнает об этом. Начиная с выпуска Net/1 (1989), серверы <code>rlogind</code> и <code>rshd</code> использовали код, аналогичный следующему:</p>
      <p><code>u_char buf[44];</code></p>
      <p><code>char lbuf[BUFSIZ];</code></p>
      <p><code>int optsize;</code></p>
      <empty-line/>
      <p><code>optsize = sizeof(buf);</code></p>
      <p><code>if (getsockopt(0, IPPROTO_IP, IP_OPTIONS,</code></p>
      <p><code> buf, &amp;optsize) == 0 &amp;&amp; optsize != 0) {</code></p>
      <p><code> /* форматируем параметры как шестнадцатеричные числа для записи в lbuf[] */</code></p>
      <p><code> syslog(LOG_NOTICE,</code></p>
      <p><code>  "Connection received using IP options (ignored):%s", lbuf);</code></p>
      <p><code> setsockopt(0, ipproto, IP_OPTIONS, NULL, 0);</code></p>
      <p><code>}</code></p>
      <p>Если устанавливается соединение с какими-либо параметрами IP (значение переменной <code>optsize</code>, возвращенное функцией <code>getsockopt</code>, не равно нулю), то с помощью функции <code>syslog</code> делается запись соответствующего сообщения и вызывается функция <code>setsockopt</code> для очистки всех параметров. Таким образом предотвращается отправка последующих сегментов TCP для данного соединения по обращенному маршруту от отправителя. Сейчас уже известно, что этой технологии недостаточно, так к моменту установления соединения трехэтапное рукопожатие TCP будет уже завершено и второй сегмент (сегмент SYN-ACK на рис. 2.5) будет уже отправлен по обращенному маршруту от отправителя к клиенту. Даже если этот сегмент не успеет дойти до клиента, то во всяком случае он дойдет до некоторого промежуточного узла, входящего в маршрут от отправителя, где, возможно, затаился хакер. Так как предполагаемый хакер видел порядковые номера TCP в обоих направлениях, даже если никаких других пакетов по маршруту от отправителя послано не будет, он по-прежнему сможет отправлять серверу сообщения с правильным порядковым номером.</p>
      <p>Единственным решением этой возможной проблемы является запрет на прием любых соединений TCP, приходящих по обращенному маршруту от отправителя, когда вы используете IP-адрес от отправителя для какой-либо формы подтверждения (как, например, в случае с <code>rlogin</code> или <code>rshd</code>). Вместо вызова функции <code>setsockopt</code> во фрагменте кода, приведенном ранее, закройте только что принятое соединение и завершите только что порожденный процесс сервера. Второй сегмент трехэтапного рукопожатия отправится, но соединение не останется открытым и не будет использоваться далее.</p>
     </section>
    </section>
    <section>
     <title>
      <p>27.4. Заголовки расширения IPv6</p>
     </title>
     <p>Мы не показываем никаких параметров в заголовке IPv6 на рис. А.2 (который всегда имеет длину 40 байт), но следом за этим заголовком могут идти <emphasis>заголовки расширения</emphasis><a l:href="#n31" type="note">[7]</a> (<emphasis>extension headers</emphasis>).</p>
     <p>1. Параметры для транзитных узлов (hop-by-hop options) должны следовать непосредственно за 40-байтовым заголовком IPv6. В настоящее время не определены какие-либо параметры для транзитных узлов, которые могли бы использоваться в приложениях.</p>
     <p>2. Параметры получателя (destination options). В настоящее время не определены какие-либо параметры получателя, которые могли бы использоваться в приложениях.</p>
     <p>3. Заголовок маршрутизации. Этот параметр маршрутизации от отправителя аналогичен по своей сути тем, которые мы рассматривали в случае IPv4 в разделе 27.3.</p>
     <p>4. Заголовок фрагментации. Этот заголовок автоматически генерируется узлом при фрагментации дейтаграммы IPv6, а затем обрабатывается получателем при сборке дейтаграммы из фрагментов.</p>
     <p>5. Заголовок аутентификации (АН — authentication header). Использование этого заголовка документировано в RFC 2402 [65].</p>
     <p>6. Заголовок шифрования (ESH — encapsulating security payload header). Использование этого заголовка документировано в RFC 2406 [66].</p>
     <p>Мы уже говорили о том, что заголовок фрагментации целиком обрабатывается ядром, как и заголовки АН и ESP, обработка которых управляется согласно базе данных соглашений о безопасности (о сокетах управления ключами читайте в главе 9). Остаются еще три параметра, которые мы обсудим в следующем разделе. Интерфейс этих параметров определен в RFC 3542 [114].</p>
    </section>
    <section>
     <title>
      <p>27.5. Параметры транзитных узлов и параметры получателя IPv6</p>
     </title>
     <p>Параметры для транзитных узлов и параметры получателя IPv6 имеют одинаковый формат, показанный на рис. 27.3. Восьмиразрядное поле <emphasis>следующий заголовок</emphasis> (<emphasis>next header</emphasis>) идентифицирует следующий заголовок, который следует за данным заголовком. Восьмиразрядное поле <emphasis>длина заголовка расширения</emphasis> (<emphasis>header extension length</emphasis>) содержит длину заголовка расширения в условных единицах (1 у.e. = 8 байт), но не учитывает первые 8 байт заголовка. Например, если заголовок занимает всего 8 байт, то значение поля длины будет равно нулю. Если заголовок занимает 16 байт, то соответственно значение этого поля будет равно 1, и т.д. Оба заголовка заполняются таким образом, чтобы длина каждого была кратна 8 байтам. Это достигается либо с помощью параметра <code>pad1</code>, либо с помощью параметра <code>padN</code>, которые мы вскоре рассмотрим.</p>
     <image l:href="#img_140.png"/>
     <p><strong>Рис. 27.3</strong>. Формат параметра для транзитных узлов и параметра получателя</p>
     <p>Заголовок параметра транзитных узлов и заголовок параметра получателя могут содержать произвольное количество отдельных параметров, как показано на рис. 27.4.</p>
     <image l:href="#img_141.png"/>
     <p><strong>Рис. 27.4</strong>. Формат отдельных параметров, входящих в заголовок параметра транзитных узлов и заголовок параметра получателя</p>
     <p>Этот формат иногда называется TLV, так как для каждого отдельного параметра указывается его тип, длина и значение (<emphasis>type</emphasis>, <emphasis>length</emphasis>, <emphasis>value</emphasis>). Восьмиразрядное <emphasis>поле типа</emphasis> (<emphasis>type</emphasis>) указывает тип параметра. В дополнение к этому два старших разряда указывают, что именно узел IPv6 будет делать с этим параметром в том случае, если он не сможет в нем разобраться:</p>
     <p>&#9632; 00 — пропустить параметр и продолжить обработку заголовка.</p>
     <p>&#9632; 01 — игнорировать пакет.</p>
     <p>&#9632; 10 — игнорировать пакет и отослать отправителю сообщение об ошибке ICMP типа 2 (см. табл. А.6), независимо от того, является ли адрес получателя пакета групповым адресом.</p>
     <p>&#9632; 11 — игнорировать пакет и отослать отправителю сообщение об ошибке ICMP типа 2 (см. табл. А.6) но только в том случае, если адрес получателя пакета не является адресом многоадресной передачи.</p>
     <p>Следующий разряд указывает, могут ли меняться данные, входящие в этот параметр, в процессе передачи пакета.</p>
     <p>&#9632; 0 — данные параметра не могут быть изменены.</p>
     <p>&#9632; 1 — данные параметра могут быть изменены.</p>
     <p>Оставшиеся пять младших разрядов задают сам параметр. Заметьте, что код параметра определяется всеми восемью битами, младших пяти битов для этого недостаточно. Однако значения параметров выбираются таким образом, чтобы обеспечивать уникальность младших пяти битов как можно дольше.</p>
     <p>8-разрядное поле длины задает длину данных этих параметров в байтах. Длина поля типа и длина самого поля длины не входят в это значение.</p>
     <p>Два параметра заполнения (pad options) определены в RFC 2460 [27] и могут быть использованы как в заголовке параметров для транзитных узлов, так и в заголовке параметров получателя. Один из параметров транзитных узлов — параметр <emphasis>размера увеличенного поля данных</emphasis> (<emphasis>jumbo pay load length option</emphasis>) — определен в RFC 2675 [9]. Ядро генерирует этот параметр по мере необходимости и обрабатывает при получении. Новый параметр увеличенного объема данных для IPv6, аналогичный параметру <emphasis>извещения маршрутизатора</emphasis> (<emphasis>router alert</emphasis>), описан в RFC 2711 [87]. Эти параметры изображены на рис. 27.5. Есть и другие параметры (например, для Mobile-IPv6), но мы их на рисунке не показываем.</p>
     <image l:href="#img_142.png"/>
     <p><strong>Рис. 27.5</strong>. Параметры IPv6 для транзитных узлов</p>
     <p>Параметр <code>pad1</code> — это единственный параметр, для которого не указывается длина и значение. Его назначение — вставка одного пустого байта для заполнения. Параметр <code>padN</code> используется, когда требуется вставить 2 или более байта заполнения. Для 2 байт заполнения длина параметра будет иметь нулевое значение, а сам параметр будет состоять из поля типа и поля длины. В случае 3 байт заполнения длина будет равна 1, а следом за полем длины будет стоять один нулевой байт. Параметр размера увеличенного поля данных допускает увеличение поля размера дейтаграмм до 32 бит и используется, когда 16-разрядное поле размера, показанное на рис. А.2, оказывается недостаточно большим.</p>
     <p>Мы показываем эти параметры схематически, потому что для всех параметров получателя и транзитных узлов действует так называемое <emphasis>условие выравнивания</emphasis> (<emphasis>alignment requirement</emphasis>), записываемое как <emphasis>xn + y</emphasis>. Это означает, что сдвиг данного параметра относительно начала заголовка равен числу, <emphasis>n</emphasis> раз кратному <emphasis>x</emphasis> байтам, к которому добавлено <emphasis>у</emphasis> байтов (то есть величина сдвига в байтах равна <emphasis>xn + y</emphasis>). Например, условие выравнивания для параметра размера увеличенного поля данных записывается как 4<emphasis>n</emphasis> + 2. Это означает, что 4-байтовое значение параметра (длина размера увеличенного поля данных) будет выровнено по 4-байтовой границе. Причина, по которой значение y для этого параметра равно 2, заключается в том, что параметры транзитных узлов и получателя начинаются именно с двух байтов — один байт используется для указания типа, другой — для указания длины (см. рис. 27.4). Для параметра уведомления маршрутизатора условие выравнивания записывается как 2<emphasis>n </emphasis>+ 0, благодаря чему 2-байтовое значение параметра оказывается выровненным по 2-байтовой границе.</p>
     <p>Параметры транзитных узлов и параметры получателя обычно задаются как вспомогательные данные в функции <code>sendmsg</code> и возвращаются функцией <code>recvmsg</code> также в виде вспомогательных данных. От приложения не требуется никаких специальных действий для отправки этих параметров — нужно только задать их при вызове функции <code>sendmsg</code>. Но для получения этих параметров должен быть включен соответствующий параметр сокета: <code>IPV6_RECVHOPOPTS</code> для параметра транзитных узлов и <code>IPV6_RECVDSTOPTS</code> для параметров получателя. Например, чтобы можно было получить оба параметра, нужен следующий код:</p>
     <p><code>const int on = 1;</code></p>
     <empty-line/>
     <p><code>setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVHOPOPTS, &amp;on, sizeof(on));</code></p>
     <p><code>setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVDSTOPTS, &amp;on, sizeof(on));</code></p>
     <p>На рис. 27.6 показан формат объектов вспомогательных данных, используемый для отправки и получения параметров транзитных узлов и параметров получателя.</p>
     <image l:href="#img_143.png"/>
     <p><strong>Рис. 27.6</strong>. Объекты вспомогательных данных, используемые для параметров транзитных узлов и параметров получателя</p>
     <p>Чтобы уменьшить объем дублированного кода, определены семь функций, которые создают и обрабатывают эти вспомогательные объекты данных. Следующие четыре функции формируют отправляемый параметр.</p>
     <p><code>#include &lt;netinet/in.h&gt;</code></p>
     <empty-line/>
     <p><code>int inet6_opt_init(void *<emphasis>extbuf</emphasis>, socklen_t <emphasis>extlen</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: количество байтов для размещения пустого заголовка расширения, -1 в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>int inet6_opt_append(void *<emphasis>extbuf</emphasis>, socklen_t <emphasis>extlen</emphasis>,</code></p>
     <p><code> int <emphasis>offset</emphasis>, uint8_t <emphasis>type</emphasis>, socklen_t <emphasis>len</emphasis>, uint_t <emphasis>align</emphasis>, void **<emphasis>databufp</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: длину расширяющего заголовка после добавления параметра, -1 в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>int inet6_opt_finish(void *<emphasis>extbuf</emphasis>, socklen_t <emphasis>extlen</emphasis>, int <emphasis>offset</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: длину законченного заголовка расширения, -1 в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>int inet6_opt_set_val(void *<emphasis>databuf</emphasis>, int <emphasis>offset</emphasis>,</code></p>
     <p><code> const void *<emphasis>val</emphasis>, socklen_t <emphasis>vallen</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: новое смещение в буфере databuf</emphasis></code></p>
     <p>Функция <code>inet6_opt_init</code> возвращает количество байтов, необходимое для данного параметра. Если аргумент <code>extbuf</code> не является нулевым указателем, функция инициализирует заголовок расширения. Значение -1 возвращается при аварийном завершении работы в том случае, если аргумент <code>extlen</code> не кратен 8. (Все заголовки параметров транзитных узлов и получателя в IPv6 должны быть кратны 8.)</p>
     <p>Функция <code>inet6_opt_append</code> возвращает общую длину заголовка расширения после добавления указанного при вызове параметра. Если аргумент <code>extbuf</code> не является нулевым указателем, функция дополнительно выполняет инициализацию параметра и вставляет необходимое заполнение. Значение -1 возвращается в случае аварийного завершения работы, если параметр не помещается в выделенный буфер. Аргумент <code>offset</code> представляет собой текущую полную длину, то есть значение, возвращенное при предыдущем вызове <code>inet6_opt_append</code> или <code>inet6_opt_init</code>. Аргументы <code>type</code> и <code>len</code> задают тип и длину параметра, они копируются непосредственно в его заголовок. Аргумент <code>align</code> указывает условие выравнивания, то есть значение x из выражения <emphasis>xn + y</emphasis>. Значение у вычисляется по <code>align</code> и <code>len</code>, поэтому указывать его явным образом необходимости нет. Аргумент <code>databufp</code> представляет собой адрес будущего указателя на значение параметра. Значение параметра копируется вызывающим процессом при помощи функции <code>inet6_opt_set_val</code> или любым другим методом.</p>
     <p>Для завершения расширяющего заголовка вызывается функция <code>inet6_opt_finish</code>, которая добавляет в заголовок заполнение, делая его длину кратной 8 байтам. Как и раньше, заполнение добавляется в буфер только в том случае, если аргумент <code>extbuf</code> представляет собой непустой указатель. В противном случае функция вычисляет обновленное значение длины. Подобно <code>inet6_opt_append</code>, аргумент <code>offset</code> задает текущую полную длину (значение, возвращаемое <code>inet6_opt_append</code> и <code>inet6_opt_init</code>). Функция <code>inet6_opt_finish</code> возвращает полную длину возвращаемого заголовка или -1, если требуемое заполнение не помещается в предоставленный буфер.</p>
     <p>Функция <code>inet6_opt_set_val</code> копирует значение параметра в буфер данных, возвращаемый <code>inet6_opt_append</code>. Аргумент <code>databuf</code> представляет собой указатель, возвращаемый <code>inet6_opt_append</code>. Аргумент <code>offset</code> представляет собой текущую длину внутри параметра, его необходимо инициализировать нулем для каждого параметра, а затем использовать возвращаемые <code>inet6_opt_set_val</code> значения по мере построения параметра. Аргументы <code>val</code> и <code>vallen</code> определяют значение для копирования в буфер значения параметра.</p>
     <p>Предполагается, что с помощью этих функций вы будете делать два прохода по списку параметров, которые вы предполагаете вставить: во время первого прохода будет вычисляться требуемая длина буфера, а во время второго прохода — выполняться фактическое построение буфера параметра. При первом проходе нужно вызвать <code>inet6_opt_init</code>, <code>inet6_opt_append</code> (один раз для каждого параметра) и <code>inet6_opt_finish</code>, передавая нулевой указатель и 0 в качестве аргументов <code>extbuf</code> и <code>extlen</code> соответственно. Затем можно динамически выделить буфер, использовав в качестве размера значение, возвращенное <code>inet6_opt_finish</code>. Этот буфер будет передаваться в качестве аргумента <code>extbuf</code> при втором проходе. Во время второго прохода вызываются функции <code>inet6_opt_init</code> и <code>inet6_opt_append</code>. Копирование значений параметров может выполняться как «вручную», так и при помощи функции <code>inet6_opt_set_val</code>. Наконец, мы должны вызвать <code>inet6_opt_finish</code>. Альтернативный вариант действий состоит в выделении буфера достаточно большого размера для нашего параметра. В этом случае первый проход можно не выполнять. Однако если изменение параметров приведет к переполнению выделенного буфера, в программе возникнет ошибка.</p>
     <p>Оставшиеся три функции обрабатывают полученный параметр.</p>
     <p><code>#include &lt;netinet/in.h&gt;</code></p>
     <empty-line/>
     <p><code>int inet6_opt_next(const void *<emphasis>extbuf</emphasis>, socklen_t <emphasis>extlen</emphasis>,</code></p>
     <p><code>int <emphasis>offset</emphasis>, uint8_t *<emphasis>typep</emphasis>, socklen_t *<emphasis>lenp</emphasis>, void **<emphasis>databufp</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: смещение следующего параметра, -1 в случае достижения конца списка параметров или в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>int inet6_opt_find(const void *<emphasis>extbuf</emphasis>, socklen_t <emphasis>extlen</emphasis>,</code></p>
     <p><code>int <emphasis>offset</emphasis>, uint8_t <emphasis>type</emphasis>, socklen_t *<emphasis>lenp</emphasis>, void **<emphasis>databufp</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: смещение следующего параметра, -1 в случае достижения конца списка параметров или в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>int inet6_opt_get_val(const void *<emphasis>databuf</emphasis>, int <emphasis>offset</emphasis>, void *<emphasis>val</emphasis>, socklen_t <emphasis>vallen</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: новое значение смещения внутри буфера databuf</emphasis></code></p>
     <p>Функция <code>inet6_opt_next</code> обрабатывает следующий параметр в буфере. Аргументы <code>extbuf</code> и <code>extlen</code> определяют буфер, в котором содержится заголовок. Как и у <code>inet6_opt_append</code>, аргумент <code>offset</code> представляет собой текущее смещение внутри буфера. При первом вызове <code>inet6_opt_next</code> значение этого аргумента должно быть равно нулю, а при всех последующих — значению, возвращенному при предыдущем вызове функции. Аргументы <code>typep</code>, <code>lenp</code> и <code>databufp</code> предназначены для возвращения функцией типа, длины и значения параметра соответственно. Функция <code>inet6_opt_next</code> возвращает -1 в случае обработки заголовка с нарушенной структурой или в случае достижения конца буфера.</p>
     <p>Функция <code>inet6_opt_find</code> аналогична предыдущей функции, но позволяет вызывающему процессу задать тип параметра, который следует искать (аргумент type), вместо того чтобы каждый раз возвращать следующий параметр.</p>
     <p>Функция <code>inet6_opt_get_val</code> предназначена для извлечения значений из параметра по указателю <code>databuf</code>, возвращаемому предшествующим вызовом <code>inet6_opt_next</code> или <code>inet6_opt_find</code>. Как и для <code>inet6_opt_set_val</code>, аргумент <code>offset</code> должен начинаться с 0 для каждого параметра, а затем должен приравниваться значению, возвращаемому предшествующим вызовом <code>inet6_opt_get_val</code>.</p>
    </section>
    <section>
     <title>
      <p>27.6. Заголовок маршрутизации IPv6</p>
     </title>
     <p>Заголовок маршрутизации IPv6 используется для маршрутизации от отправителя в IPv6. Первые два байта заголовка маршрутизации такие же, как показанные на рис. 27.3: поле <emphasis>следующего заголовка</emphasis> (<emphasis>next header</emphasis>) и поле <emphasis>длины заголовка расширения</emphasis> (<emphasis>header extension length</emphasis>). Следующие два байта задают <emphasis>тип маршрутизации</emphasis> (<emphasis>routing type</emphasis>) и <emphasis>количество оставшихся сегментов</emphasis> (<emphasis>number of segments left</emphasis>) (то есть сколько из перечисленных узлов еще нужно пройти). Определен только один тип заголовка маршрутизации, обозначаемый как тип 0. Формат заголовка маршрутизации показан на рис. 27.7.</p>
     <image l:href="#img_144.png"/>
     <p><strong>Рис. 27.7</strong>. Заголовок маршрутизации IPv6</p>
     <p>В заголовке маршрутизации IPv6 может появиться неограниченное количество адресов (реальное ограничение накладывается длиной пакета), а количество оставшихся сегментов не должно превышать количество адресов в заголовке. Документ RFC 2460 [27] описывает подробности обработки этого заголовка при пересылке его в направлении получателя. Там же вы можете найти подробно рассмотренный пример.</p>
     <p>Заголовок маршрутизации обычно задается как вспомогательные данные в функции <code>sendmsg</code> и возвращается в виде вспомогательных данных функцией <code>recvmsg</code>. Для отправки заголовка приложению не требуется выполнять какие-либо специальные действия — достаточно просто указать его при вызове функции <code>sendmsg</code>. Но для получения заголовка маршрутизации требуется, чтобы был включен параметр <code>IPV6_RECVRTHDR</code>:</p>
     <p><code>const int on = 1;</code></p>
     <empty-line/>
     <p><code>setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVRTHDR, &amp;on, sizeof(on));</code></p>
     <p>На рис. 27.8 показан формат объекта вспомогательных данных, используемый для отправки и получения заголовка маршрутизации. Для создания и обработки заголовка маршрутизации определены шесть функций. Следующие три функции используются для создания отправляемого параметра.</p>
     <p><code>#include &lt;netinet/in.h&gt;</code></p>
     <empty-line/>
     <p><code>socklen_t inet6_rth_space(int <emphasis>type</emphasis>, int <emphasis>segments</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: положительное число, равное количеству байтов в случае успешного выполнения, 0 в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>void *inet6_rth_init(void *<emphasis>rthbuf</emphasis>, socklen_t <emphasis>rthlen</emphasis>, int <emphasis>type</emphasis>, int <emphasis>segments</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: непустой указатель в случае успешного выполнения, NULL в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>int inet6_rth_add(void *<emphasis>rthbuf</emphasis>, const struct in6_addr *<emphasis>addr</emphasis>);</code></p>
     <p><code>Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки</code></p>
     <image l:href="#img_145.png"/>
     <p><strong>Рис. 27.8</strong>. Объект вспомогательных данных для заголовка маршрутизации IPv6</p>
     <p>Функция <code>inet6_rth_space</code> возвращает количество байтов, необходимое для размещения объекта вспомогательных данных, содержащего заголовок маршрутизации указанного типа (обычно это <code>IPV6_RTHDR_TYPE_0</code>) с заданным количеством сегментов.</p>
     <p>Функция <code>inet6_rth_init</code> инициализирует буфер, на который указывает аргумент <code>rthbuf</code>, для помещения заголовка маршрутизации типа type и заданного количества сегментов. Возвращаемое значение этой функции — указатель на буфер. Этот указатель используется как аргумент при вызове следующей функции. Функция <code>inet6_rth_init</code> возвращает <code>NULL</code> в случае возникновения ошибок (например, при недостаточном размере предоставленного буфера).</p>
     <p>Функция <code>inet6_rth_add</code> добавляет адрес IPv6, на который указывает аргумент <code>addr</code>, к концу составляемого заголовка маршрутизации. В случае успешного выполнения обновляется значение элемента <code>segleft</code> заголовка маршрутизации, чтобы учесть добавленный новый адрес.</p>
     <p>Следующие три функции манипулируют полученным заголовком маршрутизации:</p>
     <p><code>#include &lt;netinet/in.h&gt;</code></p>
     <empty-line/>
     <p><code>int inet6_rth_reverse(const void *<emphasis>in</emphasis>, void *<emphasis>out</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>int inet6_rth_segments(const void *<emphasis>rthbuf</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: количество сегментов в заголовке маршрутизации в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <empty-line/>
     <p><code>struct in6_addr *inet6_rth_getaddr(const void *<emphasis>rthbuf</emphasis>, int <emphasis>index</emphasis>);</code></p>
     <p><code><emphasis>Возвращает: непустой указатель в случае успешного выполнения, NULL в случае ошибки</emphasis></code></p>
     <p>Функция <code>inet6_rth_reverse</code> принимает в качестве аргумента заголовок маршрутизации, полученный в виде объекта вспомогательных данных (на который указывает аргумент <code>in</code>), и создает новый заголовок маршрутизации (в буфере, на который указывает аргумент <code>out</code>), отправляющий дейтаграммы по обратному маршруту. Указатели in и out могут указывать на один и тот же буфер.</p>
     <p>Функция <code>inet6_rth_segments</code> возвращает количество сегментов в заголовке маршрутизации, на который указывает <code>rthbuf</code>. В случае успешного выполнения функции возвращаемое значение оказывается больше 0.</p>
     <p>Функция <code>inet6_rth_getaddr</code> возвращает указатель на адрес IPv6, заданный через <code>index</code> в заголовке маршрутизации <code>rthbuf</code>. Аргумент <code>index</code> должен лежать в пределах от 1 до значения, возвращенного функцией <code>inet6_rth_segments</code>, включительно.</p>
     <p>Чтобы продемонстрировать использование этих параметров, мы создали UDP-клиент и UDP-сервер. Клиент представлен в листинге 27.5. Он принимает маршрут от отправителя в командной строке подобно TCP-клиенту IPv4, представленному в листинге 27.4. Сервер печатает маршрут полученного сообщения и обращает этот маршрут для отправки сообщения в обратном направлении.</p>
     <p><strong>Листинг 27.5</strong>. UDP-клиент, использующий маршрутизацию от отправителя</p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int с, sockfd, len = 0;</code></p>
     <p><code> 6  u_char *ptr = NULL;</code></p>
     <p><code> 7  void *rth;</code></p>
     <p><code> 8  struct addrinfo *ai;</code></p>
     <empty-line/>
     <p><code> 9  if (argc &lt; 2)</code></p>
     <p><code>10   err_quit("usage: udpcli01 [ &lt;hostname&gt; ... ] &lt;hostname&gt;");</code></p>
     <empty-line/>
     <p><code>11  if (argc &gt; 2) {</code></p>
     <p><code>12   int i;</code></p>
     <empty-line/>
     <p><code>13   len = Inet6_rth_space(IPV6_RTHDR_TYPE_0, argc-2);</code></p>
     <p><code>14   ptr = Malloc(len);</code></p>
     <p><code>15   Inet6_rth_init(ptr, len, IPV6_RTHDR_TYPE_0, argc-2);</code></p>
     <p><code>16   for (i = 1; i &lt; argc-1; i++) {</code></p>
     <p><code>17    ai = Host_serv(argv[i], NULL, AF_INET6, 0);</code></p>
     <p><code>18    Inet6_rth_add(ptr,</code></p>
     <p><code>19     &amp;((struct sockaddr_in6*)ai-&gt;ai_addr)-&gt;sin6_addr);</code></p>
     <p><code>20   }</code></p>
     <p><code>21  }</code></p>
     <empty-line/>
     <p><code>22  ai = Host_serv(argv[argc-1], SERV_PORT_STR, AF_INET6, SOCK_DGRAM);</code></p>
     <empty-line/>
     <p><code>23  sockfd = Socket(ai-&gt;ai_family, ai-&gt;ai_socktype, ai-&gt;ai_protocol);</code></p>
     <empty-line/>
     <p><code>24  if (ptr) {</code></p>
     <p><code>25   Setsockopt(sockfd, IPPROTO_IPV6, IPV6_RTHDR, ptr, len);</code></p>
     <p><code>26   free(ptr);</code></p>
     <p><code>27  }</code></p>
     <empty-line/>
     <p><code>28  dg_cli(stdin, sockfd, ai-&gt;ai_addr, ai-&gt;ai_addrlen); /* do it all */</code></p>
     <empty-line/>
     <p><code>29  exit(0);</code></p>
     <p><code>30 }</code></p>
     <subtitle>Создание маршрута</subtitle>
     <p><code>11-21</code> Если при вызове программы было указано более одного аргумента, все параметры командной строки, за исключением последнего, формируют маршрут от отправителя. Сначала мы определяем, какой объем памяти займет заголовок маршрутизации, при помощи функции <code>inet6_rth_space</code>, затем выделяем буфер соответствующего размера вызовом <code>malloc</code>. После этого каждый адрес маршрута преобразуется в числовую форму функцией <code>host_serv</code> и добавляется к маршруту функцией <code>inet6_rth_add</code>. Примерно то же самое выполнял и TCP-клиент IPv4, за тем исключением, что здесь мы используем библиотечные функции, а не свои собственные.</p>
     <subtitle>Поиск адресата и создание сокета</subtitle>
     <p><code>22-23</code> Мы определяем адрес назначения при помощи <code>host_serv</code> и создаем сокет для отправки пакетов.</p>
     <subtitle>Установка «закрепленного» параметра IPV6_RTHDR и вызов рабочей функции</subtitle>
     <p><code>24-27</code> В разделе 27.7 будет показано, что не обязательно отправлять одни и те же вспомогательные данные с каждым пакетом. Вместо этого можно вызвать <code>setsockopt</code> таким образом, что один и тот же заголовок будет добавляться ко всем пакетам в рамках одного сеанса. Этот параметр устанавливается только в том случае, если указатель <code>ptr</code> не нулевой, то есть мы уже должны были выделить буфер под заголовок маршрутизации. На последнем этапе мы вызываем рабочую функцию <code>dg_cli</code>, которая не меняется с листинга 8.4.</p>
     <p>Программа UDP-сервера не изменилась по сравнению с предыдущими примерами. Сервер открывает сокет и вызывает функцию <code>dg_echo</code>. В листинге 27.6 представлена функция <code>dg_echo</code>, печатающая информацию о маршруте от источника (если таковой был получен) и обращающая этот маршрут для отправки сообщения в обратном направлении.</p>
     <p><strong>Листинг 27.6</strong>. Функция dg_echo, печатающая маршрут</p>
     <p><code>//ipopts/dgechoprintroute.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int n;</code></p>
     <p><code> 6  char mesg[MAXLINE];</code></p>
     <p><code> 7  int on;</code></p>
     <p><code> 8  char control[MAXLINE];</code></p>
     <p><code> 9  struct msghdr msg;</code></p>
     <p><code>10  struct cmsghdr *cmsg;</code></p>
     <p><code>11  struct iovec iov[1];</code></p>
     <empty-line/>
     <p><code>12  on = 1;</code></p>
     <p><code>13  Setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVRTHDR, &amp;on, sizeof(on));</code></p>
     <empty-line/>
     <p><code>14  bzero(&amp;msg, sizeof(msg));</code></p>
     <p><code>15  iov[0].iov_base = mesg;</code></p>
     <p><code>16  msg.msg_name = pcliaddr;</code></p>
     <p><code>17  msg.msg_iov = iov;</code></p>
     <p><code>18  msg.msg_iovlen = 1;</code></p>
     <p><code>19  msg.msg_control = control;</code></p>
     <p><code>20  for (;;) {</code></p>
     <p><code>21   msg.msg_namelen = clilen;</code></p>
     <p><code>22   msg.msg_controllen = sizeof(control);</code></p>
     <p><code>23   iov[0].iov_len = MAXLINE;</code></p>
     <p><code>24   n = Recvmsg(sockfd, &amp;msg, 0);</code></p>
     <empty-line/>
     <p><code>25   for (cmsg = CMSG_FIRSTHDR(&amp;msg); cmsg != NULL;</code></p>
     <p><code>26    cmsg = CMSG_NXTHDR(&amp;msg, cmsg)) {</code></p>
     <p><code>27    if (cmsg-&gt;cmsg_level == IPPROTO_IPV6 &amp;&amp;</code></p>
     <p><code>28     cmsg-&gt;cmsg_type == IPV6_RTHDR) {</code></p>
     <p><code>29     inet6_srcrt_print(CMSG_DATA(cmsg));</code></p>
     <p><code>30     Inet6_rth_reverse(CMSG_DATA(cmsg), CMSG_DATA(cmsg));</code></p>
     <p><code>31    }</code></p>
     <p><code>32   }</code></p>
     <empty-line/>
     <p><code>33   iov[0].iov_len = n;</code></p>
     <p><code>34   Sendmsg(sockfd, &amp;msg, 0);</code></p>
     <p><code>35  }</code></p>
     <p><code>36 }</code></p>
     <subtitle>Включение параметра IPV6_RECVRTHDR и подготовка структуры msghdr</subtitle>
     <p><code>12-13</code> Чтобы получить информацию о маршруте, мы должны установить параметр сокета <code>IPV6_RECVRTHDR</code>. Кроме того, мы должны использовать функцию <code>recvmsg</code>, поэтому мы настраиваем поля структуры <code>msghdr</code>, которые не требуют изменения.</p>
     <subtitle>Настройка изменяемых полей и вызов recvmsg</subtitle>
     <p><code>21-24</code> Мы устанавливаем размер полей длины и вызываем <code>recvmsg</code>.</p>
     <subtitle>Поиск и обработка маршрута от отправителя</subtitle>
     <p><code>25-32</code> Мы перебираем вспомогательные данные, используя <code>CMSG_FIRSTHDR</code> и <code>CMSG_NXTHDR</code>. Несмотря на то, что мы ожидаем получить только один объект вспомогательных данных, выполнить такой перебор всегда полезно. Если мы обнаруживаем заголовок маршрутизации, он распечатывается функцией <code>inet6_srcrt_print</code> (листинг 27.7). Затем маршрут обращается функцией <code>inet6_rth_reverse</code> для последующего использования при возвращении пакета клиенту. В данном случае обращение производится без копирования в новый буфер, так что можно использовать старый объект вспомогательных данных для отправки пакета клиенту.</p>
     <subtitle>Отправка эхо-пакета</subtitle>
     <p><code>33-34</code> Мы устанавливаем длину пакета и передаем его клиенту вызовом <code>sendmsg</code>.</p>
     <p>Благодаря наличию вспомогательных библиотечных функций IPv6 наша функция <code>inet6_srcrt_print</code> становится почти тривиальной.</p>
     <p><strong>Листинг 27.7</strong>. Функция inet6_srcrt_print: вывод маршрута</p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 inet6_srcrt_print(void *ptr)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int i, segments;</code></p>
     <p><code> 6  char str[INET6_ADDRSTRLEN];</code></p>
     <empty-line/>
     <p><code> 7  segments = Inet6_rth_segments(ptr);</code></p>
     <p><code> 8  printf("received source route: ");</code></p>
     <p><code> 9  for (i = 0; i &lt; segments; i++)</code></p>
     <p><code>10   printf("%s ", Inet_ntop(AF_INET6, Inet6_rth_getaddr(ptr, i),</code></p>
     <p><code>11    str, sizeof(str)));</code></p>
     <p><code>12  printf("\n");</code></p>
     <p><code>13 }</code></p>
     <subtitle>Определение количества сегментов маршрута</subtitle>
     <p><code>7</code> Количество сегментов маршрута определяется функцией <code>inet6_rth_segments</code>.</p>
     <subtitle>Перебор сегментов</subtitle>
     <p><code>9-11</code> Мы перебираем сегменты маршрута, вызывая для каждого из них <code>inet6_rth_getaddr</code> и преобразуя адреса в формат представления функцией <code>inet_ntop</code>.</p>
     <p>Клиенту и серверу, работающим с маршрутами IPv6, не нужно ничего знать о формате этих маршрутов внутри пакета. Библиотечные функции интерфейса скрывают детали форматирования, но не мешают нам программировать с той же гибкостью, которая была в IPv4, где параметры нужно было строить вручную.</p>
    </section>
    <section>
     <title>
      <p>27.7. «Закрепленные» параметры IPv6</p>
     </title>
     <p>Мы рассмотрели использование вспомогательных данных с функциями <code>sendmsg</code> и <code>recvmsg</code> для отправки и получения следующих семи различных типов объектов вспомогательных данных:</p>
     <p>1. Информация о пакете IPv6: структура <code>in6_pktinfo</code>, содержащая адрес получателя и индекс интерфейса для исходящих дейтаграмм либо адрес отправителя и индекс интерфейса для приходящих дейтаграмм (индекс принимающего интерфейса) (см. рис. 22.5).</p>
     <p>2. Предельное количество транзитных узлов для исходящих или приходящих дейтаграмм (см. рис. 22.5).</p>
     <p>3. Адрес следующего транзитного узла (см. рис. 22.5).</p>
     <p>4. Класс исходящего или входящего трафика (см. рис. 22.5).</p>
     <p>5. Параметры транзитных узлов (см. рис. 27.6).</p>
     <p>6. Параметры получателя (см. рис. 27.6).</p>
     <p>7. Заголовок маршрутизации (см. рис. 27.8).</p>
     <p>В табл. 14.4 приведены значения полей <code>cmsg_level</code> и <code>cmsg_type</code> для этих объектов, а также значения для других объектов вспомогательных данных.</p>
     <p>Вместо того чтобы отсылать эти параметры при каждом вызове функции <code>sendmsg</code>, мы можем установить соответствующие параметры сокета. Параметры сокета используют те же константы, что и вспомогательные данные, то есть уровень параметра всегда должен иметь значение <code>IPPROTO_IPV6</code>, а название параметра может быть <code>IPV6_DSTOPTS</code>, <code>IPV6_HOPLIMIT</code>, <code>IPV6_HOPOPTS</code>, <code>IPV6_NEXTHOP</code>, <code>IPV6_PKTINFO</code>, <code>IPV6_RTHDR</code> или <code>IPV6_TCLASS</code>. Закрепленные параметры могут быть заменены для конкретного пакета в случае сокета UDP или символьного сокета IPv6, если при вызове функции <code>sendmsg</code> задать какие-либо другие параметры в качестве объектов вспомогательных данных. Если при вызове функции <code>sendmsg</code> указаны какие-либо вспомогательные данные, ни один из закрепленных параметров не будет послан с этим пакетом.</p>
     <p>Концепция закрепленных параметров также может быть использована и в случае TCP, поскольку вспомогательные данные никогда не отсылаются и не принимаются с помощью функций <code>sendmsg</code> или <code>recvmsg</code> на сокете TCP. Вместо этого приложение TCP может установить соответствующий параметр сокета и указать любой из упомянутых в начале этого раздела семи объектов вспомогательных данных. Тогда эти параметры будут относиться ко всем пакетам, отсылаемым с данного сокета. Поведение при повторной передаче пакетов, первоначально переданных до изменения закрепленных параметров, не определено: могут использоваться как старые, так и новые значения параметров.</p>
     <p>Не существует способа получить параметры, принятые в IP-пакете по TCP, потому что в этом протоколе отсутствует соответствие между пакетами и операциями чтения из сокета, выполняемыми пользователем.</p>
    </section>
    <section>
     <title>
      <p>27.8. История развития интерфейса IPv6</p>
     </title>
     <p>Документ RFC 2292 [113] определял более раннюю версию описываемого интерфейса, которая была реализована в некоторых системах. В этой версии для работы с параметрами получателя и транзитных узлов использовались функции <code>inet6_option_space</code>, <code>inet6_option_init</code>, <code>inet6_option_append</code>, <code>inet6_option_alloc</code>, <code>inet6_option_next</code> и <code>inet6_option_find</code>. Эти функции работали непосредственно с объектами типа <code>struct cmsghdr</code>, предполагая, что все параметры содержатся во вспомогательных данных. Для работы с заголовками маршрутизации были предназначены функции <code>inet6_rthdr_space</code>, <code>inet6_rthdr_init</code>, <code>inet6_rthdr_add</code>, <code>inet6_rthdr_lasthop</code>, <code>inet6_rthdr_reverse</code>, <code>inet6_rthdr_segments</code>, <code>inet6_rthdr_getaddr</code> и <code>inet6_rthdr_getflags</code>. Эти функции также работали непосредственно со вспомогательными данными.</p>
     <p>В этом API закрепленные параметры устанавливались при помощи параметра сокета <code>IPV6_PKTOPTIONS</code>. Объекты вспомогательных данных при этом передавались в качестве данных параметра <code>IPV6_PKTOPTIONS</code>. Нынешние параметры сокета <code>IPV6_DSTOPTS</code>, <code>IPV6_HOPOPTS</code> и <code>IPV6_RTHDR</code> были флагами, позволявшими получать соответствующие заголовки во вспомогательных данных.</p>
     <p>Подробнее обо всем этом вы можете прочесть в разделах 4–8 документа RFC 2292 [113].</p>
    </section>
    <section>
     <title>
      <p>27.9. Резюме</p>
     </title>
     <p>Из десяти определенных в IPv4 параметров наиболее часто используются параметры маршрутизации от отправителя, но в настоящее время их популярность падает из-за проблем, связанных с безопасностью. Доступ к параметрам заголовков IPv4 осуществляется с помощью параметра сокета <code>IP_OPTIONS</code>.</p>
     <p>В IPv6 определены шесть заголовков расширения. Доступ к заголовкам расширения IPv6 осуществляется с помощью функционального интерфейса, что освобождает нас от необходимости углубляться в детали фактического формата пакета. Эти заголовки расширения записываются как вспомогательные данные функцией <code>sendmsg</code> и возвращаются функцией <code>recvmsg</code> также в виде вспомогательных данных.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Что изменится, если в нашем примере, приведенном в конце раздела 27.3, мы зададим каждый промежуточный узел с параметром <code>-G</code> вместо <code>-g</code>?</p>
     <p>2. Размер буфера, указываемый в качестве аргумента функции <code>setsockopt</code> для параметра сокета <code>IP_OPTIONS</code>, должен быть кратен 4 байтам. Что бы нам пришлось делать, если бы мы не поместили параметр NOP в начало буфера, как показано на рис. 27.1?</p>
     <p>3. Каким образом программа <code>ping</code> получает маршрут от отправителя, когда используется параметр IP Record Route (запись маршрута), описанный в разделе 7.3 [128]?</p>
     <p>4. Почему в примере кода для сервера <code>rlogind</code>, приведенном в конце раздела 27.3, который предназначен для удаления полученного маршрута от отправителя, дескриптор сокета (первый аргумент функций <code>getsockopt</code> и <code>setsockopt</code>) имеет нулевое значение?</p>
     <p>5. В течение долгого времени для удаления маршрута использовался код, несколько отличающийся от приведенного в конце раздела 27.3. Он выглядел следующим образом:</p>
     <p><code>optsize = 0;</code></p>
     <p><code>setsockopt(0, ipproto, IP_OPTIONS, NULL, &amp;optsize);</code></p>
     <p>Что в этом фрагменте неправильно? Имеет ли это значение?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 28</p>
     <p>Символьные сокеты</p>
    </title>
    <section>
     <title>
      <p>28.1. Введение</p>
     </title>
     <p><emphasis>Символьные</emphasis>, или <emphasis>неструктурированные</emphasis>, <emphasis>сокеты</emphasis> (<emphasis>raw sockets</emphasis>) обеспечивают три возможности, не предоставляемые обычными сокетами TCP и UDP.</p>
     <p>1. Символьные сокеты позволяют читать и записывать пакеты ICMPv4, IGMPv4 и ICMPv6. Например, программа <code>ping</code> посылает эхо-запросы ICMP и получает эхо-ответы ICMP. (Наша оригинальная версия программы <code>ping</code> приведена в разделе 28.5.) Демон маршрутизации многоадресной передачи <code>mrouted</code> посылает и получает пакеты IGMPv4.</p>
     <p>2. Эта возможность также позволяет реализовывать как пользовательские процессы те приложения, которые построены с использованием протоколов ICMP и IGMP, вместо того чтобы помещать большее количество кода в ядро. Например, подобным образом построен демон обнаружения маршрутов (<code>in.rdisc</code> в системе Solaris 2.x. В приложении F книги [111] рассказывается, как можно получить исходный код открытой версии). Этот демон обрабатывает два типа сообщений ICMP, о которых ядро ничего не знает (извещение маршрутизатора и запрос маршрутизатору).</p>
     <p>С помощью символьных сокетов процесс может читать и записывать IPv4-дейтаграммы с полем протокола IPv4, которое не обрабатывается ядром. Посмотрите еще раз на 8-разрядное поле протокола IPv4, изображенное на рис. А.1. Большинство ядер обрабатывают дейтаграммы, содержащие значения поля протокола 1 (ICMP), 2 (IGMP), 6 (TCP) и 17 (UDP). Но для этого поля определено гораздо большее количество значений, полный список которых приведен в реестре IANA «Номера протоколов» (Protocol Numbers). Например, протокол маршрутизации OSPF не использует протоколы TCP или UDP, а работает напрямую с протоколом IP, устанавливая в поле протокола значение 89 для IP-дейтаграмм. Программа <code>gated</code>, реализующая OSPF, должна использовать для чтения и записи таких IP-дейтаграмм символьный сокет, поскольку они содержат значение поля протокола, о котором ничего не известно ядру. Эта возможность также переносится в версию IPv6.</p>
     <p>3. С помощью символьных сокетов процесс может построить собственный заголовок IPv4 при помощи параметра сокета <code>IP_HDRINCL</code>. Такую возможность имеет смысл использовать, например, для построения собственного пакета UDP или TCP. Подобный пример приведен в разделе 29.7.</p>
     <p>В данной главе описывается создание символьных сокетов, а также операции ввода и вывода с этими сокетами. Далее приводятся версии программ <code>ping</code> и <code>traceroute</code>, работающие как с версией IPv4, так и с версией IPv6.</p>
    </section>
    <section>
     <title>
      <p>28.2. Создание символьных сокетов</p>
     </title>
     <p>При создании символьных сокетов выполняются следующие шаги:</p>
     <p>1. Символьный сокет создается функцией <code>socket</code> со вторым аргументом <code>SOCK_RAW</code>. Третий аргумент (протокол) обычно ненулевой. Например, для создания символьного сокета IPv4 следует написать:</p>
     <p><code>int sockfd;</code></p>
     <p><code>sockfd = socket(AF_INET, SOCK_RAW, <emphasis>protocol</emphasis>);</code></p>
     <p>где <code>protocol</code> — одна из констант <code>IPPROTO_xxx</code>, определенных в подключенном заголовочном файле <code>&lt;netinet/in.h&gt;</code>, например <code>IPPROTO_ICMP</code>.</p>
     <p>Только привилегированный пользователь может создать символьный сокет. Такой подход предотвращает отправку IP-дейтаграмм в сеть обычными пользователями.</p>
     <p>2. Параметр сокета <code>IP_HDRINCL</code> может быть установлен следующим образом:</p>
     <p><code>const int on = 1;</code></p>
     <p><code>if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &amp;on, sizeof(on)) &lt; 0)</code></p>
     <p><code><emphasis> обработка ошибки</emphasis></code></p>
     <p>В следующем разделе описывается действие этого параметра.</p>
     <p>3. На символьном сокете можно вызвать функцию <code>bind</code>, но это делается редко. Эта функция устанавливает только локальный адрес: на символьном сокете нет понятия порта. Что касается вывода, вызов функции <code>bind</code> устанавливает IP-адрес отправителя, который будет использоваться для дейтаграмм, отправляемых на символьном сокете (только если не установлен параметр сокета <code>IP_HDRINCL</code>). Если функция <code>bind</code> не вызывается, ядро использует в качестве IP-адреса отправителя основной IP-адрес исходящего интерфейса.</p>
     <p>4. На символьном сокете можно вызвать функцию <code>connect</code>, но это делается редко. Эта функция устанавливает только внешний адрес, так как на символьном сокете нет понятия порта. О выводе можно сказать, что вызов функции connect позволяет нам вызвать функцию <code>write</code> или <code>send</code> вместо <code>sendto</code>, поскольку IP-адрес получателя уже определен.</p>
    </section>
    <section>
     <title>
      <p>28.3. Вывод на символьном сокете</p>
     </title>
     <section>
      <p>Вывод на символьном сокете регулируется следующими правилами:</p>
      <p>1. Стандартный вывод выполняется путем вызова функции <code>sendto</code> или <code>sendmsg</code> и определения IP-адреса получателя. Функции <code>write</code>, <code>writev</code> и <code>send</code> также можно использовать, если сокет был присоединен.</p>
      <p>2. Если не установлен параметр сокета <code>IP_HDRINCL</code>, то начальный адрес данных, предназначенных для записи ядром, указывает на первый байт, следующий за IP-заголовком, поскольку ядро будет строить IP-заголовок и добавлять его к началу данных из процесса. Ядро устанавливает поле протокола создаваемого заголовка IPv4 равным значению третьего аргумента функции <code>socket</code>.</p>
      <p>3. Если параметр сокета <code>IP_HDRINCL</code> установлен, то начальный адрес данных, предназначенных для записи ядром, указывает на первый байт IP-заголовка. Размер данных для записи должен включать размер IP-заголовка вызывающего процесса. Процесс полностью формирует IP-заголовок, за исключением того, что, во-первых, значение поля идентификации IPv4 может быть нулевым (что указывает ядру на необходимость самостоятельно установить это значение), во-вторых, ядро всегда вычисляет и сохраняет контрольную сумму заголовка IPv4, в-третьих, включает или не включает параметры IP (см. раздел 27.2).</p>
      <p>4. Ядро фрагментирует символьные пакеты, превышающие значение MTU исходящего интерфейса.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Согласно документации, символьные сокеты должны предоставлять протоколу такой же интерфейс, как если бы он был реализован в ядре [74]. К сожалению, это означает, что некоторые части интерфейса зависят от ядра операционной системы. В частности, это относится к порядку байтов полей заголовка IP. В Беркли-ядрах все поля имеют порядок байтов сети, за исключением полей ip_len и ip_off, имеющих порядок байтов узла [128, с. 233, с. 1057]. В системах Linux и OpenBSD все поля имеют порядок байтов сети.</p>
       <p>Параметр сокета IP_HDRINCL впервые был представлен в системе 4.3BSD Reno. До этого приложение имело единственную возможность определить свой собственный IP- заголовок в пакетах, отсылаемых на символьный сокет, — использовать заплату ядра (kernel patch), которая была представлена в 1988 году Ван Якобсоном (Van Jacobson) для поддержки программы traceroute. Эта заплата позволяла приложению создавать символьный IP-сокет, определяя протокол как IPPROTO_RAW, что соответствовало значению 255 (это значение является зарезервированным и никогда не должно появляться в поле протокола IP-заголовка).</p>
       <p>Функции, осуществляющие ввод-вывод на символьном сокете, являются одними из простейших функций в ядре. Например, в книге [128, с. 1054–1057] каждая такая функция занимает около 40 строк кода на языке С. Для сравнения: функция ввода TCP содержит около 2000 строк, а функция вывода TCP около 700 строк.</p>
      </cite>
      <p>Приводимое в этой книге описание параметра сокета <code>IP_HDRINCL</code> относится к системе 4.4BSD. В более ранних версиях, таких как Net/2, при использовании данного параметра заполнялось большее количество полей заголовка IP.</p>
      <p>В протоколе IPv4 пользовательский процесс отвечает за вычисление и установку контрольной суммы любого заголовка, следующего за заголовком IPv4. Например, в нашей программе <code>ping</code> (см. листинг 28.10), прежде чем вызывать функцию <code>sendto</code>, мы должны вычислить контрольную сумму ICMPv4 и сохранить ее в заголовке ICMPv4.</p>
     </section>
     <section>
      <title>
       <p>Особенности символьного сокета версии IPv6</p>
      </title>
      <p>Для символьного сокета IPv6 существуют несколько отличий (RFC 3542 [114]).</p>
      <p>&#9632; Все поля в заголовках протоколов, отсылаемых или получаемых на символьном сокете IPv6, должны находиться в сетевом порядке байтов.</p>
      <p>&#9632; В IPv6 не существует параметров, подобных параметру <code>IP_HDRINCL</code> сокета IPv4. Полные пакеты IPv6 (включая дополнительные заголовки) не могут быть прочитаны или записаны через символьный сокет IPv6. Приложения имеют доступ почти ко всем полям заголовка IPv6 и дополнительных заголовков через параметры сокета или вспомогательные данные (см. упражнение 28.1). Если приложению все же необходимо полностью считать или записать IPv6-дейтаграмму, необходимо использовать доступ к канальному уровню (о нем речь пойдет в главе 29).</p>
      <p>&#9632; Как вскоре будет показано, на символьном сокете IPv6 по-другому обрабатываются контрольные суммы.</p>
     </section>
     <section>
      <title>
       <p>Параметр сокета IPV6_CHECKSUM</p>
      </title>
      <p>Для символьного сокета ICMPv6 ядро всегда вычисляет и сохраняет контрольную сумму в заголовке ICMPv6, тогда как для символьного сокета ICMPv4 приложение должно выполнять данную операцию самостоятельно (сравните листинги 28.10 и 28.12). И ICMPv4, и ICMPv6 требуют от отправителя вычисления контрольной суммы, но ICMPv6 включает в свою контрольную сумму псевдозаголовок (понятие псевдозаголовка обсуждается при вычислении контрольной суммы UDP в листинге 29.10). Одно из полей этого псевдозаголовка представляет собой IPv6-адрес отправителя, и обычно приложение оставляет ядру возможность выбирать это значение. Чтобы приложению не нужно было пытаться отыскать этот адрес только для вычисления контрольной суммы, проще разрешить вычислять контрольную сумму ядру.</p>
      <p>Для других символьных сокетов IPv6 (при создании которых третий аргумент функции <code>socket</code> отличен от <code>IPPROTO_ICMPV6</code>) параметр сокета сообщает ядру, вычислять ли контрольную сумму и сохранять ли ее в исходящих пакетах, а также следует ли проверять контрольную сумму в приходящих пакетах. По умолчанию этот параметр выключен, а включается он путем присваивания неотрицательного значения параметра, как в следующем примере:</p>
      <p><code>int offset = 2;</code></p>
      <p><code>if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_CHECKSUM,</code></p>
      <p><code> &amp;offset, sizeof(offset)) &lt; 0)</code></p>
      <p><code><emphasis> обработка ошибки</emphasis></code></p>
      <p>Здесь не только разрешается вычисление контрольной суммы на данном сокете, но и сообщается ядру смещение 16-разрядной контрольной суммы в байтах: в данном примере оно составляет два байта от начала данных приложения. Чтобы отключить данный параметр, ему нужно присвоить значение -1. Если он включен, ядро будет вычислять и сохранять контрольную сумму для исходящих пакетов, посланных на данном сокете, а также проверять контрольную сумму для пакетов, получаемых данным сокетом.</p>
     </section>
    </section>
    <section>
     <title>
      <p>28.4. Ввод через символьный сокет</p>
     </title>
     <section>
      <p>Первый вопрос, на который следует ответить, говоря о символьных сокетах, следующий: какие из полученных IP-дейтаграмм ядро передает символьному сокету? Применяются следующие правила:</p>
      <p>1. Получаемые пакеты UDP и TCP <emphasis>никогда</emphasis> не передаются на символьный сокет. Если процесс хочет считать IP-дейтаграмму, содержащую пакеты UDP или TCP, пакеты должны считываться на канальном уровне, как показано в главе 29.</p>
      <p>2. <emphasis>Большинство</emphasis> ICMP-пакетов передаются на символьный сокет, после того как ядро заканчивает обработку ICMP-сообщения. Беркли-реализации посылают все получаемые ICMP-пакеты на символьный сокет, кроме эхо-запроса, запроса отметки времени и запроса маски адреса [128, с. 302–303]. Эти три типа ICMP-сообщений полностью обрабатываются ядром.</p>
      <p>3. <emphasis>Все</emphasis> IGMP-пакеты передаются на символьный сокет, после того как ядро заканчивает обработку IGMP-сообщения.</p>
      <p>4. <emphasis>Все</emphasis> IP-дейтаграммы с таким значением поля протокола, которое не понимает ядро, передаются на символьный сокет. Для этих пакетов ядро выполняет только минимальную проверку некоторых полей IP-заголовка, таких как версия IP, контрольная сумма IPv4-заголовка, длина заголовка и IP-адрес получателя [128, с. 213–220].</p>
      <p>5. Если дейтаграмма приходит фрагментами, символьному сокету ничего не передается, до тех пор, пока все фрагменты не прибудут и не будут собраны вместе.</p>
      <p>Если у ядра есть IP-дейтаграмма для пересылки символьному сокету, в поисках подходящих сокетов проверяются все символьные сокеты всех процессов. Копия IP-дейтаграммы доставляется <emphasis>каждому</emphasis> подходящему сокету. Для каждого символьного сокета выполняются три перечисленных ниже проверки, и только в том случае, если все три проверки дают положительный результат, дейтаграмма направляется данному сокету.</p>
      <p>1. Если при создании символьного сокета определено ненулевое значение <code>protocol</code> (третий аргумент функции <code>socket</code>), то значение поля протокола полученной дейтаграммы должно совпадать с этим ненулевым значением, иначе дейтаграмма не будет доставлена на данный сокет.</p>
      <p>2. Если локальный IP-адрес связан с символьным сокетом функцией <code>bind</code>, IP-адрес получателя в полученной дейтаграмме должен совпадать с этим адресом, иначе дейтаграмма не посылается данному сокету.</p>
      <p>3. Если для символьного сокета был определен внешний адрес с помощью функции <code>connect</code>, IP-адрес отправителя в полученной дейтаграмме должен совпадать с этим адресом, иначе дейтаграмма не посылается данному сокету.</p>
      <p>Следует отметить, что если символьный сокет создан с нулевым значением аргумента <code>protocol</code> и не вызывается ни функция <code>bind</code>, ни функция <code>connect</code>, то сокет получает копии всех дейтаграмм, которые ядро направляет символьным сокетам.</p>
      <p>Дейтаграммы IPv4 всегда передаются через символьные сокеты целиком, вместе с заголовками. В версии IPv6 символьному сокету передается все, кроме дополнительных заголовков (см., например, рис. 28.4 и 28.6).</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>В заголовке IPv4, передаваемом приложению, для ip_len, ip_off и ip_id установлен порядок байтов узла, а все остальные ноля имеют порядок байтов сети. В системе Linux все поля остаются в сетевом порядке байтов.</p>
       <p>Как уже говорилось, интерфейс символьных сокетов определяется таким образом, чтобы работа со всеми протоколами, в том числе и не обрабатываемыми ядром, осуществлялась одинаково. Поэтому содержимое полей зависит от ядра операционной системы.</p>
       <p>В предыдущем разделе мы отметили, что все ноля символьного сокета IPv6 остаются в сетевом порядке байтов.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Фильтрация по типу сообщений ICMPv6</p>
      </title>
      <p>Символьный сокет ICMPv4 получает большинство сообщений ICMPv4, полученных ядром. Но ICMPv6 является расширением ICMPv4, включающим функциональные возможности ARP и IGMP (см. раздел 2.2). Следовательно, символьный сокет ICMPv6 потенциально может принимать гораздо больше пакетов по сравнению с символьным сокетом ICMPv4. Но большинство приложений, использующих символьные сокеты, заинтересованы только в небольшом подмножестве всех ICMP-сообщений.</p>
      <p>Для уменьшения количества пакетов, передаваемых от ядра к приложению через символьный ICMPv6-сокет, предусмотрен фильтр, связанный с приложением. Фильтр объявляется с типом данных <code>struct icmp6_filter</code>, который определяется в заголовочном файле <code>&lt;netinet/icmp6.h&gt;</code>. Для установки и получения текущего ICMPv6-фильтра для символьного сокета ICMPv6 используются функции <code>setsockopt</code> и <code>getsockopt</code> с аргументом <code>level</code>, равным <code>IPPROTO_ICMPV6</code>, и аргументом <code>optname</code>, равным <code>ICMP6_FILTER</code>.</p>
      <p>Со структурой <code>icmp6_filter</code> работают шесть макросов.</p>
      <p><code>#include &lt;netinet/icmp6.h&gt;</code></p>
      <empty-line/>
      <p><code>void ICMP6_FILTER_SETPASSALL(struct icmp6_filter *<emphasis>filt</emphasis>);</code></p>
      <p><code>void ICMP6_FILTER_SETBLOCKALL(struct icmp6_filter *<emphasis>filt</emphasis>);</code></p>
      <p><code>void ICMP6_FILTER_SETPASS(int <emphasis>msgtype</emphasis>, struct icmp6_filter *<emphasis>filt</emphasis>);</code></p>
      <p><code>void ICMP6_FILTER_SETBLOCK(int <emphasis>msgtype</emphasis>, struct icmp6_filter *<emphasis>filt</emphasis>);</code></p>
      <p><code>int ICMP6_FILTER_WILLPASS(int <emphasis>msgtype</emphasis>, const struct icmp6_filter *<emphasis>filt</emphasis>);</code></p>
      <p><code>int ICMP6_FILTER_WILLBLOCK(int <emphasis>msgtype</emphasis>, const struct icmp6_filter *<emphasis>filt</emphasis>);</code></p>
      <p><code><emphasis>Все возвращают: 1, если фильтр пропускает (блокирует) сообщение данного типа, 0 в противном случае</emphasis></code></p>
      <p>Аргумент <code>filt</code> всех макрокоманд является указателем на переменную <code>icmp6_filter</code>, изменяемую первыми четырьмя макрокомандами и проверяемую последними двумя. Аргумент <code>msgtype</code> является значением в интервале от 0 до 255, определяющим тип ICMP-сообщения.</p>
      <p>Макрокоманда <code>SETPASSALL</code> указывает, что все типы сообщений должны пересылаться приложению, а макрокоманда <code>SETBLOCKALL</code> — что никакие сообщения не должны посылаться приложениям. По умолчанию при создании символьного сокета ICMPv6 подразумевается, что все типы ICMP-сообщений пересылаются приложению.</p>
      <p>Макрокоманда <code>SETPASS</code> определяет конкретный тип сообщений, который должен пересылаться приложению, а макрокоманда <code>SETBLOCK</code> блокирует один конкретный тип сообщений. Макрокоманда <code>WILLPASS</code> возвращает значение 1, если определенный тип пропускается фильтром. Макрокоманда <code>WILLBLOCK</code> возвращает значение 1, если определенный тип блокирован фильтром, и нуль в противном случае.</p>
      <p>В качестве примера рассмотрим приложение, которое будет получать только ICMPv6-извещения маршрутизатора:</p>
      <p><code>struct icmp6_filter myfilt;</code></p>
      <empty-line/>
      <p><code>fd = Socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);</code></p>
      <empty-line/>
      <p><code>ICMP6_FILTER_SETBLOCKALL(&amp;myfilt);</code></p>
      <p><code>ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &amp;myfilt);</code></p>
      <p><code>Setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &amp;myfilt, sizeof(myfilt));</code></p>
      <p>Сначала мы блокируем все типы сообщений (поскольку по умолчанию все типы сообщений пересылаются), а затем разрешаем пересылать только извещения маршрутизатора. Несмотря на то, что мы используем фильтр, приложение должно быть готово к получению всех типов пакетов ICMPv6, потому что любые пакеты ICMPv6, полученные между вызовами <code>socket</code> и <code>setsockopt</code>, будут добавлены в очередь на сокете. Параметр <code>ICMP6_FILTER</code> — лишь средство оптимизации условий функционирования приложения.</p>
     </section>
    </section>
    <section>
     <title>
      <p>28.5. Программа ping</p>
     </title>
     <p>В данном разделе приводится версия программы <code>ping</code>, работающая как с IPv4, так и с IPv6. Вместо того чтобы представить известный доступный исходный код, мы разработали оригинальную программу, и сделано это по двум причинам. Во-первых, свободно доступная программа <code>ping</code> страдает общей болезнью программирования, известной как «ползучий улучшизм» (стремление к постоянным ненужным усложнениям программы в погоне за мелкими улучшениями): она поддерживает 12 различных параметров. Наша цель при исследовании программы <code>ping</code> в том, чтобы понять концепции и методы сетевого программирования и не быть при этом сбитыми с толку ее многочисленными параметрами. Наша версия программы <code>ping</code> поддерживает только один параметр и занимает в пять раз меньше места, чем общедоступная версия. Во-вторых, общедоступная версия работает только с IPv4, а нам хочется показать версию, поддерживающую также и IPv6.</p>
     <p>Действие программы ping предельно просто: по некоторому IP-адресу посылается эхо-запрос ICMP, и этот узел отвечает эхо-ответом ICMP. Оба эти сообщения поддерживаются в обеих версиях — и в IPv4, и в IPv6. На рис. 28.1 приведен формат ICMP-сообщений.</p>
     <image l:href="#img_146.png"/>
     <p><strong>Рис. 28.1</strong>. Формат сообщений эхо-запроса и эхо-ответа ICMPv4 и ICMPv6</p>
     <p>В табл. А.5 и А.6 приведены значения поля <emphasis>тип</emphasis> (<emphasis>type</emphasis>) для этих сообщений и говорится, что значение поля <emphasis>код</emphasis> (<emphasis>code</emphasis>) равно нулю. Далее будет показано, что в поле <emphasis>идентификатор</emphasis> (<emphasis>identifier</emphasis>) указывается идентификатор процесса <code>ping</code>, а значение поля порядковый номер (sequence number) увеличивается на 1 для каждого отправляемого пакета. В поле <emphasis>дополнительные данные</emphasis> (<emphasis>optional data</emphasis>) сохраняется 8-байтовая отметка времени отправки пакета. Правила ICMP-запроса требуют, чтобы <emphasis>идентификатор</emphasis>, <emphasis>порядковый номер</emphasis> и все дополнительные данные возвращались в эхо-ответе. Сохранение отметки времени отправки пакета позволяет вычислить RTT при получении ответа.</p>
     <p>В листинге 28.1<a l:href="#n1" type="note">[1]</a> приведены примеры работы нашей программы. В первом используется версия IPv4, а во втором IPv6. Обратите внимание, что мы установили для нашей программы <code>ping</code> флаг set-user-ID (установка идентификатора пользователя при выполнении), потому что для создания символьного сокета требуются права привилегированного пользователя.</p>
     <p><strong>Листинг 28.1</strong>. Примеры вывода программы ping</p>
     <p><code>freebsd % <strong>ping www.google.com</strong></code></p>
     <p><code>PING www.google.com (216.239.57.99): 56 data bytes</code></p>
     <p><code>64 bytes from 216.239.57.99: seq=0, ttl=53, rtt=5.611 ms</code></p>
     <p><code>64 bytes from 216.239.57.99: seq=1, ttl=53, rtt=5.562 ms</code></p>
     <p><code>64 bytes from 216.239.57 99: seq=2, ttl=53, rtt=5.589 ms</code></p>
     <p><code>64 bytes from 216.239.57.99: seq=3, ttl=53, rtt=5.910 ms</code></p>
     <empty-line/>
     <p><code>freebsd % <strong>ping www.kame.net</strong></code></p>
     <p><code>PING orange.kame.net (2001:200:0:4819:203:47ff:fea5:3085): 56 data bytes</code></p>
     <p><code>64 bytes from 2001:200:0:4819:203:47ff:fea5:3085: seq=0, hlim=52, rtt=422.066 ms</code></p>
     <p><code>64 bytes from 2001:200:0:4819:203:47ff:fea5:3085: seq=1, hlim=52, rtt=417.398 ms</code></p>
     <p><code>64 bytes from 2001:200:0:4819:203:47ff:fea5:3085: seq=2, hlim=52, rtt=416.528 ms</code></p>
     <p><code>64 bytes from 2001:200:0:4819.203.47ff:fea5:3085: seq=3, hlim=52, rtt=429.192 ms</code></p>
     <p>На рис. 28.2 приведен обзор функций, составляющих программу <code>ping</code>.</p>
     <image l:href="#img_147.png"/>
     <p><strong>Рис. 28.2</strong>. Обзор функций программы ping</p>
     <p>Данная программа состоит из двух частей: одна половина читает все, что приходит на символьный сокет, и выводит эхо-ответы ICMP, а другая половина один раз в секунду посылает эхо-запросы ICMP. Вторая половина запускается один раз в секунду сигналом <code>SIGALRM</code>.</p>
     <p>В листинге 28.2 приведен заголовочный файл <code>ping.h</code>, подключаемый во всех файлах программы.</p>
     <p><strong>Листинг 28.2</strong>. Заголовочный файл ping.h</p>
     <p><code>//ping/ping.h</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <p><code> 2 #include &lt;netinet/in_systm.h&gt;</code></p>
     <p><code> 3 #include &lt;netinet/in.h&gt;</code></p>
     <p><code> 4 #include &lt;netinet/ip_icmp.h&gt;</code></p>
     <empty-line/>
     <p><code> 5 #define BUFSIZE 1500</code></p>
     <empty-line/>
     <p><code> 6 /* глобальные переменные */</code></p>
     <p><code> 7 char sendbuf[BUFSIZE];</code></p>
     <empty-line/>
     <p><code> 8 int datalen; /* размер данных после заголовка ICMP */</code></p>
     <p><code> 9 char *host;</code></p>
     <p><code>10 int nsent; /* увеличиваем на 1 для каждого sendto() */</code></p>
     <p><code>11 pid_t pid; /* наш PID */</code></p>
     <p><code>12 int sockfd;</code></p>
     <p><code>13 int verbose;</code></p>
     <empty-line/>
     <p><code>14 /* прототипы функций */</code></p>
     <p><code>15 void init_v6(void);</code></p>
     <p><code>16 void proc_v4(char*, ssize_t, struct msghdr*, struct timeval*);</code></p>
     <p><code>17 void proc_v6(char*, ssize_t., struct msghdr*, struct timeval*);</code></p>
     <p><code>18 void send_v4(void);</code></p>
     <p><code>19 void send_v6(void):</code></p>
     <p><code>20 void readloop(void);</code></p>
     <p><code>21 void sig_alrm(int);</code></p>
     <p><code>22 void tv_sub(struct timeval*, struct timeval*);</code></p>
     <empty-line/>
     <p><code>23 struct proto {</code></p>
     <p><code>24  void (*fproc)(char*, ssize_t, struct msghdr*, struct timeval*);</code></p>
     <p><code>25  void (*fsend)(void);</code></p>
     <p><code>26  void (*finit)(void);</code></p>
     <p><code>27  struct sockaddr *sasend; /* структура sockaddr{} для отправки,</code></p>
     <p><code>                                полученная от getaddrinfo */</code></p>
     <p><code>28  struct sockaddr *sarecv; /* sockaddr{} для получения */</code></p>
     <p><code>29  socklen_t salen; /* длина sockaddr{} */</code></p>
     <p><code>30  int icmpproto; /* значение IPPROTO_xxx для ICMP */</code></p>
     <p><code>31 } *pr;</code></p>
     <empty-line/>
     <p><code>32 #ifdef IPV6</code></p>
     <p><code>33 #include &lt;netinet/ip6.h&gt;</code></p>
     <p><code>34 #include &lt;netinet/icmp6.h&gt;</code></p>
     <empty-line/>
     <p><code>35 #endif</code></p>
     <subtitle>Подключение заголовочных файлов IPv4 и ICMPv4</subtitle>
     <p><code>1-22</code> Подключаются основные заголовочные файлы IPv4 и ICMPv4, определяются некоторые глобальные переменные и прототипы функций.</p>
     <subtitle>Определение структуры proto</subtitle>
     <p><code>23-31</code> Для обработки различий между IPv4 и IPv6 используется структура <code>proto</code>. Данная структура содержит два указателя на функции, два указателя на структуры адреса сокета, размер структуры адреса сокета и значение протокола для ICMP. Глобальный указатель <code>pr</code> будет указывать на одну из этих структур, которая будет инициализироваться для IPv4 или IPv6.</p>
     <subtitle>Подключение заголовочных файлов IPv6 и ICMPv6</subtitle>
     <p><code>32-35</code> Подключаются два заголовочных файла, определяющие структуры и константы IPv6 и ICMPv6 (RFC 3542 [114]).</p>
     <p>Функция <code>main</code> приведена в листинге 28.3.</p>
     <p><strong>Листинг 28.3</strong>. Функция main</p>
     <p><code>//ping/main.c</code></p>
     <p><code> 1 #include "ping.h"</code></p>
     <empty-line/>
     <p><code> 2 struct proto proto_v4 =</code></p>
     <p><code> 3 { proc_v4, send_v4, NULL, NULL, NULL, 0, IPPROTO_ICMP };</code></p>
     <empty-line/>
     <p><code> 4 #ifdef IPV6</code></p>
     <p><code> 5 struct proto proto_v6 =</code></p>
     <p><code> 6 { proc_v6, send_v6, init_v6, NULL, NULL, 0, IPPROTO_ICMPV6 };</code></p>
     <p><code> 7 #endif</code></p>
     <empty-line/>
     <p><code> 8 int datalen = 56; /* размер данных в эхо-запросе ICMP */</code></p>
     <empty-line/>
     <p><code> 9 int</code></p>
     <p><code>10 main(int argc, char **argv)</code></p>
     <p><code>11 {</code></p>
     <p><code>12  int c;</code></p>
     <p><code>13  struct addrinfo *ai;</code></p>
     <p><code>14  char *h;</code></p>
     <empty-line/>
     <p><code>15  opterr = 0; /* отключаем запись сообщений getopt() в stderr */</code></p>
     <p><code>16  while ((с = getopt(argc, argv, "v")) != -1) {</code></p>
     <p><code>17   switch (c) {</code></p>
     <p><code>18   case 'v':</code></p>
     <p><code>19    verbose++;</code></p>
     <p><code>20    break;</code></p>
     <empty-line/>
     <p><code>21   case '?':</code></p>
     <p><code>22    err_quit("unrecognized option %c", c);</code></p>
     <p><code>23   }</code></p>
     <p><code>24  }</code></p>
     <empty-line/>
     <p><code>25  if (optind != argc-1)</code></p>
     <p><code>26   err_quit("usage: ping [ -v ] &lt;hostname&gt;");</code></p>
     <p><code>27  host = argv[optind];</code></p>
     <empty-line/>
     <p><code>28  pid = getpid() &amp; 0xffff; /* поле идентификатора ICMP имеет размер 16 бит */</code></p>
     <p><code>29  Signal(SIGALRM, sig_alrm);</code></p>
     <empty-line/>
     <p><code>30  ai = Host_serv(host, NULL, 0, 0);</code></p>
     <empty-line/>
     <p><code>31  h = Sock_ntop_host(ai-&gt;ai_addr, ai-&gt;ai_addrlen);</code></p>
     <p><code>32  printf("PING %s (%s): %d data bytes\n",</code></p>
     <p><code>33  ai-&gt;ai_canonname ? ai-&gt;ai_canonname : h, h, datalen);</code></p>
     <empty-line/>
     <p><code>34  /* инициализация в соответствии с протоколом */</code></p>
     <p><code>35  if (ai-&gt;ai_family == AF_INET) {</code></p>
     <p><code>36   pr = &amp;proto_v4;</code></p>
     <p><code>37 #ifdef IPV6</code></p>
     <p><code>38  } else if (ai-&gt;ai_family == AF_INET6) {</code></p>
     <p><code>39   pr = &amp;proto_v6;</code></p>
     <p><code>40   if (IN6_IS_ADDR_V4MAPPED(&amp;(((struct sockaddr_in6*)</code></p>
     <p><code>41    ai-&gt;ai_addr)-&gt;sin6_addr)))</code></p>
     <p><code>42    err_quit("cannot ping IPv4-mapped IPv6 address");</code></p>
     <p><code>43 #endif</code></p>
     <p><code>44  } else</code></p>
     <p><code>45   err_quit("unknown address family %d", ai-&gt;ai_family);</code></p>
     <empty-line/>
     <p><code>46  pr-&gt;sasend = ai-&gt;ai_addr;</code></p>
     <p><code>47  pr-&gt;sarecv = Calloc(1, ai-&gt;ai_addrlen);</code></p>
     <p><code>48  pr-&gt;salen = ai-&gt;ai_addrlen;</code></p>
     <empty-line/>
     <p><code>49  readloop();</code></p>
     <empty-line/>
     <p><code>50  exit(0);</code></p>
     <p><code>51 }</code></p>
     <subtitle>Определение структуры proto для IPv4 и IPv6</subtitle>
     <p><code>2-7</code> Определяется структура <code>proto</code> для IPv4 и IPv6. Указатели структуры адреса сокета инициализируются как нулевые, поскольку еще не известно, какая из версий будет использоваться — IPv4 или IPv6.</p>
     <subtitle>Длина дополнительных данных</subtitle>
     <p><code>8</code> Устанавливается количество дополнительных данных (56 байт), которые будут посылаться с эхо-запросом ICMP. При этом полная IPv4-дейтаграмма будет иметь размер 84 байта (20 байт на IPv4-заголовок и 8 байт на ICMP-заголовок), а IPv6-дейтаграмма будет иметь длину 104 байта. Все данные, посылаемые с эхо- запросом, должны быть возвращены в эхо-ответе. Время отправки эхо-запроса будет сохраняться в первых 8 байтах области данных, а затем, при получении эхо- ответа, будет использоваться для вычисления и вывода времени RTT.</p>
     <subtitle>Обработка параметров командной строки</subtitle>
     <p><code>15-24</code> Единственный параметр командной строки, поддерживаемый в нашей версии, это параметр <code>-v</code>, в результате использования которого большинство ICMP-сообщений будут выводиться на консоль. (Мы не выводим эхо-ответы, принадлежащие другой запущенной копии программы <code>ping</code>.) Для сигнала <code>SIGALRM</code> устанавливается обработчик, и мы увидим, что этот сигнал генерируется один раз в секунду и вызывает отправку эхо-запросов ICMP.</p>
     <subtitle>Обработка аргумента, содержащего имя узла</subtitle>
     <p><code>31-48</code> Строка, содержащая имя узла или IP-адрес, является обязательным аргументом и обрабатывается функцией <code>host_serv</code>. Возвращаемая структура <code>addrinfo</code> содержит семейство протоколов — либо <code>AF_INET</code>, либо <code>AF_INET6</code>. Глобальный указатель pr устанавливается на требуемую в конкретной ситуации структуру <code>proto</code>. Также с помощью вызова функции <code>IN6_IS_ADDR_V4MAPPED</code> мы убеждаемся, что адрес IPv6 на самом деле не является адресом IPv4, преобразованным к виду IPv6, поскольку даже если возвращаемый адрес является адресом IPv6, узлу будет отправлен пакет IPv4. (Если такая ситуация возникнет, можно переключиться и использовать IPv4.) Структура адреса сокета, уже размещенная в памяти с помощью функции <code>getaddrinfo</code>, используется для отправки, а другая структура адреса сокета того же размера размещается в памяти для получения.</p>
     <p>Обработка ответов осуществляется функцией <code>readlоор</code>, представленной в листинге 28.4.</p>
     <p><strong>Листинг 28.4</strong>. Функция readloop</p>
     <p><code>//ping/readlоор.c</code></p>
     <p><code> 1 #include "ping.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 readloop(void)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int size;</code></p>
     <p><code> 6  char recvbuf[BUFSIZE];</code></p>
     <p><code> 7  char controlbuf[BUFSIZE];</code></p>
     <p><code> 8  struct msghdr msg;</code></p>
     <p><code> 9  struct iovec iov;</code></p>
     <p><code>10  ssize_t n;</code></p>
     <p><code>11  struct timeval tval;</code></p>
     <empty-line/>
     <p><code>12  sockfd = Socket(pr-&gt;sasend-&gt;sa_family, SOCK_RAW, pr-&gt;icmpproto);</code></p>
     <p><code>13  setuid(getuid()); /* права привилегированного пользователя</code></p>
     <p><code>                         больше не нужны */</code></p>
     <p><code>14  if (pr-&gt;finit)</code></p>
     <p><code>15   (*pr-&gt;finit)();</code></p>
     <empty-line/>
     <p><code>16  size = 60 * 1024; /* setsockopt может завершиться с ошибкой */</code></p>
     <p><code>17  setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &amp;size, sizeof(size));</code></p>
     <empty-line/>
     <p><code>18  sig_alrm(SIGALRM); /* отправка первого пакета */</code></p>
     <p><code>19  iov.iov_base = recvbuf;</code></p>
     <p><code>20  iov.iov_len = sizeof(recvbuf);</code></p>
     <p><code>21  msg.msg_name = pr-&gt;sarecv;</code></p>
     <p><code>22  msg.msg_iov = &amp;iov;</code></p>
     <p><code>23  msg.msg_iovlen = 1;</code></p>
     <p><code>24  msg.msg_control = controlbuf;</code></p>
     <p><code>25  for (;;) {</code></p>
     <p><code>26   msg.msg_namelen = pr-&gt;salen;</code></p>
     <p><code>27   msg.msg_controllen = sizeof(controlbuf);</code></p>
     <p><code>28   n = recvmsg(sockfd, &amp;msg, 0);</code></p>
     <p><code>29   if (n &lt; 0) {</code></p>
     <p><code>30    if (errno == EINTR)</code></p>
     <p><code>31     continue;</code></p>
     <p><code>32    else</code></p>
     <p><code>33     err_sys("recvmsg error");</code></p>
     <p><code>24   }</code></p>
     <empty-line/>
     <p><code>35   Gettimeofday(&amp;tval, NULL);</code></p>
     <p><code>36   (*pr-&gt;fproc)(recvbuf, n, &amp;msg, &amp;tval);</code></p>
     <p><code>37  }</code></p>
     <p><code>38 }</code></p>
     <subtitle>Создание сокета</subtitle>
     <p><code>12-13</code> Создается символьный сокет, соответствующий выбранному протоколу. В вызове функции <code>setuid</code> нашему эффективному идентификатору пользователя присваивается фактический идентификатор пользователя. Для создания символьных сокетов программа должна иметь права привилегированного пользователя, но когда символьный сокет уже создан, от этих прав можно отказаться. Всегда разумнее отказаться от лишних прав, если в них нет необходимости, например на тот случай, если в программе есть скрытая ошибка, которой кто-либо может воспользоваться.</p>
     <subtitle>Выполнение инициализации для протокола</subtitle>
     <p><code>14-15</code> Мы выполняем функцию инициализации для выбранного протокола. Для IPv6 такая функция представлена в листинге 28.7.</p>
     <subtitle>Установка размера приемного буфера сокета</subtitle>
     <p><code>16-17</code> Пытаемся установить размер приемного буфера сокета, равный 61 440 байт (60&#215;1024) — этот размер больше задаваемого по умолчанию. Это делается в расчете на случай, когда пользователь проверяет качество связи с помощью программы <code>ping</code>, обращаясь либо к широковещательному адресу IPv4, либо к групповому адресу. В обоих случаях может быть получено большое количество ответов. Увеличивая размер буфера, мы уменьшаем вероятность того, что приемный буфер переполнится.</p>
     <subtitle>Отправка первого пакета</subtitle>
     <p><code>18</code> Запускаем обработчик сигнала, который, как мы увидим, посылает пакет и создает сигнал <code>SIGALRM</code> один раз в секунду. Обычно обработчик сигналов не запускается напрямую, как у нас, но это можно делать. Обработчик сигналов является обычной функцией языка С, просто в нормальных условиях он асинхронно запускается ядром.</p>
     <subtitle>Подготовка msghdr для recvmsg</subtitle>
     <p><code>19-24</code> Мы записываем значения в неизменяемые поля структур <code>msghdr</code> и <code>iovec</code>, которые будут передаваться функции <code>recvmsg</code>.</p>
     <subtitle>Бесконечный цикл для считывания всех ICMP-сообщений</subtitle>
     <p><code>25-37</code> Основной цикл программы является бесконечным циклом, считывающим все пакеты, возвращаемые на символьный сокет ICMP. Вызывается функция <code>gettimeofday</code> для регистрации времени получения пакета, а затем вызывается соответствующая функция протокола (<code>proc_v4</code> или <code>proc_v6</code>) для обработки ICMP-сообщения.</p>
     <p>В листинге 28.5 приведена функция <code>tv_sub</code>, вычисляющая разность двух структур <code>timeval</code> и сохраняющая результат в первой из них.</p>
     <p><strong>Листинг 28.5</strong>. Функция tv_sub: вычитание двух структур timeval</p>
     <p><code>//lib.tv_sub.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 tv_sub(struct timeval *out, struct timeval *in)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  if ((out-&gt;tv_usec -= in-&gt;tv_usec) &lt; 0) { /* out -= in */</code></p>
     <p><code> 6   --out-&gt;tv_sec;</code></p>
     <p><code> 7   out-&gt;tv_usec += 1000000;</code></p>
     <p><code> 8  }</code></p>
     <p><code> 9  out-&gt;tv_sec -= in-&gt;tv_sec;</code></p>
     <p><code>10 }</code></p>
     <p>В листинге 28.6 приведена функция <code>proc_v4</code>, обрабатывающая все принимаемые сообщения ICMPv4. Можно также обратиться к рис. А.1, на котором изображен формат заголовка IPv4. Кроме того, следует осознавать, что к тому моменту, когда процесс получает на символьном сокете ICMP-сообщение, ядро уже проверило, что основные поля в заголовке IPv4 и в сообщении ICMPv4 действительны [128, с. 214, с. 311].</p>
     <p><strong>Листинг 28.6.</strong> Функция proc_v4: обработка сообщений ICMPv4</p>
     <p><code>//ping/prov_v4.c</code></p>
     <p><code> 1 #include "ping.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 proc_v4(char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int hlen1, icmplen;</code></p>
     <p><code> 6  double rtt;</code></p>
     <p><code> 7  struct ip *ip;</code></p>
     <p><code> 8  struct icmp *icmp;</code></p>
     <p><code> 9  struct timeval *tvsend;</code></p>
     <empty-line/>
     <p><code>10  ip = (struct ip*)ptr; /* начало IP-заголовка */</code></p>
     <p><code>11  hlen1 = ip-&gt;ip_hl &lt;&lt; 2; /* длина IP-заголовка */</code></p>
     <p><code>12  if (ip-&gt;ip_p != IPPROTO_ICMP)</code></p>
     <p><code>13   return; /* не ICMP */</code></p>
     <empty-line/>
     <p><code>14  icmp = (struct icmp*)(ptr + hlen1); /* начало ICMP-заголовка */</code></p>
     <p><code>15  if ((icmplen = len - hlen1) &lt; 8)</code></p>
     <p><code>16   return; /* плохой пакет */</code></p>
     <empty-line/>
     <p><code>17  if (icmp-&gt;icmp_type == ICMP_ECHOREPLY) {</code></p>
     <p><code>18   if (icmp-&gt;icmp_id != pid)</code></p>
     <p><code>19    return; /* это не ответ на наш ECHO_REQUEST */</code></p>
     <p><code>20   if (icmplen &lt; 16)</code></p>
     <p><code>21    return; /* недостаточно данных */</code></p>
     <empty-line/>
     <p><code>22  tvsend = (struct timeval*)icmp-&gt;icmp_data;</code></p>
     <p><code>23  tv_sub(tvrecv, tvsend);</code></p>
     <p><code>24  rtt = tvrecv-&gt;tv_sec * 1000.0 + tvrecv-&gt;tv_usec / 1000.0;</code></p>
     <empty-line/>
     <p><code>25  printf("%d bytes from %s: seq=%u, ttl=%d, rtt=%.3f ms\n",</code></p>
     <p><code>26   icmplen, Sock_ntop_host(pr-&gt;sarecv, pr-&gt;salen),</code></p>
     <p><code>27   icmp-&gt;icmp_seq, ip-&gt;ip_ttl, rtt);</code></p>
     <empty-line/>
     <p><code>28  } else if (verbose) {</code></p>
     <p><code>29   printf(" %d bytes from %s: type = %d, code = %d\n",</code></p>
     <p><code>30   icmplen, Sock_ntop_host(pr-&gt;sarecv, pr-&gt;salen),</code></p>
     <p><code>31   icmp-&gt;icmp_type, icmp-&gt;icmp_code);</code></p>
     <p><code>32  }</code></p>
     <p><code>33 }</code></p>
     <subtitle>Извлечение указателя на ICMP-заголовок</subtitle>
     <p><code>10-16</code> Значение поля длины заголовка IPv4, умноженное на 4, дает размер заголовка IPv4 в байтах. (Следует помнить, что IPv4-заголовок может содержать параметры.) Это позволяет нам установить указатель icmp так, чтобы он указывал на начало ICMP-заголовка. Мы проверяем, относится ли данный пакет к протоколу ICMP и имеется ли в нем достаточно данных для проверки временной отметки, включенной нами в эхо-запрос. На рис. 28.3 приведены различные заголовки, указатели и длины, используемые в коде.</p>
     <image l:href="#img_148.png"/>
     <p><strong>Рис. 28.3</strong>. Заголовки, указатели и длина при обработке ответов ICMPv4</p>
     <subtitle>Проверка эхо-ответа ICMP</subtitle>
     <p><code>17-21</code> Если сообщение является эхо-ответом ICMP, то необходимо проверить поле идентификатора, чтобы выяснить, относится ли этот ответ к посланному данным процессом запросу. Если программа ping запущена на одном узле несколько раз, каждый процесс получает копии всех полученных ICMP-сообщений.</p>
     <p><code>22-27</code> Путем вычитания времени отправки сообщения (содержащегося в части ICMP-ответа, отведенной под дополнительные данные) из текущего времени (на которое указывает аргумент функции <code>tvrecv</code>) вычисляется значение RTT. Время RTT преобразуется из микросекунд в миллисекунды и выводится на экран вместе с полем порядкового номера и полученным значением TTL. Поле порядкового номера позволяет пользователю проследить, не были ли пакеты пропущены, переупорядочены или дублированы, а значение TTL показывает количество транзитных узлов между двумя узлами.</p>
     <subtitle>Вывод всех полученных ICMP-сообщений при включении параметра verbose</subtitle>
     <p><code>28-32</code> Если пользователем указан параметр командной строки <code>-v</code>, также выводятся поля типа и кода из всех других полученных ICMP-сообщений.</p>
     <p>Обработка сообщений ICMPv6 управляется функцией <code>proc_v6</code>, приведенной в листинге 28.8. Она аналогична функции <code>proc_v4</code>, представленной в листинге 28.6. Однако поскольку символьные сокеты IPv6 не передают процессу заголовок IPv6, ограничение на количество транзитных узлов приходится получать в виде вспомогательных данных. Для этого нам приходится подготавливать сокет функцией <code>init_v6</code>, представленной в листинге 28.7.</p>
     <p><strong>Листинг 28.7</strong>. Функция init_v6: подготовка сокета</p>
     <p><code> 1 void</code></p>
     <p><code> 2 init_v6()</code></p>
     <p><code> 3 {</code></p>
     <p><code> 4 #ifdef IPV6</code></p>
     <p><code> 5  int on = 1;</code></p>
     <empty-line/>
     <p><code> 6  if (verbose == 0) {</code></p>
     <p><code> 7   /* установка фильтра, пропускающего только пакеты ICMP6_ECHO_REPLY. если</code></p>
     <p><code>        не включен параметр verbose (вывод всех ICMP-сообщений) */</code></p>
     <p><code> 8   struct icmp6_filter myfilt;</code></p>
     <p><code> 9   ICMP6_FILTER_SETBLOCKALL(&amp;myfilt);</code></p>
     <p><code>10   ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &amp;myfilt);</code></p>
     <p><code>11   setsockopt(sockfd, IPPROTO_IPV6, ICMP6_FILTER, &amp;myfilt,</code></p>
     <p><code>12    sizeof(myfilt));</code></p>
     <p><code>13   /* игнорируем ошибку, потому что фильтр - необязательная оптимизация */</code></p>
     <p><code>14  }</code></p>
     <empty-line/>
     <p><code>15  /* следующую ошибку тоже игнорируем; придется обойтись без вывода</code></p>
     <p><code>       ограничения на количество транзитных узлов */</code></p>
     <p><code>16 #ifdef IPV6_RECVHOPLIMIT</code></p>
     <p><code>17  /* RFC 3542 */</code></p>
     <p><code>18  setsockopt(sockfd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &amp;on, sizeof(on));</code></p>
     <p><code>19 #else</code></p>
     <p><code>20  /* RFC 2292 */</code></p>
     <p><code>21  setsockopt(sockfd, IPPROTO_IPV6, IPV6_HOPLIMIT, &amp;on, sizeof(on));</code></p>
     <p><code>22 #endif</code></p>
     <p><code>23 #endif</code></p>
     <p><code>24 }</code></p>
     <p>Приведенная в листинге 28.8 функция <code>proc_v6</code> обрабатывает входящие пакеты.</p>
     <p><strong>Листинг 28.8.</strong> Функция proc_v6: обработка сообщений ICMPv6</p>
     <p><code>//ping/proc_v6.c</code></p>
     <p><code> 1 #include "ping.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 proc_v6(char *ptr, ssize_t len, struct msghdr *msg, struct timeval* tvrecv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5 #ifdef IPV6</code></p>
     <p><code> 6  double rtt;</code></p>
     <p><code> 7  struct icmp6_hdr *icmp6;</code></p>
     <p><code> 8  struct timeval *tvsend;</code></p>
     <p><code> 9  struct cmsghdr *cmsg;</code></p>
     <p><code>10  int hlim;</code></p>
     <empty-line/>
     <p><code>11  icmp6 = (struct icmp6_hdr*)ptr;</code></p>
     <p><code>12  if (len &lt; 8)</code></p>
     <p><code>13   return; /* плохой пакет */</code></p>
     <empty-line/>
     <p><code>14  if (icmp6-&gt;icmp6_type == ICMP6_ECHO_REPLY) {</code></p>
     <p><code>15   if (icmp6-&gt;icmp6_id != pid)</code></p>
     <p><code>16    return; /* это не ответ на наш ECHO_REQUEST */</code></p>
     <p><code>17   if (len &lt; 16)</code></p>
     <p><code>18    return; /* недостаточно данных */</code></p>
     <empty-line/>
     <p><code>19   tvsend = (struct timeval*)(icmp6 + 1);</code></p>
     <p><code>20   tv_sub(tvrecv, tvsend);</code></p>
     <p><code>21   rtt = tvrecv-&gt;tv_sec * 1000.0 + tvrecv-&gt;tv_usec / 1000.0;</code></p>
     <empty-line/>
     <p><code>22   hlim = -1;</code></p>
     <p><code>23   for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;</code></p>
     <p><code>24    cmsg = CMSG_NXTHDR(msg, cmsg)) {</code></p>
     <p><code>25    if (cmsg-&gt;cmsg_level == IPPROTO_IPV6 &amp;&amp;</code></p>
     <p><code>26     cmsg-&gt;cmsg_type == IPV6_HOPLIMIT) {</code></p>
     <p><code>27     hlim = *(u_int32_t*)CMSG_DATA(cmsg);</code></p>
     <p><code>28     break;</code></p>
     <p><code>29    }</code></p>
     <p><code>30   }</code></p>
     <p><code>31   printf("%d bytes from %s; seq=%u, hlim=",</code></p>
     <p><code>32    len, Sock_ntop__host(pr-&gt;sarecv, pr-&gt;salen), icmp6-&gt;icmp6_seq);</code></p>
     <p><code>33   if (hlim == -1)</code></p>
     <p><code>34    printf("???"); /* отсутствуют вспомогательные данные */</code></p>
     <p><code>35   else</code></p>
     <p><code>36    printf("%d", hlim);</code></p>
     <p><code>37   printf(", rtt=%.3f ms\n", rtt);</code></p>
     <p><code>38  } else if (verbose) {</code></p>
     <p><code>39   printf(" %d bytes from type = %d, code = %d\n",</code></p>
     <p><code>40    len, Sock_ntop_host(pr-&gt;sarecv, pr-&gt;salen);</code></p>
     <p><code>41   icmp6-&gt;icmp6, type, icmp6-&gt;icmp6_code);</code></p>
     <p><code>42  }</code></p>
     <p><code>43 #endif /* IPV6 */</code></p>
     <p><code>44 }</code></p>
     <subtitle>Извлечение указателя на заголовок ICMPv6</subtitle>
     <p><code>11-13</code> Заголовок ICMPv6 возвращается внутри данных при чтении из сокета. (Напомним, что дополнительные заголовки IPv6, если они присутствуют, всегда возвращаются не как стандартные данные, а как вспомогательные.) На рис. 28.4 приведены различные заголовки, указатели и длина, используемые в коде.</p>
     <empty-line/>
     <image l:href="#img_149.png"/>
     <p><strong>Рис. 28.4</strong>. Заголовки, указатели и длина при обработке ответов ICMPv6</p>
     <subtitle>Проверка эхо-ответа ICMP</subtitle>
     <p><code>14-37</code> Если ICMP-сообщение является эхо-ответом, то чтобы убедиться, что ответ предназначен для нас, мы проверяем поле идентификатора. Если это подтверждается, то вычисляется значение RTT, которое затем выводится вместе с порядковым номером и предельным количеством транзитных узлов IPv4. Ограничение на количество транзитных узлов мы получаем из вспомогательных данных <code>IPV6_HOPLIMIT</code>.</p>
     <subtitle>Вывод всех полученных ICMP-сообщений при включении параметра verbose</subtitle>
     <p><code>38-42</code> Если пользователь указал параметр командной строки <code>-v</code>, выводятся также поля типа и кода всех остальных получаемых ICMP-сообщений.</p>
     <p>Обработчиком сигнала SIGALRM является функция <code>sig_alrm</code>, приведенная в листинге 28.9. В листинге 28.4 функция readloop вызывает обработчик сигнала один раз для отправки первого пакета. Эта функция в зависимости от протокола вызывает функцию <code>send_v4</code> или <code>send_v6</code> для отправки эхо-запроса ICMP и далее программирует запуск другого сигнала <code>SIGALRM</code> через 1 с.</p>
     <p><strong>Листинг 28.9</strong>. Функция sig_alrm: обработчик сигнала SIGALRM</p>
     <p><code>//ping/sig_alrm.c</code></p>
     <p><code> 1 #include "ping.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 sig_alrm(int signo)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  (*pr-&gt;fsend)();</code></p>
     <empty-line/>
     <p><code> 6  alarm(1);</code></p>
     <p><code> 7  return;</code></p>
     <p><code> 8 }</code></p>
     <p>Функция <code>send_v4</code>, приведенная в листинге 28.10, строит ICMPv4 сообщение эхо-запроса и записывает его в символьный сокет.</p>
     <p><strong>Листинг 28.10</strong>. Функция send_v4: построение эхо-запроса ICMPv4 и его отправка</p>
     <p><code>//ping/send_v4.c</code></p>
     <p><code> 1 #include "ping.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 send_v4(void)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int len;</code></p>
     <p><code> 6  struct icmp *icmp;</code></p>
     <empty-line/>
     <p><code> 7  icmp = (struct icmp*)sendbuf;</code></p>
     <p><code> 8  icmp-&gt;icmp_type = ICMP_ECHO;</code></p>
     <p><code> 9  icmp-&gt;icmp_code = 0;</code></p>
     <p><code>10  icmp-&gt;icmp_id = pid;</code></p>
     <p><code>11  icmp-&gt;icmp_seq = nsent++;</code></p>
     <p><code>12  memset(icmp-&gt;icmp_data, 0xa5, datalen); /* заполнение по шаблону */</code></p>
     <p><code>13  Gettimeofday((struct timeval*)icmp-&gt;icmp_data, NULL);</code></p>
     <p><code>14  len = 8 + datalen; /* контрольная сумма по заголовку и данным */</code></p>
     <p><code>15  icmp-&gt;icmp_cksum = 0;</code></p>
     <p><code>16  icmp-&gt;icmp_cksum = in_cksum((u_short*)icmp, len);</code></p>
     <p><code>17  Sendto(sockfd, sendbuf, len, 0, pr-&gt;sasend, pr-&gt;salen);</code></p>
     <p><code>18 }</code></p>
     <subtitle>Формирование ICMP-сообщения</subtitle>
     <p><code>7-13</code> ICMPv4 сообщение сформировано. В поле идентификатора установлен идентификатор нашего процесса, а порядковый номер установлен как глобальная переменная <code>nset</code>, которая затем увеличивается на 1 для следующего пакета. Текущее время сохраняется в части данных ICMP-сообщения.</p>
     <subtitle>Вычисление контрольной суммы ICMP</subtitle>
     <p><code>14-16</code> Для вычисления контрольной суммы ICMP значение поля контрольной суммы устанавливается равным 0, затем вызывается функция <code>in_cksum</code>, а результат сохраняется в поле контрольной суммы. Контрольная сумма ICMPv4 вычисляется по ICMPv4-заголовку и всем следующим за ним данным.</p>
     <subtitle>Отправка дейтаграммы</subtitle>
     <p><code>17</code> ICMP-сообщение отправлено на символьный сокет. Поскольку параметр сокета <code>IP_HDRINCL</code> не установлен, ядро составляет заголовок IPv4 и добавляет его в начало нашего буфера.</p>
     <p>Контрольная сумма Интернета является суммой обратных кодов 16-разрядных значений. Если длина данных является нечетным числом, то для вычисления контрольной суммы к данным дописывается один нулевой байт. Перед вычислением контрольной суммы поле контрольной суммы должно быть установлено в 0. Такой алгоритм применяется для вычисления контрольных сумм IPv4, ICMPv4, IGMPv4, ICMPv6, UDP и TCP. В RFC 1071 [12] содержится дополнительная информация и несколько числовых примеров. В разделе 8.7 книги [128] более подробно рассказывается об этом алгоритме, а также приводится более эффективная его реализация. В нашем случае контрольную сумму вычисляет функция <code>in_cksum</code>, приведенная в листинге 28.11.</p>
     <p><strong>Листинг 28.11</strong>. Функция in_cksum: вычисление контрольной суммы Интернета</p>
     <p><code>//libfree/in_cksum.c</code></p>
     <p><code> 1 uint16_t</code></p>
     <p><code> 2 in_cksum(uint16_t *addr, int len)</code></p>
     <p><code> 3 {</code></p>
     <p><code> 4  int nleft = len;</code></p>
     <p><code> 5  uint32_t sum = 0;</code></p>
     <p><code> 6  uint16_t *w = addr;</code></p>
     <p><code> 7  uint16_t answer = 0;</code></p>
     <empty-line/>
     <p><code> 8  /*</code></p>
     <p><code> 9   * Наш алгоритм прост: к 32-разрядному аккумулятору sum мы добавляем</code></p>
     <p><code>10   * 16-разрядные слова, а затем записываем все биты переноса из старших</code></p>
     <p><code>11   * 16 разрядов в младшие 16 разрядов.</code></p>
     <p><code>12   */</code></p>
     <p><code>13  while (nleft &gt; 1) {</code></p>
     <p><code>14   sum += *w++;</code></p>
     <p><code>15   nleft -= 2;</code></p>
     <p><code>16  }</code></p>
     <empty-line/>
     <p><code>17  /* при необходимости добавляем четный байт */</code></p>
     <p><code>18  if (nleft == 1) {</code></p>
     <p><code>19   *(unsigned char*)(&amp;answer) = *(unsigned char*)w;</code></p>
     <p><code>20   sum += answer;</code></p>
     <p><code>21  }</code></p>
     <empty-line/>
     <p><code>22  /* перемещение битов переноса из старших 16 разрядов в младшие */</code></p>
     <p><code>23  sum = (sum &gt;&gt; 16) + (sum &amp; 0xffff); /* добавление старших 16 к младшим */</code></p>
     <p><code>24  sum += (sum &gt;&gt; 16); /* добавление переноса */</code></p>
     <p><code>25  answer = ~sum; /* обрезаем по 16 разрядам */</code></p>
     <p><code>26  return(answer);</code></p>
     <p><code>27 }</code></p>
     <subtitle>Алгоритм вычисления контрольной суммы Интернета</subtitle>
     <p><code>1-27</code> Первый цикл <code>while</code> вычисляет сумму всех 16-битовых значений. Если длина нечетная, то к сумме добавляется конечный байт. Алгоритм, приведенный в листинге 28.11, является простым алгоритмом, подходящим для программы <code>ping</code>, но неудовлетворительным для больших объемов вычислений контрольных сумм, производимых ядром.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Эта функция взята из общедоступной версии программы ping, написанной Майком Мюссом (Mike Muuss).</p>
     </cite>
     <p>Последней функцией нашей программы <code>ping</code> является функция <code>send_v6</code>, приведенная в листинге 28.12, которая формирует и посылает эхо-запросы ICMPv6.</p>
     <p>Функция <code>send_v6</code> аналогична функции <code>send_v4</code>, но обратите внимание, что она не вычисляет контрольную сумму. Как отмечалось ранее, поскольку для вычисления контрольной суммы ICMPv6 используется адрес отправителя из IPv6-заголовка, данная контрольная сумма вычисляется для нас ядром, после того как ядро выяснит адрес отправителя.</p>
     <p><strong>Листинг 28.12</strong>. Функция send_v6: построение и отправка ICMPv6-сообщения эхо-запроса</p>
     <p><code>//ping/send_v6.c</code></p>
     <p><code> 1 #include "ping.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 send_v6()</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5 #ifdef IPV6</code></p>
     <p><code> 6  int len;</code></p>
     <p><code> 7  struct icmp6_hdr *icmp6;</code></p>
     <empty-line/>
     <p><code> 8  icmp6 = (struct icmp6_hdr*)sendbuf,</code></p>
     <p><code> 9  icmp6-&gt;icmp6_type = ICMP6_ECHO_REQUEST;</code></p>
     <p><code>10  icmp6-&gt;icmp6_code = 0;</code></p>
     <p><code>11  icmp6-&gt;icmp6_id = pid;</code></p>
     <p><code>12  icmp6-&gt;icmp6_seq = nsent++;</code></p>
     <p><code>13  memset((icmp6 + 1), 0xa5, datalen); /* заполнение по шаблону */</code></p>
     <p><code>14  Gettimeofday((struct timeval*)(icmp6 + 1), NULL);</code></p>
     <empty-line/>
     <p><code>15  len = 8 + datalen; /* 8-байтовый заголовок ICMPv6 */</code></p>
     <empty-line/>
     <p><code>16  Sendto(sockfd, sendbuf, len, 0, pr-&gt;sasend, pr-&gt;salen);</code></p>
     <p><code>17  /* ядро вычисляет и сохраняет контрольную сумму само */</code></p>
     <p><code>18 #endif /* IPV6 */</code></p>
     <p><code>19 }</code></p>
    </section>
    <section>
     <title>
      <p>28.6. Программа traceroute</p>
     </title>
     <section>
      <p>В этом разделе мы приведем собственную версию программы <code>traceroute</code>. Как и в случае с программой <code>ping</code>, приведенной в предыдущем разделе, мы представляем нашу собственную, а не общедоступную версию. Это делается для того, чтобы во-первых, получить версию, поддерживающую как IPv4, так и IPv6, а во-вторых, не отвлекаться на множество параметров, не относящихся к обсуждению сетевого программирования.</p>
      <p>Программа <code>traceroute</code> позволяет нам проследить путь IP-дейтаграмм от нашего узла до получателя. Ее действие довольно просто, а в главе 8 книги [111] оно детально описано со множеством примеров.</p>
      <p>В версии IPv6 программа <code>traceroute</code> использует поле TTL (в версии IPv4) или поле предельного количества транзитных узлов (называемое также полем ограничения пересылок), а также два типа ICMP-сообщений. Эта программа начинает свою работу с отправки UDP-дейтаграммы получателю, причем полю TTL (ограничения пересылок) присваивается значение 1. Такая дейтаграмма вынуждает первый маршрутизатор отправить ICMP-сообщение об ошибке «Time exceeded in transit» (Превышено время передачи). Затем значение TTL увеличивается на 1 и посылается следующая UDP-дейтаграмма, которая достигает следующего маршрутизатора. Когда UDP-дейтаграмма достигает конечного получателя, необходимо заставить узел вернуть ICMP-ошибку <code>Port unreachable</code> (Порт недоступен). Для этого UDP-дейтаграмма посылается на случайный порт, который (как можно надеяться) не используется на данном узле.</p>
      <p>Ранние версии программы <code>traceroute</code> могли устанавливать поле TTL в заголовке IPv4 только с помощью параметра сокета <code>IP_HDRINCL</code> путем построения своего собственного заголовка. Однако современные системы поддерживают параметр сокета <code>IP_TTL</code>, позволяющий определить значение TTL для исходящих дейтаграмм. (Данный параметр сокета впервые был представлен в выпуске 4.3BSD Reno.) Проще установить данный параметр сокета, чем полностью формировать IPv4-заголовок (хотя в разделе 29.7 показано, как строить собственные заголовки IPv4 и UDP). Параметр сокета IPv6 <code>IPV6_UNICAST_HOPS</code> позволяет контролировать поле предельного количества транзитных узлов (ограничения пересылок) в дейтаграммах IPv6.</p>
      <p>В листинге 28.13 приведен заголовочный файл t<code>race.h</code>, подключаемый ко всем файлам нашей программы.</p>
      <p><strong>Листинг 28.13</strong>. Заголовочный файл trace.h</p>
      <p><code>//traceroute/trace.h</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;netinet/in_systm.h&gt;</code></p>
      <p><code> 3 #include &lt;netinet/ip.h&gt;</code></p>
      <p><code> 4 #include &lt;netinet/ip_icmp.h&gt;</code></p>
      <p><code> 5 #include &lt;netinet/udp.h&gt;</code></p>
      <empty-line/>
      <p><code> 6 #define BUFSIZE 1500</code></p>
      <empty-line/>
      <p><code> 7 struct rec { /* структура данных UDP */</code></p>
      <p><code> 8  u_short rec_seq; /* порядковый номер */</code></p>
      <p><code> 9  u_short rec_ttl; /* значение TTL, с которым пакет отправляется */</code></p>
      <p><code>10  struct timeval rec_tv; /* время отправки пакета */</code></p>
      <p><code>11 };</code></p>
      <empty-line/>
      <p><code>12 /* глобальные переменные */</code></p>
      <p><code>13 char recvbuf[BUFSIZE];</code></p>
      <p><code>14 char sendbuf[BUFSIZE];</code></p>
      <empty-line/>
      <p><code>15 int datalen; /* размер данных в байтах после заголовка ICMP */</code></p>
      <p><code>16 char *host;</code></p>
      <p><code>17 u_short sport, dport;</code></p>
      <p><code>18 int nsent; /* добавляет 1 для каждого вызова sendto() */</code></p>
      <p><code>19 pid_t pid; /* идентификатор нашего процесса PID */</code></p>
      <p><code>20 int probe, nprobes;</code></p>
      <p><code>21 int sendfd, recvfd; /* посылает на сокет UDP. читает на</code></p>
      <p><code>                          символьном сокете ICMP */</code></p>
      <p><code>22 int ttl, max_ttl;</code></p>
      <p><code>23 int verbose;</code></p>
      <empty-line/>
      <p><code>24 /* прототипы функций */</code></p>
      <p><code>25 char *icmpcode_v4(int);</code></p>
      <p><code>26 char *icmpcode_v6(int);</code></p>
      <p><code>27 int recv_v4(int. struct timeval*);</code></p>
      <p><code>28 int recv_v6(int. struct timeval*);</code></p>
      <p><code>29 void sig_alrm(int);</code></p>
      <p><code>30 void traceloop(void);</code></p>
      <p><code>31 void tv_sub(struct timeval*, struct timeval*);</code></p>
      <empty-line/>
      <p><code>32 struct proto {</code></p>
      <p><code>33  char *(*icmpcode)(int);</code></p>
      <p><code>34  int (*recv)(int. struct timeval*);</code></p>
      <p><code>35  struct sockaddr *sasend; /* структура sockaddr{} для отправки.</code></p>
      <p><code>                                получена из getaddrinfo */</code></p>
      <p><code>36  struct sockaddr *sarecv; /* структура sockaddr{} для получения */</code></p>
      <p><code>37  struct sockaddr *salast; /* последняя структура sockaddr{} для получения */</code></p>
      <p><code>38  struct sockaddr *sabind; /* структура sockaddr{} для связывания</code></p>
      <p><code>                                порта отправителя*/</code></p>
      <p><code>39  socklen_t salen; /* длина структур sockaddr{}s */</code></p>
      <p><code>40  int icmpproto; /* значение IPPROTO_xxx для ICMP */</code></p>
      <p><code>41  int ttl level; /* значение аргумента level функции</code></p>
      <p><code>                      setsockopt() для задания TTL */</code></p>
      <p><code>42  int ttloptname; /* значение аргумента name функции</code></p>
      <p><code>                       setsockopt() для задания TTL */</code></p>
      <p><code>43 } *pr;</code></p>
      <empty-line/>
      <p><code>44 #ifdef IPV6</code></p>
      <empty-line/>
      <p><code>45 #include "ip6.h" /* должно быть &lt;netinet/ip6.h&gt; */</code></p>
      <p><code>46 #include "icmp6.h" /* должно быть &lt;netinet/icmp6.h&gt; */</code></p>
      <empty-line/>
      <p><code>47 #endif</code></p>
      <p><code>1-11</code> Подключаются стандартные заголовочные файлы IPv4, определяющие структуры и константы IPv4, ICMPv4 и UDP. Структура <code>rec</code> определяет часть посылаемой UDP-дейтаграммы, содержащую собственно данные, но, как мы увидим дальше, нам никогда не придется исследовать эти данные. Они отсылаются в основном для целей отладки.</p>
      <subtitle>Определение структуры proto</subtitle>
      <p><code>32-43</code> Как и в программе <code>ping</code>, описанной в предыдущем разделе, мы обрабатываем различие между протоколами IPv4 и IPv6, определяя структуру <code>proto</code>, которая содержит указатели на функции, указатели на структуры адресов сокетов и другие константы, различные для двух версий IP. Глобальная переменная <code>pr</code> будет установлена как указатель на одну из этих структур, инициализированных либо для IPv4, либо для IPv6, после того как адрес получателя будет обработан функцией <code>main</code> (поскольку именно адрес получателя определяет, какая версия используется — IPv4 или IPv6).</p>
      <subtitle>Подключение заголовочных файлов IPv6</subtitle>
      <p><code>44-47</code> Подключаются заголовочные файлы, определяющие структуры и константы IPv6 и ICMPv6.</p>
      <p>Функция <code>main</code> приведена в листинге 28.14. Она обрабатывает аргументы командной строки, инициализирует указатель <code>pr</code> либо для IPv4, либо для IPv6 и вызывает нашу функцию <code>traceloop</code>.</p>
      <p><strong>Листинг 28.14</strong>. Функция main программы traceroute</p>
      <p><code>//traceroute/main.c</code></p>
      <p><code> 1 #include "trace.h"</code></p>
      <empty-line/>
      <p><code> 2 struct proto proto_v4 =</code></p>
      <p><code> 3  {icmpcode_v4, recv_v4, NULL, NULL, NULL, NULL, 0,</code></p>
      <p><code> 4 IPPROTO_ICMP, IPPROTO_IP, IP_TTL};</code></p>
      <empty-line/>
      <p><code> 5 #ifdef IPV6</code></p>
      <p><code> 6 struct proto proto_v6 =</code></p>
      <p><code> 7  {icmpcode_v6, recv_v6, NULL, NULL, NULL, NULL, 0,</code></p>
      <p><code> 8 IPPROTO_ICMPV6, IPPROTO_IPV6, IPV6_UNICAST_HOPS};</code></p>
      <p><code> 9 #endif</code></p>
      <empty-line/>
      <p><code>10 int datalen = sizeof(struct rec); /* значения по умолчанию */</code></p>
      <p><code>11 int max_ttl = 30;</code></p>
      <p><code>12 int nprobes = 3;</code></p>
      <p><code>13 u_short dport = 32768 + 666;</code></p>
      <empty-line/>
      <p><code>14 int</code></p>
      <p><code>15 main(int argc, char **argv)</code></p>
      <p><code>16 {</code></p>
      <p><code>17  int c;</code></p>
      <p><code>18  struct addrinfo *ai;</code></p>
      <empty-line/>
      <p><code>19  opterr = 0; /* чтобы функция getopt() не записывала в stderr */</code></p>
      <p><code>20  while ((с = getopt(argc, argv, "m:v")) != -1) {</code></p>
      <p><code>21   switch (c) {</code></p>
      <p><code>22   case 'm':</code></p>
      <p><code>23    if ((max_ttl = atoi(optarg)) &lt;= 1)</code></p>
      <p><code>24     err_quit("invalid -m value");</code></p>
      <p><code>25    break;</code></p>
      <empty-line/>
      <p><code>26   case 'v':</code></p>
      <p><code>27    verbose++;</code></p>
      <p><code>28    break;</code></p>
      <empty-line/>
      <p><code>29   case '?':</code></p>
      <p><code>30    err_quit("unrecognized option: %c", c);</code></p>
      <p><code>31   }</code></p>
      <p><code>32  }</code></p>
      <empty-line/>
      <p><code>33  if (optind != argc - 1)</code></p>
      <p><code>34   err_quit("usage: traceroute [ -m &lt;maxttl&gt; -v ] &lt;hostname&gt;");</code></p>
      <p><code>35  host = argv[optind];</code></p>
      <empty-line/>
      <p><code>36  pid = getpid();</code></p>
      <p><code>37  Signal(SIGALRM, sig_alrm);</code></p>
      <empty-line/>
      <p><code>38  ai = Host_serv(host, NULL, 0, 0);</code></p>
      <empty-line/>
      <p><code>39  printf("traceroute to %s (%s): %d hops max, %d data bytes\n",</code></p>
      <p><code>40   ai-&gt;ai_canonname,</code></p>
      <p><code>41   Sock_ntop_host(ai-&gt;ai_addr, ai-&gt;ai_addrlen);</code></p>
      <p><code>42  max_ttl, datalen);</code></p>
      <empty-line/>
      <p><code>43  /* инициализация в зависимости от протокола */</code></p>
      <p><code>44  if (ai-&gt;ai_family == AF_INET) {</code></p>
      <p><code>45   pr = &amp;proto_v4;</code></p>
      <p><code>46 #ifdef IPV6</code></p>
      <p><code>47  } else if (ai-&gt;ai_family == AF_INET6) {</code></p>
      <p><code>48   pr = &amp;proto_v6;</code></p>
      <p><code>49  if (IN6_IS_ADDR_V4MAPPED</code></p>
      <p><code>50   (&amp;(((struct sockaddr_in6*)ai-&gt;ai_addr)-&gt;sin6_addr)))</code></p>
      <p><code>51   err_quit("cannot traceroute IPv4-mapped IPv6 address");</code></p>
      <p><code>52 #endif</code></p>
      <p><code>53  } else</code></p>
      <p><code>54   err_quit("unknown address family %d", ai-&gt;ai_family);</code></p>
      <p><code>55  pr-&gt;sasend = ai-&gt;ai_addr; /* содержит адрес получателя */</code></p>
      <p><code>56  pr-&gt;sarecv = Calloc(1, ai-&gt;ai_addrlen);</code></p>
      <p><code>57  pr-&gt;salast = Calloc(1, ai-&gt;ai_addrlen);</code></p>
      <p><code>58  pr-&gt;sabind = Calloc(1, ai-&gt;ai_addrlen);</code></p>
      <p><code>59  pr-&gt;salen = ai-&gt;ai_addrlen;</code></p>
      <empty-line/>
      <p><code>60  traceloop();</code></p>
      <empty-line/>
      <p><code>61  exit(0);</code></p>
      <p><code>62 }</code></p>
      <subtitle>Определение структуры proto</subtitle>
      <p><code>2-9</code> Определяются две структуры <code>proto</code>, одна для IPv4 и другая для IPv6, хотя указатели на структуры адреса сокета не размещаются в памяти до окончания выполнения данной функции.</p>
      <subtitle>Установка значений по умолчанию</subtitle>
      <p><code>10-13</code> Максимальное значение поля TTL или поля предельного количества транзитных узлов, используемое в программе, по умолчанию равно 30. Предусмотрен параметр командной строки <code>-m</code>, чтобы пользователь мог поменять это значение. Для каждого значения TTL посылается три пробных пакета, но их количество также может быть изменено с помощью параметра командной строки. Изначально используется номер порта получателя 32 768 + 666, и каждый раз, когда посылается новая дейтаграмма UDP, это значение увеличивается на 1. Мы можем надеяться, что порты с такими номерами не используются на узле получателя в тот момент, когда приходит дейтаграмма, однако гарантии здесь нет.</p>
      <subtitle>Обработка аргументов командной строки</subtitle>
      <p><code>19-37</code> Параметр командной строки -v позволяет вывести все остальные ICMP-сообщения.</p>
      <subtitle>Обработка имени узла или IP-адреса и завершение инициализации</subtitle>
      <p><code>38-58</code> Имя узла получателя или IP-адрес обрабатывается функцией <code>host_serv</code>, возвращающей указатель на структуру <code>addrinfo</code>. В зависимости от типа возвращенного адреса (IPv4 или IPv6) заканчивается инициализация структуры <code>proto</code>, сохраняется указатель в глобальной переменной pr, а также размещается в памяти дополнительная структура адреса сокета соответствующего размера.</p>
      <p>Функция <code>traceloop</code>, приведенная в листинге 28.15, отправляет дейтаграммы и читает вернувшиеся ICMP-сообщения. Это основной цикл программы.</p>
      <p><strong>Листинг 28.15</strong>. Функция traceloop: основной цикл обработки</p>
      <p><code>//traceroute/traceloop.c</code></p>
      <p><code> 1 #include "trace.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 traceloop(void)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int seq, code, done;</code></p>
      <p><code> 6  double rtt;</code></p>
      <p><code> 7  struct rec *rec;</code></p>
      <p><code> 8  struct timeval tvrecv;</code></p>
      <empty-line/>
      <p><code> 9  recvfd = Socket(pr-&gt;sasend-&gt;sa_family, SOCK_RAW, pr-&gt;icmpproto);</code></p>
      <p><code>10  setuid(getuid()); /* права привилегированного пользователя больше</code></p>
      <p><code>                         не нужны */</code></p>
      <empty-line/>
      <p><code>11 #ifdef IPV6</code></p>
      <p><code>12  if (pr-&gt;sasend-&gt;sa_family == AF_INET6 &amp;&amp; verbose == 0) {</code></p>
      <p><code>13   struct icmp6_filter myfilt;</code></p>
      <p><code>14   ICMP6_FILTER_SETBLOCKALL(&amp;myfilt);</code></p>
      <p><code>15   ICMP6_FILTER_SETPASS(ICMP6_TIME_EXCEEDED, &amp;myfilt);</code></p>
      <p><code>16   ICMP6_FILTER_SETPASS(ICMP6_DST_UNREACH, &amp;myfilt);</code></p>
      <p><code>17   setsockopt(recvfd, IPPROTO_IPV6, ICMP6_FILTER,</code></p>
      <p><code>18    &amp;myfilt, sizeof(myfilt));</code></p>
      <p><code>19  }</code></p>
      <p><code>20 #endif</code></p>
      <empty-line/>
      <p><code>21  sendfd = Socket(pr-&gt;sasend-&gt;sa_family, SOCK_DGRAM, 0);</code></p>
      <empty-line/>
      <p><code>22  pr-&gt;sabind-&gt;sa_family = pr-&gt;sasend-&gt;sa_family;</code></p>
      <p><code>23  sport = (getpid() &amp; 0xffff) | 0x8000; /* UDP-порт отправителя # */</code></p>
      <p><code>24  sock_set_port(pr-&gt;sabind, pr-&gt;salen, htons(sport));</code></p>
      <p><code>25  Bind(sendfd, pr-&gt;sabind, pr-&gt;salen);</code></p>
      <empty-line/>
      <p><code>26  sig_alrm(SIGALRM);</code></p>
      <empty-line/>
      <p><code>27  seq = 0;</code></p>
      <p><code>28  done = 0;</code></p>
      <p><code>29  for (ttl = 1; ttl &lt;= max_ttl &amp;&amp; done == 0; ttl++) {</code></p>
      <p><code>30   Setsockopt(sendfd, pr-&gt;ttllevel, pr-&gt;ttloptname, &amp;ttl, sizeof(int));</code></p>
      <p><code>31   bzero(pr-&gt;salast, pr-&gt;salen);</code></p>
      <empty-line/>
      <p><code>32   printf("%2d ", ttl);</code></p>
      <p><code>33   fflush(stdout);</code></p>
      <empty-line/>
      <p><code>34   for (probe = 0; probe &lt; nprobes; probe++) {</code></p>
      <p><code>35    rec = (struct rec*)sendbuf;</code></p>
      <p><code>36    rec-&gt;rec_seq = ++seq;</code></p>
      <p><code>37    rec-&gt;rec_ttl = ttl;</code></p>
      <p><code>38    Gettimeofday(&amp;rec-&gt;rec_tv, NULL);</code></p>
      <empty-line/>
      <p><code>39    sock_set_port(pr-&gt;sasend, pr-&gt;salen, htons(dport + seq));</code></p>
      <p><code>40    Sendto(sendfd, sendbuf, datalen, 0, pr-&gt;sasend, pr-&gt;salen);</code></p>
      <p><code>41    if ((code = (*pr-&gt;recv)(seq, &amp;tvrecv)) == -3)</code></p>
      <p><code>42     printf(" *"); /* тайм-аут, ответа нет */</code></p>
      <p><code>43    else {</code></p>
      <p><code>44     char str[NI_MAXHOST];</code></p>
      <empty-line/>
      <p><code>45     if (sock_cmp_addr(pr-&gt;sarecv, pr-&gt;salast, pr-&gt;salen) != 0) {</code></p>
      <p><code>46      if (getnameinfo(pr-&gt;sarecv, pr-&gt;salen, str, sizeof(str),</code></p>
      <p><code>47       NULL, 0, 0) == 0)</code></p>
      <p><code>48       printf(" %s (%s)", str,</code></p>
      <p><code>49        Sock_ntop_host(pr-&gt;sarecv, pr-&gt;salen));</code></p>
      <p><code>50      else</code></p>
      <p><code>51       printf(" %s", Sock_ntop_host(pr-&gt;sarecv, pr-&gt;salen));</code></p>
      <p><code>52      memcpy(pr-&gt;salast, pr-&gt;sarecv, pr-&gt;salen);</code></p>
      <p><code>53     }</code></p>
      <p><code>54     tv_sub(&amp;tvrecv, &amp;rec-&gt;rec_tv);</code></p>
      <p><code>55     rtt = tvrecv.tv_sec * 1000.0 + tvrecv.tv_usec / 1000.0;</code></p>
      <p><code>56     printf(" %.3f ms", rtt);</code></p>
      <empty-line/>
      <p><code>57     if (code == -1) /* порт получателя недоступен */</code></p>
      <p><code>58      done++;</code></p>
      <p><code>59     else if (code &gt;= 0)</code></p>
      <p><code>60      printf(" (ICMP %s)", (*pr-&gt;icmpcode)(code));</code></p>
      <p><code>61    }</code></p>
      <p><code>62    fflush(stdout);</code></p>
      <p><code>63   }</code></p>
      <p><code>64   printf("\n");</code></p>
      <p><code>65  }</code></p>
      <p><code>66 }</code></p>
      <subtitle>Создание двух сокетов</subtitle>
      <p><code>9-10</code> Нам необходимо два сокета: символьный сокет, на котором мы читаем все вернувшиеся ICMP-сообщения, и UDP-сокет, на который мы посылаем пробные пакеты с увеличивающимся значением поля TTL. После создания символьного сокета мы заменяем наш эффективный идентификатор пользователя на фактический, поскольку более нам не понадобятся права привилегированного пользователя.</p>
      <subtitle>Установка фильтра ICMPv6</subtitle>
      <p><code>11-20</code> Если мы отслеживаем маршрут к адресату IPv6 и параметр командной строки -V указан не был, можно установить фильтр, который будет блокировать все ICMP-сообщения, за исключением тех, которые нас интересуют: «Time exceeded» и «Destination unreachable». Это сократит число пакетов, получаемых на данном сокете.</p>
      <subtitle>Связывание порта отправителя UDP-сокета</subtitle>
      <p><code>21-25</code> Осуществляется связывание порта отправителя с UDP-сокетом, который используется для отправки пакетов. При этом берется 16 младших битов из идентификатора нашего процесса, а старшему биту присваивается 1. Поскольку несколько копий программы <code>traceroute</code> могут работать одновременно, нам необходима возможность определить, относится ли поступившее ICMP-сообщение к одной из наших дейтаграмм или оно пришло в ответ на дейтаграмму, посланную другой копией программы. Мы используем порт отправителя в UDP-заголовке для определения отправляющего процесса, поскольку возвращаемое ICMP-сообщение всегда содержит UDP-заголовок дейтаграммы, вызвавшей ICMP-ошибку.</p>
      <subtitle>Установка обработчика сигнала SIGALRM</subtitle>
      <p><code>26</code> Мы устанавливаем нашу функцию <code>sig_alrm</code> в качестве обработчика сигнала <code>SIGALRM</code>, поскольку каждый раз, когда мы посылаем UDP-дейтаграмму, мы ждем 3 с, прежде чем послать следующий пробный пакет.</p>
      <subtitle>Основной цикл: установка TTL или предельного количества транзитных узлов и отправка трех пробных пакетов</subtitle>
      <p><code>27-38</code> Основным циклом функции является двойной вложенный цикл <code>for</code>. Внешний цикл стартует со значения TTL или предельного количества транзитных узлов, равного 1, и увеличивает это значение на 1, в то время как внутренний цикл посылает три пробных пакета (UDP-дейтаграммы) получателю. Каждый раз, когда изменяется значение TTL, мы вызываем <code>setsockopt</code> для установки нового значения, используя параметр сокета <code>IP_TTL</code> или <code>IPV6_UNICAST_HOPS</code>.</p>
      <p>Каждый раз во внешнем цикле мы инициализируем нулем структуру адреса сокета, на которую указывает <code>salast</code>. Данная структура будет сравниваться со структурой адреса сокета, возвращенной функцией <code>recvfrom</code>, при считывании ICMP-сообщения, и если эти две структуры будут различны, на экран будет выведен IP-адрес из новой структуры. При использовании этого метода для каждого значения TTL выводится IP-адрес, соответствующий первому пробному пакету, а если для данного значения TTL IP-адрес изменится (то есть во время работы программы изменится маршрут), то будет выведен новый IP-адрес.</p>
      <subtitle>Установка порта получателя и отправка UDP-дейтаграммы</subtitle>
      <p><code>39-40</code> Каждый раз, когда посылается пробный пакет, порт получателя в структуре адреса сокета <code>sasend</code> меняется с помощью вызова функции <code>sock_set_port</code>. Причина, по которой порт меняется для каждого пробного пакета, заключается в том, что когда мы достигаем конечного получателя, все три пробных пакета посылаются на разные порты, чтобы увеличить шансы на обращение к неиспользуемому порту. Функция <code>sendto</code> посылает UDP-дейтаграмму.</p>
      <subtitle>Чтение ICMP-сообщения</subtitle>
      <p><code>41-42</code> Одна из функций <code>recv_v4</code> или <code>recv_v6</code> вызывает функцию recvfrom для чтения и обработки вернувшихся ICMP-сообщений. Обе эти функции возвращают значение -3 в случае истечения времени ожидания (сообщая, что следует послать следующий пробный пакет, если для данного значения TTL еще не посланы все три пакета), значение -2, если приходит ICMP-ошибка о превышении времени передачи, и значение -1, если получена ICMP-ошибка «Port unreachable» (Порт недоступен), то есть достигнут конечный получатель. Если же приходит какая-либо другая ICMP-ошибка недоступности получателя («Destination unreachable»), эти функции возвращают неотрицательный ICMP-код.</p>
      <subtitle>Вывод ответа</subtitle>
      <p><code>43-63</code> Как отмечалось выше, в случае первого ответа для данного значения TTL, а также если для данного TTL меняется IP-адрес узла, посылающего ICMP-сообщение, выводится имя узла и IP-адрес (или только IP-адрес, если вызов функции <code>getnameinfo</code> не возвращает имени узла). Время RTT вычисляется как разность между временем отправки пробного пакета и временем возвращения и вывода ICMP-сообщения.</p>
      <p>Функция <code>recv_v4</code> приведена в листинге 28.16.</p>
      <p><strong>Листинг 28.16</strong>. Функция recv_v4: чтение и обработка сообщений ICMPv4</p>
      <p><code>//traceroute/recv_v4</code></p>
      <p><code> 1 #include "trace.h"</code></p>
      <empty-line/>
      <p><code> 2 extern int gotalarm;</code></p>
      <empty-line/>
      <p><code> 3 /* Возвращает:</code></p>
      <p><code> 4  * -3 при тайм-ауте</code></p>
      <p><code> 5  * -2 при сообщении ICMP time exceeded in transit (продолжаем поиск)</code></p>
      <p><code> 6  * -1 при сообщении ICMP port unreachable (цель достигнута)</code></p>
      <p><code> 7  * неотрицательные значения соответствуют всем прочим ошибкам ICMP</code></p>
      <p><code> 8  */</code></p>
      <empty-line/>
      <p><code> 9 int</code></p>
      <p><code>10 recv_v4(int seq, struct timeval *tv)</code></p>
      <p><code>11 {</code></p>
      <p><code>12  int hlen1, hlen2, icmplen, ret;</code></p>
      <p><code>13  socklen_t len;</code></p>
      <p><code>14  ssize_t n;</code></p>
      <p><code>15  struct ip *ip, *hip;</code></p>
      <p><code>16  struct icmp *icmp;</code></p>
      <p><code>17  struct udphdr *udp;</code></p>
      <empty-line/>
      <p><code>18  gotalarm = 0;</code></p>
      <p><code>19  alarm(3);</code></p>
      <p><code>20  for (;;) {</code></p>
      <p><code>21   if (gotalarm)</code></p>
      <p><code>22    return(-3); /* истек таймер */</code></p>
      <p><code>23   len = pr-&gt;salen;</code></p>
      <p><code>24   n = recvfrom(recvfd, recvbuf, sizeof(recvbuf), 0, pr-&gt;sarecv, &amp;len);</code></p>
      <p><code>25   if (n &lt; 0) {</code></p>
      <p><code>26    if (errno == EINTR)</code></p>
      <p><code>27     continue;</code></p>
      <p><code>28    else</code></p>
      <p><code>29     err_sys("recvfrom error");</code></p>
      <p><code>30   }</code></p>
      <empty-line/>
      <p><code>31   ip = (struct ip*)recvbuf; /* начало IP-заголовка */</code></p>
      <p><code>32   hlenl = ip-&gt;ip_hl &lt;&lt; 2; /* длина IP-заголовка */</code></p>
      <empty-line/>
      <p><code>33   icmp = (struct icmp*)(recvbuf + hlen1); /* начало ICMP-заголовка */</code></p>
      <p><code>34   if ((icmplen = n - hlen1) &lt; 8)</code></p>
      <p><code>35    continue; /* недостаточно данных для проверки ICMP-заголовка */</code></p>
      <empty-line/>
      <p><code>36   if (icmp-&gt;icmp_type == ICMP_TIMXCEED &amp;&amp;</code></p>
      <p><code>37    icmp-&gt;icmp_code == ICMP_TIMXCEED_INTRANS) {</code></p>
      <p><code>38    if (icmplen &lt; 8 + sizeof(struct ip))</code></p>
      <p><code>39     continue; /* недостаточно данных для проверки внутреннего IP */</code></p>
      <empty-line/>
      <p><code>40    hip = (struct ip*)(recvbuf + hlen1 + 8);</code></p>
      <p><code>41    hlen2 = hip-&gt;ip_hl &lt;&lt; 2;</code></p>
      <p><code>42    if (icmplen &lt; 8 + hlen2 + 4)</code></p>
      <p><code>43     continue; /* недостаточно данных для проверки UDP-порта */</code></p>
      <empty-line/>
      <p><code>44    udp = (struct udphdr*)(recvbuf + hlen1 + 8 + hlen2);</code></p>
      <p><code>45    if (hip-&gt;ip_p == IPPROTO_UDP &amp;&amp;</code></p>
      <p><code>46     udp-&gt;uh_sport == htons(sport) &amp;&amp;</code></p>
      <p><code>47     udp-&gt;uh_dport == htons(dport + seq)) {</code></p>
      <p><code>48     ret = -2; /* ответил промежуточный маршрутизатор */</code></p>
      <p><code>49     break;</code></p>
      <p><code>50    }</code></p>
      <empty-line/>
      <p><code>51   } else if (icmp-&gt;icmp_type == ICMP_UNREACH) {</code></p>
      <p><code>52    if (icmplen &lt; 8 + sizeof(struct ip))</code></p>
      <p><code>53     continue; /* недостаточно данных для проверки внутреннего IP */</code></p>
      <empty-line/>
      <p><code>54    hip = (struct ip*)(recvbuf + hlen1 + 8);</code></p>
      <p><code>55    hlen2 = hip-&gt;ip_hl &lt;&lt; 2;</code></p>
      <p><code>56    if (icmplen &lt; 8 + hlen2 + 4)</code></p>
      <p><code>57     continue; /* недостаточно данных для проверки UDP-портов */</code></p>
      <empty-line/>
      <p><code>58    udp = (struct udphdr*)(recvbuf + hlen1 + 8 + hlen2);</code></p>
      <p><code>59    if (hip-&gt;ip_p == IPPROTO_UDP &amp;&amp;</code></p>
      <p><code>60     udp-&gt;uh_sport == htons(sport) &amp;&amp;</code></p>
      <p><code>61     udp-&gt;uh_dport == htons(dport + seq)) {</code></p>
      <p><code>62     if (icmp-&gt;icmp_code == ICMP_UNREACH_PORT)</code></p>
      <p><code>63      ret = -1; /* цель достигнута */</code></p>
      <p><code>64     else</code></p>
      <p><code>65      ret = icmp-&gt;icmp_code; /* 0, 1, 2, ... */</code></p>
      <p><code>66     break;</code></p>
      <p><code>67    }</code></p>
      <p><code>68   }</code></p>
      <p><code>69   if (verbose) {</code></p>
      <p><code>70    printf(" (from %s: type = %d, code - %d)\n",</code></p>
      <p><code>71     Sock_ntop_host(pr-&gt;sarecv, pr-&gt;salen),</code></p>
      <p><code>72     icmp-&gt;icmp_type, icmp-&gt;icmp_code);</code></p>
      <p><code>73   }</code></p>
      <p><code>74   /* другая ICMP-ошибка, нужно снова вызвать recvfrom() */</code></p>
      <p><code>75  }</code></p>
      <p><code>76  alarm(0); /* отключаем таймер */</code></p>
      <p><code>77  Gettimeofday(tv, NULL); /* время получения пакета */</code></p>
      <p><code>78  return(ret);</code></p>
      <p><code>79 }</code></p>
      <subtitle>Установка таймера и прочтение каждого ICMP-сообщения</subtitle>
      <p><code>17-27</code> Таймер устанавливается на 3 с, и функция входит в цикл, вызывающий <code>recvfrom</code>, считывая каждое ICMPv4-сообщение, возвращаемое на символьный сокет.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Эта функция не создает ситуации гонок, описанной в разделе 20.5, благодаря использованию глобального флага.</p>
      </cite>
      <subtitle>Извлечение указателя на ICMP-заголовок</subtitle>
      <p><code>31-35</code> Указатель <code>iр</code> указывает на начало IPv4-заголовка (напомним, что операция чтения на символьном сокете всегда возвращает IP-заголовок), а указатель <code>icmp</code> указывает на начало ICMP-заголовка. На рис. 28.5 показаны различные заголовки, указатели и длины, используемые в данном коде.</p>
      <image l:href="#img_150.png"/>
      <p><strong>Рис. 28.5</strong>. Заголовки, указатели и длины при обработке ошибки</p>
      <subtitle>Обработка ICMP-сообщения о превышении времени передачи</subtitle>
      <p><code>36-50</code> Если ICMP-сообщение является сообщением «Time exceeded in transit» (Превышено время передачи), вероятно, оно является ответом на один из наших пробных пакетов. Указатель <code>hip</code> указывает на заголовок IPv4, который возвращается в ICMP-сообщении и следует сразу за 8-байтовым ICMP-заголовком. Указатель <code>udp</code> указывает на следующий далее UDP-заголовок. Если ICMP-сообщение было сгенерировано UDP-дейтаграммой и если порты отправителя и получателя этой дейтаграммы совпадают с теми значениями, которые мы посылали, то тогда это ответ от промежуточного маршрутизатора на наш пробный пакет.</p>
      <subtitle>Обработка ICMP-сообщения о недоступности порта</subtitle>
      <p><code>51-68</code> Если ICMP-сообщение является сообщением «Destination unreachable» (Получатель недоступен), тогда, чтобы узнать, является ли это сообщение ответом на наш пробный пакет, мы смотрим на UDP-заголовок, возвращенный в данном ICMP-сообщении. Если это так и код означает сообщение «Port unreachable» (Порт недоступен), то возвращается значение -1, поскольку достигнут конечный получатель. Если же ICMP-сообщение является ответом на один из наших пробных пакетов, но не является сообщением типа «Destination unreachable» (Получатель недоступен), то тогда возвращается значение ICMP-кода. Обычным примером такого случая является ситуация, когда брандмауэр возвращает какой-либо другой код недоступности для получателя, на который посылается пробный пакет.</p>
      <subtitle>Обработка других ICMP-сообщений</subtitle>
      <p><code>69-73</code> Все остальные ICMP-сообщения выводятся, если был задан параметр <code>-v</code>.</p>
      <p>Следующая функция, recv_v6, приведена в листинге 28.18 и является IPv6-вepсией ранее описанной функции для IPv4. Эта функция почти идентична функции <code>recv_v4</code>, за исключением различий в именах констант и элементов структур. Кроме того, размер заголовка IPv6 является фиксированным и составляет 40 байт, в то время как для получения IP-параметров в заголовке IPv4 необходимо получить поле длины заголовка и умножить его на 4. На рис. 28.6 приведены различные заголовки, указатели и длины, используемые в коде.</p>
      <image l:href="#img_151.png"/>
      <p><strong>Рис. 28.6</strong>. Заголовки, указатели и длины, используемые при обработке ошибки ICMPv6</p>
      <p>Мы определяем две функции <code>icmpcode_v4</code> и <code>icmpcode_v6</code>, которые можно вызывать в конце функции <code>traceloop</code> для вывода строки описания, соответствующей ICMP-ошибке недоступности получателя. В листинге 28.19 приведена IPv6-функция. IPv4-функция аналогична, хотя и длиннее, поскольку существует большее количество ICMPv4-кодов недоступности получателя (см. табл. А.5).</p>
      <p>Последней функцией в нашей программе <code>traceroute</code> является обработчик сигнала <code>SIGALRM</code> — функция <code>sig_alrm</code>, приведенная в листинге 28.17. Эта функция лишь возвращает ошибку <code>EINTR</code> из функции <code>recvfrom</code>, как в случае функции <code>recv_v4</code>, так и в случае <code>recv_v6</code>.</p>
      <p><strong>Листинг 28.17</strong>. Функция sig_alrm</p>
      <p><code>//traceroutе/sig_alrm.c</code></p>
      <p><code>1 #include "trace.h"</code></p>
      <empty-line/>
      <p><code>2 int gotalarm;</code></p>
      <p><code>3 void</code></p>
      <p><code>4 sig_alrm(int signo)</code></p>
      <p><code>5 {</code></p>
      <p><code>6  gotalarm = 1; /* установка флага, оповещающего о сигнале */</code></p>
      <p><code>7  return; /* прерывается работа функции recvfrom() */</code></p>
      <p><code>8 }</code></p>
      <p><strong>Листинг 28.18</strong>. Функция recv_v6: чтение и обработка сообщений ICMPv6</p>
      <p><code>//traceroute/recv_v6</code></p>
      <p><code> 1 #include "trace.h"</code></p>
      <empty-line/>
      <p><code> 2 extern int gotalarm;</code></p>
      <empty-line/>
      <p><code> 3 /*</code></p>
      <p><code> 4  * Возвращает; -3 при тайм-ауте</code></p>
      <p><code> 5  * -2 для сообщения ICMP time exceeded in transit (продолжаем поиск</code></p>
      <p><code>      маршрута)</code></p>
      <p><code> 6  * -1 для сообщения ICMP port unreachable (цель достигнута)</code></p>
      <p><code> 7  * неотрицательные значения соответствуют всем прочим ICMP-сообщениям</code></p>
      <p><code> 8  */</code></p>
      <empty-line/>
      <p><code> 9 int</code></p>
      <p><code>10 recv_v6(int seq, struct timeval *tv)</code></p>
      <p><code>11 {</code></p>
      <p><code>12 #ifdef IPV6</code></p>
      <p><code>13  int hlen2, icmp6len, ret;</code></p>
      <p><code>14  ssize_t n;</code></p>
      <p><code>15  socklen_t len;</code></p>
      <p><code>16  struct ip6_hdr *hip6;</code></p>
      <p><code>17  struct icmp6_hdr *icmp6;</code></p>
      <p><code>18  struct udphdr *udp;</code></p>
      <empty-line/>
      <p><code>19  gotalarm = 0;</code></p>
      <p><code>20  alarm(3);</code></p>
      <p><code>21  for (;;) {</code></p>
      <p><code>22   if (gotalarm)</code></p>
      <p><code>23    return(-3); /* истек таймер */</code></p>
      <p><code>24   len = pr-&gt;salen;</code></p>
      <p><code>25   n = recvfrom(recvfd, recvbuf, sizeof(recvbuf), 0, pr-&gt;sarecv, &amp;len);</code></p>
      <p><code>26   if (n &lt; 0) {</code></p>
      <p><code>27    if (errno == EINTR)</code></p>
      <p><code>28     continue;</code></p>
      <p><code>29    else</code></p>
      <p><code>30     err_sys("recvfrom error");</code></p>
      <p><code>31   }</code></p>
      <empty-line/>
      <p><code>32   icmp6 = (struct icmp6_hdr*)recvbuf; /* ICMP-заголовок */</code></p>
      <p><code>33   if ((icmp6len = n) &lt; 8)</code></p>
      <p><code>34    continue; /* недостаточно для проверки ICMP-заголовка */</code></p>
      <empty-line/>
      <p><code>35   if (icmp6-&gt;icmp6_type == ICMP6_TIME_EXCEEDED &amp;&amp;</code></p>
      <p><code>36    icmp6-&gt;icmp6_code == ICMP6_TIME_EXCEED_TRANSIT) {</code></p>
      <p><code>37    if (icmp6len &lt; 8 + sizeof(struct ip6_hdr) + 4)</code></p>
      <p><code>38     continue; /* недостаточно для проверки внутреннего заголовка */</code></p>
      <empty-line/>
      <p><code>39    hip6 = (struct ip6_hdr*)(recvbuf + 8);</code></p>
      <p><code>40    hlen2 = sizeof(struct ip6_hdr);</code></p>
      <p><code>41    udp = (struct udphdr*)(recvbuf + 8 + hlen2);</code></p>
      <p><code>42    if (hip6-&gt;ip6_nxt == IPPROTO_UDP &amp;&amp;</code></p>
      <p><code>43     udp-&gt;uh_sport == htons(sport) &amp;&amp;</code></p>
      <p><code>44     udp-&gt;uh_dport == htons(dport + seq))</code></p>
      <p><code>45     ret = -2; /* ответил промежуточный маршрутизатор */</code></p>
      <p><code>46    break;</code></p>
      <empty-line/>
      <p><code>47   } else if (icmp6-&gt;icmp6_type == ICMP6_DST_UNREACH) {</code></p>
      <p><code>48    if (icmp6len &lt; 8 + sizeof(struct ip6_hdr) + 4)</code></p>
      <p><code>49     continue; /* недостаточно для проверки внутреннего заголовка */</code></p>
      <empty-line/>
      <p><code>50    hip6 = (struct ip6_hdr*)(recvbuf + 8);</code></p>
      <p><code>51    hlen2 = sizeof(struct ip6_hdr);</code></p>
      <p><code>52    udp = (struct udphdr*)(recvbuf + 8 + hlen2);</code></p>
      <p><code>53    if (hip6-&gt;ip6_nxt == IPPROTO_UDP &amp;&amp;</code></p>
      <p><code>54     udp-&gt;uh_sport == htons(sport) &amp;&amp;</code></p>
      <p><code>55     udp-&gt;uh_dport == htons(dport + seq)) {</code></p>
      <p><code>56     if (icmp6-&gt;icmp6_code == ICMP6_DST_UNREACH_NOPORT)</code></p>
      <p><code>57      ret = -1; /* цель достигнута */</code></p>
      <p><code>58     else</code></p>
      <p><code>59      ret = icmp6-&gt;icmp6_code; /* 0, 1, 2, ... */</code></p>
      <p><code>60     break;</code></p>
      <p><code>61    }</code></p>
      <p><code>62   } else if (verbose) {</code></p>
      <p><code>63    printf(" (from %s: type = %d, code = %d)\n",</code></p>
      <p><code>64     Sock_ntop_host(pr-&gt;sarecv, pr-&gt;salen);</code></p>
      <p><code>65    icmp6-&gt;icmp6_type, icmp6-&gt;icmp6_code);</code></p>
      <p><code>66   }</code></p>
      <p><code>67   /* другая ICMP-ошибка. нужно вызвать recvfrom() */</code></p>
      <p><code>68  }</code></p>
      <p><code>69  alarm(0); /* отключаем таймер */</code></p>
      <p><code>70  Gettimeofday(tv, NULL); /* get time of packet arrival */</code></p>
      <p><code>71  return(ret);</code></p>
      <p><code>72 #endif</code></p>
      <p><code>73 }</code></p>
      <p><strong>Листинг 28.19</strong>. Возвращение строки, соответствующей коду недоступности ICMPv6</p>
      <p><code>//traceroute/icmpcode_v6.c</code></p>
      <p><code> 1 #include "trace.h"</code></p>
      <empty-line/>
      <p><code> 2 const char *</code></p>
      <p><code> 3 icmpcode_v6(int code)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5 #ifdef IPV6</code></p>
      <p><code> 6  static char errbuf[100];</code></p>
      <p><code> 7  switch (code) {</code></p>
      <p><code> 8  case ICMP6_DST_UNREACH_NOROUTE:</code></p>
      <p><code> 9   return("no route to host");</code></p>
      <p><code>10  case ICMP6_DST_UNREACH_ADMIN:</code></p>
      <p><code>11   return("administratively prohibited");</code></p>
      <p><code>12  case ICMP6_DST_UNREACH_NOTNEIGHBOR:</code></p>
      <p><code>13   return("not a neighbor");</code></p>
      <p><code>14  case ICMP6_DST_UNREACH_ADDR:</code></p>
      <p><code>15   return("address unreachable");</code></p>
      <p><code>15  case ICMP6_DST_UNREACH_NOPORT:</code></p>
      <p><code>16   return("port unreachable");</code></p>
      <p><code>17  default:</code></p>
      <p><code>18   sprintf(errbuf, "[unknown code %d]",. code);</code></p>
      <p><code>19   return errbuf;</code></p>
      <p><code>20  }</code></p>
      <p><code>21 #endif</code></p>
      <p><code>22 }</code></p>
     </section>
     <section>
      <title>
       <p>Пример</p>
      </title>
      <p>Сначала приведем пример с Ipv4:</p>
      <p><code>freebsd % <strong>traceroute www.unpbook.com</strong></code></p>
      <p><code>traceroute to www.unpbook.com (206.168.112.219): 30 hops max. 24 data bytes</code></p>
      <p><code>1 12.106.32.1 (12.106.32.1) 0.799 ms 0.719 ms 0.540 ms</code></p>
      <p><code>2 12.124.47.113 (12.124.47.113) 1.758 ms 1.760 ms 1.839 ms</code></p>
      <p><code>3 gbr2-p27.sffca.ip.att.net (12.123.195.38) 2.744 ms 2.575 ms 2.648 ms</code></p>
      <p><code>4 tbr2-p012701.sffca.ip.att.net (12.122.11.85) 3.770 ms 3.689 ms 3.848 ms</code></p>
      <p><code>5 gbr3-p50.dvmco.ip.att.net (12.122.2.66) 26.202 ms 26.242 ms 26.102 ms</code></p>
      <p><code>6 gbr2-p20.dvmco.ip.att.net (12.122.5.26) 26 255 ms 26.194 ms 26.470 ms</code></p>
      <p><code>7 gar2-p370.dvmco.ip.att.net (12.123.36.141) 26.443 ms 26.310 ms 26.427 ms</code></p>
      <p><code>8 att-46.den.internap.ip.att.net (12.124.158.58) 26.962 ms 27.130 ms 27.279 ms</code></p>
      <p><code>9 border10 ge3-0-bbnet2.den.pnap.net (216.52.40.79) 27.285 ms 27 293 ms 26.860 ms</code></p>
      <p><code>10 coop-2.border10.den.pnap.net (216 52.42.118) 28.721 ms 28.991 ms 30.077 ms</code></p>
      <p><code>11 199.45.130.33 (199.45.130.33) 29.095 ms 29.055 ms 29 378 ms</code></p>
      <p><code>12 border-to-141-netrack.boulder.со.coop.net (207.174.144.178) 30.875 ms 29.747 ms 30.142 ms</code></p>
      <p><code>13 linux.unpbook.com (206.168.112.219) 31.713 ms 31.573 ms 33.952 ms</code></p>
      <p>Ниже приведен пример с IPv6. Для лучшей читаемости длинные строки разбиты.</p>
      <p><code>freebsd % <strong>traceroute www.kame.net</strong></code></p>
      <p><code>traceroute to orange.kame.net (2001:200:0:4819:203:47ff:fea5:3085): 30 hops max, 24 data bytes</code></p>
      <p><code>1 3ffe:b80:3:9ad1::1 (3ffe:b80:3:9ad1::1) 107.437 ms 99.341 ms 103.477 ms</code></p>
      <p><code>2 Viagenie-gw.int.ipv6.ascc.net (2001:288:3b0::55)</code></p>
      <p><code>  105.129 ms 89.418 ms 90.016 ms</code></p>
      <p><code>3 gw-Viagenie.int.ipv6.ascc.net (2001:288:3b0::54)</code></p>
      <p><code>  302.300 ms 291.580 ms 289.839 ms</code></p>
      <p><code>4 c7513-gw.int.ipv6.ascc.net (2001:288:3b0::c)</code></p>
      <p><code>  296.088 ms 298.600 ms 292.196 ms</code></p>
      <p><code>5 m160-c7513.int.ipv6.ascc.net (2001:288:3b0::1e)</code></p>
      <p><code>  296.266 ms 314.878 ms 302.429 ms</code></p>
      <p><code>6 m20jp-ml60tw.int.ipv6.ascc.net (2001:288:3b0::1b)</code></p>
      <p><code>  327.637 ms 326.897 ms 347.062 ms</code></p>
      <p><code>7 hitachi1.otemachi.wide.ad.jp (2001:200:0:1800::9c4:2)</code></p>
      <p><code>  420.140 ms 426.592 ms 422.756 ms</code></p>
      <p><code>8 pc3.yagami.wide.ad.jp (2001:200:0:1c04::1000:2000)</code></p>
      <p><code>  415.471 ms 418.308 ms 461.654 ms</code></p>
      <p><code>9 gr2000.k2c.wide.ad.jp (2001:200:0:8002::2000:1)</code></p>
      <p><code>  416.581 ms 422.430 ms 427.692 ms</code></p>
      <p><code>10 2001:200:0:4819:203:47ff:fea5:3085 (2001:200:0:4819:203:47ff:fea5:3085)</code></p>
      <p><code>  417.169 ms 434.674 ms 424.037 ms</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>28.7. Демон сообщений ICMP</p>
     </title>
     <section>
      <p>Получение асинхронных ошибок ICMP на сокет UDP всегда было и продолжает оставаться проблемой. Ядро получает сообщения об ошибках ICMP, но они редко доставляются приложениям, которым необходимо о них знать. Мы видели, что для получения этих ошибок в API сокетов требуется присоединение сокета UDP к одному IP-адресу (см. раздел 8.11). Причина такого ограничения заключается в том, что единственная ошибка, возвращаемая функцией <code>recvfrom</code>, является целочисленным кодом <code>errno</code>, а если приложение посылает дейтаграммы по нескольким адресам, а затем вызывает <code>recvfrom</code>, то данная функция не может сообщить приложению, какая из дейтаграмм вызвала ошибку.</p>
      <p>В данном разделе предлагается решение, не требующее никаких изменений в ядре. Мы предлагаем демон ICMP-сообщений <code>icmpd</code>, который создает символьный сокет ICMPv4 и символьный сокет ICMPv6 и получает все ICMP-сообщения, направляемые к ним ядром. Он также создает потоковый сокет домена Unix, связывает его (при помощи функции <code>bind</code>) с полным именем <code>/tmp/icmpd</code> и прослушивает входящие соединения (устанавливаемые при помощи функции <code>connect</code>) клиентов с этим сокетом. Схема соединений изображена на рис. 28.7.</p>
      <image l:href="#img_152.png"/>
      <p><strong>Рис. 28.7</strong>. Демон icmpd: создание сокетов</p>
      <p>Приложение UDP (являющееся клиентом для демона) сначала создает сокет UDP, для которого оно хочет получать асинхронные ошибки. Приложение должно связать (функция <code>bind</code>) с этим сокетом динамически назначаемый порт; для чего это делается, будет пояснено далее. Затем оно создает доменный сокет Unix и присоединяется (функция <code>connect</code>) к заранее известному полному имени файла демона. Это показано на рис. 28.8.</p>
      <image l:href="#img_153.png"/>
      <p><strong>Рис. 28.8</strong>. Приложение создает свой сокет UDP и доменный сокет Unix</p>
      <p>Далее приложение «передает» свой UDP-сокет демону через соединение домена Unix, используя технологию передачи дескрипторов, как показано в разделе 15.7. Такой подход позволяет демону получить копию сокета, так что он может вызвать функцию <code>getsockname</code> и получить номер порта, связанный с сокетом. На рис. 28.9 показана передача сокета.</p>
      <image l:href="#img_154.png"/>
      <p><strong>Рис. 28.9</strong>. Пересылка сокета UDP демону через доменный сокет Unix</p>
      <p>После того как демон получает номер порта, связанный с UDP-сокетом, он закрывает свою копию сокета, и мы возвращаемся к схеме, приведенной на рис. 28.8.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Если узел поддерживает передачу данных, идентифицирующих отправителя (см. раздел 15.8), приложение также может послать эти данные демону. Затем демон может проверить, можно ли допускать данного пользователя к данному устройству.</p>
      </cite>
      <p>В таком случае в результате любой ошибки ICMP, полученной демоном в ответ на UDP-дейтаграмму, посланную с порта, который связан с UDP-сокетом приложения, демон посылает приложению сообщение (о котором мы рассказываем чуть ниже) через доменный сокет Unix. Тогда приложение должно использовать функцию <code>select</code> или <code>poll</code>, чтобы обеспечить ожидание прибытия данных либо на UDP-сокет, либо на доменный сокет Unix.</p>
      <p>Сначала рассмотрим исходный код приложения, использующего данный демон, а затем и сам демон. В листинге 28.20 приведен заголовочный файл, подключаемый и к приложению, и к демону.</p>
      <p><strong>Листинг 28.20</strong>. Заголовочный файл unpicmpd.h</p>
      <p><code>//icmpd/unpicmpd.h</code></p>
      <p><code> 1 #ifndef __unpicmp_h</code></p>
      <p><code> 2 #define __unpicmp_h</code></p>
      <empty-line/>
      <p><code> 3 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 4 #define ICMPD_PATH "/tmp/icmpd" /* известное имя сервера */</code></p>
      <empty-line/>
      <p><code> 5 struct icmpd_err {</code></p>
      <p><code> 6 int icmpd_errno; /* EHOSTUNREACH, EMSGSIZE, ECONNREFUSED */</code></p>
      <p><code> 7 char icmpd_type; /* фактический тип ICMPv[46] */</code></p>
      <p><code> 8 char icmpd_code; /* фактический код ICMPv[46] */</code></p>
      <p><code> 9 socklen_t icmpd_len; /* длина последующей структуры sockaddr{} */</code></p>
      <p><code>10 struct sockaddr_storage icmpd_dest; /* универсальная структура</code></p>
      <p><code>                                          sockaddr_storage */</code></p>
      <p><code>11 };</code></p>
      <empty-line/>
      <p><code>12 #endif /* __unpicmp_h */</code></p>
      <p><code>4-11</code> Определяются известное полное имя сервера и структура <code>icmpd_err</code>, передаваемая от сервера приложению сразу, как только получено ICMP-сообщение, которое должно быть передано данному приложению.</p>
      <p><code>6-8</code> Проблема в том, что типы сообщений ICMPv4 отличаются численно (а иногда и концептуально) от типов сообщений ICMPv6 (см. табл. А.5 и А.6). Возвращаются реальные значения <emphasis>типа</emphasis> (<emphasis>type</emphasis>) и <emphasis>кода</emphasis> (<emphasis>code</emphasis>), но мы также отображаем соответствующие им значения <code>errno</code> (<code>icmpd_errno</code>), взятые из последнего столбца табл. А.5 и А.6. Приложение может использовать эти значения вместо зависящих от протокола значений ICMPv4 и ICMPv6. В табл. 28.1 показаны обрабатываемые сообщения ICMP и соответствующие им значения <code>errno</code>.</p>
      <empty-line/>
      <p><strong>Таблица 28.1</strong>. Значения переменной icmpd_errno, сопоставляющей ошибки ICMPv4 и ICMPv6</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">icmpd_errno</th>
        <th align="left" valign="top">Ошибка ICMPv4</th>
        <th align="left" valign="top">Ошибка ICMPv6</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">ECONNREFUSED</td>
        <td align="left" valign="top">Port unreachable (Порт недоступен)</td>
        <td align="left" valign="top">Port unreachable (Порт недоступен)</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EMSGSIZE</td>
        <td align="left" valign="top">Fragmentation needed but DF bit set (Необходима фрагментация, но установлен бит DF)</td>
        <td align="left" valign="top">Packet too big (Слишком большой пакет)</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EHOSTUNREACH</td>
        <td align="left" valign="top">Time exceeded (Превышено время передачи)</td>
        <td align="left" valign="top">Time exceeded (Превышено время передачи)</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EHOSTUNREACH</td>
        <td align="left" valign="top">Source quench (Отключение отправителя)</td>
        <td align="left" valign="top"/>
       </tr>
       <tr align="left">
        <td align="left" valign="top">EHOSTUNREACH</td>
        <td align="left" valign="top">Все другие сообщения о недоступности получателя (Destination unreachable)</td>
        <td align="left" valign="top">Все другие сообщения о недоступности получателя (Destination unreachable)</td>
       </tr>
      </table>
      <p>Демон возвращает пять типов ошибок ICMP:</p>
      <p>1. «Port unreachable» (Порт недоступен) означает, что сокет не связан с портом получателя на IP-адресе получателя.</p>
      <p>2. «Packet too big» (Слишком большой пакет) используется при определении транспортной MTU. В настоящее время нет определенного API, позволяющего UDP-приложениям осуществлять определение транспортной MTU. Если ядро поддерживает определение транспортной MTU для UDP, то обычно получение данной ошибки ICMP заставляет ядро записать новое значение транспортной MTU в таблицу маршрутизации ядра, но UDP-приложение, пославшее дейтаграмму, не извещается. Вместо этого приложение должно дождаться истечения тайм-аута и повторно послать дейтаграмму, и тогда ядро найдет новое (меньшее) значение MTU в своей таблице маршрутизации и фрагментирует дейтаграмму. Передача этой ошибки приложению позволяет ему ускорить повторную передачу дейтаграммы, и возможно, приложение сможет уменьшить размер посылаемой дейтаграммы.</p>
      <p>3. Ошибка «Time exceeded» (Превышено время передачи) обычно возникает с кодом 0 и означает, что либо значение поля TTL (в случае IPv4), либо предельное количество транзитных узлов (в случае IPv6) достигло нуля. Обычно это свидетельствует о зацикливании маршрута, что, возможно, является временной ошибкой.</p>
      <p>4. Ошибка «Source quench» (Отключение отправителя) ICMPv4 хотя и рассматривается в RFC 1812 [6] как устаревшая, может быть послана маршрутизаторами (или неправильно сконфигурированными узлами, действующими как маршрутизаторы). Такие ошибки означают, что пакет отброшен, и поэтому обрабатываются как ошибки недоступности получателя. Следует отметить, что в версии IPv6 нет ошибки отключения отправителя.</p>
      <p>5. Все остальные ошибки недоступности получателя (Destination unreachble) означают, что пакет сброшен.</p>
      <p><code>10</code> Элемент <code>icmpd_dest</code> является структурой адреса сокета, содержащей IP-адрес получателя и порта дейтаграммы, сгенерировавшей ICMP-ошибку. Этот элемент может быть структурой <code>sockaddr_in</code> для ICMPv4 либо структурой <code>sockaddr_in6</code> для ICMPv6. Если приложение посылает дейтаграммы по нескольким адресам, оно, вероятно, имеет по одной структуре адреса сокета на каждый адрес. Возвращая эту информацию в структуре адреса сокета, приложение может сравнить ее со своими собственными структурами для поиска той, которая вызвала ошибку. Тип <code>sockaddr_storage</code> используется для того, чтобы в структуре можно было хранить адреса любого типа, поддерживаемого системой.</p>
     </section>
     <section>
      <title>
       <p>Эхо-клиент UDP, использующий демон icmpd</p>
      </title>
      <p>Теперь модифицируем наш эхо-клиент UDP (функцию <code>dg_cli</code>) для использования нашего демона <code>icmpd</code>. В листинге 28.21 приведена первая половина функции.</p>
      <p><strong>Листинг 28.21</strong>. Первая часть приложения dg_cli</p>
      <p><code>//icmpd/dgcli01.c</code></p>
      <p><code> 1 #include "unpicmpd.h"</code></p>
      <empty-line/>
      <p><code> 2 void</code></p>
      <p><code> 3 dg_cli(FILE *fp, int sockfd, const SA *pservadd, socklen_t servlen)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int icmpfd, maxfdp1;</code></p>
      <p><code> 6  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
      <p><code> 7  fd_set rset;</code></p>
      <p><code> 8  ssize_t n;</code></p>
      <p><code> 9  struct timeval tv;</code></p>
      <p><code>10  struct icmpd_err icmpd_err;</code></p>
      <p><code>11  struct sockaddr_un sun;</code></p>
      <empty-line/>
      <p><code>12  Sock_bind_wild(sockfd, pservaddr-&gt;sa_family);</code></p>
      <empty-line/>
      <p><code>13  icmpfd = Socket(AF_LOCAL, SOCK_STREAM, 0);</code></p>
      <p><code>14  sun.sun_family = AF_LOCAL;</code></p>
      <p><code>15  strcpy(sun.sun_path, ICMPD_PATH);</code></p>
      <p><code>16  Connect(icmpfd, (SA*)&amp;sun, sizeof(sun));</code></p>
      <p><code>17  Write_fd(icmpfd, "1", 1, sockfd);</code></p>
      <p><code>18  n = Read(icmpfd, recvline, 1);</code></p>
      <p><code>19  if (n != 1 || recvline[0] != '1')</code></p>
      <p><code>20   err_quit("error creating icmp socket, n = %d, char = %c",</code></p>
      <p><code>21   n, recvline[0]);</code></p>
      <empty-line/>
      <p><code>22  FD_ZERO(&amp;rset);</code></p>
      <p><code>23  maxfdp1 = max(sockfd, icmpfd) + 1;</code></p>
      <p><code>2-3</code> Аргументы функции те же, что и во всех ее предыдущих версиях.</p>
      <subtitle>Связывание с универсальным адресом и динамически назначаемым портом</subtitle>
      <p><code>12</code> Вызываем функцию <code>sock_bind_wild</code> для связывания при помощи функции <code>bind</code> универсального IP-адреса и динамически назначаемого порта с UDP-сокетом. Таким образом копия сокета, который пересылается демону, оказывается связана с портом, поскольку демону необходимо знать этот порт.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Демон также может произвести подобное связывание, если локальный порт не был связан с сокетом, который был передан демону, но это работает не во всех системах. В реализациях SVR4, таких как Solaris 2.5, сокеты не являются частью ядра, и когда один процесс связывает (bind) порт с совместно используемым сокетом, другой процесс при попытке использовать копию этого сокета получает ошибки. Простейшее решение — потребовать, чтобы приложение связывало локальный порт прежде, чем передавать сокет демону.</p>
      </cite>
      <subtitle>Установление доменного соединения Unix с демоном</subtitle>
      <p><code>13-16</code> Мы создаем сокет семейства <code>AF_INET</code> и подключаемся к известному имени сервера при помощи вызова <code>connect</code>.</p>
      <subtitle>Отправка UDP-сокета демону, ожидание ответа от демона</subtitle>
      <p><code>17-21</code> Вызываем функцию <code>write_fd</code>, приведенную в листинге 15.11 для отправки копии UDP-сокета демону. Мы также посылаем одиночный байт данных — символ <code>"1"</code>, поскольку некоторые реализации не передают дескриптор без данных. Демон посылает обратно одиночный байт данных, состоящий из символа <code>"1"</code>, для обозначения успешного выполнения. Любой другой ответ означает ошибку.</p>
      <p><code>22-23</code> Инициализируем набор дескрипторов и вычисляем первый аргумент для функции <code>select</code> (максимальный из двух дескрипторов, увеличенный на единицу).</p>
      <p>Вторая половина нашего клиента приведена в листинге 28.22. Это цикл, который считывает данные из стандартного ввода, посылает строку серверу, считывает ответ сервера и записывает ответ в стандартный вывод.</p>
      <p><strong>Листинг 28.22</strong>. Вторая часть приложения dg_cli</p>
      <p><code>//icmpd/dgcli01.c</code></p>
      <p><code>24  while (Fgets(sendline, MAXLINE, fp) != NULL) {</code></p>
      <p><code>25   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);</code></p>
      <empty-line/>
      <p><code>26   tv.tv_sec = 5;</code></p>
      <p><code>27   tv.tv_usec = 0;</code></p>
      <p><code>28   FD_SET(sockfd, &amp;rset);</code></p>
      <p><code>29   FD_SET(icmpfd, &amp;rset);</code></p>
      <p><code>30   if ((n = Select(maxfdp1, &amp;rset, NULL, NULL, &amp;tv)) == 0) {</code></p>
      <p><code>31    fprintf(stderr, "socket timeout\n");</code></p>
      <p><code>32    continue;</code></p>
      <p><code>33   }</code></p>
      <empty-line/>
      <p><code>34   if (FD_ISSET(sockfd, &amp;rset)) {</code></p>
      <p><code>35    n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);</code></p>
      <p><code>36    recvline[n] = 0; /* завершающий нуль */</code></p>
      <p><code>37    Fputs(recvline, stdout);</code></p>
      <p><code>38   }</code></p>
      <empty-line/>
      <p><code>39   if (FD_ISSET(icmpfd, &amp;rset)) {</code></p>
      <p><code>40    if ((n = Read(icmpfd, &amp;icmpd_err, sizeof(icmpd_err))) == 0)</code></p>
      <p><code>41     err_quit("ICMP daemon terminated");</code></p>
      <p><code>42    else if (n != sizeof(icmpd_err))</code></p>
      <p><code>43     err_quit("n = %d, expected %d", n, sizeof(icmpd_err)),</code></p>
      <p><code>44    printf("ICMP error: dest = %s, %s, type = %d, code = %d\n",</code></p>
      <p><code>45     Sock_ntop(&amp;icmpd_err.icmpd_dest, icmpd_err.icmpd_len);</code></p>
      <p><code>46    strerror(icmpd_err.icmpd_errno),</code></p>
      <p><code>47     icmpd_err.icmpd_type, icmpd_err.icmpd_code);</code></p>
      <p><code>48   }</code></p>
      <p><code>49  }</code></p>
      <p><code>50 }</code></p>
      <subtitle>Вызов функции select</subtitle>
      <p><code>26-33</code> Поскольку мы вызываем функцию <code>select</code>, мы можем легко установить время ожидания ответа от эхо-сервера. Задаем его равным 5 с, открываем оба дескриптора для чтения и вызываем функцию <code>select</code>. Если происходит превышение времени, выводится соответствующее сообщение и осуществляется переход в начало цикла.</p>
      <subtitle>Вывод ответа сервера</subtitle>
      <p><code>34-38</code> Если дейтаграмма возвращается сервером, она выводится в стандартный поток вывода.</p>
      <subtitle>Обработка ICMP-ошибки</subtitle>
      <p><code>39-48</code> Если наше доменное соединение Unix с демоном <code>icmpd</code> готово для чтения, мы пытаемся прочитать структуру <code>icmpd_err</code>. Если это удается, выводится соответствующая информация, возвращаемая демоном.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Функция strerror является примером простой, почти тривиальной функции, которая должна быть более переносимой, чем она есть. В ANSI С ничего не говорится об ошибках, возвращаемых этой функцией. В руководстве по операционной системе Solaris 2.5 говорится, что функция возвращает пустой указатель, если ее аргумент выходит за пределы допустимых значений. Это означает, что код наподобие следующего:</p>
       <p><code>printf("%s", strerror(arg));</code></p>
       <p>является некорректным, поскольку strerror может вернуть пустой указатель. Однако реализации FreeBSD, так же как и все реализации исходного кода, которые автор смог найти, обрабатывают неправильный аргумент, возвращая указатель на строку типа «Неизвестная ошибка». Это имеет смысл и означает, что приведенный выше код правильный. POSIX изменил ситуацию, утверждая, что поскольку не предусмотрено значение, сигнализирующее об ошибке, связанной с выходом аргумента за допустимые пределы, функция присваивает переменной errno значение EIVAL. (Ничего не сказано об указателе, возвращаемом в случае ошибки.) Это означает, что полностью правильный код должен обнулить errno, вызвать функцию strerror, проверить, не равняется ли значение errno величине EINVAL, и в случае ошибки вывести некоторое сообщение.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Примеры эхо-клиента UDP</p>
      </title>
      <p>Приведем несколько примеров работы данного клиента, прежде чем рассматривать исходный код демона. Сначала посылаем дейтаграмму на IP-адрес, не связанный с Интернетом:</p>
      <p><code>freebsd % <strong>udpcli01 192.0.2.5 echo</strong></code></p>
      <p><code><strong>hi there</strong></code></p>
      <p><code>socket timeout</code></p>
      <p><code><strong>and hello</strong></code></p>
      <p><code>socket timeout</code></p>
      <p>Мы считаем, что демон <code>icmpd</code> запущен, и ждем возвращения каким-либо маршрутизатором ICMP-ошибок недоступности получателя. Вместо этого наше приложение завершается по превышению времени ожидания. Мы показываем это, чтобы повторить, что время ожидания все еще необходимо, а генерация ICMP- сообщения о недоступности узла может и не произойти.</p>
      <p>В следующем примере дейтаграмма отправляется на порт стандартного эхо- сервера узла, на котором этот сервер не запущен. Мы получаем ожидаемое ICMPv4-сообщение о недоступности порта.</p>
      <p><code>freebsd % <strong>udpcli01 aix-4 echo</strong></code></p>
      <p><code><strong>hello</strong></code></p>
      <p><code>ICMP error: dest = 192.168.42.2:7. Connection refused, type = 3, code = 1</code></p>
      <p>Выполнив ту же попытку с протоколом IPv6, мы получаем ICMPv6-сообщение о недоступности порта.</p>
      <p><code>freebsd % <strong>udpcli01 aix-6 echo hello, world</strong></code></p>
      <p><code>ICMP error: dest = [3ffe:b80:1f8d:2:204:acff:fe17:bf38]:7. Connection refused, type = 1. code = 4</code></p>
     </section>
     <section>
      <title>
       <p>Демон icmpd</p>
      </title>
      <p>Начинаем описание нашего демона <code>icmpd</code> с заголовочного файла <code>icmpd.h</code>, приведенного в листинге 28.23.</p>
      <p><strong>Листинг 28.23</strong>. Заголовочный файл icmpd.h для демона icmpd</p>
      <p><code>//icmpd/icmpd.h</code></p>
      <p><code> 1 #include "unpicmpd.h"</code></p>
      <empty-line/>
      <p><code> 2 struct client {</code></p>
      <p><code> 3  int connfd; /* потоковый доменный сокет Unix к клиенту */</code></p>
      <p><code> 4  int family; /* AF_INET или AF_INET6 */</code></p>
      <p><code> 5  int lport;  /* локальный порт, связанный с UDP-сокетом клиента */</code></p>
      <p><code> 6              /* сетевой порядок байтов */</code></p>
      <p><code> 7 } client[FD_SETSIZE];</code></p>
      <empty-line/>
      <p><code> 8 /* глобальные переменные */</code></p>
      <p><code> 9 int fd4, fd6, listenfd, maxi, maxfd, nready;</code></p>
      <p><code>10 fd_set rset, allset;</code></p>
      <p><code>11 struct sockaddr_un cliaddr;</code></p>
      <empty-line/>
      <p><code>12 /* прототипы функций */</code></p>
      <p><code>13 int readable_conn(int);</code></p>
      <p><code>14 int readable_listen(void);</code></p>
      <p><code>15 int readable_v4(void);</code></p>
      <p><code>16 int readable_v6(void);</code></p>
      <subtitle>Массив client</subtitle>
      <p><code>2-17</code> Поскольку демон может обрабатывать любое количество клиентов, для сохранения информации о каждом клиенте используется массив структур <code>client</code>. Они аналогичны структурам данных, которые использовались в разделе 6.8. Кроме дескриптора для доменного сокета Unix, через который осуществляется связь с клиентом, сохраняется также семейство адресов клиентского UDP-сокета <code>AF_INET</code> или <code>AF_INET6</code> и номер порта, связанного с сокетом. Далее объявляются прототипы функций и глобальные переменные, совместно используемые этими функциями.</p>
      <p>В листинге 28.24 приведена первая часть функции main.</p>
      <p><strong>Листинг 28.24</strong>. Первая часть функции main: создание сокетов</p>
      <p><code>//icmpd/icmpd.c</code></p>
      <p><code> 1 #include "icmpd.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 main(int argc, char **argv)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int i, sockfd;</code></p>
      <p><code> 6  struct sockaddr_un sun;</code></p>
      <empty-line/>
      <p><code> 7  if (argc != 1)</code></p>
      <p><code> 8   err_quit("usage: icmpd");</code></p>
      <empty-line/>
      <p><code> 9  maxi = -1; /* индекс массива client[] */</code></p>
      <p><code>10  for (i = 0; i &lt; FD_SETSIZE; i++)</code></p>
      <p><code>11   client[i].connfd = -1; /* -1 означает свободный элемент */</code></p>
      <p><code>12  FD_ZERO(&amp;allset);</code></p>
      <empty-line/>
      <p><code>13  fd4 = Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);</code></p>
      <p><code>14  FD_SET(fd4, &amp;allset);</code></p>
      <p><code>15  maxfd = fd4;</code></p>
      <empty-line/>
      <p><code>16 #ifdef IPV6</code></p>
      <p><code>17  fd6 = Socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);</code></p>
      <p><code>18  FD_SET(fd6, &amp;allset);</code></p>
      <p><code>19  maxfd = max(maxfd, fd6);</code></p>
      <p><code>20 #endif</code></p>
      <empty-line/>
      <p><code>21  listenfd = Socket(AF_UNIX, SOCK_STREAM, 0);</code></p>
      <p><code>22  sun.sun_family = AF_LOCAL;</code></p>
      <p><code>23  strcpy(sun.sun_path, ICMPD_PATH);</code></p>
      <p><code>24  unlink(ICMPD_PATH);</code></p>
      <p><code>25  Bind(listenfd, (SA*)&amp;sun, sizeof(sun));</code></p>
      <p><code>26  Listen(listenfd, LISTENQ);</code></p>
      <p><code>27  FD_SET(listenfd, &amp;allset);</code></p>
      <p><code>28  maxfd = max(maxfd, listenfd);</code></p>
      <subtitle>Инициализация массива client</subtitle>
      <p><code>9-10</code> Инициализируется массив <code>client</code> путем присваивания значения -1 элементу присоединенного сокета.</p>
      <subtitle>Создание сокетов</subtitle>
      <p><code>12-28</code> Создаются три сокета: символьный сокет ICMPv4, символьный сокет ICMPv6 и потоковый доменный сокет Unix. Мы связываем при помощи функции <code>bind</code> свое заранее известное полное имя с сокетом и вызываем функцию <code>listen</code>. Это сокет, к которому клиенты присоединяются с помощью функции <code>connect</code>. Для функции <code>select</code> также вычисляется максимальный дескриптор, а для вызовов функции <code>accept</code> в памяти размещается структура адреса сокета.</p>
      <p>В листинге 28.25 приведена вторая часть функции <code>main</code>. Она содержит бесконечный цикл, вызывающий функцию <code>select</code> в ожидании, когда будет готов к чтению какой-либо из дескрипторов демона.</p>
      <p><strong>Листинг 28.25</strong>. Вторая часть функции main: обработка готового к чтению дескриптора</p>
      <p><code>//icmpd/icmpd.c</code></p>
      <p><code>29  for (;;) {</code></p>
      <p><code>30   rset = allset;</code></p>
      <p><code>31   nready = Select(maxfd+1, &amp;rset, NULL, NULL, NULL);</code></p>
      <empty-line/>
      <p><code>32   if (FD_ISSET(listenfd, &amp;rset))</code></p>
      <p><code>33    if (readable_listen() &lt;= 0)</code></p>
      <p><code>34     continue;</code></p>
      <empty-line/>
      <p><code>35   if (FD_ISSET(fd4, &amp;rset))</code></p>
      <p><code>36    if (readable_v4() &lt;= 0)</code></p>
      <p><code>37     continue;</code></p>
      <empty-line/>
      <p><code>38 #ifdef IPV6</code></p>
      <p><code>39   if (FD_ISSET(fd6, &amp;rset))</code></p>
      <p><code>40    if (readable_v6() &lt;= 0)</code></p>
      <p><code>41     continue;</code></p>
      <p><code>42 #endif</code></p>
      <empty-line/>
      <p><code>43   for (i = 0; i &lt;= maxi; i++) { /* проверка всех клиентов */</code></p>
      <p><code>44    if ( (sockfd = client[i].connfd) &lt; 0)</code></p>
      <p><code>45     continue;</code></p>
      <p><code>46    if (FD_ISSET(sockfd, &amp;rset))</code></p>
      <p><code>47     if (readable_conn(i) &lt;= 0)</code></p>
      <p><code>48      break; /* готовых дескрипторов больше нет */</code></p>
      <p><code>49   }</code></p>
      <p><code>50  }</code></p>
      <p><code>51  exit(0);</code></p>
      <p><code>52 }</code></p>
      <subtitle>Проверка прослушиваемого доменного сокета Unix</subtitle>
      <p><code>32-34</code> Прослушиваемый доменный сокет Unix проверяется в первую очередь, и если он готов, запускается функция <code>readable_listen</code>. Переменная <code>nready</code> — количество дескрипторов, которое функция select возвращает как готовые к чтению — является глобальной. Каждая из наших функций <code>readablе_XXX</code> уменьшает ее значение на 1, и новое значение этой переменной является возвращаемым значением функции. Когда ее значение достигает нуля, это говорит о том, что все готовые к чтению дескрипторы обработаны, и поэтому функция <code>select</code> вызывается снова.</p>
      <subtitle>Проверка символьных сокетов ICMP</subtitle>
      <p><code>35-42</code> Проверяется символьный сокет ICMPv4, а затем символьный сокет ICMPv6.</p>
      <subtitle>Проверка присоединенных доменных сокетов Unix</subtitle>
      <p><code>43-49</code> Затем проверяется, готов ли для чтения какой-нибудь из присоединенных доменных сокетов Unix. Готовность для чтения какого-либо из таких сокетов обозначает, что клиент отослал дескриптор или завершился.</p>
      <p>В листинге 28.26 приведена функция <code>readable_listen</code>, вызываемая, когда прослушиваемый сокет готов для чтения. Это указывает на новое клиентское соединение.</p>
      <p><strong>Листинг 28.26</strong>. Обработка нового соединения клиента</p>
      <p><code>//icmpd/readablе_listen.c</code></p>
      <p><code> 1 #include "icmpd.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 readable_listen(void)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int i, connfd;</code></p>
      <p><code> 6  socklen_t clilen;</code></p>
      <empty-line/>
      <p><code> 7  clilen = sizeof(cliaddr);</code></p>
      <p><code> 8  connfd = Accept(listenfd, (SA*)&amp;cliaddr, &amp;clilen);</code></p>
      <p><code> 9  /* поиск первой свободной структуры в массиве client[] */</code></p>
      <p><code>10  for (i = 0; i &lt; FD_SETSIZE; i++)</code></p>
      <p><code>11   if (client[i].connfd &lt; 0) {</code></p>
      <p><code>12    client[i].connfd = connfd; /* сохранение дескриптора */</code></p>
      <p><code>13    break;</code></p>
      <p><code>14   }</code></p>
      <p><code>15  if (i == FD_SETSIZE) {</code></p>
      <p><code>16   close(connfd); /* невозможно обработать новый клиент */</code></p>
      <p><code>17   return(--nready); /* грубое закрытие нового соединения */</code></p>
      <p><code>18  }</code></p>
      <p><code>19  printf("new connection, i = %d, connfd = %d\n", i, connfd);</code></p>
      <empty-line/>
      <p><code>20  FD_SET(connfd, &amp;allset); /* добавление нового дескриптора в набор */</code></p>
      <p><code>21  if (connfd &gt; maxfd)</code></p>
      <p><code>22   maxfd = connfd; /* для select() */</code></p>
      <p><code>23  if (i &gt; maxi)</code></p>
      <p><code>24   maxi = i; /* максимальный индекс в массиве client[] */</code></p>
      <empty-line/>
      <p><code>25  return(--nready);</code></p>
      <p><code>26 }</code></p>
      <p><code>7-25</code> Принимается соединение и используется первый свободный элемент массива <code>client</code>. Код данной функции скопирован из начала кода, приведенного в листинге 6.4. Если свободных элементов в массиве нет, мы закрываем новое соединение и занимаемся обслуживанием уже имеющихся клиентов.</p>
      <p>Когда присоединенный сокет готов для чтения, вызывается функция <code>readablе_conn</code> (листинг 28.27), а ее аргументом является индекс данного клиента в массиве client.</p>
      <p><strong>Листинг 28.27</strong>. Считывание данных и, возможно, дескриптора от клиента</p>
      <p><code>//icmpd/readable_conn.c</code></p>
      <p><code> 1 #include "icmpd.h"</code></p>
      <empty-line/>
      <p><code> 2 int</code></p>
      <p><code> 3 readable_conn(int I)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  int unixfd, recvfd;</code></p>
      <p><code> 6  char c;</code></p>
      <p><code> 7  ssize_t n;</code></p>
      <p><code> 8  socklen_t len;</code></p>
      <p><code> 9  struct sockaddr_storage ss;</code></p>
      <empty-line/>
      <p><code>10  unixfd = client[i].connfd;</code></p>
      <p><code>11  recvfd = -1;</code></p>
      <p><code>12  if ((n = Read_fd(unixfd, &amp;c, 1, &amp;recvfd)) == 0) {</code></p>
      <p><code>13   err_msg("client %d terminated, recvfd = %d", i, recvfd);</code></p>
      <p><code>14   goto clientdone; /* вероятно, клиент завершил работу */</code></p>
      <p><code>15  }</code></p>
      <empty-line/>
      <p><code>16  /* данные от клиента, должно быть, дескриптор */</code></p>
      <p><code>17  if (recvfd &lt; 0) {</code></p>
      <p><code>18   err_msg("read_fd did not return descriptor");</code></p>
      <p><code>19   goto clienterr;</code></p>
      <p><code>20  }</code></p>
      <subtitle>Считывание данных клиента и, возможно, дескриптора</subtitle>
      <p><code>13-18</code> Вызываем функцию <code>read_fd</code>, приведенную в листинге 15.9, для считывания данных и, возможно, дескриптора. Если возвращаемое значение равно нулю, клиент закрыл свою часть соединения, вероятно, завершив свое выполнение.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>При написании кода пришлось выбирать, что использовать для связи между приложением и демоном — либо потоковый доменный сокет Unix, либо дейтаграммный доменный сокет Unix. Дескриптор сокета UDP может быть передан через любой доменный сокет Unix. Причина, по которой предпочтение было отдано потоковому сокету, заключается в том, что он позволяет определить момент завершения клиента. Все дескрипторы автоматически закрываются, когда клиент завершает работу, в том числе и доменный сокет Unix, используемый для связи с демоном, в результате чего данный клиент удаляется демоном из массива client. Если бы мы использовали сокет дейтаграмм, то не узнали бы, когда клиент завершил работу.</p>
      </cite>
      <p><code>16-20</code> Если клиент не закрыл соединение, ждем получения дескриптора. Вторая часть функции <code>readable_conn</code> приведена в листинге 28.28.</p>
      <p><strong>Листинг 28.28</strong>. Получение номера порта, который клиент связал с UDP-сокетом</p>
      <p><code>//icmpd/readable_conn.c</code></p>
      <p><code>21  len = sizeof(ss);</code></p>
      <p><code>22  if (getsockname(recvfd, (SA*)&amp;ss, &amp;len) &lt; 0) {</code></p>
      <p><code>23   err_ret("getsockname error");</code></p>
      <p><code>24   goto clienterr;</code></p>
      <p><code>25  }</code></p>
      <empty-line/>
      <p><code>26  client[i].family = ss.ss_family;</code></p>
      <p><code>27  if ((client[i].lport = sock_get_port((SA*)&amp;ss, len)) == 0) {</code></p>
      <p><code>28   client[i].lport = sock_bind_wild(recvfd, client[i].family);</code></p>
      <p><code>29   if (client[i].lport &lt;= 0) {</code></p>
      <p><code>30    err_ret("error binding ephemeral port");</code></p>
      <p><code>31    goto clienterr;</code></p>
      <p><code>32   }</code></p>
      <p><code>33  }</code></p>
      <p><code>34  Write(unixfd, "1", 1); /* сообщение клиенту об успехе */</code></p>
      <p><code>35  Close(recvfd); /* работа с UDP-сокетом клиента завершена */</code></p>
      <p><code>36  return(--nready);</code></p>
      <empty-line/>
      <p><code>37 clienterr:</code></p>
      <p><code>38  Write(unixfd, "0", 1); /* сообщение клиенту об ошибке */</code></p>
      <p><code>39 clientdone:</code></p>
      <p><code>40  Close(unixfd);</code></p>
      <p><code>41  if (recvfd &gt;= 0)</code></p>
      <p><code>42   Close(recvfd);</code></p>
      <p><code>43  FD_CLR(unixfd, &amp;allset);</code></p>
      <p><code>44  client[i].connfd = -1;</code></p>
      <p><code>45  return(--nready);</code></p>
      <p><code>46 }</code></p>
      <subtitle>Получение номера порта, связанного с сокетом UDP</subtitle>
      <p><code>21-25</code> Вызывается функция <code>getsockname</code>, так что демон может получить номер порта, связанного с сокетом. Поскольку неизвестно, каков размер буфера, необходимого для размещения структуры адреса сокета, мы используем структуру <code>sockaddr_storage</code>, которая достаточно велика для структуры адреса сокета любого поддерживаемого системой типа и обеспечивает нужное выравнивание.</p>
      <p><code>26-33</code> Семейство адресов сокета вместе с номером порта сохраняется в структуре <code>client</code>. Если номер порта равен нулю, мы вызываем функцию <code>sock_bind_wild</code> для связывания универсального адреса и динамически назначаемого порта с сокетом, но, как отмечалось ранее, такой подход не работает в реализациях SVR4.</p>
      <subtitle>Сообщение клиенту об успехе</subtitle>
      <p><code>34</code> Один байт, содержащий символ <code>"1"</code>, отправляется обратно клиенту.</p>
      <subtitle>Закрытие UDP-сокета клиента</subtitle>
      <p><code>35</code> Заканчиваем работу с UDP-сокетом клиента и закрываем его с помощью функции <code>close</code>. Дескриптор был переслан нам клиентом и, таким образом, является копией; следовательно, UDP-сокет все еще открыт на стороне клиента.</p>
      <subtitle>Обработка ошибок и завершение работы клиента</subtitle>
      <p><code>37-45</code> Если происходит ошибка, клиент получает нулевой байт. Когда клиент завершается, наша часть доменного сокета Unix закрывается, и соответствующий дескриптор удаляется из набора дескрипторов для функции <code>select</code>. Полю <code>connfd</code> структуры <code>client</code> присваивается значение -1, что является указанием на ее освобождение.</p>
      <p>Функция <code>readable_v4</code> вызывается, когда символьный сокет ICMPv4 открыт для чтения. Первая часть данной функции приведена в листинге 28.29. Этот код аналогичен коду для ICMPv4, приведенному ранее в листингах 28.6 и 28.15.</p>
      <p><strong>Листинг 28.29</strong>. Обработка полученных дейтаграмм ICMPv4, первая часть</p>
      <p><code>//icmpd/readable_v4.c</code></p>
      <p><code> 1 #include "icmpd.h"</code></p>
      <p><code> 2 #include &lt;netinet/in_systm.h&gt;</code></p>
      <p><code> 3 #include &lt;netinet/ip.h&gt;</code></p>
      <p><code> 4 #include &lt;netinet/ip_icmp.h&gt;</code></p>
      <p><code> 5 #include &lt;netinet/udp.h&gt;</code></p>
      <empty-line/>
      <p><code> 6 int</code></p>
      <p><code> 7 readable_v4(void)</code></p>
      <p><code> 8 {</code></p>
      <p><code> 9  int i, hlen1, hlen2, icmplen, sport;</code></p>
      <p><code>10  char buf[MAXLINE];</code></p>
      <p><code>11  char srcstr[INET_ADDRSTRLEN], dststr[INET_ADDRSTRLEN];</code></p>
      <p><code>12  ssize_t n;</code></p>
      <p><code>13  socklen_t len;</code></p>
      <p><code>14  struct ip *ip, *hip;</code></p>
      <p><code>15  struct icmp *icmp;</code></p>
      <p><code>16  struct udphdr *udp;</code></p>
      <p><code>17  struct sockaddr_in from, dest;</code></p>
      <p><code>18  struct icmpd_err icmpd_err;</code></p>
      <empty-line/>
      <p><code>19  len = sizeof(from);</code></p>
      <p><code>20  n = Recvfrom(fd4, buf, MAXLINE, 0, (SA*)&amp;from, &amp;len);</code></p>
      <empty-line/>
      <p><code>21  printf("%d bytes ICMPv4 from %s:", n, Sock_ntop_host((SA*)&amp;from, len));</code></p>
      <empty-line/>
      <p><code>22  ip = (struct ip*)buf; /* начало IP-заголовка */</code></p>
      <p><code>23  hlen1 = ip-&gt;ip_hl &lt;&lt; 2; /* длина IP-заголовка */</code></p>
      <empty-line/>
      <p><code>24  icmp = (struct icmp*)(buf + hlen1); /* начало ICMP-заголовка */</code></p>
      <p><code>25  if ((icmplen = n - hlen1) &lt; 8)</code></p>
      <p><code>26   err_quit("icmplen (%d) &lt; 8", icmplen);</code></p>
      <empty-line/>
      <p><code>27  printf(" type = %d, code = %d\n", icmp-&gt;icmp_type, icmp-&gt;icmp_code);</code></p>
      <p>Функция выводит некоторую информацию о каждом получаемом сообщении ICMP. Это было сделано для отладки при разработке демона, и вывод управляется аргументом командной строки.</p>
      <p>В листинге 28.30 приведена вторая часть функции <code>readable_v4</code>.</p>
      <p><strong>Листинг 28.30</strong>. Обработка полученных дейтаграмм ICMPv4, вторая часть</p>
      <p><code>//icmpd/readable_v4.c</code></p>
      <p><code>28  if (icmp-&gt;icmp_type == ICMP_UNREACH ||</code></p>
      <p><code>29   icmp-&gt;icmp_type ==ICMP_TIMXCEED ||</code></p>
      <p><code>30   icmp-&gt;icmp_type == ICMP_SOURCEQUENCH) {</code></p>
      <p><code>31   if (icmplen &lt; 8 + 20 + 8)</code></p>
      <p><code>32    err_quit("icmplen (%d) &lt; 8 + 20 + 8, icmplen);</code></p>
      <empty-line/>
      <p><code>33   hip = (struct ip*)(buf + hlen1 + 8);</code></p>
      <p><code>34   hlen2 = hip-&gt;ip_hl &lt;&lt; 2;</code></p>
      <p><code>35   printf("\tsrcip = %s, dstip = %s, proto = %d\n",</code></p>
      <p><code>36    Inet_ntop(AF_INET, &amp;hip-&gt;ip_src, srcstr, sizeof(srcstr)),</code></p>
      <p><code>37    Inet_ntop(AF_INET, &amp;hip-&gt;ip_dst, dststr, sizeof(dststr)),</code></p>
      <p><code>38    hip-&gt;ip_p);</code></p>
      <p><code>39   if (hip-&gt;ip_p == IPPROTO_UDP) {</code></p>
      <p><code>40    udp = (struct udphdr*)(buf + hlen1 + 8 + hlen2);</code></p>
      <p><code>41    sport = udp-&gt;uh_sport;</code></p>
      <empty-line/>
      <p><code>42    /* поиск доменного сокета клиента, отправка заголовка */</code></p>
      <p><code>43    for (i = 0; i &lt;= maxi; i++) {</code></p>
      <p><code>44     if (client[i].connfd &gt;= 0 &amp;&amp;</code></p>
      <p><code>45      client[i].family == AF_INET &amp;&amp;</code></p>
      <p><code>46      client[i].lport == sport) {</code></p>
      <empty-line/>
      <p><code>47      bzero(&amp;dest, sizeof(dest));</code></p>
      <p><code>48      dest.sin_family = AF_INET;</code></p>
      <p><code>49 #ifdef HAVE_SOCKADDR_SA_LEN</code></p>
      <p><code>50      dest.sin_len = sizeof(dest);</code></p>
      <p><code>51 #endif</code></p>
      <p><code>52      memcpy(&amp;dest.sin_addr, &amp;hip-&gt;ip_dst,</code></p>
      <p><code>53       sizeof(struct in_addr));</code></p>
      <p><code>54      dest.sin_port = udp-&gt;uh_dport;</code></p>
      <empty-line/>
      <p><code>55      icmpd_err.icmpd_type = icmp-&gt;icmp_type;</code></p>
      <p><code>56      icmpd_err.icmpd_code = icmp-&gt;icmp_code;</code></p>
      <p><code>57      icmpd_err.icmpd_len = sizeof(struct sockaddr_in);</code></p>
      <p><code>58      memcpy(&amp;icmpd_err.icmpd_dest, &amp;dest, sizeof(dest));</code></p>
      <empty-line/>
      <p><code>59      /* преобразование кода и типа ICMP в значение errno */</code></p>
      <p><code>60      icmpd_err.icmpd_errno = EHOSTUNREACH; /* по умолчанию */</code></p>
      <p><code>61      if (icmp-&gt;icmp_type == ICMP_UNREACH) {</code></p>
      <p><code>62       if (icmp-&gt;icmp_code == ICMP_UNREACH_PORT)</code></p>
      <p><code>63        icmpd_err.icmpd_errno = ECONNREFUSED;</code></p>
      <p><code>64       else if (icmp-&gt;icmp_code == ICMP_UNREACH_NEEDFRAG)</code></p>
      <p><code>65        icmpd_err.icmpd_errno = EMSGSIZE;</code></p>
      <p><code>66      }</code></p>
      <p><code>67      Write(client[i].connfd, &amp;icmpd_err, sizeof(icmpd_err));</code></p>
      <p><code>68     }</code></p>
      <p><code>69    }</code></p>
      <p><code>70   }</code></p>
      <p><code>71  }</code></p>
      <p><code>72  return(--nready);</code></p>
      <p><code>73 }</code></p>
      <subtitle>Проверка типа сообщения, уведомление приложения</subtitle>
      <p><code>29-31</code> ICMP-сообщения, которые посылаются приложениям, — это сообщения о недоступности порта, превышении времени и завершении клиента (см. табл. 28.1).</p>
      <subtitle>Проверка ошибки UDP, поиск клиента</subtitle>
      <p><code>34-42</code> Указатель <code>hip</code> указывает на IP-заголовок, который возвращается сразу после заголовка ICMP. Это IP-заголовок дейтаграммы, вызвавшей ICMP-ошибку. Мы убеждаемся, что эта IP-дейтаграмма является UDP-дейтаграммой, а затем извлекаем номер UDP-порта из UDP-заголовка, следующего за IP-заголовком.</p>
      <p><code>43-55</code> По всем структурам <code>client</code> осуществляется поиск подходящего семейства адресов и порта. Если соответствие найдено, строится структура адреса сокета IPv4, которая содержит IP-адрес получателя и порт из UDP-дейтаграммы, вызвавшей ошибку.</p>
      <subtitle>Построение структуры icmpd_err</subtitle>
      <p><code>56-70</code> Строится структура <code>icmpd_err</code>, посылаемая клиенту через доменный сокет Unix. Тип и код сообщения ICMP сначала отображаются в значение <code>errno</code>, как показано в табл. 28.1.</p>
      <p>Ошибки ICMPv6 обрабатываются функцией <code>readable_v6</code>, первая часть которой приведена в листинге 28.31. Обработка ошибок ICMPv6 аналогична коду, приведенному в листингах 28.7 и 28.16.</p>
      <p><strong>Листинг 28.31</strong>. Обработка полученной дейтаграммы ICMPv6, первая часть</p>
      <p><code>//icmpd/readable_v6.c</code></p>
      <p><code> 1 #include "icmpd.h"</code></p>
      <p><code> 2 #include &lt;netinet/in_systm.h&gt;</code></p>
      <p><code> 3 #include &lt;netinet/ip.h&gt;</code></p>
      <p><code> 4 #include &lt;netinet/ip_icmp.h&gt;</code></p>
      <p><code> 5 #include &lt;netinet/udp.h&gt;</code></p>
      <empty-line/>
      <p><code> 6 #ifdef IPV6</code></p>
      <p><code> 7 #include &lt;netinet/ip6.h&gt;</code></p>
      <p><code> 8 #include &lt;netinet/icmp6.h&gt;</code></p>
      <p><code> 9 #endif</code></p>
      <empty-line/>
      <p><code>10 int</code></p>
      <p><code>11 readable_v6(void)</code></p>
      <p><code>12 {</code></p>
      <p><code>13 #ifdef IPV6</code></p>
      <p><code>14  int i, hlen2, icmp6len, sport;</code></p>
      <p><code>15  char buf[MAXLINE];</code></p>
      <p><code>16  char srcstr[INET6_ADDRSTRLEN], dststr[INET6_ADDRSTRLEN];</code></p>
      <p><code>17  ssize_t n;</code></p>
      <p><code>18  socklen_t len;</code></p>
      <p><code>19  struct ip6_hdr *ip6, *hip6;</code></p>
      <p><code>20  struct icmp6_hdr *icmp6;</code></p>
      <p><code>21  struct udphdr *udp;</code></p>
      <p><code>22  struct sockaddr_in6 from, dest;</code></p>
      <p><code>23  struct icmpd_err icmpd_err;</code></p>
      <empty-line/>
      <p><code>24  len = sizeof(from);</code></p>
      <p><code>25  n = Recvfrom(fd6, buf, MAXLINE, 0, (SA*)&amp;from, &amp;len);</code></p>
      <empty-line/>
      <p><code>26  printf("%d bytes ICMPv6 from %s:", n, Sock_ntop_host((SA*)&amp;from, len));</code></p>
      <empty-line/>
      <p><code>27  icmp6 = (struct icmp6_hdr*)buf; /* начало заголовка ICMPv6 */</code></p>
      <p><code>28  if ((icmp6len = n) &lt; 8)</code></p>
      <p><code>29   err_quit("icmp6len (%d) &lt; 8", icmp6len);</code></p>
      <empty-line/>
      <p><code>30  printf(" type = %d, code = %d\n", icmp6-&gt;icmp6_type, icmp6-&gt;icmp6_code);</code></p>
      <p>Вторая часть функции <code>readable_v6</code> приведена в листинге 28.32. Код аналогичен приведенному в листинге 28.30: мы проверяем тип ICMP-ошибки, убеждаемся, что дейтаграмма, вызвавшая ошибку, является UDP-дейтаграммой, а затем строим структуру <code>icmpd_err</code>, которую отсылаем клиенту.</p>
      <p><strong>Листинг 28.32</strong>. Обработка полученной дейтаграммы ICMPv6, вторая часть</p>
      <p><code>//icmpd/readable_v6.c</code></p>
      <p><code>31  if (icmp6-&gt;icmp6_type == ICMP6_DST_UNREACH ||</code></p>
      <p><code>32   icmp6-&gt;icmp6_type == ICMP6_PACKET_TOO_BIG ||</code></p>
      <p><code>33   icmp6-&gt;icmp6_type == ICMP6_TIME_EXCEEDED) {</code></p>
      <p><code>34   if (icmp6len &lt; 8+8)</code></p>
      <p><code>35    err_quit("icmp6len (%d) &lt; 8 + 8", icmp6len);</code></p>
      <p><code>36   hip6 = (struct ip6_hdr*)(buf + 8);</code></p>
      <p><code>37   hlen2 = sizeof(struct ip6_hdr);</code></p>
      <p><code>38   printf("\tsrcip = %s, dstip = %s, next hdr = %d\n",</code></p>
      <p><code>39    Inet_ntop(AF_INET6, &amp;hip6-&gt;ip6_src, srcstr, sizeof(srcstr)),</code></p>
      <p><code>40    Inet_ntop(AF_INET6, &amp;hip6-&gt;ip6_dst, dststr, sizeof(dststr)),</code></p>
      <p><code>41    hip6-&gt;ip6_nxt);</code></p>
      <p><code>42   if (hip6-&gt;ip6_nxt == IPPROTO_UDP) {</code></p>
      <p><code>43    udp = (struct udphdr*)(buf + 8 + hlen2);</code></p>
      <p><code>44    sport = udp-&gt;uh_sport;</code></p>
      <empty-line/>
      <p><code>45    /* поиск доменного сокета клиента, отправка заголовков */</code></p>
      <p><code>46    for (i = 0; i &lt;= maxi; i++) {</code></p>
      <p><code>47     if (client[i].connfd &gt;= 0 &amp;&amp;</code></p>
      <p><code>48      client[i].family == AF_INET6 &amp;&amp;</code></p>
      <p><code>49      client[i].lport == sport) {</code></p>
      <empty-line/>
      <p><code>50      bzero(&amp;dest, sizeof(dest));</code></p>
      <p><code>51      dest.sin6_family = AF_INET6;</code></p>
      <p><code>52 #ifdef HAVE_SOCKADDR_SA_LEN</code></p>
      <p><code>53      dest.sin6_len = sizeof(dest);</code></p>
      <p><code>54 #endif</code></p>
      <p><code>55      memcpy(&amp;dest.sin6_addr, &amp;hip6-&gt;ip6_dst,</code></p>
      <p><code>56       sizeof(struct in6_addr));</code></p>
      <p><code>57      dest.sin6_port = udp-&gt;uh_dport;</code></p>
      <empty-line/>
      <p><code>58      icmpd_err.icmpd_type = icmp6-&gt;icmp6_type;</code></p>
      <p><code>59      icmpd_err.icmpd_code = icmp6-&gt;icmp6_code;</code></p>
      <p><code>60      icmpd_err.icmpd_len = sizeof(struct sockaddr_in6);</code></p>
      <p><code>61      memcpy(&amp;icmpd_err.icmpd_dest, &amp;dest, sizeof(dest));</code></p>
      <empty-line/>
      <p><code>62      /* преобразование типа и кода ICMPv6 к значению errno */</code></p>
      <p><code>63      icmpd_err.icmpd_errno = EHOSTUNREACH; /* по умолчанию */</code></p>
      <p><code>64      if (icmp6-&gt;icmp6_type == ICMP6_DST_UNREACH &amp;&amp;</code></p>
      <p><code>65       icmp6-&gt;icmp6_code ICMP6_DST_UNREACH_NOPORT)</code></p>
      <p><code>66       icmpd_err.icmpd_errno = ECONNREFUSED;</code></p>
      <p><code>67      if (icmp6-&gt;icmp6_type == ICMP6_PACKET_TOO_BIG)</code></p>
      <p><code>68       icmpd_err.icmpd_errno = EMSGSIZE;</code></p>
      <p><code>69      Write(client[i].connfd, &amp;icmpd_err, sizeof(icmpd_err));</code></p>
      <p><code>70     }</code></p>
      <p><code>71    }</code></p>
      <p><code>72   }</code></p>
      <p><code>73  }</code></p>
      <p><code>74  return(--nready);</code></p>
      <p><code>75 #endif</code></p>
      <p><code>76 }</code></p>
     </section>
    </section>
    <section>
     <title>
      <p>28.8. Резюме</p>
     </title>
     <p>Символьные сокеты обеспечивают три возможности:</p>
     <p>1. Чтение и запись пакетов ICMPv4, IGMPv4 и ICMPv6.</p>
     <p>2. Чтение и запись IP-дейтаграммы с полем протокола, которое не обрабатывается ядром.</p>
     <p>3. Формирование своих собственных заголовков IPv4, обычно используемых в диагностических целях (или, к сожалению, хакерами).</p>
     <p>Два традиционных диагностических средства — программы <code>ping</code> и <code>traceroute</code> — используют символьные сокеты. Мы разработали наши собственные версии этих программ, поддерживающие обе версии протокола — и IPv4, и IPv6. Также нами разработан наш собственный демон <code>icmpd</code>, который обеспечивает доступ к сообщениям об ошибках ICMP через сокет UDP. Данный пример также иллюстрирует передачу дескриптора через доменный сокет Unix между неродственными клиентом и сервером.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. В этой главе говорилось, что почти все поля заголовка IPv6 и все дополнительные заголовки доступны приложению через параметры сокета или вспомогательные данные. Какая информация из дейтаграммы IPv6 <emphasis>не</emphasis> доступна приложению?</p>
     <p>2. Что произойдет в листинге 28.30, если по какой-либо причине клиент перестанет производить считывание из своего доменного сокета Unix и демон <code>icmpd</code> накопит множество сообщений для данного клиента? В чем заключается простейшее решение этой проблемы?</p>
     <p>3. Если задать нашей программе <code>ping</code> адрес широковещательной передачи, направленный в подсеть, она будет работать. То есть широковещательный эхо- запрос ICMP посылается как широковещательный запрос канального уровня, даже если мы не установим параметр сокета <code>SO_BROADCAST</code>. Почему?</p>
     <p>4. Что произойдет с программой <code>ping</code>, если мы запустим ее на узле с несколькими интерфейсами, а в качестве аргумента имени узла возьмем групповой адрес 224.0.0.1?</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 29</p>
     <p>Доступ к канальному уровню</p>
    </title>
    <section>
     <title>
      <p>29.1. Введение</p>
     </title>
     <p>В настоящее время большинство операционных систем позволяют приложению получать доступ к канальному уровню. Это подразумевает следующие возможности:</p>
     <p>1. Отслеживание пакетов, принимаемых на канальном уровне, что, в свою очередь, позволяет запускать такие программы, как <code>tcpdump</code>, на обычных компьютерных системах (а не только на специальных аппаратных устройствах для отслеживания пакетов). Если добавить к этому способность сетевого интерфейса работать в <emphasis>смешанном режиме</emphasis> (<emphasis>promiscuous mode</emphasis>), приложение сможет отслеживать все пакеты, проходящие по локальному кабелю, а не только предназначенные для того узла, на котором работает эта программа.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Эта возможность не так полезна в коммутируемых сетях, которые получили широкое распространение в последнее время. Дело в том, что коммутатор пропускает трафик на конкретный порт только в том случае, если этот трафик адресован конкретному устройству или устройствам, подключенным к этому порту, каким бы трафик ни был: направленным, широковещательным или многоадресным. Для того чтобы получать трафик, передаваемый через другие порты коммутатора, нужно сначала переключить свой порт коммутатора в режим контроля (monitor mode или port mirroring). Заметьте, что многие устройства, которые обычно не считают коммутаторами, на самом деле являются таковыми. Например, двухскоростной концентратор 10/100 обычно является двухпортовым коммутатором: один порт для сетей, работающих на 100 Мбит/с, другой — для сетей на 10 Мбит/с.</p>
     </cite>
     <p>2. Возможность запуска определенных программ как обычных приложений, а не как частей ядра. Скажем, большинство версий Unix сервера RARP — это обычные приложения, которые считывают запросы RARP с канального уровня (запросы RARP не являются дейтаграммами IP), а затем передают ответы также на канальный уровень.</p>
     <p>Три наиболее распространенных средства получения доступа к канальному уровню в Unix — это пакетный фильтр BSD (BPF, BSD Packet Filter), DLPI в SVR4 (Datalink Provider Interface — интерфейс поставщика канального уровня) и интерфейс пакетных сокетов Linux (<code>SOCK_PACKET</code>). Мы приводим в этой главе обзор перечисленных средств, а затем описываем <code>libcap</code> — открытую для свободного доступа библиотеку, содержащую функции для захвата пакетов. Эта библиотека работает со всеми тремя перечисленными средствами, и использование библиотеки позволяет сделать наши программы не зависящими от фактического способа обеспечения доступа к канальному уровню, применяемому в данной операционной системе. Мы описываем эту библиотеку, разрабатывая программу, которая посылает запросы серверу имен DNS (мы составляем свои собственные дейтаграммы UDP и записываем их в символьный сокет) и считывает ответ при помощи <code>libcap</code>, чтобы определить, добавляет ли сервер имен контрольную сумму в дейтаграммы UDP.</p>
    </section>
    <section>
     <title>
      <p>29.2. BPF: пакетный фильтр BSD</p>
     </title>
     <p>4.4BSD и многие другие Беркли-реализации поддерживают BPF — пакетный фильтр BSD (BSD Packet Filter). Реализация BPF описана в главе 31 [128]. История BPF, описание псевдопроцессора BPF и сравнение с пакетным фильтром SunOs 4.1.x NIT приведены в [72].</p>
     <p>Каждый канальный уровень вызывает BPF сразу после получения пакета и непосредственно перед его передачей выше, как показано на рис. 29.1.</p>
     <image l:href="#img_155.png"/>
     <p><strong>Рис. 29.1</strong>. Захват пакета с использованием BPF</p>
     <p>Примеры подобных вызовов для интерфейса Ethernet приведены на рис. 4.11 и 4.19 в [128]. Вызов BPF должен произойти как можно скорее после получения пакета и как можно позже перед его передачей, так как это увеличивает точность временных отметок.</p>
     <p>Организовать само по себе перехватывание пакетов из канального уровня не очень сложно, однако преимущество BPF заключается в возможности их фильтрации. Каждое приложение, открывающее устройство BPF, может загрузить свой собственный фильтр, который затем BPF применяет к каждому пакету. В то время как некоторые фильтры достаточно просты (например, при использовании фильтра <code>udp or tcp</code> принимаются только пакеты UDP и TCP), другие фильтры позволяют исследовать значения определенных полей в заголовках пакетов. Например, фильтр</p>
     <p><code>tcp and port 80 and tcp[13:l] &amp; 0x7 != 0</code></p>
     <p>использовался в главе 14 [128] для отбора сегментов TCP, направлявшихся к порту 80 или от него и содержащих флаги SYN, FIN или RST. Выражение <code>tcp [13:1]</code> соответствует однобайтовому значению, начинающемуся с 13-го байта от начала заголовка TCP.</p>
     <p>В BPF реализован основанный на регистрах механизм фильтрации, который применяет специфические для приложений фильтры к каждому полученному пакету. Хотя можно написать свою программу фильтрации на машинном языке псевдопроцессора (он описан в руководстве по использованию BPF), проще всего будет компилировать строки ASCII (такие, как только что показанная строка, начинающаяся с <code>tcp</code>) в машинный язык с помощью функции <code>pcap_compile</code>, о которой мы рассказываем в разделе 29.7.</p>
     <p>В технологии BPF применяются три метода, позволяющие уменьшить накладные расходы на ее использование.</p>
     <p>1. Фильтрация BPF происходит внутри ядра, за счет чего минимизируется количество данных, которые нужно копировать из ядра в приложение. Копирование из пространства ядра в пользовательское пространство является довольно дорогостоящим. Если бы приходилось копировать каждый пакет, у BPF могли бы возникнуть проблемы при попытке взаимодействия с быстрыми каналами.</p>
     <p>2. BPF передает приложению только часть каждого пакета. Здесь речь идет о <emphasis>длине захвата</emphasis> (<emphasis>capture length</emphasis>). Большинству приложений требуется только заголовок пакета, а не содержащиеся в нем данные. Это также уменьшает количество данных, которые BPF должен скопировать в приложение. В программе <code>tcpdump</code>, например, по умолчанию это значение равно 68 байт, и этого достаточно для размещения 14-байтового заголовка Ethernet, 20-байтового заголовка IP, 20-байтового заголовка TCP и 14 байт данных. Но для вывода дополнительной информации по другим протоколам (например, DNS или NFS) требуется, чтобы пользователь увеличил это значение при запуске программы <code>tcpdump</code>.</p>
     <p>3. BPF буферизует данные, предназначенные для приложения, и этот буфер передается приложению только когда он заполнен или когда истекает заданное <emphasis>время ожидания для считывания</emphasis> (<emphasis>read timeout</emphasis>). Это время может быть задано приложением. Программа <code>tcpdump</code>, например, устанавливает время ожидания 1000 мс, а демон RARP задает нулевое время ожидания (поскольку пакетов RARP немного, а сервер RARP должен послать ответ сразу, как только он получает запрос). Назначением буферизации является уменьшение количества системных вызовов. При этом между BPF и приложением происходит обмен тем же количеством пакетов, но за счет того, что уменьшается количество системных вызовов, каждый из которых связан с дополнительными накладными расходами, уменьшается и общий объем этих расходов. Например, на рис. 3.1 [110] сравниваются накладные расходы, возникающие при системном вызове read, когда файл считывается в несколько приемов, причем размер фрагментов варьируется от 1 до 131 072 байт.</p>
     <p>Хотя на рис. 29.1 мы показываем только один буфер, BPF поддерживает по два внутренних буфера для каждого приложения и заполняет один, пока другой копируется в приложение. Эта стандартная технология носит название <emphasis>двойной буферизации</emphasis> (<emphasis>double buffering</emphasis>).</p>
     <p>На рис. 29.1 мы показываем только получение пакетов фильтром BPF: пакеты, приходящие на канальный уровень снизу (из сети) и сверху (IP). Приложение также может записывать в BPF, в результате чего пакеты будут отсылаться по канальному уровню, но большая часть приложений только считывает пакеты из BPF. У нас нет оснований использовать BPF для отправки дейтаграмм IP, поскольку параметр сокета <code>IP_HDRINCL</code> позволяет нам записывать дейтаграммы IP любого типа, включая заголовок IP. (Подобный пример мы показываем в разделе 29.7.) Записывать в BPF можно только с одной целью — чтобы отослать наши собственные сетевые пакеты, не являющиеся дейтаграммами IP. Например, демон RARP делает это для отправки ответов RARP, которые не являются дейтаграммами IP.</p>
     <p>Для получения доступа к BPF необходимо открыть (вызвав функцию <code>open</code>) еще не открытое каким-либо другим процессом устройство BPF. Скажем, можно попробовать <code>/dev/bpf0</code>, и если будет возвращена ошибка <code>EBUSY</code>, то — <code>/dev/bpf1</code>, и т.д. Когда устройство будет открыто, потребуется выполнить примерно 12 команд <code>ioctl</code> для задания характеристик устройства, таких как загрузка фильтра, время ожидания для считывания, размер буфера, присоединение канального уровня к устройству BPF, включение смешанного режима, и т.д. Затем с помощью функций <code>read</code> и <code>write</code> осуществляется ввод и вывод.</p>
    </section>
    <section>
     <title>
      <p>29.3. DLPI: интерфейс поставщика канального уровня</p>
     </title>
     <p>SVR4 обеспечивает доступ к канальному уровню через DLPI (Data Link Provider Interface — интерфейс поставщика канального уровня). DLPI — это не зависящий от протокола интерфейс, разработанный в AT&amp;T и служащий средством связи с сервисами, обеспечиваемыми канальным уровнем [124]. Доступ к DLPI осуществляется посредством отправки и получения сообщений через потоки STREAMS.</p>
     <p>Для подсоединения к канальному уровню приложение просто открывает устройство (например, <code>le0</code>) с помощью команды open и использует запрос <code>DL_ATTACH_REQ</code>. Но для эффективной работы используются два дополнительных модуля: <code>pfmod</code>, который осуществляет фильтрацию внутри ядра, и <code>bufmod</code>, буферизующий данные, предназначенные для приложения. Это показано на рис. 29.2.</p>
     <image l:href="#img_156.png"/>
     <p><strong>Рис. 29.2</strong>. Захват пакета с использованием DLPI, pfmod и bufmod</p>
     <p>Концептуально DLPI аналогичен BPF. <code>pfmod</code> поддерживает фильтрацию внутри ядра, используя псевдопроцессор, a <code>bufmod</code> сокращает количество данных и системных вызовов, поддерживая длину захвата и время ожидания для считывания.</p>
     <p>Одно интересное различие, тем не менее, заключается в том, что для BPF и фильтров <code>pfmod</code> используются разные типы псевдопроцессоров. Фильтр BPF — это <emphasis>ориентированный ациклический граф управления потоком</emphasis> (<emphasis>acyclic control flow graph</emphasis>, <emphasis>CFG</emphasis>), в то время как <code>pfmod</code> использует дерево булевых выражений. В первом случае естественным является отображение в код для вычислительной машины с регистровой организацией, а во втором — в код для машины со стековой организацией [72]. В статье [72] показано, что реализация CFG, используемая в BPF, обычно работает быстрее, чем дерево булевых выражений, в 3-20 раз в зависимости от сложности фильтра.</p>
     <p>Еще одно отличие состоит в том, что BPF всегда выполняет фильтрацию перед копированием пакета, чтобы не копировать те пакеты, которые будут сброшены фильтром. В некоторых реализациях DLPI пакеты сначала копируются в модуль <code>pfmod</code>, который затем может сбрасывать их.</p>
    </section>
    <section>
     <title>
      <p>29.4. Linux: SOCK_PACKET и PF_PACKET</p>
     </title>
     <p>Существует два метода получения пакетов канального уровня в Linux. Первоначальный метод получил более широкое распространение, но является менее гибким. Он состоит в создании сокета типа <code>SOCK_PACKET</code>. Новый метод, предоставляющий больше возможностей для настройки фильтров и оптимизации производительности, состоит в создании сокета семейства <code>PF_PACKET</code>. В любом случае мы должны обладать правами привилегированного пользователя (аналогичные необходимым для создания символьного сокета), а третий аргумент функции socket должен быть ненулевым значением, задающим тип кадра Ethernet. При использовании сокетов <code>PF_PACKET</code> второй аргумент <code>socket</code> может быть константой <code>SOCK_DGRAM</code> (для получения обработанных пакетов без заголовка канального уровня) или <code>SOCK_RAW</code> (для получения пакетов целиком). Сокеты <code>SOCK_PACKET</code> передают пакеты только целиком. Например, для получения всех кадров канального уровня мы пишем:</p>
     <p><code>fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); /* в новых системах */</code></p>
     <p>или</p>
     <p><code>fd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)); /* в старых системах */</code></p>
     <p>В результате этого будут возвращены кадры для всех протоколов, получаемые канальным уровнем. Если нам нужны кадры IPv4, то вызов будет таким:</p>
     <p><code>fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); /* в новых системах */</code></p>
     <p><code>fd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_IP)); /* в старых системах */</code></p>
     <p>Другие константы, которые могут использоваться в качестве последнего аргумента, — это, например, <code>ETH_P_ARP</code> и <code>ETH_P_IPV6</code>.</p>
     <p>Указывая протокол <code>ETH_P_ххх</code>, мы тем самым сообщаем канальному уровню, какой тип из получаемых канальным уровнем кадров передавать сокету. Если канальный уровень поддерживает смешанный режим (например, Ehternet), то устройство тоже должно работать в смешанном режиме. Это осуществляется при помощи параметра сокета <code>PACKET_ADD_MEMBERSHIP</code> с использованием структуры <code>packet_mreq</code>. При этом необходимо указать конкретный интерфейс и задать тип действия <code>PACKET_MR_PROMISC</code>. В старых системах для этого нужно вызвать функцию <code>ioctl</code> с запросом <code>SIOCGIFFLAGS</code> для получения флагов, установить флаг <code>IFF_PROMISC</code> и далее сохранить флаги с помощью <code>SIOCSIFFLAGS</code>. К сожалению, при использовании этого метода программы, работающие в смешанном режиме, могут мешать друг другу, а если в одной из них содержатся ошибки, то она может и не отключить смешанный режим по завершении.</p>
     <p>Сравнивая это средство Linux с BPF и DLPI, мы можем отметить некоторые различия.</p>
     <p>1. В Linux не обеспечивается буферизация. Фильтрация на уровне ядра доступна только в новых системах (при помощи параметра <code>SO_ATTACH_FILTER</code>). Существует обычный буфер приема сокета, но отсутствует возможность буферизации и отправки приложению нескольких кадров с помощью одной операции считывания. Это увеличивает накладные расходы, связанные с копированием потенциально возможных больших объемов данных из ядра в приложение.</p>
     <p>2. В Linux не предусмотрена фильтрация на уровне устройства. Сокеты <code>PF_PACKET</code> могут быть связаны с устройством функцией <code>bind</code>. Если в вызове функции <code>socket</code> указан аргумент <code>ETH_P_IP</code>, то все пакеты IPv4 со всех устройств (например, Ethernet, каналы PPP, каналы SLIP и закольцовка) будут переданы на сокет. Функция <code>recvfrom</code> возвращает общую структуру адреса сокета, а элемент <code>sa_data</code> содержит имя устройства (например, <code>eth0</code>). Тогда приложение само должно игнорировать данные с тех устройств, которые не представляют для него интереса. Здесь мы сталкиваемся фактически с той же проблемой: возможно, что приложение будет получать слишком много данных, особенно в случае наблюдения за высокоскоростной сетью.</p>
    </section>
    <section>
     <title>
      <p>29.5. Libcap: библиотека для захвата пакетов</p>
     </title>
     <p>Библиотека захвата пакетов <code>libcap</code> обеспечивает не зависящий от реализации доступ к средствам операционной системы, с помощью которых осуществляется этот захват. В настоящее время поддерживается только чтение пакетов (хотя добавление нескольких строк кода в библиотеку позволяет также записывать пакеты в некоторых системах). В следующем разделе приводится описание альтернативной библиотеки, которая не только дает возможность записывать пакеты на канальный уровень, но и позволяет конструировать пакеты произвольного типа.</p>
     <p>Сейчас осуществляется поддержка BPF для Беркли-ядер, DLPI для Solaris 2.x, NIT для SunOS 4.1.x, пакетных сокетов (<code>SOCK_PACKET</code>, <code>PF_PACKET</code>) в Linux и нескольких других операционных системах. Библиотека <code>libcap</code> используется программой <code>tcpdump</code>. Всего в библиотеке насчитывается порядка 25 функций, но вместо того чтобы просто описывать их, мы продемонстрируем их фактическое использование на примере, рассматриваемом в следующем разделе. Названия всех функций начинаются с <code>pcap_</code>. Они описаны более подробно на странице руководства, которая называется <code>pcap</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Библиотека libcap находится в свободном доступе по адресу http://www.tcpdump.org/.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>29.6. Libnet: библиотека создания и отправки пакетов</p>
     </title>
     <p>Библиотека <code>libnet</code> предоставляет интерфейс для создания и отправки в сеть пакетов произвольного содержимого. Она обеспечивает доступ на уровне символьных сокетов и доступ к канальному уровню в формате, не зависящем от реализации.</p>
     <p>Библиотека скрывает большую часть деталей формирования заголовков IP, UDP и TCP и обеспечивает приложению простой и переносимый интерфейс для отправки пакетов канального уровня и IP-пакетов через символьные сокеты. Как и <code>libcap</code>, библиотека <code>libnet</code> содержит достаточно много функций. Мы приведем пример использования небольшой их части, предназначенной для работы с символьными сокетами, но в следующем разделе. Для сравнения там же будет приведен код, непосредственно работающий с символьными сокетами. Все функции библиотеки начинаются с префикса <code>libnet_</code>. За более подробным их описанием вы можете обратиться к странице руководства <code>libnet</code> или к доступной в Сети документации.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Библиотека libnet свободно доступна по адресу http://www.packetfactory.net/libnet/. Руководство находится по адресу http://www.packetfactory.net/libnet/manual. На момент написания этой книги в Сети имелось руководство только по устаревшей версии 1.0. Актуальная версия 1.1 имеет значительно отличающийся интерфейс. В нашем примере используется API версии 1.1.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>29.7. Анализ поля контрольной суммы UDP</p>
     </title>
     <section>
      <p>Теперь мы приступаем к рассмотрению примера, в котором отсылается дейтаграмма UDP, содержащая запрос UDP к серверу имен, а затем считывается ответ с помощью библиотеки захвата пакетов. Цель данного примера — установить, вычисляется на сервере имен контрольная сумма UDP или нет. В случае IPv4 вычисление контрольной суммы не является обязательным. В большинстве систем в настоящее время вычисление контрольных сумм по умолчанию включено, но, к сожалению, в более старых системах, в частности SunOS 4.1.x, оно по умолчанию отключено. В настоящее время все системы, а особенно система, в которой работает сервер имен, <emphasis>всегда</emphasis> должны работать с включенными контрольными суммами UDP, поскольку поврежденные (содержащие ошибки) дейтаграммы могут повредить базу данных сервера.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Включение и выключение контрольных сумм обычно осуществляется сразу для всей системы, как показано в приложении Е [111].</p>
      </cite>
      <p>Мы формируем дейтаграмму UDP (запрос DNS) и записываем ее в символьный сокет. Параллельно мы проделаем то же самое с помощью <code>libnet</code>. Для отправки запроса мы могли бы использовать обычный сокет UDP, но мы хотим показать, как использовать параметр сокета <code>IP_HDRINCL</code> для создания полной дейтаграммы IP.</p>
      <p>Нет возможности получить контрольную сумму UDP при чтении из обычного сокета UDP, а также считывать пакеты UDP или TCP, используя символьный сокет (см. раздел 28.4). Следовательно, путем захвата пакетов нам нужно получить целую дейтаграмму UDP, содержащую ответ сервера имен.</p>
      <p>Затем мы исследуем поле контрольной суммы UDP в заголовке UDP, и если оно равно нулю, это означает, что на сервере отключено вычисление контрольной суммы.</p>
      <p>Действие нашей программы иллюстрирует рис. 29.3. Мы записываем наши собственные дейтаграммы UDP в символьный сокет и считываем ответы, используя библиотеку <code>libcap</code>. Обратите внимание, что UDP также получает ответ сервера имен и отвечает сообщением о недоступности порта ICMP, так как ничего не знает о номере порта, выбранном нашим приложением. Сервер имен игнорирует эту ошибку ICMP. Также можно отметить, что написать подобную тестовую программу, использующую TCP, было бы сложнее, даже несмотря на то, что мы с легкостью можем записывать свои собственные сегменты TCP. Дело в том, что любой ответ на сегмент TCP, который мы генерируем, обычно инициирует отправку протоколом TCP ответного сегмента RST туда, куда был послан первый сегмент.</p>
      <image l:href="#img_157.png"/>
      <p><strong>Рис. 29.3</strong>. Приложение, определяющее, включено ли на сервере вычисление контрольных сумм UDP</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Указанную проблему можно обойти. Для этого нужно посылать сегменты TCP с IP- адресом отправителя, который принадлежит присоединенной подсети, но в настоящий момент не присвоен никакому другому узлу. Нужно также добавить данные ARP на посылающем узле для этого нового IP-адреса, чтобы узел отвечал на запросы ARP для него. В результате стек IP на посылающем узле будет игнорировать пакеты, приходящие на этот IP-адрес, в предположении, что посылающий узел не является маршрутизатором.</p>
      </cite>
      <p>На рис. 29.4 приведены функции, используемые в нашей программе.</p>
      <image l:href="#img_158.png"/>
      <p><strong>Рис. 29.4</strong>. Функции, которые используются в программе udpcksum</p>
      <p>В листинге 29.1<a l:href="#n1" type="note">[1]</a> показан заголовочный файл <code>udpcksum.h</code>, в который включен наш базовый заголовочный файл <code>unp.h</code>, а также различные системные заголовки, необходимые для получения доступа к определениям структур для заголовков пакетов IP и UDP.</p>
      <p><strong>Листинг 29.1</strong>. Заголовочный файл udpcksum.h</p>
      <p><code>//udpcksum/udpcksum.h</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;pcap.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 #include &lt;netinet/in_systm.h&gt; /* необходим для ip.h */</code></p>
      <p><code> 4 #include &lt;netinet/in.h&gt;</code></p>
      <p><code> 5 #include &lt;netinet/ip.h&gt;</code></p>
      <p><code> 6 #include &lt;netinet/ip_var.h&gt;</code></p>
      <p><code> 7 #include &lt;netinet/udp.h&gt;</code></p>
      <p><code> 8 #include &lt;netinet/udp_var.h&gt;</code></p>
      <p><code> 9 #include &lt;net/if.h&gt;</code></p>
      <p><code>10 #include &lt;netinet/if_ether.h&gt;</code></p>
      <empty-line/>
      <p><code>11 #define TTL_OUT 64 /* исходящее TTL */</code></p>
      <empty-line/>
      <p><code>12 /* объявление глобальных переменных */</code></p>
      <p><code>13 extern struct sockaddr *dest, *local;</code></p>
      <p><code>14 extern socklen_t destlen, locallen;</code></p>
      <p><code>15 extern int datalink;</code></p>
      <p><code>16 extern char *device;</code></p>
      <p><code>17 extern pcap_t *pd;</code></p>
      <p><code>18 extern int rawfd;</code></p>
      <p><code>19 extern int snaplen;</code></p>
      <p><code>20 extern int verbose;</code></p>
      <p><code>21 extern int zerosum;</code></p>
      <empty-line/>
      <p><code>22 /* прототипы функций */</code></p>
      <p><code>23 void cleanup(int);</code></p>
      <p><code>24 char *next_pcap(int*);</code></p>
      <p><code>25 void open_output(void);</code></p>
      <p><code>26 void open_pcap(void);</code></p>
      <p><code>27 void send_dns_query(void);</code></p>
      <p><code>28 void test_udp(void);</code></p>
      <p><code>29 void udp_write(char*, int);</code></p>
      <p><code>30 struct udpiphdr *udp_read(void);</code></p>
      <p><code>3-10</code> Для работы с полями заголовков IP и UDP требуются дополнительные заголовочные файлы Интернета.</p>
      <p><code>11-30</code> Мы определяем некоторые глобальные переменные и прототипы для своих собственных функций, которые вскоре покажем.</p>
      <p>Первая часть функции <code>main</code> показана в листинге 29.2.</p>
      <p><strong>Листинг 29.2</strong>. Функция main: определения</p>
      <p><code>//udpcksum/main.c</code></p>
      <p><code> 1 #include "udpcksum.h"</code></p>
      <empty-line/>
      <p><code> 2 /* определение глобальных переменных */</code></p>
      <p><code> 3 struct sockaddr *dest, *local;</code></p>
      <p><code> 4 struct sockaddr_in locallookup;</code></p>
      <p><code> 5 socklen_t destlen, locallen;</code></p>
      <empty-line/>
      <p><code> 6 int datalink; /* из pcap_datalink(), файл &lt;net/bpf.h&gt; */</code></p>
      <p><code> 7 char *device; /* устройство pcap */</code></p>
      <p><code> 8 pcap_t *pd; /* указатель на структуру захваченных пакетов */</code></p>
      <p><code> 9 int rawfd; /* символьный сокет */</code></p>
      <p><code>10 int snaplen = 200; /* объем захваченных данных */</code></p>
      <p><code>11 int verbose;</code></p>
      <p><code>12 int zerosum; /* отправка UDP-запроса без контрольной суммы */</code></p>
      <empty-line/>
      <p><code>13 static void usage(const char*);</code></p>
      <empty-line/>
      <p><code>14 int</code></p>
      <p><code>15 main(int argc, char *argv[])</code></p>
      <p><code>16 {</code></p>
      <p><code>17  int c, lopt=0;</code></p>
      <p><code>18  char *ptr, localname[1024], *localport;</code></p>
      <p><code>19  struct addrinfo *aip;</code></p>
      <p>В следующей части функции <code>main</code>, представленной в листинге 29.3, обрабатываются аргументы командной строки.</p>
      <p><strong>Листинг 29.3</strong>. Функция main: обработка аргументов командной строки</p>
      <p><code>//udpcksum/main.c</code></p>
      <p><code>20  opterr = 0; /* отключаем запись сообщений getopt() в stderr */</code></p>
      <p><code>21  while ((с = getopt(argc, argv, "0i:l:v")) != -1) {</code></p>
      <p><code>22   switch (с) {</code></p>
      <empty-line/>
      <p><code>23   case '0':</code></p>
      <p><code>24    zerosum = 1;</code></p>
      <p><code>25    break;</code></p>
      <empty-line/>
      <p><code>26   case 'i';</code></p>
      <p><code>27    device = optarg; /* устройство pcap */</code></p>
      <p><code>28    break;</code></p>
      <empty-line/>
      <p><code>29   case 'l'; /* локальный IP адрес и номер порта; a.b.c.d.p */</code></p>
      <p><code>30    if ((ptr = strrchr(optarg, '.')) == NULL)</code></p>
      <p><code>31     usage("invalid -l option");</code></p>
      <empty-line/>
      <p><code>32    *ptr++ = 0; /* нуль заменяет последнюю точку. */</code></p>
      <p><code>33    local port = ptr; /* имя сервиса или номер порта */</code></p>
      <p><code>34    strncpy(localname, optarg, sizeof(localname));</code></p>
      <p><code>35    lopt = 1;</code></p>
      <p><code>36    break;</code></p>
      <empty-line/>
      <p><code>37   case 'v':</code></p>
      <p><code>38    verbose = 1;</code></p>
      <p><code>39    break;</code></p>
      <empty-line/>
      <p><code>40   case '?':</code></p>
      <p><code>41    usage("unrecognized option");</code></p>
      <p><code>42   }</code></p>
      <p><code>43  }</code></p>
      <subtitle>Обработка аргументов командной строки</subtitle>
      <p><code>20-25</code> Мы вызываем функцию <code>getopt</code> для обработки аргументов командной строки. С помощью параметра <code>-0</code> мы посылаем запросы UDP без контрольной суммы UDP, чтобы выяснить, обрабатываются ли эти дейтаграммы сервером иначе, чем дейтаграммы с контрольной суммой.</p>
      <p><code>26</code> Параметр <code>-i</code> позволяет нам задать интерфейс, на котором будут приниматься ответы сервера. Если этот интерфейс не будет задан, библиотека для захвата пакетов выберет какой-либо интерфейс самостоятельно, но в случае узла с несколькими сетевыми интерфейсами этот выбор может оказаться некорректным. В этом заключается одно из различий между считыванием из обычного сокета и из устройства для захвата пакетов: в первом случае мы можем указать универсальный локальный адрес, что позволяет получать пакеты, прибывающие на любой из сетевых интерфейсов. Но во втором случае при работе с устройством для захвата пакетов мы можем получать пакеты, прибывающие только на конкретный интерфейс.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Можно отметить, что для пакетных сокетов Linux захват пакетов не ограничен одним устройством. Тем не менее библиотека libcap обеспечивает фильтрацию либо по умолчанию, либо согласно заданному нами параметру -i.</p>
      </cite>
      <p><code>29-36</code> Параметр <code>-l</code> позволяет нам задать IP-адрес отправителя и номер порта. В качестве номера порта (или названия службы) берется строка, следующая за последней точкой, а IP-адресом является все, что расположено перед последней точкой.</p>
      <p>Последняя часть функции <code>main</code> показана в листинге 29.4.</p>
      <p><strong>Листинг 29.4</strong>. Функция main: преобразование имен узлов и названий служб, создание сокета</p>
      <p><code>//udpcksum/main.c</code></p>
      <p><code>44  if (optind != argc-2)</code></p>
      <p><code>45   usage("missing &lt;host&gt; and/or &lt;serv&gt;");</code></p>
      <empty-line/>
      <p><code>46  /* преобразование имени получателя и службы */</code></p>
      <p><code>47  aip = Host_serv(argv[optind], argv[optind+1], AF_INET, SOCK_DGRAM);</code></p>
      <p><code>48  dest = aip-&gt;ai_addr; /* не освобождаем память при помощи freeaddrinfo() */</code></p>
      <p><code>49  destlen = aip-&gt;ai_addrlen;</code></p>
      <empty-line/>
      <p><code>50  /*</code></p>
      <p><code>51   * Нужен локальный IP-адрес для указания в UDP-дейтаграммах.</code></p>
      <p><code>52   * Нельзя задать 0 и предоставить выбор уровню IP,</code></p>
      <p><code>53   * потому что адрес нужен для вычисления контрольной суммы.</code></p>
      <p><code>54   * Если указан параметр -1, используем заданные при вызове значения.</code></p>
      <p><code>55   * в противном случае соединяем сокет UDP с адресатом и определяем</code></p>
      <p><code>56   * правильный адрес отправителя.</code></p>
      <p><code>57   */</code></p>
      <p><code>58  if (lopt) {</code></p>
      <p><code>59   /* преобразование локального имени и сервиса */</code></p>
      <p><code>60   aip = Host_serv(localname, localport, AF_INET, SOCK_DGRAM);</code></p>
      <p><code>61   local = aip-&gt;ai_addr; /* не вызываем freeaddrinfo() */</code></p>
      <p><code>62   locallen = aip-&gt;ai_addrlen;</code></p>
      <p><code>63  } else {</code></p>
      <p><code>64   int s;</code></p>
      <p><code>65   s = Socket(AF_INET, SOCK_DGRAM, 0);</code></p>
      <p><code>66   Connect(s, dest, destlen);</code></p>
      <p><code>67   /* ядро выбирает правильный локальный адрес */</code></p>
      <p><code>68   locallen = sizeof(locallookup);</code></p>
      <p><code>69   local = (struct sockaddr*)&amp;locallookup;</code></p>
      <p><code>70   Getsockname(s, local, &amp;locallen);</code></p>
      <p><code>71   if (locallookup.sin_addr.s_addr == htonl(INADDR_ANY))</code></p>
      <p><code>72    err_quit("Can't determine local address - use -l\n");</code></p>
      <p><code>73   close(s);</code></p>
      <p><code>74  }</code></p>
      <empty-line/>
      <p><code>75  open_output(); /* открываем поток вывода (символьный сокет или libnet) */</code></p>
      <p><code>76  open_pcap(); /* открываем устройство захвата пакетов */</code></p>
      <empty-line/>
      <p><code>77  setuid(getuid()); /* права привилегированного пользователя больше</code></p>
      <p><code>                         не нужны */</code></p>
      <empty-line/>
      <p><code>78  Signal(SIGTERM, cleanup);</code></p>
      <p><code>79  Signal(SIGINT, cleanup);</code></p>
      <p><code>80  Signal(SIGHUP, cleanup);</code></p>
      <empty-line/>
      <p><code>81  test_udp();</code></p>
      <empty-line/>
      <p><code>82  cleanup(0);</code></p>
      <p><code>83 }</code></p>
      <subtitle>Обработка имени узла и порта получателя, затем локального имени узла и порта</subtitle>
      <p><code>46-49</code> Мы убеждаемся, что остается ровно два аргумента командной строки: имя узла получателя и название службы. Мы вызываем функцию <code>host_serv</code> для преобразования их в структуру адреса сокета, указатель на которую мы сохраняем в переменной <code>dest</code>.</p>
      <subtitle>Обработка локального имени и порта</subtitle>
      <p><code>50-74</code> Если в командной строке был указан соответствующий параметр, мы преобразуем имя локального узла и номер порта, сохраняя указатель на структуру адреса сокета под именем <code>local</code>. В противном случае для определения локального IP-адреса мы подключаемся через дейтаграммный сокет к нужному адресату и сохраняем полученный при этом локальный адрес под тем же именем <code>local</code>. Поскольку мы формируем собственные заголовки IP и UDP, мы должны знать IP-адрес отправителя при записи дейтаграммы UDP. Нельзя оставить адрес нулевым и предоставить уровню IP выбрать его самостоятельно, потому что адрес является частью псевдозаголовка UDP (о котором мы вскоре расскажем), используемого при вычислении контрольной суммы UDP.</p>
      <subtitle>Создаем символьный сокет и открываем устройство для захвата пакетов</subtitle>
      <p><code>75-76</code> Функция <code>open_output</code>  выбирает метод отправки пакетов (символьный сокет или <code>libnet</code>). Функция <code>open_pcap</code> открывает устройство захвата пакетов. Она будет рассмотрена далее.</p>
      <subtitle>Изменение прав и установка обработчиков сигналов</subtitle>
      <p><code>77-80</code> Для создания символьного сокета необходимо иметь права привилегированного пользователя. Обычно такие привилегии нужны нам для того, чтобы открыть устройство для захвата пакетов, но это зависит от реализации. Например, в случае BPF администратор может установить разрешения для устройств <code>/dev/bpf</code> любым способом в зависимости от того, что требуется для данной системы. Здесь мы не используем эти дополнительные разрешения, предполагая, что для файла программы установлен бит SUID. Процесс выполняется с правами привилегированного пользователя, а когда они становятся не нужны, при вызове функции setuid фактический идентификатор пользователя (real user ID), эффективный идентификатор пользователя (effective user ID) и сохраненный SUID принимают значение фактического идентификатора пользователя (<code>getuid</code>). Мы устанавливаем обработчики сигналов на тот случай, если пользователь завершит программу раньше, чем будут изменены права.</p>
      <subtitle>Выполнение теста и очистка</subtitle>
      <p><code>81-82</code> Функция <code>test_udp</code> (см. листинг 29.6) выполняет тестирование и возвращает управление. Функция <code>cleanup</code> (см. листинг 29.14) выводит итоговую статистику библиотеки захвата пакетов, а затем завершает процесс.</p>
      <p>В листинге 29.5 показана функция <code>open_pcap</code>, которую мы вызвали из функции <code>main</code>, чтобы открыть устройство для захвата пакетов.</p>
      <p><strong>Листинг 29.5</strong>. Функция open_pcap: открытие и инициализация устройства для захвата пакетов</p>
      <p><code>//udpcksum/pcap.c</code></p>
      <p><code> 1 #include "udpcksum.h"</code></p>
      <empty-line/>
      <p><code> 2 #define CMD "udp and src host %s and src port %d"</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 open_pcap(void)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  uint32_t localnet, netmask;</code></p>
      <p><code> 7  char cmd[MAXLINE], errbuf[PCAP_ERRBUF_SIZE], strl[INET_ADDRSTRLEN],</code></p>
      <p><code> 8   str2[INET_ADDRSTRLEN];</code></p>
      <p><code> 9  struct bpf_program fcode;</code></p>
      <empty-line/>
      <p><code>10  if (device == NULL) {</code></p>
      <p><code>11   if ((device = pcap_lookupdev(errbuf)) == NULL)</code></p>
      <p><code>12    err_quit("pcap_lookup: %s", errbuf);</code></p>
      <p><code>13  }</code></p>
      <p><code>14  printf("device = %s\n", device);</code></p>
      <empty-line/>
      <p><code>15  /* жестко задано; promisc=0, to_ms=500 */</code></p>
      <p><code>16  if ((pd = pcap_open_live(device, snaplen, 0, 500, errbuf)) == NULL)</code></p>
      <p><code>17   err_quit("pcap_open_live: %s", errbuf);</code></p>
      <empty-line/>
      <p><code>18  if (pcap_lookupnet(device, &amp;localnet, &amp;netmask, errbuf) &lt; 0)</code></p>
      <p><code>19   err_quit("pcap_lookupnet %s", errbuf);</code></p>
      <p><code>20  if (verbose)</code></p>
      <p><code>21   printf("localnet = %s, netmask = %s\n",</code></p>
      <p><code>22    Inet_ntop(AF_INET, &amp;localnet, str1, sizeof(str1)),</code></p>
      <p><code>23    Inet_ntop(AF_INET, &amp;netmask. str2, sizeof(str2)));</code></p>
      <empty-line/>
      <p><code>24  snprintf(cmd, sizeof(cmd), CMD,</code></p>
      <p><code>25   Sock_ntop_host(dest, destlen),</code></p>
      <p><code>26   ntohs(sock_get_port(dest, destlen)));</code></p>
      <p><code>27  if (verbose)</code></p>
      <p><code>28   printf("cmd = %s\n", cmd);</code></p>
      <p><code>29  if (pcap_compile(pd, &amp;fcode, cmd, 0, netmask) &lt; 0)</code></p>
      <p><code>30   err_quit("pcap_compile: %s", pcap_geterr(pd));</code></p>
      <p><code>31  if (pcap_setfilter(pd, &amp;fcode) &lt; 0)</code></p>
      <p><code>32   err_quit("pcap_setfilter: %s", pcap_geterr(pd));</code></p>
      <empty-line/>
      <p><code>33  if ((datalink = pcap_datalink(pd)) &lt; 0)</code></p>
      <p><code>34   err_quit("pcap_datalink: %s", pcap_geterr(pd));</code></p>
      <p><code>35  if (verbose)</code></p>
      <p><code>36   printf("datalink = %d\n", datalink);</code></p>
      <p><code>37 }</code></p>
      <subtitle>Выбор устройства для захвата пакетов</subtitle>
      <p><code>10-14</code> Если устройство для захвата пакетов не было задано (с помощью параметра командной строки <code>-i</code>), то выбор этого устройства осуществляется с помощью функции <code>pcap_lookupdev</code>. С помощью запроса <code>SIOCGIFCONF</code> функции <code>ioctl</code> выбирается включенное устройство с минимальным порядковым номером, но только не устройство обратной связи. Многие из библиотечных функций <code>pcap</code> возвращают сообщения об ошибках в виде строк. Единственным аргументом функции <code>pcap_lookupdev</code> является массив, в который записывается строка с сообщением об ошибке.</p>
      <subtitle>Открываем устройство</subtitle>
      <p><code>15-17</code> Функция <code>pcap_open_live</code> открывает устройство. Слово <code>live</code> присутствует в названии функции потому, что здесь имеется в виду фактическое устройство для захвата пакетов, а не файл, содержащий предыдущие сохраненные пакеты. Первым аргументом функции является имя устройства, вторым — количество байтов, которое нужно сохранять для каждого пакета (значение <code>shaplen</code>, которое мы инициализировали числом 200 в листинге 29.2), а третий аргумент — это флаг, указывающий на смешанный режим. Четвертый аргумент — это значение времени ожидания в миллисекундах, а пятый — указатель на массив, содержащий сообщения об ошибках.</p>
      <p>Если установлен флаг смешанного режима, интерфейс переходит в этот режим, в результате чего он принимает все пакеты, проходящие по кабелю. Это обычное состояние программы <code>tcpdump</code>. Тем не менее в нашем примере ответы сервера DNS будут посланы непосредственно на наш узел (то есть можно обойтись без смешанного режима).</p>
      <p>Четвертый аргумент — время ожидания при считывании. Вместо того чтобы возвращать пакет процессу каждый раз, когда приходит очередной пакет (что может быть весьма неэффективно, так как в этом случае потребуется выполнять множество операций копирования отдельных пакетов из ядра в процесс), это делается, когда считывающий буфер устройства оказывается заполненным либо когда истекает время ожидания. Если время ожидания при считывании равно нулю, то каждый пакет будет переправляться процессу, как только будет получен.</p>
      <subtitle>Получение сетевого адреса и маски подсети</subtitle>
      <p><code>18-23</code> Функция <code>pcap_lookupnet</code> возвращает сетевой адрес и маску подсети для устройства захвата пакетов. При вызове функции <code>pcap_compile</code>, которая будет вызвана следующей, нужно задать маску подсети, поскольку с помощью маски фильтр пакетов определяет, является ли IP-адрес адресом широковещательной передачи для данной подсети.</p>
      <subtitle>Компиляция фильтра пакетов</subtitle>
      <p><code>24-30</code> Функция <code>pcap_compile</code> получает строку, построенную нами как массив <code>cmd</code>, и компилирует ее, создавая тем самым программу для фильтрации (записывая ее в <code>fcode</code>). Эта программа будет отбирать те пакеты, которые мы хотим получить.</p>
      <subtitle>Загрузка программы фильтрации</subtitle>
      <p><code>31-32</code> Функция <code>pcap_setfilter</code> получает только что скомпилированную программу фильтрации и загружает ее в устройство для захвата пакетов. Таким образом инициируется захват пакетов, выбранных нами путем настройки фильтра.</p>
      <subtitle>Определение типа канального уровня</subtitle>
      <p><code>33-36</code> Функция <code>pcap_datalink</code> возвращает тип канального уровня для устройства захвата пакетов. Эта информация нужна нам при захвате пакетов для того, чтобы определить размер заголовка канального уровня, который будет добавлен в начало каждого считываемого нами пакета (см. листинг 29.10).</p>
      <p>После вызова функции <code>open_pcap</code> функция main вызывает функцию <code>test_udp</code>, показанную в листинге 29.6. Эта функция посылает запрос DNS и считывает ответ сервера.</p>
      <p><strong>Листинг 29.6</strong>. Функция test_udp: отправка запросов и считывание ответов</p>
      <p><code>//udpcksum/udpcksum.c</code></p>
      <p><code>12 void</code></p>
      <p><code>13 test_udp(void)</code></p>
      <p><code>14 {</code></p>
      <p><code>15  volatile int nsent = 0, timeout = 3;</code></p>
      <p><code>16  struct udpiphdr *ui;</code></p>
      <empty-line/>
      <p><code>17  Signal(SIGALRM, sig_alrm);</code></p>
      <empty-line/>
      <p><code>18  if (sigsetjmp(jmpbuf, 1)) {</code></p>
      <p><code>19   if (nsent &gt;= 3)</code></p>
      <p><code>20    err_quit("no response");</code></p>
      <p><code>21   printf("timeout\n");</code></p>
      <p><code>22   timeout *= 2; /* геометрическая прогрессия: 3, 6, 12 */</code></p>
      <p><code>23  }</code></p>
      <p><code>24  canjump = 1; /* siglongjmp разрешен */</code></p>
      <empty-line/>
      <p><code>25  send_dns_query();</code></p>
      <p><code>26  nsent++;</code></p>
      <empty-line/>
      <p><code>27  alarm(timeout);</code></p>
      <p><code>28  ui = udp_read();</code></p>
      <p><code>29  canjump = 0;</code></p>
      <p><code>30  alarm(0);</code></p>
      <empty-line/>
      <p><code>31  if (ui-&gt;ui_sum == 0)</code></p>
      <p><code>32   printf("UDP checksums off\n");</code></p>
      <p><code>33  else</code></p>
      <p><code>34   printf("UDP checksums on\n");</code></p>
      <p><code>35  if (verbose)</code></p>
      <p><code>36   printf("received UDP checksum = %x\n", ntohs(ui-&gt;ui_sum));</code></p>
      <p><code>37 }</code></p>
      <subtitle>Переменные volatile</subtitle>
      <p><code>15</code> Нам нужно, чтобы две динамические локальные переменные <code>nsent</code> и <code>timeout</code> сохраняли свои значения после возвращения <code>siglongjmp</code> из обработчика сигнала в нашу функцию. Реализация допускает восстановление значений динамических локальных переменных, предшествовавших вызову функции <code>sigsetjump</code> [110, с. 178], но добавление спецификатора <code>volatile</code> предотвращает это восстановление.</p>
      <subtitle>Установление обработчика сигналов и буфера перехода</subtitle>
      <p><code>15-16</code> Для сигнала <code>SIGALRM</code> устанавливается обработчик сигнала, а функция <code>sigsetjmp</code> устанавливает буфер перехода для функции <code>siglongjmp</code>. (Эти две функции подробно описаны в разделе 10.15 [110].) Значение 1 во втором аргументе функции <code>sigsetjmp</code> указывает, что требуется сохранить текущую маску сигнала, так как мы будем вызывать функцию <code>siglongjmp</code> из нашего обработчика сигнала.</p>
      <subtitle>Функция siglongjmp</subtitle>
      <p><code>19-23</code> Этот фрагмент кода выполняется, только когда функция <code>siglongjmp</code> вызывается из нашего обработчика сигнала. Вызов указывает на возникновение условий, при которых мы входим в состояние ожидания: мы отправили запрос, на который не пришло никакого ответа. Если после того, как мы отправим три запроса, ответа не будет, мы прекращаем выполнение кода. По истечении времени ожидания, отведенного на получение ответа, мы выводим соответствующее сообщение и увеличиваем значение времени ожидания в два раза, то есть задаем <emphasis>экспоненциальное смещение</emphasis> (<emphasis>exponential backoff</emphasis>), которое также описано в разделе 20.5. Первое значение времени ожидания равно 3 с, затем — 6 с и 12 с.</p>
      <p>Причина, по которой в этом примере мы используем функции <code>sigsetjmp</code> и <code>siglongjmp</code>, вместо того чтобы просто перехватывать ошибку <code>EINTR</code> (как мы поступили в листинге 14.1), заключается в том, что библиотечные функции захвата пакетов (которые вызываются из нашей функции <code>udp_read</code>) заново запускают операцию чтения в случае возвращения ошибки <code>EINTR</code>. Поскольку мы не хотим модифицировать библиотечные функции, единственным решением для нас является перехватывание сигнала <code>SIGALRM</code> и выполнение нелокального перехода (оператора <code>goto</code>), который возвращает управление в наш код, а не в библиотечную функцию.</p>
      <subtitle>Отправка запроса DNS и считывание ответа</subtitle>
      <p><code>25-26</code> Функция <code>send_dns_query</code> (см. листинг 29.8) отправляет запрос DNS на сервер имен. Функция <code>dns_read</code> считывает ответ. Мы вызываем функцию <code>alarm</code> для предотвращения «вечной» блокировки функции <code>read</code>. Если истекает заданное (в секундах) время ожидания, генерируется сигнал <code>SIGALRM</code>, и наш обработчик сигнала вызывает функцию <code>siglongjmp</code>.</p>
      <subtitle>Анализ полученной контрольной суммы UDP</subtitle>
      <p><code>27-32</code> Если значение полученной контрольной суммы UDP равно нулю, это значит, что сервер не вычислил и не отправил контрольную сумму.</p>
      <p>В листинге 29.7 показана наша функция <code>sig_alrm</code> — обработчик сигнала <code>SIGALRM</code>.</p>
      <p><strong>Листинг 29.7</strong>. Функция sig_alrm: обработка сигнала SIGALRM</p>
      <p><code>//udpcksum/udpcksum.c</code></p>
      <p><code> 1 #include "udpcksum.h"</code></p>
      <p><code> 2 #include &lt;setjmp.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 static sigjmp_buf jmpbuf;</code></p>
      <p><code> 4 static int canjump;</code></p>
      <empty-line/>
      <p><code> 5 void</code></p>
      <p><code> 6 sig_alrm(int signo)</code></p>
      <p><code> 7 {</code></p>
      <p><code> 8  if (canjump == 0)</code></p>
      <p><code> 9   return;</code></p>
      <p><code>10  siglongjmp(jmpbuf, 1);</code></p>
      <p><code>11 }</code></p>
      <p><code>8-10</code> Флаг <code>canjump</code> был установлен в листинге 29.6 после инициализации буфера перехода функцией <code>sigsetjmp</code>. Если флаг был установлен, в результате вызова функции <code>siglongjmp</code> управление осуществляется таким образом, как если бы функция <code>sigsetjmp</code> из листинга 29.6 возвратила бы значение 1.</p>
      <p>В листинге 29.8 показана функция <code>send_dns_query</code>, посылающая запрос UDP на сервер DNS. Эта функция формирует запрос DNS.</p>
      <p><strong>Листинг 29.8</strong>. Функция send_dns_query: отправка запроса UDP на сервер DNS</p>
      <p><code>//udpcksum/senddnsquery-raw.c</code></p>
      <p><code> 6 void</code></p>
      <p><code> 7 send_dns_query(void)</code></p>
      <p><code> 8 {</code></p>
      <p><code> 9  size_t nbytes;</code></p>
      <p><code>10  char *buf, *ptr;</code></p>
      <empty-line/>
      <p><code>11  buf = Malloc(sizeof(struct udpiphdr) + 100);</code></p>
      <p><code>12  ptr = buf + sizeof(struct udpiphdr); /* место для заголовков IP и UDP */</code></p>
      <empty-line/>
      <p><code>13  *((uint16_t*)ptr) = htons(1234); /* идентификатор */</code></p>
      <p><code>14  ptr += 2;</code></p>
      <p><code>15  *((uint16_t*)ptr) = htons(0x0100); /* флаги */</code></p>
      <p><code>16  ptr += 2;</code></p>
      <p><code>17  *((uint16_t*)ptr) = htons(1); /* количество запросов */</code></p>
      <p><code>18  ptr += 2;</code></p>
      <p><code>19  *((uint16_t*)ptr) = 0; /* количество записей в ответе */</code></p>
      <p><code>20  ptr += 2;</code></p>
      <p><code>21  *((uint16_t*)ptr) = 0; /* количество авторитетных записей */</code></p>
      <p><code>22  ptr += 2;</code></p>
      <p><code>23  *((uint16_t*)ptr) = 0; /* количество дополнительных записей */</code></p>
      <p><code>24  ptr += 2;</code></p>
      <empty-line/>
      <p><code>25  memcpy(ptr, "\001a\014root-servers\003net\000", 20);</code></p>
      <p><code>26  ptr += 20;</code></p>
      <p><code>27  *((uint16_t*)ptr) = htons(1); /* тип запроса = А */</code></p>
      <p><code>28  ptr += 2;</code></p>
      <p><code>29  *((uint16_t*)ptr) = htons(1); /* класс запроса = 1 (IP-адрес) */</code></p>
      <p><code>30  ptr += 2;</code></p>
      <empty-line/>
      <p><code>31  nbytes = (ptr - buf) - sizeof(struct udpiphdr);</code></p>
      <p><code>32  udp_write(buf, mbytes),</code></p>
      <p><code>33  if (verbose)</code></p>
      <p><code>35  printf("sent: %d bytes of data\n", nbytes);</code></p>
      <p><code>36 }</code></p>
      <subtitle>Инициализация указателя на буфер</subtitle>
      <p><code>11-12</code> В буфере <code>buf</code> имеется место для 20-байтового заголовка IP, 8-байтового заголовка UDP и еще 100 байт для пользовательских данных. Указатель <code>ptr</code> установлен на первый байт пользовательских данных.</p>
      <subtitle>Формирование запроса DNS</subtitle>
      <p><code>13-24</code> Для понимания деталей устройства дейтаграммы UDP требуется понимание формата сообщения DNS. Эту информацию можно найти в разделе 14.3 [111]. Мы присваиваем полю идентификации значение 1234, сбрасываем флаги, задаем количество запросов — 1, а затем обнуляем количество записей ресурсов (RR, resource records), получаемых в ответ, количество RR, определяющих полномочия, и количество дополнительных RR.</p>
      <p><code>25-30</code> Затем мы формируем простой запрос, который располагается после заголовка: запрос типа А IP-адреса узла <code>a.root-servers.net</code>. Это доменное имя занимает 20 байт и состоит из 4 фрагментов: однобайтовая часть <code>a</code>, 12-байтовая часть <code>root-servers</code>, 3-байтовая часть <code>net</code> и корневая часть, длина которой занимает 0 байт. Тип запроса 1 (так называемый запрос типа А), и класс запроса также 1.</p>
      <subtitle>Запись дейтаграммы UDP</subtitle>
      <p><code>31-32</code> Это сообщение состоит из 36 байт пользовательских данных (восемь 2-байтовых полей и 20-байтовое доменное имя). Мы вызываем нашу функцию <code>udp_write</code> для формирования заголовков UDP и IP и последующей записи дейтаграммы UDP в наш символьный сокет.</p>
      <p>В листинге 29.9 показана функция <code>open_output</code>, работающая с символьными сокетами.</p>
      <p><strong>Листинг 29.9</strong>. Функция open_output: подготовка символьного сокета</p>
      <p><code> 2 int rawfd; /* символьный сокет */</code></p>
      <empty-line/>
      <p><code> 3 void</code></p>
      <p><code> 4 open_output(void)</code></p>
      <p><code> 5 {</code></p>
      <p><code> 6  int on=1;</code></p>
      <p><code> 7  /*</code></p>
      <p><code> 8   * Для отправки IP-дейтаграмм нужен символьный сокет</code></p>
      <p><code> 9   * Для его создания нужны права привилегированного пользователя.</code></p>
      <p><code>10   * Кроме того, необходимо указать параметр сокета IP_HDRINCL.</code></p>
      <p><code>11   */</code></p>
      <p><code>12  rawfd = Socket(dest-&gt;sa_family, SOCK_RAW, 0);</code></p>
      <empty-line/>
      <p><code>13  Setsockopt(rawfd, IPPROTO_IP, IP_HDRINCL, &amp;on., sizeof(on));</code></p>
      <p><code>14 }</code></p>
      <subtitle>Объявление дескриптора символьного сокета</subtitle>
      <p><code>2</code> Мы объявляем глобальную переменную, в которой будет храниться дескриптор символьного сокета.</p>
      <subtitle>Создание сокета и установка IP_HDRINCL</subtitle>
      <p><code>7-13</code> Мы создаем символьный сокет и включаем параметр сокета <code>IP_HDRINCL</code>. Это позволяет нам формировать IP-дейтаграммы целиком, включая заголовок IP.</p>
      <p>В листинге 29.10 показана наша функция <code>udp_write</code>, которая формирует заголовки IP и UDP, а затем записывает дейтаграмму в символьный сокет.</p>
      <p><strong>Листинг 29.10</strong>. Функция udp_write: формирование заголовков UDP и IP и запись дейтаграммы IP в символьный сокет</p>
      <p><code>//udpcksum/udpwrite.c</code></p>
      <p><code>19 void</code></p>
      <p><code>20 udp_write(char *buf, int userlen)</code></p>
      <p><code>21 {</code></p>
      <p><code>22  struct udpiphdr *ui;</code></p>
      <p><code>23  struct ip *ip;</code></p>
      <empty-line/>
      <p><code>24  /* заполнение заголовка и вычисление контрольной суммы */</code></p>
      <p><code>25  ip = (struct ip*)buf;</code></p>
      <p><code>26  ui = (struct udpiphdr*)buf;</code></p>
      <p><code>27  bzero(ui, sizeof(*ui));</code></p>
      <p><code>28  /* добавляем 8 к длине псевдозаголовка */</code></p>
      <p><code>29  ui-&gt;ui_len = htons((uint16_t)(sizeof(struct udphdr) + userlen));</code></p>
      <p><code>30  /* добавление 28 к длине IP-дейтаграммы */</code></p>
      <p><code>31  userlen += sizeof(struct udpiphdr);</code></p>
      <empty-line/>
      <p><code>32  ui-&gt;ui_pr = IPPROTO_UDP;</code></p>
      <p><code>33  ui-&gt;ui_src.s_addr = ((struct sockaddr_in*)local)-&gt;sin_addr.s_addr;</code></p>
      <p><code>34  ui-&gt;ui_dst.s_addr = ((struct sockaddr_in*)dest)-&gt;sin_addr.s_addr;</code></p>
      <p><code>35  ui-&gt;ui_sport = ((struct sockaddr_in*)local)-&gt;sin_port;</code></p>
      <p><code>36  ui-&gt;ui_dport = ((struct sockaddr_in*)dest)-&gt;sin_port;</code></p>
      <p><code>37  ui-&gt;ui_ulen = ui-&gt;ui_len;</code></p>
      <p><code>38  if (zerosum == 0) {</code></p>
      <p><code>39 #if 1 /* заменить на if 0 для Solaris 2.x. x &lt; 6 */</code></p>
      <p><code>40   if ((ui-&gt;ui_sum = m_cksum((u_int16_t*)in, userlen)) == 0)</code></p>
      <p><code>41   ui-&gt;ui_sum = 0xffff;</code></p>
      <p><code>42 #else</code></p>
      <p><code>43   ui-&gt;ui_sum = ui-&gt;ui_len;</code></p>
      <p><code>44 #endif</code></p>
      <p><code>45  }</code></p>
      <empty-line/>
      <p><code>46  /* заполнение оставшейся части IP-заголовка */</code></p>
      <p><code>47  /* функция p_output() вычисляет и сохраняет контрольную сумму IP */</code></p>
      <p><code>48  ip-&gt;ip_v = IPVERSION;</code></p>
      <p><code>49  ip-&gt;ip_hl = sizeof(struct ip) &gt;&gt; 2;</code></p>
      <p><code>50  ip-&gt;ip_tos = 0;</code></p>
      <p><code>51 #if defined(linux) || defined(__OpenBSD__)</code></p>
      <p><code>52  ip-&gt;ip_len = htons(userlen); /* сетевой порядок байтов */</code></p>
      <p><code>53 #else</code></p>
      <p><code>54  ip-&gt;ip_len = userlen; /* порядок байтов узла */</code></p>
      <p><code>55 #endif</code></p>
      <p><code>56  ip-&gt;ip_id = 0; /* это пусть устанавливает уровень IP */</code></p>
      <p><code>57  ip-&gt;ip_off = 0; /* смещение флагов, флаги MF и DF */</code></p>
      <p><code>58  ip-&gt;ip_ttl = TTL_OUT;</code></p>
      <p><code>59  Sendto(rawfd, buf, userlen, 0, dest, destlen);</code></p>
      <p><code>60 }</code></p>
      <subtitle>Инициализация указателей на заголовки пакетов</subtitle>
      <p><code>24-26</code> Указатель <code>ip</code> указывает на начало заголовка IP (структуру <code>ip</code>), а указатель <code>ui</code> указывает на то же место, но структура <code>udpiphdr</code> является объединением заголовков IP и UDP.</p>
      <subtitle>Обнуление заголовка</subtitle>
      <p><code>27</code> Мы явным образом записываем в заголовок нули, чтобы предотвратить учет случайного мусора, который мог остаться в буфере, при вычислении контрольной суммы.</p>
      <subtitle>Обновление значений длины</subtitle>
      <p><code>28-31</code> Переменная <code>ui_len</code> — это длина дейтаграммы UDP: количество байтов пользовательских данных плюс размер заголовка UDP (8 байт). Переменная <code>userlen</code> (количество байтов пользовательских данных, которые следуют за заголовком UDP) увеличивается на 28 (20 байт на заголовок IP и 8 байт на заголовок UDP), для того чтобы соответствовать настоящему размеру дейтаграммы IP.</p>
      <subtitle>Заполнение заголовка UDP и вычисление контрольной суммы UDP</subtitle>
      <p><code>32-45</code> При вычислении контрольной суммы UDP учитывается не только заголовок и данные UDP, но и поля заголовка IP. Эти дополнительные поля заголовка IP образуют то, что называется <emphasis>псевдозаголовком</emphasis> (<emphasis>pseudoheader</emphasis>). Включение псевдозаголовка обеспечивает дополнительную проверку на то, что если значение контрольной суммы верно, то дейтаграмма была доставлена на правильный узел и с правильным кодом протокола. В указанных строках располагаются операторы инициализации полей в IP-заголовке, формирующих псевдозаголовок. Данный фрагмент кода несколько запутан, но его объяснение приводится в разделе 23.6 [128]. Конечным результатом является запись контрольной суммы UDP в поле <code>ui_sum</code>, если не установлен флаг <code>zerosum</code> (что соответствует наличию аргумента командной строки -0).</p>
      <p>Если при вычислении контрольной суммы получается 0, вместо него записывается значение <code>0xffff</code>. В обратном коде эти числа совпадают, но протокол UDP устанавливает контрольную сумму в нуль, чтобы обозначить, что она вовсе не была вычислена. Обратите внимание, что в листинге 28.10 мы не проверяем, равно ли значение контрольной суммы нулю: дело в том, что в случае ICMPv4 нулевое значение контрольной суммы не означает ее отсутствия.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Следует отметить, что в Solaris 2.x, где x&lt;6, в случаях, когда дейтаграммы UDP или сегменты TCP отправляются с символьного сокета при установленном параметре IP_HDRINCL, возникает ошибка. Контрольную сумму вычисляет ядро, а мы должны установить поле ui_sum равным длине дейтаграммы UDP.</p>
      </cite>
      <subtitle>Заполнение заголовка IP</subtitle>
      <p><code>36-49</code> Поскольку мы установили параметр сокета <code>IP_HDRINCL</code>, нам следует заполнить большую часть полей в заголовке IP. (В разделе 28.3 обсуждается запись в символьный сокет при включенном параметре <code>IP_HDRINCL</code>.) Мы присваиваем полю идентификации нуль (<code>ip_id</code>), что указывает IP на необходимость задания значения этого поля. IP также вычисляет контрольную сумму IP, а функция <code>sendto</code> записывает дейтаграмму IP.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Обратите внимание, что поле ip_len может иметь либо сетевой порядок байтов, либо порядок байтов узла. Это типичная проблема с совместимостью, возникающая при использовании символьных сокетов.</p>
      </cite>
      <p>Следующая функция — это <code>udp_read</code>, показанная в листинге 29.11. Она вызывается из кода, представленного в листинге 29.6.</p>
      <p><strong>Листинг 29.11</strong>. Функция udp_read: чтение очередного пакета из устройства захвата пакетов</p>
      <p><code>//udpcksum/udpread.c</code></p>
      <p><code> 7 struct udpiphdr*</code></p>
      <p><code> 8 udp_read(void)</code></p>
      <p><code> 9 {</code></p>
      <p><code>10  int len;</code></p>
      <p><code>11  char *ptr;</code></p>
      <p><code>12  struct ether_header *eptr;</code></p>
      <empty-line/>
      <p><code>13  for (;;) {</code></p>
      <p><code>14   ptr = next_pcap(&amp;len);</code></p>
      <empty-line/>
      <p><code>15   switch (datalink) {</code></p>
      <p><code>16   case DLT_NULL: /* заголовок обратной петли = 4 байта */</code></p>
      <p><code>17    return (udp_check(ptr + 4, len — 4));</code></p>
      <empty-line/>
      <p><code>18   case DLT_EN10MB:</code></p>
      <p><code>19    eptr = (struct ether_header*)ptr;</code></p>
      <p><code>20    if (ntohs(eptr-&gt;ether_type) != ETHERTYPE_IP)</code></p>
      <p><code>21     err_quit("Ethernet type not IP", ntohs(eptr-&gt;ether_type));</code></p>
      <p><code>22    return (udp_check(ptr + 14, len — 14));</code></p>
      <empty-line/>
      <p><code>23   case DLT_SLIP: /* заголовок SLIP = 24 байта */</code></p>
      <p><code>24    return (udp_check(ptr + 24, len — 24));</code></p>
      <empty-line/>
      <p><code>25   case DLT_PPP: /* заголовок PPP = 24 байта */</code></p>
      <p><code>26    return (udp_check(ptr + 24, len — 24));</code></p>
      <empty-line/>
      <p><code>27   default:</code></p>
      <p><code>28    err_quit("unsupported datalink (%d)", datalink);</code></p>
      <p><code>29   }</code></p>
      <p><code>30  }</code></p>
      <p><code>31 }</code></p>
      <p><code>14-29</code> Наша функция <code>next_pcap</code> (см. листинг 29.12) возвращает следующий пакет из устройства захвата пакетов. Поскольку заголовки канального уровня различаются в зависимости от фактического типа устройства, мы применяем ветвление в зависимости от значения, возвращаемого функцией <code>pcap_datalink</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Сдвиги на 4, 14 и 24 байта объясняются на рис. 31.9 [128]. Сдвиг, равный 24 байтам, показанный для заголовков SLIP и PPP, применяется в BSD/OS 2.1.</p>
       <p>Несмотря на то, что в названии DLT_EN10MB фигурирует обозначение «10МВ», этот тип канального уровня используется для сетей Ethernet, в которых скорость передачи данных равна 100 Мбит/с.</p>
      </cite>
      <p>Наша функция <code>udp_check</code> (см. листинг 29.13) исследует пакет и проверяет поля в заголовках IP и UDP.</p>
      <p>В листинге 29.12 показана функция <code>next_pcap</code>, возвращающая следующий пакет из устройства захвата пакетов.</p>
      <p><strong>Листинг 29.12</strong>. Функция next_pcap: возвращает следующий пакет</p>
      <p><code>//udpcksum/pcap.c</code></p>
      <p><code>38 char*</code></p>
      <p><code>39 next_pcap(int *len)</code></p>
      <p><code>40 {</code></p>
      <p><code>41  char *ptr;</code></p>
      <p><code>42  struct pcap_pkthdr hdr;</code></p>
      <empty-line/>
      <p><code>43  /* продолжаем следить, пока пакет не будет готов */</code></p>
      <p><code>44  while ((ptr = (char*)pcap_next(pd, &amp;hdr)) == NULL);</code></p>
      <empty-line/>
      <p><code>45  *len = hdr.caplen; /* длина захваченного пакета */</code></p>
      <p><code>46  return (ptr);</code></p>
      <p><code>47 }</code></p>
      <p><code>43-44</code> Мы вызываем библиотечную функцию <code>pcap_next</code>, возвращающую следующий пакет. Указатель на пакет является возвращаемым значением данной функции, а второй аргумент указывает на структуру <code>pcap_pkthdr</code>, которая тоже возвращается заполненной:</p>
      <p><code>struct pcap_pkthdr {</code></p>
      <p><code> struct timeval ts;     /* временная метка */</code></p>
      <p><code> bpf_u_int32    caplen; /* длина захваченного фрагмента */</code></p>
      <p><code> bpf_u_int32    len;    /* полная длина пакета, находящегося в канале */</code></p>
      <p><code>};</code></p>
      <p>Временная отметка относится к тому моменту, когда пакет был считан устройством захвата пакетов, в противоположность моменту фактической передачи пакета процессу, которая может произойти чуть позже. Переменная <code>caplen</code> содержит длину захваченных данных (вспомним, что в листинге 29.2 нашей переменной <code>shaplen</code> было присвоено значение 200 и она являлась вторым аргументом функции <code>pcap_open_live</code> в листинге 29.5). Назначение устройства захвата пакетов состоит в захвате заголовков, а не всего содержимого каждого пакета. Переменная <code>len</code> — это полная длина пакета, находящегося в канале. Значение <code>caplen</code> будет всегда меньше или равно значению <code>len</code>.</p>
      <p><code>45-46</code> Перехваченная часть пакета возвращается через указатель (аргумент функции), и возвращаемым значением функции является указатель на пакет. Следует помнить, что указатель на пакет указывает фактически на заголовок канального уровня, который представляет собой 14-байтовый заголовок Ethernet в случае кадра Ethernet или 4-байтовый псевдоканальный (pseudo-link) заголовок в случае закольцовки на себя.</p>
      <p>Если мы посмотрим на библиотечную реализацию функции <code>pcap_next</code>, мы увидим, что между различными функциями существует некоторое «разделение труда», схематически изображенное на рис. 29.5. Наше приложение вызывает функции <code>pcap_</code>, среди которых есть как зависящие, так и не зависящие от устройства захвата пакетов. Например, мы показываем, что реализация BPF вызывает функцию <code>read</code>, в то время как реализация DLPI вызывает функцию <code>getmsg</code>, а реализация Linux вызывает <code>recvfrom</code>.</p>
      <image l:href="#img_159.png"/>
      <p><strong>Рис. 29.5</strong>. Организация вызовов функций для чтения из библиотеки захвата пакетов</p>
      <p>Наша функция <code>udp_check</code> проверяет различные поля в заголовках IP и UDP. Она показана в листинге 29.13. Эту проверку необходимо выполнить, так как при получении пакета от устройства захвата пакетов уровень IP не замечает этого пакета. Для символьного сокета это не так.</p>
      <p><code>44-61</code> Длина пакета должна включать хотя бы заголовки IP и UDP. Версия IP проверяется вместе с длиной и контрольной суммой заголовка IP. Если поле протокола указывает на дейтаграмму UDP, функция возвращает указатель на объединенный заголовок IP/UDP. В противном случае программа завершается, так как фильтр захвата пакетов, заданный при вызове функции <code>pcap_setfilter</code> в листинге 29.5, не должен возвращать пакеты никакого другого типа.</p>
      <p><strong>Листинг 29.13</strong>. Функция udp_check: проверка полей в заголовках IP и UDP</p>
      <p><code>//udpcksum/udpread.c</code></p>
      <p><code>38 struct udpiphdr*</code></p>
      <p><code>39 udp_check(char *ptr, int len)</code></p>
      <p><code>40 {</code></p>
      <p><code>41  int hlen;</code></p>
      <p><code>42  struct ip *ip;</code></p>
      <p><code>43  struct udpiphdr *ui;</code></p>
      <empty-line/>
      <p><code>44  if (len &lt; sizeof(struct ip) + sizeof(struct udphdr))</code></p>
      <p><code>45   err_quit("len = %d", len);</code></p>
      <empty-line/>
      <p><code>46  /* минимальная проверка заголовка IP */</code></p>
      <p><code>47  ip = (struct ip*)ptr;</code></p>
      <p><code>48  if (ip-&gt;ip_v != IPVERSION)</code></p>
      <p><code>49   err_quit("ip_v = %d", ip-&gt;ip_v);</code></p>
      <p><code>50  hlen = ip-&gt;ip_hl &lt;&lt; 2;</code></p>
      <p><code>51  if (hlen &lt; sizeof(struct ip))</code></p>
      <p><code>52   err_quit("ip_hl = %d", ip-&gt;ip_hl);</code></p>
      <p><code>53  if (len &lt; hlen + sizeof(struct udphdr))</code></p>
      <p><code>54   err_quit("len = %d, hlen = %d", len, hlen);</code></p>
      <empty-line/>
      <p><code>55  if ((ip-&gt;ip_sum = in_cksum((u_short )ip, hlen)) != 0)</code></p>
      <p><code>56   err_quit("ip checksum error");</code></p>
      <empty-line/>
      <p><code>57  if (ip-&gt;ip_p == IPPROTO_UDP) {</code></p>
      <p><code>58   ui = (struct udpiphdr*)ip;</code></p>
      <p><code>59   return (ui);</code></p>
      <p><code>60  } else</code></p>
      <p><code>61  err_quit("not a UDP packet");</code></p>
      <p><code>62 }</code></p>
      <p>Функция <code>cleanup</code>, показанная в листинге 29.14, вызывается из функции <code>main</code> непосредственно перед тем, как программа завершается, а также вызывается в качестве обработчика сигнала в случае, если пользователь прерывает выполнение программы (см. листинг 29.4).</p>
      <p><strong>Листинг 29.14</strong>. Функция cleanup</p>
      <p><code>//udpcksum/cleanup.c</code></p>
      <p><code> 2 void</code></p>
      <p><code> 3 cleanup(int signo)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5  struct pcap_stat stat;</code></p>
      <empty-line/>
      <p><code> 6  fflush(stdout);</code></p>
      <p><code> 7  putc('\n', stdout);</code></p>
      <empty-line/>
      <p><code> 8  if (verbose) {</code></p>
      <p><code> 9   if (pcap_stats(pd, &amp;stat) &lt; 0)</code></p>
      <p><code>10    err_quit("pcap_stats: %s\n", pcap_geterr(pd));</code></p>
      <p><code>11   printf("%d packets received by filter\n", stat.ps_recv);</code></p>
      <p><code>12   printf("%d packets dropped by kernel\n", stat.ps_drop);</code></p>
      <p><code>13  }</code></p>
      <p><code>14  exit(0);</code></p>
      <p><code>15 }</code></p>
      <subtitle>Получение и вывод статистики по захвату пакетов</subtitle>
      <p><code>8-13</code> Функция <code>pcap_stats</code> получает статистику захвата пакетов: общее количество полученных фильтром пакетов и количество пакетов, переданных ядру.</p>
     </section>
     <section>
      <title>
       <p>Пример</p>
      </title>
      <p>Сначала мы запустим нашу программу с аргументом командной строки <code>-0</code> и убедимся, что сервер имен отвечает на приходящие дейтаграммы, не содержащие контрольной суммы. Мы также задаем флаг <code>-v</code>.</p>
      <p><code>macosx # <strong>udpcksum -i en1 -0 -v bridget.rudoff.com domain</strong></code></p>
      <p><code>device = en1</code></p>
      <p><code>local net = 172.24.37.64. netmask = 255.255.255.224</code></p>
      <p><code>cmd = udp and src host 206.168.112.96 and src port 53</code></p>
      <p><code>datalink = 1</code></p>
      <p><code>sent: 36 bytes of data</code></p>
      <p><code>UDP checksums on</code></p>
      <p><code>received UDP checksum = 9d15</code></p>
      <empty-line/>
      <p><code>3 packets received by filter</code></p>
      <p><code>0 packets dropped by kernel</code></p>
      <p>Затем мы запускаем нашу программу, обращаясь к локальному серверу имен, в котором отключен подсчет контрольных сумм. Чем дальше, тем сложнее становится найти сервер имен с отключенным подсчетом контрольных сумм.</p>
      <p><code>macosx # <strong>udpcksum -i en1 -v freebsd4.unpbook.com domain</strong></code></p>
      <p><code>device = en1</code></p>
      <p><code>localnet = 172.24.37.64, netmask = 255.255.255.224</code></p>
      <p><code>cmd = udp and src host 172.24.37.94 and src port 53</code></p>
      <p><code>datalink = 1</code></p>
      <p><code>sent: 36 bytes of data</code></p>
      <p><code>UDP checksums off</code></p>
      <p><code>received UDP checksum = 0</code></p>
      <empty-line/>
      <p><code>3 packets received by filter</code></p>
      <p><code>0 packets dropped by kernel</code></p>
     </section>
     <section>
      <title>
       <p>Функции libnet</p>
      </title>
      <p>В этом разделе приводятся альтернативные версии функций <code>open_output</code> и <code>send_dns_query</code>, в которых вместо символьных сокетов используются функции библиотеки <code>libnet</code>. Библиотека <code>libnet</code> берет на себя заботу о множестве деталей, в частности, устраняет проблемы с переносимостью, связанные с вычислением контрольных сумм и порядком байтов в заголовке, о которых мы говорили выше. Функция open output представлена в листинге 29.15.</p>
      <p><strong>Листинг 29.15</strong>. Функция open_output, использующая libnet</p>
      <p><code>//udpcksum/senddnsquery-libnet.c</code></p>
      <p><code> 7 static libnet_t *l; /* дескриптор libnet */</code></p>
      <empty-line/>
      <p><code> 8 void</code></p>
      <p><code> 9 open_output(void)</code></p>
      <p><code>10 {</code></p>
      <p><code>11  char errbuf[LIBNET_ERRBUF_SIZE];</code></p>
      <p><code>12  /* инициализация libnet с символьным сокетом IPv4 */</code></p>
      <p><code>13  l = libnet_init(LIBNET_RAW4, NULL, errbuf);</code></p>
      <p><code>14  if (l == NULL) {</code></p>
      <p><code>15   err_quit("Can't initialize libnet: %s", errbuf);</code></p>
      <p><code>16  }</code></p>
      <p><code>17 }</code></p>
      <subtitle>Объявление дескриптора libnet</subtitle>
      <p><code>7</code> В библиотеке <code>libnet</code> используется непрозрачный тип <code>libnet_t</code>. Функция <code>libnet_init</code> возвращает указатель на этот тип, который затем передается другим функциям <code>libnet</code> для обращения к конкретному сокету. В этом смысле данный тип аналогичен дескрипторам сокетов и устройств <code>pcap</code>.</p>
      <subtitle>Инициализация libnet</subtitle>
      <p><code>12-16</code> Мы вызываем функцию <code>libnet_init</code>, запрашивая открытие символьного сокета IPv4. Для этого в качестве первого аргумента указывается константа <code>LIBNET_RAW4</code>. В случае возникновения ошибки функция возвращает текст сообщения в аргументе <code>errbuf</code>, который мы распечатываем.</p>
      <p>Функция <code>send_dns_query</code> для <code>libnet</code> представлена в листинге 29.16. Сравните ее с функциями <code>send_dns_query</code> и <code>udp_write</code> для символьных сокетов.</p>
      <p><strong>Листинг 29.16</strong>. Функция send_dns_query, использующая libnet</p>
      <p><code>//udpcksum/senddnsquery-libnet.c</code></p>
      <p><code>18 void</code></p>
      <p><code>19 send_dns_query(void)</code></p>
      <p><code>20 {</code></p>
      <p><code>21  char qbuf[24], *ptr;</code></p>
      <p><code>22  u_int16_t one;</code></p>
      <p><code>23  int packet_size = LIBNET_UDP_H + LIBNET_DNSV4_H + 24;</code></p>
      <p><code>24  static libnet_ptag_t ip_tag, udp_tag, dns_tag;</code></p>
      <empty-line/>
      <p><code>25  /* построение запроса внутри UDP-пакета */</code></p>
      <p><code>26  ptr = qbuf;</code></p>
      <p><code>27  memcpy(ptr, "\001a\014root-servers\003net\000", 20);</code></p>
      <p><code>28  ptr += 20;</code></p>
      <p><code>29  one = htons(1);</code></p>
      <p><code>30  memcpy(ptr, &amp;one, 2); /* тип запроса = A */</code></p>
      <p><code>31  ptr += 2;</code></p>
      <p><code>32  memcpy(ptr, &amp;one, 2); /* класс запроса = 1 (IP-адрес) */</code></p>
      <empty-line/>
      <p><code>33  /* формирование пакета DNS */</code></p>
      <p><code>34  dns_tag = libnet_build_dnsv4(</code></p>
      <p><code>35   1234 /* идентификатор */,</code></p>
      <p><code>36   0x0100 /* флаги: рекурсия разрешена */,</code></p>
      <p><code>37   1 /* кол-во запросов */, 0 /* кол-во записей в ответе */,</code></p>
      <p><code>38   0 /* кол-во авторитетных записей */, 0 /* кол-во дополнительных */,</code></p>
      <p><code>39   qbuf /* запрос */,</code></p>
      <p><code>40   24 /* длина запроса */, 1, dns_tag);</code></p>
      <p><code>41  /* формирование заголовка UDP */</code></p>
      <p><code>42  udp_tag = libnet_build_udp(</code></p>
      <p><code>43   ((struct sockaddr_in*)local)-&gt;</code></p>
      <p><code>44    sin_port /* порт отправителя */,</code></p>
      <p><code>45   ((struct sockaddr_in*)dest)-&gt;</code></p>
      <p><code>46    sin_port /* порт получателя */,</code></p>
      <p><code>47   packet_size /* длина */, 0 /* контрольная сумма */,</code></p>
      <p><code>48   NULL /* полезные данные */, 0 /* длина полезн. данных */, l, udp_tag);</code></p>
      <p><code>49  /* Так как мы установили контр. сумму равной нулю, libnet автоматически */</code></p>
      <p><code>50  /* рассчитает контр. сумму UDP. Эту функцию можно отключить. */</code></p>
      <p><code>51  if (zerosum)</code></p>
      <p><code>52   if (libnet_toggle_checksum(l, udp_tag, LIBNET_OFF) &lt; 0)</code></p>
      <p><code>53    err_quit("turning off checksums: %s\n", libnet_geterror(l));</code></p>
      <p><code>54  /* формирование IP-заголовка */</code></p>
      <p><code>55  ip_tag = libnet_build_ipv4(packet_size + LIBNET_IPV4_H /* длина */,</code></p>
      <p><code>56   0 /* tos */, 0 /* IP ID */, 0 /* фрагмент*/,</code></p>
      <p><code>57   TTL_OUT /* ttl */, IPPROTO_UDP /* протокол */,</code></p>
      <p><code>58   0 /* контр. сумма */,</code></p>
      <p><code>59   ((struct sockaddr_in*)local)-&gt;sin_addr.s_addr /* отправитель */,</code></p>
      <p><code>60   ((struct sockaddr_in*)dest)-&gt;sin_addr.s_addr /* получатель */,</code></p>
      <p><code>61   NULL /* полезные данные */, 0 /* длина полезн. данных */, l, ip_tag);</code></p>
      <empty-line/>
      <p><code>62  if (libnet_write(l) &lt; 0) {</code></p>
      <p><code>63   err_quit("libnet_write: %s\n", libnet_geterror(l));</code></p>
      <p><code>64  }</code></p>
      <p><code>65  if (verbose)</code></p>
      <p><code>66   printf("sent: %d bytes of data\n", packet_size);</code></p>
      <p><code>67  }</code></p>
      <subtitle>Формирование запроса DNS</subtitle>
      <p><code>25-32</code> Мы начинаем с формирования запроса DNS, которое выполняется так же, как в строках 25–30 листинга 29.8.</p>
      <p><code>34-40</code> Затем мы вызываем функцию <code>libnet_build_dnsv4</code>, которая принимает поля пакета DNS в виде отдельных аргументов. Нам достаточно знать содержимое запроса, а упорядочением этого содержимого в заголовке пакета DNS занимается функция.</p>
      <subtitle>Заполнение заголовка UDP и подготовка к вычислению контрольной суммы UDP</subtitle>
      <p><code>42-48</code> Мы формируем заголовок UDP, вызывая функцию <code>libnet_build_udp</code>. Поля заголовка UDP принимаются этой функцией также в виде отдельных аргументов. Если значение переданной контрольной суммы равно 0, <code>libnet</code> автоматически рассчитывает контрольную сумму.</p>
      <p><code>49-52</code> Если пользователь запретил вычисление контрольной суммы, мы должны отключить эту функцию <code>libnet</code> явным образом.</p>
      <subtitle>Заполнение заголовка IP</subtitle>
      <p><code>53-65</code> Окончательное формирование пакета требует построения заголовка IPv4 вызовом <code>libnet_build_ipv4</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Библиотека libnet автоматически записывает поле ip_len в нужном порядке байтов. Это пример повышения переносимости программы благодаря использованию библиотек.</p>
      </cite>
      <subtitle>Отправка UDP-дейтаграммы</subtitle>
      <p><code>66-70</code> Мы вызываем функцию <code>libnet_write</code> для отправки подготовленной дейтаграммы в сеть.</p>
      <p>Функция <code>send_dns_query</code>, использующая <code>libnet</code>, состоит всего из 67 строк, тогда как в версии, работавшей с символьными сокетами, общая длина кода составила 96 строк, в которых было по крайней мере 2 трюка, связанных с переносимостью.</p>
     </section>
    </section>
    <section>
     <title>
      <p>29.8. Резюме</p>
     </title>
     <p>Символьные сокеты предоставляют возможность записывать и считывать IP-дейтаграммы, которые могут быть не поняты ядром, а доступ к канальному уровню позволяет считывать и записывать кадры канального уровня <emphasis>любых</emphasis> типов (не только дейтаграммы IP). Программа <code>tcpdump</code> — это, вероятно, наиболее широко используемая программа, имеющая непосредственный доступ к канальному уровню.</p>
     <p>В различных операционных системах применяются различные способы доступа к канальному уровню. Мы рассмотрели пакетный фильтр Беркли, DLPI SVR4 и пакетные сокеты Linux (<code>SOCK_PACKET</code>). Но у нас имеется возможность, не вникая в различия перечисленных способов, использовать находящуюся в свободном доступе переносимую библиотеку захвата пакетов <code>libcap</code>.</p>
     <p>Отправка символьных дейтаграмм осуществляется в разных системах по-разному. Свободно распространяемая библиотека <code>libnet</code> скрывает различия между системами и предоставляет интерфейс для вывода через символьные сокеты и непосредственно на канальном уровне.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Каково назначение флага <code>canjump</code> в листинге 29.7?</p>
     <p>2. При работе программы <code>udpcksum</code> наиболее распространенным сообщением об ошибке является сообщение о недоступности порта ICMP (в пункте назначения не работает сервер имен) или недоступности узла ICMP. В обоих случаях нам не нужно ждать истечения времени ожидания, заданного функцией <code>udp_read</code> в листинге 29.6, так как сообщение о подобной ошибке фактически является ответом на наш запрос DNS. Модифицируйте программу таким образом, чтобы она перехватывала эти ошибки ICMP.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 30</p>
     <p>Альтернативное устройство клиента и сервера</p>
    </title>
    <section>
     <title>
      <p>30.1. Введение</p>
     </title>
     <p>При написании сервера под Unix мы можем выбирать из следующих вариантов управления процессом:</p>
     <p>&#9632; Наш первый сервер, показанный в листинге 1.5, был <emphasis>последовательным</emphasis> (<emphasis>iterative</emphasis>), но количество сценариев, для которых этот вариант является предпочтительным, весьма ограничено, поскольку последовательный сервер не может начать обработку очередного клиентского запроса, не закончив полностью обработку текущего запроса.</p>
     <p>&#9632; В листинге 5.1 показан первый в данной книге <emphasis>параллельный</emphasis> (<emphasis>concurrent</emphasis>) сервер, который для обработки каждого клиентского запроса порождал дочерний процесс с помощью функции <code>fork</code>. Традиционно большинство серверов, работающих под Unix, попадают в эту категорию.</p>
     <p>&#9632; В разделе 6.8 мы разработали другую версию сервера TCP, в котором имеется только один процесс, обрабатывающий любое количество клиентских запросов с помощью функции <code>select</code>.</p>
     <p>&#9632; В листинге 26.2 мы модифицировали параллельный сервер, создав для каждого клиента по одному потоку вместо одного процесса.</p>
     <p>В этой главе мы рассмотрим два других способа модификации устройства параллельного сервера.</p>
     <p>&#9632; <emphasis>Предварительное создание дочерних процессов</emphasis> (<emphasis>preforking</emphasis>). В этом случае при запуске сервера выполняется функция <code>fork</code>, которая создает определенное количество (пул) дочерних процессов. Обработкой очередного клиентского запроса занимается процесс, взятый из этого набора.</p>
     <p>&#9632; <emphasis>Предварительное создание потоков</emphasis> (<emphasis>prethreading</emphasis>). При запуске сервера создается некоторое количество (пул) потоков, и для обработки каждого клиента используется поток из данного набора.</p>
     <p>В данной главе мы будем рассматривать множество вопросов, связанных с предварительным созданием потоков и процессов. Например, что произойдет, если в пуле окажется недостаточное количество процессов или потоков? А если их будет слишком много? Как родительский и дочерние процессы (или потоки) синхронизируют свои действия?</p>
     <p>Обычно написать клиент легче, чем сервер, за счет простоты управления процессом клиента. Тем не менее мы уже исследовали различные способы написания простого эхо-клиента, которые вкратце изложены в разделе 30.2.</p>
     <p>В этой главе мы рассматриваем девять различных способов устройства сервера и взаимодействие каждого из этих серверов с одним и тем же клиентом. Клиент-серверный сценарий типичен для WWW: клиент посылает небольшой по объему запрос, а сервер отвечает ему, отсылая соответствующие запросу данные. Некоторые из этих серверов мы уже достаточно подробно обсуждали (например, параллельный сервер, вызывающий функцию <code>fork</code> для обработки каждого клиентского запроса), в то время как предварительное создание процессов и потоков являются новыми для нас концепциями, которые и будут подробно рассмотрены в этой главе.</p>
     <p>Мы запускали различные экземпляры клиента с каждым сервером, измеряя время, которое процессор тратит на обслуживание определенного количества клиентских запросов. Чтобы информация об этом не оказалась рассеянной по всей главе, мы свели все полученные результаты в табл. 30.1, на которую в этой главе будем неоднократно ссылаться. Следует отметить, что значения времени, указанные в этой таблице, соответствуют процессорному времени, затраченному <emphasis>только на управление процессом</emphasis>, так как из фактического значения времени процессора мы вычитаем время, которое тратит на выполнение того же задания последовательный сервер, не имеющий накладных расходов, связанных с управлением процессом. Иными словами, нулевой точкой отсчета в данной таблице для нас является время, затраченное последовательным сервером. Для большей наглядности мы включили в таблицу строку для последовательного сервера с нулевыми значениями времени. В этой главе термином <emphasis>время центрального процессора на управление процессом</emphasis> (<emphasis>process control CPU time</emphasis>) мы обозначаем разность между фактическим значением времени центрального процессора и временем, затраченным последовательным сервером, для каждой конкретной системы.</p>
     <empty-line/>
     <p><strong>Таблица 30.1</strong>. Сравнительные значения времени, затраченного каждым из обсуждаемых в данной главе сервером</p>
     <table>
      <tr align="left">
       <th align="left" valign="top"/>
       <th align="left" valign="top">Описание сервера</th>
       <th align="left" valign="top">Время центрального процессора на управление процессом</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Последовательный (точка отсчета; затраты на управление процессом отсутствуют)</td>
       <td align="left" valign="top">0,0</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Параллельный сервер, один вызов функции fork для обработки одного клиента</td>
       <td align="left" valign="top">20,90</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">Предварительное создание дочерних процессов, каждый из которых вызывает функцию accept</td>
       <td align="left" valign="top">1,80</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">Предварительное создание дочерних процессов с блокировкой для защиты accept</td>
       <td align="left" valign="top">2,07</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">Предварительное создание дочерних процессов с использованием взаимного исключения для защиты accept</td>
       <td align="left" valign="top">1,75</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">5</td>
       <td align="left" valign="top">Предварительное создание дочерних процессов, родительский процесс передает дочернему дескриптор сокета</td>
       <td align="left" valign="top">2,58</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">6</td>
       <td align="left" valign="top">Параллельный сервер, создание одного потока на каждый клиентский запрос</td>
       <td align="left" valign="top">0,99</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">Предварительное создание потоков с использованием взаимного исключения для защиты accept</td>
       <td align="left" valign="top">1,93</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">8</td>
       <td align="left" valign="top">Предварительное создание потоков, главный поток вызывает accept</td>
       <td align="left" valign="top">2,05</td>
      </tr>
     </table>
     <p>Все приведенные выше значения времени были получены путем запуска клиента, показанного в листинге 30.1, на двух различных узлах в той же подсети, что и сервер. Во всех тестах оба клиента порождали пять дочерних процессов для создания пяти одновременных соединений с сервером, таким образом максимальное количество одновременных соединений с сервером было равно 10. Каждый клиент запрашивал 4000 байт данных от сервера по каждому соединению. В случае, когда тест подразумевает предварительное создание дочерних процессов или потоков при запуске сервера, их количество равно 15.</p>
     <p>Некоторые версии нашего сервера работали с предварительно созданным пулом потоков или процессов. Интересным моментом является распределение клиентских запросов по потокам или дочерним процессам, находящимся в накопителе. В табл. 30.2 показаны варианты этого распределения, которые также будут обсуждаться в соответствующих разделах.</p>
     <empty-line/>
     <p><strong>Таблица 30.2</strong>. Количество клиентов, обслуженных каждым из 15 дочерних процессов или потоков</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">№ процесса или потока</th>
       <th align="left" valign="top">Предварительное создание процессов без защиты accept (строка 2)</th>
       <th align="left" valign="top">Предварительное создание процессов с защитой accept (строка 3)</th>
       <th align="left" valign="top">Предварительное создание процессов, передача дескриптора (строка 5)</th>
       <th align="left" valign="top">Предварительное порождение потоков, защита accept (строка 7)</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">333</td>
       <td align="left" valign="top">347</td>
       <td align="left" valign="top">1006</td>
       <td align="left" valign="top">333</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">340</td>
       <td align="left" valign="top">328</td>
       <td align="left" valign="top">950</td>
       <td align="left" valign="top">323</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">335</td>
       <td align="left" valign="top">332</td>
       <td align="left" valign="top">720</td>
       <td align="left" valign="top">333</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">335</td>
       <td align="left" valign="top">335</td>
       <td align="left" valign="top">583</td>
       <td align="left" valign="top">328</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">332</td>
       <td align="left" valign="top">338</td>
       <td align="left" valign="top">485</td>
       <td align="left" valign="top">329</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">5</td>
       <td align="left" valign="top">331</td>
       <td align="left" valign="top">340</td>
       <td align="left" valign="top">457</td>
       <td align="left" valign="top">322</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">6</td>
       <td align="left" valign="top">333</td>
       <td align="left" valign="top">335</td>
       <td align="left" valign="top">385</td>
       <td align="left" valign="top">324</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">333</td>
       <td align="left" valign="top">343</td>
       <td align="left" valign="top">250</td>
       <td align="left" valign="top">360</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">8</td>
       <td align="left" valign="top">332</td>
       <td align="left" valign="top">324</td>
       <td align="left" valign="top">105</td>
       <td align="left" valign="top">341</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">9</td>
       <td align="left" valign="top">331</td>
       <td align="left" valign="top">315</td>
       <td align="left" valign="top">32</td>
       <td align="left" valign="top">348</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">10</td>
       <td align="left" valign="top">334</td>
       <td align="left" valign="top">326</td>
       <td align="left" valign="top">14</td>
       <td align="left" valign="top">358</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">11</td>
       <td align="left" valign="top">333</td>
       <td align="left" valign="top">340</td>
       <td align="left" valign="top">9</td>
       <td align="left" valign="top">331</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">12</td>
       <td align="left" valign="top">334</td>
       <td align="left" valign="top">330</td>
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">321</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">13</td>
       <td align="left" valign="top">332</td>
       <td align="left" valign="top">331</td>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">329</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">14</td>
       <td align="left" valign="top">332</td>
       <td align="left" valign="top">336</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">320</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top"/>
       <td align="left" valign="top">5000</td>
       <td align="left" valign="top">5000</td>
       <td align="left" valign="top">5000</td>
       <td align="left" valign="top">5000</td>
      </tr>
     </table>
    </section>
    <section>
     <title>
      <p>30.2. Альтернативы для клиента TCP</p>
     </title>
     <p>Мы уже обсуждали различные способы устройства клиентов, но стоит тем не менее еще раз обратить внимание на относительные достоинства и недостатки этих способов.</p>
     <p>1. В листинге 5.4 показан основной способ устройства клиента TCP. С этой программой были связаны две проблемы. Во-первых, когда она блокируется в ожидании ввода пользователя, она не замечает происходящих в сети событий, например отключения собеседника от соединения. Во-вторых, она действует в режиме остановки и ожидания, что неэффективно в случае пакетной обработки.</p>
     <p>2. Листинг 6.1 содержит следующую, модифицированную версию клиента. С помощью функции <code>select</code> клиент получает информацию о событиях в сети во время ожидания ввода пользователя. Однако проблема этой версии заключается в том, что программа не способна корректно работать в пакетном режиме. В листинге 6.2 эта проблема решается путем применения функции <code>shutdown</code>.</p>
     <p>3. С листинга 16.1 начинается рассмотрение клиентов, использующих неблокируемый ввод-вывод.</p>
     <p>4. Первым из рассмотренных нами клиентов, вышедшим за пределы ограничений, связанных с наличием единственного процесса или потока для обслуживания всех запросов, является клиент, изображенный в листинге 16.6. В этом случае использовалась функция <code>fork</code>, и один процесс обрабатывал передачу данных от клиента к серверу, а другой — в обратном направлении.</p>
     <p>5. В листинге 26.1 используются два потока вместо двух процессов.</p>
     <p>В конце раздела 16.2 мы резюмируем различия между перечисленными версиями. Как мы отметили, хотя версия с неблокируемым вводом-выводом является самой быстродействующей, ее код слишком сложен, а применение двух потоков или двух процессов упрощает код.</p>
    </section>
    <section>
     <title>
      <p>30.3. Тестовый клиент TCP</p>
     </title>
     <p>В листинге 30.1<a l:href="#n1" type="note">[1]</a> показан клиент, который будет использоваться для тестирования всех вариаций нашего сервера.</p>
     <p><strong>Листинг 30.1</strong>. Код клиента TCP для проверки различных версий сервера</p>
     <p><code>//server/client.с</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 #define MAXN 16384 /* максимальное количество байтов, которые могут быть</code></p>
     <p><code>                         запрошены клиентом от сервера */</code></p>
     <empty-line/>
     <p><code> 3 int</code></p>
     <p><code> 4 main(int argc, char **argv)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  int i, j, fd, nchildren, nloops, nbytes;</code></p>
     <p><code> 7  pid_t pid;</code></p>
     <p><code> 8  ssize_t n,</code></p>
     <p><code> 9   char request[MAXLINE], reply[MAXN];</code></p>
     <empty-line/>
     <p><code>10  if (argc != 6)</code></p>
     <p><code>11   err_quit("usage: client &lt;hostname or IPaddr&gt; &lt;port&gt; &lt;#children&gt; "</code></p>
     <p><code>12    "&lt;#loops/child&gt; &lt;#bytes/request&gt;");</code></p>
     <empty-line/>
     <p><code>13  nchildren = atoi(argv[3]);</code></p>
     <p><code>14  nloops = atoi(argv[4]);</code></p>
     <p><code>15  nbytes = atoi(argv[5]);</code></p>
     <p><code>16  snprintf(request, sizeof(request), "%d\n", nbytes); /* в конце</code></p>
     <p><code>                                            символ новой строки */</code></p>
     <empty-line/>
     <p><code>17  for (i = 0; i &lt; nchildren; i++) {</code></p>
     <p><code>18   if ((pid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>19    for (j = 0; j &lt; nloops; j++) {</code></p>
     <p><code>20     fd = Tcp_connect(argv[1], argv[2]);</code></p>
     <empty-line/>
     <p><code>21     Write(fd, request, strlen(request));</code></p>
     <empty-line/>
     <p><code>22     if ((n = Readn(fd, reply, nbytes)) != nbytes)</code></p>
     <p><code>23      err_quit("server returned %d bytes", n);</code></p>
     <empty-line/>
     <p><code>24     Close(fd); /* состояние TIME_WAIT на стороне клиента,</code></p>
     <p><code>                     а не сервера */</code></p>
     <p><code>25    }</code></p>
     <p><code>26    printf("child %d done\n", i);</code></p>
     <p><code>27    exit(0);</code></p>
     <p><code>28   }</code></p>
     <p><code>29   /* родительский процесс снова вызывает функцию fork */</code></p>
     <p><code>30  }</code></p>
     <empty-line/>
     <p><code>31  while (wait(NULL) &gt; 0) /* теперь родитель ждет завершения всех</code></p>
     <p><code>                              дочерних процессов */</code></p>
     <p><code>32   ;</code></p>
     <p><code>33  if (errno != ECHILD)</code></p>
     <p><code>34   err_sys("wait error");</code></p>
     <empty-line/>
     <p><code>35  exit(0);</code></p>
     <p><code>36 }</code></p>
     <p><code>10-12</code> Каждый раз при запуске клиента мы задаем имя узла или IP-адрес сервера, порт сервера, количество дочерних процессов, порождаемых функцией <code>fork</code> (что позволяет нам инициировать несколько одновременных соединений с сервером), количество запросов, которое каждый дочерний процесс должен посылать серверу, и количество байтов, отправляемых сервером в ответ на каждый запрос.</p>
     <p><code>17-30</code> Родительский процесс вызывает функцию <code>fork</code> для порождения каждого дочернего процесса, и каждый дочерний процесс устанавливает указанное количество соединений с сервером. По каждому соединению дочерний процесс посылает запрос, задавая количество байтов, которое должен вернуть сервер, а затем дочерний процесс считывает это количество данных с сервера. Родительский процесс просто ждет завершения выполнения всех дочерних процессов. Обратите внимание, что клиент закрывает каждое соединение TCP, таким образом состояние TCP TIME_WAIT имеет место на стороне клиента, а не на стороне сервера. Это отличает наше клиент-серверное соединение от обычного соединения HTTP.</p>
     <p>При тестировании различных серверов из этой главы мы запускали клиент следующим образом:</p>
     <p><code>% <strong>client 192.168.1.20 8888 5 500 4000</strong></code></p>
     <p>Таким образом создается 2500 соединений TCP с сервером: по 500 соединений от каждого из 5 дочерних процессов. По каждому соединению от клиента к серверу посылается 5 байт (<code>"4000\n"</code>), а от сервера клиенту передается 4000 байт. Мы запускаем клиент на двух различных узлах, соединяясь с одним и тем же сервером, что дает в сумме 5000 соединений TCP, причем максимальное количество одновременных соединений с сервером в любой момент времени равно 10.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Для проверки различных веб-серверов существуют изощренные контрольные тесты. Один из них называется WebStone. Информация о нем находится в свободном доступе по адресу http://www.mindcraft.com/webstone. Для общего сравнения различных альтернативных устройств сервера, которые мы рассматриваем в этой главе, нам не нужны столь сложные тесты.</p>
     </cite>
     <p>Теперь мы представим девять различных вариантов устройства сервера.</p>
    </section>
    <section>
     <title>
      <p>30.4. Последовательный сервер TCP</p>
     </title>
     <p>Последовательный сервер TCP полностью обрабатывает запрос каждого клиента, прежде чем перейти к следующему клиенту. Последовательные серверы редко используются, но один из них, простой сервер времени и даты, мы показали в листинге 1.5.</p>
     <p>Тем не менее у нас имеется область, в которой желательно применение именно последовательного сервера — это сравнение характеристик других серверов. Если мы запустим клиент следующим образом:</p>
     <p><code>% <strong>client 192.168.1.20 8888 1 5000 4000</strong></code></p>
     <p>и соединимся с последовательным сервером, то получим такое же количество соединений TCP (5000) и такое же количество данных, передаваемых по одному соединению. Но поскольку сервер является последовательным, на нем <emphasis>не осуществляется никакого управления процессами</emphasis>. Это дает нам возможность получить базовое значение времени, затрачиваемого центральным процессором на обработку указанного количества запросов, которое потом мы можем вычесть из результатов измерений для других серверов. С точки зрения управления процессами последовательный сервер является самым быстрым, поскольку он вовсе не занимается этим управлением. Взяв последовательный сервер за точку отсчета, мы можем сравнивать результаты измерений быстродействия других серверов, показанные в табл. 30.1.</p>
     <p>Мы не приводим код для последовательного сервера, так как он представляет собой тривиальную модификацию параллельного сервера, показанного в следующем разделе.</p>
    </section>
    <section>
     <title>
      <p>30.5. Параллельный сервер TCP: один дочерний процесс для каждого клиента</p>
     </title>
     <p>Традиционно параллельный сервер TCP вызывает функцию <code>fork</code> для порождения нового дочернего процесса, который будет выполнять обработку очередного клиентского запроса. Это позволяет серверу обрабатывать несколько запросов одновременно, выделяя по одному дочернему процессу для каждого клиента. Единственным ограничением на количество одновременно обрабатываемых клиентских запросов является ограничение операционной системы на количество дочерних процессов, допустимое для пользователя, в сеансе которого работает сервер. Листинг 5.9 содержит пример параллельного сервера, и большинство серверов TCP написаны в том же стиле.</p>
     <p>Проблема с параллельными серверами заключается в количестве времени, которое тратит центральный процессор на выполнение функции <code>fork</code> для порождения нового дочернего процесса для каждого клиента. Давным-давно, в конце 80-х годов XX века, когда наиболее загруженные серверы обрабатывали сотни или тысячи клиентов за день, это было приемлемо. Но расширение Сети изменило требования. Теперь загруженными считаются серверы, обрабатывающие миллионы соединений TCP в день. Сказанное относится лишь к одиночным узлам, но наиболее загруженные сайты используют несколько узлов, распределяя нагрузку между ними (в разделе 14.2 [112] рассказывается об общепринятом способе распределения этой нагрузки, называемом <emphasis>циклическим обслуживанием DNS — DNS round robin</emphasis>). В последующих разделах описаны различные способы, позволяющие избежать вызова функции <code>fork</code> для каждого клиентского запроса, но тем не менее параллельные серверы остаются широко распространенными.</p>
     <p>В листинге 30.2 показана функция <code>main</code> для нашего параллельного сервера TCP.</p>
     <p><strong>Листинг 30.2</strong>. Функция main для параллельного сервера TCP</p>
     <p><code>//server/serv01.c</code></p>
     <p><code> 1 include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int listenfd, connfd;</code></p>
     <p><code> 6  pid_t childpid;</code></p>
     <p><code> 7  void sig_chld(int), sig_int(int), web_child(int);</code></p>
     <p><code> 8  socklen_t clilen, addrlen;</code></p>
     <p><code> 9  struct sockaddr *cliaddr;</code></p>
     <empty-line/>
     <p><code>10  if (argc == 2)</code></p>
     <p><code>11   listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
     <p><code>12  else if (argc == 3)</code></p>
     <p><code>13   listenfd = Tcp_listen(argv[1], argv[2], &amp;addrlen);</code></p>
     <p><code>14  else</code></p>
     <p><code>15   err_quit("usage: serv01 [ &lt;host&gt; ] &lt;port#&gt;");</code></p>
     <p><code>16  cliaddr = Malloc(addrlen);</code></p>
     <empty-line/>
     <p><code>17  Signal(SIGCHLD, sig_chld);</code></p>
     <p><code>18  Signal(SIGINT, sig_int);</code></p>
     <p><code>19  for (;;) {</code></p>
     <p><code>20   clilen = addrlen;</code></p>
     <p><code>21   if ((connfd = accept(listenfd, cliaddr, &amp;clilen)) &lt; 0) {</code></p>
     <p><code>22    if (errno == EINTR)</code></p>
     <p><code>23     continue; /* назад к for() */</code></p>
     <p><code>24    else</code></p>
     <p><code>25     err_sys("accept error");</code></p>
     <p><code>26   }</code></p>
     <p><code>27   if ((childpid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>28    Close(listenfd); /* закрываем прослушиваемый сокет */</code></p>
     <p><code>29    web_child(connfd); /* обрабатываем запрос */</code></p>
     <p><code>30    exit(0);</code></p>
     <p><code>31   }</code></p>
     <p><code>32   Close(connfd); /* родительский процесс закрывает</code></p>
     <p><code>                       присоединенный сокет */</code></p>
     <p><code>33  }</code></p>
     <p><code>34 }</code></p>
     <p>Эта функция аналогична функции, показанной в листинге 5.9: она вызывает функцию <code>fork</code> для каждого клиентского соединения и обрабатывает сигналы <code>SIGCHLD</code>, приходящие от закончивших свое выполнение дочерних процессов. Тем не менее мы сделали эту функцию не зависящей от протокола за счет вызова функции <code>tcp_listen</code>. Мы не показываем обработчик сигнала <code>sig_chld</code>: он совпадает с показанным в листинге 5.8, но только без функции <code>printf</code>.</p>
     <p>Мы также перехватываем сигнал <code>SIGINT</code>, который генерируется при вводе символа прерывания. Мы вводим этот символ после завершения работы клиента, чтобы было выведено время, потраченное центральным процессором на выполнение данной программы. В листинге 30.3 показан обработчик сигнала. Это пример обработчика сигнала, который никогда не возвращает управление.</p>
     <p><strong>Листинг 30.3</strong>. Обработчик сигнала SIGINT</p>
     <p><code>//server/serv01.c</code></p>
     <p><code>35 void</code></p>
     <p><code>36 sig_int(int signo)</code></p>
     <p><code>37 {</code></p>
     <p><code>38  void pr_cpu_time(void);</code></p>
     <p><code>39  pr_cpu_time();</code></p>
     <p><code>40  exit(0);</code></p>
     <p><code>41 }</code></p>
     <p>В листинге 30.4 показана функция <code>pr_cpu_time</code>, вызываемая из обработчика сигнала.</p>
     <p><strong>Листинг 30.4</strong>. Функция pr_cpu_time: вывод полного времени центрального процессора</p>
     <p><code>//server/pr_cpu_time.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <p><code> 2 #include &lt;sys/resource.h&gt;</code></p>
     <empty-line/>
     <p><code> 3 #ifndef HAVE_GETRUSAGE_PROTO</code></p>
     <p><code> 4 int getrusage(int, struct rusage*);</code></p>
     <p><code> 5 #endif</code></p>
     <empty-line/>
     <p><code> 6 void</code></p>
     <p><code> 7 pr_cpu_time(void)</code></p>
     <p><code> 8 {</code></p>
     <p><code> 9  double user, sys;</code></p>
     <p><code>10  struct rusage myusage, childusage;</code></p>
     <empty-line/>
     <p><code>11  if (getrusage(RUSAGE_SELF, &amp;myusage) &lt; 0)</code></p>
     <p><code>12   err_sys("getrusage error");</code></p>
     <p><code>13  if (getrusage(RUSAGE_CHILDREN, &amp;childusage) &lt; 0)</code></p>
     <p><code>14   err_sys("getrusage error");</code></p>
     <empty-line/>
     <p><code>15  user = (double)myusage.ru_utime.tv_sec +</code></p>
     <p><code>16   myusage.ru_utime.tv_usec / 1000000.0;</code></p>
     <p><code>17  user += (double)childusage.ru_utime.tv_sec +</code></p>
     <p><code>18   childusage.ru_utime.tv_usec / 1000000.0;</code></p>
     <p><code>19  sys = (double)myusage.ru_stime.tv_sec +</code></p>
     <p><code>20   myusage.ru_stime.tv_usec / 1000000.0;</code></p>
     <p><code>21  sys += (double)childusage.ru_stime.tv_sec +</code></p>
     <p><code>22   childusage.ru_stime.tv_usec / 1000000.0;</code></p>
     <empty-line/>
     <p><code>21  printf("\nuser time = %g, sys time = %g\n", user, sys);</code></p>
     <p><code>22 }</code></p>
     <p>Функция <code>getrusage</code> вызывается дважды: она позволяет получить данные об использовании ресурсов вызывающим процессом (<code>RUSAGE_SELF</code>) и всеми его дочерними процессами, которые завершили свое выполнение (<code>RUSAGE_CHILDREN</code>). Выводится время, затраченное центральным процессором на выполнение пользовательского процесса (общее пользовательское время, total user time), и время, которое центральный процессор затратил внутри ядра на выполнение задач, заданных вызывающим процессом (общее системное время, total system time).</p>
     <p>Возвращаясь к листингу 30.2, мы видим, что для обработки каждого клиентского запроса вызывается функция <code>web_child</code>. Эта функция показана в листинге 30.5.</p>
     <p><strong>Листинг 30.5</strong>. Функция web_child: обработка каждого клиентского запроса</p>
     <p><code>//server/web_child.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 #define MAXN 16384 /* максимальное количество байтов, которое клиент</code></p>
     <p><code>может запросить */</code></p>
     <empty-line/>
     <p><code> 3 void</code></p>
     <p><code> 4 web_child(int sockfd)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  int ntowrite;</code></p>
     <p><code> 7  ssize_t nread;</code></p>
     <p><code> 8  char line[MAXLINE], result[MAXN];</code></p>
     <empty-line/>
     <p><code> 9  for (;;) {</code></p>
     <p><code>10   if ((nread = Readline(sockfd, line, MAXLINE)) == 0)</code></p>
     <p><code>11    return; /* соединение закрыто другим концом */</code></p>
     <empty-line/>
     <p><code>12   /* line задает, сколько байтов следует отправлять обратно */</code></p>
     <p><code>13   ntowrite = atol(line);</code></p>
     <p><code>14   if ((ntowrite &lt;= 0) || (ntowrite &gt; MAXN))</code></p>
     <p><code>15    err_quit("client request for bytes", ntowrite);</code></p>
     <p><code>16   Writen(sockfd, result, ntowrite);</code></p>
     <p><code>17  }</code></p>
     <p><code>18 }</code></p>
     <p>Установив соединение с сервером, клиент записывает одну строку, задающую количество байтов, которое сервер должен вернуть. Это отчасти похоже на HTTP: клиент отправляет небольшой запрос, а сервер в ответ отправляет требуемую информацию (часто это файл HTML или изображение GIF). В случае HTTP сервер обычно закрывает соединение после отправки клиенту затребованных данных, хотя более новые версии используют <emphasis>постоянные соединения</emphasis> (<emphasis>persistent connection</emphasis>), оставляя соединения TCP открытыми для дополнительных клиентских запросов. В нашей функции <code>web_child</code> сервер допускает дополнительные запросы от клиента, но, как мы видели в листинге 24.1, клиент посылает серверу только по одному запросу на каждое соединение, а по получении ответа от сервера это соединение закрывается.</p>
     <p>В строке 1 табл. 30.1 показаны результаты измерения времени, затраченного параллельным сервером. При сравнении со следующими строками этой таблицы видно, что параллельный сервер тратит больше процессорного времени, чем все другие типы серверов — то, что мы и ожидали при вызове функции <code>fork</code>.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Один из способов устройства сервера, который мы не рассматриваем в этой главе, — это сервер, инициируемый демоном inetd (см. раздел 13.5). С точки зрения управления процессами такой сервер подразумевает использование функций fork и exec, так что затраты времени центрального процессора будут еще больше, чем показанные в строке 1 для параллельного сервера.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>30.6. Сервер TCP с предварительным порождением процессов без блокировки для вызова accept</p>
     </title>
     <section>
      <p>В первом из рассматриваемых нами «усовершенствованных» серверов используется технология, называемая <emphasis>предварительным созданием процессов</emphasis> (<emphasis>preforking</emphasis>). Вместо того чтобы вызывать функцию <code>fork</code> каждый раз при поступлении очередного запроса от клиента, сервер создает при запуске некоторое количество дочерних процессов, и впоследствии они обслуживают клиентские запросы по мере установления соединений с клиентами. На рис. 30.1 показан сценарий, при котором родитель предварительно создал <emphasis>N</emphasis> дочерних процессов, и в настоящий момент имеется два соединения с клиентами.</p>
      <image l:href="#img_160.png"/>
      <p><strong>Рис. 30.1</strong>. Предварительное создание дочерних процессов сервером</p>
      <p>Преимущество этой технологии заключается в том, что обслуживание нового клиента не требует вызова функции <code>fork</code> родительским процессом, тем самым стоимость этого обслуживания понижается. Недостатком же является необходимость угадать, сколько дочерних процессов нужно создать при запуске. Если в некоторый момент времени количество имеющихся дочерних процессов будет равно количеству обслуживаемых клиентов, то дополнительные клиентские запросы будут игнорироваться до того момента, когда освободится какой-либо дочерний процесс. Но, как сказано в разделе 4.5, клиентские запросы в такой ситуации игнорируются не полностью. Для каждого из этих дополнительных клиентов ядро выполнит трехэтапное рукопожатие (при этом общее количество соединений не может превышать значения аргумента <code>backlog</code> функции <code>listen</code>), и при вызове функции <code>accept</code> установленные соединения будут переданы серверу. При этом, однако, приложение-клиент может заметить некоторое ухудшение в скорости получения ответа, так как, хотя функция <code>connect</code> может быть выполнена сразу же, запрос может не поступать на обработку еще некоторое время.</p>
      <p>За счет некоторого дополнительного усложнения кода всегда можно добиться того, что сервер справится со всеми клиентскими запросами. От родительского процесса требуется постоянно отслеживать количество свободных дочерних процессов, и если это количество падает ниже некоторого минимального предела, родитель должен вызвать функцию <code>fork</code> и создать недостающее количество дочерних процессов. Аналогично, если количество свободных дочерних процессов превосходит некоторую максимальную величину, некоторые из этих процессов могут быть завершены родителем, так как излишнее количество свободных дочерних процессов тоже отрицательно влияет на производительность (об этом мы поговорим чуть позже).</p>
      <p>Но прежде чем углубляться в детали, исследуем основную структуру этого типа сервера. В листинге 30.6 показана функция <code>main</code> для первой версии нашего сервера с предварительным порождением дочерних процессов.</p>
      <p><strong>Листинг 30.6</strong>. Функция main сервера с предварительным порождением дочерних процессов</p>
      <p><code>//server/serv02.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 static int nchildren;</code></p>
      <p><code> 3 static pid_t *pids;</code></p>
      <empty-line/>
      <p><code> 4 int</code></p>
      <p><code> 5 main(int argc, char **argv)</code></p>
      <p><code> 6 {</code></p>
      <p><code> 7  int listenfd, i;</code></p>
      <p><code> 8  socklen_t addrlen;</code></p>
      <p><code> 9  void sig_int(int);</code></p>
      <p><code>10  pid_t child_make(int, int, int);</code></p>
      <empty-line/>
      <p><code>11  if (argc == 3)</code></p>
      <p><code>12   listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
      <p><code>13  else if (argc == 4)</code></p>
      <p><code>14   listenfd = Tcp_listen(argv[1], argv[2], &amp;addrlen);</code></p>
      <p><code>15  else</code></p>
      <p><code>16   err_quit("usage: serv02 [ &lt;host&gt; ] &lt;port#&gt; &lt;#children&gt;");</code></p>
      <p><code>17  nchildren = atoi(argv[argc - 1]);</code></p>
      <p><code>18  pids = Calloc(nchildren, sizeof(pid_t));</code></p>
      <empty-line/>
      <p><code>19  for (i = 0; i &lt; nchildren; i++)</code></p>
      <p><code>20   pids[i] = child_make(i, listenfd, addrlen); /* возвращение родительского процесса */</code></p>
      <p><code>21  Signal (SIGINT, sig_int);</code></p>
      <empty-line/>
      <p><code>22  for (;;)</code></p>
      <p><code>23   pause(); /* дочерние процессы завершились */</code></p>
      <p><code>24 }</code></p>
      <p><code>11-18</code> Дополнительный аргумент командной строки указывает, сколько требуется создать дочерних процессов. В памяти выделяется место для размещения массива, в который записываются идентификаторы дочерних процессов, используемые функцией <code>main</code> при окончании работы программы для завершения этих процессов.</p>
      <p><code>19-20</code> Каждый дочерний процесс создается функцией <code>child_make</code>, которую мы показываем в листинге 30.8.</p>
      <p>Код обработчика сигнала <code>SIGINT</code>, представленный в листинге 30.7, отличается от кода, приведенного в листинге 30.3.</p>
      <p><strong>Листинг 30.7</strong>. Обработчик сигнала SIGINT</p>
      <p><code>//server/serv02.c</code></p>
      <p><code>25 void</code></p>
      <p><code>26 sig_int(int signo)</code></p>
      <p><code>27 {</code></p>
      <p><code>28  int i;</code></p>
      <p><code>29  void pr_cpu_time(void);</code></p>
      <empty-line/>
      <p><code>30  /* завершаем все дочерние процессы */</code></p>
      <p><code>31  for (i = 0; i &lt; nchildren; i++)</code></p>
      <p><code>32   kill(pids[i], SIGTERM);</code></p>
      <p><code>33  while (wait(NULL) &gt; 0) /* ждем завершения всех дочерних процессов */</code></p>
      <p><code>34   ;</code></p>
      <p><code>35  if (errno != ECHILD)</code></p>
      <p><code>36   err_sys("wait error");</code></p>
      <empty-line/>
      <p><code>37  pr_cpu_time();</code></p>
      <p><code>38  exit(0);</code></p>
      <p><code>39 }</code></p>
      <p><code>30-34</code> Функция <code>getrusage</code> сообщает об использовании ресурсов всеми дочерними процессами, <emphasis>завершившими</emphasis> свое выполнение, поэтому мы должны завершить все дочерние процессы к моменту вызова функции <code>pr_cpu_time</code>. Для этого дочерним процессам посылается сигнал <code>SIGTERM</code>, после чего мы вызываем функцию <code>wait</code> и ждем завершения выполнения дочерних процессов.</p>
      <p>В листинге 30.8 показана функция <code>child_make</code>, вызываемая из функции main для порождения очередного дочернего процесса.</p>
      <p><strong>Листинг 30.8</strong>. Функция child_make: создание очередного дочернего процесса</p>
      <p><code>//server/child02.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 pid_t</code></p>
      <p><code> 3 child_make(int i, int listenfd, int addrlen)</code></p>
      <p><code> 4 {</code></p>
      <p><code> 5 pid_t pid;</code></p>
      <p><code> 6 void child_main(int, int, int);</code></p>
      <empty-line/>
      <p><code> 7 if ( (pid = Fork()) &gt; 0)</code></p>
      <p><code> 8 return (pid); /* родительский процесс */</code></p>
      <empty-line/>
      <p><code> 9 child_main(i, listenfd, addrlen); /* никогда не завершается */</code></p>
      <p><code>10 }</code></p>
      <p><code>7-9</code> Функция <code>fork</code> создает очередной дочерний процесс и возвращает родителю идентификатор дочернего процесса. Дочерний процесс вызывает функцию <code>child_main</code>, показанную в листинге 30.9, которая представляет собой бесконечный цикл.</p>
      <p><strong>Листинг 30.9</strong>. Функция child_main: бесконечный цикл, выполняемый каждым дочерним процессом</p>
      <p><code>//server/child02.c</code></p>
      <p><code>11 void</code></p>
      <p><code>12 child_main(int i, int listenfd, int addrlen)</code></p>
      <p><code>13 {</code></p>
      <p><code>14  int connfd;</code></p>
      <p><code>15  void web_child(int);</code></p>
      <p><code>16  socklen_t clilen;</code></p>
      <p><code>17  struct sockaddr *cliaddr;</code></p>
      <empty-line/>
      <p><code>18  cliaddr = Malloc(addrlen);</code></p>
      <empty-line/>
      <p><code>19  printf("child %ld starting\n", (long)getpid());</code></p>
      <p><code>20  for (;;) {</code></p>
      <p><code>21   clilen = addrlen;</code></p>
      <p><code>22   connfd = Accept(listenfd, cliaddr, &amp;clilen);</code></p>
      <empty-line/>
      <p><code>23   web_child(connfd); /* обработка запроса */</code></p>
      <p><code>24   Close(connfd);</code></p>
      <p><code>25  }</code></p>
      <p><code>26 }</code></p>
      <p><code>20-25</code> Каждый дочерний процесс вызывает функцию <code>accept</code>, и когда она завершается, функция <code>web_child</code> (см. листинг 30.5) обрабатывает клиентский запрос. Дочерний процесс продолжает выполнение цикла, пока родительский процесс не завершит его.</p>
     </section>
     <section>
      <title>
       <p>Реализация 4.4BSD</p>
      </title>
      <p>Если вы никогда ранее не сталкивались с таким типом устройства сервера (несколько процессов, вызывающих функцию <code>accept</code> на одном и том же прослушиваемом сокете), вас, вероятно, удивляет, что это вообще может работать. Пожалуй, здесь уместен краткий экскурс, описывающий реализацию этого механизма в Беркли-ядрах (более подробную информацию вы найдете в [128]).</p>
      <p>Родитель сначала создает прослушиваемый сокет, а затем — дочерние процессы. Напомним, что каждый раз при вызове функции <code>fork</code> происходит копирование всех дескрипторов в каждый дочерний процесс. На рис. 30.2 показана организация структур <code>proc</code> (по одной структуре на процесс), одна структура <code>file</code> для прослушиваемого дескриптора и одна структура <code>socket</code>.</p>
      <image l:href="#img_161.png"/>
      <p><strong>Рис. 30.2</strong>. Организация структур proc, file и socket</p>
      <p>Дескрипторы — это просто индексы массива, содержащегося в структуре <code>proc</code>, который ссылается на структуру <code>file</code>. Одна из целей дублирования дескрипторов в дочерних процессах, осуществляемого функцией <code>fork</code>, заключается в том, чтобы данный дескриптор в дочернем процессе ссылался на ту же структуру <code>file</code>, на которую этот дескриптор ссылается в родительском процессе. Каждая структура <code>file</code> содержит счетчик ссылок, который начинается с единицы, когда открывается первый файл или сокет, и увеличивается на единицу при каждом вызове функции fork и при каждом дублировании дескриптора (с помощью функции <code>dup</code>). В нашем примере с <emphasis>N</emphasis> дочерними процессами счетчик ссылок в структуре <code>file</code> будет содержать значение <emphasis>N</emphasis>+1 (учитывая родительский процесс, у которого по-прежнему открыт прослушиваемый дескриптор, хотя родительский процесс никогда не вызывает функцию <code>accept</code>).</p>
      <p>При запуске программы создается <emphasis>N</emphasis> дочерних процессов, каждый из которых может вызывать функцию <code>accept</code>, и все они переводятся родительским процессом в состояние ожидания [128, с. 458]. Когда от клиента прибывает первый запрос на соединение, все <emphasis>N</emphasis> дочерних процессов «просыпаются», так как все они были переведены в состояние ожидания по одному и тому же «каналу ожидания» — полю <code>so_timeo</code> структуры <code>socket</code>, как совместно использующие один и тот же прослушиваемый дескриптор, указывающий на одну и ту же структуру <code>socket</code>. Хотя «проснулись» все <emphasis>N</emphasis> дочерних процессов, только один из них будет связан с клиентом. Остальные <emphasis>N</emphasis> - 1 снова перейдут в состояние ожидания, так как длина очереди клиентских запросов снова станет равна нулю, после того как первый из дочерних процессов займется обработкой поступившего запроса.</p>
      <p>Такая ситуация иногда называется <emphasis>thundering herd</emphasis> — более или менее дословный перевод будет звучать как «общая побудка», так как все <emphasis>N</emphasis> процессов должны быть выведены из спящего состояния, хотя нужен всего один процесс, и остальные потом снова «засыпают». Тем не менее этот код работает, хотя и имеет побочный эффект — необходимость «будить» слишком много дочерних процессов каждый раз, когда требуется принять (<code>accept</code>) очередное клиентское соединение. В следующем разделе мы исследуем, как это влияет на производительность в целом.</p>
     </section>
     <section>
      <title>
       <p>Эффект наличия слишком большого количества дочерних процессов</p>
      </title>
      <p>В табл. 30.1 (строка 2) указано время (1,8 с), затрачиваемое центральным процессором в случае наличия 15 дочерних процессов, обслуживающих не более 10 клиентов. Мы можем оценить эффект «общей побудки», увеличивая количество дочерних процессов и оставляя то же максимальное значение количества обслуживаемых клиентов (10). Мы не показываем результаты, получаемые при увеличении количества дочерних потоков, потому что они не настолько интересны. Поскольку любое количество дочерних потоков свыше 10 может считаться избыточным, проблема «общей побудки» усугубляется, а затрачиваемое на управление процессами время увеличивается.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Некоторые ядра Unix снабжены функцией, которая выводит из состояния ожидания только один процесс для обработки одного клиентского запроса [107]. Чаще всего она называется wakeup_one.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Распределение клиентских соединений между дочерними процессами</p>
      </title>
      <p>Следующей темой обсуждения является распределение клиентских соединений между свободными дочерними процессами, блокированными в вызове функции <code>accept</code>. Для получения этой информации мы модифицируем функцию <code>main</code>, размещая в совместно используемой области памяти массив счетчиков, которые представляют собой длинные целые числа (один счетчик на каждый дочерний процесс). Это делается следующим образом:</p>
      <p><code>long *cptr, *meter(int); /* для подсчета количества клиентов на один</code></p>
      <p><code>дочерний процесс */</code></p>
      <p><code>cptr = meter(nchildren); /* перед порождением дочернего процесса */</code></p>
      <p>В листинге 30.10 показана функция <code>meter</code>.</p>
      <p><strong>Листинг 30.10</strong>. Функция meter, которая размещает массив в совместно используемой памяти</p>
      <p><code>//server/meter.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <p><code> 2 #include &lt;sys/mman.h&gt;</code></p>
      <empty-line/>
      <p><code> 3 /* Размещаем массив "nchildren" длинных целых чисел</code></p>
      <p><code> 4  * в совместно используемой области памяти.</code></p>
      <p><code> 5  * Эти числа используются как счетчики количества</code></p>
      <p><code>    * клиентов, обслуженных данным дочерним процессом,</code></p>
      <p><code> 6  * см. с. 467-470 книги [110]"</code></p>
      <p><code> 7  */</code></p>
      <empty-line/>
      <p><code> 8 long*</code></p>
      <p><code> 9 meter(int nchildren)</code></p>
      <p><code>10 {</code></p>
      <p><code>11  int fd;</code></p>
      <p><code>12  long *ptr;</code></p>
      <empty-line/>
      <p><code>13 #ifdef MAP_ANON</code></p>
      <p><code>14  ptr = Mmap(0, nchildren * sizeof(long), PROT_READ | PROT_WRITE,</code></p>
      <p><code>15   MAP_ANON | MAP_SHARED, -1, 0);</code></p>
      <p><code>16 #else</code></p>
      <p><code>17  fd = Open("/dev/zero", O_RDWR, 0);</code></p>
      <empty-line/>
      <p><code>18  ptr = Mmap(0, nchildren * sizeof(long), PROT_READ | PROT_WRITE,</code></p>
      <p><code>19   MAP_SHARED, fd, 0);</code></p>
      <p><code>20  Close(fd);</code></p>
      <p><code>21 #endif</code></p>
      <empty-line/>
      <p><code>22  return (ptr);</code></p>
      <p><code>23 }</code></p>
      <p>Мы используем неименованное отображение в память, если оно поддерживается (например, в 4.4BSD), или отображение файла <code>/dev/zero</code> (например, SVR4). Поскольку массив создается функцией <code>mmap</code> до того, как родительский процесс порождает дочерние, этот массив затем используется совместно родительским и всеми дочерними процессами, созданными функцией <code>fork</code>.</p>
      <p>Затем мы модифицируем нашу функцию <code>child_main</code> (см. листинг 30.9) таким образом, чтобы каждый дочерний процесс увеличивал значение соответствующего счетчика на единицу при завершении функции <code>accept</code>, а после завершения выполнения всех дочерних процессов обработчик сигнала <code>SIGINT</code> выводил бы упомянутый массив счетчиков.</p>
      <p>В табл. 30.2 показано распределение нагрузки по дочерним процессам. Когда свободные дочерние процессы блокированы вызовом функции <code>accept</code>, имеющийся в ядре алгоритм планирования равномерно распределяет нагрузку, так что в результате все дочерние процессы обслуживают примерно одинаковое количество клиентских запросов.</p>
     </section>
     <section>
      <title>
       <p>Коллизии при вызове функции select</p>
      </title>
      <p>Рассматривая данный пример в 4.4BSD, мы можем исследовать еще одну проблему, которая встречается довольно редко и поэтому часто остается непонятой до конца. В разделе 16.13 [128] говорится о <emphasis>коллизиях</emphasis> (<emphasis>collisions</emphasis>), возникающих при вызове функции <code>select</code> несколькими процессами на одном и том же дескрипторе, и о том, каким образом ядро решает эту проблему. Суть проблемы в том, что в структуре <code>socket</code> предусмотрено место только для одного идентификатора процесса, который выводится из состояния ожидания по готовности дескриптора. Если же имеется несколько процессов, ожидающих, когда будет готов данный дескриптор, то ядро должно вывести из состояния ожидания все процессы, блокированные в вызове функции <code>select</code>, так как ядро не знает, какие именно процессы ожидают готовности данного дескриптора.</p>
      <p>Коллизии при вызове функции <code>select</code> в нашем примере можно форсировать, предваряя вызов функции <code>accept</code> из листинга 30.9 вызовом функции <code>select</code> в ожидании готовности к чтению на прослушиваемом сокете. Дочерние процессы будут теперь блокированы в вызове функции <code>select</code>, а не в вызове функции accept. В листинге 30.11 показана изменяемая часть функции <code>child_main</code>, при этом измененные по отношению к листингу 30.9 строки отмечены знаками <code>+</code>.</p>
      <p><strong>Листинг 30.11</strong>. Модификация листинга 30.9: блокирование в вызове select вместо блокирования в вызове accept</p>
      <p><code>  printf("child %ld starting\n", (long)getpid());</code></p>
      <p><code>+ FD_ZERO(&amp;rset);</code></p>
      <p><code>  for (;;) {</code></p>
      <p><code>+  FD_SET(listenfd, &amp;rset);</code></p>
      <p><code>+  Select(listenfd+1, &amp;rset, NULL, NULL, NULL);</code></p>
      <p><code>+  if (FD_ISSET(listenfd, &amp;rset) == 0)</code></p>
      <p><code>+   err_quit("listenfd readable");</code></p>
      <p><code>+</code></p>
      <p><code>   clilen = addrlen;</code></p>
      <p><code>   connfd = Accept(listenfd, cliaddr, &amp;clilen);</code></p>
      <empty-line/>
      <p><code>   web_child(connfd); /* обработка запроса */</code></p>
      <p><code>   Close(connfd);</code></p>
      <p><code>  }</code></p>
      <p>Если, проделав это изменение, мы проверим значение счетчика ядра BSD/OS <code>nselcoll</code>, мы увидим, что в первом случае при запуске сервера произошло 1814 коллизий, а во втором случае — 2045. Так как при каждом запуске сервера два клиента создают в сумме 5000 соединений, приведенные выше значения указывают, что примерно в 35-40% случаев вызовы функции <code>select</code> приводят к коллизиям.</p>
      <p>Если сравнить значения времени, затраченного центральным процессором в этом примере, то получится, что при добавлении вызова функции <code>select</code> это значение увеличивается с 1,8 до 2,9 с. Частично это объясняется, вероятно, добавлением системного вызова (так как теперь мы вызываем не только <code>accept</code>, но еще и <code>select</code>), а частично — накладными расходами, связанными с коллизиями.</p>
      <p>Из этого примера следует вывод, что когда несколько процессов блокируются на одном и том же дескрипторе, лучше, чтобы эта блокировка была связана с функцией <code>accept</code>, а не с функцией <code>select</code>.</p>
     </section>
    </section>
    <section>
     <title>
      <p>30.7. Сервер TCP с предварительным порождением процессов и защитой вызова accept блокировкой файла</p>
     </title>
     <section>
      <p>Описанная выше реализация, позволяющая нескольким процессам вызывать функцию <code>accept</code> на одном и том же прослушиваемом дескрипторе, возможна только для систем 4.4BSD, в которых функция <code>accept</code> реализована внутри ядра. Ядра системы SVR4, в которых accept реализована как библиотечная функция, не допускают этого. В самом деле, если мы запустим сервер из предыдущего раздела, в котором имеется несколько дочерних процессов, в Solaris 2.5 (система SVR4), то вскоре после того, как клиенты начнут соединяться с сервером, вызов функции <code>accept</code> в одном из дочерних процессов вызовет ошибку <code>EPROTO</code>, что свидетельствует об ошибке протокола.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Причины возникновения этой проблемы с библиотечной версией функции accept в SVR4 связаны с реализацией потоков STREAMS и тем фактом, что библиотечная функция accept не является атомарной операцией. В Solaris 2.6 эта проблема решена, но в большинстве реализаций SVR4 она остается.</p>
      </cite>
      <p>Решением этой проблемы является защита вызова функции <code>accept</code> при помощи блокировки, так что в данный момент времени только один процесс может быть блокирован в вызове этой функции. Другие процессы также будут блокированы, так как они будут стремиться установить блокировку для вызова функции <code>accept</code>.</p>
      <p>Существует несколько способов реализации защиты вызова функции <code>accept</code>, о которых рассказывается во втором томе<a l:href="#n13" type="note">[2]</a> данной серии. В этом разделе мы используем блокировку файла функцией <code>fcntl</code> согласно стандарту POSIX.</p>
      <p>Единственным изменением в функции <code>main</code> (см. листинг 30.6) будет добавление вызова функции <code>my_lock_init</code> перед началом цикла, в котором создаются дочерние процессы:</p>
      <p><code>+ my_lock_init("/tmp/lock.XXXXXX"); /* один файл для всех дочерних</code></p>
      <p><code>                                       процессов */</code></p>
      <p><code>  for (i = 0; i &lt; nchildren; i++)</code></p>
      <p><code>   pids[i] = child_make(i, listenfd, addrlen); /* возвращение</code></p>
      <p><code>                                       родительского процесса */</code></p>
      <p>Функция <code>child_make</code> остается такой же, как в листинге 30.8. Единственным изменением функции <code>child_main</code> (см. листинг 30.9) является блокирование перед вызовом функции <code>accept</code> и снятие блокировки после завершения этой функции:</p>
      <p><code>  for (;;) {</code></p>
      <p><code>   clilen = addrlen;</code></p>
      <p><code>+  my_lock_wait();</code></p>
      <p><code>    connfd = Accept(listenfd, cliaddr, &amp;clilen);</code></p>
      <p><code>+   my_lock_release();</code></p>
      <empty-line/>
      <p><code>    web_child(connfd); /* обработка запроса */</code></p>
      <p><code>    Close(connfd);</code></p>
      <p><code>   }</code></p>
      <p>В листинге 30.12 показана наша функция <code>my_lock_init</code>, в которой используется блокировка файла согласно стандарту POSIX.</p>
      <p><strong>Листинг 30.12</strong>. Функция my_lock_init: блокировка файла</p>
      <p><code>//server/lock_fcntl.c</code></p>
      <p><code> 1 #include "unp.h"</code></p>
      <empty-line/>
      <p><code> 2 static struct flock lock_it, unlock_it;</code></p>
      <p><code> 3 static int lock_fd = -1;</code></p>
      <p><code> 4 /* fcntl() не выполнится, если не будет вызвана функция my_lock_init() */</code></p>
      <empty-line/>
      <p><code> 5 void</code></p>
      <p><code> 6 my_lock_init(char *pathname)</code></p>
      <p><code> 7 {</code></p>
      <p><code> 8  char lock_file[1024];</code></p>
      <empty-line/>
      <p><code> 9  /* копируем строку вызывающего процесса на случай, если это константа */</code></p>
      <p><code>10  strncpy(lock_file, pathname, sizeof(lock_file));</code></p>
      <p><code>11  lock_fd = Mkstemp(lock_file);</code></p>
      <empty-line/>
      <p><code>12  Unlink(lock_file); /* но lock_fd остается открытым */</code></p>
      <empty-line/>
      <p><code>13  lock_it.l_type = F_WRLCK;</code></p>
      <p><code>14  lock_it.l_whence = SEEK_SET;</code></p>
      <p><code>15  lock_it.l_start = 0;</code></p>
      <p><code>16  lock_it.l_len = 0;</code></p>
      <empty-line/>
      <p><code>17  unlock_it.l_type = F_UNLCK;</code></p>
      <p><code>18  unlock_it.l_whence = SEEK_SET;</code></p>
      <p><code>19  unlock_it.l_start = 0;</code></p>
      <p><code>20  unlock_it.l_len = 0;</code></p>
      <p><code>21 }</code></p>
      <p><code>9-12</code> Вызывающий процесс задает шаблон для имени файла в качестве аргумента функции <code>my_lock_init</code>, и функция <code>mkstemp</code> на основе этого шаблона создает уникальное имя файла. Затем создается файл с этим именем и сразу же вызывается функция <code>unlink</code>, в результате чего имя файла удаляется из каталога. Если в программе впоследствии произойдет сбой, то файл исчезнет безвозвратно. Но пока он остается открытым в одном или нескольких процессах (иными словами, пока счетчик ссылок для этого файла больше нуля), сам файл не будет удален. (Отметим, что между удалением имени файла из каталога и закрытием открытого файла существует фундаментальная разница.)</p>
      <p><code>13-20</code> Инициализируются две структуры flock: одна для блокирования файла, другая для снятия блокировки. Блокируемый диапазон начинается с нуля (<code>l_whence =SEEK_SET, l_start=0</code>). Значение <code>l_len</code> равно нулю, то есть блокирован весь файл. В этот файл ничего не записывается (его длина всегда равна нулю), но такой тип блокировки в любом случае будет правильно обрабатываться ядром.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Сначала автор инициализировал эти структуры при объявлении:</p>
       <p><code>static struct flock lock_it = { F_WRLCK, 0, 0, 0, 0 };</code></p>
       <p><code>static struct flock unlock_it = { F_UNLCK, 0, 0, 0, 0 };</code></p>
       <p>но тут возникли две проблемы: у нас нет гарантии, что константа SEEK_SET равна нулю, но, что более важно, стандарт POSIX не регламентирует порядок расположения полей этой структуры. POSIX гарантирует только то, что требуемые поля присутствуют в структуре. POSIX не гарантирует какого-либо порядка следования полей структуры, а также допускает наличие в ней полей, не относящихся к стандарту POSIX. Поэтому когда требуется инициализировать эту структуру (если только не нужно инициализировать все поля нулями), это приходится делать через фактический код С, а не с помощью инициализатора при объявлении структуры.</p>
       <p>Исключением из этого правила является ситуация, когда инициализатор структуры обеспечивается реализацией. Например, при инициализации взаимного исключения в POSIX в главе 26 мы писали:</p>
       <p><code>pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;</code></p>
       <p>Тип данных pthread_mutex_t — это некая структура, но инициализатор предоставляется реализацией и может быть различным для разных реализаций.</p>
      </cite>
      <p>В листинге 30.13 показаны две функции, которые устанавливают и снимают блокировку с файла. Они представляют собой вызовы функции <code>fcntl</code>, использующие структуры, инициализированные в листинге 30.12.</p>
      <p><strong>Листинг 30.13</strong>. Функции my_lock_wait (установление блокировки файла) и my_lock_release (снятие блокировки файла)</p>
      <p><code>//server/lock_fcntl.c</code></p>
      <p><code>23 void</code></p>
      <p><code>24 my_lock_wait()</code></p>
      <p><code>25 {</code></p>
      <p><code>26  int rc;</code></p>
      <p><code>27  while ((rc = fcntl(lock_ld, F_SETLKW, &amp;lock_it)) &lt; 0 {</code></p>
      <p><code>28   if (errno == EINTR)</code></p>
      <p><code>29    continue;</code></p>
      <p><code>30   else</code></p>
      <p><code>31    errsys("fcntl error for my_lock_wait");</code></p>
      <p><code>32  }</code></p>
      <p><code>33 }</code></p>
      <empty-line/>
      <p><code>34 void</code></p>
      <p><code>35 my_lock_release()</code></p>
      <p><code>36 {</code></p>
      <p><code>37  if (fcntl(lock_fd, F_SETLKW, &amp;unlock_it)) &lt; 0)</code></p>
      <p><code>38   errsys("fcntl error for my_lock_release");</code></p>
      <p><code>39 }</code></p>
      <p>Новая версия нашего сервера с предварительным порождением процессов работает теперь под SVR4, гарантируя, что в данный момент времени только один дочерний процесс блокирован в вызове функции <code>accept</code>. Сравнивая строки 2 и 3 в табл. 30.1 (результаты для серверов Digital Unix и BSD/OS), мы видим, что такой тип блокировки увеличивает время, затрачиваемое центральным процессором на узле сервера.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Веб-сервер Apache (http://www.apache.org) использует технологию предварительного порождения процессов, причем если позволяет реализация, все дочерние процессы блокируются в вызове функции accept, иначе используется блокировка файла для защиты вызова accept.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Эффект наличия слишком большого количества дочерних процессов</p>
      </title>
      <p>Мы можем проверить, возникает ли в данной версии сервера эффект «общей побудки», рассмотренный в предыдущем разделе. Как и раньше, время работы ухудшается пропорционально числу избыточных дочерних процессов.</p>
     </section>
     <section>
      <title>
       <p>Распределение клиентских соединений между дочерними процессами</p>
      </title>
      <p>Используя функцию, показанную в листинге 30.10, мы можем исследовать распределение клиентских запросов между свободными дочерними процессами. Результат показан в табл. 30.2. Операционная система распределяет блокировки файла равномерно между ожидающими процессами, и такое поведение характерно для нескольких протестированных нами систем.</p>
     </section>
    </section>
    <section>
     <title>
      <p>30.8. Сервер TCP с предварительным порождением процессов и защитой вызова accept при помощи взаимного исключения</p>
     </title>
     <p>Как мы уже говорили, существует несколько способов синхронизации процессов путем блокирования. Блокировка файла по стандарту POSIX, рассмотренная в предыдущем разделе, переносится на все POSIX-совместимые системы, но она подразумевает некоторые операции с файловой системой, которые могут потребовать времени. В этом разделе мы будем использовать блокировку при помощи взаимного исключения, обладающую тем преимуществом, что ее можно применять для синхронизации не только потоков внутри одного процесса, но и потоков, относящихся к различным процессам.</p>
     <p>Функция <code>main</code> остается такой же, как и в предыдущем разделе, то же относится к функциям <code>child_make</code> и <code>child_main</code>. Меняются только три функции, осуществляющие блокировку. Чтобы использовать взаимное исключение между различными процессами, во-первых, требуется хранить это взаимное исключение в разделяемой процессами области памяти, а во-вторых, библиотека потоков должна получить указание о том, что взаимное исключение совместно используется различными процессами.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Требуется также, чтобы библиотека потоков поддерживала атрибут PTHREAD_PROCESS_SHARED.</p>
     </cite>
     <p>Существует несколько способов разделения памяти между различными процессами, что мы подробно описываем во втором томе<a l:href="#n13" type="note">[2]</a> данной серии. В этом примере мы используем функцию <code>mmap</code> с устройством <code>/dev/zero</code>, которое работает с ядрами Solaris и другими ядрами SVR4. В листинге 30.14 показана только функция <code>my_lock_init</code>.</p>
     <p><strong>Листинг 30.14</strong>. Функция my_lock_init: использование взаимного исключения потоками, относящимися к различным процессам (технология Pthread)</p>
     <p><code>//server/lock_pthread.c</code></p>
     <p><code> 1 #include "unpthread.h"</code></p>
     <empty-line/>
     <p><code> 2 #include &lt;sys/mman.h&gt;</code></p>
     <p><code> 3 static pthread_mutex_t *mptr; /* фактически взаимное исключение будет</code></p>
     <p><code>                                    в совместно используемой памяти */</code></p>
     <empty-line/>
     <p><code> 4 void</code></p>
     <p><code> 5 my_lock_init(char *pathname)</code></p>
     <p><code> 6 {</code></p>
     <p><code> 7  int fd;</code></p>
     <p><code> 8  pthread_mutexattr_t mattr;</code></p>
     <empty-line/>
     <p><code> 9  fd = Open("/dev/zero", O_RDWR, 0);</code></p>
     <empty-line/>
     <p><code>10  mptr = Mmap(0, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE,</code></p>
     <p><code>11   MAP_SHARED, fd, 0);</code></p>
     <p><code>12  Close(fd);</code></p>
     <empty-line/>
     <p><code>13  Pthread_mutexattr_init(&amp;mattr);</code></p>
     <p><code>14  Pthread_mutexattr_setpshared(&amp;mattr, PTHREAD_PROCESS_SHARED);</code></p>
     <p><code>15  Pthread_mutex_init(mptr, &amp;mattr);</code></p>
     <p><code>16 }</code></p>
     <p><code>9-12</code> Мы открываем (<code>open</code>) файл <code>/dev/zero</code>, а затем вызываем <code>mmap</code>. Количество байтов (второй аргумент этой функции) — это размер переменной <code>pthread_mutex_t</code>. Затем дескриптор закрывается, но для нас это не имеет значения, так как файл уже отображен в память.</p>
     <p><code>13-15</code> В приведенных ранее примерах взаимных исключений Pthread мы инициализировали глобальные статические взаимные исключения, используя константу <code>PTHREAD_MUTEX_INITIALIZER</code> (см., например, листинг 26.12). Но располагая взаимное исключение в совместно используемой памяти, мы должны вызвать некоторые библиотечные функции Pthreads, чтобы сообщить библиотеке о наличии семафора в совместно используемой памяти и о том, что он будет применяться для синхронизации потоков, относящихся к различным процессам. Мы должны инициализировать структуру <code>pthread_mutexattr_t</code> задаваемыми по умолчанию атрибутами взаимного исключения, а затем установить значение атрибута <code>PTHREAD_PROCESS_SHARED</code>. (По умолчанию значением этого атрибута должно быть <code>PTHREAD_PROCESS_PRIVATE</code>, что подразумевает использование взаимного исключения только в пределах одного процесса.) Затем вызов <code>pthread_mutex_init</code> инициализирует взаимное исключение указанными атрибутами.</p>
     <p>В листинге 30.15 показаны только функции <code>my_lock_wait</code> и <code>my_lock_release</code>. Они содержат вызовы функций Pthreads, предназначенных для блокирования и разблокирования взаимного исключения.</p>
     <p><strong>Листинг 30.15</strong>. Функции my_lock_wait и my_lock_release: использование блокировок Pthread</p>
     <p><code>//server/lock_pthread.c</code></p>
     <p><code>17 void</code></p>
     <p><code>18 my_lock_wait()</code></p>
     <p><code>19 {</code></p>
     <p><code>20  Pthread_mutex_lock(mptr),</code></p>
     <p><code>21 }</code></p>
     <empty-line/>
     <p><code>22 void</code></p>
     <p><code>23 my_lock_release()</code></p>
     <p><code>24 {</code></p>
     <p><code>25  Pthread_mutex_unlock(mptr);</code></p>
     <p><code>26 }</code></p>
     <p>Сравнивая строки 3 и 4 табл. 30.1, можно заметить, что версия, использующая синхронизацию процессов при помощи взаимного исключения, характеризуется более высоким быстродействием, чем версия с блокировкой файла.</p>
    </section>
    <section>
     <title>
      <p>30.9. Сервер TCP с предварительным порождением процессов: передача дескриптора</p>
     </title>
     <p>Последней модификацией нашего сервера с предварительным порождением процессов является версия, в которой только родительский процесс вызывает функцию <code>accept</code>, а затем «передает» присоединенный сокет какому-либо одному дочернему процессу. Это помогает обойти необходимость защиты вызова <code>accept</code>, но требует некоторого способа передачи дескриптора между родительским и дочерним процессами. Эта техника также несколько усложняет код, поскольку родительскому процессу приходится отслеживать, какие из дочерних процессов заняты, а какие свободны, чтобы передавать дескриптор только свободным дочерним процессам.</p>
     <p>В предыдущих примерах сервера с предварительным порождением процессов родительскому процессу не приходилось беспокоиться о том, какой дочерний процесс принимает соединение с клиентом. Этим занималась операционная система, организуя вызов функции <code>accept</code> одним из свободных дочерних процессов или блокировку файла или взаимного исключения. Из первых двух столбцов табл. 30.2 видно, что операционная система, в которой мы проводим измерения, осуществляет равномерную циклическую загрузку свободных процессов клиентскими соединениями.</p>
     <p>В данном примере для каждого дочернего процесса нам нужна некая структура, содержащая информацию о нем. Заголовочный файл <code>child.h</code>, в котором определяется структура <code>Child</code>, показан в листинге 30.16.</p>
     <p><strong>Листинг 30.16</strong>. Структура Child</p>
     <p><code>//server/child.h</code></p>
     <p><code>1 typedef struct {</code></p>
     <p><code>2  pid_t child_pid;  /* ID процесса */</code></p>
     <p><code>3  int child_pipefd; /* программный (неименованный) канал между</code></p>
     <p><code>                        родительским и дочерним процессами */</code></p>
     <p><code>4  int child_status; /* 0 = готово */</code></p>
     <p><code>5  long child_count; /* количество обрабатываемых соединений */</code></p>
     <p><code>6 } Child;</code></p>
     <empty-line/>
     <p><code>7 Child *cptr; /* массив структур Child */</code></p>
     <p>Мы записываем идентификатор дочернего процесса, дескриптор программного канала (pipe) родительского процесса, связанного с дочерним, статус дочернего процесса и количество обрабатываемых дочерним процессом клиентских соединений. Это количество выводится обработчиком сигнала <code>SIGINT</code> и позволяет нам отслеживать распределение клиентских запросов между дочерними процессами.</p>
     <p>Рассмотрим сначала функцию <code>child_make</code>, которая приведена в листинге 30.17. Мы создаем канал и доменный сокет Unix (см. главу 14) перед вызовом функции <code>fork</code>. После того, как создан дочерний процесс, родительский процесс закрывает один дескриптор (<code>sockfd[1]</code>), а дочерний процесс закрывает другой дескриптор (<code>sockfd[0]</code>). Более того, дочерний процесс подключает свой дескриптор канала (<code>sockfd[1]</code>) к стандартному потоку сообщений об ошибках, так что каждый дочерний процесс просто использует это устройство для связи с родительским процессом. Этот механизм проиллюстрирован схемой, приведенной на рис. 30.3.</p>
     <p><strong>Листинг 30.17</strong>. Функция child_make: передача дескриптора в сервере с предварительным порождением дочерних процессов</p>
     <p><code>//server/child05.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <p><code> 2 #include "child.h"</code></p>
     <empty-line/>
     <p><code> 3 pid_t</code></p>
     <p><code> 4 child_make(int i, int listenfd, int addrlen)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  int sockfd[2];</code></p>
     <p><code> 7  pid_t pid;</code></p>
     <p><code> 8  void child_main(int, int, int);</code></p>
     <empty-line/>
     <p><code> 9  Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);</code></p>
     <empty-line/>
     <p><code>10  if ((pid = Fork()) &gt; 0) {</code></p>
     <p><code>11   Close(sockfd[1]);</code></p>
     <p><code>12   cptr[i].child_pid = pid;</code></p>
     <p><code>13   cptr[i].child_pipefd = sockfd[0];</code></p>
     <p><code>14   cptr[i].child_status = 0;</code></p>
     <p><code>15   return (pid); /* родительский процесс */</code></p>
     <p><code>16  }</code></p>
     <p><code>17  Dup2(sockfd[1], STDERR_FILENO); /* канал от дочернего процесса к</code></p>
     <p><code>                                       родительскому */</code></p>
     <p><code>18  Close(sockfd[0]);</code></p>
     <p><code>19  Close(sockfd[1]);</code></p>
     <p><code>20  Close(listenfd); /* дочернему процессу не требуется, чтобы</code></p>
     <p><code>                        он был открыт */</code></p>
     <p><code>21  child_main(i, listenfd, addrlen); /* никогда не завершается */</code></p>
     <p><code>22 }</code></p>
     <image l:href="#img_162.png"/>
     <p><strong>Рис. 30.3</strong>. Канал после того, как дочерний и родительский процесс закрыли один конец</p>
     <p>После создания всех дочерних процессов мы получаем схему, показанную на рис. 30.4. Мы закрываем прослушиваемый сокет в каждом дочернем процессе, поскольку только родительский процесс вызывает функцию <code>accept</code>. Мы показываем на рисунке, что родительский процесс должен обрабатывать прослушиваемый сокет, а также все доменные сокеты. Как можно догадаться, родительский процесс использует функцию <code>select</code> для мультиплексирования всех дескрипторов.</p>
     <image l:href="#img_163.png"/>
     <p><strong>Рис. 30.4</strong>. Каналы после создания всех дочерних процессов</p>
     <p>В листинге 30.18 показана функция <code>main</code>. В отличие от предыдущих версий этой функции, в данном случае в памяти размещаются все наборы дескрипторов и в каждом наборе включены все биты, соответствующие прослушиваемому сокету и каналу каждого дочернего процесса. Вычисляется также максимальное значение дескриптора и выделяется память для массива структур <code>Child</code>. Основной цикл запускается при вызове функции <code>select</code>.</p>
     <p><strong>Листинг 30.18</strong>. Функция main, использующая передачу дескриптора</p>
     <p><code>//server/serv05.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <p><code> 2 #include "child.h"</code></p>
     <empty-line/>
     <p><code> 3 static int nchildren;</code></p>
     <empty-line/>
     <p><code> 4 int</code></p>
     <p><code> 5 main(int argc, char **argv)</code></p>
     <p><code> 6 {</code></p>
     <p><code> 7  int listenfd, i, navail, maxfd, nsel, connfd, rc;</code></p>
     <p><code> 8  void sig_int(int);</code></p>
     <p><code> 9  pid_t child_make(int, int, int);</code></p>
     <p><code>10  ssize_t n;</code></p>
     <p><code>11  fd_set rset, masterset;</code></p>
     <p><code>12  socklen_t addrlen, clilen;</code></p>
     <p><code>13  struct sockaddr *cliaddr;</code></p>
     <empty-line/>
     <p><code>14  if (argc == 3)</code></p>
     <p><code>15   listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
     <p><code>16  else if (argc == 4)</code></p>
     <p><code>17   listenfd = Tcp_listen(argv[1], argv[2], &amp;addrlen);</code></p>
     <p><code>18  else</code></p>
     <p><code>19   err_quit("usage; serv05 [ &lt;host&gt; ] &lt;port#&gt; &lt;#children&gt;");</code></p>
     <empty-line/>
     <p><code>20  FD_ZERO(&amp;masterset);</code></p>
     <p><code>21  FD_SET(listenfd, &amp;masterset);</code></p>
     <p><code>22  maxfd = listenfd;</code></p>
     <p><code>23  cliaddr = Malloc(addrlen);</code></p>
     <empty-line/>
     <p><code>24  nchildren = atoi(argv[argc - 1]);</code></p>
     <p><code>25  navail = nchildren;</code></p>
     <p><code>26  cptr = Calloc(nchildren, sizeof(Child));</code></p>
     <empty-line/>
     <p><code>27  /* предварительное создание дочерних процессов */</code></p>
     <p><code>28  for (i = 0; i &lt; nchildren; i++) {</code></p>
     <p><code>29   child_make(i, listenfd, addrlen); /* родительский процесс</code></p>
     <p><code>                                          завершается */</code></p>
     <p><code>30   FD_SET(cptr[i].child_pipefd, &amp;masterset);</code></p>
     <p><code>31   maxfd = max(maxfd, cptr[i].child_pipefd);</code></p>
     <p><code>32  }</code></p>
     <empty-line/>
     <p><code>33  Signal(SIGINT, sig_int);</code></p>
     <empty-line/>
     <p><code>34  for (;;) {</code></p>
     <p><code>35   rset = masterset;</code></p>
     <p><code>36   if (navail &lt;= 0)</code></p>
     <p><code>37    FD_CLR(listenfd, &amp;rset); /* выключаем, если нет свободных</code></p>
     <p><code>                                  дочерних процессов */</code></p>
     <p><code>38   nsel = Select(maxfd + 1, &amp;rset, NULL, NULL, NULL);</code></p>
     <empty-line/>
     <p><code>39   /* проверка новых соединений */</code></p>
     <p><code>40   if (FD_ISSET(listenfd, &amp;rset)) {</code></p>
     <p><code>41    clilen = addrlen;</code></p>
     <p><code>42    connfd = Accept(listenfd, cliaddr, &amp;clilen);</code></p>
     <empty-line/>
     <p><code>43    for (i = 0; i &lt; nchildren; i++)</code></p>
     <p><code>44     if (cptr[i].child_status == 0)</code></p>
     <p><code>45      break; /* свободный */</code></p>
     <empty-line/>
     <p><code>46    if (i == nchildren)</code></p>
     <p><code>47     err_quit("no available children");</code></p>
     <p><code>48    cptr[i].child_status = 1; /* отмечаем этот дочерний процесс как</code></p>
     <p><code>                                   занятый */</code></p>
     <p><code>49    cptr[i].child_count++;</code></p>
     <p><code>50    navail--;</code></p>
     <empty-line/>
     <p><code>51    n = Write_fd(cptr[i].child_pipefd, 1, connfd);</code></p>
     <p><code>52    Close(connfd);</code></p>
     <p><code>53    if (--nsel == 0)</code></p>
     <p><code>54     continue; /* с результатами select() закончено */</code></p>
     <p><code>55   }</code></p>
     <p><code>56   /* поиск освободившихся дочерних процессов */</code></p>
     <p><code>57   for (i = 0; i &lt; nchildren; i++) {</code></p>
     <p><code>58    if (FD_ISSET(cptr[i].child_pipefd, &amp;rset)) {</code></p>
     <p><code>59     if ((n = Read(cptr[i].child_pipefd, &amp;rc, 1)) == 0)</code></p>
     <p><code>60      err_quit("child %d terminated unexpectedly", i);</code></p>
     <p><code>61     cptr[i].child_status = 0;</code></p>
     <p><code>62     navail++;</code></p>
     <p><code>63     if (--nsel == 0)</code></p>
     <p><code>64      break; /* с результатами select() закончено */</code></p>
     <p><code>65    }</code></p>
     <p><code>66   }</code></p>
     <p><code>67  }</code></p>
     <p><code>68 }</code></p>
     <subtitle>Отключение прослушиваемого сокета в случае отсутствия свободных дочерних процессов</subtitle>
     <p><code>36-37</code> Счетчик <code>navail</code> отслеживает количество свободных дочерних процессов. Если его значение становится равным нулю, прослушиваемый сокет в наборе дескрипторов функции <code>select</code> выключается. Это предотвращает прием нового соединения в тот момент, когда нет ни одного свободного дочернего процесса. Ядро по- прежнему устанавливает эти соединения в очередь, пока их количество не превысит значения аргумента <code>backlog</code> функции <code>listen</code>, заданного для прослушиваемого сокета, но мы не хотим их принимать, пока у нас не появится свободный дочерний процесс, готовый обрабатывать клиентский запрос.</p>
     <subtitle>Прием нового соединения</subtitle>
     <p><code>39-55</code> Если прослушиваемый сокет готов для считывания, можно принимать (<code>accept</code>) новое соединение. Мы находим первый свободный дочерний процесс и передаем ему присоединенный сокет с помощью функции <code>write_fd</code>, приведенной в листинге 15.11. Вместе с дескриптором мы передаем 1 байт, но получатель не интересуется содержимым этого байта. Родитель закрывает присоединенный сокет.</p>
     <p>Мы всегда начинаем поиск свободного дочернего процесса с первого элемента массива структур <code>Child</code>. Это означает, что новое соединение для обработки поступившего клиентского запроса всегда получает первый элемент этого массива. Этот факт мы проверим при рассмотрении табл. 30.2 и значения счетчика <code>child_count</code> после завершения работы сервера. Если мы не хотим оказывать такое предпочтение первому элементу массива, мы можем запомнить, какой дочерний процесс получил последнее клиентское соединение, и каждый раз начинать поиск свободного дочернего процесса со следующего за ним, а по достижении конца массива переходить снова к первому элементу. В этом нет особого смысла (на самом деле все равно, какой дочерний процесс обрабатывает очередное соединение, если имеется несколько свободных дочерних процессов), если только планировочный алгоритм операционной системы не накладывает санкций на процессы, которые требуют относительно больших временных затрат центрального процессора. Более равномерное распределение загрузки между всеми дочерними процессами приведет к выравниванию времен, затраченных на их выполнение.</p>
     <subtitle>Обработка вновь освободившихся дочерних процессов</subtitle>
     <p><code>56-66</code> Когда дочерний процесс заканчивает обработку клиентского запроса, наша функция <code>child_main</code> записывает один байт в канал для родительского процесса. Тем самым родительский конец канала становится доступным для чтения. Упомянутый байт считывается (но его значение при этом игнорируется), а дочерний процесс помечается как свободный. Если же дочерний процесс завершит свое выполнение неожиданно, его конец канала будет закрыт, а операция чтения (<code>read</code>) возвратит нулевое значение. Это значение перехватывается и дочерний процесс завершается, но более удачным решением было бы записать ошибку и создать новый дочерний процесс для замены завершенного.</p>
     <p>Функция <code>child_main</code> показана в листинге 30.19.</p>
     <p><strong>Листинг 30.19</strong>. Функция child_main: передача дескриптора в сервере с предварительным порождением дочерних процессов</p>
     <p><code>//server/child05.c</code></p>
     <p><code>23 void</code></p>
     <p><code>24 child_main(int i, int listenfd, int addrlen)</code></p>
     <p><code>25 {</code></p>
     <p><code>26  char c;</code></p>
     <p><code>27  int connfd;</code></p>
     <p><code>28  ssize_t n;</code></p>
     <p><code>29  void web_child(int);</code></p>
     <empty-line/>
     <p><code>30  printf("child %ld starting\n", (long)getpid());</code></p>
     <p><code>31  for (;;) {</code></p>
     <p><code>32   if ((n = Read_fd(STDERR_FILENO, &amp;c, 1, &amp;connfd)) == 0)</code></p>
     <p><code>33    err_quit("read_fd returned 0");</code></p>
     <p><code>34   if (connfd &lt; 0)</code></p>
     <p><code>35    err_quit("no descriptor from read_fd");</code></p>
     <empty-line/>
     <p><code>36   web_child(connfd); /* обработка запроса */</code></p>
     <p><code>37   Close(connfd);</code></p>
     <empty-line/>
     <p><code>38   Write(STDERR_FILENO, "", 1); /* сообщаем родительскому процессу</code></p>
     <p><code>                                     о том, что дочерний освободился */</code></p>
     <p><code>39  }</code></p>
     <p><code>40 }</code></p>
     <subtitle>Ожидание дескриптора от родительского процесса</subtitle>
     <p><code>32-33</code> Эта функция отличается от аналогичных функций из двух предыдущих разделов, так как дочерний процесс не вызывает более функцию <code>accept</code>. Вместо этого дочерний процесс блокируется в вызове функции <code>read_fd</code>, ожидая, когда родительский процесс передаст ему дескриптор присоединенного сокета.</p>
     <subtitle>Сообщение родительскому процессу о готовности дочернего к приему новых запросов</subtitle>
     <p><code>38</code> Закончив обработку очередного клиентского запроса, мы записываем (<code>write</code>) 1 байт в канал, чтобы сообщить, что данный дочерний процесс освободился.</p>
     <p>В табл. 30.1 при сравнении строк 4 и 5 мы видим, что данный сервер медленнее, чем версия, рассмотренная нами в предыдущем разделе, которая использовала блокировку потоками взаимного исключения. Передача дескриптора по каналу от родительского процесса к дочернему и запись одного байта в канал для сообщения родительскому процессу о завершении обработки клиентского запроса занимает больше времени, чем блокирование и разблокирование взаимного исключения или файла.</p>
     <p>В табл. 30.2 показаны значения счетчиков <code>child_count</code> из структуры <code>Child</code>, которые выводятся обработчиком сигнала <code>SIGINT</code> по завершении работы сервера. Дочерние процессы, расположенные ближе к началу массива, обрабатывают большее количество клиентских запросов, как было указано при обсуждении листинга 30.18.</p>
    </section>
    <section>
     <title>
      <p>30.10. Параллельный сервер TCP: один поток для каждого клиента</p>
     </title>
     <p>Предыдущие пять разделов были посвящены рассмотрению серверов, в которых для обработки клиентских запросов используются дочерние процессы, либо заранее порождаемые с помощью функции <code>fork</code>, либо требующие вызова этой функции для каждого вновь поступившего клиентского запроса. Если же сервер поддерживает потоки, мы можем применить потоки вместо дочерних процессов.</p>
     <p>Наша первая версия сервера с использованием потоков показана в листинге 30.20. Это модификация листинга 30.2: в ней создается один поток для каждого клиента вместо одного дочернего процесса для каждого клиента. Эта версия во многом похожа на сервер, представленный в листинге 26.2.</p>
     <p><strong>Листинг 30.20</strong>. Функция main для сервера TCP, использующего потоки</p>
     <p><code>//server/serv06.c</code></p>
     <p><code> 1 #include "unpthread.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int listenfd, connfd;</code></p>
     <p><code> 6  void sig_int(int);</code></p>
     <p><code> 7  void *doit(void*);</code></p>
     <p><code> 8  pthread_t tid;</code></p>
     <p><code> 9  socklen_t clilen, addrlen;</code></p>
     <p><code>10  struct sockaddr *cliaddr;</code></p>
     <empty-line/>
     <p><code>11  if (argc == 2)</code></p>
     <p><code>12   listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
     <p><code>13  else if (argc == 3)</code></p>
     <p><code>14   listenfd = Tcp_listen(argv[1], argv[2], &amp;addrlen);</code></p>
     <p><code>15  else</code></p>
     <p><code>16   err_quit("usage: serv06 [ &lt;host&gt; ] &lt;port#&gt;");</code></p>
     <p><code>17  cliaddr = Malloc(addrlen);</code></p>
     <p><code>18  Signal (SIGINT, sig_int);</code></p>
     <p><code>19  for (;;) {</code></p>
     <p><code>20   clilen = addrlen;</code></p>
     <p><code>21   connfd = Accept(listenfd, cliaddr, &amp;clilen);</code></p>
     <empty-line/>
     <p><code>22   Pthread_create(&amp;tid, NULL, &amp;doit, (void*)connfd);</code></p>
     <p><code>23  }</code></p>
     <p><code>24 }</code></p>
     <empty-line/>
     <p><code>25 void*</code></p>
     <p><code>26 doit(void *arg)</code></p>
     <p><code>27 {</code></p>
     <p><code>28  void web_child(int);</code></p>
     <empty-line/>
     <p><code>29  Pthread_detach(pthread_self());</code></p>
     <p><code>30  web_child((int)arg);</code></p>
     <p><code>31  Close((int)arg);</code></p>
     <p><code>32  return (NULL);</code></p>
     <p><code>33 }</code></p>
     <subtitle>Цикл основного потока</subtitle>
     <p><code>19-23</code> Основной поток блокируется в вызове функции accept, и каждый раз, когда прибывает новое клиентское соединение, функцией <code>pthread_create</code> создается новый поток. Функция, выполняемая новым потоком, — это функция <code>doit</code>, а ее аргументом является присоединенный сокет.</p>
     <subtitle>Функция прочих потоков</subtitle>
     <p><code>25-33</code> Функция <code>doit</code> выполняется как отсоединенный (detached) поток, потому что основному потоку не требуется ждать ее завершения. <code>Doit</code> вызывает функцию <code>web_child</code> (см. листинг 30.5). Когда эта функция возвращает управление, присоединенный сокет закрывается.</p>
     <p>Из табл. 30.1 мы видим, что эта простая версия с использованием потоков является более быстродействующей, чем даже самая быстрая из версий с предварительным порождением процессов. Кроме того, эта версия, в которой каждый клиент обслуживается одним потоком, во много раз быстрее версии, в которой каждый клиент обслуживается специально созданным для него дочерним процессом (первая строка табл. 30.1).</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В разделе 26.5 мы упомянули о трех вариантах преобразования функции, которая не является безопасной в многопоточной среде, в функцию, обеспечивающую требуемую безопасность. Функция web_child вызывает функцию readline, и версия, показанная в листинге 3.12, не является безопасной в многопоточной среде. На примере, приведенном в листинге 30.20, были испробованы вторая и третья альтернативы из раздела 26.5. Увеличение быстродействия при переходе от альтернативы 3 к альтернативе 2 составило менее одного процента, вероятно, потому, что функция readline использовалась лишь для считывания значения счетчика (5 символов) от клиента. Поэтому в данной главе для простоты мы использовали более медленную версию из листинга 3.11 для сервера с предварительным порождением потоков.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>30.11. Сервер TCP с предварительным порождением потоков, каждый из которых вызывает accept</p>
     </title>
     <p>Ранее в этой главе мы обнаружили, что версии, в которых заранее создается пул дочерних процессов, работают быстрее, чем те, в которых для каждого клиентского запроса приходится вызывать функцию <code>fork</code>. Для систем, поддерживающих потоки, логично предположить, что имеется та же закономерность: быстрее сразу создать пул потоков при запуске сервера, чем создавать по одному потоку по мере поступления запросов от клиентов. Основная идея такого сервера заключается в том, чтобы создать пул потоков, каждый из которых вызывает затем функцию <code>accept</code>. Вместо того чтобы блокировать потоки в вызове <code>accept</code>, мы используем взаимное исключение, как в разделе 30.8. Это позволяет вызывать функцию accept только одному потоку в каждый момент времени. Использовать блокировку файла для защиты <code>accept</code> в таком случае бессмысленно, так как при наличии нескольких потоков внутри данного процесса можно использовать взаимное исключение.</p>
     <p>В листинге 30.21 показан заголовочный файл <code>pthread07.h</code>, определяющий структуру <code>Thread</code>, содержащую определенную информацию о каждом потоке.</p>
     <p><strong>Листинг 30.21</strong>. Заголовочный файл pthread07.h</p>
     <p><code>//server/pthread07.h</code></p>
     <p><code>1 typedef struct {</code></p>
     <p><code>2  pthread_t thread_tid; /* идентификатор потока */</code></p>
     <p><code>3  long thread_count; /* количество обработанных запросов */</code></p>
     <p><code>4 } Thread;</code></p>
     <p><code>5 Thread *tptr; /* массив структур Thread */</code></p>
     <empty-line/>
     <p><code>6 int listenfd, nthreads;</code></p>
     <p><code>7 socklen_t addrlen;</code></p>
     <p><code>8 pthread_mutex_t mlock;</code></p>
     <p>Мы также объявляем несколько глобальных переменных, таких как дескриптор прослушиваемого сокета и взаимное исключение, которые должны совместно использоваться всеми потоками.</p>
     <p>В листинге 30.22 показана функция <code>main</code>.</p>
     <p><strong>Листинг 30.22</strong>. Функция main для сервера TCP с предварительным порождением потоков</p>
     <p><code>//server/serv07.c</code></p>
     <p><code> 1 #include "unpthread.h"</code></p>
     <p><code> 2 #include "pthread07.h"</code></p>
     <empty-line/>
     <p><code> 3 pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;</code></p>
     <empty-line/>
     <p><code> 4 int</code></p>
     <p><code> 5 main(int argc, char **argv)</code></p>
     <p><code> 6 {</code></p>
     <p><code> 7  int i;</code></p>
     <p><code> 8  void sig_int(int), thread_make(int);</code></p>
     <empty-line/>
     <p><code> 9  if (argc == 3)</code></p>
     <p><code>10   listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
     <p><code>11  else if (argc == 4)</code></p>
     <p><code>12   listenfd = Tcp_1isten(argv[1], argv[2], &amp;addrlen);</code></p>
     <p><code>13  else</code></p>
     <p><code>14   err_quit("usage: serv07 [ &lt;host&gt; ] &lt;port#&gt; &lt;#threads&gt;");</code></p>
     <p><code>15  nthreads = atoi(argv[argc - 1]);</code></p>
     <p><code>16  tptr = Calloc(nthreads, sizeof(Thread));</code></p>
     <empty-line/>
     <p><code>17  for (i = 0; i &lt; nthreads; i++)</code></p>
     <p><code>18  thread_make(i); /* завершается только основной поток */</code></p>
     <empty-line/>
     <p><code>19  Signal(SIGINT, sig_int);</code></p>
     <empty-line/>
     <p><code>20  for (;;)</code></p>
     <p><code>21   pause(); /* потоки все выполнили */</code></p>
     <p><code>22 }</code></p>
     <p>Функции <code>thread_make</code> и <code>thread_main</code> показаны в листинге 30.23.</p>
     <p><strong>Листинг 30.23</strong>. Функции thread_make и thread_main</p>
     <p><code>//server/pthread07.c</code></p>
     <p><code> 1 #include "unpthread.h"</code></p>
     <p><code> 2 #include "pthread07.h"</code></p>
     <empty-line/>
     <p><code> 3 void</code></p>
     <p><code> 4 thread_make(int i)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  void *thread_main(void*);</code></p>
     <empty-line/>
     <p><code> 7  Pthread_create(&amp;tptr[i].thread_tid, NULL, &amp;thread_main, (void*)i);</code></p>
     <p><code> 8  return; /* завершается основной поток */</code></p>
     <p><code> 9 }</code></p>
     <empty-line/>
     <p><code>10 void*</code></p>
     <p><code>11 thread_main(void *arg)</code></p>
     <p><code>12 {</code></p>
     <p><code>13  int connfd;</code></p>
     <p><code>14  void web_child(int);</code></p>
     <p><code>15  socklen_t clilen;</code></p>
     <p><code>16  struct sockaddr *cliaddr;</code></p>
     <empty-line/>
     <p><code>17  cliaddr = Malloc(addrlen);</code></p>
     <empty-line/>
     <p><code>18  printf("thread %d starting\n", (int)arg);</code></p>
     <p><code>19  for (;;) {</code></p>
     <p><code>20   clilen = addrlen;</code></p>
     <p><code>21   Pthread_mutex_lock(&amp;mlock);</code></p>
     <p><code>22   connfd = Accept(listenfd, cliaddr, &amp;clilen);</code></p>
     <p><code>23   Pthread_mutex_unlock(&amp;mlock);</code></p>
     <p><code>24   tptr[(int)arg].thread_count++;</code></p>
     <empty-line/>
     <p><code>25   web_child(connfd); /* обработка запроса */</code></p>
     <p><code>26   Close(connfd);</code></p>
     <p><code>27  }</code></p>
     <p><code>28 }</code></p>
     <subtitle>Создание потоков</subtitle>
     <p><code>7</code> Создаются потоки, каждый из которых выполняет функцию <code>pthread_main</code>. Единственным аргументом этой функции является порядковый номер потока.</p>
     <p><code>21-23</code> Функция <code>thread_main</code> вызывает функции <code>pthread_mutex_lock</code> и <code>pthread_mutex_unlock</code> соответственно до и после вызова функции <code>accept</code>.</p>
     <p>Сравнивая строки 6 и 7 в табл. 30.1, можно заметить, что эта последняя версия нашего сервера быстрее, чем версия с созданием нового потока для каждого клиентского запроса. Этого можно было ожидать, так как в данной версии мы сразу создаем пул потоков и не тратим время на создание новых потоков по мере поступления клиентских запросов. На самом деле эта версия сервера — самая быстродействующая для всех операционных систем, которые мы испытывали.</p>
     <p>В табл. 30.2 показано распределение значений счетчика <code>thread_count</code> структуры <code>Thread</code>, которые мы выводим с помощью обработчика сигнала <code>SIGINT</code> по завершении работы сервера. Равномерность этого распределения объясняется тем, что при выборе потока, который будет блокировать взаимное исключение, алгоритм планирования загрузки потоков последовательно перебирает все потоки в цикле.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В Беркли-ядрах нам не нужна блокировка при вызове функции accept, так что мы можем использовать версию, представленную в листинге 30.23, без взаимных исключений. Но в результате этого время, затрачиваемое центральным процессором, увеличится. Если рассмотреть два компонента, из которых складывается время центрального процессора — пользовательское и системное время — то окажется, что первый компонент уменьшается при отсутствии блокировки (поскольку блокирование осуществляется в библиотеке потоков, входящей в пользовательское пространство), но системное время возрастает (за счет эффекта «общей побудки», возникающего, когда все потоки, блокированные в вызове функции accept, выходят из состояния ожидания при появлении нового клиентского соединения). Для того чтобы каждое соединение передавалось только одному потоку, необходима некая разновидность взаимного исключения, и оказывается, что быстрее это делают сами потоки, а не ядро.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>30.12. Сервер с предварительным порождением потоков: основной поток вызывает функцию accept</p>
     </title>
     <p>Последняя рассматриваемая нами версия сервера устроена следующим образом: главный поток создает пул потоков при запуске сервера, после чего он же вызывает функцию <code>accept</code> и передает каждое клиентское соединение какому-либо из свободных на данный момент потоков. Это аналогично передаче дескриптора в версии, рассмотренной нами в разделе 30.9.</p>
     <p>При таком устройстве сервера необходимо решить, каким именно образом должна осуществляться передача присоединенного дескриптора одному из потоков в пуле. Существует несколько способов решения этой задачи. Можно, как и прежде, использовать передачу дескриптора, но при этом не требуется передавать дескриптор от одного потокам к другому, так как все они, в том числе и главный поток, принадлежат одному и тому же процессу. Все, что требуется знать потоку, получающему дескриптор, — это номер дескриптора. В листинге 30.24 показан заголовочный файл <code>pthread08.h</code>, определяющий структуру <code>Thread</code>, аналогичный файлу, показанному в листинге 30.21.</p>
     <p><strong>Листинг 30.24</strong>. Заголовочный файл pthread08.h</p>
     <p><code>//server/pthread08.h</code></p>
     <p><code> 1 typedef struct {</code></p>
     <p><code> 2  pthread_t thread_tid; /* идентификатор потока */</code></p>
     <p><code> 3  long thread_count; /* количество обработанных запросов */</code></p>
     <p><code> 4 } Thread;</code></p>
     <p><code> 5 Thread *tptr; /* массив структур Thread */</code></p>
     <empty-line/>
     <p><code> 6 #define MAXNCLI 32</code></p>
     <p><code> 7 int clifd[MAXNCLI], iget, iput;</code></p>
     <p><code> 8 pthread_mutex_t clifd_mutex;</code></p>
     <p><code> 9 pthread_cond_t clifd_cond;</code></p>
     <subtitle>Определение массива для записи дескрипторов присоединенных сокетов</subtitle>
     <p>6-9 Мы определяем массив <code>clifd</code>, в который главный поток записывает дескрипторы присоединенных сокетов. Свободные потоки из пула получают по одному дескриптору из этого массива и обрабатывают соответствующий запрос, <code>iput</code> — это индекс в данном массиве для очередного элемента, записываемого в него главным потоком, a <code>iget</code> — это индекс очередного элемента массива, передаваемого свободному потоку для обработки. Разумеется, эта структура данных, совместно используемая всеми потоками, должна быть защищена, и поэтому мы используем условную переменную и взаимное исключение.</p>
     <p>В листинге 30.25 показана функция <code>main</code>.</p>
     <p><strong>Листинг 30.25</strong>. Функция main для сервера с предварительным порождением потоков</p>
     <p><code>//server/serv08.c</code></p>
     <p><code> 1 #include "unpthread.h"</code></p>
     <p><code> 2 #include "pthread08.h"</code></p>
     <empty-line/>
     <p><code> 3 static int nthreads;</code></p>
     <p><code> 4 pthread_mutex_t clifd_mutex = PTHREAD_MUTEX_INITIALIZER;</code></p>
     <p><code> 5 pthread_cond_t clifd_cond = PTHREAD_COND_INITIALIZER;</code></p>
     <empty-line/>
     <p><code> 6 int</code></p>
     <p><code> 7 main(int argc, char **argv)</code></p>
     <p><code> 8 {</code></p>
     <p><code> 9  int i, listenfd, connfd;</code></p>
     <p><code>10  void sig_int(int), thread_make(int);</code></p>
     <p><code>11  socklen_t addrlen, clilen;</code></p>
     <p><code>12  struct sockaddr *cliaddr;</code></p>
     <empty-line/>
     <p><code>13  if (argc == 3)</code></p>
     <p><code>14   listenfd = Tcp_listen(NULL, argv[1], &amp;addrlen);</code></p>
     <p><code>15  else if (argc == 4)</code></p>
     <p><code>16   listenfd = Tcp_listen(argv[1], argv[2], &amp;addrlen);</code></p>
     <p><code>17  else</code></p>
     <p><code>18   err_quit("usage: serv08 [ &lt;host&gt; ] &lt;port#&gt; &lt;#threads&gt;");</code></p>
     <p><code>19  cliaddr = Malloc(addrlen);</code></p>
     <empty-line/>
     <p><code>20  nthreads = atoi(argv[argc - 1]);</code></p>
     <p><code>21  tptr = Calloc(nthreads, sizeof(Thread));</code></p>
     <p><code>22  iget = iput = 0;</code></p>
     <empty-line/>
     <p><code>23  /* создание всех потоков */</code></p>
     <p><code>24  for (i = 0; i &lt; nthreads; i++)</code></p>
     <p><code>25   thread_make(i); /* завершается только основной поток */</code></p>
     <empty-line/>
     <p><code>26  Signal(SIGINT, sig_int);</code></p>
     <empty-line/>
     <p><code>27  for (;;) {</code></p>
     <p><code>28   clilen = addrlen;</code></p>
     <p><code>29   connfd = Accept(listenfd, cliaddr, &amp;clilen);</code></p>
     <empty-line/>
     <p><code>30   Pthread_mutex_lock(&amp;clifd_mutex);</code></p>
     <p><code>31   clifd[iput] = connfd;</code></p>
     <p><code>32   if (++iput == MAXNCLI)</code></p>
     <p><code>33    iput = 0;</code></p>
     <p><code>34   if (iput == iget)</code></p>
     <p><code>35    err_quit("iput = iget = %d", iput);</code></p>
     <p><code>36   Pthread_cond_signal(&amp;clifd_cond);</code></p>
     <p><code>37   Pthread_mutex_unlock(&amp;clifd_mutex);</code></p>
     <p><code>38  }</code></p>
     <p><code>39 }</code></p>
     <subtitle>Создание пула потоков</subtitle>
     <p><code>23-25</code> Функция <code>thread_make</code> создает все потоки.</p>
     <subtitle>Ожидание прихода клиентского соединения</subtitle>
     <p><code>27-38</code> Основной поток блокируется в вызове функции <code>accept</code>, ожидая появления нового соединения. При появлении этого соединения дескриптор присоединенного сокета записывается в следующий элемент массива <code>clifd</code> после блокирования взаимного исключения. Мы также следим, чтобы индекс <code>iget</code> не совпал со значением индекса <code>iput</code>, что укажет на недостаточно большой размер массива. Условная переменная сигнализирует о прибытии нового запроса, и взаимное исключение разблокируется, позволяя одному из потоков пула обслужить прибывший запрос.</p>
     <p>Функции <code>thread_make</code> и <code>thread_main</code> показаны в листинге 30.26. Первая из них идентична функции, приведенной в листинге 30.23.</p>
     <p><strong>Листинг 30.26</strong>. Функции thread_make и thread_main</p>
     <p><code>//server/pthread08.c</code></p>
     <p><code> 1 #include "unpthread.h"</code></p>
     <p><code> 2 #include "pthread08.h"</code></p>
     <empty-line/>
     <p><code> 3 void</code></p>
     <p><code> 4 thread_make(int i)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  void *thread_main(void*);</code></p>
     <empty-line/>
     <p><code> 7  Pthread_create(&amp;tptr[i].thread_tid, NULL, &amp;thread_main, (void*)i);</code></p>
     <p><code> 8  return; /* завершается основной поток */</code></p>
     <p><code> 9 }</code></p>
     <empty-line/>
     <p><code>10 void*</code></p>
     <p><code>11 thread_main(void *arg)</code></p>
     <p><code>12 {</code></p>
     <p><code>13  int connfd;</code></p>
     <p><code>14  void web_child(int);</code></p>
     <empty-line/>
     <p><code>15  printf("thread %d starting\n", (int)arg);</code></p>
     <p><code>16  for (;;) {</code></p>
     <p><code>17   Pthread_mutex_lock(&amp;clifd_mutex);</code></p>
     <p><code>18   while (iget == iput)</code></p>
     <p><code>19    Pthread_cond_wait(&amp;clifd_cond, &amp;clifd_mutex);</code></p>
     <p><code>20   connfd = clifd[iget]; /* присоединенный сокет, который требуется</code></p>
     <p><code>                              обслужить */</code></p>
     <p><code>21   if (++iget == MAXNCLI)</code></p>
     <p><code>22    iget = 0;</code></p>
     <p><code>23   Pthread_mutex_unlock(&amp;clifd_mutex);</code></p>
     <p><code>24   tptr[(int)arg].thread_count++;</code></p>
     <empty-line/>
     <p><code>25   web_child(connfd); /* обработка запроса */</code></p>
     <p><code>26   Close(connfd);</code></p>
     <p><code>27  }</code></p>
     <p><code>28 }</code></p>
     <subtitle>Ожидание присоединенного сокета, который требует обслуживания</subtitle>
     <p><code>17-26</code> Каждый поток из пула пытается блокировать взаимное исключение, блокирующее доступ к массиву <code>clifd</code>. Если после того, как взаимное исключение заблокировано, оказывается, что индексы <code>iput</code> и <code>iget</code> равны, то вызывается функция <code>pthread_cond_wait</code>, и поток переходит в состояние ожидания, так как ему пока нечего делать. После прибытия очередного клиентского запроса основной поток вызывает функцию <code>pthread_cond_signal</code>, выводя тем самым из состояния ожидания поток, заблокировавший взаимное исключение. Когда этот поток получает соединение, он вызывает функцию <code>web_child</code>.</p>
     <p>Значения времени центрального процессора, приведенные в табл. 30.1, показывают, что эта версия сервера медленнее рассмотренной в предыдущем разделе (когда каждый поток из пула сам вызывал функцию <code>accept</code>). Причина заключается в том, что рассматриваемая в данном разделе версия использует как взаимное исключение, так и условную переменную, тогда как в предыдущем случае (см. листинг 30.23) применялось только взаимное исключение.</p>
     <p>Если мы рассмотрим гистограмму количества клиентов, обслуживаемых каждым потоком из пула, то окажется, что распределение клиентских запросов по потокам будет таким же, как показано в последнем столбце табл. 30.2. Это означает, что если основной поток вызывает функцию <code>pthread_cond_signal</code>, то при выборе очередного потока, который будет выведен из состояния ожидания для обслуживания клиентского запроса, осуществляется последовательный перебор всех имеющихся свободных потоков.</p>
    </section>
    <section>
     <title>
      <p>30.13. Резюме</p>
     </title>
     <p>В этой главе мы рассмотрели 9 различных версий сервера и их работу с одним и тем же веб-клиентом, чтобы сравнить значения времени центрального процессора, затраченного на управление процессом.</p>
     <p>0. Последовательный сервер (точка отсчета — управление процессом отсутствует).</p>
     <p>1. Параллельный сервер, по одному вызову функции <code>fork</code> для каждого клиента.</p>
     <p>2. Предварительное порождение дочерних процессов, каждый из которых вызывает функцию <code>accept</code>.</p>
     <p>3. Предварительное порождение дочерних процессов с блокировкой файла для защиты функции <code>accept</code>.</p>
     <p>4. Предварительное порождение дочерних процессов с блокировкой взаимного исключения дочерними процессами для защиты функции <code>accept</code>.</p>
     <p>5. Предварительное порождение дочерних процессов с передачей дескриптора от родительского процесса дочернему.</p>
     <p>6. Параллельный сервер, поочередное создание потоков по мере поступления клиентских запросов.</p>
     <p>7. Предварительное порождение потоков с блокировкой взаимного исключения потоками для защиты функции <code>accept</code>.</p>
     <p>8. Предварительное порождение потоков, основной поток вызывает функцию <code>accept</code>.</p>
     <p>Резюмируя материал этой главы, можно сделать несколько комментариев.</p>
     <p>&#9632; Если сервер не слишком загружен, хорошо работает традиционная модель параллельного сервера, в которой при поступлении очередного клиентского запроса вызывается функция <code>fork</code> для создания нового дочернего процесса. Этот вариант допускает комбинирование с демоном <code>inetd</code>, принимающим все клиентские запросы. Остальные версии применимы в случае загруженных серверов, таких как веб-серверы.</p>
     <p>&#9632; Создание пула дочерних процессов или потоков сокращает временные затраты центрального процессора по сравнению с традиционной моделью (один вызов функции <code>fork</code> для каждого запроса) в 10 и более раз. При этом не слишком усложняется код, но становится необходимо (как говорилось при обсуждении примеров) отслеживать количество свободных дочерних процессов и корректировать его по мере необходимости, так как количество клиентских запросов, которые требуется обслужить, динамически изменяется.</p>
     <p>&#9632; Некоторые реализации допускают блокирование нескольких потоков или дочерних процессов в вызове функции <code>accept</code>, в то время как другие реализации требуют использования блокировки того или иного типа для защиты <code>accept</code>. Можно использовать для этого либо блокировку файла, либо блокировку взаимного исключения Pthreads.</p>
     <p>&#9632; Как правило, версия, в которой каждый поток или дочерний процесс вызывает функцию <code>accept</code>, проще и быстрее, чем версия, где вызов функции <code>accept</code> осуществляется только основным потоком (или родительским процессом), впоследствии передающим дескриптор присоединенного сокета другому потоку или дочернему процессу.</p>
     <p>&#9632; Блокировка всех дочерних процессов или программных потоков в вызове функции <code>accept</code> предпочтительнее, чем блокировка в вызове функции <code>select</code>, что объясняется возможностью появления коллизий при вызове функции <code>select</code>.</p>
     <p>&#9632; Использование потоков, как правило, дает больший выигрыш во времени, чем использование процессов. Но выбор между версиями 1 и 6 (один дочерний процесс на каждый запрос и один поток на каждый запрос) зависит от свойств операционной системы и от того, какие еще программы задействованы в обслуживании клиентских запросов. Например, если сервер, принимающий клиентское соединение, вызывает функции <code>fork</code> и <code>exec</code>, то может оказаться быстрее породить с помощью функции <code>fork</code> процесс с одним потоком, чем процесс с несколькими потоками.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. Почему на рис. 30.2 родительский процесс оставляет присоединенный сокет открытым, вместо того чтобы закрыть его, когда созданы все дочерние процессы?</p>
     <p>2. Попробуйте изменить сервер из раздела 30.9 таким образом, чтобы использовать дейтаграммный доменный сокет Unix вместо потокового сокета домена Unix. Что при этом изменяется?</p>
     <p>3. Запустите клиент и те серверы из рассмотренных в этой главе, которые позволяет запустить конфигурация вашей системы, и сравните полученные результаты с приведенными в тексте.</p>
    </section>
   </section>
   <section>
    <title>
     <p>Глава 31</p>
     <p>Потоки (STREAMS)</p>
    </title>
    <section>
     <title>
      <p>31.1. Введение</p>
     </title>
     <p>В этой главе мы приводим обзор потоков STREAMS и функций, используемых приложением для доступа к потоку. Наша цель — понять, как реализованы сетевые протоколы в рамках потоковых систем. Также мы создаем простой клиент TCP с использованием TPI — интерфейса, который обеспечивает доступ к транспортному уровню и обычно применяется сокетами в системах, основанных на потоках. Дополнительную информацию о потоках, в том числе о написании программ для ядер, использующих потоки, можно найти в [98].</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Технология потоков была введена Денисом Ритчи (Dennis Ritchie) [104] и получила широкое распространение с появлением системы SVR3 в 1986 году. Спецификация POSIX определяет STREAMS как «дополнительную группу», то есть система может не поддерживать потоки STREAMS, но если она их поддерживает, то реализация должна соответствовать POSIX. Любая система, производная от System V, должна поддерживать потоки, а различные системы 4x.BSD потоки не поддерживают.</p>
      <p>Потоковая система часто обозначается как STREAMS, но поскольку это название не является акронимом, то в данной книге используется слово «потоки».</p>
      <p>Не следует смешивать «потоковую систему ввода-вывода» (streams I/O system), которую мы описываем в данной главе, и «стандартные потоки ввода-вывода» (standard I/O streams), а также программные потоки (threads). Второй термин используется применительно к стандартной библиотеке ввода-вывода (например, таким функциям, как fopen, fgets, printf и т.п.).</p>
     </cite>
    </section>
    <section>
     <title>
      <p>31.2. Обзор</p>
     </title>
     <section>
      <p>Потоки обеспечивают двустороннее соединение между процессом и <emphasis>драйвером</emphasis>, как показано на рис. 31.1. Хотя нижний блок на этом рисунке мы и называем драйвером, его не следует ассоциировать с каким-либо аппаратным устройством, поскольку это может быть и драйвер псевдоустройства (например, программный драйвер).</p>
      <image l:href="#img_164.png"/>
      <p><strong>Рис. 31.1</strong>. Поток между процессом и драйвером</p>
      <p><emphasis>Головной модуль потока</emphasis> (<emphasis>stream head</emphasis>) состоит из программ ядра, которые запускаются при обращении приложения к дескриптору потока (например, при вызове функций <code>read</code>, <code>putmsg</code>, <code>ioctl</code> и т.п.).</p>
      <p>Процесс может динамически добавлять и удалять промежуточные <emphasis>модули обработки</emphasis> (<emphasis>processing modules</emphasis>) между головным модулем и драйвером. Такой модуль осуществляет некий тип фильтрации сообщений, проходящих в одну или другую сторону по потоку. Этот процесс показан на рис. 31.2.</p>
      <image l:href="#img_165.png"/>
      <p><strong>Рис. 31.2</strong>. Поток с модулем обработки</p>
      <p>В поток может быть помещено любое количество модулей. Под словом «поместить» (push) в данном случае понимается, что каждый новый модуль вставляется сразу после (на рисунке — ниже) головного модуля.</p>
      <p>Определенный тип псевдодрайвера называется <emphasis>мультиплексором</emphasis> (<emphasis>multiplexor</emphasis>). Он принимает данные из различных источников. Основанная на потоках реализация набора протоколов TCP/IP, используемая, например, в SVR4, может иметь вид, показанный на рис. 31.3.</p>
      <image l:href="#img_166.png"/>
      <p><strong>Рис. 31.3</strong>. Упрощенный вид реализации набора протоколов TCP/IP, основанной на потоках</p>
      <p>&#9632; При создании сокета библиотекой сокетов в поток помещается модуль <code>sockmod</code>. Именно комбинация библиотеки сокетов и потокового модуля обеспечивает API сокетов для процесса.</p>
      <p>&#9632; При создании точки доступа XTI библиотекой XTI в поток помещается модуль <code>timod</code>. Именно комбинация библиотеки XTI и потокового модуля обеспечивает API XTI для процесса.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Это одно из немногих мест, где мы говорим об XTI. Предыдущее издание этой книги описывало интерфейс XTI очень подробно, но он уже вышел из широкого употребления, и даже спецификация POSIX больше не включает его, поэтому мы решили исключить ставшие ненужными главы из книги. На рис. 31.3 показано, каким образом обычно реализуется интерфейс XTI. В этой главе мы кратко расскажем о нем, но не будем вдаваться в подробности, потому что причин для использования XTI в настоящее время практически нет.</p>
      </cite>
      <p>&#9632; Для использования функций <code>read</code> или <code>write</code> в точке доступа XTI требуется поместить в поток потоковый модуль <code>tirdwr</code>. Это осуществляется процессом, использующим TCP, который на рис. 31.3 изображен четвертым слева. Вероятно, этот процесс тем самым отказался от использования XTI, поэтому мы убрали надпись «библиотека XTI» из соответствующего блока.</p>
      <p>&#9632; Формат сетевых сообщений, передаваемых по потокам вверх и вниз, определяют интерфейсы различных сервисов. Мы описываем три наиболее широко распространенных. <emphasis>TPI</emphasis> (<emphasis>Transport Provider Interface — интерфейс поставщика транспортных служб</emphasis>) [126] определяет интерфейс, предоставляемый поставщиком услуг транспортного уровня (например, TCP или UDP). <emphasis>NPI</emphasis> (<emphasis>Network Provider Interface — интерфейс поставщика сетевого уровня</emphasis>) [125] определяет интерфейс, предоставляемый поставщиком услуг сетевого уровня (например, IP). <emphasis>DLPI</emphasis> (<emphasis>Data Link Provider Interface</emphasis>) — это <emphasis>интерфейс поставщика канального уровня</emphasis> [124]. Еще один источник информации по TPI и DLPI, в котором имеются также исходные коды на языке С, — это [98].</p>
      <p>Каждый компонент потока — головной модуль, все модули обработки и драйвер — содержат по меньшей мере одну пару очередей: очередь на запись и очередь на чтение. Это показано на рис. 31.4.</p>
      <image l:href="#img_167.png"/>
      <p><strong>Рис. 31.4</strong>. Каждый компонент потока содержит по меньшей мере одну пару очередей</p>
     </section>
     <section>
      <title>
       <p>Типы сообщений</p>
      </title>
      <p>Потоковые сообщения могут быть классифицированы как <emphasis>имеющие высокий приоритет</emphasis> (<emphasis>high priority</emphasis>), <emphasis>входящие в полосу приоритета</emphasis> (<emphasis>priority band</emphasis>) и обычные (<emphasis>normal</emphasis>). Существует 256 полос приоритета со значениями между 0 и 255, причем обычные сообщения соответствуют полосе 0. Приоритет потокового сообщения используется как при постановке сообщения в очередь, так и для управления потоком (flow control). По соглашению, на сообщения с высоким приоритетом управление потоком не влияет.</p>
      <p>На рис. 31.5 показан порядок следования сообщений в одной конкретной очереди.</p>
      <image l:href="#img_168.png"/>
      <p><strong>Рис. 31.5</strong>. Порядок следования потоковых сообщений в очереди в зависимости от их приоритета</p>
      <p>Хотя потоковые системы поддерживают 256 различных полос приоритета, в сетевых протоколах обычно используется полоса 1 для срочных (внеполосных) данных и полоса 0 для обычных данных.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Внеполосные данные TCP в TPI не рассматриваются как истинные срочные данные. В самом деле, в TCP полоса 0 используется как для обычных, так и для внеполосных данных. Полоса 1 используется для отправки срочных данных в тех протоколах, в которых срочные данные (а не просто срочный указатель, как в TCP) отправляются перед обычными данными. В данном контексте следует внимательно отнестись к термину «обычный» (normal). В системах SVR, предшествующих SVR4, не было полос приоритета, а сообщения делились на обычные и приоритетные (priority messages). В SVR4 были введены полосы приоритета, что потребовало также введения функций getpmsg и putpmsg, которые мы вскоре опишем. Приоритетные сообщения были переименованы в сообщения с высоким приоритетом, и встал вопрос, как называть сообщения, относящиеся к полосам приоритета от 1 до 255. Наиболее распространенной является терминология [98], согласно которой все сообщения, которые не являются сообщениями с высоким приоритетом, называются обычными сообщениями и разделяются на подкатегории согласно своим полосам приоритета. Термин «обычное сообщение» в любом случае должен соответствовать сообщению из полосы приоритета 0.</p>
      </cite>
      <p>Хотя пока мы говорили только о сообщениях с высоким приоритетом и об обычных сообщениях, существует около 12 типов обычных сообщений и около 18 типов сообщений с высоким приоритетом. С точки зрения приложений и функций <code>getmsg</code> и <code>putmsg</code>, которые мы опишем в следующем разделе, нам интересны только три различных типа сообщений: <code>M_DATA</code>, <code>M_PROTO</code> и <code>M_PCPROTO</code> (<code>PC</code> означает «priority control», то есть приоритетное управление, и подразумевает сообщения с высоким приоритетом). В табл. 31.1 показано, как эти три типа сообщений генерируются функциями <code>write</code> и <code>putmsg</code>.</p>
      <empty-line/>
      <p><strong>Таблица 31.1</strong>. Типы потоковых сообщений, генерируемые функциями write и putmsg</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Функция</th>
        <th align="left" valign="top">Управляющая информация?</th>
        <th align="left" valign="top">Данные?</th>
        <th align="left" valign="top">Флаги</th>
        <th align="left" valign="top">Генерируемый тип сообщения</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">write</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top"/>
        <td align="left" valign="top">M_DATA</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">putmsg</td>
        <td align="left" valign="top">Нет</td>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top">0</td>
        <td align="left" valign="top">M_DATA</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">putmsg</td>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top">Все равно</td>
        <td align="left" valign="top">0</td>
        <td align="left" valign="top">M_PROTO</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">putmsg</td>
        <td align="left" valign="top">Да</td>
        <td align="left" valign="top">Все равно</td>
        <td align="left" valign="top">MSG_HIPRI</td>
        <td align="left" valign="top">M_PCPROTO</td>
       </tr>
      </table>
     </section>
    </section>
    <section>
     <title>
      <p>31.3. Функции getmsg и putmsg</p>
     </title>
     <p>Данные, передаваемые в обоих направлениях по потоку, состоят из сообщений, а каждое сообщение содержит <emphasis>данные</emphasis>, <emphasis>управляющую информацию</emphasis> или и то и другое. Если мы используем функции <code>read</code> или <code>write</code>, то мы можем передавать только данные. Для того чтобы процесс мог записывать и считывать как данные, так и управляющую информацию, необходимо добавить две новые функции.</p>
     <p><code>#include &lt;stropts.h&gt;</code></p>
     <empty-line/>
     <p><code>int getmsg(int <emphasis>fd</emphasis>, struct strbuf *<emphasis>ctlptr</emphasis>, struct strbuf *<emphasis>dataptr</emphasis>, int *<emphasis>flagsp</emphasis>);</code></p>
     <p><code>int putmsg(int <emphasis>fd</emphasis>, const struct strbuf *<emphasis>ctlptr</emphasis>,</code></p>
     <p><code> const struct strbuf *<emphasis>dataptr</emphasis>, int <emphasis>flags</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают: неотрицательное значение в случае успешного выполнения (см. пояснения в тексте), -1 в случае ошибки</emphasis></code></p>
     <p>Обе составляющие сообщения — и сами данные, и управляющая информация — описываются структурой <code>strbuf</code>:</p>
     <p><code>struct strbuf {</code></p>
     <p><code> int  maxlen; /* максимальный размер буфера buf */</code></p>
     <p><code> int  len;    /* фактическое количество данных в buf */</code></p>
     <p><code> char *buf;   /* данные */</code></p>
     <p><code>};</code></p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Обратите внимание на аналогию между структурами strbuf и netbuf. Имена элементов обеих структур одинаковы.</p>
      <p>Однако обе длины в структуре netbuf относятся к типу данных unsigned int (целое без знака), тогда как обе длины в структуре srtbuf — к типу int (целое со знаком). Причина в том, что некоторые потоковые функции используют значение -1 элементов len и maxlen для указания на определенные специальные ситуации.</p>
     </cite>
     <p>С помощью функции <code>putmsg</code> мы можем отправлять или данные, или управляющую информацию, или и то и другое вместе. Для указания на отсутствие управляющей информации мы можем или задать <code>ctlptr</code> как пустой указатель, или установить значение <code>ctlptr-&gt;len</code> равным -1. Этот же способ используется для указания на отсутствие данных.</p>
     <p>При отсутствии управляющей информации функцией <code>putmsg</code> генерируется сообщение типа <code>M_DATA</code> (см. табл. 31.1), в противном случае генерируется сообщение типа <code>M_PROTO</code> либо <code>M_PCPROTO</code> в зависимости от значения аргумента <code>flags</code>. Этот аргумент функции <code>putmsg</code> имеет нулевое значение для обычных сообщений, а для сообщений с высоким приоритетом его значение равно <code>RS_HIPRI</code>.</p>
     <p>Последний аргумент функции <code>getmsg</code> имеет тип «значение-результат». Если при вызове функции целочисленное значение, на которое указывает аргумент <code>flagsp</code>, — это 0, то возвращается первое сообщение из потока (которое может быть как обычным, так и имеющим высокий приоритет). Если при вызове функции целочисленное значение соответствует <code>RS_HIPRI</code>, то функция будет ждать появления в головном модуле потока сообщения с высоким приоритетом. В обоих случаях в зависимости от типа возвращенного сообщения значение, на которое указывает аргумент <code>flagsp</code>, будет либо 0, либо <code>RS_HIPRI</code>.</p>
     <p>Предположим, что мы передаем функции <code>getmsg</code> непустые указатели <code>ctlptr</code> и <code>dataptr</code>. Тогда указанием на отсутствие управляющей информации (возвращается сообщение типа <code>M_DATA</code>) является значение <code>ctlptr-&gt;len</code>, установленное в -1. Аналогично, если отсутствуют данные, указанием на это является значение -1 элемента <code>dataptr-&gt;len</code>.</p>
     <p>Если функция <code>putmsg</code> выполнилась успешно, то она возвращает нулевое значение, а в случае ошибки возвращается значение -1. Но функция <code>getmsg</code> возвращает нулевое значение только в том случае, если вызывающему процессу было доставлено все сообщение целиком. Если буфер, предназначенный для приема управляющей информации, слишком мал, то возвращается значение <code>MORECTL</code> (о котором заранее известно, что оно является неотрицательным). Аналогично, если буфер для приема данных оказывается слишком мал, возвращается значение <code>MOREDATA</code>. Если же оба эти буфера оказываются слишком малы, то возвращается логическая сумма этих двух флагов.</p>
    </section>
    <section>
     <title>
      <p>31.4. Функции getpmsg и putpmsg</p>
     </title>
     <p>Когда с выпуском SVR4 к потоковым системам была добавлена поддержка различных полос приоритета, появились новые варианты функций <code>getmsg</code> и <code>putmsg</code>.</p>
     <p><code>#include &lt;stropts.h&gt;</code></p>
     <empty-line/>
     <p><code>int getpmsg(int <emphasis>fd</emphasis>, struct strbuf *<emphasis>ctlptr</emphasis>,</code></p>
     <p><code> struct strbuf *<emphasis>dataptr</emphasis>, int *<emphasis>bandp</emphasis>, int *<emphasis>flagsp</emphasis>);</code></p>
     <p><code>int putpmsg(int <emphasis>fd</emphasis>, const struct strbuf *<emphasis>ctlptr</emphasis>,</code></p>
     <p><code> const struct strbuf *<emphasis>dataptr</emphasis>, int <emphasis>band</emphasis>, int <emphasis>flags</emphasis>);</code></p>
     <p><code><emphasis>Обе функции возвращают: неотрицательное значение в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Аргумент <code>band</code> функции <code>putpmsg</code> должен иметь значение в пределах от 0 до 255 включительно. Если аргумент <code>flags</code> имеет значение <code>MSG_BAND</code>, то генерируется сообщение в соответствующей полосе приоритета. Присваивание аргументу <code>flags</code> значения <code>MSG_BAND</code> и задание полосы 0 эквивалентно вызову функции <code>putmsg</code>. Если значение аргумента <code>flags</code> равно <code>MSG_HIPRI</code>, то аргумент <code>band</code> должен быть равен нулю, и тогда генерируется сообщение с высоким приоритетом. (Обратите внимание на то, что этот флаг имеет название, отличающееся от названия <code>RS_HIPRI</code>, используемого в случае функции <code>putmsg</code>.)</p>
     <p>Два целочисленных значения, на которые указывают аргументы <code>bandp</code> и <code>flagsp</code> функции <code>getpmsg</code>, являются аргументами типа «значение-результат». Целочисленное значение, на которое указывает аргумент <code>flagsp</code> функции <code>getpmsg</code>, может соответствовать <code>MSG_HIPRI</code> (для чтения сообщений с высоким приоритетом), <code>MSG_BAND</code> (для чтения сообщений из полосы приоритета, по меньшей мере равной целочисленному значению, на которое указывает аргумент <code>bandp</code>) или <code>MSG_ANY</code> (для чтения любых сообщений). По завершении функции целочисленное значение, на которое указывает аргумент <code>bandp</code>, указывает на полосу приоритета прочитанного сообщения, а целое число, на которое указывает аргумент <code>flagsp</code>, соответствует <code>MSG_HIPRI</code> (если было прочитано сообщение с высоким приоритетом) или MSG_BAND (если было прочитано иное сообщение).</p>
    </section>
    <section>
     <title>
      <p>31.5. Функция ioctl</p>
     </title>
     <p>Говоря о потоках, мы снова возвращаемся к функции <code>ioctl</code>, которая уже была описана в главе 17.</p>
     <p><code>#include &lt;stropts.h&gt;</code></p>
     <empty-line/>
     <p><code>int ioctl(int <emphasis>fd</emphasis>, int <emphasis>request</emphasis>, ... /* void *<emphasis>arg</emphasis> */ );</code></p>
     <p><code><emphasis>Возвращает: 0 в случае успешного выполнения, -1 в случае ошибки</emphasis></code></p>
     <p>Единственным изменением относительно прототипа функции, приведенного в разделе 17.2, является включение заголовочного файла, необходимого для работы с потоками.</p>
     <p>Существует примерно 30 запросов (<code>request</code>), так или иначе влияющих на головной модуль потока. Каждый из запросов начинается с <code>I_</code>, и обычно документация на них приводится на странице руководства <code>streamio</code>.</p>
    </section>
    <section>
     <title>
      <p>31.6. TPI: интерфейс поставщика транспортных служб</p>
     </title>
     <p>На рис. 31.3 мы показали, что TPI — это интерфейс, предоставляющий доступ к транспортному уровню для расположенных выше уровней. Этот интерфейс используется в потоковой среде как сокетами, так и XTI. Из рис. 31.3 видно, что комбинация библиотеки сокетов и <code>sokmod</code>, а также комбинация библиотеки XTI и <code>timod</code> обмениваются сообщениями TPI с TCP и UDP.</p>
     <p>TPI является интерфейсом, <emphasis>основанным на сообщениях</emphasis> (<emphasis>message-based</emphasis>). Он определяет сообщения, которыми обменивается приложение (например, XTI или библиотека сокетов) и транспортный уровень. Точнее, TPI задает формат этих сообщений и то, какое действие производит каждое из сообщений. Во многих случаях приложение посылает запрос поставщику (например, «Связать данный локальный адрес»), а поставщик посылает обратно ответ («Выполнено» или «Ошибка»). Некоторые события, происходящие асинхронно на стороне поставщика (например, прибытие запроса на соединение с сервером), инициируют отправку сигнала или сообщения вверх по потоку.</p>
     <p>Мы можем обойти как XTI, так и сокеты, и использовать непосредственно TPI. В этом разделе мы заново перепишем код нашего простого клиента времени и даты с использованием TPI вместо сокетов (сокетная версия представлена в листинге 1.1). Если провести аналогию с языками программирования, то использование XTI или сокетов можно сравнить с программированием на языках высокого уровня, таких как С или Pascal, а непосредственно TPI — с программированием на ассемблере. Мы не являемся сторонниками непосредственного использования TPI в реальной жизни. Но понимание того, как работает TPI, и написание примера с использованием этого протокола позволит нам глубже понять, как работает библиотека сокетов в потоковой среде.</p>
     <p>В листинге 31.1<a l:href="#n1" type="note">[1]</a> показан наш заголовочный файл <code>tpi_daytime.h</code>.</p>
     <p><strong>Листинг 31.1</strong>. Наш заголовочный файл tpi_daytime.h</p>
     <p><code>//streams/tpi_daytime.h</code></p>
     <p><code> 1 #include "unpxti.h"</code></p>
     <p><code> 2 #include &lt;sys/stream.h&gt;</code></p>
     <p><code> 3 #include &lt;sys/tihdr.h&gt;</code></p>
     <empty-line/>
     <p><code> 4 void tpi_bind(int, const void*, size_t);</code></p>
     <p><code> 5 void tpi_connect(int, const void*, size_t);</code></p>
     <p><code> 6 ssize_t tpi_read(int, void*, size_t);</code></p>
     <p><code> 7 void tpi_close(int);</code></p>
     <p>Нам нужно включить еще один дополнительный заголовочный файл помимо <code>&lt;sys/tihdr.h&gt;</code>, содержащего определения структур для всех сообщений TPI.</p>
     <p><strong>Листинг 31.2</strong>. Функция main для нашего клиента времени и даты с использованием TPI</p>
     <p><code>//streams/tpi_daytime.c</code></p>
     <p><code> 1 #include "tpi_daytime.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int fd, n;</code></p>
     <p><code> 6  char recvline[MAXLINE + 1];</code></p>
     <p><code> 7  struct sockaddr_in myaddr, servaddr;</code></p>
     <empty-line/>
     <p><code> 8  if (argc != 2)</code></p>
     <p><code> 9   err_quit("usage: tpi_daytime &lt;Ipaddress&gt;");</code></p>
     <empty-line/>
     <p><code>10  fd = Open(XTI_TCP, O_RDWR, 0);</code></p>
     <empty-line/>
     <p><code>11  /* связываем произвольный локальный адрес */</code></p>
     <p><code>12  bzero(&amp;myaddr, sizeof(myaddr));</code></p>
     <p><code>13  myaddr.sin_family = AF_INET;</code></p>
     <p><code>14  myaddr.sin_addr.s_addr = htonl(INADDR_ANY);</code></p>
     <p><code>15  myaddr.sin_port = htons(0);</code></p>
     <empty-line/>
     <p><code>16  tpi_bind(fd, &amp;myaddr, sizeof(struct sockaddr_in));</code></p>
     <empty-line/>
     <p><code>17  /* заполняем адрес сервера */</code></p>
     <p><code>18  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>19  servaddr.sin_family = AF_INET;</code></p>
     <p><code>20  servaddr.sin_port = htons(13); /* сервер времени и даты */</code></p>
     <p><code>21  Inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr);</code></p>
     <empty-line/>
     <p><code>22  tpi_connect(fd, &amp;servaddr, sizeof(struct sockaddr_in));</code></p>
     <empty-line/>
     <p><code>23  for (;;) {</code></p>
     <p><code>24   if ((n = tpi_read(fd, recvline, MAXLINE)) &lt;= 0) {</code></p>
     <p><code>25    if (n == 0)</code></p>
     <p><code>26     break;</code></p>
     <p><code>27    else</code></p>
     <p><code>28    err_sys("tpi_read error");</code></p>
     <p><code>29   }</code></p>
     <p><code>30   recvline[n] = 0; /* завершающий нуль */</code></p>
     <p><code>31   fputs(recvline, stdout);</code></p>
     <p><code>32  }</code></p>
     <p><code>33  tpi_close(fd);</code></p>
     <p><code>34  exit(0);</code></p>
     <p><code>35 }</code></p>
     <subtitle>Открытие транспортного устройства, связывание локального адреса</subtitle>
     <p><code>10-16</code> Мы открываем устройство, соответствующее поставщику транспортных служб (обычно <code>/dev/tcp</code>). Мы заполняем структуру адреса сокета Интернета значениями <code>INADDR_ANY</code> и 0 (для порта), указывая тем самым TCP связать произвольный локальный адрес с нашей точкой доступа. Мы вызываем свою собственную функцию <code>tpi_bind</code> (которая будет приведена чуть ниже) для выполнения этого связывания.</p>
     <subtitle>Заполнение структуры адреса сервера, установление соединения</subtitle>
     <p><code>17-22</code> Мы заполняем другую структуру адреса сокета Интернета, внося в нее IP-адрес сервера (из командной строки) и порт (13). Мы вызываем нашу функцию <code>tpi_connect</code> для установления соединения.</p>
     <subtitle>Считывание данных с сервера, копирование в стандартный поток вывода</subtitle>
     <p><code>23-33</code> Как и в случае других клиентов времени и даты, мы просто копируем данные, пришедшие по соединению, в стандартный поток вывода, останавливаясь при получении признака конца файла, присланного сервером (например, сегмент FIN). Мы сделали этот цикл похожим на тот, который использовался в коде сокетного клиента (см. листинг 1.1), поскольку наша функция <code>tpi_read</code> при нормальном завершении соединения на стороне сервера будет возвращать нулевое значение. Затем мы вызываем нашу функцию <code>tpi_close</code> для того, чтобы закрыть эту точку доступа.</p>
     <p>Наша функция <code>tpi_bind</code> показана в листинге 31.3.</p>
     <p><strong>Листинг 31.3</strong>. Функция tpi_bind: связывание локального адреса с точкой доступа</p>
     <p><code>//streams/tpi_bind.c</code></p>
     <p><code> 1 #include "tpi_daytime.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 tpi_bind(int fd, const void *addr, size_t addrlen)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  struct {</code></p>
     <p><code> 6   struct T_bind_req msg_hdr;</code></p>
     <p><code> 7   char addr[128];</code></p>
     <p><code> 8  } bind_req;</code></p>
     <p><code> 9  struct {</code></p>
     <p><code>10   struct T_bind_ack msg_hdr;</code></p>
     <p><code>11   char addr[128];</code></p>
     <p><code>12  } bind_ack;</code></p>
     <p><code>13  struct strbuf ctlbuf;</code></p>
     <p><code>14  struct T_error_ack *error_ack;</code></p>
     <p><code>15  int flags;</code></p>
     <p><code>16  bind_req.msg_hdr.PRIM_type = T_BIND_REQ;</code></p>
     <p><code>17  bind_req.msg_hdr.ADDR_length = addrlen;</code></p>
     <p><code>18  bind_req.msg_hdr.ADDR_offset = sizeof(struct T_bind_req);</code></p>
     <p><code>19  bind_req.msg_hdr.CONIND_number = 0;</code></p>
     <p><code>20  memcpy(bind_req.addr, addr, addrlen); /* sockaddr_in{} */</code></p>
     <empty-line/>
     <p><code>21  ctlbuf.len = sizeof(struct T_bind_req) + addrlen;</code></p>
     <p><code>22  ctlbuf.buf = (char*)&amp;bind_req;</code></p>
     <p><code>23  Putmsg(fd, &amp;ctlbuf, NULL, 0);</code></p>
     <empty-line/>
     <p><code>24  ctlbuf.maxlen = sizeof(bind_ack);</code></p>
     <p><code>25  ctlbuf.len = 0;</code></p>
     <p><code>26  ctlbuf.buf = (char*)&amp;bind_ack;</code></p>
     <p><code>27  flags = RS_HIPRI;</code></p>
     <p><code>28  Getmsg(fd, &amp;ctlbuf, NULL, &amp;flags);</code></p>
     <p><code>29  if (ctlbuf.len &lt; (int)sizeof(long))</code></p>
     <p><code>30   err_quit("bad length from getmsg");</code></p>
     <empty-line/>
     <p><code>31  switch (bind_ack.msg_hdr.PRIM_type) {</code></p>
     <p><code>32  case T_BIND_ACK:</code></p>
     <p><code>33   return;</code></p>
     <empty-line/>
     <p><code>34  case T_ERROR_ACK:</code></p>
     <p><code>35   if (ctlbuf.len &lt; (int)sizeof(struct T_error_ack))</code></p>
     <p><code>36    err_quit("bad length for T_ERROR_ACK");</code></p>
     <p><code>37   error_ack = (struct T_error_ack*)&amp;bind_ack.msg_hdr;</code></p>
     <p><code>38   err_quit("T_ERROR_ACK from bind (%d, %d)",</code></p>
     <p><code>39    error_ack-&gt;TLI_error, error_ack-&gt;UNIX_error);</code></p>
     <empty-line/>
     <p><code>40  default:</code></p>
     <p><code>41   err_quit("unexpected message type: %d", bind_ack.msg_hdr.PRlM_type);</code></p>
     <p><code>42  }</code></p>
     <p><code>43 }</code></p>
     <subtitle>Заполнение структуры T_bind_req</subtitle>
     <p><code>16-20</code> Заголовочный файл <code>&lt;sys/tihdr.h&gt;</code> определяет структуру <code>T_bind_req</code>:</p>
     <p><code>struct T_bind_req {</code></p>
     <p><code> long          PRIM_type;     /* T_BIND_REQ */</code></p>
     <p><code> long          ADDR_length;   /* длина адреса */</code></p>
     <p><code> long          ADDR_offset;   /* смещение адреса */</code></p>
     <p><code> unsigned long CONIND_number; /* сообщения о соединении */</code></p>
     <p><code> /* далее следует адрес протокола для связывания */</code></p>
     <p><code>};</code></p>
     <p>Все запросы TPI определяются как структуры, начинающиеся с поля типа <code>long</code>. Мы определяем свою собственную структуру <code>bind_req</code>, начинающуюся со структуры <code>T_bind_req</code>, после которой располагается буфер, содержащий локальный адрес для связывания. TPI ничего не говорит о содержимом буфера — оно определяется поставщиком. Поставщик TCP предполагает, что этот буфер содержит структуру <code>sockaddr_in</code>.</p>
     <p>Мы заполняем структуру <code>T_bind_req</code>, устанавливая элемент <code>ADDR</code>_length равным размеру адреса (16 байт для структуры адреса сокета Интернета), а элемент <code>ADDR_offset</code> — равным байтовому сдвигу адреса (он следует непосредственно за структурой <code>T_bind_req</code>). У нас нет гарантии, что это местоположение соответствующим образом выровнено для записи структуры <code>sockaddr_in</code>, поэтому мы вызываем функцию <code>memcpy</code>, чтобы скопировать структуру вызывающего процесса в нашу структуру <code>bind_req</code>. Мы присваиваем элементу <code>CONIND_number</code> нулевое значение, потому что мы находимся на стороне клиента, а не на стороне сервера.</p>
     <subtitle>Вызов функции putmsg</subtitle>
     <p><code>21-23</code> TPI требует, чтобы только что созданная нами структура была передана поставщику как одно сообщение <code>M_PROTO</code>. Следовательно, мы вызываем функцию <code>putmsg</code>, задавая структуру <code>bind_req</code> в качестве управляющей информации, без каких-либо данных и с флагом 0.</p>
     <subtitle>Вызов функции getmsg для чтения сообщений с высоким приоритетом</subtitle>
     <p><code>24-30</code> Ответом на наш запрос <code>T_BIND_REQ</code> будет либо сообщение <code>T_BIND_ACK</code>, либо сообщение <code>T_ERROR_ACK</code>. Сообщения, содержащие подтверждение, отправляются как сообщения с высоким приоритетом (<code>M_PCPROTO</code>), так что мы считываем их при помощи функции <code>getmsg</code> с флагом <code>RS_HIPRI</code>. Поскольку ответ является сообщением с высоким приоритетом, он получает преимущество перед всеми обычными сообщениями в потоке.</p>
     <p>Эти два сообщения выглядят следующим образом:</p>
     <p><code>struct T_bind_ack {</code></p>
     <p><code> long          PRIM_type;     /* T_BIND_ACK */</code></p>
     <p><code> long          ADDR_length;   /* длина адреса */</code></p>
     <p><code> long          ADDR_offset;   /* смещение адреса */</code></p>
     <p><code> unsigned long CONIND_number; /* индекс подключения для помещения</code></p>
     <p><code>                                 в очередь */</code></p>
     <p><code>};</code></p>
     <empty-line/>
     <p><code> /* затем следует связанный адрес */</code></p>
     <p><code>struct T_error_ack {</code></p>
     <p><code> long PRIM_type;  /* T_ERROR_ACK */</code></p>
     <p><code> long ERROR_prim; /* примитивная ошибка ввода */</code></p>
     <p><code> long TLI_error;  /* код ошибки TLI */</code></p>
     <p><code> long UNIX_error; /* код ошибки UNIX */</code></p>
     <p><code>};</code></p>
     <p>В начале каждого сообщения указан его тип, так что мы можем начать считывать ответ, предполагая, что это сообщение <code>T_BIND_ACK</code>, а затем, прочитав его тип, обрабатывать его тем или иным способом. Мы не ждем никаких данных от поставщика, поэтому третий аргумент функции <code>getmsg</code> мы задаем как пустой указатель.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Когда мы проверяем, соответствует ли количество возвращенной управляющей информации по меньшей мере размеру длинного целого, нужно проявить осторожность, преобразуя значение sizeof в целое число. Оператор sizeof возвращает целое число без знака, но существует вероятность того, что значение возвращенного поля len будет -1. Поскольку при выполнении операции сравнения слева располагается значение со знаком, а справа — без знака, компилятор преобразует значение со знаком в значение без знака. Если рассматривать -1 как целое без знака в архитектуре с дополнением до 2, это число получается очень большим, то есть -1 оказывается больше 4 (если предположить, что длинное целое число занимает 4 байта).</p>
     </cite>
     <subtitle>Обработка ответа</subtitle>
     <p><code>31-33</code> Если ответ — это сообщение <code>T_BIND_ACK</code>, то связывание прошло успешно, и мы возвращаемся. Фактический адрес, связанный с точкой доступа, возвращается в элементе <code>addr</code> нашей структуры <code>bind_ack</code>, которую мы игнорируем.</p>
     <p><code>34-39</code> Если ответ — это сообщение <code>T_ERROR_ACK</code>, мы проверяем, было ли сообщение получено целиком, и выводим три значения, содержащиеся в возвращенной структуре. В этой простой программе при возникновении ошибки мы просто прекращаем выполнение и ничего не возвращаем вызывающему процессу.</p>
     <p>Чтобы увидеть ошибки, которые могут возникнуть в результате запроса на связывание, мы слегка изменим нашу функцию <code>main</code> и попробуем связать какой- либо порт, отличный от 0. Например, если мы попробуем связать порт 1 (что требует прав привилегированного пользователя, так как это порт с номером меньше 1024), мы получим следующий результат:</p>
     <p><code>solaris % <strong>tpi_daytime 127.0.0.1</strong></code></p>
     <p><code>T_ERROR_ACK from bind (3, 0)</code></p>
     <p>В этой системе значение константы <code>EACCESS</code> равно 3. Если мы поменяем номер порта, задав значение большее 1023, но используемое в настоящий момент другой точкой доступа TCP, мы получим:</p>
     <p><code>solaris % <strong>tpi_daytime 127.0.0.1</strong></code></p>
     <p><code>T_ERROR_ACK from bind (23, 0)</code></p>
     <p>В данной системе значение константы <code>EADDRBUSY</code> равно 23.</p>
     <p>Следующая функция показана в листинге 31.4. Это функция <code>tpi_connect</code>, устанавливающая соединение с сервером.</p>
     <p><strong>Листинг 31.4</strong>. Функция tpi_connect: установление соединения с сервером</p>
     <p><code>//streams/tpi_connect.c</code></p>
     <p><code> 1 #include "tpi_daytime.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 tpi_connect(int fd, const void *addr, size_t addrlen)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  struct {</code></p>
     <p><code> 6   struct T_conn_req msg_hdr;</code></p>
     <p><code> 7   char addr[128];</code></p>
     <p><code> 8  } conn_req;</code></p>
     <p><code> 9  struct {</code></p>
     <p><code>10   struct l_conn_con msg_hdr;</code></p>
     <p><code>11   char addr[128];</code></p>
     <p><code>12  } conn_con;</code></p>
     <p><code>13  struct strbuf ctlbuf;</code></p>
     <p><code>14  union T_primitives rcvbuf;</code></p>
     <p><code>15  struct T_error_ack *error_ack;</code></p>
     <p><code>16  struct T_discon_ind *discon_ind;</code></p>
     <p><code>17  int flags;</code></p>
     <empty-line/>
     <p><code>18  conn_req.msg_hdr.PRIM_type = T_CONN_REQ;</code></p>
     <p><code>19  conn_req.msg_hdr.DEST_length = addrlen;</code></p>
     <p><code>20  conn_req.msg_hdr.DEST_offset = sizeof(struct T_conn_req);</code></p>
     <p><code>21  conn_req.msg_hdr.OPT_length = 0;</code></p>
     <p><code>22  conn_req.msg_hdr.OPT_offset = 0;</code></p>
     <p><code>23  memcpy(conn_req.addr, addr, addrlen); /* sockaddr_in{} */</code></p>
     <p><code>24  ctlbuf.len = sizeof(struct T_conn_req) + addrlen;</code></p>
     <p><code>25  ctlbuf.buf = (char*)&amp;conn_req;</code></p>
     <p><code>26  Putmsg(fd, &amp;ctlbuf, NULL, 0);</code></p>
     <empty-line/>
     <p><code>27  ctlbuf.maxlen = sizeof(union T_primitives);</code></p>
     <p><code>28  ctlbuf.len = 0;</code></p>
     <p><code>29  ctlbuf.buf = (char*)&amp;rcvbuf;</code></p>
     <p><code>30  flags = RS_HIPRI;</code></p>
     <p><code>31  Getmsg(fd, &amp;ctlbuf, NULL, &amp;flags);</code></p>
     <p><code>32  if (ctlbuf.len &lt; (int)sizeof(long))</code></p>
     <p><code>33   err_quit("tpi_connect: bad length from getmsg");</code></p>
     <empty-line/>
     <p><code>34  switch (rcvbuf.type) {</code></p>
     <p><code>35  case T_OK_ACK:</code></p>
     <p><code>36   break;</code></p>
     <empty-line/>
     <p><code>37  case T_ERROR_ACK:</code></p>
     <p><code>38   if (ctlbuf.len &lt; (int)sizeof(struct T_error_ack))</code></p>
     <p><code>39    err_quit("tpi_connect: bad length for T_ERROR_ACK");</code></p>
     <p><code>40   error_ack = (struct T_error_ack*)&amp;rcvbuf;</code></p>
     <p><code>41   err_quit("tpi_connect: T_ERROR_ACK from conn %d, %d)",</code></p>
     <p><code>42    error_ack-&gt;TLI_error, error_ack-&gt;UNIX_error);</code></p>
     <empty-line/>
     <p><code>43  default:</code></p>
     <p><code>44   err_quit("tpi connect, unexpected message type: &amp;d", rcvbuf.type);</code></p>
     <p><code>45  }</code></p>
     <empty-line/>
     <p><code>46  ctlbuf.maxlen = sizeof(conn_con);</code></p>
     <p><code>47  ctlbuf.len = 0;</code></p>
     <p><code>48  ctlbuf.buf = (char*)&amp;conn_con;</code></p>
     <p><code>49  flags = 0;</code></p>
     <p><code>50  Getmsg(fd, &amp;ctlbuf, NULL, &amp;flags);</code></p>
     <p><code>51  if (ctlbuf.len &lt; (int)sizeof(long))</code></p>
     <p><code>52   err_quit("tpi_connect2: bad length from getmsg");</code></p>
     <empty-line/>
     <p><code>53  switch (conn_con.msg_hdr.PRIM_type) {</code></p>
     <p><code>54  case T_CONN_CON:</code></p>
     <p><code>55   break;</code></p>
     <empty-line/>
     <p><code>56  case T_DISCON_IND:</code></p>
     <p><code>57   if (ctlbuf.len &lt; (int)sizeof(struct T_discon_ind))</code></p>
     <p><code>58    err_quit("tpi_connect2: bad length for T_DISCON_IND");</code></p>
     <p><code>59   discon_ind = (struct T_discon_ind*)&amp;conn_con.msg_hdr;</code></p>
     <p><code>60   err_quit("tpi_connect2: T_DISCON_IND from conn (%d)",</code></p>
     <p><code>61   discon_ind-&gt;DISCON_reason);</code></p>
     <empty-line/>
     <p><code>62  default:</code></p>
     <p><code>63   err_quit("tpi_connect2: unexpected message type. %d",</code></p>
     <p><code>64   conn_con.msg_hdr PRIM_type);</code></p>
     <p><code>65  }</code></p>
     <p><code>66 }</code></p>
     <subtitle>Заполнение структуры запроса и отправка поставщику</subtitle>
     <p><code>18-26</code> В TPI определена структура <code>T_conn_req</code>, содержащая адрес протокола и параметры для соединения:</p>
     <p><code>struct T_conn_req {</code></p>
     <p><code> long PRIM_type;   /* T_CONN_REQ */</code></p>
     <p><code> long DEST_length; /* длина адреса получателя */</code></p>
     <p><code> long DEST_offset; /* смещение адреса получателя */</code></p>
     <p><code> long OPT_length;  /* длина параметров */</code></p>
     <p><code> long OPT_offset;  /* смещение параметров */</code></p>
     <p><code> /* затем следуют адреса протокола и параметры соединения */</code></p>
     <p><code>};</code></p>
     <p>Как и в случае функции <code>tpi_bind</code>, мы определяем свою собственную структуру с именем <code>conn_req</code>, которая включает в себя структуру <code>T_conn_req</code>, а также содержит место для адреса протокола. Мы заполняем структуру <code>conn_req</code>, обнуляя поля <code>OPT_length</code> и <code>OPT_offset</code>. Мы вызываем функцию <code>putmsg</code> только с управляющей информацией и флагом 0 для отправки сообщения типа <code>M_PROTO</code> вниз по потоку.</p>
     <subtitle>Чтение ответа</subtitle>
     <p><code>27-45</code> Мы вызываем функцию <code>getmsg</code>, ожидая получить в ответ либо сообщение <code>T_OK_ACK</code>, если было начато установление соединения, либо сообщение <code>T_ERROR_ACK</code> (которые мы уже показывали выше). В случае ошибки мы завершаем выполнение программы. Поскольку мы не знаем, сообщение какого типа мы получим, то определяем объединение с именем <code>T_primitives</code> для приема всех возможных запросов и ответов и размещаем это объединение в памяти как входной буфер для управляющей информации при вызове функции <code>getmsg</code>.</p>
     <p><code>struct T_ok_ack {</code></p>
     <p><code> long PRIM_type;    /* T_OK_ACK */</code></p>
     <p><code> long CORRECT_prim; /* корректный примитив */</code></p>
     <p><code>};</code></p>
     <subtitle>Ожидание завершения установления соединения</subtitle>
     <p><code>46-65</code> Сообщение <code>T_OK_ACK</code>, полученное нами на предыдущем этапе, указывает лишь на то, что соединение успешно начало устанавливаться. Теперь нам нужно дождаться сообщения <code>T_CONN_CON</code>, указывающего на то, что другой конец соединения подтверждает получение запроса на соединение.</p>
     <p><code>struct T_conn_con {</code></p>
     <p><code> long PRIM_type;  /* T_CONN_CON */</code></p>
     <p><code> long RES_length; /* длина адреса собеседника */</code></p>
     <p><code> long RES_offset; /* смещение адреса собеседника */</code></p>
     <p><code> long OPT_length; /* длина параметра */</code></p>
     <p><code> long OPT_offset; /* смещение параметра */</code></p>
     <p><code> /* далее следуют адрес протокола и параметры собеседника */</code></p>
     <p><code>};</code></p>
     <p>Мы снова вызываем функцию <code>getmsg</code>, но ожидаемое нами сообщение посылается как сообщение типа <code>M_PROTO</code>, а не как сообщение <code>M_PCPROTO</code>, поэтому мы обнуляем флаги. Если мы получаем сообщение <code>T_CONN_CON</code>, значит, соединение установлено, и мы возвращаемся, но если соединение не было установлено (по причине того, что процесс собеседника не запущен, истекло время ожидания или еще по какой-либо причине), то вместо этого вверх по потоку отправляется сообщение <code>T_DISCON_IND</code>:</p>
     <p><code>struct T_discon_ind {</code></p>
     <p><code> long PRIM_type;     /* T_DISCON_IND */</code></p>
     <p><code> long DISCON_reason; /* причина разрыва соединения */</code></p>
     <p><code> long SEQ_number;    /* порядковый номер */</code></p>
     <p><code>};</code></p>
     <p>Мы можем посмотреть, какие ошибки могут быть возвращены поставщиком. Сначала мы задаем IP-адрес узла, на котором не запущен сервер времени и даты:</p>
     <p><code>solaris26 % <strong>tpi_daytime 192.168.1.10</strong></code></p>
     <p><code>tpi_connect2: T_DISCON_IND from conn (146)</code></p>
     <p>Код 146 соответствует ошибке <code>ECONNREFUSED</code>. Затем мы задаем IP-адрес, который не связан с Интернетом:</p>
     <p><code>solaris26 % <strong>tpi_daytime 192.3.4.5</strong></code></p>
     <p><code>tpi_connect2: T_DISCON_IND from conn (145)</code></p>
     <p>На этот раз возвращается ошибка <code>ETIMEDOUT</code>. Но если мы снова запустим нашу программу, задавая тот же самый IP-адрес, то получим другую ошибку:</p>
     <p><code>solaris26 % <strong>tpi_daytime 192.3.4.5</strong></code></p>
     <p><code>tpi_connect2: T_DISCON_IND from conn (148)</code></p>
     <p>На этот раз мы получаем ошибку <code>EHOSTUNREACH</code>. Различие в том, что в первый раз не было возвращено сообщение ICMP о недоступности узла, а во второй раз мы получили это сообщение.</p>
     <p>Следующая функция, которую мы рассмотрим, — это <code>tpi_read</code>, показанная в листинге 31.5. Она считывает данные из потока.</p>
     <p><strong>Листинг 31.5</strong>. Функция tpi_read: считывание данных из потока</p>
     <p><code>//streams/tpi_read.c</code></p>
     <p><code> 1 #include "tpi_daytime.h"</code></p>
     <empty-line/>
     <p><code> 2 ssize_t</code></p>
     <p><code> 3 tpi_read(int fd, void *buf, size_t len)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  struct strbuf ctlbuf;</code></p>
     <p><code> 6  struct strbuf datbuf;</code></p>
     <p><code> 7  union T_primitives rcvbuf;</code></p>
     <p><code> 8  int flags;</code></p>
     <empty-line/>
     <p><code> 9  ctlbuf maxlen = sizeof(union T_primitives);</code></p>
     <p><code>10  ctlbuf.buf = (char*)&amp;rcvbuf;</code></p>
     <empty-line/>
     <p><code>11  datbuf.maxlen = len;</code></p>
     <p><code>12  datbuf.buf = buf;</code></p>
     <p><code>13  datbuf.len = 0;</code></p>
     <empty-line/>
     <p><code>14  flags = 0;</code></p>
     <p><code>15  Getmsg(fd, &amp;ctlbuf, &amp;datbuf, &amp;flags);</code></p>
     <empty-line/>
     <p><code>16  if (ctlbuf.len &gt;= (int)sizeof(long)) {</code></p>
     <p><code>17   if (rcvbuf.type == T_DATA_IND)</code></p>
     <p><code>18    return (datbuf.len);</code></p>
     <p><code>19   else if (rcvbuf.type == T_ORDREL_IND)</code></p>
     <p><code>20    return (0);</code></p>
     <p><code>21   else</code></p>
     <p><code>22    err_quit("tpi_read: unexpected type %d", rcvbuf.type);</code></p>
     <p><code>23  } else if (ctlbuf.len == -1)</code></p>
     <p><code>24   return (datbuf.len);</code></p>
     <p><code>25  else</code></p>
     <p><code>26   err_quit("tpi_read: bad length from getmsg");</code></p>
     <p><code>27 }</code></p>
     <subtitle>Считывание управляющей информации и данных, обработка ответа</subtitle>
     <p><code>9-26</code> На этот раз мы вызываем функцию <code>getmsg</code> для считывания как данных, так и управляющей информации. Структура <code>strbuf</code>, предназначенная для данных, указывает на буфер вызывающего процесса. В потоке события могут развиваться по четырем различным сценариям.</p>
     <p>&#9632; Данные могут прибыть в виде сообщения <code>M_DATA</code>, и указанием на это является возвращенное значение длины управляющей информации, равное -1. Данные скопированы в буфер вызывающего процесса функцией <code>getmsg</code>, и функция просто возвращает длину этих данных.</p>
     <p>&#9632; Данные могут прибыть как сообщение <code>T_DATA_IND</code>, в этом случае управляющая информация будет содержаться в структуре <code>T_data_ind</code>:</p>
     <p><code>struct T_data_ind {</code></p>
     <p><code> long PRIM_type; /* T_DATA_IND */</code></p>
     <p><code> long MORE_flag; /* еще данные */</code></p>
     <p><code>};</code></p>
     <p>Если возвращено такое сообщение, мы игнорируем поле <code>MORE_flag</code> (оно вообще не задается для таких протоколов, как TCP) и просто возвращаем длину данных, скопированных в буфер вызывающего процесса функцией <code>getmsg</code>.</p>
     <p>&#9632; Сообщение <code>T_ORDREL_IND</code> возвращается, если все данные получены и следующим элементом является сегмент <code>FIN</code>:</p>
     <p><code>struct T_ordrel_ind {</code></p>
     <p><code> long PRIM_type; /* T_ORDREL_IND */</code></p>
     <p><code>};</code></p>
     <p>Это нормальное завершение. Мы просто возвращаем нулевое значение, указывая вызывающему процессу, что по соединению получен признак конца файла.</p>
     <p>&#9632; Сообщение <code>T_DISCON_IND</code> возвращается, если произошел разрыв соединения. Наша последняя функция — это <code>tpi_close</code>, показанная в листинге 31.6.</p>
     <p><strong>Листинг 31.6</strong>. Функция tpi_close: отправка запроса о завершении собеседнику</p>
     <p><code>//streams/tpi_close.c</code></p>
     <p><code> 1 #include "tpi_daytime.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 tpi_close(int fd)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  struct T_ordrel_req ordrel_req;</code></p>
     <p><code> 6  struct strbuf ctlbuf;</code></p>
     <empty-line/>
     <p><code> 7  ordrel_req PRIM_type = T_ORDREL_REQ;</code></p>
     <p><code> 8  ctlbuf.len = sizeof(struct T_ordrel_req);</code></p>
     <p><code> 9  ctlbuf.buf = (char*)&amp;ordrel_req;</code></p>
     <p><code>10  Putmsg(fd, &amp;ctlbuf, NULL, 0);</code></p>
     <empty-line/>
     <p><code>11  Close(fd);</code></p>
     <p><code>12 }</code></p>
     <subtitle>Отправка запроса о завершении собеседнику</subtitle>
     <p><code>7-10</code> Мы формируем структуру <code>T_ordrel_req</code>:</p>
     <p><code>struct T_ordrel_req {</code></p>
     <p><code> long PRIM_type; /* T_ORDREL_REQ */</code></p>
     <p><code>};</code></p>
     <p>и посылаем ее как сообщение <code>M_PROTO</code> с помощью функции <code>putmsg</code>. Это соответствует функции XTI <code>t_sndrel</code>.</p>
     <p>Этот пример позволил нам почувствовать специфику TPI. Приложение посылает сообщения вниз по потоку (запросы), а поставщик посылает сообщения вверх по потоку (ответы). Некоторые обмены сообщений организованы согласно простому сценарию «запрос-ответ» (связывание локального адреса), в то время как остальные могут занять некоторое время (установление соединения), позволяя нам заняться чем-то другим в процессе ожидания ответа. Для знакомства с TPI мы выбрали этот пример (написание клиента TCP) из-за его относительной простоты. Если бы мы решили написать с использованием TPI TCP-сервер, обрабатывающий одновременно несколько соединений, это было бы гораздо сложнее.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Можно сравнить количество системных вызовов, необходимых для осуществления определенных сетевых операций, показанных в этой главе, в случае применения TPI и когда используется ядро, реализующее сокеты. Связывание с локальным адресом в случае TPI требует двух системных вызовов, но в случае сокетного ядра требуется только один вызов [128, с. 454]. Для установления соединения на блокируемом дескрипторе с использованием TPI требуется три системных вызова, а в случае сокетного ядра — только один [128, с. 466].</p>
     </cite>
    </section>
    <section>
     <title>
      <p>31.7. Резюме</p>
     </title>
     <p>Иногда сокеты реализуются с использованием потоков STREAMS. Для обеспечения доступа к потоковой подсистеме вводятся четыре новые функции: <code>getmsg</code>, <code>putmsg</code>, <code>getpmsg</code> и <code>putpmsg</code>. Также в потоковой подсистеме широко используется уже описанная ранее функция <code>ioctl</code>.</p>
     <p>TPI представляет собой потоковый интерфейс системы SVR4, предоставляющий доступ из верхних уровней на транспортный уровень. Он используется как сокетами, так и XTI, как показано на рис. 31.3. В этой главе в качестве примера использования основанного на сообщениях интерфейса мы разработали версию клиента времени и даты, в котором непосредственно применяется интерфейс TPI.</p>
    </section>
    <section>
     <title>
      <p>Упражнения</p>
     </title>
     <p>1. В листинге 31.6 мы вызываем функцию <code>putmsg</code>, чтобы отправить вниз по потоку запрос на нормальное завершение соединения, а затем немедленно вызываем функцию <code>close</code> для закрытия потока. Что произойдет, если наш запрос будет потерян потоковой подсистемой, а мы закроем поток?</p>
    </section>
   </section>
  </section>
  <section>
   <title>
    <p>Приложения</p>
   </title>
   <section>
    <title>
     <p>Приложение А</p>
     <p>Протоколы IPv4, IPv6, ICMPv4 и ICMFV6</p>
    </title>
    <section>
     <title>
      <p>А.1. Введение</p>
     </title>
     <p>В этом приложении приведен обзор протоколов IPv4, IPv6, ICMPv4 и ICMPv6. Данный материал позволяет глубже понять рассмотренные в главе 2 протоколы TCP и UDP. Некоторые возможности IP и ICMP рассматриваются также более подробно и в других главах, например параметры IP (см. главу 27), и программы <code>ping</code> и <code>traceroute</code> (см. главу 28).</p>
    </section>
    <section>
     <title>
      <p>А.2. Заголовок IPv4</p>
     </title>
     <p>Уровень IP обеспечивает не ориентированную на установление соединения (connectionless) и ненадежную службу доставки дейтаграмм (RFC 791 [94]). Уровень IP делает все возможное для доставки IP-дейтаграммы определенному адресату, но не гарантирует, что дейтаграмма будет доставлена, прибудет в нужном порядке относительно других пакетов, а также будет доставлена в единственном экземпляре. Если требуется надежная доставка дейтаграммы, она должна быть обеспечена на более высоком уровне. В случае приложений TCP и SCTP надежность обеспечивается транспортным уровнем. Приложению UDP надежность должно обеспечивать само приложение, поскольку уровень UDP также не предоставляет гарантии надежной доставки дейтаграмм, что было показано на примере в разделе 22.5.</p>
     <p>Одной из наиболее важных функций уровня IP является <emphasis>маршрутизация</emphasis> (<emphasis>routing</emphasis>). Каждая IP-дейтаграмма содержит адрес отправителя и адрес получателя. На рис. А.1 показан формат заголовка Ipv4.</p>
     <image l:href="#img_169.png"/>
     <p><strong>Рис. А.1</strong>. Формат заголовка IPv4</p>
     <p>&#9632; Значение 4-разрядного поля версия (<emphasis>version</emphasis>) равно 4. Это версия протокола IP, используемая с начала 80-х.</p>
     <p>&#9632; В поле <emphasis>длина заголовка</emphasis> (<emphasis>header length</emphasis>) указывается полная длина IP-заголовка, включающая любые параметры, описанные 32-разрядными словами. Максимальное значение этого 4-разрядного поля равно 15, и это значение задает максимальную длину IP-заголовка 60 байт. Таким образом, если заголовок занимает фиксированные 20 байт, то 40 байт остается на различные параметры.</p>
     <p>&#9632; 16-разрядное поле кода дифференцированных сервисов (Differentiated Services Code Point, DSCP) (RFC 2474 [82]) и 2-разрядное поле явного уведомления о загруженности сети (Explicit Congestion Notification, ECN) (RFC 3168 [100]) заменили 8-разрядное поле <emphasis>тип службы</emphasis> (<emphasis>сервиса</emphasis>) (<emphasis>type-of-service</emphasis>, TOS), которое описывалось в RFC 1349 [5]. Все 8 разрядов этого поля можно установить с помощью параметра сокета IP_TOS (см. раздел 7.6), хотя ядро может перезаписать любое установленное нами значение при проведении политики Diffserv или реализации ECN.</p>
     <p>&#9632; Поле <emphasis>общая длина</emphasis> (<emphasis>total length</emphasis>) имеет размер 16 бит и задает полную длину IP- дейтаграммы в байтах, включая заголовок IPv4. Количество данных в дейтаграмме равно значению этого поля минус длина заголовка, умноженная на 4. Данное поле необходимо, поскольку некоторые каналы передачи данных заполняют кадр до некоторой минимальной длины (например, Ethernet) и возможна ситуация, когда размер действительной IP-дейтаграммы окажется меньше требуемого минимума.</p>
     <p>&#9632; 16-разрядное поле <emphasis>идентификации</emphasis> (<emphasis>identification</emphasis>) является уникальным для каждой IP-дейтаграммы и используется при фрагментации и последующей сборке в единое целое (см. раздел 2.11). Значение должно быть уникальным для каждого сочетания отправителя, получателя и протокола в течение того времени, пока дейтаграмма может находиться в пути. Если пакет ни при каких условиях не может подвергнуться фрагментации (например, установлен бит DF), нет необходимости устанавливать значение этого поля.</p>
     <p>&#9632; Бит DF (флаг запрета фрагментации), бит MF (указывающий, что есть еще фрагменты для обработки) и 13-разрядное поле <emphasis>смещения фрагмента</emphasis> (<emphasis>fragment offset</emphasis>) также используются при фрагментации и последующей сборке в единое целое. Бит DF полезен при обнаружении транспортной MTU (раздел 2.11).</p>
     <p>&#9632; 8-разрядное поле <emphasis>времени жизни</emphasis> (<emphasis>time-to-live</emphasis>, TTL) устанавливается отправителем и уменьшается на единицу каждым последующим маршрутизатором, через который проходит дейтаграмма. Дейтаграмма отбрасывается маршрутизатором, который уменьшает данное поле до нуля. При этом время жизни любой дейтаграммы ограничивается 255 пересылками. Обычно по умолчанию данное поле имеет значение 64, но можно сделать соответствующий запрос и изменить его с помощью параметров сокета <code>IP_TTL</code> и <code>IP_MULTICAST_TTL</code> (см. раздел 7.6).</p>
     <p>&#9632; 8-разрядное поле <emphasis>протокола</emphasis> (<emphasis>protocol</emphasis>) определяет тип данных, содержащихся в IP-дейтаграмме. Характерные значения этого поля — 1 (ICMPv4), 2 (IGMPv4), 6 (TCP) и 17 (UDP). Эти значения определены в реестре IANA «Номера протоколов».</p>
     <p>&#9632; 16-разрядная <emphasis>контрольная сумма заголовка</emphasis> (<emphasis>header checksum</emphasis>) вычисляется для IP-заголовка (включая параметры). В качестве алгоритма вычисления используется стандартный алгоритм контрольных сумм для Интернета — простое суммирование 16-разрядных обратных кодов, как показано в листинге 28.11.</p>
     <p>&#9632; Два поля — <emphasis>IPv4-адрес отправителя</emphasis> (<emphasis>source IPv4 address</emphasis>) и <emphasis>IPv4-адрес получателя</emphasis> (<emphasis>destination IPv4 address</emphasis>) — занимают по 32 бита.</p>
     <p>&#9632; Поле <emphasis>параметров</emphasis> (<emphasis>options</emphasis>) описывается в разделе 27.2, а пример IPv4-параметра маршрута от отправителя приведен в разделе 27.3.</p>
    </section>
    <section>
     <title>
      <p>А.3. Заголовок IPv6</p>
     </title>
     <p>На рис. А.2 показан формат заголовка IPv6 (RFC 2460 [27]).</p>
     <image l:href="#img_170.png"/>
     <p><strong>Рис. А.2</strong>. Формат заголовка IPv6</p>
     <p>&#9632; Значение 4-разрядного поля номера <emphasis>версии</emphasis> (<emphasis>version</emphasis>) равно 6. Данное поле занимает первые 4 бита первого байта заголовка (так же как и в версии IPv4, см. рис. А.1), поэтому если получающий стек IP поддерживает обе версии, он имеет возможность определить, какая из версий используется.</p>
     <p>Когда в начале 90-х развивался протокол IPv6 и еще не был принят номер версии 6, протокол назывался <emphasis>IPng</emphasis> (IP next generation — IP нового поколения). До сих пор можно встретить ссылки на IPng.</p>
     <p>&#9632; 6-разрядное поле кода дифференцированных сервисов (Differentiated Services Code Point, DSCP) (RFC 2474 [82]) и 2-разрядное поле явного уведомления о загруженности сети (Explicit Congestion Notification, ECN) (RFC 3168 [100]) заменили 8-разрядное поле класса трафика, которое описывалось RFC 2460. Все 8 бит этого поля можно установить при помощи параметра сокета IPV6_TCLASS (раздел 22.8), но ядро может перезаписать установленное нами значение, выполняя политику Diffserv или реализуя ECN.</p>
     <p>&#9632; Поле <emphasis>метки потока</emphasis> (<emphasis>flow label</emphasis>) занимает 20 разрядов и может заполняться приложением для данного сокета. Поток представляет собой последовательность пакетов от конкретного отправителя определенному получателю, для которых отправитель потребовал специальную обработку промежуточными маршрутизаторами. Если для данного потока отправитель назначил метку, она уже не изменяется. Метка потока, равная нулю (по умолчанию), обозначает пакеты, не принадлежащие потоку. Метка потока не меняется при передаче по сети. Подробное описание использования меток потока приводится в [99]. Интерфейс метки потока еще не определен до конца. Поле <code>sin6_flowinfo</code> структуры адреса сокета <code>sockaddr_in6</code> (см. листинг 3.3) зарезервировано для будущего использования. Некоторые системы копируют младшие 28 разрядов <code>sin6_flowinfo</code> непосредственно в заголовок пакета IPv6, перезаписывая поля DSCP и ECN.</p>
     <p>&#9632; Поле <emphasis>длины данных</emphasis> (<emphasis>payload length</emphasis>) занимает 16 бит и содержит длину данных в байтах, которые следуют за 40 байтами IPv6-заголовка. Нулевое значение этого поля указывает, что длина требует больше 16 бит и содержится в параметре размера увеличенного поля данных (jumbo payload length option) (см. рис. 27.5). Данные с увеличенной таким образом длиной называются <emphasis>джумбограммой</emphasis> (<emphasis>jumbogram</emphasis>).</p>
     <p>&#9632; Следующее поле содержит 8 бит и называется <emphasis>полем следующего заголовка</emphasis> (<emphasis>next header</emphasis>). Оно аналогично полю протокола (<emphasis>protocol</emphasis>) IPv4. Действительно, когда верхний уровень в основном не меняется, используются те же значения, например, 6 для TCP и 17 для UDP. Но при переходе от ICMPv4 к ICMPv6 возникло так много изменений, что для последнего было принято новое значение 58. Дейтаграмма IPv6 может иметь множество заголовков, следующих за 40-байтовым заголовком IPv6. Поэтому поле и называется «полем следующего заголовка», а не полем протокола.</p>
     <p>&#9632; Поле <emphasis>ограничения пересылок</emphasis> или <emphasis>предельного количества транзитных узлов</emphasis> (<emphasis>hop limit</emphasis>) аналогично полю TTL IPv4. Значение этого поля уменьшается на единицу каждым маршрутизатором, через который проходит дейтаграмма, и дейтаграмма отбрасывается тем маршрутизатором, который уменьшает данное поле до нуля. Значение этого поля можно установить и получить с помощью параметров сокета <code>IPV6_UNICAST_HOPS</code> и <code>IPV6_MULTICAST_HOPS</code> (см. раздел 7.8 и 21.6). Параметр сокета <code>IPV6_HOPLIMIT</code> также позволяет установить это поле, а параметр <code>IPV6_RECVHOPLIMIT</code> — узнать его значение для полученной дейтаграммы.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В ранних спецификациях IPv4 говорилось, что маршрутизаторы должны уменьшать значение TTL либо на единицу, либо на количество секунд, в течение которых дейтаграмма находилась на маршрутизаторе, если это количество превышает единицу. Поэтому поле и называлось «время жизни». Однако на практике TTL всегда уменьшалось на единицу. IPv6 разрешает уменьшать поле количества транзитных узлов только на единицу, поэтому и название поля было изменено.</p>
     </cite>
     <p>&#9632; Два следующих поля <emphasis>IPv6-адрес отправителя</emphasis> (<emphasis>source IPv6 address</emphasis>) и <emphasis>IPv6-адрес получателя</emphasis> (<emphasis>destination IPv6 address</emphasis>) занимают по 128 бит.</p>
     <p>Наиболее значительным изменением, произошедшим при переходе от IPv4 к IPv6, несомненно, является увеличение поля адресов в IPv6. Другое изменение относится к упрощению заголовка, поскольку чем проще заголовок, тем быстрее он будет обработан маршрутизатором. Кроме того, можно отметить еще несколько различий между заголовками:</p>
     <p>&#9632; В IPv6 нет поля длины заголовка, поскольку в заголовке отсутствуют параметры. Существует возможность использовать после фиксированного 40-байтового заголовка дополнительные заголовки, но каждый из них имеет свое поле длины.</p>
     <p>&#9632; Два адреса IPv6 выровнены по 64-разрядной границе, если заголовок также является 64-разрядным. Такой подход может увеличить скорость обработки на 64-разрядных архитектурах. Адреса IPv4 имеют 32-разрядное выравнивание в заголовке IPv4, который в целом выровнен по 64 разрядам.</p>
     <p>&#9632; В заголовке IPv6 нет поля фрагментации, поскольку для этой цели существует специальный заголовок фрагментации. Такое решение было принято, поскольку фрагментация является исключением, а исключения не должны замедлять нормальную обработку.</p>
     <p>&#9632; Заголовок IPv6 не включает в себя свою контрольную сумму. Такое изменение было сделано, поскольку все верхние уровни — TCP, UDP и ICMPv6 — имеют свои контрольные суммы, включающие в себя заголовок верхнего уровня, данные верхнего уровня и такие поля из IPv6-заголовка, как IPv6-адрес отправителя, IPv6-адрес получателя, длину данных и следующий заголовок. Исключив контрольную сумму из заголовка, мы приходим к тому, что маршрутизатор, перенаправляющий пакет, не должен будет пересчитывать контрольную сумму заголовка после того, как изменит поле ограничения пересылок. Ключевым моментом здесь также является скорость маршрутизации.</p>
     <p>Если это ваше первое знакомство с IPv6, также следует отметить главные отличия IPv6 от IPv4:</p>
     <p>&#9632; В IPv6 отсутствует многоадресная передача (см. главу 20). Групповая адресация (см. главу 21), не являющаяся обязательной для IPv4, требуется для IPv6.</p>
     <p>&#9632; В IPv6 маршрутизаторы не фрагментируют перенаправляемые пакеты. Если пакет слишком велик, маршрутизатор сбрасывает его и отправляет сообщение об ошибке ICMPv6 (раздел А.6). Фрагментация при использовании IPv6 осуществляется только узлом отправителя.</p>
     <p>&#9632; IPv6 требует поддержки обнаружения транспортной MTU (раздел 2.11). Технически эта поддержка не является обязательной и может не включаться в реализации, обладающие минимальной функциональностью, такие как сетевые загрузчики, но если узел не обнаруживает транспортную MTU, он не должен отсылать дейтаграммы, размер которых превышает минимальную канальную MTU IPv6 (1280 байт). В разделе 22.9 описываются параметры сокетов, управляющие поведением механизма обнаружения транспортной MTU.</p>
     <p>&#9632; IPv6 требует поддержки параметра аутентификации (подтверждения прав доступа) и параметра обеспечения безопасности. Эти параметры добавляются после основного заголовка.</p>
    </section>
    <section>
     <title>
      <p>А.4. Адресация IPv4</p>
     </title>
     <section>
      <p>Адреса IPv4 состоят из 32 разрядов и обычно записываются в виде последовательности из четырех чисел в десятичной форме, разделенных точками. Такая запись называется <emphasis>точечно-десятичной</emphasis>. Первое из четырех чисел определяет тип адреса (табл. А.1). Исторически IP-адреса делились на пять классов. Три класса направленных адресов эквивалентны друг другу с функциональной точки зрения, поэтому мы показываем их как один диапазон.</p>
      <empty-line/>
      <p><strong>Таблица А.1</strong>. Диапазоны и классы IP-адресов</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Назначение</th>
        <th align="left" valign="top">Класс</th>
        <th align="left" valign="top">Диапазон</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Направленная передача</td>
        <td align="left" valign="top">А, В, С</td>
        <td align="left" valign="top">0.0.0.0–223.255.255.255</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Многоадресная передача</td>
        <td align="left" valign="top">D</td>
        <td align="left" valign="top">224.0.0.0–239.255.255.255</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Экспериментальные</td>
        <td align="left" valign="top">Е</td>
        <td align="left" valign="top">240.0.0.0–255.255.255.255</td>
       </tr>
      </table>
      <p>Под сетевым адресом IPv4 подразумевается 32-разрядный адрес и соответствующая ему 32-разрядная маска подсети. Биты маски, равные 1, указывают адрес сети, а нулевые биты — адрес узла. Поскольку биты со значением 1 всегда занимают места в маске непрерывно начиная с крайнего левого бита, а нулевые биты — начиная с крайнего правого бита, то маску адреса можно определить как <emphasis>префиксную длину</emphasis> (<emphasis>prefix length</emphasis>), указывающую на количество заполненных единицами битов начиная с крайнего левого бита. Например, маска 255.255.255.0 соответствует префиксной длине 24. Такая адресация называется бесклассовой (classless), потому что маска указывается явно, а не задается классом адреса. Пример вы можете увидеть на рис. 1.7.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Маски подсети, не являющиеся непрерывными, не были явно запрещены ни в одном RFC, но такие маски усложняют работу администраторов и не могут быть представлены в префиксной записи. Протокол междоменной маршрутизации Интернета BGP4 может работать только с непрерывными масками. В протоколе IPv6 требование непрерывности маски выдвигается явно.</p>
      </cite>
      <p>Использование бесклассовых адресов подразумевает бесклассовую маршрутизацию, которую обычно называют бесклассовой междоменной маршрутизацией (classless interdomain routing — CIDR) (RFC 1519 [31]). Бесклассовая междоменная маршрутизация позволяет сократить размер таблиц маршрутизации опорной сети Интернета и снизить скорость расходования адресов IPv4. Все маршруты CIDR характеризуются маской или длиной префикса. Маска больше не может быть определена по классу адреса. Более подробно CIDR описывается в разделе 10.8 книги [111].</p>
     </section>
     <section>
      <title>
       <p>Адреса подсетей</p>
      </title>
      <p>Обычно IPv4-адреса разделяются на подсети (RFC 950 [79]). Такой подход добавляет еще один уровень иерархии адресов:</p>
      <p>&#9632; идентификатор сети (присваивается предприятию);</p>
      <p>&#9632; идентификатор подсети (выбирается предприятием);</p>
      <p>&#9632; идентификатор узла (выбирается предприятием).</p>
      <p>Граница между идентификатором сети и идентификатором подсети фиксирована префиксной длиной присвоенного адреса сети. Эта префиксная длина присваивается организациям их интернет-провайдером. Граница же между идентификатором подсети и идентификатором узла выбирается предприятием. Все узлы данной подсети имеют одинаковую <emphasis>маску подсети</emphasis>, которая и определяет границу между идентификатором подсети и идентификатором узла. Биты, заполненные единицами в маске подсети, соответствуют идентификатору подсети, а биты, заполненные нулями — идентификатору узла.</p>
      <p>В качестве примера рассмотрим предприятие, которому был выделен адрес 192.168.42.0/24. Если это предприятие будет использовать 3-разрядный идентификатор подсети, на идентификатор узла останется 5 разрядов (рис. А.3.)</p>
      <image l:href="#img_171.png"/>
      <p><strong>Рис. А.3</strong>. 24-разрядный адрес сети с 3-разрядным адресом подсети и 5-разрядным адресом узла</p>
      <p>В результате такого деления мы получаем подсети, показанные в табл. А.2.</p>
      <empty-line/>
      <p><strong>Таблица А.2</strong>. Список подсетей для 3-разрядного адреса подсети и 5-разрядного адреса узла</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Подсеть</th>
        <th align="left" valign="top">Префикс</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">0</td>
        <td align="left" valign="top">192.168.42.0/27+</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">1</td>
        <td align="left" valign="top">192.168.42.32/27+</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">2</td>
        <td align="left" valign="top">192.168.42.64/27</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">3</td>
        <td align="left" valign="top">192.168.42.96/27</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">4</td>
        <td align="left" valign="top">192.168.42.128/27</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">5</td>
        <td align="left" valign="top">192.168.42.160/27</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">6</td>
        <td align="left" valign="top">192.168.42.192/27</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">7</td>
        <td align="left" valign="top">192.168.42.224/27+</td>
       </tr>
      </table>
      <p>В результате мы получаем 6–8 подсетей (идентификаторы 1–6 или 0–7), в каждой из которых может находиться до 30 узлов (идентификаторы 1–30). RFC 950 не рекомендует использовать подсети, идентификаторы которых состоят из одних нулей и одних единиц (знак «+» в табл. А.2). В настоящее время большинство систем поддерживают и такие адреса подсетей. Максимальный идентификатор узла (в нашем случае 31) зарезервирован за широковещательным адресом. Идентификатор 0 используется для адресации сети в целом и зарезервирован во избежание конфликтов со старыми системами, в которых нулевой адрес узла использовался в качестве адреса широковещательной передачи. В полностью контролируемых сетях, где такие системы отсутствуют, идентификатор 0 использовать можно. Вообще говоря, сетевые приложения не должны заботиться об идентификаторах подсетей и узлов, рассматривая IP-адреса как непрозрачные объекты.</p>
     </section>
     <section>
      <title>
       <p>Адрес закольцовки</p>
      </title>
      <p>По соглашению адрес 127.0.0.1 присвоен <emphasis>интерфейсу закольцовки на себя</emphasis> (<emphasis>loopback interface</emphasis>). Все, что посылается на этот IP-адрес, получается самим узлом. Обычно этот адрес используется при тестировании клиента и сервера на одном узле. Этот адрес известен под именем <code>INADDR_LOOPBACK</code>.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Любой адрес из подсети 127/8 можно присвоить интерфейсу закольцовки, но обычно используется именно 127.0.0.1.</p>
      </cite>
     </section>
     <section>
      <title>
       <p>Неопределенный адрес</p>
      </title>
      <p>Адрес, состоящий из 32 нулевых битов, является в IPv4 <emphasis>неопределенным</emphasis> (<emphasis>unspecified</emphasis>) адресом. В пакете IPv4 он может появиться только как адрес получателя в тех пакетах, которые посланы узлом, находящимся в состоянии загрузки, когда узел еще не знает своего IP-адреса. В API сокетов этот адрес называется <emphasis>универсальным адресом</emphasis> (<emphasis>wildcard address</emphasis>) и обычно обозначается <code>INADDR_ANY</code>. Указание этого адреса при вызове <code>bind</code> для прослушиваемого сокета TCP говорит о том, что сокет будет принимать входящие соединения на любой адрес данного узла.</p>
     </section>
     <section>
      <title>
       <p>Частные адреса</p>
      </title>
      <p>RFC 1918 [101] выделяет три диапазона адресов для «частных интрасетей», то есть сетей, не имеющих прямого подключения к Интернету. Эти диапазоны представлены в табл. А.3.</p>
      <empty-line/>
      <p><strong>Таблица А.3</strong>. Диапазоны частных IP-адресов</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Количество адресов</th>
        <th align="left" valign="top">Префикс</th>
        <th align="left" valign="top">Диапазон</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">16777216</td>
        <td align="left" valign="top">10/8</td>
        <td align="left" valign="top">10.0.0.0–10.255.255.255</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">1 048 576</td>
        <td align="left" valign="top">172.16/12</td>
        <td align="left" valign="top">172.16.0.0–172.31.255.255</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">65 536</td>
        <td align="left" valign="top">192.168/16</td>
        <td align="left" valign="top">192.168.0.0–192.168.255.255</td>
       </tr>
      </table>
      <p>Пакеты с этими адресами никогда не должны появляться в Интернете, они зарезервированы для использования в частных сетях. Многие небольшие предприятия используют эти адреса и осуществляют трансляцию сетевых адресов в единственный общий IP-адрес, видимый из Интернета.</p>
     </section>
     <section>
      <title>
       <p>Многоинтерфейсность и псевдонимы адресов</p>
      </title>
      <p>Традиционно многоинтерфейсный узел определяется как узел с несколькими интерфейсами, например узел, имеющий два интерфейса Ethernet или интерфейсы Ethernet и PPP. Каждый из интерфейсов должен иметь свой уникальный IPv4-адрес. При подсчете интерфейсов (для определения, является ли узел многоинтерфейсным) интерфейс закольцовки не учитывается.</p>
      <p>Маршрутизатор по определению является многоинтерфейсным, поскольку он пересылает пакеты, поступившие на один интерфейс, через другой интерфейс. Но обратное неверно, то есть многоинтерфейсный узел не является маршрутизатором, если он не передает пакеты. Действительно, многоинтерфейсный узел еще не может рассматриваться как маршрутизатор. Он будет функционировать как маршрутизатор, только если он сконфигурирован для такой работы (обычно администратор должен включить соответствующие параметры конфигурации).</p>
      <p>Термин «многоинтерфейсность» является более общим и охватывает два различных сценария (раздел 3.3.4 RFC 1122 [10]).</p>
      <p>1. Узел с несколькими интерфейсами является многоинтерфейсным, при этом каждый интерфейс должен иметь свой IP-адрес. Это традиционное определение.</p>
      <p>2. Современные узлы имеют возможность присваивать одному физическому интерфейсу несколько IP-адресов. Каждый IP-адрес, созданный в дополнение к первичному, или основному (primary), называется <emphasis>альтернативным именем</emphasis>, <emphasis>псевдонимом</emphasis> (<emphasis>alias</emphasis>) или <emphasis>логическим интерфейсом</emphasis>. Часто альтернативные IP-адреса используют ту же маску подсети, что и основной адрес, но имеют другие идентификаторы узла. Но допустима также ситуация, когда псевдонимы имеют адрес сети или подсети, совершенно отличный от первичного адреса. В разделе 17.6 приведен пример альтернативных адресов.</p>
      <p>Таким образом, многоинтерфейсные узлы — это узлы, имеющие несколько интерфейсов IP-уровня, независимо от того, являются ли эти интерфейсы физическими или логическими.</p>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Довольно часто загруженные серверы имеют несколько соединений с одним коммутатором Ethernet, причем эти соединения настраиваются как одно логическое соединение с повышенной пропускной способностью. Такая система имеет несколько физических интерфейсов, но не считается многоинтерфейсной, поскольку обладает одним-единственным логическим интерфейсом с точки зрения уровня IP.</p>
      </cite>
      <cite>
       <subtitle>ПРИМЕЧАНИЕ</subtitle>
       <p>Многоинтерфейсность также используется в другом контексте. Сеть, имеющая несколько соединений с сетью Интернет, также называется многоинтерфейсной. Например, некоторые сайты имеют два соединения с Интернетом вместо одного, что обеспечивает дублирование на случай неполадок. Транспортный протокол SCTP позволяет передавать информацию о количестве интерфейсов узла его собеседнику.</p>
      </cite>
     </section>
    </section>
    <section>
     <title>
      <p>А.5. Адресация IPv6</p>
     </title>
     <section>
      <p>Адреса IPv6 содержат 128 бит и обычно записываются как восемь 16-разрядных шестнадцатеричных чисел. Старшие биты 128-разрядного адреса обозначают тип адреса (RFC 3513 [44]). В табл. А.4 приведены различные значения старших битов и соответствующие им типы адресов.</p>
      <empty-line/>
      <p><strong>Таблица А.4</strong>. Значение старших битов адреса IPv6</p>
      <table>
       <tr align="left">
        <th align="left" valign="top">Значение</th>
        <th align="left" valign="top">Размер идентификатора</th>
        <th align="left" valign="top">Префикс формата</th>
        <th align="left" valign="top">Документ</th>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Не определен</td>
        <td align="left" valign="top">нет</td>
        <td align="left" valign="top">0000 0000 … 0000 0000 (128 разрядов)</td>
        <td align="left" valign="top">RFC 3513</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Закольцовка</td>
        <td align="left" valign="top">нет</td>
        <td align="left" valign="top">0000 0000 … 0000 0001 (128 разрядов)</td>
        <td align="left" valign="top">RFC 3513</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Глобальный адрес направленной передачи</td>
        <td align="left" valign="top">произвольный</td>
        <td align="left" valign="top">000</td>
        <td align="left" valign="top">RFC 3513</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Глобальный адрес NSAP</td>
        <td align="left" valign="top">произвольный</td>
        <td align="left" valign="top">0000001</td>
        <td align="left" valign="top">RFC 1888</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Объединяемый глобальный адрес направленной передачи</td>
        <td align="left" valign="top">64 разряда</td>
        <td align="left" valign="top">001</td>
        <td align="left" valign="top">RFC 3587</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Глобальный адрес направленной передачи</td>
        <td align="left" valign="top">64 разряда</td>
        <td align="left" valign="top">все остальное</td>
        <td align="left" valign="top">RFC 3513</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Локальный в пределах канала адрес направленной передачи</td>
        <td align="left" valign="top">64 разряда</td>
        <td align="left" valign="top">1111 111010</td>
        <td align="left" valign="top">RFC 3513</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Локальный в пределах сайта адрес направленной передачи</td>
        <td align="left" valign="top">64 разряда</td>
        <td align="left" valign="top">1111 111011</td>
        <td align="left" valign="top">RFC 3513</td>
       </tr>
       <tr align="left">
        <td align="left" valign="top">Групповой адрес</td>
        <td align="left" valign="top">нет</td>
        <td align="left" valign="top">1111 1111</td>
        <td align="left" valign="top">RFC 3513</td>
       </tr>
      </table>
      <p>Эти старшие биты называются <emphasis>форматным префиксом</emphasis>. Например, если 3 старших бита — 001, адрес называется <emphasis>объединяемым глобальным индивидуальным адресом</emphasis> (<emphasis>aggregatable global unicast address</emphasis>). Если 8 старших битов — <code>11111111</code> (<code>0xff</code>), это групповой адрес.</p>
     </section>
     <section>
      <title>
       <p>Объединяемые глобальные индивидуальные адреса</p>
      </title>
      <p>Архитектура IPv6 корректировалась в процессе своего развития исходя из результатов внедрения новой версии протокола и из статистики применения старой версии. Согласно изначальному определению объединяемых глобальных индивидуальных адресов, они начинались с префикса 001 и имели фиксированную структуру, встроенную в сам адрес. Эта структура, однако, была отменена RFC 3587 [45]. Адреса, начинающиеся с префикса 001, будут и впредь выделяться в первую очередь, однако никаких отличий между ними и другими глобальными адресами больше не будет. Эти адреса будут использоваться в тех областях, где сейчас используются направленные адреса IPv4.</p>
      <p>Формат объединяемых индивидуальных адресов определяется в RFC 3513 [44] и RFC 3587 [45] и содержит следующие поля, слева направо:</p>
      <p>&#9632; глобальный префикс маршрутизации (n разрядов);</p>
      <p>&#9632; идентификатор подсети (64 - <emphasis>n</emphasis> разрядов);</p>
      <p>&#9632; идентификатор интерфейса (64 разряда).</p>
      <p>На рис. А.4 приведен пример объединяемого глобального индивидуального адреса.</p>
      <image l:href="#img_172.png"/>
      <p><strong>Рис. А.4</strong>. Объединяемый глобальный индивидуальный адрес IPv6</p>
      <p>Идентификатор интерфейса должен быть построен в модифицированном формате <emphasis>EUI-64</emphasis> (Extended User Interface — расширенный интерфейс пользователя) [51]. Это расширение множества 48-разрядных адресов IEEE 802 MAC (Media Access Control — уровень управления доступом к среде передачи), которые присвоены большинству карт сетевых интерфейсов локальной сети. Этот идентификатор должен автоматически присваиваться интерфейсу и по возможности основываться на MAC-адресе карты. Более подробное описание построения идентификаторов интерфейса, основанных на EUI-64, описывается в приложении А RFC 3513 [44].</p>
      <p>Поскольку модифицированный адрес EUI-64 может быть глобально уникальным идентификатором интерфейса, а сам интерфейс может однозначно идентифицировать пользователя, модифицированный формат EUI-64 создает определенные проблемы, связанные с конфиденциальностью. Может оказаться возможным отслеживать действия и перемещение конкретного пользователя, например путешествующего с портативным компьютером, просто по его IPv6-адресу. RFC 3041 [80] описывает расширения протокола, предназначенные для генерации идентификаторов интерфейса, меняющихся по несколько раз в день и, таким образом, устраняющие описанную проблему.</p>
     </section>
     <section>
      <title>
       <p>Тестовые адреса 6bone</p>
      </title>
      <p>6bone — это виртуальная сеть, используемая для тестирования протоколов IPv6 (см. раздел Б.3). Объединяемые глобальные индивидуальные адреса уже назначаются, но сайты, не имеющие права на адресное пространство согласно региональной политике назначения адресов, могут использовать адреса специального формата в сети 6bone RFC 2471 [46] (рис. А.5).</p>
      <image l:href="#img_173.png"/>
      <p><strong>Рис. А.5</strong>. Тестовые адреса IPv6 для сети 6bone</p>
      <p>Эти адреса рассматриваются как временные, и узлы, использующие такие адреса, необходимо будет перенумеровать, когда будут назначены объединяемые глобальные индивидуальные адреса.</p>
      <p>Старшие три байта имеют значение 0x3ffe. Идентификатор сайта 6bone назначается председателем руководства 6bone. Назначение проводится в том же порядке, в котором оно будет проводиться для реальных адресов IPv6. Активность 6bone постепенно сворачивается по мере того, как начинается внедрение IPv6 (в 2002 году было выделено больше реальных адресов IPv6, чем во всей сети 6bone за 8 лет). Идентификаторы подсети и интерфейса используются, как и раньше, для обозначения подсети и узла.</p>
      <p>В разделе 11.2 был показан IPv6-адрес 3ffe:b80:1f8d:1:a00:20ff:fea7:686b для узла <code>freebsd</code> (см. рис. 1.7). Идентификатор 6bone имеет значение 0x0b801f8d, а идентификатор подсети 0x1. Младшие 64 разряда представляют собой модифицированный адрес EUI-64, полученный из MAC-адреса Ethernet-карты узла.</p>
     </section>
     <section>
      <title>
       <p>Адреса IPv4, преобразованные к виду IPv6</p>
      </title>
      <p>Адреса IPv4, преобразованные к виду IPv6 (IPv4-mapped IPv6 addresses), позволяют приложениям, запущенным на узлах, поддерживающих как IPv4, так и IPv6, связываться с узлами, поддерживающими только IPv4, в процессе перехода сети Интернет на версию протокола IPv6. Такие адреса автоматически создаются на серверах DNS (см. табл. 11.3), когда приложением IPv6 запрашивается IPv6-адрес узла, который имеет только адреса IPv4.</p>
      <p>Рисунок 12.3 показывает, что использование данного типа адресов с сокетом IPv6 приводит к отправке IPv4-дейтаграммы узлу. Такие адреса не хранятся ни в каких файлах данных DNS — при необходимости они создаются сервером.</p>
      <image l:href="#img_174.png"/>
      <p><strong>Рис. А.6</strong>. Адреса IPv4, преобразованные к виду IPv6</p>
      <p>На рис. А.6 приведен формат таких адресов. Младшие 32 бита содержат адрес IPv4.</p>
      <p>При записи IPv6-адреса последовательная строка из нулей может быть сокращена до двух двоеточий. Вложенный IPv4-адрес представлен в точечно-десятичной записи. Например, преобразованный к виду IPv6 IPv4-адрес 0:0:0:0:0:FFFF:206.62.226.33 можно сократить до ::FFFF:206.62.226.33.</p>
     </section>
     <section>
      <title>
       <p>Адреса IPv6, совместимые с IPv4</p>
      </title>
      <p>Для перехода от версии IPv4 к IPv6 планировалось также использовать адреса IPv6, совместимые с IPv4 (IPv4-compatible IPv6 addresses). Администратор узла, поддерживающего как IPv4, так и IPv6, и не имеющего соседнего IPv6-маршрутизатора, должен создать DNS запись типа AAAA, содержащую адрес IPv6, совместимый с IPv4. Любой другой IPv6-узел, посылающий IPv6-дейтаграмму на адрес IPv6, совместимый с IPv4, должен <emphasis>упаковать</emphasis> (<emphasis>encapsulate</emphasis>) IPv6-дейтаграмму в заголовок IPv4 — такой способ называется <emphasis>автоматическим туннелированием</emphasis> (<emphasis>automatic tunnel</emphasis>). Однако после рассмотрения вопросов, связанных с внедрением IPv6, использование этой возможности заметно сократилось. Более подробно вопросы туннелирования будут рассмотрены в разделе Б.3, а на рис. Б.2 будет приведен пример IPv6-дейтаграмм такого типа, упакованных в заголовок IPv4.</p>
      <p>На рис. А.7 показан формат адреса IPv4, совместимого с IPv6.</p>
      <image l:href="#img_175.png"/>
      <p><strong>Рис. А.7</strong>. Адрес IPv6, совместимый с IPv4</p>
      <p>В качестве примера такого адреса можно привести ::206.62.226.33.</p>
      <p>Адреса IPv6, совместимые с IPv4 могут появляться и в пакетах IPv6, не передающихся по туннелю, если используется механизм перехода SIIT IPv4/IPv6 (RFC 2765 [83]).</p>
     </section>
     <section>
      <title>
       <p>Адрес закольцовки</p>
      </title>
      <p>Адрес IPv6 ::1, состоящий из 127 нулевых битов и единственного единичного бита, является адресом закольцовки IPv6. В API сокетов он называется <code>in6addr_loopback</code> или <code>IN6ADDR_LOOPBACK_INIТ</code>.</p>
     </section>
     <section>
      <title>
       <p>Неопределенный адрес</p>
      </title>
      <p>Адрес IPv6, состоящий из 128 нулевых битов, записываемый как 0::0 или просто ::, является <emphasis>неопределенным адресом</emphasis> IPv6 (<emphasis>unspecified address</emphasis>). В пакете IPv6 он может появиться только как адрес получателя в пакетах, посланных узлом, который находится в состоянии загрузки и еще не знает своего IPv6-адреса.</p>
      <p>В API сокетов этот адрес называется универсальным адресом, и его использование, например, в функции <code>bind</code> для связывания прослушиваемого сокета TCP означает, что сокет будет принимать клиентские соединения, предназначенные любому из адресов узла. Этот адрес имеет имя <code>in6addr_any</code> или <code>IN6ADDR_ANY_INIT</code>.</p>
     </section>
     <section>
      <title>
       <p>Адрес локальной связи</p>
      </title>
      <p>Адрес локальной связи (link-local, локальный в пределах физической подсети) используется для соединения в пределах одной физической подсети, когда известно, что дейтаграмма не будет перенаправляться. Примерами использования таких адресов являются автоматическая конфигурация адреса во время загрузки и поиска соседних узлов (neighbor discovery) (подобно ARP для IPv4). На рис. А.8 приведен формат такого адреса.</p>
      <image l:href="#img_176.png"/>
      <p><strong>Рис. А.8</strong>. IPv6-адрес локальной связи</p>
      <p>Такие адреса всегда начинаются с <code>fe80</code>. Маршрутизатор IPv6 не должен перенаправлять дейтаграммы, у которых в поле отправителя или получателя указан адрес локальной связи, по другому соединению. В разделе 11.2 приведен адрес локальной связи, связанный с именем <code>aiх-611</code>.</p>
     </section>
     <section>
      <title>
       <p>Адрес, локальный на уровне сайта</p>
      </title>
      <p>На момент написания этой книги рабочей группой IETF по IPv6 было принято решение отменить локальные в пределах сайта адреса в их текущей форме. В тех адресах, которые придут им на замену, может использоваться тот же диапазон, который был отведен для локальных на уровне сайта адресов изначально (fec0/10).</p>
      <p>Адрес, локальный в пределах сайта, должен был использоваться для адресации внутри предприятия, когда не требуется глобальный префикс. На рис. А.9 показан формат таких адресов.</p>
      <image l:href="#img_177.png"/>
      <p><strong>Рис. А.9</strong>. IPv6-адрес, локальный в пределах сайта</p>
      <p>Маршрутизатор IPv6 не должен перенаправлять дейтаграммы, для которых в поле отправителя или получателя указан такой адрес, за пределы предприятия.</p>
     </section>
    </section>
    <section>
     <title>
      <p>А.6. ICMPv4 и ICMPv6: протоколы управляющих сообщений в сети Интернет</p>
     </title>
     <p>Протокол ICMP (Internet Control Message Protocol) является необходимой и неотъемлемой частью любой реализации IPv4 или IPv6. Протокол ICMP обычно используется для обмена сообщениями об ошибках между узлами, как маршрутизирующими, так и обычными, но иногда этот протокол используется и приложениями. Например, приложения <code>ping</code> и <code>traceroute</code> (см. главу 28) используют протокол ICMP.</p>
     <p>Первые 32 бита сообщений совпадают для ICMPv4 и ICMPv6 и приведены на рис. А.10. ICMPv4 документируется в RFC 792 [95], а ICMPv6 — в RFC 2463 [21].</p>
     <image l:href="#img_178.png"/>
     <p><strong>Рис. А.10</strong>. Формат сообщений ICMPv4 и ICMPv6</p>
     <p>Восьмиразрядное поле <emphasis>тип</emphasis> (<emphasis>type</emphasis>) указывает тип сообщения ICMPv4 или ICMPv6, а некоторые типы имеют дополнительную 8-разрядную информацию, указанную в поле <emphasis>кода</emphasis> (<emphasis>code</emphasis>). Поле <emphasis>контрольной суммы</emphasis> (<emphasis>checksum</emphasis>) является стандартной контрольной суммой, используемой в сети Интернет. Отличия между ICMPv4 и ICMPv6 заключаются в том, какие именно поля используются при подсчете контрольной суммы.</p>
     <p>С точки зрения сетевого программирования необходимо понимать, какие сообщения ICMP могут быть возвращены приложению, что именно вызывает ошибку и каким образом эта ошибка возвращается приложению. В табл. А.5 приведены все сообщения ICMPv4 и показано, как они обрабатываются операционной системой 4.4BSD. В последнем столбце приведены значения переменной <code>errno</code> — то есть те ошибки, которые возвращаются приложениям. В табл. А.6 приведен список сообщений ICMPv6. При использовании TCP ошибка не возвращается приложению немедленно. Если TCP разрывает соединение по тайм-ауту, все накопленные ошибки возвращаются приложению. При использовании UDP ошибка возвращается при очередной операции чтения или записи, но только на присоединенном сокете (раздел 8.9).</p>
     <empty-line/>
     <p><strong>Таблица А.5</strong>. Обработка различных типов ICMP-сообщений в 4.4BSD</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Тип</th>
       <th align="left" valign="top">Код</th>
       <th align="left" valign="top">Описание</th>
       <th align="left" valign="top">Обработчик или errno</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Echo-reply (Эхо-ответ)</td>
       <td align="left" valign="top">Пользовательский процесс (Ping)</td>
      </tr>
      <tr align="left">
       <td rowspan="17" align="left" valign="top">3</td>
       <td colspan="3" align="left" valign="top">Destination unreachable (Получатель недоступен)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Network unreachable (Сеть недоступна)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Host unreachable (Узел недоступен)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">Protocol unreachable (Протокол недоступен)</td>
       <td align="left" valign="top">ECONNREFUSED</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">Port unreachable (Порт недоступен)</td>
       <td align="left" valign="top">ECONNREFUSED</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">Fragmentation needed but DF bit set (Необходима фрагментация, но установлен бит DF)</td>
       <td align="left" valign="top">EMSGSIZE</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">5</td>
       <td align="left" valign="top">Source route failed (Сбой маршрута отправителя)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">6</td>
       <td align="left" valign="top">Destination network unknown (Неизвестна сеть получателя)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">7</td>
       <td align="left" valign="top">Destination host unknown (Неизвестен узел получателя)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">8</td>
       <td align="left" valign="top">Source host isolated (Узел отправителя изолирован). Устаревший тип сообщений</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">9</td>
       <td align="left" valign="top">Destination network administratively prohibited (Сеть получателя запрещена администратором)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">10</td>
       <td align="left" valign="top">Destination host administratively prohibited (Узел получателя запрещен администратором)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">11</td>
       <td align="left" valign="top">Network unreachable for TOS (Сеть недоступна для TOS)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">12</td>
       <td align="left" valign="top">Host unreachable for TOS (Узел недоступен для TOS)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">13</td>
       <td align="left" valign="top">Communication administratively prohibited (Связь запрещена администратором)</td>
       <td align="left" valign="top">(Игнорируется)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">14</td>
       <td align="left" valign="top">Host precedence violation (Нарушение порядка старшинства узлов)</td>
       <td align="left" valign="top">(Игнорируется)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">15</td>
       <td align="left" valign="top">Precedence cutoff in effect (Действует старшинство узлов)</td>
       <td align="left" valign="top">(Игнорируется)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Source quench (Отключение отправителя)</td>
       <td align="left" valign="top">Обрабатывается ядром в случае TCP, игнорируется в случае UDP</td>
      </tr>
      <tr align="left">
       <td rowspan="5" align="left" valign="top">5</td>
       <td colspan="3" align="left" valign="top">Redirect (Перенаправление)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Redirect for network (Перенаправление для сети)</td>
       <td align="left" valign="top">Ядро обновляет таблицу маршрутизации</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Redirect for host (Перенаправление для узла)</td>
       <td align="left" valign="top">Ядро обновляет таблицу маршрутизации</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">Redirect for type-of-service and network (Перенаправление для типа сервиса и сети)</td>
       <td align="left" valign="top">Ядро обновляет таблицу маршрутизации</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">Redirect for type of service and host (Перенаправление для типа сервиса и узла)</td>
       <td align="left" valign="top">Ядро обновляет таблицу маршрутизации</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">8</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Echo request (Эхо-запрос)</td>
       <td align="left" valign="top">Ядро генерирует ответ</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">9</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Router advertisement (Извещение маршрутизатора)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">10</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Router solicitation (Запрос маршрутизатору)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td rowspan="3" align="left" valign="top">11</td>
       <td colspan="3" align="left" valign="top">Time exceeded (Превышено время передачи)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">TTL equals 0 during transit (Время жизни равно 0 во время передачи)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">TTL equals 0 during reassembly (Время жизни равно 0 во время сборки)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td rowspan="3" align="left" valign="top">12</td>
       <td colspan="3" align="left" valign="top">Parameter problem (Проблема с параметром)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">IP header bad (Неправильный IP-заголовок). Типичная ошибка</td>
       <td align="left" valign="top">ENOPROTOOPT</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Required option missed (Пропущен необходимый параметр)</td>
       <td align="left" valign="top">ENOPROTOOPT</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">13</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Timestamp request (Запрос отметки времени)</td>
       <td align="left" valign="top">Ядро генерирует ответ</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">14</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Timestamp reply (Ответ об отметке времени)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">15</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Information request (Информационный запрос). Устаревший тип сообщений</td>
       <td align="left" valign="top">(игнорируется)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">16</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Information reply (Информационный ответ). Устаревший тип сообщений</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">17</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Address mask request (Запрос маски адреса)</td>
       <td align="left" valign="top">Ядро генерирует ответ</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">18</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Address mask reply (Ответ маски адреса)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
     </table>
     <empty-line/>
     <p><strong>Таблица А.6</strong>. Сообщения ICMPv6</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Тип</th>
       <th align="left" valign="top">Код</th>
       <th align="left" valign="top">Описание</th>
       <th align="left" valign="top">Обработчик или errno</th>
      </tr>
      <tr align="left">
       <td rowspan="4" align="left" valign="top"/>
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Administratively prohibited, firewall filter (Запрещено администратором, фильтр брандмауэра)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">Not a neighbor, incorrect strict source route (He сосед, некорректный маршрут отправителя)</td>
       <td align="left" valign="top">EHOSTUNREACH</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">3</td>
       <td align="left" valign="top">Address unreachable (Адрес недоступен)</td>
       <td align="left" valign="top">EHOSTDOWN</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">4</td>
       <td align="left" valign="top">Port unreachable (Порт недоступен)</td>
       <td align="left" valign="top">ECONNREFUSED</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Packet too big (Слишком большой пакет)</td>
       <td align="left" valign="top">Ядро выполняет обнаружение транспортной MTU</td>
      </tr>
      <tr align="left">
       <td rowspan="3" align="left" valign="top">3</td>
       <td colspan="3" align="left" valign="top">Time exceeded (Превышено время передачи)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Hop limit exceeded in transit (При передаче превышено значение предельного количества транзитных узлов)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Fragment reassembly time exceeded (Истекло время сборки из фрагментов)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td rowspan="4" align="left" valign="top">4</td>
       <td colspan="3" align="left" valign="top">Parameter problem (Проблема с параметром)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Erroneous header filed (Ошибочное поле заголовка)</td>
       <td align="left" valign="top">ENOPROTOOPT</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">1</td>
       <td align="left" valign="top">Unrecognized next header (Следующий заголовок нераспознаваем)</td>
       <td align="left" valign="top">ENOPROTOOPT</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">2</td>
       <td align="left" valign="top">Unrecognized option (Неизвестный параметр)</td>
       <td align="left" valign="top">ENOPROTOOPT</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">128</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Echo request (Эхо-запрос (Ping))</td>
       <td align="left" valign="top">Ядро генерирует ответ</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">129</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Echo reply (Эхо-ответ (Ping))</td>
       <td align="left" valign="top">Пользовательский процесс (Ping)</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">130</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Group membership query (Запрос о членстве в группе)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">131</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Group membership report (Отчет о членстве в группе)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">132</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Group membership reduction (Сокращение членства в группе)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">133</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Router solicitation (Запрос маршрутизатору)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">134</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Router advertisement (Извещение маршрутизатора)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">135</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Neighbor solicitation (Запрос соседу)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">136</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Neighbor advertisement (Извещение соседа)</td>
       <td align="left" valign="top">Пользовательский процесс</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">137</td>
       <td align="left" valign="top">0</td>
       <td align="left" valign="top">Redirect (Перенаправление)</td>
       <td align="left" valign="top">Ядро обновляет таблицу маршрутизации</td>
      </tr>
     </table>
     <p>Запись «пользовательский процесс» в этой таблице означает, что ядро не обрабатывает сообщение и ждет обработки данного сообщения от пользовательского процесса с символьным сокетом. Также следует отметить, что различные реализации могут обрабатывать одни и те же сообщения по-разному. Например, в Unix сообщения типа <code>Router solicitation</code> (Запрос маршрутизатору) и <code>Router advertisement</code> (Извещение маршрутизатора) обычно обрабатываются как пользовательские процессы, но некоторые реализации могут обрабатывать эти сообщения в ядре.</p>
     <p>Версия ICMPv6 сбрасывает старший бит поля тип для сообщения об ошибке (типы 1-4) и устанавливает этот бит для информационного сообщения (типы 128–137).</p>
    </section>
   </section>
   <section>
    <title>
     <p>Приложение Б</p>
     <p>Виртуальные сети</p>
    </title>
    <section>
     <title>
      <p>Б.1. Введение</p>
     </title>
     <p>Поддержка новых возможностей протокола TCP, например каналов с повышенной пропускной способностью (RFC 1323), требуется только на узле, использующем TCP, тогда как маршрутизаторы в модернизации не нуждаются. Эти изменения, описанные в RFC 1323, постепенно проявляются в реализациях TCP на узлах. Когда устанавливается новое TCP-соединение, каждая сторона может определить, поддерживает ли другая сторона новую возможность, и если для обоих узлов это так, ею можно воспользоваться.</p>
     <p>Иная ситуация с изменениями IP-уровня, такими как многоадресная передача, появившаяся в конце 80-х, или новая версия протокола IPv6, возникшая в середине 90-х, поскольку они требуют изменений на <emphasis>всех</emphasis> узлах и на всех маршрутизаторах. Но люди хотят начать использовать новые возможности, не дожидаясь, когда все системы будут модернизированы. Для этого существующий протокол IPv4 был дополнен так называемыми <emphasis>виртуальными сетями</emphasis> (<emphasis>virtual network</emphasis>), использующими <emphasis>туннели</emphasis> (<emphasis>tunnels</emphasis>).</p>
    </section>
    <section>
     <title>
      <p>Б.2. MBone</p>
     </title>
     <p>Наш первый пример виртуальной сети, построенной с использованием туннелей, — это сеть MBone, которая начала использоваться примерно с 1992 года [29]. Если два или более узла в локальной сети поддерживают многоадресную передачу, то на всех этих узлах могут быть запущены приложения многоадресной передачи, которые могут общаться друг с другом. Для соединения одной локальной сети с другой локальной сетью, также содержащей узлы с возможностью многоадресной передачи, между двумя узлами из этих сетей конфигурируется туннель, как показано на рис. Б.1. На этом рисунке отмечены следующие шаги:</p>
     <image l:href="#img_179.png"/>
     <p><strong>Рис. Б.1</strong>. Упаковка IPv4 в IPv4, применяемая в MBone</p>
     <p>1. Приложение на узле отправителя MH1 посылает групповую дейтаграмму адресам класса D.</p>
     <p>2. На рисунке эта дейтаграмма показана как UDP-дейтаграмма, поскольку большинство приложений многоадресной передачи используют протокол UDP. Более подробно о многоадресной передаче и о том, как посылать и получать многоадресные дейтаграммы, рассказано в главе 21.</p>
     <p>3. Дейтаграмма принимается всеми узлами в локальной сети, поддерживающими многоадресную передачу, в том числе и MR2. Отметим, что MR2 также работает как многоадресный маршрутизатор, на котором запущена программа <code>mrouted</code>, осуществляющая маршрутизацию многоадресной передачи.</p>
     <p>4. MR2 добавляет перед дейтаграммой другой IPv4-заголовок, в котором в поле адреса получателя записан индивидуальный адрес конечного узла туннеля (tunnel endpoint) MR. Этот индивидуальный адрес конфигурируется администратором узла MR2 и считывается программой <code>mrouted</code> при ее запуске. Аналогичным образом индивидуальный адрес узла MR2 сконфигурирован на узле MR — на другом конце туннеля. В поле протокола нового IPv4-заголовка установлено значение 4, соответствующее упаковке IPv4 в IPv4. Дейтаграмма посылается следующему маршрутизатору, UR3, который явно указан как маршрутизатор направленной передачи, то есть не поддерживает многоадресную передачу, и поэтому приходится использовать туннель. Выделенная на рисунке серым цветом часть IPv4-дейтаграммы не изменяется по сравнению шагом 1, только значение поля TTL в выделенном цветом IPv4-заголовке уменьшается на 1.</p>
     <p>5. UR3 узнает адрес получателя из самого внешнего IPv4-заголовка и перенаправляет дейтаграмму следующему маршрутизатору направленной передачи — UR4.</p>
     <p>6. UR4 доставляет дейтаграмму по назначению — узлу MR, который является конечным узлом туннеля.</p>
     <p>7. MR получает дейтаграмму, и поскольку в поле протокола указана упаковка IPv4 в IPv4, удаляет внешний IPv4-заголовок и передает оставшуюся часть дейтаграммы (копию той, которая была групповой дейтаграммой в локальной сети, изображенной на рисунке вверху) в качестве многоадресной дейтаграммы в своей локальной сети.</p>
     <p>8. Все узлы сети, изображенной на рисунке внизу, поддерживающие многоадресную передачу, получают многоадресную дейтаграмму.</p>
     <p>В результате многоадресная дейтаграмма, отправленная в локальной сети, изображенной вверху, передается как многоадресная дейтаграмма в локальной сети, изображенной внизу. Это происходит несмотря на то, что два маршрутизатора, присоединенные к этим двум локальным сетям, а также все маршрутизаторы между ними не поддерживают многоадресную передачу.</p>
     <p>В данном примере показана функция маршрутизации многоадресной передачи, осуществляемая программой <code>mrouted</code>, запущенной на одном из узлов в каждой из локальных сетей. Таким образом начинала свою работу сеть MBone. Но, начиная примерно с 1996 г. большинство основных поставщиков маршрутизаторов стали включать функцию групповой маршрутизации в свои маршрутизаторы. Если бы два маршрутизатора направленной передачи UR3 и UR4 на рис. Б.1 имели возможность маршрутизации многоадресной передачи, то нам не пришлось бы запускать <code>mrouted</code>, а маршрутизаторы UR3 и UR4 работали бы как маршрутизаторы многоадресной передачи. Но если между UR3 и UR4 существуют другие маршрутизаторы, не поддерживающие многоадресную передачу, туннель все же необходим. Однако конечными пунктами туннеля в этом случае могут стать MR3 (новое имя для UR3, поддерживающего многоадресную передачу) и MR4 (новое имя для UR4, поддерживающего многоадресную передачу), а не MR2 и MR.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В сценарии, приведенном на рис. Б.1, каждый многоадресный пакет появляется дважды в локальной сети, расположенной вверху рисунка, и дважды в локальной сети, расположенной внизу. Один раз это многоадресный пакет, а второй раз — направленный пакет внутри туннеля, так как пакет идет между узлом, на котором запущена программа mrouted, и следующим маршрутизатором направленной передачи (то есть между MR2 и UR3, а затем между UR4 и MR). Лишняя копия — это цена туннелирования. Преимущество замены маршрутизаторов направленной передачи UR3 и UR4 на рис. Б.1 на маршрутизаторы многоадресной передачи (те, что мы назвали MR3 и MR4) заключается в том, что мы избежали появления этой дополнительной копии многоадресного пакета в каждой из сетей. Даже если MR3 и MR4 должны установить туннель между собой, поскольку некоторые промежуточные маршрутизаторы между ними (которые на рисунке не показаны) не поддерживают многоадресную передачу, такой вариант предпочтительнее, так как в этом случае не происходит дублирования пакетов в каждой из локальных сетей.</p>
     </cite>
     <p>На данный момент сеть MBone практически прекратила свое существование и заменена нормальной многоадресной передачей. Возможно, в многоадресной инфраструктуре Интернета все еще существуют туннели, но они устанавливаются между маршрутизаторами, поддерживающими многоадресную передачу, и внутри сетей поставщиков услуг Интернета, а потому невидимы для конечного пользователя.</p>
    </section>
    <section>
     <title>
      <p>Б.3. 6bone</p>
     </title>
     <p>Виртуальная сеть 6bone была создана в 1996 году по тем же причинам, что и MBone: пользователи в группах узлов, поддерживающих версию протокола IPv6, хотели соединить их вместе с помощью виртуальной сети, не дожидаясь поддержки IPv6 всеми промежуточными маршрутизаторами. На момент написания этой книги сеть 6bone выходит из употребления по мере внедрения IPv6; полное прекращение функционирования 6bone ожидается в июне 2006 года [30]. Мы рассказываем о туннелях только потому, что до сих пор можно встретить настроенные туннели. О динамических туннелях мы расскажем в разделе Б.4.</p>
     <image l:href="#img_180.png"/>
     <p><strong>Рис. Б.2</strong>. Упаковка IPv6 в IPv4, используемая в сети 6bone</p>
     <p>На рис. Б.2 приведен пример двух локальных сетей, поддерживающих IPv6, соединенных с помощью туннеля только через маршрутизаторы IPv4. На рисунке отмечены следующие шаги:</p>
     <p>1. Узел HI локальной сети, показанной на рисунке вверху, посылает IP-дейтаграмму, содержащую TCP-сегмент, узлу H4 из локальной сети, показанной внизу. Будем называть эти два узла IPv6-узлами, хотя, вероятно, оба они поддерживают и протокол IPv4. В таблице маршрутизации IPv6 на узле H1 записано, что следующим маршрутизатором является узел H2, и IPv6-дейтаграмма отсылается этому маршрутизатору.</p>
     <p>2. На узле HR2 имеется сконфигурированный туннель до узла HR3. Этот туннель позволяет посылать IPv6-дейтаграммы между двумя конечными узлами туннеля через сеть IPv4 путем упаковки IPv6-дейтаграмм в IPv4-дейтаграммы (упаковка IPv6 в IPv4). В поле протокола указано значение 4. Отметим, что оба узла IPv4/IPv6 на концах туннеля — HR2 и HR3 — работают как маршрутизаторы IPv6, поскольку они перенаправляют IPv6-дейтаграммы, получаемые на один интерфейс, через другой интерфейс. Сконфигурированный туннель считается интерфейсом, хотя он является виртуальным, а не физическим интерфейсом.</p>
     <p>3. Конечный узел туннеля (HR3) получает упакованную дейтаграмму, отбрасывает IPv4-заголовок и посылает IPv6-дейтаграмму в свою локальную сеть.</p>
     <p>4. Дейтаграмма приходит по назначению на узел H4.</p>
    </section>
    <section>
     <title>
      <p>Б.4. Переход на IPv6: 6to4</p>
     </title>
     <p>Механизм перехода 6to4 (6на4) полностью описан в документе «Соединение доменов IPv6 через облака IPv4» (RFC 3056 [17]). Это метод динамического создания туннелей, подобных изображенному на рис. Б.2. В отличие от предыдущих механизмов динамического создания туннелей, которые требовали наличия у всех узлов адресов IPv4, а также явного задания механизма туннелирования, 6to4 реализует туннелирование исключительно через маршрутизаторы. Это упрощает конфигурацию и позволяет централизованно устанавливать политику безопасности. Кроме того, появляется возможность совмещать функциональность 6to4 с типичной функциональностью трансляции сетевых адресов и межсетевой защиты (например, это может быть сделано на устройстве NAT, расположенном на стороне клиента).</p>
     <p>Адреса 6to4 лежат в диапазоне 2002/16. В следующих четырех байтах адреса записывается адрес IPv4 (рис. Б.3). 16-разрядный префикс 2002 и 32-разрядный адрес IPv4 создают общий 48-разрядный идентификатор. Для идентификатора подсети, идущего перед 64-разрядным идентификатором интерфейса, остается 2 байта. Например, нашему узлу <code>freebsd</code> с адресом IPv4 12.106.32.254 соответствует префикс 2002:c6a:20fe/48.</p>
     <image l:href="#img_181.png"/>
     <p><strong>Рис. Б.3</strong>. Адреса 6to4</p>
     <p>Преимущество 6to4 перед 6bone состоит в том, что туннели, формирующие инфраструктуру, образуются автоматически. Для их создания не требуется предварительное конфигурирование. Сайт, использующий 6to4, настраивает основной маршрутизатор на известный адрес передачи наиболее подходящему узлу (anycast) IPv4 192.88.99.1 (RFC 3068 [48]). Он соответствует адресу IPv6 2002: :с058:6301::. Маршрутизаторы инфраструктуры IPv6, готовые действовать в качестве шлюзов 6to4, объявляют о маршруте к сети 2002/16 и отправляют упакованный трафик на адрес IPv4, скрытый внутри адреса 6to4. Такие маршрутизаторы могут быть локальными, региональными или глобальными в зависимости от областей действия их маршрутов.</p>
     <p>Смысл виртуальных сетей состоит в том, чтобы постепенно исчезнуть с течением времени, когда промежуточные маршрутизаторы обретут требуемую функциональность (в частности, способность работать с IPv6).</p>
    </section>
   </section>
   <section>
    <title>
     <p>Приложение В</p>
     <p>Техника отладки</p>
    </title>
    <section>
     <p>Это приложение содержит некоторые рекомендации и описание методов отладки сетевых приложений. Ни один из приведенных методов не является панацеей от всех возможных проблем, однако существует множество инструментальных средств, с которыми следует ознакомиться, чтобы в дальнейшем использовать подходящие для конкретной среды.</p>
    </section>
    <section>
     <title>
      <p>В.1. Трассировка системных вызовов</p>
     </title>
     <section>
      <p>Многие версии Unix предоставляют возможность трассировки (отслеживания) системных вызовов. Зачастую это может оказаться полезным методом отладки.</p>
      <p>Работая на этом уровне, необходимо различать <emphasis>системный вызов</emphasis> и <emphasis>функцию</emphasis>. Системный вызов является точкой входа в ядро, и именно это можно отследить с помощью инструментальных средств, описанных в данном разделе. Стандарт POSIX и большинство других стандартов используют термин <emphasis>функция</emphasis>, вкладывая в это понятие тот же смысл, что и пользователи, хотя на самом деле это может быть системный вызов. Например, в Беркли-ядрах <code>socket</code> — это системный вызов, хотя программист приложений может считать, что это обычная функция языка С. В системе SVR4, как будет показано далее, это функция из библиотеки сокетов, которая содержит вызовы <code>putmsg</code> и <code>getmsg</code>, в действительности являющиеся системными вызовами.</p>
      <p>В этом разделе мы рассмотрим системные вызовы, задействованные в работе клиента времени и даты (см. листинг 1.1).</p>
     </section>
     <section>
      <title>
       <p>Сокеты ядра BSD</p>
      </title>
      <p>Мы начнем с FreeBSD, операционной системы с Беркли-ядром, в котором все функции сокетов являются системными вызовами. Программа трассировки системных вызовов имеет название <code>ktrace</code>. Она выводит информацию о трассировке в файл (по умолчанию имя этого файла <code>ktrace.out</code>), который можно вывести на экран с помощью <code>kdump</code>. Клиент сокета запускается следующим образом:</p>
      <p><code>freebsd % <strong>ktrace daytimetcpcli 192.168.42.2</strong></code></p>
      <p><code>Tue Aug 19 23:35.10 2003</code></p>
      <p>Затем запускаем <code>kdump</code>, чтобы направить трассировочную информацию в стандартный поток вывода.</p>
      <p><code>3211 daytimetcpcli CALL socket(0x2,0x1,0)</code></p>
      <p><code>3211 daytimetcpcli RET socket 3</code></p>
      <p><code>3211 daytimetcpcli CALL connect(0x3,0x7fdffffe820,0x10)</code></p>
      <p><code>3211 daytimetcpcli RET connect 0</code></p>
      <p><code>3211 daytimetcpcli CALL read(0x3,0x7fdffffe830,0x1000)</code></p>
      <p><code>3211 daytimetcpcli GIO fd 3 read 26 bytes</code></p>
      <p><code>     "Tue Aug 19 23:35:10 2003</code></p>
      <p><code>     "</code></p>
      <p><code>3211 daytimetcpcli RET read 26/0x1a</code></p>
      <p><code>...</code></p>
      <p><code>3211 daytimetcpcli CALL write(0x1,0x204000,0x1a)</code></p>
      <p><code>3211 daytimetcpcli GIO fd 1 wrote 26 bytes</code></p>
      <p><code>     "Tue Aug 19 23:35:10 2003</code> </p>
      <p><code>     "</code></p>
      <p><code>3211 daytimetcpcli RET write 26/0x1a</code></p>
      <p><code>3211 daytimetcpcli CALL read(0x3,0x7fdffffe830,0x1000)</code></p>
      <p><code>3211 daytimetcpcli GIO fd 3 read 0 bytes</code></p>
      <p><code>     ""</code></p>
      <p><code>3211 daytimetcpcli RET read 0</code></p>
      <p><code>3211 daytimetcpcli CALL exit(0)</code></p>
      <p>Число 3211 является идентификатором процесса. <code>CALL</code> идентифицирует системный вызов, <code>RET</code> обозначает возвращение управления, <code>GIO</code> подразумевает общую операцию ввода-вывода. Мы видим системные вызовы <code>socket</code> и <code>connect</code>, за которыми следуют вызовы <code>read</code>, возвращающие 26 байт. Наш клиент записывает эти байты в стандартный поток вывода, и при следующем вызове <code>read</code> возвращает нулевое значение (конец файла).</p>
     </section>
     <section>
      <title>
       <p>Сокеты ядра Solaris 9</p>
      </title>
      <p>Операционная система Solaris 2.x основывается на SVR4, и во всех версиях ранее 2.6 сокеты реализуются так, как показано на рис. 31.3. Однако во всех версиях SVR4 с подобными реализациями сокетов существует одна проблема: они редко обеспечивают полную совместимость с сокетами Беркли-ядер. Для обеспечения дополнительной совместимости в Solaris 2.6 способ реализации изменен за счет использования файловой системы <code>sockfs</code>. Такой подход обеспечивает поддержку сокетов ядра, что можно проверить с помощью программы <code>truss</code> на нашем клиенте (использующем сокеты).</p>
      <p><code>solaris % <strong>truss -v connect daytimetcpcli 127.0.0.1</strong></code></p>
      <p><code>Mon Sep 8 12:16:42 2003</code></p>
      <p>После обычного подключения библиотеки осуществляется первый системный вызов <code>so_socket</code> — системный вызов, инициированный нашим вызовом <code>socket</code>.</p>
      <p><code>so_socket(PF_INET, SOCK_STREAM, IPPROTO_IP, 1) = 3</code></p>
      <p><code>connect(3, 0xFFBFDEF0, 16, 1) = 0</code></p>
      <p><code>AF_INET name = 127.0.0.1 port = 13</code></p>
      <p><code>read(3, " M o n S e p 8 1", ... 4096) = 26</code></p>
      <p><code>Mon Sep 8 12:48:06 2003</code></p>
      <p><code>write(1, " M o n S e p 8 1", ... 26) = 26</code></p>
      <p><code>read(3, 0xFFBFDF03, 4096) = 0</code></p>
      <p><code>_exit(0)</code></p>
      <p>Первые три аргумента системного вызова <code>so_socket</code> являются нашими аргументами <code>socket</code>.</p>
      <p>Далее мы видим, что <code>connect</code> является системным вызовом, a <code>truss</code> при вызове с флагом <code>-v connect</code> выводит на экран содержимое структуры адреса сокета, на которую указывает второй аргумент (IP-адрес и номер порта). Мы не показываем системные вызовы, относящиеся к стандартным потокам ввода и вывода.</p>
     </section>
    </section>
    <section>
     <title>
      <p>В.2. Стандартные службы Интернета</p>
     </title>
     <p>Рекомендуем ознакомиться со стандартными службами Интернета, приведенными в табл. 2.1. Для тестирования наших клиентов мы много раз использовали службу, позволяющую определить дату и время. Служба, игнорирующая присылаемые данные, является удобным портом, на который можно отправлять данные. Эхо-служба аналогична эхо-серверу, неоднократно упоминаемому в этой книге.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>В настоящее время многие сайты перекрывают доступ к этим службам с помощью брандмауэров, так как некоторые атаки типа «отказ в обслуживании» (DoS), имевшие место в 1996 году, были направлены именно на эти службы (см. упражнение 13.3). Тем не менее можно успешно использовать эти службы внутри локальной сети.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>В.3. Программа sock</p>
     </title>
     <p>Программа <code>sock</code>, написанная Уильямом Стивенсом, впервые появилась в книге [111], где широко использовалась для генерации специальных условий, большинство которых затем проверялось с помощью программы <code>tcpdump</code>. Удобство этой программы заключается в том, что она генерирует такое множество различных сценариев, что нет необходимости писать специальные тестовые программы.</p>
     <p>В этой книге исходный код программы не приведен (более 2000 строк на языке С), но он находится в свободном доступе (см. предисловие).</p>
     <p>Программа работает в одном из четырех режимов, и в каждом из них можно использовать либо протокол TCP, либо протокол UDP.</p>
     <p>1. Клиент стандартного ввода и стандартного вывода (рис. В.1).</p>
     <image l:href="#img_182.png"/>
     <empty-line/>
     <p><strong>Рис. В.1</strong>. Клиент sock: стандартный ввод и стандартный вывод</p>
     <p>В клиентском режиме все, что считывается из стандартного потока ввода, передается в сеть, а все, что получается из сети, записывается в стандартный поток вывода. Должны быть указаны IP-адрес сервера и номер порта, и в случае TCP выполняется активное открытие.</p>
     <p>2. Сервер стандартного ввода и стандартного вывода. Этот режим аналогичен предыдущему, за исключением того, что программа связывает заранее известный порт со своим сокетом и в случае TCP осуществляется пассивное открытие.</p>
     <p>3. Клиент-отправитель (рис. В.2).</p>
     <image l:href="#img_183.png"/>
     <p><strong>Рис. В.2</strong>. Программа sock в качестве клиента-отправителя</p>
     <p>Программа осуществляет фиксированное количество передач пакетов некоторого определенного размера в сеть.</p>
     <p>4. Сервер-получатель (рис. В.3).</p>
     <image l:href="#img_184.png"/>
     <empty-line/>
     <p><strong>Рис. В.3</strong>. Программа sock в качестве сервера-получателя</p>
     <p>Программа осуществляет фиксированное количество считываний из сети.</p>
     <p>Эти четыре рабочих режима соответствуют следующим четырем командам:</p>
     <p><code>sock [<emphasis>параметры</emphasis>] <emphasis>узел служба</emphasis></code></p>
     <p><code>sock [<emphasis>параметры</emphasis>] -s [<emphasis>узел</emphasis>] <emphasis>служба</emphasis></code></p>
     <p><code>sock [<emphasis>параметры</emphasis>] -i <emphasis>узел служба</emphasis></code></p>
     <p><code>sock [<emphasis>параметры</emphasis>] -is [<emphasis>узел</emphasis>] <emphasis>служба</emphasis></code></p>
     <p>где <emphasis>узел</emphasis>— это имя или IP-адрес узла, а <emphasis>служба</emphasis> — это имя или номер порта. В двух серверных режимах выполняется связывание с универсальным адресом, если не задан необязательный параметр <emphasis>узел</emphasis>.</p>
     <p>Можно также определить около 40 параметров командной строки, запускающих дополнительные возможности программы. Здесь мы не будем подробно останавливаться на этих параметрах, отметим только, что можно использовать почти все параметры сокетов, упомянутые в главе 7. Запуск программы без аргументов выводит на экран краткое описание всех параметров:</p>
     <p><code>-b n связывает n в качестве клиентского локального номера порта</code></p>
     <p><code>-с   конвертирует символ новой строки в CR/LF и наоборот</code></p>
     <p><code>-f a.b.c.d.p удаленный IP-адрес = a.b.c.d, удаленный номер порта = р</code></p>
     <p><code>-g a.b.c.d свободная маршрутизация</code></p>
     <p><code>-h   половинное закрытие TCP при получении EOF из стандартного потока ввода</code></p>
     <p><code>-i   отправка данных на сокет, прием данных с сокета (w/-s)</code></p>
     <p><code>-j a.b.c.d присоединение к группе многоадресной передачи</code></p>
     <p><code>-k   осуществляет write или writev порциями</code></p>
     <p><code>-l a.b.c.d.p клиентский локальный IP-адрес = a.b.c.d. локальный номер порта = р</code></p>
     <p><code>-n n размер буфера для записи клиентом "рассылки" (по умолчанию 1024)</code></p>
     <p><code>-о   НЕ присоединять UDP-клиент</code></p>
     <p><code>-р n время ожидания (в мс) перед каждым считыванием или записью (рассылка/прием)</code></p>
     <p><code>-q n размер очереди на прослушиваемом сокете для сервера TCP</code></p>
     <p><code>     (по умолчанию 5)</code></p>
     <p><code>-r n количество байтов за одну операцию считывания (read) для сервера "приема"</code></p>
     <p><code>     (по умолчанию 1024)</code></p>
     <p><code>-s   работает как сервер, а не как клиент</code></p>
     <p><code>-u   использовать UDP вместо TCP</code></p>
     <p><code>-v   подробный вывод</code></p>
     <p><code>-w n количество байтов для каждой записи (write) клиента "рассылки"</code></p>
     <p><code>     (по умолчанию 1024)</code></p>
     <p><code>-x n время (в ms) для SO_RCVTIMEO (получение тайм-аута)</code></p>
     <p><code>-y n время (в ms) для SO_SNDTIMEO (отправка тайм-аута)</code></p>
     <p><code>-A   параметр SO_REUSEADDR</code></p>
     <p><code>-B   параметр SO_BROADCAST</code></p>
     <p><code>-D   параметр SO_DEBUG</code></p>
     <p><code>-E   параметр IP_RECVDSTADDR</code></p>
     <p><code>-F   порождение дочерних процессов (fork) после установления соединения</code></p>
     <p><code>     (параллельный TCP-сервер)</code></p>
     <p><code>-G a.b.c.d жесткая маршрутизация</code></p>
     <p><code>-H n параметр IP_TOS (16=min del, 8=max thru, 4=max rel, 2=min cost)</code></p>
     <p><code>-I   сигнал SIGIO</code></p>
     <p><code>-J n параметр IP_TTL</code></p>
     <p><code>-K   параметр SO_KEEPALIVE</code></p>
     <p><code>-L n параметр SO_LINGER, n = linger time</code></p>
     <p><code>-N   параметр TCP_NODELAY</code></p>
     <p><code>-O n время (в мс) для ожидания после вызова listen, но перед первым приемом (accept)</code></p>
     <p><code>-Р n время (в мс) перед первым считыванием или записью (рассылка/прием)</code></p>
     <p><code>-Q n время (в мс) ожидания после получения FIN, но перед закрытием</code></p>
     <p><code>-R n параметр SO_RCVBUF</code></p>
     <p><code>-S n параметр SO_SNDBUF</code></p>
     <p><code>-Т   параметр SO_REUSEPORT</code></p>
     <p><code>-U n войти в срочный режим, прежде чем записать число n (только для отправителя)</code></p>
     <p><code>-V   использовать writev() вместо write(): включает -k</code></p>
     <p><code>-W   игнорировать ошибки записи для клиента приема</code></p>
     <p><code>-X n параметр TCP_MAXSEG (устанавливает MSS)</code></p>
     <p><code>-Y   параметр SO_DONTROUTE</code></p>
     <p><code>-Z   MSG_PEEK</code></p>
     <p><code>-2   параметр IP_ONESBCAST (255.255.255.255) для широковещательной передачи</code></p>
    </section>
    <section>
     <title>
      <p>В.4. Небольшие тестовые программы</p>
     </title>
     <p>Другим полезным методом отладки, которым автор пользовался при написании книги, является создание небольших тестовых программ, позволяющих увидеть, как работает одно конкретное свойство в тщательно выстроенной тестовой ситуации. При написании небольших тестовых программ полезно иметь набор библиотечных функций-оберток и некоторых простых функций вывода сообщений об ошибках, наподобие тех, что использовались на протяжении всей книги. Такой подход уменьшает размер создаваемого кода и в то же время обеспечивает требуемую проверку ошибок.</p>
    </section>
    <section>
     <title>
      <p>В.5. Программа tcpdump</p>
     </title>
     <p>Бесценным средством отладки в сетевом программировании является такая программа, как <code>tcpdump</code>. Она считывает пакеты из сети и выводит на экран большое количество информации об этих пакетах. Эта программа также позволяет нам задать некоторые критерии отбора пакетов, в результате чего будут выводиться только пакеты, удовлетворяющие этим критериям. Например,</p>
     <p><code>% <strong>tcpdump '(udp and port daytime) or icmp'</strong></code></p>
     <p>выводит только UDP-дейтаграммы с номером порта отправителя или получателя, равным 13 (сервер времени и даты), или ICMP-пакеты. Следующая команда:</p>
     <p><code>% <strong>tcpdump 'tcp and port 80 and tcp[13:1] &amp; 2 != 0'</strong></code></p>
     <p>выводит только TCP-сегменты с номером порта отправителя или получателя, равным 80 (сервер HTTP), у которых установлен флаг SYN. Флаг SYN имеет значение 2 в 13-м байте от начала TCP-заголовка. Следующая команда:</p>
     <p><code>% <strong>tcpdump 'tcp and tcp[0:2] &gt; 7000 and tcp[0:2] &lt;= 7005'</strong></code></p>
     <p>выводит только те TCP-сегменты, у которых номер порта отправителя лежит в интервале от 7001 до 7005. Номер порта отправителя занимает 2 байта в самом начале TCP-заголовка (нулевое смещение).</p>
     <p>В приложении А книги [111] более подробно описано действие данной программы.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Эта программа доступна по адресу http://www.tcpdump.org/ и работает под множеством реализаций Unix. Она написана Ван Якобсоном (Van Jacobson), Крэгом Лересом (Craig Leres) и Стивеном МакКаном (Steven McCanne) из LBL, и в настоящее время сопровождается командой tcpdump.org.</p>
      <p>Некоторые поставщики предлагают свои программы, обладающие теми же возможностями. Например, в Solaris 2.x есть программа snoop. Но программа tcpdump функционирует под множеством версий Unix, а возможность использования одного и того же средства в неоднородном окружении является большим преимуществом.</p>
     </cite>
    </section>
    <section>
     <title>
      <p>В.6. Программа netstat</p>
     </title>
     <p>В тексте книги много раз использовалась программа <code>netstat</code>. Эта программа служит для следующих целей.</p>
     <p>&#9632; Она выводит статус точек доступа сети. Это было показано в разделе 5.6, когда мы прослеживали статус нашей точки доступа при запуске клиента и сервера.</p>
     <p>&#9632; Она показывает, к какой группе принадлежит каждый из интерфейсов узла. Обычно для этой цели используется флаг <code>-ia</code>, а в Solaris 2.x используется флаг <code>-g</code>.</p>
     <p>&#9632; С параметром <code>-s</code> эта программа сообщает статистику по каждому протоколу. Подобный пример был приведен в разделе 8.13, когда мы говорили о недостаточном управлении потоками в UDP.</p>
     <p>&#9632; При использовании параметра <code>-r</code> программа выводит таблицу маршрутизации, а с параметром <code>-i</code> — информацию об интерфейсе. Эта возможность была использована в разделе 1.9, когда с помощью программы <code>netstat</code> мы выясняли топологию сети.</p>
     <p>Программа <code>netstat</code> обладает и другими возможностями, а многие поставщики добавляют свои собственные. Обратитесь к руководству по вашей системе.</p>
    </section>
    <section>
     <title>
      <p>В.7. Программа lsof</p>
     </title>
     <p>Название <code>lsof</code> происходит от «list open files» (перечислить открытые файлы). Как и <code>tcpdump</code>, эта программа является общедоступной и представляет собой удобное средство для отладки, которое было перенесено на множество версий Unix.</p>
     <p>Одним из общих способов применения программы <code>lsof</code> при работе в сети является выявление процесса, имеющего открытый сокет, по указанному IP-адресу или порту. Программа <code>netstat</code> позволяет выяснить, какой IP-адрес или порт используется, а также узнать состояние TCP-соединения, но она не позволяет идентифицировать процесс. Например, чтобы определить, какой процесс запустил сервер времени и даты, выполним следующую команду:</p>
     <p><code>solaris % <strong>lsof -i TCP:daytime</strong></code></p>
     <p><code>COMMAND PID USER FD  TYPE DEVICE     SIZE/OFF INODE NAME</code></p>
     <p><code>inetd   222 root 15u inet 0xf5a801f8 0t0      TCP   *:daytime</code></p>
     <p>В выводе приводятся следующие данные: команда (данный сервис обеспечивается сервером <code>inetd</code>), идентификатор процесса, владелец процесса, дескриптор (15 и u означает, что он открыт на чтение и на запись), тип сокета, адрес протокола блока управления, размер смещения файла (не имеет значения для сокета), тип протокола и имя.</p>
     <p>Еще один из традиционных случаев применения данной программы имеет место, когда мы запускаем сервер, который связывает свой заранее известный порт и получает ошибку, указывающую, что адрес уже используется. Тогда мы запускаем программу <code>lsof</code>, чтобы выяснить, каким процессом используется данный порт.</p>
     <p>Поскольку программа <code>lsof</code> сообщает об открытых файлах, она не может сообщать о точках доступа, не ассоциированных с открытым файлом, то есть точках доступа TCP в состоянии TIME_WAIT.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Программа находится по адресу ftp://vic.cc.purdue.edu/pub/tools/unix/lsof. Она написана Виком Абелем (Vic Abell).</p>
      <p>Некоторые поставщики предлагают свои программы с похожими возможностями. Например, в BSD/OS предлагается программа fstat. Однако программа lsof работает под множеством версий Unix, а использование одного инструмента в неоднородном окружении вместо подбора различных средств для каждой среды является большим преимуществом.</p>
     </cite>
    </section>
   </section>
   <section>
    <title>
     <p>Приложение Г</p>
     <p>Различные исходные коды</p>
    </title>
    <section>
     <title>
      <p>Г.1. Заголовочный файл unp.h</p>
     </title>
     <p>Почти каждая программа в этой книге начинается с подключения заголовочного файла <code>unp.h</code>, показанного в листинге Г.1<a l:href="#n1" type="note">[1]</a>. Этот файл подключает все стандартные системные заголовочные файлы, необходимые для работы большинства программ, а также некоторые общие системные заголовочные файлы. В нем также определены такие константы, как <code>MAXLINE</code>, прототипы функций ANSI С для тех функций, которые мы определяем в тексте (например, <code>readline</code>), и все используемые нами функции-обёртки. Сами прототипы в приведенном ниже листинге мы не показываем.</p>
     <p><strong>Листинг Г.1</strong>. Заголовочный файл unp.h</p>
     <p><code>//lib/unp.h</code></p>
     <p><code>  1 /* Наш собственный заголовочный файл */</code></p>
     <empty-line/>
     <p><code>  2 #ifndef __unp_h</code></p>
     <p><code>  3 #define __unp_h</code></p>
     <empty-line/>
     <p><code>  4 #include "../config.h" /* параметры конфигурации для данной ОС */</code></p>
     <p><code>  5 /* "../config.h" генерируется сценарием configure */</code></p>
     <empty-line/>
     <p><code>  6 /* изменив список директив #include,</code></p>
     <p><code>  7    нужно также изменить файл acsite.m4 */</code></p>
     <empty-line/>
     <p><code>  8 #include &lt;sys/types.h&gt; /* основные системные типы данных */</code></p>
     <p><code>  9 #include &lt;sys/socket.h&gt; /* основные определения сокетов */</code></p>
     <p><code> 10 #include &lt;sys/time.h&gt; /* структура timeval{} для функции select() */</code></p>
     <p><code> 11 #include &lt;time.h&gt; /* структура timespec{} для функции pselect() */</code></p>
     <p><code> 12 #include &lt;netinet/in.h&gt; /* структура sockaddr_in{} и другие сетевые</code></p>
     <p><code>                               определения */</code></p>
     <p><code> 13 #include &lt;arpa/inet.h&gt; /* inet(3) функции */</code></p>
     <p><code> 14 #include &lt;errno.h&gt;</code></p>
     <p><code> 15 #include &lt;fcntl.h&gt; /* для неблокируемых сокетов */</code></p>
     <p><code> 16 #include &lt;netdb.h&gt;</code></p>
     <p><code> 17 #include &lt;signal.h&gt;</code></p>
     <p><code> 18 #include &lt;stdio.h&gt;</code></p>
     <p><code> 19 #include &lt;stdlib.h&gt;</code></p>
     <p><code> 20 #include &lt;string.h&gt;</code></p>
     <p><code> 21 #include &lt;sys/stat.h&gt; /* для констант S_xxx */</code></p>
     <p><code> 22 #include &lt;sys/uio.h&gt; /* для структуры iovec{} и ready/writev */</code></p>
     <p><code> 23 #include &lt;unistd.h&gt;</code></p>
     <p><code> 24 #include &lt;sys/wait.h&gt;</code></p>
     <p><code> 25 #include &lt;sys/un.h&gt; /* для доменных сокетов Unix */</code></p>
     <empty-line/>
     <p><code> 26 #ifdef HAVE_SYS_SELECT_H</code></p>
     <p><code> 27 #include &lt;sys/select.h&gt; /* для удобства */</code></p>
     <p><code> 28 #endif</code></p>
     <empty-line/>
     <p><code> 29 #ifdef HAVE_SYS_SYSCTL_H</code></p>
     <p><code> 30 #include &lt;sys/sysctl.h&gt;</code></p>
     <p><code> 31 #endif</code></p>
     <empty-line/>
     <p><code> 32 #ifdef HAVE_POLL_H</code></p>
     <p><code> 33 #include &lt;poll.h&gt; /* для удобства */</code></p>
     <p><code> 34 #endif</code></p>
     <empty-line/>
     <p><code> 35 #ifdef HAVE_SYS_EVENT_H</code></p>
     <p><code> 36 #include &lt;sys/event.h&gt; /* для kqueue */</code></p>
     <p><code> 37 #endif</code></p>
     <empty-line/>
     <p><code> 38 #ifdef HAVE_STRINGS_H</code></p>
     <p><code> 39 #include &lt;strings.h&gt; /* для удобства */</code></p>
     <p><code> 40 #endif</code></p>
     <empty-line/>
     <p><code> 41 /* Три заголовочных файла обычно нужны для вызова ioctl</code></p>
     <p><code> 42    для сокета/файла: &lt;sys/ioctl.h&gt;, &lt;sys/filio.h&gt;,</code></p>
     <p><code> 43    &lt;sys/sockio.h&gt; */</code></p>
     <p><code> 44 #ifdef HAVE_SYS_IOCTL_H</code></p>
     <p><code> 45 #include &lt;sys/ioctl.h&gt;</code></p>
     <p><code> 46 #endif</code></p>
     <p><code> 47 #ifdef HAVE_SYS_FILIO_H</code></p>
     <p><code> 48 #include &lt;sys/filio.h&gt;</code></p>
     <p><code> 49 #endif</code></p>
     <p><code> 50 #ifdef HAVE_SYS_SOCKIO_H</code></p>
     <p><code> 51 #include &lt;sys/sockio.h&gt;</code></p>
     <p><code> 52 #endif</code></p>
     <empty-line/>
     <p><code> 53 #ifdef HAVE_PTHREAD_H</code></p>
     <p><code> 54 #include &lt;pthread.h&gt;</code></p>
     <p><code> 55 #endif</code></p>
     <empty-line/>
     <p><code> 56 #ifdef HAVE_NET_IF_DL_H</code></p>
     <p><code> 57 #include &lt;net/if_dl.h&gt;</code></p>
     <p><code> 58 #endif</code></p>
     <empty-line/>
     <p><code> 59 #ifdef HAVE_NETINET_SCTP_H</code></p>
     <p><code> 60 #include &lt;netinet/sctp.h&gt;</code></p>
     <p><code> 61 #endif</code></p>
     <empty-line/>
     <p><code> 62 /* OSF/1 фактически запрещает recv() и send() в &lt;sys/socket.h&gt; */</code></p>
     <p><code> 63 #ifdef __osf__</code></p>
     <p><code> 64 #undef recv</code></p>
     <p><code> 65 #undef send</code></p>
     <p><code> 66 #define recv(a,b,c,d) recvfrom(a,b,c,d,0,0)</code></p>
     <p><code> 67 #define send(a,b,c,d) sendto(a,b,c,d,0,0)</code></p>
     <p><code> 68 #endif</code></p>
     <empty-line/>
     <p><code> 69 #ifndef INADDR_NONE</code></p>
     <p><code> 70 #define INADDR_NONE 0xffffffff /* должно было быть в &lt;netinet/in.h&gt; */</code></p>
     <p><code> 71 #endif</code></p>
     <empty-line/>
     <p><code> 72 #ifndef SHUT_RD     /* три новые константы Posix.1g */</code></p>
     <p><code> 73 #define SHUT_RD   0 /* отключение чтения */</code></p>
     <p><code> 74 #define SHUT_WR   1 /* отключение записи */</code></p>
     <p><code> 75 #define SHUT_RDWR 2 /* отключение чтения и записи */</code></p>
     <p><code> 76 #endif</code></p>
     <empty-line/>
     <p><code> 77 #ifndef INET_ADDRSTRLEN</code></p>
     <p><code> 78 #define INET_ADDRSTRLEN 16 /* "ddd.ddd.ddd.ddd\0"</code></p>
     <p><code> 79 1234567890123456 */</code></p>
     <p><code> 80 #endif</code></p>
     <empty-line/>
     <p><code> 81 /* Нужно, даже если нет поддержки IPv6, чтобы мы всегда могли</code></p>
     <p><code> 82    разместить в памяти буфер требуемого размера без директив #ifdef */</code></p>
     <p><code> 83 #ifndef INET6_ADDRSTRLEN</code></p>
     <p><code> 84 #define INET6_ADDRSTRLEN 46 /* максимальная длина строки адреса IPv6:</code></p>
     <p><code> 85 "xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx" или</code></p>
     <p><code> 86 "xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:ddd.ddd.ddd.ddd\0"</code></p>
     <p><code> 87 1234567890123456789012345678901234567890123456 */</code></p>
     <p><code> 88 #endif</code></p>
     <empty-line/>
     <p><code> 89 /* Определяем bzero() как макрос, если эта функция отсутствует в</code></p>
     <p><code>       стандартной библиотеке С */</code></p>
     <p><code> 90 #ifndef HAVE_BZERO</code></p>
     <p><code> 91 #define bzero(ptr,n) memset(ptr, 0, n)</code></p>
     <p><code> 92 #endif</code></p>
     <empty-line/>
     <p><code> 93 /* В более старых распознавателях отсутствует gethostbyname2() */</code></p>
     <p><code> 94 #ifndef HAVE_GETHOSTBYNAME2</code></p>
     <p><code> 95 #define gethostbyname2(host, family) gethostbyname((host))</code></p>
     <p><code> 96 #endif</code></p>
     <empty-line/>
     <p><code> 97 /* Структура, возвращаемая функцией recvfrom_flags() */</code></p>
     <p><code> 98 struct in_pktinfo {</code></p>
     <p><code> 99  struct in_addr ipi_addr; /* IPv4-адрес получателя */</code></p>
     <p><code>100  int    ipi_ifindex; /* полученный индекс интерфейса */</code></p>
     <p><code>101 };</code></p>
     <empty-line/>
     <p><code>102 /* Нам нужны более новые макросы CMSG_LEN() и CMSG_SPACE(), но в</code></p>
     <p><code>103    настоящее время их поддерживают далеко не все реализации. Им требуется</code></p>
     <p><code>104    макрос ALIGN(), но это зависит от реализации */</code></p>
     <p><code>105 #ifndef CMSG_LEN</code></p>
     <p><code>106 #define CMSG_LEN(size) (sizeof(struct cmsghdr) + (size))</code></p>
     <p><code>107 #endif</code></p>
     <p><code>108 #ifndef CMSG_SPACE</code></p>
     <p><code>109 #define CMSG_SPACE(size) (sizeof(struct cmsghdr) + (size))</code></p>
     <p><code>110 #endif</code></p>
     <empty-line/>
     <p><code>111 /* POSIX требует макрос SUN_LEN(), но он определен</code></p>
     <p><code>112 не во всех реализациях. Этот макрос 4.4BSD работает</code></p>
     <p><code>123 независимо от того, имеется ли поле длины */</code></p>
     <p><code>114 #ifndef SUN_LEN</code></p>
     <p><code>115 #define SUN_LEN(su) \</code></p>
     <p><code>116  (sizeof(*(su)) - sizeof((su)-&gt;sun_path) + strlen((su)-&gt;sun_path))</code></p>
     <p><code>117 #endif</code></p>
     <empty-line/>
     <p><code>118 /* В POSIX "домен Unix" называется "локальным IPC".</code></p>
     <p><code>119    Но пока не во всех системах определены AF_LOCAL и PF_LOCAL */</code></p>
     <p><code>120 #ifndef AF_LOCAL</code></p>
     <p><code>121 #define AF_LOCAL AF_UNIX</code></p>
     <p><code>122 #endif</code></p>
     <p><code>123 #ifndef PF_LOCAL</code></p>
     <p><code>124 #define PF_LOCAL PF_UNIX</code></p>
     <p><code>125 #endif</code></p>
     <empty-line/>
     <p><code>126 /* POSIX требует определения константы INFTIM в &lt;poll.h&gt;, но во многих</code></p>
     <p><code>127    системах она по-прежнему определяется в &lt;sys/stropts.h&gt;. Чтобы</code></p>
     <p><code>128    не подключать все функции работы с потоками, определяем ее здесь.</code></p>
     <p><code>129    Это стандартное значение, но нет гарантии, что оно равно -1 */</code></p>
     <p><code>130 #ifndef INFTIM</code></p>
     <p><code>131 #define INFTIM (-1) /* бесконечный тайм-аут */</code></p>
     <p><code>132 #ifdef HAVE_POLL_H</code></p>
     <p><code>133 #define INFTIM_UNPH /* надо указать в unpxti.h, что эта константа</code></p>
     <p><code>                           определена здесь */</code></p>
     <p><code>134 #endif</code></p>
     <p><code>135 #endif</code></p>
     <empty-line/>
     <p><code>136 /* Это значение можно было бы извлечь из SOMAXCONN в &lt;sys/socket.h&gt;,</code></p>
     <p><code>137    но многие ядра по-прежнему определяют его как 5,</code></p>
     <p><code>       хотя на самом деле поддерживается гораздо больше */</code></p>
     <p><code>138 #define LISTENQ 1024 /* второй аргумент функции listen() */</code></p>
     <empty-line/>
     <p><code>139 /* Различные константы */</code></p>
     <p><code>140 #define MAXLINE  4096 /* максимальная длина текстовой строки */</code></p>
     <p><code>141 #define BUFFSIZE 8192 /* размер буфера для чтения и записи */</code></p>
     <empty-line/>
     <p><code>142 /* Определение номера порта, который может быть использован для</code></p>
     <p><code>       взаимодействия клиент-сервер */</code></p>
     <p><code>143 #define SERV_PORT      9877  /* клиенты и серверы TCP и UDP */</code></p>
     <p><code>144 #define SERV_PORT_STR "9877" /* клиенты и серверы TCP и UDP */</code></p>
     <p><code>145 #define UNIXSTR_PATH "/tmp/unix.str" /* потоковые клиенты и серверы</code></p>
     <p><code>                                            домена Unix */</code></p>
     <p><code>146 #define UNIXDG_PATH "/tmp/unix.dg" /* клиенты и серверы протокола</code></p>
     <p><code>                                          дейтаграмм домена Unix */</code></p>
     <p><code>147 /* Дальнейшие определения сокращают преобразования типов</code></p>
     <p><code>       аргументов-указателей */</code></p>
     <p><code>148 #define SA struct sockaddr</code></p>
     <empty-line/>
     <p><code>149 #define HAVE_STRUCT_SOCKADDR_STORAGE</code></p>
     <p><code>150 #ifndef HAVE_STRUCT_SOCKADDR_STORAGE</code></p>
     <p><code>151 /*</code></p>
     <p><code>152  * RFC 3493: протокольно-независимая структура адреса сокета</code></p>
     <p><code>153  */</code></p>
     <p><code>154 #define __SS_MAXSIZE 128</code></p>
     <p><code>155 #define __SS_ALIGNSIZE (sizeof(int64_t))</code></p>
     <p><code>156 #ifndef HAVE_SOCKADDR_SA_LEN</code></p>
     <p><code>157 #define __SS_PADS1SIZE (__SS_ALIGNSIZE - sizeof(u_char) -</code></p>
     <p><code>sizeof(sa_family_t))</code></p>
     <p><code>158 #else</code></p>
     <p><code>159 #define _SS_PAD1SIZE (__SS_ALIGNSIZE - sizeof(sa_family_t))</code></p>
     <p><code>160 #endif</code></p>
     <p><code>161 #define __SS_PAD2SIZE (__SS_MAXSIZE — 2*__SS_ALIGNSIZE)</code></p>
     <empty-line/>
     <p><code>162 struct sockaddr_storage {</code></p>
     <p><code>163 #ifdef HAVE_SOCKADDR_SA_LEN</code></p>
     <p><code>164  u_char ss_len;</code></p>
     <p><code>165 #endif</code></p>
     <p><code>166  sa_family_t ss_family;</code></p>
     <p><code>167  char        __ss_pad1[__SS_PAD1SIZE];</code></p>
     <p><code>168  int64_t     ss_align;</code></p>
     <p><code>169  char        __ss_pad2[_SS_PAD2SIZE];</code></p>
     <p><code>170 };</code></p>
     <p><code>171 #endif</code></p>
     <empty-line/>
     <p><code>172 #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)</code></p>
     <p><code>173 /* заданные по умолчанию разрешения на доступ для новых файлов */</code></p>
     <p><code>174 #define DIR_MODE (FILE_MODE | S_IXUSR | S_IXGRP | S_IXOTH)</code></p>
     <p><code>175 /* разрешения по умолчанию на доступ к файлам для новых каталогов */</code></p>
     <empty-line/>
     <p><code>176 typedef void Sigfunc(int); /* для обработчиков сигналов */</code></p>
     <empty-line/>
     <p><code>177 #define min(a, b) ((а) &lt; (b) ? (a) : (b))</code></p>
     <p><code>178 #define max(a, b) ((a) &gt; (b) ? (a) : (b))</code></p>
     <empty-line/>
     <p><code>179 #ifndef HAVE_ADDRINFO_STRUCT</code></p>
     <p><code>180 #include "../lib/addrinfo.h"</code></p>
     <p><code>181 #endif</code></p>
     <empty-line/>
     <p><code>182 #ifndef HAVE_IF_NAMEINDEX_STRUCT</code></p>
     <p><code>183 struct if_nameindex {</code></p>
     <p><code>184  unsigned int if_index; /* 1, 2, ... */</code></p>
     <p><code>185  char *if_name; /* имя, заканчивающееся нулем: "le0", ... */</code></p>
     <p><code>186 };</code></p>
     <p><code>187 #endif</code></p>
     <empty-line/>
     <p><code>188 #ifndef HAVE_TIMESPEC_STRUCT</code></p>
     <p><code>189 struct timespec {</code></p>
     <p><code>190  time_t tv_sec; /* секунды */</code></p>
     <p><code>191  long tv_nsec;  /* и наносекунды */</code></p>
     <p><code>192 };</code></p>
     <p><code>193 #endif</code></p>
    </section>
    <section>
     <title>
      <p>Г.2. Заголовочный файл config.h</p>
     </title>
     <p>Для обеспечения переносимости всего исходного кода, используемого в тексте книги, применялась утилита GNU <code>autoconf</code>. Ее можно загрузить по адресу <code>http://ftp.gnu.org/gnu/autoconf</code>. Эта программа генерирует сценарий интерпретатора с названием configure, который надо запустить после загрузки программного обеспечения в свою систему. Этот сценарий определяет, какие свойства обеспечивает ваша система Unix: имеется ли в структуре адреса сокета поле длины, поддерживается ли многоадресная передача, поддерживаются ли структуры адреса сокета канального уровня, и т.д. В результате получается файл с названием <code>config.h</code>. Этот файл — первый заголовочный файл, включенный в <code>unp.h</code> (см. предыдущий раздел). В листинге Г.2 показан заголовочный файл <code>config.h</code> для BSD/OS 3.0.</p>
     <p>Строки, начинающиеся с <code>#define</code>, относятся к тем свойствам, которые обеспечены данной системой. Закомментированные строки и строки, начинающиеся с <code>#undef</code>, относятся к свойствам, данной системой не поддерживаемым.</p>
     <p><strong>Листинг Г.2</strong>. Заголовочный файл config.h для BSD/OS</p>
     <p><code>i386-pc-bsdi3.0/config.h</code></p>
     <p><code> 1 /* config.h. Автоматически генерируется сценарием configure. */</code></p>
     <p><code> 2 /* Определяем константы, если имеется соответствующий заголовочный файл */</code></p>
     <p><code> 3 #define CPU_VENDOR_OS "i386-pc-bsdi3.0"</code></p>
     <p><code> 4 /* #undef HAVE_NETCONFIG_H */ /* &lt;netconfig.h&gt; */</code></p>
     <p><code> 5 /* #undef HAVE_NETDIR_H */    /* &lt;netdir.h&gt; */</code></p>
     <p><code> 6 #define HAVE_PTHREAD_H 1      /* &lt;pthread.h&gt; */</code></p>
     <p><code> 7 #define HAVE_STRINGS_H 1      /* &lt;strings.h&gt; */</code></p>
     <p><code> 8 /* #undef HAVE_XTI_INET_H */  /* &lt;xti_inet.h&gt; */</code></p>
     <p><code> 9 #define HAVE_SYS_FILIO_H 1    /* &lt;sys/filio.h&gt; */</code></p>
     <p><code>10 #define HAVE_SYS_IOCTL_H 1    /* &lt;sys/ioctl.h&gt; */</code></p>
     <p><code>11 #define HAVE_SYS_SELECT_H 1   /* &lt;sys/select.h&gt; */</code></p>
     <p><code>12 #define HAVE_SYS_SOCKIO_H 1   /* &lt;sys/sockio.h&gt; */</code></p>
     <p><code>13 #define HAVE_SYS_SYSCTL_H 1   /* &lt;sys/sysctl.h&gt; */</code></p>
     <p><code>14 #define HAVE_SYS_TIME_H 1     /* &lt;sys/time.h&gt; */</code></p>
     <empty-line/>
     <p><code>15 /* Определена, если можно подключить &lt;time.h&gt; и &lt;sys/time.h&gt; */</code></p>
     <p><code>16 #define TIME_WITH_SYS_TIME 1</code></p>
     <empty-line/>
     <p><code>17 /* Определены, если имеются соответствующие функции */</code></p>
     <p><code>18 #define HAVE_BZERO 1</code></p>
     <p><code>19 #define HAVE_GETHOSTBYNAME2 1</code></p>
     <p><code>20 /* #undef HAVE_PSELECT */</code></p>
     <p><code>21 #define HAVE_VSNPRINTF 1</code></p>
     <empty-line/>
     <p><code>22 /* Определены, если прототипы функций есть в заголовочном файле */</code></p>
     <p><code>23 /* #undef HAVE_GETADDRINFO_PROTO */    /* &lt;netdb.h&gt; */</code></p>
     <p><code>24 /* #undef HAVE_GETNAMEINFO_PROTO */    /* &lt;netdb.h&gt; */</code></p>
     <p><code>25 #define HAVE_GETHOSTNAME_PROTO 1       /* &lt;unistd.h&gt; */</code></p>
     <p><code>26 #define HAVE_GETRUSAGE_PROTO 1         /* &lt;sys/resource.h&gt; */</code></p>
     <p><code>27 #define HAVE_HSTRERROR_PROTO 1         /* &lt;netdb.h&gt; */</code></p>
     <p><code>28 /* #undef HAVE_IF_NAMETOINDEX_PROTO */ /* &lt;net/if.h&gt; */</code></p>
     <p><code>29 #define HAVE_INET_ATON_PROTO 1         /* &lt;arpa/inet.h&gt; */</code></p>
     <p><code>30 #define HAVE_INET_PTON_PROTO 1         /* &lt;arpa/inet.h&gt; */</code></p>
     <p><code>31 /* #undef HAVE_ISFDTYPE_PROTO */       /* &lt;sys/stat.h&gt; */</code></p>
     <p><code>32 /* #undef HAVE_PSELECT_PROTO */        /* &lt;sys/select.h&gt; */</code></p>
     <p><code>33 #define HAVE_SNPRINTF_PROTO 1          /* &lt;stdio.h&gt; */</code></p>
     <p><code>34 /* #undef HAVE_SOCKATMARK_PROTO */     /* &lt;sys/socket.h&gt; */</code></p>
     <empty-line/>
     <p><code>35 /* Определены, если определены соответствующие структуры */</code></p>
     <p><code>36 /* #undef HAVE_ADDRINFO_STRUCT */     /* &lt;netdb.h&gt; */</code></p>
     <p><code>37 /* #undef HAVE_IF_NAMEINDEX_STRUCT */ /* &lt;net/if.h&gt; */</code></p>
     <p><code>38 #define HAVE_SOCKADDR_DL_STRUCT 1     /* &lt;net/if_dl.h&gt; */</code></p>
     <p><code>39 #define HAVE TIMESPEC STRUCT 1        /* &lt;time.h&gt; */</code></p>
     <empty-line/>
     <p><code>40 /* Определены, если имеется указанное свойство */</code></p>
     <p><code>41 #define HAVE_SOCKADDR_SA_LEN 1    /* в sockaddr{} есть поле sa_len */</code></p>
     <p><code>42 #define HAVE_MSGHDR_MSG_CONTROL 1 /* в msghdr{} есть поле msg_control */</code></p>
     <empty-line/>
     <p><code>43 /* Имена устройств XTI для TCP и UDP */</code></p>
     <p><code>44 /* #undef HAVE_DEV_TCP */               /* большинство здесь */</code></p>
     <p><code>45 /* #undef HAVE_DEV_XTI_TCP */           /* для AIX */</code></p>
     <p><code>46 /* #undef HAVE_DEV_STREAMS_XTISO_TCP */ /* для OSF 3.2 */</code></p>
     <empty-line/>
     <p><code>47 /* При необходимости определяем типы данных */</code></p>
     <p><code>48 /* #undef int8_t */             /* &lt;sys/types.h&gt; */</code></p>
     <p><code>49 /* #undef int16_t */            /* &lt;sys/types.h&gt; */</code></p>
     <p><code>50 /* #undef int32_t */            /* &lt;sys/types.h&gt; */</code></p>
     <p><code>51 #define uint8_t unsigned char   /* &lt;sys/types.h&gt; */</code></p>
     <p><code>52 #define uint16_t unsigned short /* &lt;sys/types.h&gt; */</code></p>
     <p><code>53 #define uint32_t unsigned int   /* &lt;sys/types.h&gt; */</code></p>
     <p><code>54 /* #undef size_t */             /* &lt;sys/types.h&gt; */</code></p>
     <p><code>55 /* #undef ssize_t */            /* &lt;sys/types.h&gt; */</code></p>
     <p><code>56 /* socklen_t должен иметь тип uint32_t, но configure определяет его</code></p>
     <p><code>57    как unsigned int. т. к. это значение используется в начале компиляции.</code></p>
     <p><code>58    иногда до того, как в данной реализации определяется тип uint32_t */</code></p>
     <p><code>59 #define socklen_t unsigned int  /* &lt;sys/socket.h&gt; */</code></p>
     <p><code>60 #define sa_family_t SA_FAMILY_T /* &lt;sys/socket.h&gt; */</code></p>
     <p><code>61 #define SA_FAMILY_T uint8_t</code></p>
     <empty-line/>
     <p><code>62 #define t_scalar_t int32_t /* &lt;xti.h&gt; */</code></p>
     <p><code>63 #define t_uscalar_t uint32_t /* &lt;xti.h&gt; */</code></p>
     <empty-line/>
     <p><code>64 /* Определены, если система поддерживает указанное свойство */</code></p>
     <p><code>65 #define IPV4 1       /* IPv4, V в верхнем регистре */</code></p>
     <p><code>66 #define IPv4 1       /* IPv4, v в нижнем регистре, на всякий случай */</code></p>
     <p><code>67 /* #undef IPV6 */    /* IPv6, V в верхнем регистре */</code></p>
     <p><code>68 /* #undef IPv6 */    /* IPv6, v в нижнем регистре, на всякий случай */</code></p>
     <p><code>69 #define UNIXDOMAIN 1 /* доменные сокеты Unix */</code></p>
     <p><code>70 #define UNIXdomain 1 /* доменные сокеты Unix */</code></p>
     <p><code>71 #define MCAST 1      /* поддержка многоадресной передачи */</code></p>
    </section>
    <section>
     <title>
      <p>Г.3. Стандартные функции обработки ошибок</p>
     </title>
     <p>В этой книге мы определяем набор своих собственных функций для обработки ошибок. Причина, по который мы создаем эти функции, заключается в том, что они позволяют нам обрабатывать ошибки с помощью одной строки кода, как, например, показано ниже:</p>
     <p><code>if (<emphasis>условие ошибки</emphasis>)</code></p>
     <p><code> err_sys(<emphasis>формат printf с любым количеством аргументов</emphasis>);</code></p>
     <p>вместо</p>
     <p><code>if (<emphasis>условие ошибки</emphasis>) {</code></p>
     <p><code> char buff[200];</code></p>
     <p><code> snprintf(buff, sizeof(buff), <emphasis>формат printf с любым количеством аргументов</emphasis>);</code></p>
     <p><code> perror(buff);</code></p>
     <p><code> exit(1);</code></p>
     <p><code>}</code></p>
     <p>Наши функции обработки ошибок используют следующую возможность ANSI С: список аргументов может иметь переменную длину. Более подробную информацию об этом вы найдете в разделе 7.3 книги [68].</p>
     <p>В табл. Г.1 показано, в чем заключаются различия между функциями обработки ошибок. Если глобальная целочисленная переменная <code>daemon_proc</code> отлична от нуля, то сообщение об ошибке передается функции <code>syslog</code> с указанным уровнем, в противном случае оно отправляется в стандартный поток вывода сообщений об ошибках.</p>
     <empty-line/>
     <p><strong>Таблица Г.1</strong>. Стандартные функции обработки ошибок</p>
     <table>
      <tr align="left">
       <th align="left" valign="top">Функция</th>
       <th align="left" valign="top">strerror (errno ?)</th>
       <th align="left" valign="top">Завершение ?</th>
       <th align="left" valign="top">Уровень syslog</th>
      </tr>
      <tr align="left">
       <td align="left" valign="top">err_dump</td>
       <td align="left" valign="top">Да</td>
       <td align="left" valign="top">abort();</td>
       <td align="left" valign="top">LOG_ERR</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">err_msg</td>
       <td align="left" valign="top">Нет</td>
       <td align="left" valign="top">return;</td>
       <td align="left" valign="top">LOG_INFO</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">err_quit</td>
       <td align="left" valign="top">Нет</td>
       <td align="left" valign="top">exit(1);</td>
       <td align="left" valign="top">LOG_ERR</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">err_ret</td>
       <td align="left" valign="top">Да</td>
       <td align="left" valign="top">return;</td>
       <td align="left" valign="top">LOG_INFO</td>
      </tr>
      <tr align="left">
       <td align="left" valign="top">err_sys</td>
       <td align="left" valign="top">Да</td>
       <td align="left" valign="top">exit(1);</td>
       <td align="left" valign="top">LOG_ERR</td>
      </tr>
     </table>
     <p>В листинге Г.3 показаны первые пять функций из табл. Г.1.</p>
     <p><strong>Листинг Г.3</strong>. Стандартные функции обработки ошибок</p>
     <p><code>//lib/error.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 #include &lt;stdarg.h&gt; /* заголовочный файл ANSI С */</code></p>
     <p><code> 3 #include &lt;syslog.h&gt; /* для syslog() */</code></p>
     <empty-line/>
     <p><code> 4 int daemon_proc; /* устанавливается в ненулевое значение с</code></p>
     <p><code>                       помощью daemon_init() */</code></p>
     <empty-line/>
     <p><code> 5 static void err_doit(int, int, const char*, va_list);</code></p>
     <empty-line/>
     <p><code> 6 /* Нефатальная ошибка, связанная с системным вызовом.</code></p>
     <p><code> 7    Выводим сообщение и возвращаем управление */</code></p>
     <empty-line/>
     <p><code> 8 void</code></p>
     <p><code> 9 err_ret(const char *fmt , ...)</code></p>
     <p><code>10 {</code></p>
     <p><code>11  va_list ap;</code></p>
     <empty-line/>
     <p><code>12  va_start(ap, fmt);</code></p>
     <p><code>13  err_doit(1, LOG_INFO, fmt, ap);</code></p>
     <p><code>14  va_end(ap);</code></p>
     <p><code>15  return;</code></p>
     <p><code>16 }</code></p>
     <empty-line/>
     <p><code>17 /* Фатальная ошибка, связанная с системным вызовом.</code></p>
     <p><code>18    Выводим сообщение и завершаем работу */</code></p>
     <empty-line/>
     <p><code>19 void</code></p>
     <p><code>20 err_sys(const char *fmt)</code></p>
     <p><code>21 {</code></p>
     <p><code>22  va_list ap;</code></p>
     <p><code>23  va_start(ap, fmt);</code></p>
     <p><code>24  err_doit(1, LOG_ERR, fmt, ap);</code></p>
     <p><code>25  va_end(ap);</code></p>
     <p><code>26  exit(1);</code></p>
     <p><code>27 }</code></p>
     <empty-line/>
     <p><code>28 /* Фатальная ошибка, связанная с системным вызовом.</code></p>
     <p><code>29    Выводим сообщение, сохраняем дамп памяти процесса и заканчиваем работу */</code></p>
     <empty-line/>
     <p><code>30 void</code></p>
     <p><code>31 err_dump(const char *fmt, ... )</code></p>
     <p><code>32 {</code></p>
     <p><code>33  va_list ар;</code></p>
     <empty-line/>
     <p><code>34  va_start(ap, fmt);</code></p>
     <p><code>35  err_doit(1, LOG_ERR, fmt, ap);</code></p>
     <p><code>36  va_end(ap);</code></p>
     <p><code>37  abort(); /* сохраняем дамп памяти и заканчиваем работу */</code></p>
     <p><code>38  exit(1);</code></p>
     <p><code>39 }</code></p>
     <empty-line/>
     <p><code>40 /* Нефатальная ошибка, не относящаяся к системному вызову.</code></p>
     <p><code>41    Выводим сообщение и возвращаем управление */</code></p>
     <empty-line/>
     <p><code>42 void</code></p>
     <p><code>43 err_msg(const char *fmt , ...)</code></p>
     <p><code>44 {</code></p>
     <p><code>45  va_list ap;</code></p>
     <empty-line/>
     <p><code>46  va_start(ap, fmt);</code></p>
     <p><code>47  err_doit(0, LOG_INFO, fmt, ap);</code></p>
     <p><code>48  va_end(ap);</code></p>
     <p><code>49  return;</code></p>
     <p><code>50 }</code></p>
     <empty-line/>
     <p><code>51 /* Фатальная ошибка, не относящаяся к системному вызову.</code></p>
     <p><code>52    Выводим сообщение и заканчиваем работу. */</code></p>
     <empty-line/>
     <p><code>53 void</code></p>
     <p><code>54 err_quit(const char *fmt, ...)</code></p>
     <p><code>55 {</code></p>
     <p><code>56  va_list ap;</code></p>
     <empty-line/>
     <p><code>57  va_start(ap, fmt);</code></p>
     <p><code>58  err_doit(0, LOG_ERR, fmt, ap);</code></p>
     <p><code>59  va_end(ap);</code></p>
     <p><code>60  exit(1);</code></p>
     <p><code>61 }</code></p>
     <empty-line/>
     <p><code>62 /* Выводим сообщение и возвращаем управление.</code></p>
     <p><code>63    Вызывающий процесс задает "errnoflag" и "level" */</code></p>
     <empty-line/>
     <p><code>64 static void</code></p>
     <p><code>65 err_doit(int errnoflag, int level, const char *fmt, va_list ap)</code></p>
     <p><code>66 {</code></p>
     <p><code>67  int errno_save, n;</code></p>
     <p><code>68  char buf[MAXLINE + 1];</code></p>
     <empty-line/>
     <p><code>69  errno_save = errno; /* значение может понадобиться вызвавшему</code></p>
     <p><code>                           процессу */</code></p>
     <p><code>70 #ifdef HAVE_VSNPRINTF</code></p>
     <p><code>71  vsnprintf(buf, MAXLINE, fmt, ap); /* защищенный вариант */</code></p>
     <p><code>72 #else</code></p>
     <p><code>73  vsprintf(buf, fmt, ap); /* незащищенный вариант */</code></p>
     <p><code>74 #endif</code></p>
     <p><code>75  n = strlen(buf);</code></p>
     <p><code>76  if (errnoflag)</code></p>
     <p><code>77   snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save));</code></p>
     <p><code>78  strcat(buf, "\n");</code></p>
     <empty-line/>
     <p><code>79  if (daemon_proc) {</code></p>
     <p><code>80   syslog(level, buf);</code></p>
     <p><code>81  } else {</code></p>
     <p><code>82   fflush(stdout); /* если stdout и stderr совпадают */</code></p>
     <p><code>83   fputs(buf, stderr);</code></p>
     <p><code>84   fflush(stderr);</code></p>
     <p><code>85  }</code></p>
     <p><code>86  return;</code></p>
     <p><code>87 }</code></p>
    </section>
   </section>
   <section>
    <title>
     <p>Приложение Д</p>
     <p>Решения некоторых упражнений</p>
    </title>
    <section>
     <title>
      <p>Глава 1</p>
     </title>
     <p>1.3. В операционной системе Solaris получаем:</p>
     <p><code>solaris % <strong>daytimetcpcli 127.0.0.1</strong></code></p>
     <p><code>socket error: Protocol not supported</code></p>
     <p>Для получения дополнительной информации об этой ошибке сначала используем программу <code>grep</code>, чтобы найти строку <code>Protocol not supported</code> в заголовочном файле <code>&lt;sys/errno.h&gt;</code>.</p>
     <p><code>solaris % <strong>grep 'Protocol not supported' /usr/include/sys/errno.h</strong></code></p>
     <p><code>#define EPROTONOSUPPORT 120 /* Protocol not supported */</code></p>
     <p>Это значение <code>errno</code> возвращается функцией <code>socket</code>. Далее смотрим в руководство пользователя:</p>
     <p><code>solaris % <strong>man socket</strong></code></p>
     <p>В большинстве руководств пользователя в конце под заголовком «Errors» приводится дополнительная, хотя и лаконичная информация об ошибках.</p>
     <p>1.4. Заменяем первое описание на следующее:</p>
     <p><code>int sockfd, n, counter = 0;</code></p>
     <p>Добавляем оператор</p>
     <p><code>counter++;</code></p>
     <p>в качестве первого оператора цикла <code>while</code>. Наконец, прежде чем прервать программу, выполняем</p>
     <p><code>printf("counter = %d\n", counter);</code></p>
     <p>На экран всегда выводится значение 1.</p>
     <p>1.5. Объявим переменную i типа int и заменим вызов функции <code>write</code> на следующий:</p>
     <p><code>for (i = 0; i &lt; strlen(buff); i++)</code></p>
     <p><code> Write(connfd, &amp;buff[i], 1);</code></p>
     <p>Результат зависит от расположения клиентского узла и узла сервера. Если клиент и сервер находятся на одном узле, счетчик обычно равен 1. Это значит, что даже если сервер выполнит функцию <code>write</code> 26 раз, данные будут возвращены за одну операцию считывания (<code>read</code>). Но если клиент запущен в Solaris 2.5.1, а сервер в BSD/OS 3.0, счетчик обычно равен 2. Просмотрев пакеты Ethernet, мы увидим, что первый символ отправляется в первом пакете сам по себе, а следующий пакет содержит остальные 25 символов. (Обсуждение алгоритма Нагла в разделе 7.9 объясняет причину такого поведения.)</p>
     <p>Цель этого примера — продемонстрировать, что разные реализации TCP по-разному поступают с данными, поэтому наше приложение должно быть готово считывать данные как поток байтов, пока не будет достигнут конец потока.</p>
    </section>
    <section>
     <title>
      <p>Глава 2</p>
     </title>
     <p>2.1 Зайдите на веб-страницу <code>http://www.iana.org/numbers.htm</code> и найдите журнал под названием «IP Version Number». Номер версии 0 зарезервирован, версии 1-3 не использовались, а версия 5 представляет собой потоковый протокол Интернета (Internet Stream Protocol).</p>
     <p>2.2. Все RFC бесплатно доступны по электронной почте, через FTP или Web. Стартовая страница для поиска находится по адресу <code>http://www.ietf.org</code>. Одним из мест расположения RFC является каталог <code>ftp://ftp.isi.edu/in-notes</code>. Для начала следует получить файл с текущим каталогом RFC, обычно это файл <code>rfc-index.txt</code>. HTML-версия хранится в файле <code>http://www.rfc-editor.org/rfc-index.html</code>. Если с помощью какого-либо редактора осуществить поиск термина «stream» (поток) в указателе RFC, мы выясним, что RFC 1819 определяет версию 2 потокового протокола Интернета. Какую бы информацию, которая может содержаться в RFC, мы ни искали, для поиска следует использовать указатель (каталог) RFC.</p>
     <p>2.3. В версии IPv4 при таком значении MSS генерируется 576-байтовая дейтаграмма (20 байт для заголовка IPv4 и 20 байт для заголовка TCP). Это минимальный размер буфера для сборки фрагментов в Ipv4.</p>
     <p>2.4. В данном примере сервер (а не клиент) осуществляет активное закрытие.</p>
     <p>2.5. Узел в сети Token Ring не может посылать пакет, содержащий больше, чем 1460 байт данных, поскольку полученное им значение MSS равно 1460. Узел в сети Ethernet может посылать пакет размером до 4096 байт данных, но не превышающий величину MTU исходящего интерфейса (Ethernet) во избежание фрагментации. Протокол TCP не может превысить величину MSS, объявленную другой стороной, но он всегда может посылать пакеты меньшего размера.</p>
     <p>2.6. В разделе «Protocol Numbers» (номера протоколов) RFC «Assigned Numbers» («Присвоенные номера») указано значение 89 для протокола OSPF.</p>
     <p>2.7. Выборочное уведомление указывает лишь на получение пакетов с конкретными последовательными номерами. Кумулятивное уведомление сообщает о получении данных вплоть до конкретного порядкового номера (включительно). При освобождении буфера отправки в соответствии с выборочным уведомлением система может удалять только те данные, доставка которых была подтверждена явно, но не те, номера которых меньше или больше подтвержденных.</p>
    </section>
    <section>
     <title>
      <p>Глава 3</p>
     </title>
     <p>3.1. В языке С функция не может изменить значение аргумента, передаваемого по значению. Чтобы вызванная функция изменила значение, передаваемое вызывающим процессом, требуется, чтобы вызывающий процесс передал указатель на значение, подлежащее изменению.</p>
     <p>3.2. Указатель должен увеличиваться на количество считанных или записанных байтов, но в языке С нет возможности увеличивать указатели типа <code>void</code> (поскольку компилятору не известно, на какой тип данных указывает указатель).</p>
    </section>
    <section>
     <title>
      <p>Глава 4</p>
     </title>
     <p>4.1. Посмотрите на определение констант, начинающихся с <code>INADDR_</code>, кроме <code>INADDR_ANY</code> (состоит из нулевых битов) и <code>INADDR_NONE</code> (состоит из единичных битов). Например, адрес многоадресной передачи класса D <code>INADDR_MAX_LOCAL_GROUP</code> определяется как <code>0xe00000ff</code> с комментарием «224.0.0.255», что явно указывает на порядок байтов узла.</p>
     <p>4.2. Приведем новые строки, добавленные после вызова <code>connect</code>:</p>
     <p><code>len = sizeof(cliaddr);</code></p>
     <p><code>Getsockname(sockfd, (SA*)&amp;cliaddr, &amp;len);</code></p>
     <p><code>printf("local addr: %s\n",</code></p>
     <p><code>Sock_ntop((SA*)&amp;cliaddr, len));</code></p>
     <p>Это требует описания переменной <code>len</code> как <code>socklen_t</code>, a <code>cliaddr</code> как структуры <code>struct sockaddr_in</code>. Обратите внимание, что аргумент типа «значение-результат» для функции <code>getsockname(len)</code> должен быть до вызова функции инициализирован размером переменной, на которую указывает второй аргумент. Наиболее частая ошибка программирования при использовании аргументов типа «значение-результат» заключается в том, что про эту инициализацию забывают.</p>
     <p>4.3. Когда дочерний процесс вызывает функцию <code>close</code>, счетчик ссылок уменьшается с 2 до 1, так что клиенту не посылается сегмент FIN. Позже, когда родительский процесс вызывает функцию <code>close</code>, счетчик ссылок уменьшается до нуля, и тогда сегмент FIN посылается.</p>
     <p>4.4. Функция <code>accept</code> возвращает значение <code>EINVAL</code>, так как первый аргумент не является прослушиваемым сокетом.</p>
     <p>4.5. Вызов функции <code>listen</code> без вызова функции <code>bind</code> присваивает прослушиваемому сокету динамически назначаемый порт.</p>
    </section>
    <section>
     <title>
      <p>Глава 5</p>
     </title>
     <p>5.1. Длительность состояния TIME_WAIT должна находиться в интервале между 1 и 4 минутами, что дает величину MSL от 30 с до 2 мин.</p>
     <p>5.2. Наши клиент-серверные программы не работают с двоичными файлами. Допустим, что первые 3 байта в файле являются двоичной единицей (1), двоичным нулем (0) и символом новой строки. При вызове функции <code>fgets</code> в листинге 5.4 либо считывается <code>MAXLINE</code> - 1 символов, либо считываются символы до символа новой строки или до конца файла. В данном примере функция считает три символа, а затем прервет строку нулевым байтом. Но вызов функции <code>strlen</code> в листинге 5.4 возвращает значение 1, так как она остановится на первом нулевом байте. Один байт посылается серверу, но сервер блокируется в своем вызове функции <code>readline</code>, ожидая символа новой строки. Клиент блокируется, ожидая ответа от сервера. Такое состояние называется <emphasis>зависанием</emphasis>, или <emphasis>взаимной блокировкой</emphasis>: оба процесса блокированы и при этом каждый ждет от другого некоторого действия, которое никогда не произойдет. Проблема заключается в том, что функция <code>fgets</code> обозначает нулевым байтом конец возвращаемых ею данных, поэтому данные, которые она считывает, не должны содержать нулевой байт.</p>
     <p>5.3. Программа <code>Telnet</code> преобразует входные строки в NVT ASCII (см. раздел 26.4 книги [111]), что означает прерывание каждой строки 2-символьной последовательностью CR (carriage return — возврат каретки) и LF (linefeed — новая строка). Наш клиент добавляет только разделитель строк (newline), который в действительности является символом новой строки (linefeed, LF). Тем не менее можно использовать клиент Telnet для связи с нашим сервером, поскольку наш сервер отражает каждый символ, включая CR, предшествующий каждому разделителю строк.</p>
     <p>5.4. Нет, последние два сегмента из последовательности завершения соединения не посылаются. Когда клиент посылает серверу данные после уничтожения дочернего процесса сервера (ввод строки <code>another line</code>, см. раздел 5.12), сервер TCP отвечает сегментом RST. Сегмент RST прекращает соединение, а также предотвращает переход в состояние TIME_WAIT на стороне сервера (конец соединения, осуществивший активное закрытие).</p>
     <p>5.5. Ничего не меняется, потому что процесс, запущенный на узле сервера, создает прослушиваемый сокет и ждет прибытия запросов на соединение. На третьем шаге мы посылаем сегмент данных, предназначенный для установленного соединения TCP (состояние ESTABLISHED). Наш сервер с прослушиваемым сокетом не увидит этот сегмент данных, и TCP сервера по-прежнему будет посылать клиенту сегмент RST.</p>
     <p>5.6. В листинге Д.1<a l:href="#n1" type="note">[1]</a> приведена программа. Запуск этой программы в Soalris генерирует следующий вывод:</p>
     <p><code>solaris % <strong>tsigpipe 192.168.1.10</strong></code></p>
     <p><code>SIGPIPE received</code></p>
     <p><code>write error: Broken pipe</code></p>
     <p>Начальный вызов функции <code>sleep</code> и переход в режим ожидания на 2 с нужен, чтобы сервер времени и даты отправил ответ и закрыл свой конец соединения. Первая функция <code>write</code> отправляет сегмент данных серверу, который отвечает сегментом RST (поскольку сервер времени и даты полностью закрыл свой сокет). Обратите внимание, что наш TCP позволяет писать в сокет, получивший сегмент FIN. Второй вызов функции <code>sleep</code> позволяет получить от сервера сегмент RST, а во втором вызове функции <code>write</code> генерируется сигнал <code>SIGPIPE</code>. Поскольку наш обработчик сигналов возвращает управление, функция write возвращает ошибку <code>EPIPE</code>.</p>
     <p><strong>Листинг Д.1</strong>. Генерация SIGPIPE</p>
     <p><code>//tcpcliserv/tsigpipe.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 void</code></p>
     <p><code> 3 sig_pipe(int signo)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  printf("SIGPIPE received\n");</code></p>
     <p><code> 6  return;</code></p>
     <p><code> 7 }</code></p>
     <empty-line/>
     <p><code> 8 int</code></p>
     <p><code> 9 main(int argc, char **argv)</code></p>
     <p><code>10 {</code></p>
     <p><code>11  int sockfd;</code></p>
     <p><code>12  struct sockaddr_in servaddr;</code></p>
     <empty-line/>
     <p><code>13  if (argc != 2)</code></p>
     <p><code>14   err_quit("usage: tcpcli &lt;Ipaddress&gt;");</code></p>
     <empty-line/>
     <p><code>15  sockfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code>16  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>17  servaddr.sin_family = AF_INET;</code></p>
     <p><code>18  servaddr.sin_port = htons(13); /* сервер времени и даты */</code></p>
     <p><code>19  Inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr);</code></p>
     <empty-line/>
     <p><code>20  Signal(SIGPIPE, sig_pipe);</code></p>
     <empty-line/>
     <p><code>21  Connect(sockfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>22  sleep(2);</code></p>
     <p><code>23  Write(sockfd, "hello", 5);</code></p>
     <p><code>24  sleep(2);</code></p>
     <p><code>25  Write(sockfd, "world", 5);</code></p>
     <empty-line/>
     <p><code>26  exit(0);</code></p>
     <p><code>27 }</code></p>
     <p>5.7. В предположении, что узел сервера поддерживает <emphasis>модель системы с гибкой привязкой</emphasis> (см. раздел 8.8), все будет работать. Узел сервера примет IP-дейтаграмму (которая в данном случае содержит TCP-сегмент), прибывшую на самый левый канал, даже если IP-адрес получателя является адресом самого правого канала. Это можно проверить, если запустить наш сервер на узле <code>linux</code> (см. рис. 1.7), а затем запустить клиент на узле <code>solaris</code>, но на стороне клиента задать другой IP-адрес сервера (206.168.112.96). После установления соединения, запустив на стороне сервера программу <code>netstat</code>, мы увидим, что локальный IP-адрес является IP-адресом получателя из клиентского сегмента SYN, а не IP-адресом канала, на который прибыл сегмент SYN (как отмечалось в разделе 4.4).</p>
     <p>5.8. Наш клиент был запущен в системе Intel с прямым порядком байтов, где 32-разрядное целое со значением 1 хранится так, как показано на рис. Д.1.</p>
     <image l:href="#img_185.png"/>
     <p><strong>Рис. Д.1</strong>. Представление 32-разрядного целого числа 1 в формате прямого порядка байтов</p>
     <p>Четыре байта посылаются на сокет в следующем порядке: A, A + 1, A + 2 и A + 3, и там хранятся в формате обратного порядка байтов, как показано на рис. Д.2.</p>
     <image l:href="#img_186.png"/>
     <p><strong>Рис. Д.2</strong>. Представление 32-разрядного целого числа с рис. Д.1 в формате обратного порядка байтов</p>
     <p>Значение <code>0x01000000</code> интерпретируется как 16 777 216. Аналогично, целое число 2, отправленное клиентом, интерпретируется сервером как <code>0x02000000</code>, или 33 554 432. Сумма этих двух целых чисел равна 50 331 648, или <code>0x03000000</code>. Когда это значение, записанное в обратном порядке байтов, отправляется клиенту, оно интерпретируется клиентом как целое число 3.</p>
     <p>Но 32-разрядное целое число -22 представляется в системе с прямым порядком байтов так, как показано на рис. Д.3 (мы предполагаем, что используется поразрядное дополнение до двух для отрицательных чисел).</p>
     <image l:href="#img_187.png"/>
     <p><strong>Рис. Д.3</strong>. Представление 32-разрядного целого числа -22 в формате прямого порядка байтов</p>
     <p>В системе с обратным порядком байтов это значение интерпретируется как <code>0xeaffffff</code>, или -352 521 537. Аналогично, представление числа -77 в прямом порядке байтов выглядит как <code>0xffffffb3</code>, но в системах с обратным порядком оно представляется как <code>0xb3ffffff</code>, или -1 275 068 417. Сложение, выполняемое сервером, приводит к результату <code>0x9efffffe</code>, или -1 627 389 954. Полученное значение в обратном порядке байтов посылается через сокет клиенту, где в прямом порядке байтов оно интерпретируется как <code>0xfeffff9e</code>, или -16 777 314 — это то значение, которое выводится в нашем примере.</p>
     <p>5.9. Метод правильный (преобразование двоичных значений в сетевой порядок байтов), но нельзя использовать функции <code>htonl</code> и <code>ntohl</code>. Хотя символ <code>l</code> в названиях данных функций обозначает «long», эти функции работают с 32-разрядными целыми (раздел 3.4). В 64-разрядных системах <code>long</code> занимает 64 бита, и эти две функции работают некорректно. Для решения этой проблемы следует определить две новые функции <code>hton64</code> и <code>ntoh64</code>, но они не будут работать в системах, представляющих значения типа <code>long</code> 32 битами.</p>
     <p>5.10. В первом сценарии сервер будет навсегда блокирован при вызове функции <code>readn</code> в листинге 5.14, поскольку клиент посылает два 32-разрядных значения, а сервер ждет два 64-разрядных значения. В случае, если клиент и сервер поменяются узлами, клиент будет посылать два 64-разрядных значения, а сервер считает только первые 64 бита, интерпретируя их как два 32-разрядных значения. Второе 64-разрядное значение останется в приемном буфере сокета сервера. Сервер отправит обратно 32-разрядное значение, и клиент навсегда заблокируется в вызове функции <code>readn</code> в листинге 5.13, поскольку будет ждать для считывания 64-разрядное значение.</p>
     <p>5.11. Функция IP-маршрутизации просматривает IP-адрес получателя (IP-адрес сервера) и пытается по таблице маршрутизации определить исходящий интерфейс и следующий маршрутизатор (см. главу 9 [111]). В качестве адреса отправителя используется первичный IP-адрес исходящего интерфейса, если сокет еще не связан с локальным IP-адресом.</p>
    </section>
    <section>
     <title>
      <p>Глава 6</p>
     </title>
     <p>6.1. Массив целых чисел содержится внутри структуры, а язык С позволяет использовать со структурами оператор присваивания.</p>
     <p>6.2. Если функция <code>select</code> сообщает, что сокет готов к записи, причем буфер отправки сокета вмещает 8192 байта, а мы вызываем для этого блокируемого сокета функцию <code>write</code> с буфером размером 8193 байта, то функция <code>write</code> может заблокироваться, ожидая места для последнего байта. Операции считывания на блокируемом сокете будут возвращать сообщение о неполном считывании, если доступны какие-либо данные, но операции записи на блокируемом сокете заблокированы до принятия всех данных ядром. Поэтому, чтобы избежать блокирования при использовании функции <code>select</code> для проверки на возможность записи, следует переводить сокет в неблокируемый режим.</p>
     <p>6.3. Если оба дескриптора готовы для чтения, выполняется только первый тест — тест сокета. Но это не нарушает работоспособность клиента, а только лишь уменьшает его эффективность. Поэтому если при завершении функции <code>select</code> оба дескриптора готовы для чтения, первое условие <code>if</code> оказывается истинным, в результате чего сначала вызывается функция <code>readline</code> для считывания из сокета, а затем функция <code>fputs</code> для записи в стандартный поток вывода. Следующее условие <code>if</code> пропускается (поскольку мы добавили <code>else</code>), но функция <code>select</code> вызывается снова, сразу находит стандартное устройство ввода, готовое к чтению, и завершается. Суть в том, что условие готовности стандартного потока ввода для чтения сбрасывается считыванием из сокета, а не возвратом функции <code>select</code>.</p>
     <p>6.4. Воспользуйтесь функцией <code>getrlimit</code> для получения значений константы <code>RLIMIT_NOFILE</code>, а затем вызовите функцию <code>setrlimit</code> для установки текущего гибкого предела (<code>rlim_cur</code>) равным жесткому пределу (<code>rlim_max</code>). Например, в Solaris 2.5 гибкий предел равен 64, но любой процесс может увеличить это значение до используемого по умолчанию значения жесткого предела (1024).</p>
     <p>6.5. Серверное приложение непрерывно посылает данные клиенту, клиент TCP подтверждает их прием и сбрасывает.</p>
     <p>6.6. Функция <code>shutdown</code> с аргументами <code>SHUT_WR</code> и <code>SHUT_RDWR</code> всегда посылает сегмент FIN, в то время как функция <code>close</code> посылает сегмент FIN только если в момент вызова функции <code>close</code> счетчик ссылок дескриптора равен 1.</p>
     <p>6.7. Функция <code>readline</code> возвращает ошибку, и наша функция-обертка <code>Readline</code> завершает работу сервера. Но серверы должны справляться с такими ситуациями. Обратите внимание на то, как мы обрабатываем эти условия в листинге 6.6, хотя даже этот код не является удовлетворительным. Рассмотрим, что произойдет, если соединение между клиентом и сервером прервется и время ожидания одного из ответов сервера будет превышено. Возвращаемой ошибкой может быть ошибка <code>ETIMEDOUT</code>.</p>
     <p>Обычно сервер не должен прекращать свою работу из-за подобных ошибок. Он должен записать ее в файл журнала, закрыть сокет и продолжать обслуживание других клиентов. Следует понимать, что обработка таких ошибок путем прекращения работы сервера недопустима для серверов, у которых один процесс выполняет обработку всех клиентов. Но если сервер был дочерним процессом, обрабатывающим только один клиент, то прекращение работы одного дочернего процесса не отразится ни на родительском процессе (который, по нашему предположению, обрабатывает все новые соединения и порождает новые дочерние процессы), ни на одном из других дочерних процессов, обрабатывающих другие клиенты.</p>
    </section>
    <section>
     <title>
      <p>Глава 7</p>
     </title>
     <p>7.2. Решение упражнения приведено в листинге Д.2. Вывод строки данных, возвращаемых сервером, был удален, поскольку это значение нам не нужно.</p>
     <p><strong>Листинг Д.2</strong>. Вывод размера приемного буфера сокета и MSS до и после установления соединения</p>
     <p><code>//sockopt/rcvbuf.c</code></p>
     <p><code> 1 #include "urp.h"</code></p>
     <p><code> 2 #include &lt;netinet/tcp.h&gt; /* для TCP_MAXSEG */</code></p>
     <empty-line/>
     <p><code> 3 int</code></p>
     <p><code> 4 main(int argc, char **argv)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  int sockfd, rcvbuf, mss;</code></p>
     <p><code> 7  socklen_t len;</code></p>
     <p><code> 8  struct sockaddr_in servaddr;</code></p>
     <empty-line/>
     <p><code> 9  if (argc != 2)</code></p>
     <p><code>10   err_quit("usage: rcvbuf &lt;Ipaddress&gt;");</code></p>
     <empty-line/>
     <p><code>11  sockfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code>12  len = sizeof(rcvbuf);</code></p>
     <p><code>13  Getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &amp;rcvbuf, &amp;len);</code></p>
     <p><code>14  len = sizeof(mss);</code></p>
     <p><code>15  Getsockopt(sockfd, IPPROTO_TCP, TCP_MAXSEG, &amp;mss, &amp;len);</code></p>
     <p><code>16  printf("defaults: SO_RCVBUF = %d. MSS = %d\n", rcvbuf, mss);</code></p>
     <empty-line/>
     <p><code>17  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>18  servaddr.sin_family = AF_INET;</code></p>
     <p><code>19  servaddr.sin_port = htons(13); /* сервер времени и даты */</code></p>
     <p><code>20  Inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr);</code></p>
     <empty-line/>
     <p><code>21  Connect(sockfd, (SA*)&amp;servaddr, sizeof(servaddr));</code></p>
     <empty-line/>
     <p><code>22  len = sizeof(rcvbuf);</code></p>
     <p><code>23  Getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &amp;rcvbuf, &amp;len);</code></p>
     <p><code>24  len = sizeof(mss);</code></p>
     <p><code>25  Getsockopt(sockfd, IPPROTO_TCP, TCP_MAXSEG, &amp;mss, &amp;len);</code></p>
     <p><code>26  printf("after connect: SO_RCVBUF = %d, MSS = %d\n", rcvbuf, mss);</code></p>
     <empty-line/>
     <p><code>27  exit(0);</code></p>
     <p><code>28 }</code></p>
     <p>He существует какого-то одного «правильного» вывода для данной программы. Результаты зависят от системы. Некоторые системы (в особенности Solaris 2.5.1 и более ранние версии) всегда возвращают нулевой размер буфера сокета, не давая нам возможности увидеть, что происходит с этим значением в процессе соединения.</p>
     <p>До вызова функции <code>connect</code> выводится значение MSS по умолчанию (часто 536 или 512), а значение, выводимое после вызова функции connect, зависит от возможных параметров MSS, полученных от собеседника. Например, в локальной сети Ethernet после выполнения функции connect MSS может иметь значение 1460. Однако после соединения (<code>connect</code>) с сервером в удаленной сети значение MSS может быть равно значению по умолчанию, если только ваша система не поддерживает обнаружение транспортной MTU. Если это возможно, запустите во время работы вашей программы программу <code>tcpdump</code> или подобную ей (см. раздел В.5), чтобы увидеть фактическое значение параметра MSS в сегменте SYN, полученном от собеседника.</p>
     <p>Многие реализации после установления соединения округляют размер приемного буфера сокета в большую сторону, чтобы он было кратным MSS. Чтобы узнать размер приемного буфера сокета после установления соединения, можно исследовать пакеты с помощью программы типа <code>tcpdump</code> и посмотреть, каков размер объявленного окна TCP.</p>
     <p>7.3. Разместите в памяти структуру <code>linger</code> по имени <code>ling</code> и проинициализируйте ее следующим образом:</p>
     <p><code>str_cli(stdin, sockfd);</code></p>
     <empty-line/>
     <p><code>ling.l_onoff = 1;</code></p>
     <p><code>ling.l_linger = 0;</code></p>
     <p><code>Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &amp;ling, sizeof(ling));</code></p>
     <empty-line/>
     <p><code>exit(0);</code></p>
     <p>Это заставит TCP на стороне клиента прекратить работу путем отправки сегмента RST вместо нормального обмена четырьмя сегментами. Дочерний процесс сервера вызывает функцию <code>readline</code>, возвращает ошибку <code>ECONNRESET</code> и выводит следующее сообщение:</p>
     <p><code>readline error: Connection reset by peer</code></p>
     <p>Клиентский сокет не должен проходить через состояние ожидания TIME_WAIT, даже если клиент выполняет активное закрытие.</p>
     <p>7.4. Первый клиент вызывает функции <code>setsockopt</code>, <code>bind</code> и <code>connect</code>. Но если второй клиент вызовет функцию <code>bind</code> между вызовами функций <code>bind</code> и <code>connect</code> первого клиента, возвращается ошибка <code>EADDRINUSE</code>. Но как только первый клиент установит соединение с собеседником, вызов функции <code>bind</code> второго клиента будет работать, поскольку сокет первого клиента уже присоединен. В случае возвращения ошибки <code>EADDRINUSE</code> второму клиенту следует вызывать <code>bind</code> несколько раз, а не останавливаться при появлении первой ошибки — это единственный способ справиться с данной ситуацией.</p>
     <p>7.5. Запускаем программу на узле без поддержки многоадресной передачи (MacOS X 10.2.6).</p>
     <p><code>macosx % <strong>sock -s 9999 &amp;</strong> <emphasis>запускаем первый сервер с универсальным адресом</emphasis></code></p>
     <p><code>[1] 29697</code></p>
     <p><code>macosx % <strong>sock -s 172.24.37.78 9999</strong> <emphasis>пробуем второй сервер, но без -А</emphasis></code></p>
     <p><code>can't bind local address: Address already in use</code></p>
     <p><code>macosx % <strong>sock -s -A 172.24.37.78 9999 &amp;</strong> <emphasis>пробуем опять с -A: работает</emphasis></code></p>
     <p><code>[2] 29699</code></p>
     <p><code>macosx % <strong>sock -s -A 127.0.0.1 9999 &amp;</strong> <emphasis>третий сервер с -A; работает</emphasis></code></p>
     <p><code>[3] 29700</code></p>
     <p><code>macosx % <strong>netstat -na | grep 9999</strong></code></p>
     <p><code>tcp4 0 0 127.0.0.1.9999     *.* LISTEN</code></p>
     <p><code>tcp4 0 0 206.62.226.37.9999 *.* LISTEN</code></p>
     <p><code>tcp4 0 0 *.9999             *.* LISTEN</code></p>
     <p>7.6. Теперь попробуем проделать то же на узле с поддержкой многоадресной передачи, но без поддержки параметра <code>SO_REUSEADDR</code> (Solaris 9).</p>
     <p><code>solaris % <strong>sock -s -u 8888 &amp;</strong> <emphasis>запускаем первый</emphasis></code></p>
     <p><code>[1] 24051</code></p>
     <p><code>solaris % <strong>sock -s -u 8888</strong></code></p>
     <p><code>can't bind local address: Address already in use</code></p>
     <p><code>solaris % <strong>sock -s -u -A 8888 &amp;</strong> <emphasis>снова пробуем запустить второй с -A:</emphasis></code></p>
     <p><code><emphasis>                               работает</emphasis></code></p>
     <p><code>solaris % <strong>netstat -na | grep 8888</strong> <emphasis>мы видим дублированное связывание</emphasis></code></p>
     <p><code>*.8888 Idle</code></p>
     <p><code>* 8888 Idle</code></p>
     <p>В этой системе задавать параметр <code>SO_REUSEADDR</code> было необходимо только для второго связывания. Наконец, запускаем сценарий в MacOS X 10.2.6, где поддерживается как многоадресная передача, так и параметр <code>SO_REUSEPORT</code>. Сначала пробуем использовать <code>SO_REUSEADDR</code> для обоих серверов, но это не работает.</p>
     <p><code>macosx % <strong>sock -u -s -A 7777 &amp;</strong></code></p>
     <p><code>[1] 17610</code></p>
     <p><code>macosx % <strong>sock -u -s -A 7777</strong></code></p>
     <p><code>can't bind local address: Address already in use</code></p>
     <p>Тогда пробуем использовать параметр <code>SO_REUSEPORT</code> только для второго сервера. Это также не работает, так как полностью дублированное связывание требует включения данного параметра для всех сокетов, совместно использующих соединение.</p>
     <p><code>macosx % <strong>sock -u -s 8888 &amp;</strong></code></p>
     <p><code>[1] 17612</code></p>
     <p><code>macosx % <strong>sock -u -s -T 8888</strong></code></p>
     <p><code>can't bind local address: Address already in use</code></p>
     <p>Наконец, задаем параметр <code>SO_REUSEPORT</code> для обоих серверов, и этот вариант работает.</p>
     <p><code>macosx % <strong>sock -u -s -Т 9999 &amp;</strong></code></p>
     <p><code>[1] 17614</code></p>
     <p><code>macosx % <strong>sock -u -s -T 9999 &amp;</strong></code></p>
     <p><code>[2] 17615</code></p>
     <p><code>macosx % <strong>netstat -na | grep 9999</strong></code></p>
     <p><code>udp4 0 0 *.9999 *.*</code></p>
     <p><code>udp4 0 0 *.9999 *.*</code></p>
     <p>7.7. Этот параметр (<code>-d</code>) не делает ничего, поскольку программа <code>ping</code> использует ICMP-сокет, а параметр сокета <code>SO_DEBUG</code> влияет только на TCP-сокеты. Описание параметра сокета <code>SO_DEBUG</code> всегда было довольно расплывчатым, наподобие «этот параметр допускает отладку на соответствующем уровне протокола», и единственный уровень протокола, где реализуется данный параметр — это TCP.</p>
     <p>7.8. Временная диаграмма приведена на рис. Д.4.</p>
     <image l:href="#img_188.png"/>
     <p><strong>Рис. Д.4</strong>. Взаимодействие алгоритма Нагла с задержанными сегментами ACK</p>
     <p>7.9. Установка параметра сокета <code>TCP_NODELAY</code> приводит к немедленной отправке данных из второй функции <code>write</code>, даже если имеется еще один небольшой пакет, ожидающий отправки. Это показано на рис. Д.5. Полное время в данном примере превышает 150 мс.</p>
     <image l:href="#img_189.png"/>
     <p><strong>Рис Д.5</strong>. Предотвращение алгоритма Нагла путем установки параметра TCP_NODELAY</p>
     <p>7.10. Как показано на рис. Д.6, преимущество данного решения состоит в уменьшении числа пакетов.</p>
     <image l:href="#img_190.png"/>
     <p><strong>Рис. Д.6</strong>. Использование функции writev вместо параметра сокета TCP_NODELAY</p>
     <p>7.11. В разделе 4.2.3.2 говорится: «задержка ДОЛЖНА быть меньше 0,5 с, а в потоке полноразмерных сегментов СЛЕДУЕТ использовать сегмент ACK по крайней мере для каждого второго сегмента». Беркли-реализации задерживают сегмент ACK не более, чем на 200 мс [128, с. 821].</p>
     <p>7.12. Родительский процесс сервера в листинге 5.1 большую часть времени блокирован в вызове функции <code>accept</code>, а дочерний процесс в листинге 5.2 большую часть времени блокирован в вызове функции <code>read</code>, который содержится в функции <code>readline</code>. Проверка работоспособности с помощью параметра <code>SO_KEEPALIVE</code> не влияет на прослушиваемый сокет, поэтому в случае, если клиентский узел выйдет из строя, родительский процесс не пострадает. Функция read дочернего процесса возвратит ошибку <code>ETIMEDOUT</code> примерно через 2 ч после последнего обмена данными через соединение.</p>
     <p>7.13. Клиент, приведенный в листинге 5.4, большую часть времени блокирован вызовом функции <code>fgets</code>, который, в свою очередь, блокирован операцией чтения из стандартной библиотеки ввода-вывода на стандартном устройстве ввода. Когда примерно через 2 ч после последнего обмена данными через соединение истечет время таймера проверки работоспособности и проверочные сообщения не выявят работоспособности сервера, ошибка сокета, ожидающая обработки, примет значение <code>ETIMEDOUT</code>. Но клиент блокирован вызовом функции <code>fgets</code>, поэтому он не увидит этой ошибки, пока не осуществит чтение или запись на сокете. Это одна из причин, по которой в главе 6 листинг 5.4 был изменен таким образом, чтобы использовать функцию <code>select</code>.</p>
     <p>7.14. Этот клиент большую часть времени блокирован вызовом функции <code>select</code>, которая сообщит, что сокет готов для чтения, как только ожидающая обработки ошибка будет установлена в <code>ETIMEDOUT</code> (как показано в предыдущем решении).</p>
     <p>7.15. Происходит обмен только двумя сегментами, а не четырьмя. Вероятность того, что таймеры двух систем будут строго синхронизированы, очень мала, следовательно, на одном конце соединения таймер проверки работоспособности сработает немного раньше, чем на другом. Первый из сработавших таймеров посылает проверочное сообщение, заставляя другой конец послать в ответ сегмент ACK. Но получение проверочного сообщения приводит к тому, что таймеру проверки работоспособности с более медленными часами будет присвоено новое значение — он сдвинется на 2 ч вперед.</p>
     <p>7.16 Изначально в API сокетов не было функции <code>listen</code>. Вместо этого четвертый аргумент функции <code>socket</code> содержал параметр сокета, а параметр <code>SO_ACCEPTCONN</code> использовался для задания прослушиваемого сокета. Когда добавилась функция <code>listen</code>, флаг остался, но теперь его может устанавливать только ядро [128, с. 456].</p>
    </section>
    <section>
     <title>
      <p>Глава 8</p>
     </title>
     <p>8.1. Да. Функция <code>read</code> возвращает 4096 байт данных, а функция <code>recvfrom</code> возвращает 2048 байт (первую из двух дейтаграмм). Функция <code>recvfrom</code> на сокете дейтаграмм никогда не возвращает больше одной дейтаграммы, независимо от того, сколько приложение запрашивает.</p>
     <p>8.2. Если протокол использует структуры адреса сокета переменной длины, <code>clilen</code> может быть слишком длинным. В главе 15 будет показано, что это не вызывает проблем со структурами адреса доменного сокета Unix, но корректным решением будет использовать для функции <code>sendto</code> фактическую длину, возвращаемую функцией <code>recvfrom</code>.</p>
     <p>8.4. Запуск программы <code>ping</code> с такими параметрами позволяет просмотреть ICMP-сообщения, получаемые узлом, на котором она запущена. Мы используем уменьшенное количество отправляемых пакетов вместо обычного значения 1 пакет в секунду, только чтобы уменьшить объем выводимой на экран информации. Если запустить наш UDP-клиент на узле <code>solaris</code>, указав IP-адрес сервера 192.168.42.1, а затем запустить программу <code>ping</code>, получим следующий вывод:</p>
     <p><code>aix % <strong>ping -v -I 60 127.0.0.1</strong></code></p>
     <p><code>PING 127.0.0.1: {127.0.0.1}: 56 data bytes</code></p>
     <p><code>64 bytes from 127 0.0.1: icmp_seq=0 ttl=255 time=0 ms</code></p>
     <p><code>36 bytes from 192.168.42.1: Destination Port Unreachable</code></p>
     <p><code>Vr HL TOS  Len   ID Fig  Off TTL Pro cks  Src Dst Data</code></p>
     <p><code>4   5  00 0022 0007 0   0000  1e  11 c770 192 168 42.2 192.168.42.1</code></p>
     <p><code>UDP: from port 40645. to port 9877 (decimal)</code></p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>He все версии ping выводят сообщения об ICMP-ошибках, даже если задан параметр -v.</p>
     </cite>
     <p>8.5. Прослушиваемый сокет может иметь приемный буфер определенного размера, но прослушиваемым TCP-сокетом данные никогда не принимаются. Большинство реализаций не выделяют заранее память под буферы отправки и приема. Размеры буферов сокета, определяемые параметрами <code>SO_SNDBUF</code> и <code>SO_RCVBUF</code>, являются предельными значениями для соответствующего сокета.</p>
     <p>8.6. Запустим программу <code>sock</code> с параметром <code>-u</code> (использовать UDP) и параметром <code>-l</code> (определяет локальный адрес и порт) на многоинтерфейсном узле <code>freebsd</code>.</p>
     <p><code>freebsd % <strong>sock -u -l 12.106.32.254.4444 192.168.42.2 8888</strong></code></p>
     <p><code><strong>hello</strong></code></p>
     <p>Локальный IP-адрес подключен к Интернету (см. рис. 1.7), но чтобы достичь получателя, дейтаграмма должна выйти через другой интерфейс. Наблюдая за сетью с помощью программы <code>tcpdump</code>, мы увидим, что IP-адрес отправителя, связанный с клиентом, не является адресом исходящего интерфейса.</p>
     <p><code>14:28:29.614846 12.106.32.254.444 &gt; 192.168.42.2.8888. udp 6</code></p>
     <p><code>14:28:29.615255 192.168.42.2 &gt; 12 106.32.254: icmp: 192.168 42.2</code></p>
     <p><code>udp port 8888 unreachable</code></p>
     <p>8.7. Использование функции <code>printf</code> на стороне клиента приведет к возникновению задержки между отправками дейтаграмм, что позволит серверу получать большее количество дейтаграмм. Использование функции <code>printf</code> на стороне сервера приведет к тому, что сервер будет терять большее количество дейтаграмм.</p>
     <p>8.8. Наибольший размер IPv4-дейтаграммы составляет 65 535 байт и ограничивается 16-разрядным полем полной длины, показанным на рис. А.1. IP-заголовок требует 20 байт, UDP-заголовок — 8 байт, и для пользовательских данных остается не более 65 507 байт. В IPv6 (без поддержки джумбограмм) размер IP-заголовка составляет 40 байт, и под пользовательские данные отводится 65 487 байт.</p>
     <p>В листинге Д.3 приведена новая версия <code>dg_cli</code>. Если забыть установить размер буфера отправки, Беркли-ядра возвратят из функции <code>sendto</code> ошибку <code>EMSGSIZE</code>, поскольку размер буфера отправки сокета обычно меньше, чем максимально возможный размер UDP-дейтаграммы (чтобы убедиться в этом, выполните упражнение 7.1).</p>
     <p><strong>Листинг Д.3</strong>. Запись дейтаграммы UDP/IPv4 максимального размера</p>
     <p><code>//udpcliserv/dgclibig.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 #undef MAXLINE</code></p>
     <p><code> 3 #define MAXLINE 65507</code></p>
     <empty-line/>
     <p><code> 4 void</code></p>
     <p><code> 5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)</code></p>
     <p><code> 6 {</code></p>
     <p><code> 7  int size;</code></p>
     <p><code> 8  char sendline[MAXLINE], recvline[MAXLINE + 1];</code></p>
     <p><code> 9  ssize_t n;</code></p>
     <empty-line/>
     <p><code>10  size = 70000;</code></p>
     <p><code>11  Setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &amp;size, sizeof(size));</code></p>
     <p><code>12  Setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &amp;size, sizeof(size));</code></p>
     <empty-line/>
     <p><code>13  Sendto(sockfd, sendline, MAXLINE, 0, pservaddr, servlen);</code></p>
     <empty-line/>
     <p><code>14  n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);</code></p>
     <empty-line/>
     <p><code>15  printf("received %d bytes\n", n);</code></p>
     <p><code>16 }</code></p>
     <p>Но если установить размеры буферов сокета клиента, как показано в листинге Д.3, и запустить программу, сервер ничего не возвратит. С помощью программы <code>tcpdump</code> можно убедиться, что клиентская дейтаграмма отправляется серверу, но если в сервер поместить функцию <code>printf</code>, вызов функции <code>recvfrom</code> не возвратит дейтаграмму. Проблема заключается в том, что приемный буфер UDP-сокета сервера меньше, чем посланная нами дейтаграмма, поэтому дейтаграмма отбрасывается и не доставляется на сокет. В системах BSD/OS это можно проверить, запустив программу <code>netstat -s</code> и проверив счетчик, указывающий количество дейтаграмм, отброшенных из-за переполнения буферов сокета (<code>dropped due to full socket buffers</code>), до и после получения нашей длинной дейтаграммы. Решением является модификация сервера путем задания размеров буферов приема и отправки сокета.</p>
     <p>В большинстве сетей дейтаграмма длиной 65 535 байт фрагментируется. Как отмечалось в разделе 2.9, IP-уровнем должен поддерживаться размер буфера для сборки фрагментов, равный всего лишь 576 байт. Поэтому некоторые узлы не получат дейтаграмму максимального размера, посылаемую в данном упражнении. Кроме того, во многих Беркли-реализациях, включая 4.4BSD-Lite2, имеется ошибка, связанная со знаковыми типами данных, которая не позволяет UDP принимать дейтаграммы больше, чем 32 767 байт (см. строка 95, с. 770 [128]).</p>
    </section>
    <section>
     <title>
      <p>Глава 9</p>
     </title>
     <p>9.1. В некоторых ситуациях функция <code>sctp_peeloff</code> может оказаться очень полезной. Примером приложения, которому может понадобиться эта функция, является традиционный сервер дейтаграмм, обрабатывающий небольшие транзакции, которому периодически приходится устанавливать долговременные соединения. Чаще всего сервер передает одно-два коротких сообщения, но время от времени поступает запрос на проверку базы сервера, и тогда ему приходится передавать большие объемы данных. В такой ситуации имеет смысл отделить ассоциацию, по которой передаются проверочные данные, для обработки ее отдельным процессом или потоком.</p>
     <p>9.2. На стороне сервера выполняется автоматическое закрытие после закрытия ассоциации клиентом. SCTP не поддерживает состояние неполного закрытия, поэтому когда клиент вызывает <code>close</code>, все подготовленные сервером данные сбрасываются и ассоциация закрывается.</p>
     <p>9.3. Сокет типа «один-к-одному» требует вызова <code>connect</code>, поэтому когда собеседнику отсылается сегмент COOKIE, никаких данных в буфере отправки быть еще не может. Сокет типа «один-ко-многим» допускает отправку данных с одновременной установкой соединения. Поэтому сегмент COOKIE в этом случае может быть совмещен с сегментом DATA.</p>
     <p>9.4. Собеседник, с которым устанавливается ассоциация, может прислать данные только в том случае, если у него будет готов сегмент DATA до того, как соединение будет установлено, то есть если на обеих сторонах используются сокеты типа «один-ко-многим» и каждая сторона выполняет операцию send с неявной установкой соединения. Такой процесс установки ассоциации называется коллизией пакетов INIT и подробно описывается в главе 4 [117].</p>
     <p>9.5. В некоторых случаях не все связанные адреса могут быть переданы собеседнику. В частности, если приложение связало с сокетом как частные, так и общие IP-адреса, собеседник получит информацию только об общих IP-адресах. Еще одним примером являются локальные в рамках канала адреса IPv6, которые не обязательно сообщаются собеседнику.</p>
    </section>
    <section>
     <title>
      <p>Глава 10</p>
     </title>
     <p>10.1 Если функция <code>sctp_sendmsg</code> возвращает ошибку, сообщение не будет отправлено, а приложение вызовет функцию <code>sctp_recvmsg</code> и заблокируется в ней навсегда, ожидая ответного сообщения, которое никогда не придет.</p>
     <p>Чтобы избежать этой неприятности, нужно проверять коды возврата. Если при отправке возникла ошибка, клиент не должен пытаться получить ответ. Ему следует просто сообщить о возникшей ошибке.</p>
     <p>Если функция <code>sctp_recvmsg</code> вернет ошибку, никаких сообщений получено не будет, но сервер все равно попытается отправить сообщение, что может привести к установлению ассоциации. Для предотвращения этого следует проверять код ошибки и, в зависимости от его значения, сообщать об ошибке и закрывать сокет (при этой операции также может быть возвращена ошибка) или повторно вызывать <code>sctp_recvmsg</code>.</p>
     <p>10.2. Если сервер получает запрос и завершает работу, клиент (в его нынешней форме) зависает навечно в ожидании ответа сервера. Клиенту следует включить доставку уведомлений о событиях для данной ассоциации. Когда сервер завершит работу, клиент получит соответствующее сообщение и сможет принять какие-либо меры, например связаться с другим сервером. Альтернативным решением может быть установка таймера и завершение работы по истечении времени ожидания.</p>
     <p>10.3. Чтобы каждая порция данных была помещена в свой пакет, мы установили размер сообщения 800 байт. Более правильным решением будет получение значения параметра сокета <code>SCTP_MAXSEG</code> для определения размера данных, помещающихся в один пакет.</p>
     <p>10.4. Алгоритм Нагла (управляемый параметром сокета <code>SCTP_NODELAY</code>, см. раздел 7.10) вызывает проблемы только при передаче данных небольших объемов. Если данные передаются порциями такого размера, что SCTP вынужден передавать их немедленно, никакого замедления быть не может. Установка небольшого размера <code>out_sz</code> исказит результаты, потому что в некоторых случаях передача будет задерживаться до получения выборочных уведомлений от собеседника. Поэтому при передаче данных небольшого размера алгоритм Нагла следует отключать.</p>
     <p>10.5. Если приложение устанавливает ассоциацию и изменяет количество потоков, количество потоков в данной ассоциации не меняется. Количество потоков может быть задано только для новых ассоциаций, но не для существующих.</p>
     <p>Сокет типа «один-ко-многим» позволяет устанавливать ассоциации неявно. Для изменения параметров ассоциации необходимо вызвать <code>sendmsg</code> со вспомогательными данными. Фактически при этом обязательно использовать неявное установление ассоциации.</p>
    </section>
    <section>
     <title>
      <p>Глава 11</p>
     </title>
     <p>11.1. В листинге Д.4 приведена программа, вызывающая функцию <code>gethostbyaddr</code>.</p>
     <p><strong>Листинг Д.4</strong>. Изменение листинга 11.1 для вызова функции gethostbyaddr</p>
     <p><code>//names/hostent2.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  char *ptr, **pptr;</code></p>
     <p><code> 6  char str[INET6_ADDRSTRLEN];</code></p>
     <p><code> 7  struct hostent *hptr;</code></p>
     <empty-line/>
     <p><code> 8  while (--argc &gt; 0) {</code></p>
     <p><code> 9   ptr = *++argv;</code></p>
     <p><code>10   if ( (hptr = gethostbyname(ptr)) == NULL) {</code></p>
     <p><code>11    err_msg("gethostbyname error for host: %s: %s",</code></p>
     <p><code>12     ptr, hstrerror(h_errno));</code></p>
     <p><code>13    continue;</code></p>
     <p><code>14   }</code></p>
     <p><code>15   printf("official hostname: %s\n", hptr-&gt;h_name);</code></p>
     <p><code>16   for (pptr = hptr-&gt;h_aliases; *pptr != NULL; pptr++)</code></p>
     <p><code>17    printf(" alias: %s\n", *pptr);</code></p>
     <empty-line/>
     <p><code>18   switch (hptr-&gt;h_addrtype) {</code></p>
     <p><code>19   case AF_INET:</code></p>
     <p><code>20 #ifdef AF_INET6</code></p>
     <p><code>21   case AF_INET6:</code></p>
     <p><code>22 #endif</code></p>
     <p><code>23    pptr = hptr-&gt;h_addr_list;</code></p>
     <p><code>24    for (; *pptr != NULL; pptr++) {</code></p>
     <p><code>25     printf("\taddress: %s\n",</code></p>
     <p><code>26      Inet_ntop(hptr-&gt;h_addrtype, *pptr, str, sizeof(str)));</code></p>
     <empty-line/>
     <p><code>27     if ((hptr = gethostbyaddr(*pptr, hptr-&gt;h_length,</code></p>
     <p><code>28      ptr-&gt;h_addrtype)) == NULL)</code></p>
     <p><code>29      printf("\t(gethostbyaddr failed)\n");</code></p>
     <p><code>30     else if (hptr-&gt;h_name != NULL)</code></p>
     <p><code>31      printf("\tname = %s\n", hptr-&gt;h_name);</code></p>
     <p><code>32     else</code></p>
     <p><code>33      printf("\t(no hostname returned by gethostbyaddr)\n");</code></p>
     <p><code>34    }</code></p>
     <p><code>35    break;</code></p>
     <empty-line/>
     <p><code>36   default:</code></p>
     <p><code>37    err_ret("unknown address type");</code></p>
     <p><code>38    break;</code></p>
     <p><code>39   }</code></p>
     <p><code>40  }</code></p>
     <p><code>41  exit(0);</code></p>
     <p><code>42 }</code></p>
     <p>Эта программа корректно работает на узле с единственным IP-адресом. Если запустить программу из листинга 11.1 на узле с четырьмя IP-адресами, то получим:</p>
     <p><code>freebsd % <strong>hostent cnn.com</strong></code></p>
     <p><code>official hostname: cnn.com</code></p>
     <p><code>address: 64.236.16.20</code></p>
     <p><code>address: 64.236.16.52</code></p>
     <p><code>address: 64.236.16.84</code></p>
     <p><code>address: 64.236.16.116</code></p>
     <p><code>address: 64 236.24.4</code></p>
     <p><code>address: 64.236.24.12</code></p>
     <p><code>address: 64.236.24.20</code></p>
     <p><code>address: 64.236.24.28</code></p>
     <p>Но если запустить программу из листинга Д.4 на том же узле, в выводе будет только первый IP-адрес:</p>
     <p><code>freebsd % <strong>hostent2 cnn.com</strong></code></p>
     <p><code>official hostname: cnn.com</code></p>
     <p><code>address: 64.236.24.4</code></p>
     <p><code>name = www1.cnn.com</code></p>
     <p>Проблема заключается в том, что две функции, <code>gethostbyname</code> и <code>gethostbyaddr</code>, совместно используют одну и ту же структуру <code>hostent</code>, как было показано в разделе 11.18. Когда наша новая программа вызывает функцию <code>gethostbyaddr</code>, она повторно использует данную структуру вместе с областью памяти, на которую структура указывает (массив указателей <code>h_addr_list</code>), стирая три оставшиеся IP-адреса, возвращаемые функцией <code>gethostbyname</code>.</p>
     <p>11.2. Если ваша система не поддерживает повторно входимую версию функции <code>gethostbyaddr</code> (см. раздел 11.19), то прежде чем вызывать функцию <code>gethostbyaddr</code>, вам следует создать копию массива указателей, возвращаемых функцией <code>gethostbyname</code>, и данных, на которые указывает этот массив.</p>
     <p>11.3. Сервер <code>chargen</code> отправляет клиенту данные до тех пор, пока клиент не закрывает соединение (то есть пока вы не завершите выполнение клиента).</p>
     <p>11.4. Эта возможность поддерживается некоторыми распознавателями, но переносимая программа не может использовать ее, потому что POSIX никак ее не оговаривает. В листинге Д.5 приведена измененная версия. Порядок тестирования строки с именем узла имеет значение. Сначала мы вызываем функцию <code>inet_pton</code>, поскольку она обеспечивает быстрый тест «внутри памяти» (in-memory) для проверки, является ли строка допустимым IP-адресом в точечно-десятичной записи. Только если тест заканчивается неудачно, мы запускаем функцию <code>gethostbyname</code>, которая обычно требует некоторых сетевых ресурсов и времени.</p>
     <p>Если строка является допустимым IP-адресом в точечно-десятичной записи, мы создаем свой массив указателей (<code>addrs</code>) на один IP-адрес, оставив без изменений цикл, использующий <code>pptr</code>.</p>
     <p>Поскольку адрес уже был переведен в двоичное представление в структуре адреса сокета, мы заменяем вызов функции <code>memcpy</code> в листинге 11.2 на вызов функции <code>memmove</code>, так как при вводе IP-адреса в точечно-десятичной записи исходное и конечное поля в данном вызове одинаковые.</p>
     <p><strong>Листинг Д.5</strong>. Допускаем как использование IP-адреса в точечно-десятичной записи, так и задание имени узла, номера порта или имени службы</p>
     <p><code>//names/daytimetcpcli2.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd, n;</code></p>
     <p><code> 6  char recvline[MAXLINE + 1];</code></p>
     <p><code> 7  struct sockaddr_in servaddr;</code></p>
     <p><code> 8  struct in_addr **pptr, *addrs[2];</code></p>
     <p><code> 9  struct hostent *hp;</code></p>
     <p><code>10  struct servent *sp;</code></p>
     <empty-line/>
     <p><code>11  if (argc != 3)</code></p>
     <p><code>12   err_quit("usage: daytimetcpcli2 &lt;hostname&gt; &lt;service&gt;");</code></p>
     <empty-line/>
     <p><code>13  bzero(&amp;servaddr, sizeof(servaddr));</code></p>
     <p><code>14  servaddr.sin_family = AF_INET;</code></p>
     <empty-line/>
     <p><code>15  if (inet_pton(AF_INET, argv[1], &amp;servaddr.sin_addr) == 1) {</code></p>
     <p><code>16   addrs[0] = &amp;servaddr.sin_addr;</code></p>
     <p><code>17   addrs[1] = NULL;</code></p>
     <p><code>18   pptr = &amp;addrs[0];</code></p>
     <p><code>19  } else if ((hp = gethostbyname(argv[1])) != NULL) {</code></p>
     <p><code>20   pptr = (struct in_addr**)hp-&gt;h_addr_list;</code></p>
     <p><code>21  } else</code></p>
     <p><code>22   err_quit("hostname error for %s: %s", argv[1], hstrerror(h_errno));</code></p>
     <empty-line/>
     <p><code>23  if ((n = atoi(argv[2])) &gt; 0)</code></p>
     <p><code>24   servaddr.sin_port = htons(n);</code></p>
     <p><code>25  else if ((sp = getservbyname(argv[2], "tcp")) != NULL)</code></p>
     <p><code>26   servaddr.sin_port = sp-&gt;s_port;</code></p>
     <p><code>27  else</code></p>
     <p><code>28   err_quit("getservbyname error for %s", argv[2]);</code></p>
     <empty-line/>
     <p><code>29  for (; *pptr != NULL; pptr++) {</code></p>
     <p><code>30   sockfd = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code>31   memmove(&amp;servaddr.sin_addr, *pptr, sizeof(struct in_addr));</code></p>
     <p><code>32   printf("trying %s\n",</code></p>
     <p><code>33    Sock_ntop((SA*)&amp;servaddr, sizeof(servaddr)));</code></p>
     <empty-line/>
     <p><code>34   if (connect(sockfd, (SA*)&amp;servaddr, sizeof(servaddr)) == 0)</code></p>
     <p><code>35    break; /* успех */</code></p>
     <p><code>36   err_ret("connect error");</code></p>
     <p><code>37   close(sockfd);</code></p>
     <p><code>38  }</code></p>
     <p><code>39  if (*pptr == NULL)</code></p>
     <p><code>40   err_quit("unable to connect");</code></p>
     <empty-line/>
     <p><code>41  while ((n = Read(sockfd, recvline, MAXLINE)) &gt; 0) {</code></p>
     <p><code>42   recvline[n] = 0; /* завершающий нуль */</code></p>
     <p><code>43   Fputs(recvline, stdout);</code></p>
     <p><code>44  }</code></p>
     <p><code>45  exit(0);</code></p>
     <p><code>46 }</code></p>
     <p>11.5. Программа приведена в листинге Д.6.</p>
     <p><strong>Листинг Д.6</strong>. Модификация листинга 11.2 для работы с IPv4 и IPv6</p>
     <p><code>//names/daytimetcpcli3.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd, n;</code></p>
     <p><code> 6  char recvline[MAXLINE + 1];</code></p>
     <p><code> 7  struct sockaddr_in servaddr;</code></p>
     <p><code> 8  struct sockaddr_in6 servaddr6;</code></p>
     <p><code> 9  struct sockaddr *sa;</code></p>
     <p><code>10  socklen_t sal en;</code></p>
     <p><code>11  struct in_addr **pptr;</code></p>
     <p><code>12  struct hostent *hp;</code></p>
     <p><code>13  struct servent *sp;</code></p>
     <empty-line/>
     <p><code>14  if (argc != 3)</code></p>
     <p><code>15   err_quit("usage: daytimetcpcli3 &lt;hostname&gt; &lt;service&gt;");</code></p>
     <empty-line/>
     <p><code>16  if ((hp = gethostbyname(argv[1])) == NULL)</code></p>
     <p><code>17   err_quit("hostname error for %s: %s", argv[1], hstrerror(h_errno));</code></p>
     <empty-line/>
     <p><code>18  if ((sp = getservbyname(argv[2], "tcp")) == NULL)</code></p>
     <p><code>19   err_quit("getservbyname error for %s", argv[2]);</code></p>
     <empty-line/>
     <p><code>20  pptr = (struct in_addr**)hp-&gt;h_addr_list;</code></p>
     <p><code>21  for (; *pptr != NULL; pptr++) {</code></p>
     <p><code>22   sockfd = Socket(hp-&gt;h_addrtype, SOCK_STREAM, 0);</code></p>
     <empty-line/>
     <p><code>23   if (hp-&gt;h_addrtype == AF_INET) {</code></p>
     <p><code>24    sa = (SA*)&amp;servaddr;</code></p>
     <p><code>25    salen = sizeof(servaddr);</code></p>
     <p><code>26   } else if (hp-&gt;h_addrtype == AF_INET6) {</code></p>
     <p><code>27    sa = (SA*)&amp;servaddr6;</code></p>
     <p><code>28    salen = sizeof(servaddr6);</code></p>
     <p><code>29   } else</code></p>
     <p><code>30    err_quit("unknown addrtype %d", hp-&gt;h_addrtype);</code></p>
     <empty-line/>
     <p><code>31   bzero(sa, salen);</code></p>
     <p><code>32   sa-&gt;sa_family = hp-&gt;h_addrtype;</code></p>
     <p><code>33   sock_set_port(sa, salen, sp-&gt;s_port);</code></p>
     <p><code>34   sock_set_addr(sa, salen, *pptr);</code></p>
     <empty-line/>
     <p><code>35   printf("trying %s\n", Sock_ntop(sa, salen));</code></p>
     <empty-line/>
     <p><code>36   if (connect(sockfd, sa, salen) == 0)</code></p>
     <p><code>37    break; /* успех */</code></p>
     <p><code>38   err_ret("connect error");</code></p>
     <p><code>39   close(sockfd);</code></p>
     <p><code>40  }</code></p>
     <p><code>41  if (*pptr == NULL)</code></p>
     <p><code>42   err_quit("unable to connect");</code></p>
     <empty-line/>
     <p><code>43  while ((n = Read(sockfd, recvline, MAXLINE)) &gt; 0) {</code></p>
     <p><code>44   recvline[n] = 0; /* завершающий нуль */</code></p>
     <p><code>45   Fputs(recvline, stdout);</code></p>
     <p><code>46  }</code></p>
     <p><code>47  exit(0);</code></p>
     <p><code>48 }</code></p>
     <p>Используем значение <code>h_addrtype</code>, возвращаемое функцией <code>gethostbyname</code>, для определения типа адреса. Также используем функции <code>sock_set_port</code> и <code>sock_set_addr</code> (см. раздел 3.8), чтобы установить два соответствующих поля в структуре адреса сокета.</p>
     <p>Эта программа работает, однако имеется два ограничения. Во-первых, мы должны обрабатывать все различия, следя за <code>h_addrtype</code> и задавая соответствующим образом <code>sa</code> или <code>salen</code>. Более удачным решением было бы иметь библиотечную функцию, которая не только просматривает имя узла и имя службы, но и заполняет всю структуру адреса сокета (например, <code>getaddrinfo</code>, см. раздел 11.6). Во-вторых, эта программа компилируется только на узлах с поддержкой IPv6. Чтобы ее можно было откомпилировать на узле, поддерживающем только IPv4, следует добавить в код огромное количество директив <code>#ifdef</code>, что, несомненно, усложнит программу.</p>
     <p>11.7. Разместите в памяти большой буфер (превышающий по размеру любую структуру адреса сокета) и вызовите функцию <code>getsockname</code>. Третий аргумент является аргументом типа «значение-результат», возвращающим фактический размер адресов протоколов. К сожалению, это допускают только структуры адреса сокета с фиксированной длиной (IPv4 и IPv6). Нет гарантии, что этот буфер будет работать с протоколами, которые могут вернуть структуру адреса сокета переменной длины (доменные сокеты Unix, см. главу 15).</p>
     <p>11.8. Сначала размещаем в памяти массивы, содержащие имя узла и имя службы:</p>
     <p><code>char host[NI_MAXHOST], serv[NI_MAXSERV];</code></p>
     <p>После того как функция <code>accept</code> возвращает управление, вызываем вместо функции <code>sock_ntop</code> функцию <code>getnameinfo</code>:</p>
     <p><code>if (getnameinfo(cliaddr, len, host, NI_MAXHOST, serv, NI_MAXSERV,</code></p>
     <p><code> NI_NUMERICHOST | NI_NUMERICSERV) == 0)</code></p>
     <p><code> printf("connection from %s.%s\n", host, serv);</code></p>
     <p>Поскольку мы имеем дело с сервером, определяем флаги <code>NI_NUMERICHOST</code> и <code>NI_NUMERICSERV</code>, чтобы избежать поиска в DNS и <code>/etc/services</code>.</p>
     <p>11.9. Первая проблема состоит в том, что второй сервер не может связаться (<code>bind</code>) с тем же портом, что и первый сервер, поскольку не установлен параметр сокета <code>SO_REUSEADDR</code>. Простейший способ справиться с такой ситуацией — создать копию функции <code>udp_server</code>, переименовать ее в <code>udp_server_reuseaddr</code>, сделать так, чтобы она установила параметр сокета, и вызывать ее в сервере.</p>
     <p>11.10. Когда клиент выводит <code>Trying 206.62.226.35...</code>, функция <code>gethostname</code> возвращает IP-адрес. Пауза перед этим выводом означает, что распознаватель ищет имя узла. Вывод <code>Connected to bsdi.unpbook.com.</code> значит, что функция <code>connect</code> возвратила управление. Пауза между этими двумя выводами говорит о том, что функция connect пытается установить соединение.</p>
    </section>
    <section>
     <title>
      <p>Глава 12</p>
     </title>
     <p>12.1. Далее приведен сокращенный листинг. Обратите внимание, что клиент FTP в системе <code>freebsd</code> всегда пытается использовать команду <code>EPRT</code> (независимо от версии IP), но если это не срабатывает, то он пробует команду <code>PORT</code>.</p>
     <p><code>freebsd % <strong>ftp aix-4</strong></code></p>
     <p><code>Connected to aix-4.unpbook.com.</code></p>
     <p><code>220 aix FTP server ...</code></p>
     <p><code>...</code></p>
     <p><code>230 Guest login ok. access restrictions apply.</code></p>
     <p><code>ftp&gt; <strong>debug</strong></code></p>
     <p><code>Debugging on (debug=1).</code></p>
     <p><code>ftp&gt; <strong>passive</strong></code></p>
     <p><code>Passive mode: off; fallback to active mode= off</code></p>
     <p><code>ftp&gt; <strong>dir</strong></code></p>
     <p><code>---&gt; EPRT |1|192 168.42.1|50484|</code></p>
     <p><code>500 'EPRT |1|192.168.42.1|50484|' command not understood.</code></p>
     <p><code>disabling epsv4 for this connection</code></p>
     <p><code>---&gt; PORT 192.168.42.1.197.52</code></p>
     <p><code>200 PORT command successful.</code></p>
     <p><code>---&gt; LIST</code></p>
     <p><code>150 Opening ASCII mode data connection for /bin/ls</code></p>
     <p><code>...</code></p>
     <p><code>freebsd % <strong>ftp ftp.kame.net</strong></code></p>
     <p><code>Trying 2001.200:0:4819:203:47ff:fea5:3085...</code></p>
     <p><code>Connected to orange.kame.net.</code></p>
     <p><code>220 orange.kame.net FTP server ...</code></p>
     <p><code>...</code></p>
     <p><code>230 Guest login ok. access restrictions apply.</code></p>
     <p><code>ftp&gt; <strong>debug</strong></code></p>
     <p><code>Debugging on (debug=1).</code></p>
     <p><code>ftp&gt; <strong>passive</strong></code></p>
     <p><code>Passive mode: off; fallback to active mode: off.</code></p>
     <p><code>ftp&gt; <strong>dir</strong></code></p>
     <p><code>---&gt; EPRT |2|3ffe:b80:3:9ad1::2|50480|</code></p>
     <p><code>200 EPRT command successful</code></p>
     <p><code>---&gt; LIST</code></p>
     <p><code>150 Opening ASCII mode data connection for '/bin/ls'.</code></p>
    </section>
    <section>
     <title>
      <p>Глава 13</p>
     </title>
     <p>13.1. Все сообщения об ошибках, даже ошибка загрузки, такая как неправильный аргумент командной строки, должны сохраняться в файлах журнала с помощью функции <code>syslog</code>.</p>
     <p>13.2. TCP-версии серверов <code>echo</code>, <code>discard</code> и <code>chargen</code> запускаются как дочерние процессы, после того как демон <code>inetd</code> вызовет функцию <code>fork</code>, поскольку эти три сервера работают, пока клиент не прервет соединение. Два других TCP-сервера, <code>time</code> и <code>daytime</code>, не требуют использования функции <code>fork</code>, поскольку эти службы легко реализовать (получить текущую дату, преобразовать ее, записать и закрыть соединение). Эти два сервера обрабатываются непосредственно демоном <code>inetd</code>. Все пять UDP-служб обрабатываются без использования функции <code>fork</code>, поскольку каждая из них генерирует единственную дейтаграмму в ответ на клиентскую дейтаграмму, которая запускает эту службу. Эти пять служб обрабатываются напрямую демоном <code>inetd</code>.</p>
     <p>13.3. Это известная атака типа «отказ в обслуживании» [18]. Первая дейтаграмма с порта 7 заставляет сервер <code>chargen</code> отправить дейтаграмму обратно на порт 7. На эту дейтаграмму приходит эхо-ответ, и серверу <code>chargen</code> посылается другая дейтаграмма. Происходит зацикливание. Одним из решений, реализованным в системе BSD/OS, является игнорирование дейтаграмм, направленных любому внутреннему серверу, если номер порта отправителя пришедшей дейтаграммы принадлежит одному из внутренних серверов. Другим решением может быть запрещение этих внутренних служб — либо с помощью демона <code>inetd</code> на каждом узле, либо на маршрутизаторе, связывающем внутреннюю сеть организации с Интернетом.</p>
     <p>13.4. IP-адрес и номер порта клиента могут быть получены из структуры адреса сокета, заполняемой функцией <code>accept</code>.</p>
     <p>Причина, по которой демон <code>inetd</code> не делает этого для UDP-сокета, состоит в том, что чтение дейтаграмм (<code>recvfrom</code>) осуществляется с помощью функции <code>exec</code> сервером, а не самим демоном <code>inetd</code>.</p>
     <p>Демон <code>inetd</code> может считать дейтаграмму с флагом <code>MSG_PEEK</code> (см. раздел 14.7), только чтобы получить IP-адрес и номер порта клиента, но оставляет саму дейтаграмму для чтения серверу.</p>
    </section>
    <section>
     <title>
      <p>Глава 14</p>
     </title>
     <p>14.1. Если не установлен обработчик, первый вызов функции <code>signal</code> будет возвращать значение <code>SIG_DFL</code>, а вызов функции <code>signal</code> для восстановления обработчика просто вернет его в исходное состояние.</p>
     <p>14.3. Приведем цикл <code>for</code>:</p>
     <p><code>for (;;) {</code></p>
     <p><code> if ((n = Recv(sockfd, recvline, MAXLINE, MSG_PEEK)) == 0)</code></p>
     <p><code>  break; /* сервер закрыл соединение */</code></p>
     <empty-line/>
     <p><code> Ioctl(sockfd, FIONREAD, &amp;npend);</code></p>
     <p><code> printf("%d bytes from PEEK, %d bytes pending\n", n, npend);</code></p>
     <empty-line/>
     <p><code> n = Read(sockfd, recvline, MAXLINE);</code></p>
     <p><code> recvline[n] = 0; /* завершающий нуль */</code></p>
     <p><code> Fputs(recvline, stdout);</code></p>
     <p><code>}</code></p>
     <p>14.4. Данные продолжают выводиться, поскольку выход из функции <code>main</code> — это то же самое, что и возврат из этой функции. Функция <code>main</code> вызывается программой запуска на языке С следующим образом:</p>
     <p><code>exit(main(argc, argv));</code></p>
     <p>Следовательно, вызывается функция <code>exit</code>, а затем и программа очистки стандартного ввода-вывода.</p>
    </section>
    <section>
     <title>
      <p>Глава 15</p>
     </title>
     <p>15.1. Функция <code>unlink</code> удаляет имя файла из файловой системы, и когда клиент позже вызовет функцию <code>connect</code>, она не выполнится. Это не влияет на прослушиваемый сокет сервера, но клиенты не смогут выполнить функции <code>connect</code> после вызова функции <code>unlink</code>.</p>
     <p>15.2. Клиент не сможет соединиться с сервером с помощью функции <code>connect</code>, даже если полное имя существует, поскольку для успешного соединения с помощью функции <code>connect</code> доменный сокет Unix должен быть открыт и связан с этим полным именем (см. раздел 15.4).</p>
     <p>15.3. При выводе адреса протокола клиента путем вызова функции <code>sock_ntop</code> мы получим сообщение <code>datagram from (no pathname bound)</code> (дейтаграмма от (имя не задано)), поскольку по умолчанию с сокетом клиента не связывается никакое имя.</p>
     <p>Одним из решений является проверить доменный сокет Unix в функциях <code>udp_client</code> и <code>udp_connect</code> и связать с сокетом при помощи функции <code>bind</code> временное полное имя. Это приведет к зависимости от протокола в библиотечной функции, но не в нашем приложении.</p>
     <p>15.4. Даже если мы заставим сервер вернуть в функции <code>write</code> 1 байт на его 26- байтовый ответ, использование функции <code>sleep</code> на стороне клиента гарантирует, что все 26 сегментов будут получены до вызова функции <code>read</code>, в результате чего функция <code>read</code> вернет полный ответ. Это еще одно подтверждение того, что TCP является потоком байтов с отсутствием границ записей.</p>
     <p>Чтобы использовать доменные протоколы Unix, запускаем клиент и сервер с двумя аргументами командной строки <code>/lосаl</code> (или <code>/unix</code>) и <code>/tmp/daytime</code> (или любое другое временное имя, которое вы хотите использовать). Ничего не изменится: 26 байт будут возвращаться функцией <code>read</code> каждый раз, когда будет запускаться клиент.</p>
     <p>Поскольку для каждой функции <code>send</code> сервер определяет флаг <code>MSG_EOR</code>, каждый байт рассматривается как логическая запись, и функция <code>read</code> при каждом вызове возвращает 1 байт. Причина в том, что Беркли-реализации поддерживают флаг <code>MSG_EOR</code> по умолчанию. Однако этот факт не документирован и не может использоваться в серийном коде. В данном примере мы используем эту особенность, чтобы показать разницу между потоком байтов и ориентированным на записи протоколом. С точки зрения реализации, каждая операция вывода идет в <code>mbuf</code> (буфер памяти) и флаг <code>MSG_EOR</code> сохраняется ядром вместе с <code>mbuf</code>, когда <code>mbuf</code> переходит из отправляющего сокета в приемный буфер принимающего сокета. Когда вызывается функция read, флаг <code>MSG_EOR</code> все еще присоединен к каждому <code>mbuf</code>, так что основная подпрограмма ядра <code>read</code> (поддерживающая флаг <code>MSG_EOR</code>, поскольку некоторые протоколы используют этот флаг) сама возвращает каждый байт. Если бы вместо <code>read</code> мы использовали <code>recvmsg</code>, флаг <code>MSG_EOR</code> возвращался бы в поле <code>msg_flags</code> каждый раз, когда <code>recvmsg</code> возвращала бы 1 байт. Такой подход в TCP не срабатывает, поскольку отправляющий TCP не анализирует флаг <code>MSG_EOR</code> в отсылаемом <code>mbuf</code> и в любом случае у нас нет возможности передать этот флаг принимающему TCP в TCP-заголовке. (Выражаем благодарность Мату Томасу (Matt Thomas) за то, что он указал нам это недокументированное «средство».)</p>
     <p>15.5. В листинге Д.7 приведена реализация данной программы.</p>
     <p><strong>Листинг Д.7</strong>. Определение фактического количества собранных в очередь соединений для различных значений аргумента backlog</p>
     <p><code>//debug//backlog.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 #define PORT 9999</code></p>
     <p><code> 3 #define ADDR "127 0.0.1"</code></p>
     <p><code> 4 #define MAXBACKLOG 100</code></p>
     <empty-line/>
     <p><code> 5 /* глобальные переменные */</code></p>
     <p><code> 6 struct sockaddr_in serv;</code></p>
     <p><code> 7 pid_t pid; /* дочерний процесс */</code></p>
     <empty-line/>
     <p><code> 8 int pipefd[2];</code></p>
     <p><code> 9 #define pfd pipefd[1] /* сокет родительского процесса */</code></p>
     <p><code>10 #define cfd pipefd[0] /* сокет дочернего процесса */</code></p>
     <empty-line/>
     <p><code>11 /* прототипы функций */</code></p>
     <p><code>12 void do_parent(void);</code></p>
     <p><code>13 void do_child(void);</code></p>
     <empty-line/>
     <p><code>14 int</code></p>
     <p><code>15 main(int argc, char **argv)</code></p>
     <p><code>16 {</code></p>
     <p><code>17  if (argc != 1)</code></p>
     <p><code>18   err_quit("usage: backlog");</code></p>
     <empty-line/>
     <p><code>19  Socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd);</code></p>
     <empty-line/>
     <p><code>20  bzero(&amp;serv, sizeof(serv));</code></p>
     <p><code>21  serv.sin_family = AF_INET;</code></p>
     <p><code>22  serv.sin_port = htons(PORT);</code></p>
     <p><code>23  Inet_pton(AF_INET, ADDR, &amp;serv.sin_addr);</code></p>
     <empty-line/>
     <p><code>24  if ((pid = Fork()) == 0)</code></p>
     <p><code>25   do_child();</code></p>
     <p><code>26  else</code></p>
     <p><code>27  do_parent();</code></p>
     <empty-line/>
     <p><code>28  exit(0);</code></p>
     <p><code>29 }</code></p>
     <empty-line/>
     <p><code>30 void</code></p>
     <p><code>31 parent_alrm(int signo)</code></p>
     <p><code>32 {</code></p>
     <p><code>33  return; /* прерывание блокированной функции connect() */</code></p>
     <p><code>34 }</code></p>
     <empty-line/>
     <p><code>35 void</code></p>
     <p><code>36 do_parent(void)</code></p>
     <p><code>27 {</code></p>
     <p><code>38  int backlog, j, k, junk, fd[MAXBACKLOG + 1];</code></p>
     <empty-line/>
     <p><code>39  Close(cfd);</code></p>
     <p><code>40  Signal(SIGALRM, parent_alrm);</code></p>
     <empty-line/>
     <p><code>41  for (backlog = 0; backlog &lt;= 14; backlogs) {</code></p>
     <p><code>42   printf("backlog = %d. ", backlog);</code></p>
     <p><code>43   Write(pfd, &amp;backlog. sizeof(int)); /* сообщение значения дочернему процессу */</code></p>
     <p><code>44   Read(pfd, &amp;junk, sizeof(int)); /* ожидание дочернего процесса */</code></p>
     <empty-line/>
     <p><code>45   for (j = 1; j &lt;= MAXBACKLOG; j++) {</code></p>
     <p><code>46    fd[j] = Socket(AF_INET, SOCK_STREAM, 0);</code></p>
     <p><code>47    alarm(2);</code></p>
     <p><code>48    if (connect(fd[j], (SA*)&amp;serv, sizeof(serv)) &lt; 0) {</code></p>
     <p><code>49     if (errno != EINTR)</code></p>
     <p><code>50      err_sys("connect error, j = %d", j);</code></p>
     <p><code>51     printf("timeout, %d connections completed\n", j - 1);</code></p>
     <p><code>52     for (k = 1; k &lt;= j; k++)</code></p>
     <p><code>53      Close(fd[k]);</code></p>
     <p><code>54     break; /* следующее значение backlog */</code></p>
     <p><code>55    }</code></p>
     <p><code>56    alarm(0);</code></p>
     <p><code>57   }</code></p>
     <p><code>58   if (j &gt; MAXBACKLOG)</code></p>
     <p><code>59    printf("Id connections?\n", MAXBACKLOG);</code></p>
     <p><code>60  }</code></p>
     <p><code>61  backlog = -1; /* сообщаем дочернему процессу, что все сделано */</code></p>
     <p><code>62  Write(pfd, &amp;backlog, sizeof(int));</code></p>
     <p><code>63 }</code></p>
     <empty-line/>
     <p><code>64 void</code></p>
     <p><code>65 do_child(void)</code></p>
     <p><code>66 {</code></p>
     <p><code>67  int listenfd, backlog, junk;</code></p>
     <p><code>68  const int on = 1;</code></p>
     <empty-line/>
     <p><code>69  Close(pfd);</code></p>
     <empty-line/>
     <p><code>70  Read(cfd, &amp;backlog, sizeof(int)); /* ожидание родительского процесса */</code></p>
     <p><code>71  while (backlog &gt;= 0) {</code></p>
     <p><code>72   listenfd = Socket(AF_NET, SOCK_STREAM, 0);</code></p>
     <p><code>73   Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
     <p><code>74   Bind(listenfd, (SA*)&amp;serv, sizeof(serv));</code></p>
     <p><code>75   Listen(listenfd, backlog); /* начало прослушивания */</code></p>
     <empty-line/>
     <p><code>76   Write(cfd, &amp;junk, sizeof(int)); /* сообщение родительскому процессу */</code></p>
     <empty-line/>
     <p><code>77   Read(cfd, &amp;backlog, sizeof(int)); /* ожидание родительского процесса */</code></p>
     <p><code>78   Close(listenfd); /* также закрывает все соединения в очереди */</code></p>
     <p><code>79  }</code></p>
     <p><code>80 }</code></p>
    </section>
    <section>
     <title>
      <p>Глава 16</p>
     </title>
     <p>16.1. Дескриптор используется совместно родительским и дочерним процессами, поэтому его счетчик ссылок равен 2. Если родительский процесс вызывает функцию <code>close</code>, счетчик ссылок уменьшается с 2 до 1, и пока он больше нуля, сегмент FIN не посылается. Еще одна цель вызова функции <code>shutdown</code> — послать сегмент FIN, даже если дескриптор больше нуля.</p>
     <p>16.2. Родительский процесс продолжит запись в сокет, получивший сегмент FIN, а первый сегмент, посланный серверу, вызовет получение сегмента RST в ответ. После этого функция <code>write</code> пошлет родительскому процессу сигнал <code>SIGPIPE</code>, как показано в разделе 5.12.</p>
     <p>16.3. Когда дочерний процесс вызывает функцию <code>getppid</code> для отправки сигнала <code>SIGTERM</code>, возвращаемый идентификатор процесса будет равен 1. Это указывает на процесс <code>init</code>, наследующий все продолжающие работать дочерние процессы, родительские процессы которых завершились. Дочерний процесс будет пытаться послать сигнал процессу <code>init</code>, не имея необходимых прав доступа. Но если не исключается, что данный клиент будет запущен с правами привилегированного пользователя, позволяющими посылать сигналы процессу <code>init</code>, то возвращенное функцией <code>getppid</code> значение должно быть проверено перед отправкой сигнала.</p>
     <p>16.4. Если удалить эти две строки, вызывается функция <code>select</code>. Но функция <code>select</code> немедленно завершится, поскольку соединение установлено и сокет открыт для записи. Эта проверка и оператор <code>goto</code> предотвращают ненужный вызов функции <code>select</code>.</p>
     <p>16.5. Это может случиться, если сервер отправляет данные сразу, как только завершается его функция <code>accept</code>, и если узел клиента занят, когда приходит второй пакет трехэтапного рукопожатия для завершения соединения со стороны клиента (см. рис. 2.5). SMTP-серверы, например, немедленно отсылают клиенту сообщение по новому соединению, прежде чем произвести из него считывание.</p>
    </section>
    <section>
     <title>
      <p>Глава 17</p>
     </title>
     <p>17.1. Нет, это не имеет значения, поскольку первые три элемента объединения в листинге 17.1 являются структурами адреса сокета.</p>
    </section>
    <section>
     <title>
      <p>Глава 18</p>
     </title>
     <p>18.1. Элемент <code>sdl_nlen</code> будет равен 5, а элемент <code>sdl_alen</code> будет равен 8. Для этого требуется 21 байт, поэтому размер округляется до 24 байт [128, с. 89] в предположении, что используется 32-разрядная архитектура.</p>
     <p>18.2. На этот сокет никогда не посылается ответ от ядра. Данный параметр сокета (<code>SO_USELOOPBACK</code>) определяет, посылает ли ядро ответ отправляющему процессу, как показано на с. 649-650 [128]. По умолчанию этот параметр включен, поскольку большинство процессов ожидают ответа. Но отключение данного параметра препятствует отправке ответов отправителю.</p>
    </section>
    <section>
     <title>
      <p>Глава 20</p>
     </title>
     <p>20.1. Если вы получаете большое количество ответов, они могут следовать каждый раз в разном порядке. Правда, отправляющий узел обычно выводится первым, поскольку дейтаграммы, направленные к нему или от него, не появляются в реальной сети.</p>
     <p>20.2. Когда в FreeBSD обработчик сигналов записывает байт в канал, а затем завершается, функция <code>select</code> возвращает ошибку <code>EINTR</code>. Она вызывается заново и при завершении сообщает о возможности чтения из канала.</p>
    </section>
    <section>
     <title>
      <p>Глава 21</p>
     </title>
     <p>21.1.Если запустить программу, то она не выведет ничего. Для предотвращения получения многоадресных дейтаграмм сервером, не ожидающим их, ядро не доставляет дейтаграммы на сокет, не выполнявший никаких многоадресных операций (в частности, не присоединявшийся к группам). Происходит следующее. В адресе получателя UDP-дейтаграммы стоит 224.0.0.1 — это группа всех узлов, в которой должны состоять узлы, поддерживающие многоадресную передачу. UDP-дейтаграмма посылается как многоадресный кадр Ethernet, и все узлы с поддержкой многоадресной передачи должны получить ее, поскольку все они входят в указанную группу. Все отвечающие узлы передают полученную UDP-дейтаграмму серверу времени и даты (обычно он является частью демона <code>inetd</code>), даже если этот сокет не находится в группе. Однако ядро сбрасывает полученную дейтаграмму, потому что процесс, связанный с портом сервера времени и даты, не установил параметры многоадресной передачи.</p>
     <p>21.2. В листинге Д.8 показаны простые изменения функции <code>main</code> для связывания (<code>bind</code>) с адресом многоадресной передачи и портом 0.</p>
     <p><strong>Листинг Д.8</strong>. Функция main UDP-клиента, осуществляющая связывание с адресом многоадресной передачи</p>
     <p><code>//mcast/udpcli06.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int sockfd;</code></p>
     <p><code> 6  socklen_t salen;</code></p>
     <p><code> 7  struct sockaddr *cli, *serv;</code></p>
     <empty-line/>
     <p><code> 8  if (argc != 2)</code></p>
     <p><code> 9   err_quit("usage: udpcli06 &lt;Ipaddress&gt;");</code></p>
     <empty-line/>
     <p><code>10  sockfd = Udp_client(argv[1], "daytime", (void**)&amp;serv, &amp;salen);</code></p>
     <empty-line/>
     <p><code>11  cli = Malloc(salen);</code></p>
     <p><code>12  memcpy(cli, serv, salen); /* копируем структуру адреса сокета */</code></p>
     <p><code>13  sock_set_port(cli, salen, 0); /* и устанавливаем порт в 0 */</code></p>
     <p><code>14  Bind(sockfd, cli, salen);</code></p>
     <empty-line/>
     <p><code>15  dg_cli(stdin, sockfd, serv, salen);</code></p>
     <empty-line/>
     <p><code>16  exit(0);</code></p>
     <p><code>17 }</code></p>
     <p>К сожалению, все три системы, на которых проводилась проверка — FreeBSD 4.8, MacOS X и Linux 2.4.7, — позволяют использовать функцию <code>bind</code>, а затем посылают UDP-дейтаграммы с IP-адресом многоадресной передачи отправителя.</p>
     <p>21.3. Если мы запустим программу <code>ping</code> для группы узлов 224.0.0.1 на нашем узле <code>aix</code>, получим следующий вывод:</p>
     <p><code>solaris % <strong>ping 224.0.0.1</strong></code></p>
     <p><code>PING 224.0.0.1: 56 data bytes</code></p>
     <p><code>64 bytes from 192.168.42.2: icmp_seq=0 ttl=255 time=0 ms</code></p>
     <p><code>64 bytes from 192.168.42.1: icmp_seq=0 ttl=64 time=1 ms (DUP!)</code></p>
     <p><code><strong>^C</strong></code></p>
     <p><code>----224.0.0.1 PING Statistics----</code></p>
     <p><code>1 packets transmitted. 1 packets received. +1 duplicates. 0% packet loss</code></p>
     <p><code>round-trip min/avg/max = 0/0/0 ms</code></p>
     <p>Ответили оба узла в правой сети Ethernet на рис. 1.7.</p>
     <cite>
      <subtitle>ПРИМЕЧАНИЕ</subtitle>
      <p>Для предотвращения определенных типов атак некоторые системы не отвечают на широковещательные и многоадресные ICMP-запросы. Чтобы получить ответ от freebsd, нам пришлось специально настроить эту систему:</p>
      <p><code>freebsd % <strong>sysctl net.inet.icmp.bmcastecho=1</strong></code></p>
     </cite>
     <p>21.5. Величина 1 073 741 824 преобразуется в значение с плавающей точкой и делится на 4 294 967 296, что дает значение 0,250. В результате умножения на 1 000 000 получаем значение 250 000 в микросекундах, а это одна четверть секунды. Наибольшая дробная часть получается при делении 4 294 967 295 на 429 4967 296 и составляет 0,99 999 999 976 716 935 634. Умножая это число на 1 000 000 и отбрасывая дробную часть, получаем 999 999 — наибольшее значение количества микросекунд.</p>
    </section>
    <section>
     <title>
      <p>Глава 22</p>
     </title>
     <p>22.1. Вспомните, что функция <code>sock_ntop</code> использует свой собственный статический буфер для хранения результата. Если мы вызовем ее дважды в качестве аргумента в вызове <code>printf</code>, второй вызов приведет к перезаписи результата первого вызова.</p>
     <p>22.2. Да, если ответ содержит 0 байт пользовательских данных (например, структура <code>hdr</code>).</p>
     <p>22.3. Поскольку функция <code>select</code> не изменяет структуру <code>timeval</code>, которая определяет ее ограничение по времени, нам следует заметить время отправки первого пакета (оно возвращается в миллисекундах функцией <code>rtt_ts</code>). Если функция <code>select</code> сообщает, что сокет готов к чтению, заметьте текущее время, а если функция <code>recvmsg</code> вызывается повторно, вычислите новый тайм-аут для функции <code>select</code>.</p>
     <p>22.4. Обычным решением будет создать по одному сокету на каждый адрес интерфейса, как было сделано в разделе 22.6, и отправлять ответ с того же сокета, на который пришел запрос.</p>
     <p>22.5. Вызов функции <code>getaddrinfо</code> без аргумента имени узла и без флага <code>AI_PASSIVE</code> заставляет эту функцию считать, что используется локальный адрес 0::1 (для IPv6) или 127.0.0.1 (для IPv4). Напомним, что структура адреса сокета IPv6 возвращается функцией <code>getaddrinfo</code> перед структурой адреса сокета IPv4 при условии, что поддерживается протокол IPv6. Если узел поддерживает оба протокола, вызов функции socket в <code>udp_client</code> закончится успешно при указании семейства протоколов <code>AF_INET6</code>.</p>
     <p>В листинге Д.9 приведена не зависящая от протокола версия программы.</p>
     <p><strong>Листинг Д.9</strong>. Не зависящая от протокола версия программы из раздела 22.6</p>
     <p><code>//advio/udpserv04.c</code></p>
     <p><code> 1 #include "unpifi.h"</code></p>
     <empty-line/>
     <p><code> 2 void mydg_echo(int, SA*, socklen_t);</code></p>
     <empty-line/>
     <p><code> 3 int</code></p>
     <p><code> 4 main(int argc, char **argv)</code></p>
     <p><code> 5 {</code></p>
     <p><code> 6  int sockfd, family, port;</code></p>
     <p><code> 7  const int on = 1;</code></p>
     <p><code> 8  pid_t pid;</code></p>
     <p><code> 9  socklen_t salen;</code></p>
     <p><code>10  struct sockaddr *sa, *wild;</code></p>
     <p><code>11  struct ifi_info *ifi, *ifihead;</code></p>
     <empty-line/>
     <p><code>12  if (argc == 2)</code></p>
     <p><code>13   sockfd = Udp_client(NULL, argv[1], (void**)&amp;sa, &amp;salen);</code></p>
     <p><code>14  else if (argc == 3)</code></p>
     <p><code>15   sockfd = Udp_client(argv[1], argv[2], (void**)&amp;sa, &amp;salen);</code></p>
     <p><code>16  else</code></p>
     <p><code>17   err_quit("usage; udpserv04 [ &lt;host&gt; ] &lt;service or port&gt;");</code></p>
     <p><code>18  family = sa-&gt;sa_family;</code></p>
     <p><code>19  port = sock_get_port(sa, salen);</code></p>
     <p><code>20  Close(sockfd); /* хотим узнать семейство, порт salen */</code></p>
     <empty-line/>
     <p><code>21  for (ifihead = ifi = Get_ifi_info(family, 1),</code></p>
     <p><code>22   ifi ! = NULL; ifi = ifi-&gt;ifi_next) {</code></p>
     <empty-line/>
     <p><code>23   /* связывание с многоадресными адресами */</code></p>
     <p><code>24   sockfd = Socket(family, SOCK_DGRAM, 0);</code></p>
     <p><code>25   Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
     <empty-line/>
     <p><code>26   sock_set_port(ifi-&gt;ifi_addr, salen, port);</code></p>
     <p><code>27   Bind(sockfd, ifi-&gt;ifi_addr, salen);</code></p>
     <p><code>28   printf("bound %s\n", Sock_ntop(ifi-&gt;ifi_addr, salen));</code></p>
     <empty-line/>
     <p><code>29   if ((pid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>30    mydg_echo(sockfd, ifi-&gt;ifi_addr, salen);</code></p>
     <p><code>31    exit(0); /* никогда не выполняется */</code></p>
     <p><code>32   }</code></p>
     <p><code>33   if (ifi-&gt;ifi_flags &amp; IFF_BROADCAST) {</code></p>
     <p><code>34    /* попытка связывания с широковещательным адресом */</code></p>
     <p><code>35    sockfd = Socket(family, SOCK_DGRAM, 0);</code></p>
     <p><code>36    Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
     <empty-line/>
     <p><code>37    sock_set_port(ifi-&gt;ifi_brdaddr, salen, port);</code></p>
     <p><code>38    if (bind(sockfd, ifi-&gt;ifi_brdaddr, salen) &lt; 0) {</code></p>
     <p><code>39     if (errno == EADDRINUSE) {</code></p>
     <p><code>40      printf("EADDRINUSE: %s\n",</code></p>
     <p><code>41       Sock_ntop(ifi-&gt;ifi_brdaddr, salen));</code></p>
     <p><code>42      Close(sockfd);</code></p>
     <p><code>43      continue;</code></p>
     <p><code>44     } else</code></p>
     <p><code>45      err_sys("bind error for %s",</code></p>
     <p><code>46     Sock_ntop(ifi-&gt;ifi_brdaddr, salen));</code></p>
     <p><code>47    }</code></p>
     <p><code>48    printf ("bound %s\n", Sock_ntop(ifi-&gt;ifi_brdaddr, salen));</code></p>
     <empty-line/>
     <p><code>49    if ((pid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>50     mydg_echo(sockfd, ifi-&gt;ifi_brdaddr, salen);</code></p>
     <p><code>51     exit(0); /* никогда не выполняется */</code></p>
     <p><code>52    }</code></p>
     <p><code>53   }</code></p>
     <p><code>54  }</code></p>
     <empty-line/>
     <p><code>55  /* связывание с универсальным адресом */</code></p>
     <p><code>56  sockfd = Socket(family, SOCK_DGRAM, 0);</code></p>
     <p><code>57  Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;on, sizeof(on));</code></p>
     <empty-line/>
     <p><code>58  wild = Malloc(salen);</code></p>
     <p><code>59  memcpy(wild, sa, salen); /* копирует семейство и порт */</code></p>
     <p><code>60  sock_set_wild(wild, salen);</code></p>
     <empty-line/>
     <p><code>61  Bind(sockfd, wild, salen);</code></p>
     <p><code>62  printf("bound %s\n", Sock_ntop(wild, salen));</code></p>
     <empty-line/>
     <p><code>63  if ((pid = Fork()) == 0) { /* дочерний процесс */</code></p>
     <p><code>64   mydg_echo(sockfd, wild, salen);</code></p>
     <p><code>65   exit(0); /* никогда не выполняется */</code></p>
     <p><code>66  }</code></p>
     <p><code>67  exit(0);</code></p>
     <p><code>68 }</code></p>
     <empty-line/>
     <p><code>69 void</code></p>
     <p><code>70 mydg_echo(int sockfd, SA *myaddr, socklen_t salen)</code></p>
     <p><code>71 {</code></p>
     <p><code>72  int n;</code></p>
     <p><code>73  char mesg[MAXLINE];</code></p>
     <p><code>74  socklen_t len;</code></p>
     <p><code>75  struct sockaddr *cli;</code></p>
     <empty-line/>
     <p><code>76  cli = Malloc(salen);</code></p>
     <empty-line/>
     <p><code>77  for (;;) {</code></p>
     <p><code>78   len = salen;</code></p>
     <p><code>79   n = Recvfrom(sockfd, mesg, MAXLINE, 0, cli, &amp;len);</code></p>
     <p><code>80   printf("child %d, datagram from %s",</code></p>
     <p><code>81    getpid(), Sock_ntop(cli, len));</code></p>
     <p><code>82   printf(", to %s\n", Sock_ntop(myaddr, salen));</code></p>
     <empty-line/>
     <p><code>83   Sendto(sockfd, mesg, n, 0, cli, len),</code></p>
     <p><code>84  }</code></p>
     <p><code>85 }</code></p>
    </section>
    <section>
     <title>
      <p>Глава 24</p>
     </title>
     <p>24.1. Да, разница есть. В первом примере два байта отсылаются с единственным срочным указателем, который указывает на байт, следующий за <code>b</code>. Во втором же примере (вызываются две функции) сначала отсылается символ <code>a</code> с указателем срочности, который указывает на следующий за ним байт, а за этим сегментом следует еще один TCP-сегмент, содержащий символ <code>b</code> с другим указателем срочности, указывающим на следующий за ним байт.</p>
     <p>24.2. В листинге Д.10 приведена версия программы с использованием функции <code>poll</code>.</p>
     <p><strong>Листинг Д.10</strong>. Версия программы из листинга 24.4, использующая функцию poll вместо функции select</p>
     <p><code>//oob/tcprecv03p.c</code></p>
     <p><code> 1 #include "unp.h"</code></p>
     <empty-line/>
     <p><code> 2 int</code></p>
     <p><code> 3 main(int argc, char **argv)</code></p>
     <p><code> 4 {</code></p>
     <p><code> 5  int listenfd, connfd, n, justreadoob = 0;</code></p>
     <p><code> 6  char buff[100];</code></p>
     <p><code> 7  struct pollfd pollfd[1];</code></p>
     <empty-line/>
     <p><code> 8  if (argc == 2)</code></p>
     <p><code> 9   listenfd = Tcp_listen(NULL, argv[1], NULL);</code></p>
     <p><code>10  else if (argc == 3)</code></p>
     <p><code>11   listenfd = Tcp_listen(argv[1], argv[2], NULL);</code></p>
     <p><code>12  else</code></p>
     <p><code>13   err_quit("usage: tcprecv03p [ &lt;host&gt; ] &lt;port#&gt;");</code></p>
     <empty-line/>
     <p><code>14  connfd = Accept(listenfd, NULL, NULL);</code></p>
     <empty-line/>
     <p><code>15  pollfd[0].fd = connfd;</code></p>
     <p><code>16  pollfd[0].events = POLLRDNORM;</code></p>
     <p><code>17  for (;;) {</code></p>
     <p><code>18   if (justreadoob == 0)</code></p>
     <p><code>19    pollfd[0].events |= POLLRDBAND;</code></p>
     <empty-line/>
     <p><code>20   Poll(pollfd, 1, INFTIM);</code></p>
     <empty-line/>
     <p><code>21   if (pollfd[0].revents &amp; POLLRDBAND) {</code></p>
     <p><code>22    n = Recv(connfd, buff, sizeof(buff) - 1, MSG_OOB);</code></p>
     <p><code>23    buff[n] = 0; /* завершающий нуль */</code></p>
     <p><code>24    printf("read %d OOB byte: %s\n", n, buff);</code></p>
     <p><code>25    justreadoob = 1;</code></p>
     <p><code>26    pollfd[0].events &amp;= ~POLLRDBAND; /* отключение бита */</code></p>
     <p><code>27   }</code></p>
     <p><code>28   if (pollfd[0].revents &amp; POLLRDNORM) {</code></p>
     <p><code>29    if ((n = Read(connfd, buff, sizeof(buff) - 1)) == 0) {</code></p>
     <p><code>30     printf("received EOF\n");</code></p>
     <p><code>31     exit(0);</code></p>
     <p><code>32    }</code></p>
     <p><code>33    buff[n] = 0; /* завершающий нуль */</code></p>
     <p><code>34    printf("read %d bytes %s\n", n, buff);</code></p>
     <p><code>35    justreadoob = 0;</code></p>
     <p><code>36   }</code></p>
     <p><code>37  }</code></p>
     <p><code>38 }</code></p>
    </section>
    <section>
     <title>
      <p>Глава 25</p>
     </title>
     <p>25.1. Нет, такая модификация приведет к ошибке. Проблема состоит в том, что <code>nqueue</code> уменьшается до того, как завершается обработка элемента массива <code>dg[iget]</code>, что позволяет обработчику сигналов считывать новую дейтаграмму в данный элемент массива.</p>
    </section>
    <section>
     <title>
      <p>Глава 26</p>
     </title>
     <p>26.1. В примере с функцией <code>fork</code> будет использоваться 101 дескриптор, один прослушиваемый сокет и 100 присоединенных сокетов. Но каждый из 101 процесса (один родительский и 100 дочерних) имеет только один открытый дескриптор (игнорируем все остальные, такие как стандартный поток ввода, если сервер не является демоном). В случае сервера с потоками используется 101 дескриптор для одного процесса. Каждым потоком (включая основной) обрабатывается один дескриптор.</p>
     <p>26.2. Обмена двумя последними сегментами завершения TCP-соединения (сегмент FIN сервера и сегмент ACK клиента в ответ на сегмент FIN сервера) не произойдет. Это переведет клиентский конец соединения в состояние FIN_WAIT_2 (см. рис. 2.4). Беркли-реализации прервут работу клиентского конца, если он остался в этом состоянии, по тайм-ауту через 11 минут [128, с. 825–827]. У сервера же в конце концов закончатся дескрипторы.</p>
     <p>26.3. Это сообщение будет выводиться основным программным потоком в том случае, когда он считывает из сокета признак конца файла <emphasis>и при этом</emphasis> другой поток продолжает работать. Простейший способ выполнить это — объявить другую внешнюю переменную по имени <code>done</code>, инициализируемую нулем. Прежде чем функция <code>copyto</code> программного потока вернет управление, она установит эту переменную в 1. Основной программный поток проверит эту переменную, и если она равна нулю, выведет сообщение об ошибке. Поскольку значение переменной устанавливает только один программный поток, нет необходимости в синхронизации.</p>
    </section>
    <section>
     <title>
      <p>Глава 27</p>
     </title>
     <p>27.1. Ничего не изменится. Все системы являются соседями, поэтому гибкая маршрутизация идентична жесткой.</p>
     <p>27.2. Мы бы поместили EOL (нулевой байт) в конец буфера.</p>
     <p>27.3. Поскольку программа <code>ping</code> создает символьный (неструктурированный) сокет (см. главу 28), она получает полный IP-заголовок, включая все IP-параметры, для каждой дейтаграммы, которую она считывает с помощью функции <code>recvfrom</code>.</p>
     <p>27.4. Потому что сервер <code>rlogind</code> запускается демоном <code>inetd</code> (см. раздел 13.5).</p>
     <p>27.5. Проблема заключается в том, что пятый аргумент функции <code>setsockopt</code> является указателем на длину, а не самой длиной. Эта ошибка, вероятно, была выявлена, когда впервые использовались прототипы ANSI С.</p>
     <p>Ошибка оказалась безвредной, поскольку, как отмечалось, для отключения параметра сокета <code>IP_OPTIONS</code> можно либо задать пустой указатель в качестве четвертого аргумента, либо установить нулевое значение в пятом аргументе (длине) [128, с. 269].</p>
    </section>
    <section>
     <title>
      <p>Глава 28</p>
     </title>
     <p>28.1. Недоступными являются поле номера версии и поле следующего заголовка в IPv6. Поле полезной длины доступно либо как аргумент одной из функций вывода, либо как возвращаемое значений одной из функций ввода, но если требуется параметр увеличенного поля данных (jumbo payload option), сам параметр приложению недоступен. Заголовок фрагментации также недоступен приложению.</p>
     <p>28.2. В конце концов приемный буфер клиентского сокета заполнится, и при этом функция демона <code>write</code> будет заблокирована. Мы не хотим, чтобы это произошло, поскольку демон тогда перестанет обрабатывать данные на всех своих сокетах. Простейшим решением является следующее: демон должен сделать свой конец соединения домена Unix с клиентом неблокируемым. Для этого демон должен вызывать функцию <code>write</code> вместо функции-обертки <code>Write</code> и игнорировать ошибку <code>EWOULDBLOCK</code>.</p>
     <p>28.3. По умолчанию Беркли-ядра допускают широковещательную передачу через символьный сокет [128, с. 1057]. Поэтому параметр сокета <code>SO_BROADCAST</code> необходимо определять только для UDP-сокетов.</p>
     <p>28.4. Наша программа не проверяет адреса многоадресной передачи и не устанавливает параметр сокета <code>IP_MULTICAST_IF</code>. Следовательно, ядро выбирает исходящий интерфейс, вероятно, просматривая таблицу маршрутизации для 224.0.0.1. Мы также не устанавливаем значение поля <code>IP_MULTICAST_TTL</code>, поэтому по умолчанию оно равно 1, и это правильное значение.</p>
    </section>
    <section>
     <title>
      <p>Глава 29</p>
     </title>
     <p>29.1. Этот флаг означает, что буфер перехода устанавливается функцией <code>sigsetjmp</code> (см. листинг 29.6). Хотя этот флаг может казаться лишним, существует вероятность, что сигнал может быть доставлен после того, как устанавливается обработчик ошибок, но перед тем как вызывается функция <code>sigsetjmp</code>. Даже если программа не вызывает генерацию сигнала, сигнал всё равно может быть сгенерирован другим путем (например, как в случае с командой <code>kill</code>).</p>
    </section>
    <section>
     <title>
      <p>Глава 30</p>
     </title>
     <p>30.1. Родительский процесс оставляет прослушиваемый сокет открытым в том случае, если ему позже будет необходимо создать дополнительный дочерний процесс с помощью функции <code>fork</code> (это будет расширением нашего кода).</p>
     <p>30.2. Для передачи дескриптора действительно можно вместо потокового сокета использовать сокет дейтаграмм. В случае сокета дейтаграмм родительский процесс не получает признака конца файла на своем конце канала, когда дочерний процесс прерывается преждевременно, но для этих целей родительский процесс может использовать сигнал <code>SIGCHLD</code>. Следует иметь в виду, что эта ситуация отличается от случая с применением нашего демона <code>icmpd</code> (см. раздел 28.7): тогда между клиентом и сервером не было иерархических отношений (родительский процесс — дочерний процесс), поэтому использование признака конца файла было единственным способом для сервера обнаружить исчезновение клиента.</p>
    </section>
    <section>
     <title>
      <p>Глава 31</p>
     </title>
     <p>31.1. Здесь предполагается, что по умолчанию для протокола осуществляется нормальное завершение при закрытии потока, и для TCP это правильно.</p>
    </section>
   </section>
  </section>
  <section>
   <title>
    <p>Литература</p>
   </title>
   <p>Все документы RFC находятся в свободном доступе и могут быть получены по электронной почте, через анонимные FTP-серверы или WWW. Стартовая точка для поиска — <code>http://www.ietf.org</code>. Документы RFC расположены по адресу <code>ftp://ftp.rfc-editor.org/in-notes</code>. Отдельные документы RFC не снабжены адресами URL.</p>
   <p>Пункты, помеченные как «интернет-проект», — это еще не законченные разработки IETF (Internet Engineering Task Force — целевая группа инженерной поддержки Интернета). После выхода этой книги в свет эти проекты, возможно, изменятся или будут опубликованы как RFC. Они находятся в свободном доступе, как и документы RFC. Основное хранилище интернет-проектов — <code>http://www.ietf.org</code>. Часть URL, содержащая имя файла, приведена рядом с названием каждого проекта, так как в ней содержится номер версии.</p>
   <p>Для книг, статей и других источников, имеющих электронные версии, указаны адреса сайтов. Они могут меняться, поэтому следите за списком обновлений на сайте этой книги <code>http://www.unpbook.com</code>.</p>
   <p>1. Albitz, P. and Liu, С. 2001. <emphasis>DNS and Bind, Fourth Edition</emphasis>. O'Reilly &amp; Associates, Sebastopol, CA.</p>
   <p>2. Allman, M., Floyd, S., and Partridge, C. 2002. "Increasing TCP's Initial Window," RFC 3390.</p>
   <p>3. Allman, M., Ostermann, S., and Metz, C. W. 1998. "FTP Extensions for IPv6 and NATs," RFC 2428.</p>
   <p>4. Allman, M., Paxson, V., and Stevens, W. R. 1999. "TCP Congestion Control," RFC 2581.</p>
   <p>5. Almquist, P. 1992. "Type of Service in the Internet Protocol Suite," RFC 1349 (obsoleted by RFC 2474).</p>
   <p>Обсуждается использование поля <emphasis>тип сервиса</emphasis> в заголовке IPv4.</p>
   <p>6. Baker, F. 1995. "Requirements for IP Version 4 Routers," RFC 1812.</p>
   <p>7. Borman, D. A. 1997a. "Re: Frequency of RST Terminated Connections," end2end-interest mailing list (<code>http://www.unpbook.com/borman.97jan30.txt</code>).</p>
   <p>8. Borman, D. A. 1997b. "Re: SYN/RST cookies," tcp-impl mailing list (<code>http://www.unpbook.com/borman.97jun06.txt</code>).</p>
   <p>9. Borman, D. A., Deering, S. E., and Hinden, R. 1999. "IPv6 Jumbograms," RFC 2675.</p>
   <p>10. Braden, R. T. 1989. "Requirements for Internet Hosts —Communication Layers," RFC 1122.</p>
   <p>Первая часть Host Requirements RFC: канальный уровень, IPv4, ICMPv4, IGMPv4, ARP, TCP и UDP.</p>
   <p>11. Braden, R. T. 1992. "TIME-WAIT Assassination Hazards in TCP," RFC 1337.</p>
   <p>12. Braden, R. T., Borman, D. A., and Partridge, C. 1988. "Computing the Internet checksum," RFC 1071.</p>
   <p>13. Bradner, S. 1996. "The Internet Standards Process - Revision 3," RFC 2026.</p>
   <p>14. Bush, R. 2001. "Delegation of IP6.ARPA," RFC 3152.</p>
   <p>15. Butenhof, D. R. 1997. <emphasis>Programming with POSIX Threads</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>16. Cain, В., Deering, S. E., Kouvelas, I., Fenner, В., and Thyagarajan, A. 2002. "Internet Group Management Protocol, Version 3," RFC 3376.</p>
   <p>17. Carpenter, B. and Moore, K. 2001. "Connection of IPv6 Domains via IPv4 Clouds," RFC 3056.</p>
   <p>18. CERT, 1996a. "UDP Port Denial-of-Service Attack," Advisory CA-96.01, Computer Emergency Response Team, Pittsburgh, PA.</p>
   <p>19. CERT, 1996b. "TCP SYN Flooding and IP Spoofing Attacks," Advisory CA-96.21, Computer Emergency Response Team, Pittsburgh, PA.</p>
   <p>20. Cheswick, W. R., Bellovin, S. M., and Rubin, A. D. 2003. Firewalls and Internet Security: Repelling the Wily Hacker, Second Edition. Addison-Wesley, Reading, MA.</p>
   <p>21. Conta, A. and Deering, S. E. 1998. "Internet Control Message Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification," RFC 2463.</p>
   <p>22. Conta, A. and Deering, S. E. 2001. "Internet Control Message Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification," draft-ietf-ipngwg-icmp-v3-02.txt (Internet Draft).</p>
   <p>23. Crawford, M. 1998a. "Transmission of IPv6 Packets over Ethernet Networks," RFC 2464.</p>
   <p>24. Crawford, M. 1998b. "Transmission of IPv6 Packets over FDDI Networks," RFC 2467.</p>
   <p>25. Crawford, M., Narten, T., and Thomas, S. 1998. "Transmission of IPv6 Packets over Token Ring Networks," RFC 2470.</p>
   <p>26. Deering, S. E. 1989. "Host extensions for IP multicasting," RFC 1112.</p>
   <p>27. Deering, S. E. and Hinden, R. 1998. "Internet Protocol, Version 6 (IPv6) Specification," RFC 2460.</p>
   <p>28. Draves, R. 2003. "Default Address Selection for Internet Protocol version 6 (IPv6)," RFC 3484.</p>
   <p>29. Eriksson, H. 1994. "MBONE: The Multicast Backbone," <emphasis>Communications of the ACM</emphasis>, vol. 37, no. 8, pp. 54–60.</p>
   <p>30. Fink, R. and Hinden, R. 2003. "6bone (IPv6 Testing Address Allocation) Phase- out," draft-fink-6bone-phaseout-04.txt (Internet Draft).</p>
   <p>31. Fuller, V., Li, T., Yu, J. Y., and Varadhan, K. 1993. "Classless Inter-Domain Routing (CIDR): an Address Assignment and Aggregation Strategy," RFC 1519.</p>
   <p>32. Garfinkel, S. L., Schwartz, A., and Spafford, E. H. 2003. <emphasis>Practical UNIX &amp; Internet Security, 3rd Edition</emphasis>. O'Reilly &amp; Associates, Sebastopol, CA.</p>
   <p>33. Gettys, J. and Nielsen, H. F. 1998. <emphasis>SMUX Protocol Specification</emphasis> (<code>http://www.w3.org/TR/WD?mux</code>).</p>
   <p>34. Gierth, A. 1996. <emphasis>Private communication</emphasis>.</p>
   <p>35. Gilligan, R. E. and Nordmark, E. 2000. "Transition Mechanisms for IPv6 Hosts and Routers," RFC 2893.</p>
   <p>36. Gilligan, R. E., Thomson, S., Bound, J., McCann, J., and Stevens, W. R. 2003. "Basic Socket Interface Extensions for IPv6," RFC 3493.</p>
   <p>37. Gilligan, R. E., Thomson, S., Bound, J., and Stevens, W. R. 1997. "Basic Socket Interface Extensions for IPv6," RFC 2133 (устарело после выхода RFC 2553).</p>
   <p>38. Gilligan, R. E., Thomson, S., Bound, J., and Stevens, W. R. 1999. "Basic Socket Interface Extensions for IPv6," RFC 2553 (устарело после выхода RFC 3493).</p>
   <p>39. Haberman, В. 2002. "Allocation Guidelines for IPv6 Multicast Addresses," RFC 3307.</p>
   <p>40. Haberman, B. and Thaler, D. 2002. "Unicast-Prefix-based IPv6 Multicast Addresses," RFC 3306.</p>
   <p>41. Handley, M. and Jacobson, V. 1998. "SDP: Session Description Protocol," RFC 2327.</p>
   <p>42. Handley, M., Perkins, C., and Whelan, E. 2000. "Session Announcement Protocol," RFC 2974.</p>
   <p>43. Harkins, D. and Carrel, D. 1998. "The Internet Key Exchange (IKE)," RFC 2409.</p>
   <p>44. Hinden, R. and Deering, S. E. 2003. "Internet Protocol Version 6 (IPv6) Addressing Architecture," RFC 3513.</p>
   <p>45. Hinden, R., Deering, S. E., and Nordmark, E. 2003. "IPv6 Global Unicast Address Format," RFC 3587.</p>
   <p>46. Hinden, R., Fink, R., and Postel, J. B. 1998. "IPv6 Testing Address Allocation," RFC 2471.</p>
   <p>47. Holbrook, H. and Cheriton, D. 1999. "IP multicast channels: EXPRESS support for large-scale single-source applications," <emphasis>Computer Communication Review</emphasis>, vol. 29, no. 4, pp. 65–78.</p>
   <p>48. Huitema, C. 2001. "An Anycast Prefix for 6to4 Relay Routers," RFC 3068.</p>
   <p>49. IANA, 2003. <emphasis>Protocol/Number Assignments Directory</emphasis> (<code>http://www.iana.org/numbers.htm</code>).</p>
   <p>50. IEEE, 1996. "Information Technology — Portable Operating System Interface (POSIX) — Part 1: System Application Program Interface (API) [C Language]," IEEE Std 1003.1, 1996 Edition, Institute of Electrical and Electronics Engineers, Piscataway, NJ.</p>
   <p>Данная версия POSIX.1 (называемая также ISO/IEC 9945-1:1996) содержит базовый интерфейс API (1990), расширения реального времени 1003.1b (1993), программные потоки Pthreads 1003.1c (1995) и технические поправки 1003.1i (1995). Чтобы сделать заказ, обратитесь на сайт <code>http://www.ieee.org</code>. К сожалению, стандарты IEEE не распространяются свободно через Интернет.</p>
   <p>51. IEEE, 1997. <emphasis>Guidelines for 64-bit Global Identifier (EUI-64) Registration Authority</emphasis>. Institute of Electrical and Electronics Engineers, Piscataway, NJ (<code>http://standards.ieee.org/regauth/oui/tutorials/EUI64.html</code>).</p>
   <p>52. Jacobson, V. 1988. "Congestion Avoidance and Control," <emphasis>Computer Communication Review</emphasis>, vol. 18, no. 4, pp. 314–329 (<code>ftp://ftp.ee.lbl.g0v/papers/c0ngav0id.ps.z</code>).</p>
   <p>Классическая статья, описывающая алгоритмы медленного старта и предотвращения перегрузки сети для TCP.</p>
   <p>53. Jacobson, V., Braden, R. T., and Borman, D. A. 1992. "TCP Extensions for High Performance," RFC 1323.</p>
   <p>Описывается параметр масштабирования окна, параметр отметки времени, алгоритм PAWS, а также приводятся причины необходимости этих модификаций.</p>
   <p>54. Jacobson, V., Braden, R. T., and Zhang, L. 1990. "TCP Extension for High-Speed Paths," RFC 1185 (устарело после выхода RFC 1323).</p>
   <p>55. Josey, A., ed. 1997. <emphasis>Go Solo 2: The Authorized Guide to Version 2 of the Single UNIX Specification</emphasis>. Prentice Hall, Uppser Saddle River, NJ.</p>
   <p>56. Josey, A., ed. 2002. T<emphasis>he Single UNIX Specification —The Authorized Guide to Version 3</emphasis>. The Open Group, Berkshire, UK.</p>
   <p>57. Joy, W. N. 1994. <emphasis>Private communication</emphasis>.</p>
   <p>58. Karn, P. and Partridge, C. 1991. "Improving Round-Trip Time Estimates in Reliable Transport Protocols," <emphasis>ACM Transactions on Computer Systems</emphasis>, vol. 9, no. 4, pp. 364–373.</p>
   <p>59. Katz, D. 1993. "Transmission of IP and ARP over FDDI Networks," RFC 1390.</p>
   <p>60. Katz, D. 1997. "IP Router Alert Option," RFC 2113.</p>
   <p>61. Kent, S.T. 1991. "U.S. Department of Defense Security Options for the Internet Protocol," RFC 1108.</p>
   <p>62. Kent, S. T. 2003a. "IP Authentication Header," draft-ietf-ipsec-rfc2402bis-04.txt (Internet Draft).</p>
   <p>63. Kent, S. T. 2003b. "IP Encapsulating Security Payload (ESP)," draft-ietf-ipsec-esp-v3-06.txt (Internet Draft).</p>
   <p>64. Kent, S. T. and Atkinson, R.J. 1998a. "Security Architecture for the Internet Protocol," RFC 2401.</p>
   <p>65. Kent, S.T. and Atkinson, R.J. 1998b. "IP Authentication Header," RFC 2402.</p>
   <p>66. Kent, S. T. and Atkinson, R. J. 1998c. "IP Encapsulating Security Payload (ESP)," RFC 2406.</p>
   <p>67. Kernighan, B. W. and Pike, R. 1984. <emphasis>The UNIX Programming Environment</emphasis> Prentice Hall, Englewood Cliffs, NJ.</p>
   <p>68. Kernighan, B. W. and Ritchie, D. M. 1988. <emphasis>The С Programming Language, Second Edition</emphasis>. Prentice Hall, Englewood Cliffs, NJ.</p>
   <p>69. Lanciani, D. 1996. "Re: sockets: AF_INET vs. PF_INET," Message-ID: &lt;3561@news.IPSWITCH.COM&gt;, USENET comp.protocols.tcp-ip Newsgroup (<code>http://www.unpbook.com/lanciani.96apr10.txt</code>).</p>
   <p>70. Maslen, T. M. 1997. "Re: gethostbyXXXX() and Threads," Message-ID: &lt;maslen.862463630@shellx&gt;, USENET comp.programming.threads Newsgroup (<code>http://www.unpbook.com/maslen.97may01.txt</code>).</p>
   <p>71. McCann, J., Deering, S.E., and Mogul, J.C. 1996. "Path MTU Discovery for IP version 6," RFC 1981.</p>
   <p>72. McCanne, S. and Jacobson, V. 1993. "The BSD Packet Filter: A New Architecture for User-Level Packet Capture," <emphasis>Proceedings of the 1993 Winter USENIX Conference</emphasis>, San Diego, CA, pp. 259–269.</p>
   <p>73. McDonald, D. L., Metz, C.W., and Phan, B.G. 1998. "PF_KEY Key Management API, Version 2," RFC 2367.</p>
   <p>74. McKusick, M.K., Bostic, K., Karels, M.J., and Quarterman, J.S. 1996. <emphasis>The Design and Implementation of the 4.4BSD Operating System</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>75. Meyer, D. 1998. "Administratively Scoped IP Multicast," RFC 2365.</p>
   <p>76. Mills, D. L. 1992. "Network Time Protocol (Version 3) Specification, Implementation," RFC 1305.</p>
   <p>77. Mills, D. L. 1996. "Simple Network Time Protocol (SNTP) Version 4 for IPv4, IPv6 and OSI," RFC 2030.</p>
   <p>78. Mogul, J.C. and Deering, S.E. 1990. "Path MTU discovery," RFC 1191.</p>
   <p>79. Mogul, J.C. and Postel, J.B. 1985. "Internet Standard Subnetting Procedure," RFC 950.</p>
   <p>80. Narten, T. and Draves, R. 2001. "Privacy Extensions for Stateless Address Auto- configuration in IPv6," RFC 3041.</p>
   <p>81. Nemeth, E. 1997. <emphasis>Private communication</emphasis>.</p>
   <p>82. Nichols, K., Blake, S., Baker, F., and Black, D. 1998. "Definition of the Differentiated Services Field (DS Field) in the IPv4 and IPv6 Headers," RFC 2474.</p>
   <p>83. Nordmark, E. 2000. "Stateless IP/ICMP Translation Algorithm (SIIT)," RFC 2765.</p>
   <p>84. Ong, L., Rytina, I., Garcia, M., Schwarzbauer, H., Coene, L., Lin, H., Juhasz, I., Holdrege, M., and Sharp, C. 1999. "Framework Architecture for Signaling Transport," RFC 2719.</p>
   <p>85. Ong, L. and Yoakum, J. 2002. "An Introduction to the Stream Control Transmission Protocol (SCTP)," RFC 3286.</p>
   <p>86. The Open Group, 1997. <emphasis>CAE Specification, Networking Services (XNS), Issue 5</emphasis>. The Open Group, Berkshire, UK.</p>
   <p>Спецификация сокетов и XTI для Unix 98. Это руководство также содержит приложения, в которых описано использование XTI с NetBIOS, протоколов OSI, SNA, а также Netware IPX и SPX. Эти приложения охватывают использование сокетов и XTI с ATM.</p>
   <p>87. Partridge, С. and Jackson, A. 1999. "IPv6 Router Alert Option," RFC 2711.</p>
   <p>88. Partridge, C., Mendez, T., and Milliken, W. 1993. "Host Anycasting Service," RFC 1546.</p>
   <p>89. Partridge, C. and Pink, S. 1993. "A Faster UDP," <emphasis>IEEE/ACM Transactions on Networking</emphasis>, vol. 1, no. 4, pp. 429–440.</p>
   <p>90. Paxson, V. 1996. "End-to-End Routing Behavior in the Internet," <emphasis>Computer Communication Review</emphasis>, vol. 26, no. 4, pp. 25–38 (<code>ftp://ftp.ee.lbl.gov/papers/routing.SIGCOMM.ps.Z</code>).</p>
   <p>91. Paxson, V. and Allman, M. 2000. "Computing TCP's Retransmission Timer," RFC 2988.</p>
   <p>92. Plauger, P.J. 1992. <emphasis>The Standard C Library</emphasis>. Prentice Hall, Englewood Cliffs, NJ.</p>
   <p>93. Postel, J.B. 1980. "User Datagram Protocol," RFC 768.</p>
   <p>94. Postel, J.B. 1981a. "Internet Protocol," RFC 791.</p>
   <p>95. Postel, J.B. 1981b. "Internet Control Message Protocol," RFC 792.</p>
   <p>96. Postel, J.B. 1981c. "Transmission Control Protocol," RFC 793.</p>
   <p>97. Pusateri, T. 1993. "IP Multicast over Token-Ring Local Area Networks," RFC 1469.</p>
   <p>98. Rago, S.A. 1993. <emphasis>UNIX System V Network Programming</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>99. Rajahalme, J., Conta, A., Carpenter, B., and Deering, S.E. 2003. "IPv6 Flow Label Specification," draft-ietf-ipv6-flow-label-07.txt (Internet Draft).</p>
   <p>100. Ramakrishnan, K., Floyd, S., and Black, D. 2001. "The Addition of Explicit Congestion Notification (ECN) to IP," RFC 3168.</p>
   <p>101. Rekhter, Y., Moskowitz, B., Karrenberg, D., de Groot, G.J., and Lear, E. 1996. "Address Allocation for Private Internets," RFC 1918.</p>
   <p>102. Reynolds, J.K. 2002. "Assigned Numbers: RFC 1700 is Replaced by an On-line Database," RFC 3232.</p>
   <p>103. Reynolds, J.K. and Postel, J.B. 1994. "Assigned Numbers," RFC 1700 (устарело после выхода RFC 3232).</p>
   <p>104. Ritchie, D.M. 1984. "A Stream Input-Output System," <emphasis>AT&amp;T Bell Laboratories Technical Journal</emphasis>, vol. 63, no. 8, pp. 1897–1910.</p>
   <p>105. Salus, P.H. 1994. <emphasis>A Quarter Century of Unix</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>106. Salus, P.H. 1995. <emphasis>Casting the Net: From ARPANET to Internet and Beyond</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>107. Schimmel, C. 1994. <emphasis>UNIX Systems for Modern Architectures: Symmetric Multiprocessing and Caching for Kernel Programmers</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>108. Spero, S. 1996. <emphasis>Session Control Protocol (SCP)</emphasis> (<code>http://www.w3.org/Protocols/HTTP- NG/http-ng-scp.html</code>).</p>
   <p>109. Srinivasan, R. 1995. "XDR: External Data Representation Standard," RFC 1832.</p>
   <p>110. Stevens, W.R. 1992. <emphasis>Advanced Programming in the UNIX Environment</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>Программирование в Unix — детальное описание.</p>
   <p>111. Stevens, W.R. 1994. <emphasis>TCP/IP Illustrated, Volume 1: The Protocols</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>Введение в протоколы Интернета.</p>
   <p>112. Stevens, W.R. 1996. <emphasis>TCP/IP Illustrated, Volume 3: TCP for Transactions, HTTP, NNTP, and the UNIX Domain Protocols</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>113. Stevens, W.R. and Thomas, M. 1998. "Advanced Sockets API for IPv6," RFC 2292 (устарело после выхода RFC 3542).</p>
   <p>114. Stevens, W.R., Thomas, M., Nordmark, E., and Jinmei, T. 2003. "Advanced Sockets Application Program Interface (API) for IPv6," RFC 3542.</p>
   <p>115. Stewart, R. R., Bestler, C., Jim, J., Ganguly, S., Shah, H., and Kashyap, V. 2003a. "Stream Control Transmission Protocol (SCTP) Remote Direct Memory Access (RDMA) Direct Data Placement (DDP) Adaptation," draft-stewart-rddp-sctp-02.txt (Internet Draft).</p>
   <p>116. Stewart, R.R., Ramalho, M., Xie, Q., Tuexen, M., Rytina, I., Belinchon, M., and Conrad, P. 2003b. "Stream Control Transmission Protocol (SCTP) Dynamic Address Reconfiguration," draftietf-tsvwg-addip-sctp-07.txt (Internet Draft).</p>
   <p>117. Stewart, R.R. and Xie, Q. 2001. <emphasis>Stream Control Transmission Protocol (SCTP): A Reference Guide</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>118. Stewart, R.R., Xie, Q., Morneault, K., Sharp, C., Schwarzbauer, H., Taylor, T., Rytina, I., Kalla, M., Zhang, L., and Paxson, V. 2000. "Stream Control Transmission Protocol," RFC 2960.</p>
   <p>119. Stone, J., Stewart, R.R., and Otis, D. 2002. "Stream Control Transmission Protocol (SCTP) Checksum Change," RFC 3309.</p>
   <p>120. Tanenbaum, A. S. 1987. <emphasis>Operating Systems Design and Implementation</emphasis>. Prentice Hall, Englewood Cliffs, NJ.</p>
   <p>121. Thomson, S. and Huitema, C. 1995. "DNS Extensions to support IP version 6," RFC 1886.</p>
   <p>122. Torek, C. 1994. "Re: Delay in re-using TCP/IP port," Message-ID: &lt;199501010028.QAA16863@elf.bsdi.com&gt;, USENET comp.unix.wizards Newsgroup (<code>http://www.unpbook.com/torek.94dec31.txt)</code>.</p>
   <p>123. Touch, J. 1997. "TCP Control Block Interdependence," RFC 2140.</p>
   <p>124. Unix International, 1991. <emphasis>Data Link Provider Interface Specification</emphasis>. Unix International, Parsippany, NJ, Revision 2.0.0 (<code>http://www.unpbook.com/dlpi.2.0.0.ps</code>). Более новая версия этой спецификации доступна по адресу <code>http://www.rdg.opengroup.org/pubs/catalog/web.htm</code>.</p>
   <p>125. Unix International, 1992а. <emphasis>Network Provider Interface Specification</emphasis>. Unix International, Parsippany, NJ, Revision 2.0.0 (<code>http://www.unpbook.eom/npi.2.0.0.ps</code>).</p>
   <p>126. Unix International, 1992b. <emphasis>Transport Provider Interface Specification</emphasis>. Unix International, Parsippany, NJ, Revision 1.5 (<code>http://www.unpbook.eom/tpi.1.5.ps</code>). Более новая версия этой спецификации доступна по адресу <code>http://www.rdg.opengroup.org/pubs/catalog/web.htm</code>.</p>
   <p>127. Vixie, P. А. 1996. <emphasis>Private communication</emphasis>.</p>
   <p>128. Wright, G.R. and Stevens, W.R. 1995. <emphasis>TCP/IP Illustrated, Volume 2: The Implementation</emphasis>. Addison-Wesley, Reading, MA.</p>
   <p>Реализация протоколов Интернета в операционной системе 4.4BSD-Lite.</p>
  </section>
 </body>
 <body name="notes">
  <title>
   <p>Примечания</p>
  </title>
  <section id="n1">
   <title>
    <p>1</p>
   </title>
   <p>Все исходные коды программ, опубликованные в этой книге, вы можете найти по адресу http://www.piter.com.</p>
  </section>
  <section id="n13">
   <title>
    <p>2</p>
   </title>
   <p><emphasis>Стивенс У</emphasis>. UNIX: взаимодействие процессов. — СПб.: Питер, 2002.</p>
  </section>
  <section id="n21">
   <title>
    <p>3</p>
   </title>
   <p>Имеется в виду адрес, записанный с помощью символов подстановки. — <emphasis>Примеч. перев.</emphasis></p>
  </section>
  <section id="n24">
   <title>
    <p>4</p>
   </title>
   <p>Иногда переводится как «экстренный режим» или «режим срочности». — <emphasis>Прим. перев</emphasis>.</p>
  </section>
  <section id="n25">
   <title>
    <p>5</p>
   </title>
   <p>Также (ошибочно) используется термин «указатель срочности». — <emphasis>Примеч. перев</emphasis>.</p>
  </section>
  <section id="n26">
   <title>
    <p>6</p>
   </title>
   <p>Также используется термин «срочное смещение». — <emphasis>Примеч. перев.</emphasis></p>
  </section>
  <section id="n31">
   <title>
    <p>7</p>
   </title>
   <p>Используются также термины «расширенные заголовки» и «дополнительные заголовки». — <emphasis>Примеч. перев.</emphasis></p>
  </section>
 </body>
 <binary id="img_0.jpeg" content-type="image/jpeg">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
MYiGCKuXqK8V0QLIByjc2CTV3F2TI5aQ7CWSpUgaBuWOoSOnNDAJrGDHKgABFJ0KGAxFoOHR
JreAuekmoouGgaYU1ZBcyiuROgmnJLHDm5EMJuMlAyycdqVBcATVJ+883s7856+f4e/09nGT
a1EgFkgLhlBQQOiJpA0RSpUqSHaLIQ81OlQgExwFRTnIhyUkpijZYRbIphCuZaVRYFTF47Sz
SQNqE1dDiqgDJhVCa49BJ9MZMWfzayXFy1F46bVReOyupx9Jk5b5vJvfSnzD1erqNzwG+1uN
aTnfqfDbzr+i9+2xrn/Js90ny3fXudzxeHtPNi6LmvpnJ2c10mXqZea82326/Luiz4LOv5jp
9TNajTfRNXc/Pt9i7yOXxbnYr852HT6OyzoPZNclpev5bWNhi7Dwy8Vufb0S/OfN6evudNk6
rw5vK6X6pw25o051Jb3uae3rdbi8/pPpuks4XdX3C8lh6T3r8y9PWaDWcp0fvxeN1/Z87qaL
c7ncy8j5Ot9p8u2ubd7mnrsdVzvOaf6jy2ppvd7ukl+Z4ff4umZ2Wu7PN1Wq+j/Pc3Ya/wCg
aaOV2e02NfPMeSN5Kc0AcOiuL64WLJFiuMtQmRcjqlJH0zzenzcumj63keuriPRirpzvY7nD
y6e7jey4rWZzc3utTN2kLGuQ3nG9rZsuF7vgl+g8x0/MyanYajs60HX+T251z+v2Gvuet4Pv
eBrt/P6PPm812nG9kvB9dyXX6zXzP6Z8zs7Lo+b6TGvnXg9/g7Y+n+L2nHpj82z5auS6jl9t
vG2vb58bz8l13HXPNTcbydXyvVy9XwH0D59nXRYOQLnqOu5Dr8a4br8it83zr6B8/wB43/ac
Z2mN8Zod/oevP6Flxe7l04jtsZJy201G302/Dd3EPnOh+f2dD0Gg6HN+f+H6UanzHtNRtjef
NvpPzaXtPVz/AFkvj0XUfP7nXgumUgKSfHoqjJ1xE5MdiywUwIJyQJp5fS8PoyY6cr2Hh96c
R1XIdfqXyfY+DN2nFdrxmnsye/YRw/e8x068du9HubnbcH3+ql23M9NyqevodBv5eA77S7u3
n/F7ddc9fwPfctL0Xn9vgXnu04ftzguw0nQJHzP6V811nr+m53o8a+deL2+Dvj6f4fd4OPT2
+Lyb2Pln0XhO/wBZy8h2Opl3XH9hx6cuI6ZfV8r08vYfP+/5LGvRg6nwJpOu5Dr2uD9eTp7O
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
RVZWe7UsqtRucFbr0xkxFeynWVW7UnV1oqt/W1B+TdqSqytVZbKrU8KTvDd1vDVnZDyr2U6x
ge7Uj/8AN6dFdv62o2V2RXZWqu92+vX9kDtJrOrZTgrPdqSrV6dIqtTq1Oyrdloqst5U7wWC
5tqzy3iyrZTrDs6Kt2pKrrRXanRVu1W7LRXZbs9YLeGsVb79tWynWLHu1JVdaPyPm1OipWbs
tFdlu1VkuLrNE26F7Ktn49XQrPdqSrV1o/Ju1OirWC0V2Wxjcu1K/iO1OsUMXvRVsp1dKb1W
TgbbsjsqstD1hToqstjwq3andm8VjN2iLvCvZ+PQ8YHSQ7QxKDsqRDFq0EMhi0kVIgS5KuEQ
Jc2qXJApu7IV3gvVVso0TwIjofzk81eObserbOsHbZ1ZD/Qq2U6usljF0L0vBDwVux46EdYu
1eynQvR0IQxWd24PJibbuvQrzi7/AHZi9FWynQ7OzE2SxvglspJZyip8EspY29DbQ3wKehPk
bOSXHBLFPYmxuWPgllLmz4JYmNksngllMsdT6PJoqfBLE5PJzZsTbE22S5i9WyiyHZi2PY4S
OSnZrk2yrQtMp2OENt7HooFseyop0djEIcdk9Ip3Z6KRIexyynRVsS4Ox8j0TwUoezyHopHw
yn5vVso1ktjKieIKRPkex6J4Kdj2Nyz+pS4Fseyop0Pdls0yo6KbPQhOWPY2U8j2LQtjY9C0
ynY9lR0Ui5ZTu9WynWVO0eKkaIEiEeKkqEpQlB4yeBHEHgJQNHiiDxVvGOSBo8UJRfxEoGjx
Qh0y5s6RUjUniKmB08niRJ48CUHjyO1WynWKs3xdCxSzQjXtYx3+/Q92q2U6tI92Ss/i6F6W
dWZ1k9Ev1T6HhVsWsEPVmM6KbVbHoSkWyp8jUC5RSxbG5ZVohxJS+hvkq2dCYtlWxsp59UYI
6xRVsWsEPYtj2M6Kd3/qUFGyrZUU6NM7Kdn5OkKYEoY9jns/rZbKtmyk+hndurM6zjFFWxau
rdC2PYzop3ZjfBR2U7RVsbkXFMlOzsfDKvkT/jBTseyvZ/UWxbKtkcFGUYfWWiMKti1dWZBA
xLgXAqUQmQhQiEmMhESJJHih0pnUHiiIPFDSfJC0JJHhA6RoSg6Oxex5VbEPB49elDtN+sur
d5r0q0Wq2U6Hfq0+jY8FafSx27uzvGME7TktYTZkjYmTsTZPAnyS0N8kkk2dtDtB9itu70K7
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
134t7InTp2+hHumG8ot8/wDS3f8AFbcMrjrVnNra5OOXNM6tbde//wDhaPh43XKVLq9d5xuf
d7F8GrPdVe9q4Ltwv+zZHp3W9R8qde6m/wDed4/Z3nfOxh9U9Lrdfbl4etP/ABuxw7xH1b/u
uqr/AOvXM9dj7sf/ALVXLquE3UlZr2uTfJsBD/wf2x58duqOF5/P3d+HGPr71yetfPHM3Spb
PXO0+ivOSc3Cldrf6x4ar3J9c3oXP36Wdt3XBvss5Dx+Djj/ANfp31pU1GNAvvT6ew7nGtq3
K1/e3dXzu1/9b+b3R/H/ANHc8XcGbPv/AP3zrWe9hvXj9t8XzWV5ncd1a9Helf3jfXTXmuLr
im3G0T0u4kLkavYr1wf6e3ff1WkgB6wZXu+fl091jFPxSi/7oZ52nBba9CV2z8PI56i3xefH
t6G8l+/8mzNuJ9EmW7Dqu2xAasX84NtrtmfP79X/AEoIvZeCs7/QEm/P/PlRbj/djHPbr6N9
58S5l30+Wvjt357c1FSb5mXn/Qv4W3pG/RJzFYa1k73c2t0yZ8+WuzMqo7n7y3XbKOdW+Y8/
M4IgfO63mYJu6+9qB4VJZj+8zCdQy2+er38/3hOP6OPdzT+fN79f7W7/AF7IfQ+v+ez6/dMJ
fbetVtT9JaJtZZqrFxe+o0zm/R5OLK6N2lz/AC9sWIn/AO1993eYb9Kd6u3atr73N67RF4p3
Vf8A1GlfLZdfnCMPOZ9eefVq0LKIeLmGtkil98vf+c91/cb+/wDFeXrX8O9A17PIT1o7/wD+
zwVqg2EOTaD0/eN4srwNTzGvdHewzsSEzgWVrlahJt8U6tr5m+buO+rp1rh517mmeWC5oYzI
Sj89vkTe1zME9OpyoDGu6zWX7r9Cb7xx055n6DWf2DxUurXfk+tXxthazKtTS933/wAQ8UsE
5b9UvhoNLt4NkenPv+x+/SH65bz953m67j3610xSre86++W2Wroy3jL879W+oHfEWaCKtqSo
y86BxuE63wdj2f2r98Pr8++ne3TVUZBuH0rzPuhzO9VF/wA9dt2vXLoCqDXUqisV6JtbPVCU
6d80daefoEyFnnVn2PudRdCwd5VHborv/wCuqfPdt+fmaVeNvMWoHF6N7Ur9zknfaZ84a+3K
fNs418329mNOv6suK6uenPatyx2f7PTR+I5zu3f17d7pdHWY3PfonnxpiI9miU9Q3OkVfb4g
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
RXyf+PH3900C+5/0/Egpz2/q5z+2m/jO7K7tLRO/OKj0cP68zqcj5uA65mmffIeu3fcsu+8x
H1z454K7wb5FPb3xe7zb90yratW2nr5s/RTerbZ502a+bY1/515qya7n/uHZXyrTw/XP3Kdp
9Pvnb7MPf7MYu1oP7SV65TjDXhmfOXLZp5o0+5bx/vlcxm5lpxv3EMUvbqw1+ydqQcYNxI2P
VOfwbXVKkhTfT5fF+x2okV3k/u26Xnanc483rd0/dzep+/8Ajie/8pRDdXvIz7/14Lr+y7bw
2uMx/wDTmufxbRX1Me+7v/1rX5fAefsNxL833Z7Hh8SV7nI0nX5QrxgvR3Tv1Dqq930vQHfO
e595rx+xmi+w79xOj3eNJay8v/TZVnx+Pcfv92cJTxwO9s1jGm5N7TnmuaLNSc1/g3ZJi26j
uOHEdNuP7jr27zdLXFrgLXnpRM9kqMwg/wDzqHj1XdD+5ydyeP2jO0Ii/wBwq/8Aa/fr5R9v
pL0S1/0M0ei20Inz2LcIV/upq9ca9r+aF9WL/O3ut9rv8s+fm5/x7K+QQ/8AeL/e/wDL/rip
zNZ7oinM7ddgTqopASY97f0rzpku9H6NEcVl978OR/vo7PZGt/8Ay2wL11dSewq538rsm6VM
7znlce+WR6U++RGZdfvzmmE5fiHtfgffF6+M1DCow4XPHXXvW/p2b1/8+2r2ruZzX/8AhLV/
PPvtff8AudXf+vrevnjuf3zx9+6zMyd08U8O71+3+Xe4+kf9Pfa263fZcnfa3Y91zgsHoX71
r4sIj/8A7ptnf+Jv0O91ca/3ff8A62Oe7z6r5DhcbP8Ameqj/wAi2Zvzcvf79STstmdzg9/D
u8d+irxOkJG0C/5Ab7jw/wB55pPAZ5tPnvq6cztO/wDPJd4xHNTZe7nfq72lOtb3E9ru9cj0
+F7fJS/nHuDVniFqut9LBvLsWRtQPYfVr2ppS/i8bdE6yYLf4E8vkHIZSCZ3r58rfru6m3v3
yr/YOlLWTXZWc9sG/wBmxzniOz047E/itbYt5d7fmn8f8qqfEXeFvP8Ahl+PufaOvP8AGSnr
HfcMdvn4H74Ine3XxVzYVvXCD1VkgM/Y7w8//vdtVkWif+5r3Hk+YXPkfS+lta3Tp2fHmqEm
NtovoH21we+uz8q7D8tZU9W7v3/+P9S9RGa6mmcr3r9tCt//APmHbvcrnFeXLb//AOZ+/wD+
77PXf1tqfb1b6/5d3ndt8+88beZ1jyVWiRTAPn//2Q==</binary>
 <binary id="img_1.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAOsAAAAoAQMAAAAluJ+MAAAACXBIWXMAAA7FAAAOxQFHbOz/
AAAABlBMVEUAAAD///+l2Z/dAAABKUlEQVR4nGNoYACD+v8o4JH8ri8MRvUPGBiwSh+AidtD
+GjS9yFUw4iQ3oxfejl+6XXSic2HJJLycUjz3dbdfMs7yh+HdL+E4eZ5EkX8OKSXRatuvudR
pQ+S/mMPlf5uD5PecNpn8y1uD7DdP+dDpb+BvVy/////TdKJ1o+Ek4HS9+//vvOYrxAk/eGh
mbnz/xqUcDl+//cnz9xtIOmPb1Uup/9PQJE+eP/3B18OsXqQ7rMsAun/Gez//t3f/jf9BqsC
k3T9Afnfl30Zv4Gkv/mKXAJKowL5n5M9VaeBneZrMh3d8AP3/1g48xiAPSZrZp7/vwJF+jAw
WM7//44ULPbI0ueB0u9RpP+jAGyBOipNLen9+KUJFD0gffv3n0eV/f+RgYEFKKkAAH8U4JER
CavVAAAAAElFTkSuQmCC</binary>
 <binary id="img_2.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAOQAAACxAQMAAADu2oOKAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADC0lEQVR4nO3Xv2sTYRgH8DdksIiSjkGRyyAFUcRJEIWr
OJgOLm5uKg4OaagIjl462CxCoWOWOIiTf0GXi3aI4HBzC3Ktid5Sbc6rmuLb97wfSe79dV/t
IFrwXZK7D+89l/d5877PS6wwbvuhrg3In1Z6CPVvjVWubkP9BHXr9Zv5I7naa70959JCXt/W
FHHp6O0U7bfOPnVpI6dv/8T96MmdHGWX6bxLN3OenHxS30AadJH+8JGGVBOYy4KJ+oaawFzf
AKpm4vHZVwPz6kDtQR1CZUpgYU4uq5q2ZOJ4qnIXe1CVwIKGBlQ5EaLKM0BUeQaIKidCUgdq
AJVClcZDUikRsvagDqGKgWUVA8fKatwNT1ah7UEVEqGoMANU7UL1oVKo/AzQqAO1B3UIlUvE
gDSyf9moRYnoRLeOVsn4P8g/IkpE2oUsaHa6KBF2+m2sjNMoERM1FY0SgbS7A54c+h5Seh1p
WIS6CLUBdfRDcnQAFY7GL3VojfRj/IUJa4JpB+ZI+0nfAa8l2/OXymuxfljbrjeZsP8WbK+/
cvNqrFsbc+cdRoRmepur08dTrVZclk6dd/tfbpeePIz02Sq5kcTdmKo4TNi7i3Yws3JtLtHW
mZcuE5aTkj18sFSO41qfT9bqLhN+sGXHA+KAscKajoZ1CNXUKEVKCVJWgE8u/qZq3nnYnCgh
lWlSFhamwIlWlUu7s2SB3/fHzXE74zUnVJsx/qJZ67jFX6fZ+q1TD2obqbDGKorXZ7y2LyPl
91hV+T/UQfcyB6qBFO+/QhGlqLrv17kbbUUfZ9e43sC1Cq5zYI2E6yupSD5QXdeFaiDFtSiu
Y+XiW9S2oo2sZtDU3lcunL5b7R6LL5QDw4BfVZTDhqCa80KmurNGprpzSqa6M06muvNRpgZS
zYGQU81hklPteXCialhOmRqWU3UsOA3UsUj0m5WokoJUd5O+7029fn1Vu9gU931O/fU7Lxy2
mKuzxGG6HSCJu154HqmV8863KjMOM3L6fj9179EO06E46/7rP6rqOYXXBtKfaJC2sOnNJEEA
AAAASUVORK5CYII=</binary>
 <binary id="img_3.png" content-type="image/png">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
tqR1NczidMBXWfoNkzddbU4tJOR9wLKjy4Ctt3+x+tg3MG/TJ7ns20RMyXPSzuJbrT2tuvT6
PkbXBHcl8iQi/f1YSGPrHdThJEUDsRpb8uS0PSltFatvTKslN+pJyZLJiklvW7IC/+T5Swn+
ZI3bQ9Du4CvSyH5dgp7Txe20S9DFR5Vdqj9O7M9L+eVc07U7DbjTiPW+cqy5tmvofvNThZfy
vusaubNKmj7aWtm4/OPMhiNTr10ZTM2rklby/MOP9ns+fqihZeq162ZDoTTtrT722Pc3bX2o
oePSHVfMI7/YJEn75+1fg7u/mGx45fLO/uZrr1TgThMVLV+SDkiJNbtYjZ2PWZPSyeUtWsf9
Hg/2IFG/9PfCwq8OHa9I/X3Dhrxmlf4QbpJF1trs150lZuNZ7dPrO7omjSVbpLTaCyuz33X2
ZOrbtPvXO97P3Lxni5RWe2ZP2jszPX8ytGn2r29+f2jzngwp+nW5Zu2VkomKvo1PbdCYZfqU
Asxp5DZvneum914M+K/56Ppm1VDraUkaLBwM/WhD01oJJQeRn1Szoe/SdGxKTscnKXdoctoq
Vs+gV3MmOLRWcV+5Veqk8o5PPxVNy1SNssZlMtkuHVigdcQlPpIWlSjBktNqMJdOVQCfHJhk
sg3v9qvgycvBV8+tCM94fjyt9n/NJUdDMvrf8NMKVKLaPVz/nEeXqJ4Z1jTqE9ahZaKEtGtE
wy4TJaiLeo26RLWj7JVeU8JaIN1Q/V8Iw4nBTI+aOgAAAABJRU5ErkJggg==</binary>
 <binary id="img_4.png" content-type="image/png">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</binary>
 <binary id="img_5.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAcgAAAETAQMAAACIlgRFAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAALK0lEQVR4nNXaDVAbVR4A8CCxBBtKLE5NS2tgRKo3dC61
FXVsL3zcnEQTq9aZO66DNNOrJWe5aqVYpqfhAoI6lXrgcJh4DdQMNzfjOJTW1F7B5Mhd0an9
OHpT4aiFHhXa0QolyCZms3u7SfYr+5G377TWN0NIXvaX/9u3L/vft3mKazYc852y4bKLYkbB
FFlegbBexCTro9LkSfLRQPyFcRzhbZ9Uxst3KlEdrIychpYflyx8zzOvi0p0Sk5rb3ry0VU9
c66oxKblSO3O365SrI3FxMosTfdXgrZ2ZmdVrl0Zi4kvVR9uB5azv7b26/8Rl869Sk0pcMyi
T/oPL4m1Fj+m1g+VA0q6RPfzwVxluxFK7n6h5f6HYSQ5tlnm+kiqfK8yMtWxRbv6oAffJ1fO
lKpKVTmdnvKVciWirQ4Z3S29j6cRMpjrUT1SatWDyY6X/p7e2dJbRMpQwYETP3Fa80Fjdljd
7l7TNkKG32/yd9lVOYD7meFLdXfWmzNJ+V7riRxHVQ9Y3+Iu4tscHrORRyXU9ebHj9xk7ZZ1
PGPy3X50dFv1+xAjgTqZXE9Jl+9XXsRH8ate3A8hlR2WezI8DfLl1bVOk33rYSeErMk22RU5
RGtnUl5NyNnJYm4y2a0HCDlHiDUqhaL+VkCp9FvuST+QDXFUMpFAxIWEIaQh/l++pN6+jmOI
LF5KRqgNQKWLkigvZ0/HdUBY6iiJlREZqZItpwAlvik3bwlHTg5qn/eIthbLpGU2kbM5WXDW
rrrrMIh07m3UFLNlsMOieMsmJtE0Wh5V33eOk+1DHZsVTbrkEnuAuKrhXGGEHNaCw2OiMpWO
ycu8gQZV7mHRmBGvuIwd6+RjiC7fhcRvVInqB9XFWsvxMYuEHOd8PCUXZ1nvNeb1/sdMS7+u
5/jQ/RX7GOljPk5Hy0h7yrPIqwX2ZUZaOoz6oafaytMYeerFjEDL80hdrNMp+Vnqs/OqVQuW
Mq11XM53KhVPrGTJ4psvKjVKfeUtizcw8twyq8dY0DtSzZbHlG8Ws2NW1NTV7D/Z/nRVK0ue
8SlrVaamUWY/HWXdjtvazCs5cmmNpmxJSZ09kzkqCG/u4FjaMjCUZd7GkuW79uY1nWz/1ebX
f8qS7A6PPmJUTzKypCZXqfh0Qckty3ZIjQQMT5TREsZ2DLoqp4HGkMgZDEDeeCOeqcDkSf4M
XUTOTCVI4NaONyzsIs+k8uWk41l33xiU7Piycz1Ua4mY3R9CxZxvSM+p18FIvPmdW48buDIC
Jl34WPwZLfnZXlAyhZbYdMCFwMkyi7KwEkbi2Rl3Ac3tqXcY+dBysLm9gSePme87tym5xHgS
221RLhGMyT2bYDqeDCKxzZPF5EmBbC8o0UyepMr3JoPwkrqJJ1smjATyvDTVF6uVJ8k3B7Tb
G3UA0sCT5x813eeCku+oitaBxOS2ltzPjoBpPVzMAU2GGyQmXyJH8VOMAW0tyPFEFJUiMZPJ
QN0YpAyGZcv4NxuNS+odIGlgx/Ry5LXJcSLnphQJZ95gVH67R0jyczarInU8KgMPCclv7rbR
F058WU8+GpBjhPQRz+oVKfWGyA6XxH5itui5CfHdrSFktNrH+kTy5KCYEpaxsxoyY2C9jRvI
Zp5qnlHoJGLGJcqR1H7GWisdk95YvuQUrkSw1PjrfXIlncPHBPo2WhHcsr1oj5A8rx/VblGt
6j8iJsPG8nxB+Xm7o9SYX92/UkwGPtcWZgvKDnu4ovbiK2li+xn+s6awVFDWOwIVtVc/uF1M
Bo2Pikg32dqrXaISnaiqXSjSQ9lbFl1944jsoxKhNqekz59KSGxenSlXBt5+MCovTSfMVxIl
UyjpPUrKubkESefPmBQYtwFvQ0wmtBblSsGYzaSsndOJXCeIyz5vVF5KvKrhSWx8Ql3oZcnX
olKpFrseYuSQ0VroYiTuJyW+aEXi8eRJ/JcVqpxmlhQbCVxJ9u3tFSndbQDSwIt5rzEXSFKF
kT0T6lqQ1vLlmniNtER1PIm9CCSDfEkXaRk6vwdWdlia4SSavX0NpByFlSFaeqNyjsi7it/5
NJO5FxXSMuBLSWPHlMz23F/CL6XuY8fkt5ad7TkSkXEdD3/f5Ia84+KClRjZ+VNQErFcqAvD
SBS9tVGzEUpGnHlNT0C1Fs1WpnHuqgPLL0bXNsn5JZyWuDq7UTOSIL8yIBAjASvCvZd1fpgx
9Biuu3J6DCam6V7dJ/5UqNa2uy5nl8LIDf/WXfm2Akoe110JjoCMBMSVIE87P0EB+hbzFavz
XTjneIajTFhGDtFyY4W1my9xgN90NhLZvgEHHn0smVVhScjZkhJjpJ7YTxkxWb9drUYu4IPg
Eqclxs/20lJHS2pb2ZIuoNL7A0j8B5MCd9XDoDFR8v8Y8Tcbk//fujewmNdNUm/LlvS1qmzJ
WTEXu7wdBJTkirm18RVz26JV04yclpBo9pPGAv3cWFSaCgczv+5HGDklJR/YWZVXvzYzKld4
2qxf9/+FkYOZJL5gE2zttZ1VOa3KWEydZqJq/A2WPKvKsavK1QZBOft4VV9PfMWcaXFDVc4f
/sTINmuf4+SwRVgGi67QK+Ys7a3WgoMs2WDtOZbhe05Q0oWUv1lSf7nglSOMPGrtJmN65Y++
B62d9owRVfKYPBnBD+1bj++HG/He6NMb41sW9CtKVasPejZlCmd7naic1VaPGt3u+Io57Cvk
jpfZkr7jxpdIRySQ7m7pfSItnu1VnBk6avvai5PfHiH5kj+9Z29vUSzmRnO6hy1V+gHt0oxG
v1BrS8yOFLe71Rhb33f7ck62xyxNy85qtza2CfbQioHU9w7Wl8fWFGaZLd1OtvT97Axxtm2Q
OCo2wWyPbW1ynN1cbXcml6vpl7FHtX5guC69SSpmPGe/kCD3100FwxnzgzfI6AOVQnNBrOzz
RVobTExssTmvVVzGR66gXFesPA8XU1OsPGMTzvb4jD/94PHlFhH5pDlvKCEm/bv9fLk1r9W4
SkQWVaScEWvtfLlKYZ/ogjgq88PbOxdPuCHkZImC+AbmQMjxh1f0iPaQpJzWxbsrmfRGn33J
Op5jYDJ+uxIBHfHX1KsPxST6yr+ODE/8ohxUXlK+dWdMRj54q2fIuCRBDorKaWXHu5Rc3nmm
Ul/MldMSUtsQb61Sa9c8zZbhLpXFo0kTkZN5jVl49I4opj7XdrYsy8zI0CpnwYGRIyJyPOWP
8R7C+84cGv7vWZaMbHC2HAg1Q4wEdIezpTXUBiGx6rfzWkcaYGTgQ4s3G6q1eHwSIFuGoCVd
kuaVyxPqZ7xQcsB4T+LcnsixIHLjCpWCe4ebKCEAiWctV3Dn9qGP9I2FNhBpTJjbh9Y1nhRf
I8qS+omEuX1w/fjwbSCSN0MP9k+OvAkgedkeD/U1nm8FiUkVWkY+Wn2B7CGRu7ASkio/risM
aflPWAm8Yo4nBaoA5/b8qq92J0QGloDlRydjy+NdLqayGVBGF7RiC9RU1SlbpgTgSgz3YrNf
0HLbopeCWz5d43luypBEPqO13IzO/p6q8pUoNcHiBZvdhXap4KT8+QnjLjRMt9a3e1f7HlOh
6TWPY4O0jDx1orhmXeAS3dri5z/bY6p9zNn+tyQxI7f5zbv2z35Dy/Kt7XvMhdv/2p00Zrum
rOaOBUomplofLl5gcecn209ijnQnamMfT/waPjgmgRiJGBJqEfw0f1MhyStS32hpCVIEZWRa
qBZIJt9LMbmva/+LU8Mw8rV3lYVFGhuEtK65qzX9ZQiJVq25uT5FDyOvHNrVtLAQRn55SJ1f
+hyExKOrecOSUEomKT+uM/X/AFjqez2ffGDHAAAAAElFTkSuQmCC</binary>
 <binary id="img_6.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAYYAAAJJAQMAAACUGAJaAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAATMElEQVR4nO3cfVRTZ5oA8IRESIZgInRrUGpCG0PdpTVA
148tEJDTKVlBZ87s2aPOYpOCH5w6BUp3KkW9+fAEaalg4rEE0DAOS89Ou67VHlvFmoS0AiMK
1T0tVmvAdEh7lprgnTHJJuTuzYdIyP14b1cdZ4fnj8R7k99573Pve1/ufZ9caQjF8NPuv2h2
GhGf0wcs4DFGXMKt6l42sLAPLMh+anCrrhRYWL30+bWDW/WDwEIzSVfX3pAzmoCFCl7wcq2t
OgW8DZWdn11uq1g0ASxuF9DjEmxyNQdYwIWaBu5QTV8asAiFE0GmqAkHgoAf89ggEhOYa4mE
mbK4hBxxBt9twe9BXBCxhrU8ffzLwnnpLiuNz34vnVy0VmZkjn8pqyi5s3HH7yvf45ALpVxI
Gx/+l9dL7qTt6t0upJGLRZWZz4wfKENF2Y6blR8kkYteljB9NAXNY7SQM17ZSZ7HjfElkzCM
WA+fvFluRI87TCqQABR8taEdC0KG7q4EE2iACcwgF6q+hJfQ5vLAhUXPKBYgHdMDFpHYL3As
Poa4V9evNSJNY5PnvQ01hMKlYD6+JjHzpBt6be2RIc4fRtUtTAmhgGWiL2svPP1bNxRfvKQ5
M2k8WyQ7SCzozOGXE4Uad15+f2A+0+vMFg4ME4tS0RebLjz5tnv1nguBdRXe8WzxwCGIMI9C
2pU1rPh2t2YXKlxel7ZlkDgPF3wYceyWBsceXyCAjibOcB8g2KrQqylmPb7w4ax/EP1qdmtz
Yk7MiTnx1y28D6GNv17RDlEV5ocgRqOWHoxwURaTD0FQP4LRMSf+sgQ8ptIkuCXWd8DFqOoA
41a2TAQs7B5Dc86EWLYZWFi8Kk3W0Io188GFRzWWNSQp2AqBCtVEa2rOUC4fXFjkKhVjSCIT
A2+VL11lTXBKrPuBRSjQO1Q/NeGmPPsRFfdXuG0zl9D+RSpg08wlx+kcUuFLb8rSlr7Twlyl
XnyswS2tJxXeXy3tVMuWHhrcq1ubqXdD5G34178u5MuYCYNiBvtpjRsizyOwvoLWGhTxcSxh
ins1RC4uiphtsi26Um2LTKx3f0guPGMciZqehN6cqtniBreGXATDGLPm/ovYmBNzAisezWvR
uXuDOTEn5sScuBcPY4R7GG1cNVEVZillAVEVo9RF1NKDmWegLqgfQeoiOuYEdsB2hjZBie5d
3ycKLZCwW+hxCRZUuFOX6iAQYfVmJdfaWRLFncX/pgcTk4x4VPAKHYb0BiCh8dJ7ax1sPc3x
SSZYG6p+fnb5OFtDdxg5OWBtFKKZ32S3FDpaRWBtwOUMLddBb1W4P1H0AYl79G6BGFigg1wA
oSRmxH0WTmnoTRB8MVs1AKIvLEKvY40GADEusXLye/4H4SteXwYfPg0gboquLO1ue3NobenS
92ETkBBekzPVi5qbktLtsAkkj++OtsuF6jjuvqRGu++sCUR0JSu6215+tSipsdHXSirsCDJw
nFYhXvSTKlbS9WU+yzky0Rx8WR1ZsCHT3QRXBKQzlwT3/okrPFFLJgBhx1mPL5qpiug0QEQw
jfk1vd+VN0/U9EzsWm3t+Ol3VwhFMA2m8HJx4bxMUZsslydj/37N3xiJRPCXN1uUl39ZEv9E
ZxwrT7fxhaslpQIC4ZeGhH5xUKhZeeYy9tW0EiIRmsbf0o2K59jdbcvyvtqouJb8Ty8RiL7g
S4WEnlM4Tyg5sz1v2Jpk/WTYRiBC7e9GVkuHEDiUrw2xmSIbjCn8UPB1CnkTGgpOP4bEDSmR
GMNsmEg0UBaC8JsL6zNM4YHC75j9F1OE0uhAnGOfKtPPC+bXxNf0pO1a7ejICg52mCKUBitu
/ehZtVy5hCXcJ2wrzkVHO+EJAbYI9fSAQsy91ZT/6u0br3Rd1eoT845uKM09acQWoRM2UL2C
e0uZ/6p6ySud17Ro11JuYAvVOG2EEg6Ur1g+flxXkb25sutqd6ssT7upUvRHG7YIn7CsFZUu
pVauvLFDeF2oS8xrsdJof+RiisgJ2wF9j746bMF/O4IvttspCIQppsed4BV+6OdtEeE2Bt8w
BO64Ew4MgTvu4InQCRsVZFevsd2PTPRRFgKqInzCUhGTSEyQiNg0yISAqsBIg1jAWOMOobBj
jTuEoldAVZgxALFQURZ0qiKAOaZORS3NElwEI4gEdjyMO8j/v2JGD3cOCUDEjO6niU8AER2X
oLuLPdnbQIRRSdPyd5Yq01dze/JrgYS+ouVyHVt8ccE2jRZIHGHItbx68x7zgobTN4HEoH6r
ckO9csVFesbp14CEWSNXpuxkigfoL6rigPYV3FejTdlVun/sfW6DFuMMoX7Mqc9GjVIXEKa4
O9nmjPqYQMCT3usSK2QIbI0VMXNLntrmfMT+rS/1VDvU6N8OItKZzHb7LTi1pT1j315WrPDM
Fl6a5DM9fAu+2nLApKKtixUxx8NDk4xq4Fvu68xCQOHlSHrfhr+FbfH1pgO0zbECmS3QPOLa
4U54RJLynwbtEgARgLkr2n0fwter+5YbtAIAEbkBDx5Csm9PC1PwX9570xXkglLMCYJ4iZKA
xxjqhOBIflrLrwISdsuCuPLg8z41urUZQMLqXTC/drCXKag5YV8JJEIPIpnVzCN1uqIcIKHy
LrhZa1HKL9WpGYCiPzW7vF9dsb/uxAUwASvocQmDcRVL6tSsN8BEuUbLvVS0VVrflwYmpkMa
uW0BF6a7tzt//p7ogqgKN+U23E45S9Gg5ctZ3zRJrFXHAbaqStaTtFInY2eXiU61V4rIxe2n
XurhMJRW27nC11eoWEKIfKuWlfVwVqLieOHrj7exxABtPC3rKc1oucA+VSaKb9veZSPPQ1H+
iaJBzS8/XtYUb608biQVyKx6F3WBgIjYeLDCz1klpdjGaoORUIxqeb/OOsUvTPrSame9yFF0
SZ+/RiwmmHrRwaNW9tIvZd/LdMWi/zA+7yMRWVfmjRy10vc+Rp+kn3nlN82CHg9xHiGhttKZ
PEZQdDZJey4gxEKiF+3V/aJ4qS61X6Gr7Pp34+q3icUP8SkVEjTzeZeL7PTPJoRd0tzFQ8R7
14Tcqy9EwklZEG8VTjxA4V85IqUmPAtHBABirJK2kduUxc/SDns/DnYqUvFDXMnVDJWYLVS3
eN4AEkMNHEvCZ+Lvj+me27UQKI8hLcecYEaFWlL/7jUEQAz+pGQgI11ceUwnrtMDidHipG+4
ynhGp1q5U/JLJ4CYGVMIVYH8CIE8MDH7jyGAAG7jVEb6xys3NtOEVqfJP1FnKnJB/F+dIxJd
R9MlyRszBpQFso/PDgr1xfKNrJITQwTit0/SaOqfJwzGb0DFenFK2Y4v3uIcbSYQDUo6jfdC
Rn/8Bvbxs0xUcCz7OGougWhRJkkul2YMJPOXncqldydv7Bgo4uiIxIml6dnJpc2DPP52Wq5Q
qCzqGKAJk2VgR1CPBCaDtYkp/zkvmLAFL8ds0uDo5aPSS6Dw2/3sV3d7Bxx5F5CKuxPcETE9
GY0r5ivO82v/NOJ71uwQ+KsCzusQmnQ10djOEepY6c/p6sTKQk5/5okhZu5wEXo7qxDhikrt
mkTOc7ydwhOpFW9nHt2/5cgV2ZZTbYQiLpHGREUOv2Jfppr7rzdSypgrlDQhnsiv1CavTZL8
YmcX2gY7863lW27klG3RthUcxRH+PUyJmpEuGan7QFmUxBK2VTEFOYXM+F5FN46YDARnc0I7
1o7uq65zyBEox9rxqmP3OI7oC0xf68Jo9wj+NQxAtnDz2EJw75/uSIciFljz29OfYYp7s3/j
MRZb3J3fNtqcaBKCqM8wRaSSY0NMRlTMKohginBdrUEt0fKc0Bh6kc9Z4RNweNfPDxfhiHBH
1//uTssVp/R7ka4oqVtXJdKV6Pr1OCJcydH87teGazbTD9XDRXKajvu5slidZMUWkTQ07x8y
XBsyXawxF8mFalSY1UkabBGZS9K/P3zmmrN2QKQtrux+q2qp9me6izhbFTlfG44tOSdw1qr2
8wp2iNu5jS3/pb2I08b0BCHanZy1h2d9iiHuFcK9gMfcG7OGTGBUckiEgKrA6+luCE9gFKSC
Y7rTIcUTUWl4QmfK2XGPb7/ThCOizwZX4Zavzgn4/3ikt7gKT9ytIIfjdmnFu+duvLE5d9N/
47YRXZC6U8rknROs3PwmKow4onmWeEyXb7uy+flNF/DamPVbidGkvx/OF3y9qW3TWhzhi541
RexSxBWQ8sdH7AIcAc8qvc6cH8YWdvzSq1uKKaJ/UokZswUpmC0w62qEAuB6IFoEaBDVNgBi
Tjx6Aib/2ixhubfIwKrExYoO2/Ti81iVuFgRrMRJdsqU6TnYlTgMoas4qKtji2TJVYx40qRC
IlSJs+yxLOCevIkhYmvC17N3KPn1qvyLNO6q1zDmEGOFWV3RotuZJB6gvZiDVYmLFfa+Gu3C
XaVa+98lYFbiqFeqHwHhBBWWSV+HZBxKDUQX6rCFp/YShAx8C6eK+qBGv0IKINKZTP3VCTi1
u2954971AMJLk+xN8U64DSLHchVzHUgboUrchLtD4japaIMAeXg56G1A34TbeNYtPcDcEAMw
84hbbBG5DULH8gNMPsBWBeD5K9otWfD1Gvfy0/tBBGklDu+Y41fiHoGeOCfmxCMrRgWUhZSq
cFEX0IMXMb+AeADCH730QI7grBb/IoXXb7FofK3TT+MACI/ZooJXp3dDoGLSM/iO4U4K5w6w
GHUVWAyOds7dp1jIhXm0YNTgaBXaKQgO3+DokJwEFp6OAprK0f7M3ed3yMXUEbNF40D3LrAI
hXvmH1UgAc+sff25+5WfcsXd76AsxkfMEsv8VT38XImGk3VKQC5+ernkkEyka5Pldqo5IomR
VEz988LSYVl1gqYxr5M/T5JN3sbUYwtlvRurF6pZeZ2t7E41gDh0paSFvfTQooG87jaFyGUj
Fx+NrJPQOZIziXnHNTTa7URSEYqzUOjNCBsj1XfyYx4RCAIsZge+wLtpwxeRubKYuUJ8IaAq
QlN+32hT5PmnVu5GPlpZFC8lEaEpv33Zl2VdXbqN2/YeKhQZSUQojcbs4dKu5sd//uJ+XuFW
AYkIfa6SfCFTNiUXJjTxCkUkIjzlp6Sp5E/ubXlhoepQ6haSrQrPlR1UJsrFB3lJKz96vKCC
pI3IXJnXGdy70pmf4Ih7U35Tsz/CEZhPyRAKK2UhwF0gqn+EB2fT7P8rFVuEuog/d0Hv7WuJ
x84PsTvkic+WKwXl+CLURbyf7tW5vpZ164a2t7IWiQtFQzJcEZ4V9k5p45K/ZgnPNLOU3FZR
2R7rE7giPLnt/VQbV/B1pfAMl0bjtgrL9ljwRbiL+PO0OlpcYveJqoJEtuFpmXgAf6vCZZzA
7uPqZIbgmLaKvUFh+Nui/VbczPGfoMMTnthVJIL4gRwsQfxADmH9A1QQp4ElCHo6jsB8EoJI
EFX6sAVW/YNYkJRxyOofIIIsDbL6B4ggehAQU5B0kVgxu/5BLmCSLhIrxgRUxcOofygpC8rV
jB9f/4j9vRmpIJhDx/kbRXBa4Qg/5loiQZDIQxX+PwWClwcQqBC08iV76kxTl/j1UkCReiA7
f7fA23S51gQkAgI+Lyd/J8/XZC03Agn/Ej4/J7+OVz+oS7QBCU8aa34KrU5Sf4tPBxP+NLoq
RYKKi0ZAgaTs40vy6bT6QV4dWB7IyZHc6kBSq/fWKh+giETwotBESQSPupSSmA5CERnMpTPX
QoQiXIzw3BP+IUcRoRjfuW3Khuz02SY/Dl/b+tffpAfwT1y0jeo4Xlw+T9HYeYJfwINYy/wl
T9YGkFG5qn8VhC12KR+f6LByfvPBCau4F0p8ys8SlweQ8XWtX/CkmGJ8l3LlRJqV0y9R08Qu
iI+K+ElUbOu4oscTrQt3oG0omC7r1n4o9Sn/PqEluFVH3r2Kk8eu3p+VLGEJ6crbrK0MiLYs
LMZ3LXn3OnYbk1OOkVvStJp3bgdykYXQ9memBmscAcSxLu3kV9gCcxeibchpp/UmKmJ00rpq
FaU2Qh9CVAVmzBAzvwUmZo7nP0agXVrTgHUBiCscCGLAKmESiM+yDP+AVbzF36qBLhXWY5QE
bYzQDL3UxNXuw29SEZDjqsiQ/Qa4QKTu6zUaNYV9hTAwvku8VVLKAhfcT+GQtsgtsd8nEHap
XlxMSYylL8y4QUn8QXS8+gZERXwr1OdiPhOJK8YzP36WmnAIlcISDBAlbFGiZthFtndnFpHA
jqCLupD+3wTu1egMMfNXaoGg96yqpiLcTyBevRiKAVF7N0o4WnrZ+vekMYDgCB7ibedvMxGK
6LHdcYhWeSSDWMwMVKxYVHmFZKuihUvSuz1FTEX4Jx3f8eUQBYH9CN208AcKLBr4YkBw2srJ
ABIef4FZBX8X4H7YtsMdEXgRFi5PyecG2NwwlNW6vRZMuAosrXC/pnl3S8HzQGLUVVDdyvhc
I9htSWgHE9UcvoFhVkt3+5xgwnOwoEDlYsZxd6uLc4CE/0ihRePKPM/N60sDy3w6TJHjAC6M
kUdDH5X7wYcvCCZacATBfJH/fwGv2VNNo0L3wAAAAABJRU5ErkJggg==</binary>
 <binary id="img_7.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAegAAAFEAQMAAADWQD/aAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAN5UlEQVR4nO3cf1AU1x0A8AUOuQjmqJoGA/FOh4QktQaT
TEQBF41TpTHCNJlMTMdGBiO1QUlCoqh4y48G0mkqBGds8Ac3bUxs+mNaiTVBNHd4jogo50ia
QETuyFFWp8fdwVlvN7e7r2+537fvfi1xlKRf/7i53f3c9723b9++fceJgckEdst0FTYRcjDE
OvRRa5XzRW/Jo7taSEKsXktfzOoUm3tkLX1BaReb25BH90Sv1S5tKZghIrdXs13LJqGZ1BbR
GhDwHxCtg0YkbS5OTy737dQql75hkts+yba0qdGaVIEEWRrbSQZqu1OP6myt5bpFwkJMaKOK
m78pn97bMRZQ8v92WqA2Qf2pcU9QnXx65zbtWHu/KSC3tWYzAXVSg+1Th6VNF0TPfIG+m2Tb
6aGA3DffK+W1KSWkTpvrOGNk220GoZ4oOR1KDzfO/caqZdtJk0Cv4XOPAvrJXeZg2rTvxddf
bmb36gS69i58QlMl2eZT+iC6cVVC/EH27PINAXrilOvtrvdBtDsEZ8xfq50vzKhOZkHogFCj
UzImPaETramtuLwhvFahtaMMj6Tkbj1mobIbGjybrVtx0k9zDgBwOpg2lNAHfpjg2WxbqCoR
aGClytDavIruVto9m+1/1c3w04w2TVlsd5QE0c9SqXf7bP/IcshPszXS9HkGq+D6dpV8FpV6
NrBabm2D+kDXBwtGrNv996ld2lj7mwPjdgH16hOJ1QtGbEG0pY2+8vp2AdV7dFN39YKbQXUu
3Z0gE2idR59OvLLA2hdMowrto5mJt0xAq0FNAUFfo3B45DLcR3POHQFJoLYSAk3LYVmbVT7a
Wxbf0PAzpng4Z/ILR7P6bG63XqiFYVhWlRCwydHYWpPbCyLRfMkDgnpwZg0zy0/zfWFMcKCz
1QJzj+TVTMz+vFrDl3KD8FAWpVfvZx0qX90DW5hcKywmCyxZjPI1xui9vB3Wi6dZBvfTVftT
yAJU7gtNO3KPUV1lwn0+JW9t1BqQ+ndf7gLHqL4S4T6v1p6QxJHCewWM0cEKIpMZCKl1J9J7
SZ2w3vBOkUbj6eF07YOzyGFUbssch7qO6Q+p9Z1ENuqEs2B0L517mOkSXqAR9DUWmDPXbFzU
4nPGhJqaeISIQeng4dFWDJNAHidOu4Y8RwSacT7qYOK0awtH3HItjNupQaA2dE6m3sxeEVpJ
uDQbjWZcuhJ3aTqakrv1bpVLO2pFaMKj28Vod8k/PiBCe1pNrRXRWyon1ddK5T46pm7RWX0U
fQ349NQOLib91/vkkfYWi09fs2Kgmtvy1kgCHol2BbwVuXJbsRhuS/xIcUQabmEexgEl9y35
lvS6pohKDs+YI53gx1GZWxu5LXWZZxCthtTMQsIvd3S9hYa5vfWOWs/DxY4tfMlx9e0Z1zzX
mNh6T16Dbzu34KnoW9eTuwtOrt7uLZGUPHhM6i44udwBrWbAMEJkq1mdM5lI5kyI8+0K4bxP
qDnXnEmohYEaUx0YpuJfxWl33Nk6VNzZeuq2+fdVh4o7W0/dNp+6+hI+CV2FYcF5OE1hMVXC
u5NAB1k50Chisaqgyb13QUkHfGsNyMNJ+c9ErHYEaPT9m4rDkhWBJeKCrhwEaMO5lW8XVsG7
pZ92vYSdeVSf++M9Dxca5CgdfuZx/7GfLJw979l6UZqbnXc6r/r5vz8bofYvOZW4EtPkHU1b
AlARTlvvScQ02HMxswkxuQ3TY+CZWRwvwcXo87Ols6XJMqZSHqWeWCOEl8h8DJv7wFoV3NNA
plUuooa8i4eINqfrZAOZlR2OOrlTJ2PJi3/2VgE8ZvHZK7s/HC/QhZojj40Thw5StK1VD9/B
ywbWu+KfLx6BD4v2nm7wGShoCJHbwd6UXzxI2Rz/4EsomR4D03+w/WgJ3EOazylzuOWPhqo3
O1JwtZmz0bzm5sb/qBHDCo/srIV7dOZuvI1hC5Aa1oZgSAfrsPSkcnan/tev5vxZEnt0RcZf
oFaNdON7aSYBpY2sS5/QXWznSJqvN/PIpUzJRyskWFIq1Akj60rfH+uyoHQH1WhM2tA5xtYk
DmQPkkv4Nmc+1yQrYu7Hqlf/gdcgx7zhUjGvjfDM2j3ainVX09PyixbUG4DFecotBByMr3Uo
8mIkVbF/y/ftLQwgee19gmaxGDpz+cbSIwbC5zC2ih+YFMnYyhm+miLOPCUtqmc8T+9j9g56
fsGmpDhDnMaaI4urMmTdnVDNf+fAL5OulmDwPdxYkZYQo3k1BTc1bf5xhnflYJg00o/lF6WT
Pt8SlAFGikmxPAWG5cX45x6tSXx/O+XJPULa2Sxj0muk3+jAYNMxhXCkhnr/K23bGdy9SjWy
CjW2MJhEIRFqBow25c/L4JRubb0XrflrTDGhtQksRRodoAfUc2C0M6W4Hng0F4fSXCyGrVRg
VbzufpQd1163ggtEvae3uPVxI1JL4L1gpSK5OjYOfDNt45PwCu2UjtoE+qgdqVdjyXnTC6v5
xzRjfk5j/46RprWjdoEOMiLf1/SQadpDKxSzEwD9y9zL61jD1z2AjFTXSxdpFGmKPIzXp75Y
x1A1PYTerUvUoXXNc498LsUKDyfLQP/mk/t+PjrcupZQRXov0Ujv6dLANpfIgbY0PVNbdLT2
7tG4iMdzDJaa7+y4X2NGmNuKJb8t4YdGQoymJFUS2FfyMCBGw1lCVR68wmJEaSCFdwP4AXHh
tI0f+6cFrkry40NMYcC0B6FdKwcBExyDJJbPjofTrjD6a0a6EquaFjjriXjlgD9qReBkkZkf
OOMKEhqJNOFP4Wd7QQIOL7F5RLC9YefIGikW+EUuQsOhuLZOJlzq4TAMwQK1AYDmmatskazW
IDR55rXmpeXGIcERiLUeGbC+OsQQPmfMoEmv3lNuHA2RyaP5SUwppfTVfYrq0+W9wtwITeke
/8S8g/HRZJ/i0M7y6oj0nLrkTEuFb8nJ/g+bHysZMwmORdQ7FUvOKduF++oBRXVNgqM0Eu2Q
zFIvJNTCfl4UkX6id3GpAw9/laB1XUpmEVUmdnXOHXfMqgUhXjMcIIbqw4KgejtxcXFkaqxj
UKby11VP7dqMGINQrWbQ9ExL8NetXZVFiGEEpc0De27aA3R1JarkKG0c+HAT8NcnmiufiVBb
Ox5MCtTLdyyPsOTcS9PM/hrOQwhbg/BIZL1fen1kOxDb1wyDO20y0dodd7aeuqtU31cdKu5s
PXXb/M7WYuN2asukNAirQ7Xa5DVbI5cty9y4W5ymx3Wbclo2o5c9w+oxUjeCt1iORqMZrawn
hSi6ZMNoi+oinmZZFI2mrpbsuaIs/aIcYy14M36oZ0voIgbkvkq+k1VZOViMsT1Elrx/QBOV
7tKZUnbsPqnF2MFU2RPNfdF8aQ+YIZWptyK7rRfqRNmT2X25Uel+HOrH26tF9RZGO8OcUqbI
tInra+QMkGPZ8Oqu29rPv6saFUO1HFPOnmPH+sKdMVSMHuHoLfSV3cOXxWg29r45SUd/W7Tw
S6OIetOHW1avOdWXzzRqRWjm8NzrSexX6+njs0Ro87HB/7zD9q63fRIjQg8fa8l/mO3NJ4+X
i9Bjx5emFZ3qK1JUiLzG3HFL70RTV0/d63vq6jDxndUsRTaYUzjQoMPF6PGnH+25XGnMqFOJ
0TfXEe+MV2hLnkDuDqe5F2SmFM5GZsrFaKavzHRZadNV6cVour/E9KXSptKI0cB2pczcpLTl
HhOV21o8w5w1aI+vw0VoxK+RItUGDMtEpoxIa5zfL0wsu7m+a8D9fwCH1HCqb+UqcL2z5BPf
1rvKoO7ggM778yikhuRmJcFr/kCLa9OEvsrBfUTo3PH7uEqC0Dvf+Wnd2TppOP1AI0cpkXqo
qfWn28LoN/vZcaX7J29+mlp6+pz3NCJ17pvNDusypB5Y2tSjDK3j30i2DpYidUdr6/ltngPR
ets260sWpDbW1V2zhtauQGn/I/6vvaEANyz2WiOyr5lw0NlpcaiD663EsIW09cOrhPPV/Iep
r6nAu7VDQ6qgmisr/rUpw06r9WDcT8Pbsbr/ylO52TqzPrjekJ8+4wjpgNpGevWSPrsK9tTy
/N0qbSht0Gw0Z1hsKpi75jzh0lxy4XIZUA/OeqZS2zWsC67NXRuvZehIvuT7L7hzc/Zf5ONA
re9dn7uLtYbQW/PTr2fodAH63sL8Bljvy+uXZF81hdBlxW+ZHh/s4PWJc56S73t+fRmsd9b6
2tqhecHr7T63OJ870afe62G9XUlDaLXrQ/R+n4hP7LI436qCa3e4NKqnqielwa3TdocDtCCv
EmPtQbk8jLZBnYTrfbbgLt3x7sEk7/dJaD3+g9SrW5Alv9qW9m9LOD3z6etSd27/O1Fb6UXv
cUH0+UTjXWqUHqpN8vnzpiD6q8Svi5Ca+UwSNveNr175+mWkHvjsxoVw9b7ZldiVhKx3x0nr
SLg2d4be81szkX2NcileD0WtrbirwxLO32lHoTXu/+0HeOdMkWvgP9szCHeH0Z56A2e9o9O+
z2OIdfYoNOIXP6H1Sd8FYseY4P8qCq2X+Onh30eluWzfpU6H6ePocn/sp5lJaTpK3e6nbVHq
Nr9WI/dGpW2f+mlFlGdscr0lam2EE2zvs0OUmumQ9aQo38gRp6n+kj29lW++JzJ3P2nKUpY0
Av4Oj3v+dpkfmWxyoOKIMCXXmVKUZfzDOoYKWWjNfyWg3LqBAJGEsOS4uVdZyiAPDq+18eaU
tWvSxGlABv17tUh0VPE/Npej4b4IE1sAAAAASUVORK5CYII=</binary>
 <binary id="img_8.png" content-type="image/png">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
S6b/XKSnAAAAAElFTkSuQmCC</binary>
 <binary id="img_9.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAXcAAACOAQMAAADw5NrYAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAGAUlEQVR4nO3ZbUwTZxwA8HNWKLNAN5LFDqWwdPWD6EAX
YXGsFOaEUCXxE022rI2wL0KmazZ0jV7FZbgFAftlOhz127Jky95McC+RAgaYEs+XxLC5lDaY
NgutPWxme7nrPbtee/Ree3dfzJLt+dBrn/vdw//533PX/xUIFGw47zMJFfYhld4P4CsrKvzN
aY3eqsKPDJg8NjXe0w0V8D/x/QI1frO0P8f3/gENVCD+07E/o+vZHaHZqiZE2g9qztiKOT0k
DAp5EwQhnB4Acw+X9dzG814T9LoaP2qat/F8vJA/373I3Q8A93C58/vv8wXnq378yf+9Oi+T
/ycez39wvvE0vYdQ4bEkmCTxtCIfiC/CKRCwkElCiQ8NbdOB1CGdhUyccZbJ5zN+NNIH8F+2
UfGciJnlfcIVKaX8Ycp/EdwtHw/mWj6R87H4B/I+tv0rXQPm7rOQeN+3CvzyUN94fwoQDQAv
O8r1cFLV+kmcXFbjh5y2GjXe/ZJhQo3fM9LYoMbfGDGIzDe7nkdheoOz/cr29r/FxyfAj0Do
dc7eerH1MFSjhatnSgzDdfebFFy/WNM3PeAFu8NuGm3ZIuUHPuxn/Ko79D4wdzmm6qf3DUn5
czv7Ezkfc8cpv8lhrfe/oZPyY+aGnEdix+PdkzsNjv0mb/OClB8cZnw8NFRfBtdVlpYW6Q+W
Mn4Rpjc445eHP2N8MlOjZLotAGBZjzPnA2P8K4G5ZC4ekPE++jWTf2xWP1cHvs/scYM04yvG
5ybyPt9I6JH32Tu3QfXUhpIifbOO8acvRgZz8fB88ud7ozgw203tdXdaFljnCxEfn/IeHGzt
OkLld58OeIzcfCEi/ioOdtu799ZNt9iAB6IaC8wI4793pwuuM2zQaPQQHX8Uols5Dab4PjUL
Iq+CKxfOni1r7KilE5fpH80eBPk7eT67zTALoPLpY+8lPMyf4fulrMe4nmoS4681vhfMV8aP
CK537iMN3wvvDwnWR2H8Qp/E19PzJYEgP6I+/DR0Zr/GCOkrb1XoFNyvwvPRMdvL473Xbvzu
7VUyvjYKNR+pil6btw/wxveJ+rZoR9uRQPTaDcOnvVxvFI+/t82mqbLdet7wOWd8Utw/3ggv
xoYtWxYb3xrbzPZERDQeJv+Z8iDN8gQRWqI2I3yPszzG8hfI09TaQcXX51pb8+gYuQ8BZLHS
eibYSvYjwA8rXJ8YMvMYBkS5XL2U91ModfXK1lc5H0GRBQygRoWeRB51HoBB5tFTkb+6hB7o
BEFY3k9mXhLjRrSayiVQ6G+PGyOTVC4Vemx0vDOSyaUyT2q/q3onDk4BhZ6I7qp6j86lQk+6
jSdJ5jFeQTzFxxEQtCj2YG4PINae+uXPFxGx5HKp0BeDVP4qURCPEazLdyhZP2uTVTY+yf6J
Q8H4fnaHvE8ZVfhJH3uyCryFvkqkPcq8zbJfG7k3KIEPcv0VbjRCH27VOScO9ZwnKr+euHo/
V29gqwEpn5oqMXmtTv2xktqay+cYH3wzaUmKesKq3eG1boBczupPLj8DztMevWl/7J4T9Q+b
oR3edRrI1e0ouTQFfPT9G12aQfFB8fj1VpO33eF1mXpLBtpz95/g2zNoMCLqUb+uxtPaM5oq
i14qsjP+wN2ghKdesjteoxdD1t+0XQi7JH32K+oY/S2f9QHHxTAs6dnNl9umkxZF/pSR1yHj
M3Xbuq0sXtDHs9cLdYzmy2yPdH21vPnu5lYm/jRdHFJrzy/p/7B3dLzLqU/o2rWcWfAkzwdW
ug7HhfVheVjabxTxyMNprdP88UoNWmFv3c96ng0c7nIL/RyS8rZpzN7rteGWrop21vP7cqVd
e0jgfQgxDdWaB7RQ+Lp1l7Uo75P0zYrvQfzhU9Za8w9tuvD8fAXb58YT+AdeqBby/mZ6sGnx
4N7nZD2CUvOFZlfq0S33/aWC3xOEHuQWMIlRJbls/czymUJMmc+3J+6XxHxahQcJ0rUJVuGx
j2p0asZfPR7rU+ND7lCPtPcJPPU8XmB8of+r6UWdRdIL2+qwqwpW4QH7fwyq/T9SoNQwJV1j
iwAAAABJRU5ErkJggg==</binary>
 <binary id="img_10.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAYkAAACgAQMAAADDxwK0AAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAE5UlEQVR4nO3ZYUxbRRwA8EOI0KyTpi4xDvVJmBij08qS
EQLhscUYmpCQ+Ek+zK0uk/hFMGDmRukrZSkzErdVJUSJb0tMjJ+GfJjWodeQZZOkrI3uA46x
FgXrYqVtMPSRvvZ85b2W12t57y7Rb72E9HrXH3f3f3f3Xq8A0SYn0KrlqUmmmpqIr/avBCAd
ES0d7lpKYu7wdGsSDid/mjseCmiSBpy8Yulya7fyLELL6iBljBafzlgeMUz/WhBXdgNl8N5i
xGo6whdU65NOgJFSqYA0dgIrJXm682IPJbHMXwzpCp2ZjBAsE23ClUmZlEmZlEmZ/H/EdT8V
IyJ8nvx4OxEgIqE8ab9JRtbuDVxQyMM3ExdISGzpam+OBPNkU/0Il8I7tvRVda5jk3Hlk3A1
pkFiS15OIW2T/yiPlDC6arkRDH3ksCw2x1ELTpZ/yBH71OYOMZs8x6ra99159NK1JzGSuGfL
DV96ElVeoX+tetZs7ednbwQ/v7wXHwsqvvoQ+ler20xH+hnTQrCuqhQBUuJV73kYXZkwvdZz
6gv3HU+dabl0K5ksA5XbwUFj0lhM54I9xnZT0FQ3g49F3bGZbQcaRmAkVyVKf1u6Y5EaZJNq
olyXiOozIXz4ThYVJSdQX90YTqphKeJv23PScUaYC953ThYRtiRZuPLZc4P74p5rb/g9vThB
MI2XJJzgdsP39fU++9zho1HT6WKyJc8EPoGSoe2SNaljDd76Cp/oOvx69GgJEn7R4vbUrnTZ
BFuNzb30RH3UCeaveA+emtg0TwSjP5cgP31pafR0r/cdTxkNB2buWj+WiP+Z67ZBS3zONxdd
jBSThU+db7lq12uOCV3n33EtnX8suhPk7DUriphELk03flgbG+rZMr79wsxdw7fRnUu5Cwkf
mgb7+2JDVgHUHLjssjav6m598VYY3x9DUzZhYNzmrjA2J3RJWvV1LLY9L3clyudgWjUH5WHv
SsTtbJjFm92dbMxms2LpaVmSZDZgNjtCcbOYHRiTsgJLQdreZKTsKMX95ZD9eZ5FcY6ciGbH
1yH55IKUtCxmPkDIh8hJpkVoX0AiQ0Pqtp7ipADTkJ6/+GyAaYhhHqFKREWqGBTmqIj4G5c/
GiIlSTnAFCStBJiCICXANCTO0hL1sRghyQWYnIjqRxAy4lMXEBFlcuVJEmEJJxwqPBKEaEOH
OLhwYT2U78Ia5H0HdoIGxdZxmybpr8D+JUyn3vtbk5xgMcKmUoMrmgRwSkbZxX3sVup0nybp
yLUi3wIFBqavH7yqSTiFpGQyinhxaPxdbaK8tv6ezYUp9uQHjz9g5RlNSjIGQ5SVJxsxGa74
g5VXMzl5OcLKq5mY7P0mqcxoYtLmRcqMJiXIDnOrmZgM51czMeGQE1ESJDDUJL9siEk8R8mD
vLOaSYlqJyMkAkNNKlUFZER1qyDdYAu2SyIyUlCgTxxcQbdIiN1RWVivT/o7sHp9coKhJvkN
loKw1MRBT/B+EZPImPK9i5zcauqdUn6sIyZnu8djDBXxOJhz63TE5WA+GaXrWNPZ7ulRSEXO
NAW+C9CRYXfEi+gIyj5fkhIA9hTWE7QiAgDUi5+sY79kDzqU9zwZkYsBOI6yhyrEREqCpF4a
oSJSioOiJadHpGq8QJ+MoOKkQ4p+eNUnJfqlu/b/I/IvZeAANG4nLS8AAAAASUVORK5C
YII=</binary>
 <binary id="img_11.png" content-type="image/png">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
+13riU7PjGMrv6hp2YxUbl9RM7i1ypSvmW+q+TwIQhtSMk74BPvZKwdLn1h6XOqp4c5zV/bf
/N3o2j36Hv1/Ba24i1mDfm2SmfiQ+X8yXbS7klnquNdSv7V+7UXPiaWxLoNl/TOLnybqZ9bV
1v75aSUnVIbutWyiX39VV/jqQ+VniwpPjC46ub3w8V8/nVz6RF1R9dynNqjS/SjK0DPd73zP
6s8r/GD8l5NbDSakiih4pbKEptVlf1jH0tsKXy0s9S+3fnCr7KOthSbkcaJg4PGi6qJapQmX
DH2qhimxX1kc30UMfuRYcdi03FrodK/fU2p8srIWqZP9L0qB5sYxJXHc770TjBbHDuDvpF8G
eMWud/CtOLXpsDqdCanMODgl/IUCzr0pTWTJ6prG/wVo0LqDMq3nfx6UaT3+f5iurXRFzxnn
Fu8qum/fF30fd7heWqVRcWC6+EnnRG91UWDvY284v+h7v6fX+eYPp0AX9k/O/U8IsucHJR67
qfNMvsOuMquXSx+bnHvef2XP2XmmN5G3TnUgl6dAI7WmSffFCWPzZWTGF8hbntnI7CnQWwl7
4mY6hTb/+b7OP/3+40urv1g1BRow/7EXm4o3POd5p+6KnlK4R9+j/3+iU0BzCZAGLbMLShd9
yRXA6Z5H/KtkxEt/TF8DY/SoIg6S4wlujQ9j3XMbKN5r0Uvf84bD6q9Gmty2be6NWysQS73J
4S61WZaGLU9G20rySnJPhsnQXfnMivWzm88P/nh9clVjZVttw+mP6ksXeXqf3LDtv40dfGzS
PviDf8093TxDn81n8nxx83mkspBYNW5xVxpPv7ndMKM/b4Ot3h09iEzm+Z8JKMnuefNBhnaW
5pU8B1Y1Ooy1DeUfbbOW9ud/Zqs3jR2snCwKVA4+rUB3zGZOTe9zWyyVhdjHNkftxq3Nq7Ya
DO91hBu3OocPVU+2lRQhSrLhgGXeKH4KJcl6FKiXgcz567m0cE9KoNkfk6P/N9kYDP/jgLFb
AAAAAElFTkSuQmCC</binary>
 <binary id="img_12.png" content-type="image/png">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
VTZt6Rn/QUUAAAAASUVORK5CYII=</binary>
 <binary id="img_13.png" content-type="image/png">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</binary>
 <binary id="img_14.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAZEAAACHAQMAAADtF/YmAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAE+0lEQVR4nNXYf0wbVRwA8COIsPCjjUScOHZFq5iZKYmV
rQE8dIkxGsW/FhUNDJLxh2QzawJEkOsAixoTcSwEdLWamPivzmWuLqwtNUDlZ2LCzDg4CIHa
bNIyMIVwvee7668r9O7ea4zM98e17/I++X7v9fX9OAJgFzMhqTSmYEpSMHkkvslJL+7V7G2z
rGiG9b1JQrkUzZiui9prrEpGP6bvS2Js3QeLM+VMnUeX1OjdxflyBoD168lyu2+M+EneJC0e
vScD17jg83RhGsD9YurBNgBQuAYWRwpGWvbH8PR/Y8C/aNh9N7YUDBszu2cNWbPONNgixuVA
NP65Z85EDDNEoZrvo2Z06Lh00lAy1/MiZukTYzeJZuwdRLglc758foJEMEtMw9N0GDH28nmf
x6ZugDhGReQyllt9SwM0ogFmYZa3P2u9vVJ4DNUAczq89FjHV95GN2AxDV6uPXznxYfQDQhA
xFV8dvomhgFbEPGHVkflTFBSW4gYwAnprXnlzFqH0FGh0cDCR4NTUSMiPihn7hiyiw3arc4L
hyf712IGcNKkd5vpvi+PPjfScbfspXniTNxARMUNJSE7ZmIif6A4w9k+fOwNJlMSB6YWj8Sa
ptvggLRdoh1bwGgmJr8YOPq4hS/s1zL90jhSxJ7q1l0pO1F94MirM5eL4PM8OPitoTTQOTM8
PzOVYOKIbVryf9d/sHrqUd/8hUkz4RdubgMgdNJCookhtum8v1s7oPn4iT/f7cwxE9EfIZmJ
IrbJMHJxtvBsw5H3ij5tUpuvxd5j2abO0isFJzRZT2blfpWrPsdDNBrra6FBCGFdIEg365A0
2EFZSwiCTagjrT9mEt84UzBuNj5MYRMkY5MYjkJdfxgvCbci18D6RKUD1TzVzFq0lgfeOtVt
tKGatnq2l7m6UNNetr3byK31bOtJ1jxhcdbU3b/HyMeZZXtHLs/W5JSFkE3rMNmptWirdBb0
fotm7hA+sQzAN9GC2G//U3Pv7sX2sQ+C+HHcjdjGa6+OmG0kw7eUepcpEicO155f2nibipxw
2SKvUc7E+oDLemTOUOKhzkYMfeuumvngnNH9W8HFF4ZawvUF7lazSm7uNpPdPTbyK9NnEuvE
YW7nBxUzWC4a79fihmeRoNgP526qmM/T2+zjzKq40nIE3AGz5MhxtdzSTEN/HBIX9C1xN8Im
PHRSs200XSJpMUiaeAPBWO09DuHTGd3yIBiXgxXTiu3lEYxwLOEJgo7dRzBA6GDpixEkQ6TR
0vsIJiDZ7qmbDoh4cxrYVRRNOw2D7H3Fo2TA+7RzTxA1U0WkJyHKRvKboMfBN3wrNCSmoSnA
5/rxcoNh+L98iU1QTKVv5jGs3OAlJ5v5ZgPPhCaymbW6xFZqhvONM/7X38Ey/IFxq7/2zd8d
qEb4E4Q80Lzc59AgGlpAXOVU7Ss/3jitQTTALERyTtUbbmyubiAaIMw2O4urZRWhzXMaBCOu
C074d9jxNutCm/UrARrNgEXhP9RS8bdgqhBN+MD9vH7lZIY30IVowijvtYKi5Q0HqoETCbwU
9YcWTegmjK6GvEHk3IAww8PvXEnwZzUj3VMIB25e14KRWwSBoAbLQASrbjwDh3mkjmHEwym2
iUysSibJPlF4rzSNaYRXRKO4BgQyXdgGjqNabKN8DpbZkzuVcpMxPUrmXj7/pGSSE+Uximb+
AWZTxkUztnkqAAAAAElFTkSuQmCC</binary>
 <binary id="img_15.png" content-type="image/png">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
AElFTkSuQmCC</binary>
 <binary id="img_16.png" content-type="image/png">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</binary>
 <binary id="img_17.png" content-type="image/png">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
TkSuQmCC</binary>
 <binary id="img_18.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAASQAAABmAQMAAABY0H58AAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAACs0lEQVR4nO3WTWgTQRQA4LaBhNalQbyMWEyLrTkobS7G
g6lL6MUcaq+92R48VCpRimhq2o0tVIWQFgr+1JKA0LM0BY/dSsAokUTMqSpEKBgPhqQONCkb
ZtxsNml0NjNb24gH3yG7+/h23stkMkwT1hG5JuWSt2AvkrBEV1/NcXCnMxdJUNVrtz95t3fn
yjy9YjbzHjvQwAu6SmQ+TPcXN910JWaSwtpu0URX/Lukxwo36WqnNQ4udS4MZKlKjik09iOt
jWoULRqg0EQ0H+CLWKCq4ujiVrf4bJqhxqWtqdAJB12hcQg53iIw1EgaXl0PspQ3AW995hjd
o+sh2BM6zlI3ZyYC55/00RUjZJVXbuRRRPki7To6LBpqY8QGAGdsMTT7fPK9DWiqFJEjM1pK
q68/HavRikg1XJE5Mv7P6sH7ImNfytdUDhO1Iq8+5v+GwoeolJywf0WGVvf6VN2KPN7Spd7q
UqE4gPPM+RLnhp1WtgrHXLeZFddbvjnZajkcG7Qy5+vVHBg1s1cOKn3qUcJB1wRWxzoM9UuO
rsRaRUYDVqGoS+kb6/fdpMBXRQrX2QslnLNUn0L1VAHDp+nlXhQGUP7t/P0PbFo7ZoGHC7HW
s/bwJ9eFyaGuHvuNspKy2CKfolOCwgpm+Ah4Vl4+P+lanBzyzBrV7mEUXRa28TxfVL6EAT4G
nlX7DHAeazN7Zu2qykfR95ISyxXlsYbHVruXIoOGa31dK6crCuO4gLNYrPYVObLmWwKjZ9rc
/nMBVckH1WCpp7LaVV6DxKzK6qKgTo4yj3klt6dwuSKa3huLmJuqCrZ75bfrqkr3pT8RQ1U6
5QlQqyoh6FKYWvGQFNlFoxWZI+PfPQ197Aig+6eOPnyz4ZbkRYqR1aKhCoBrNxma7218KR21
Oc5o4jUUkdKI3E8SarwucYuBYAAAAABJRU5ErkJggg==</binary>
 <binary id="img_19.png" content-type="image/png">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
gg==</binary>
 <binary id="img_20.png" content-type="image/png">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
jgAAAABJRU5ErkJggg==</binary>
 <binary id="img_21.png" content-type="image/png">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
SUVORK5CYII=</binary>
 <binary id="img_22.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAYgAAADoAQMAAADPDuGLAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAIk0lEQVR4nO3af0wb1x0A8GfsYLs42AFETGZh0znQP6A4
ASHSNNgmUWsSENE2TSRSVpwQbZUSYWqpZEHNucE1bUeDcSqUND9c1EiTqv7BFKmTSlTAjoA0
DBoiNSvlhymR3alRITEFuz7f7XzGvjP2nf3aEUVrviLw/O4++b57997dO9sA5bhAKARiPLUA
PrAW/NDLQZBmIl+lgWiEimkmBVlGCBGI2NWwYA91nJhL0qTHIQZCYknNLgL0F2SOlfUCE/up
Xez40FrJExWrvSvSHvNXxyiB08QAPkFvNdmq1U8KJ7V9X9VQIvDhjg5nnuJmT8Vhsepgu4Dv
FNVWi8pogndPDyRaSgiFyvNTwkIbLzer+fU/CvTZt/T7GmroIueettxKy3G8znR9qrGwXyTJ
Er/+klTfP2PZB5poojdn8g/muzRxv87cT4jPeBLhmkgH2xpp4qMd/+4Zvks7cp6OaNWrFtv7
Pc80VxzUctpH9UCqpQS+uL53M10tHVPCc8NfVmxbLGxwbh5x8vZXC1gEjvrxAQyJ9G64FkOo
3o30PCX8QUrYo8LDIlji54noiEhZxM7aZEJNCLKARGpSmbWxggofUxpGEfxViuAcdA5Y8dkW
izoiuNFajE0cmbXHCx+L+Dg4PRcRbSm16mxwWh4RpwWlnQUmjmmpu8fA2WR1yJlE9MhPv/r3
gmuFJ/oebcox6rbbpPUJxWRwmMqh7x0yWRr7tnZKjCKDScZ4BtWUuKY3pzf28XmEaLFKF5Oe
89OcD4Hi2UZVGrAZOTygEScVbdm9nXpLy05p9722vZ0CZ+Ijpwt6TJB3BpBZOT8iI/5lvtji
SSpCAXCfkcyBvWhY6JBVPvR05aMhEAzVrhIXj4BnTh3acxYNX+9iW4WdeujsIjPKSD8/ci5c
aHFawg0xPnlzMAAtXAisGIcXWpEaslU1BXZYoYcUbmgxDngsY9dlj6+bBWxH7lInSHIDYend
hAJnE5GlXVwwiwR17MLHLvYMxAsGgAM05n5OBcooomcqugo3XVknRlcBu0gUXEYxx9AU2jL5
cQksXSqPikSPOsNxOS7fnosIjB/TrX7Bmd9Ub61G1osrAXtUvOF7Y5Xa/FPbGf/he6/EC1+0
Vdgek2TvPCgw55Q5sjTAazzzV0Ko14uL/uiRY7xrjkNjF5Xl3R9ZB21N3mxE2rD15Thx5TYl
DMWDh8YuPW/e0n55qN2w6kRGG0bic1zOoMRrymFCFPO2tF8YPN/o/R754rAzXpA3iDWR3ijZ
++CSUvl+timtvenRgTP7f1cdL8hYOx/YqcqR+cC8KbdsOOtlg1eGy1+SzsX1FV2gaOjEr63l
184cxi5wuvCTD14Y1Lhy0nJRArsjXkx9JMbO2lQE+RuJvPxCDStcDLOWwyhw8qJO/vyArNXT
hjKjYI3HJ+ixUTkGfpmIPDymLlB56A/GFdEvXeOKoRhBj6DfHt6naYJWOy46EiPUdBHwacZO
bFqc/nHCdrQi60CBy5G9o2O4aIJZeB/V3ip+a3EaFdsUksn9CrftvOL8TYWTJcdSrUAx1jyN
ys8SQgfceyWgf7DyVIxAYo7DWStQTounf8TfBRKHTujmWkH/nUwZ85H7JLVC5b+6Zo+vvgts
WTVKtxUoz99RjjILtHKmuHi6dHbz6gdZuaqMXtdgzj86vjHMM4toiZwY4QvAALU96UhkELSK
FMeVGlrA53j8rdqTcM/IRS6RqGznh2/rV8EuXBVUIBeJsgwYUeNPWqwsobjeioXa6e3U7sJU
/kJSSN1Gf8EjDXopkZDa1Q7hQl3XSGfrLqyw7fk82X1HPSF8dSu1Kn8iIWnnWwlRZCcEWmhU
HJbdn6oXuI1LohWdKi/RKAHzCxcadZrWLlNrJSCEQ+SaEgO3gSNya00XEuXYV2m80CjQtC6E
RLqx9Kbo2w/EwPMaV+SuN3+dSDS9ybXViGv/7LW1tuMqb6ln+tttpUoP11/irr95Mb5VLvnJ
Eb5TUFon5pIiUOqZcW9rLvR0oPqV+vG8+Bzrl99+nHoP00ucyHixfvkdpN3TiII3gUDiGhoT
8SMxqcDEg7H3Rsbld0QQ93PiD/ZpdBWeVMSuMnCWBTujSDqj4kSy+JkiwIcV/jLoHG3QOYzQ
Avo4AsjGCz+0IONXKJbIWfvblAUxa+VY1YMb65+dmQX5eyDFvZ+KXyqenJXMU/FUbLzogBah
N90wbWh5syx/SNuCtW1iELtDWzWkELvowgA8DEJ4NGO2tqJqtHh54T30bnf3wNdd6XlXOqt2
XGYQL/ylpmxWU171znPLQxy0G1hth/g8SR4vn1HsFgz1zWrNVX9TLAsBugWkZ2n4O6Vv864W
MIlKjbYvv96Un6FYRmtRCbAQQiUFvFkOkzgFaq7n109UHVAuoyfQg+lmW02RxMQpvypkEm3C
Y31/emXic2fxMqq/8aXVaqoR88Bb5vxbV8UpnHNyExLqahQJzMnhRLgiXiRdJ/6/CMwyGP1I
mYwHc0kECuRkYSxyP3fJk4ho93sjQp14T5rAwv9pZJXB+IEBJdCumBwpCP/3xkwVJRg/MKCJ
hZNKqx1KzGsMuRNRkcLzh/+B2FDhjNakINCJEqXlm9jvw7ALTF6QCbgwggj1D0jqgmw2guOQ
IhSprywjJS/bbgkFfKtgxHvfHTunhhLb/yO8ZocSvOc2/1MOJ94WlcMJ5Tui47CtauTB5SDu
ia0bfT6gReh9fB4oS31cAfBm+mTsx3P/+1aFJ4YcQhSRBTuEKK4YzphpW4QQmz+1HZg5XQ8h
Mq0O3a0XxBBCZDqr0z5TCiG2W3bezjsKI1S5pu+OZDRDiIFw6Qkbu0+mwFwLspNwolonKIG6
imK/l+pK4K4+2VJQwIcSWQ36kiIooXIITsLlIL9BCXXkRlixFhsosARfWUgmuPgCZtr9earv
FP0XMkYFbqTXBTUAAAAASUVORK5CYII=</binary>
 <binary id="img_23.png" content-type="image/png">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</binary>
 <binary id="img_24.png" content-type="image/png">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
YII=</binary>
 <binary id="img_25.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAeMAAAF7AQMAAADbjHFVAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAUZUlEQVR4nO2cDVgTV7rHJ4AaipbUui5tbUmsVFhlxdb6
UUVi8LZ+dCtXXeqFpwsUW6ERrbW6vdU60Q1qt1Apfl8fTVBotZVbeZrgttbGgsVurwpF6weo
kfq13gIRowmYzJx7zkzIfGYYkr3tvc/ueXjITGZ+83/Ped/zvmcmAQzIatdx9NvBe/cyJo++
Sv2+wbmWjyZTSbz7tL+lbPAjhwj/Zss3H6z1ZHl8e7fY2uR0r5++aernR7azaGPJmtt/mOPb
66gZqGvO7abnLWToiFg/siMy2k8fVZWqdKm+va5TA3UzUrvpKUrG8jiWdqT/SleNWCmm7aZv
NanClOHd9HMsumM4Q+ebGLpkYJh2ZjfdMFA3PaqbztrGWN7BaG/MZ3px1vlVyrFM38513WBd
rd/yrF0M7WTGvDZ/FhBvdvpFrr//rjT4f0R/q8yJHBU0/V1+ZORw/puy6R9zFUp18HSkMnJY
0PT1ITnvaoKncZE3ZdP1IdGi7Z/0PyBt89gIcgG1b0flY0IKte0g4a+NvhM9gABeF05td5eY
azTttAHS7aPtJLnU5qPhe5/76cPgans3fRGgK7dD+opqf42may2iO4vSR8d465yI9l4fEXMU
v3KYhh/487OjY9c6KHrbN7kPRB/NfaAI0e0v7ppXQWxBtMdaP3I2+e04RBONexrLV5Yl0/Ty
v9ypzPqQpp8Z9EbhqEs/vfTTIkjfPrbtoY8IqxO+31V1cmQzee4jO9I+f2Pw097KlTQ9dsLX
xfiH9dSAbJ304wn8ZNuS7KxsDHQe2zHvI8KCtMmquqI55PmDJrT9b1eehbRP+8z474vxapqu
mfTtCdx1Ypneno36vVc3nKim+z1bE042PYUs73x2H1ZIJvr6rVqj0+C+fit/ePkBvO7xJe+1
+/3tG3OAjlNjTjrwL4+Riw93+xauWeppeqfD/Qhw/NHTyqIXMEHgTGG2twNW46YnUzdNRgP/
gX6gy0/Xgmbm7FvAxWyj06/+4nH+D0kbMF9TAP8m1g/4N7FoEmO3WHIbpsKw/pgagz8p8Bpo
ssJZa0Mv1DrYTbmfXtRSSYJ6j2om+gUdB2bMFph2SNCApkW1nbLpdZAmzrq+Lwa2FnRyTpcl
dRxYmYLoWO+CxQztxZ0MjZbVkD4IaS9wvXgR2NAa3rGY2Ox2g5XQEIcHP5SF6E6cou3AvQzR
1Mr5BPnDYkhXD7JMvmen6cJPN7R/SmyG0/WdA9Yqhxs/dOlTSHtyIesGO4B7KaKp9fgJMM4E
acvpg5Pv1bs2QrrVWuho30MYyXHgneqKSsfdlEO7KDpHnwrpvcD9IEUn5uH4RTAR0dUlxsmd
wLUV0u2bFOvaRxEV5FlIG0c7OvBDu0YhumDaCkh/AtwbEV2wbxEO7oIVNki/WWqd7MZdZ5G2
pXBxez1R4UL05nJHG37opQ0UnZBK07+n6DIljheCp6D2ZMUZy2QAbn8P6SNjKvu11XdZlo8D
b/XdbHY48KJXaiF9K3EEopd6l56BNKmoeA3HraQVjvnkpYy/LzHRssLO8vetWPwK5e8/Io+R
4W0rcZwdLd4UXqxxaLeNHS1UAuXQ8iK1FrDi/Ove0qjZu2lK2998MzRMMdJ94deg71N/qodz
jLBn+2doDIYZTIZYgk2n+OIWbwHrgMd34nA4v7W+7Xqo3f5tGKZOouc34QC2444n0LbaT3db
DhjLAcdyX7Mzt5f3QqJZlgdJO2OprRuIXkJtekVowu7bZSQo+grudayop7RhKSQDaBOmG7hJ
hL4dq5x7oPDTOUg7p8OoU1l/wkXowTWbqaxg4liO35u1cNlRy3CKnu+6Pu+M5b/F6LSGB3GR
fnvy85c0WNQzEb2o79qMwUalCO1taDiF87Q90GOeWa+pLHFmin7zqiYj3fquGH1m4OZZPJpA
HjPFTP1tn7JMasxbjM8esYwQoQEITzEJteFpuBdNXMrfN4C9XXTMAThEv5j40UKf6YsWOwhA
+5qN229/L+TFmo213YtIJejEEGSc+yg7A3sgTexZrmkp0qUgmhz76uTR658T0m15zyZpFqZy
aaRNVMaVX9U8YUJ0ye6MP0SUnxLSNxNLjxaHNwtp76PDftu67MNYRK+u0V04WV0rpFsGGE6s
Dq8V0S7us78tp5zS/uBIxqWT1SLa1wpKjJWaR3ijBj1GlM3VtBUtp7RLNbohDWtFtNsKjiaN
HpkKxMb8BujqHnN442JziI25VzjmDpp2MDRJxYNEtJgENNVkxhqLvhsSHUxGDk2b4zF+HfOt
NNk0KjnMSpOBiV5od9dv1vwOgma0yd7TQc5vN1otcLR7k5nc4K88GmnfsDH0BEn6fFRBLHvU
YLTg32VRm6iGkipp+rWCejbthXXMU79t6ugNn55C2peIk0u3gfEB6It5OdHsUSOhttMeNbe8
4j+pKrjr9qbIaRlpgbTzp81iRyqkU7q2571y1KJORfTFswt+NzN1oID+E0Ub8t+J5owapC/i
+mUl1v2IJpu3/npAlE6o7aBo6m7axtHGf4ydFjGsjzmTGrXbJZG6jFIB3V4iFqleyt/JsAq6
aX931B27jI8T0lv4a8Vuy2EVJNAhinaCejcu9Fj74MA03aRija3Nzciy6NPovXutQHSWEP08
MuK8cXqGXYx+Z6lTBl0zLcwksHytypi8ZA9FP6lJa1k/9fEA9HeR02CketnaOHBtURB9PqHo
pI2Tr71yID8A3bRvXhavCuJgLaRXTaFo7MGka8v+khMotyBZFk1lRcsmS/Kqf6fosFODoHak
ZGZi6Ey04jIo3n9nFT3mqyam3Sk+ViaTpiwnjtF2yMyKLJpa7YHe0KxZYpMVa9ynFsGt9kSe
HPTecjuLNmOj4G7t1edj4frTltU7mhTeC4rU0IDaBkzdd6pBawjDjmgCW+57atEitHzs2vWY
1oCNNzwQmPbQ9AkhHf/DtxjWQFkemM7Ro6ceJ6IeiubRg0Y0qDEzFqGSogumo6cWJ34TWc+j
J2SZoeGGCGyKFK1E2hf3KqJ5uWUSnoZplVi8lOXOxBGZiE5QzuJp/wY3a/epMMwsoX1rAPW0
xlBQyalENjOm2peGxlyNSXjM99SCqkRcbcVLhjTYb2zov/Y6UmH9VmAqFaaU9lgAGs6x8f3D
dPASGCYRLQFo9Kwn7ANsdFhQ2rAC/2pog1G1fhAWVibhsaLch4A7paooF7/C1lZjI/qWqdRT
obu/D0y37ZueDtwvWuMS9DO5lo/ZrTBr1XMx7b7AdIve0Ancqso+ivtSuXR82mqdQYVpNRLR
0qa//zpw6RKTB0xPtZNsWjGUeqij0ElEy2VlLgbcGLZak5/J0m6CHlPQ6cQgMeZkDvAAd9dx
tMuiYbQkPKnqzkwBaeABYh5zQG0NJIOLNVT9u6Xl0pz6PeljOMOU8BKrg6ljI9LN9JM0uXHO
8Zhqb1q51jAImxo4Up1OO47eo77mEcvAcI4lpJkRHDEgsOXcKsjQd+CN1vh0hVbdd72up377
GisrvqXGxqjUWFEEppKqBgFoGC3xKh32/DQZ/hbQBFwjY0Uq8+ww2HWZtMm/RcC7mvghhp4j
lU0z0eJFGVkFJyjMyHJppt+d0GNLzL3SZo2aBZIFabTpEvM7gHYN1NaiKtiLfjObMFrG9Kfh
IGjYb/WQoPsNxzxiP6YO6w3N7ELtpGFY77Q5lj8zB6UlTBuENlyfD1nPaHcV0VCsPG2YWybQ
7qbmWIfV1hvL3WZsnEGrhsVfORBGS9edSRrV2ie+sMuj21G0oJUDpkJrJqI1ufLM2PIvTPJo
6LGk9bD2K+AFYL+JixVlWyLK1gj6TX/6b3MK+v30kQjobgU9ajsPV6ZvLt+dxaerluPA7rgf
rXvY2mrsUYM2wu9v48oc1YE+fxb0u2p+cqLKGJFwH872GKQHYexogffSXXbhmBfHjVccqDqQ
sArnRUvCQF8NneKj0fsCOgLRVWVvCLQN9McyWqlY2/6rzwdUb46LC+Now1VPfLqaF+ekeLR8
/v79Bds52l/CvLaHYpm8ZvLi4rFmo46ztM/B+a3VMNqkc8iNKo/cSCVoq/3zm8iZkWqMD6At
pM1YXBgcNG23dkF21pZPUmTScMyVbH93Jeozq9+TppndO2qmtFJ0pDK15LiY5cSXo/vn8rRR
JcJQoGZrqbWDd/WkG25RjxHJyflRPG04ajFqHZWYaI9dxKlgE/msZlJidixFMzm1C+Y16DEZ
1cB7uFL/V562BfZbrZWTU71fYoJ+30RjrtDKysgX6LdYtJvyGComyp4zso9mLId30Elw3VIS
XDWAc2z8IOoJtVbqTjIADSN1kpqiS3pcaQppFC1aRTyGDUtbLWV5fcc64DXl16XUc3KLGYux
YHChCrPbu4Hpe1tOF7ZFjnq5cctHvLwWP9tAP5qX6HdXTYwlR784/ZmkdWz6rhp7TJ1m7imv
dZYarWX6BQ8+s4lDtyJVdY/Rcm/NkTGJ+ujSmsHj2PSXaMDTZESq04ubQBXa5Y4a3VRYWM/R
8hRFM9oAeUxFpdRg1g683CKHZjYvqTHSQMPDeq8NsyLrQG9pMgia2YSzJARtVxA0AGe7t6Us
vxxI+2D3paXodkeKXVT74Msxdfroea2rJOm6wcYUMe3Xp+u2Jj7X9N2bkvQXm0aL0PCo7uwz
ifdp49+WpkuSxOnn657e+0LTe8hyW0C6ZFOFKH0w7MjRx+9PU74GIzU6EE1+40JrZiFtA070
+QsBVx5k4cdjjJr37Eq8F7HmoT69uQBp69DT1or8yJm9oekGsyJpeeJW1er1CtnarLWDGSP7
726sKs9XThOhqUVxYO0mOOY7Px5s1ETFhMul0e59wOG0dc9vH8al2558rH9Bf1xUOzLjbJkJ
uCTom0k7rQkjEN23pF/nqA/P0TkoFh1fpFOU2SRzS0vYoxZNHu5/ahGGKZLXYPTzdv2Mx4ab
0JhTZ+Mi9LWwR6sq9LjgT3FQ02U0xtl8mYlsnwyLo5vf73mX+o/ME6VJJe7psKHcUhcL8OPe
pd5XJ4qMOfolRtMvHjXWmF6d3DoprrzpEfm0r8FRazq9Nbl15/m+YjSQpmFmamo0JrdePNr3
3A4BjXvFaL/7SahtaLQcbp1kfKKGp10Prid3kNR3JwK1TjM2kSQnAXyprX0Fl75wo+58S/HQ
0abAdBftMR/BoZsy7/xwtWLsaXtAy1H9Zr3PoXdkur65bhzbIKCZlh04UiF95PowWpv6vETY
LIhGia2L4NONqdcfvdWnWoVo9lerWa0V0YvhhqtDQOP0F4Eo+vGY3xVXxrylO7OOTd+i6KER
lpVdfLq71CGPORP00+MiZ7ydUeoUaOujs61egeX+bURr8qZEKJRvZ6jWCei82OxCUppO1E9J
Lpr+Rkb6Z2z6DqLzo+ZbXYg2+FdA/Xh0WczviyujXn/4TC2bRtFC5ucut946LKXtSx9uHHAa
IWfd4qf57Z6clQePYSL1slztK+hXF+86t+XS1Id6T3O8Ddydcuk28ljlpWVOruWyVnsUnfMv
laYlHq7l8unwVo1tvoerLWud6qPLba/ytO/JpU+oF+5Pef0QV9sll7ZzCyndZHtMtLmDoFlW
BBGprBaS5bLW5wFpzy+oTXJpkv5GcWdwo0Yk9kqbn1uSW9bXJVmb8OBGbUJr+zlLhVyaP8c+
G/l+01hjTHDaxEdtr80ut74nSTORephLe3Na+n9jsGTK1BZkZAcwCb5RHFD7Jj/WfKcGFS3+
LXm0PbQ4F9KsvzfoieZn5KKmIbX6qY/Hyhs1Xg29mZA5Mm5a4/TF8rR5dEteeHaisuYlmTTP
8mv6mdmJ8aVy6btcj7UqM5evHqHLlew303hxTr5K//mTzDgXqWOOnmimya5jotrCCryhR5pp
nYI4j+2FtsByZ8wCjWztW3ztm/fN3jhLLu3kax/Xv+yJlmv5Vj59Qa9rHiVX+zyfvhmue3Cx
XO27glGzyZ/fHtlrJrHmlb1GFsdDyorBxDnr/JDoYFYeQaxTRRt3hppU4UrRe6JAjUMbXp+C
/iUG2aIAQVjObnJoT0i0Owia1ULSvhOSNhmSdmdoY24OxfJgPMY0bnYAHb2jef2+DJyddU8B
t0lev7mfG4D/mr33u9mbrGXRMj3Go3WLoh5WGUcvDirWLq9ZGPWwsVAjV5s3av9hnlaDWfcF
pw3AZx7gBC2StNQc675JlqMd2vwGIUUqKaD/9lxtF7q8HG1CYHkz9WVIXN6aiaPtzdGn2lPX
DFKmyqO5dYwomLniUuqOTc1zpGjW+WYurUy90HxO1SypzWoc7a6CEZnnmxtKmzPl0dxq0DlA
n3puQd3gCzK1ufMb/R8P+lx5oybMig7fCbIsDyLWAkcL9fwtRZoOrH0HXTvLJpPmzzFXdbi9
3p4iSQeuYy5L9RnTLtnaPMtdha4S20mTTG1+ZnJtrp791clYSdr/DRUPn76+ZWz6V49IW876
Vu/PnpkC9/t/n2a1kGhbSHTszz5qrBZSLQmt31+FRIe24robUr+JkOjQPCas3z3TUvW7N9rB
3NUw7V4otP3n12ZGzRqS9qWQtG+GpB3a/di9kOI8RI/9X8yKzD8WDaw91xCMv/2f2IZfDqTN
fK8tOsD1wS/9P7H+Sf9M9KUe6a8l6OP0CxmY3k5emV+w8ErtYyIf2h+v231KE9WkCkzvVM54
JVGf2ZiekSKkby/aOEHffEaCjsLUmv6pTQ1zYgXHGmpeUGnydFLaUS8oKvW5TQ3MHyv7W2Np
/Nx39RmNEnS+blBiXqah8ZxNSNfuBpfz8MGBaWIb9fI5IIXa/hbY355AB2TRclpPtERKlKZR
RupaVy8BmyToBbcH31liMwkPeFdmKh4p6yhOjJagX0kYYyHW2YUHiImNnulxbRWJGyTo+dGv
FxJuEdo7scaj1LRZNFLaC4vfqBCliQkzmvMq2qz7Z0lZvme5lSgU016ZOfHhqiM4SAne3+iy
IUbL/wDVpNU2XJjZpgAAAABJRU5ErkJggg==</binary>
 <binary id="img_26.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAQcAAADKAQMAAACSZBc6AAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAERklEQVR4nO3Zb2gTZxwH8HS2JmXR4hsd2C0G5thANJvM
PyDeZIgUF2TzxRiKNPg31g61EyeBJYQQ2V60Q0W0FgOlMAZSunS0ezGarMKKtJ0ZERxj9hIy
lpXUpuxgSZfL85jLpXeX3u+en3OllM7nRfrc8en3+XNPrvdcTRQpxFT6yHPKccZA2JTjFCwK
j5Kkrd0xbUvxxWSWK/B68Yt7lz1wM3gw+kFTwv+iq5bTiXgrZ/3qVjjOv/X+VDjz3oWITjxs
Xbk6sN4fO+LYO/1d5p1L+lbizo3WvltD8cNr9k6HR51v60XMWWsPrPfFjr7rnA6+ZA/o+kGL
NKSc6aCRCa9OaEuERqqOIUERMb8sT0HMWR4WcFmhERyYXfhXwrsAGf9DQTAhBLyIKAxgGYsl
OEToy/IV3fLXzFs6nJGrNdVCOrvS1MVLInU3USbVIlepSiItV0VjUakTNEMn8l6sFVwI56jo
ZYpcc3iSY4sT3Qc4Zk9zJ/wWTHzRwhaJ1Z1/VkQSFmlOGe0YLLIRRYQCu/tebtzFmPXQWLDH
aWWK8QftkydZ4vb4z+2pY7Agck/Hbvam3F36NTa3CtPJ33YOCNZG/UrmK4c5WizQnJcYizQt
CjTNEvAqrM747wJvJUdHXjn3E8cU/U0br9mY4m5LTyTEFP6M73t2Rjhz5xt2hs/iCrAzxGRp
rOzrIj1d6kWHKuSzOrFObaVcxPmCfIhl6IQug5rVVlId/VagH7yaca/ObgHWmCaD/8QZwsQP
NwARUsX4cedVQNxXRXKVHcggmlbg+XiKdYpnKEKa0wgk+KcXOQNRPRYwg6IihIp1mCAH58RM
TZZy0ApS1tjcPc14xiitbAsM58NQ4BkNC9AKLmyooKhAx6J+b59d0H2oOIkKdCwEvy4hVCzE
1cdFFhMimjG7HROFQTQjh/YjjY4FX2OLIqKO6q3rl9GEdvfqKz9c11zsrpyRbxBmjaBgK3S5
CWkvWfwMEoJGEDAjNUIE6hh53LftUGtmCBKTw5YNxHSl9dXrh9yZ3ZB4/Kt7i/iH370ptt9y
GhTRzsQW8W//qc2x5vrXwVYmOt294o+dp3rin7rfAMXtF6z2obXDdX1bPWfcoKA7OOkzLZaH
DIrSXmVuPpbClTMUZIcg3KdCV94z9ubA2WFAiI49ieCoy3TJHjx+Z20MEMXBj37/a9S53bNp
28efX5+BRP+eKb/lcEmcL4komNE0FZYzznwdvAGIvLkp6h91OTz2y65vX4MyZhtolJdeAKaM
Rlvg6US5ljUSSg0XdFFF0mSKVt2kts7+Y6q+S5nzdR2aX+Kkl3BtGsGBrSyBe/IgJIg6uNKf
tGcUZo1oAPuhEQQXNlCUZ/2yfIohKt3lQMFL1bZGD1vQtvoN0o8IKMplRUuvUodFuKUdEefv
IRnkrKUZyRDrBUQUNf+wWJrv2J4LrcDf8/vkjZN6pnbek9ITRAG3JxOzKuoAAAAASUVORK5C
YII=</binary>
 <binary id="img_27.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAQgAAADIAQMAAAAup+28AAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAE60lEQVR4nOXYf2gbVRwA8NQGsmJdKoNRKTYVqlOwXQa1
ItKeA/+wulL/2h+CsMAyDGvF0fpHV9cL2XTiH7ZM2XATW0G6f2Q4KmTartEGFJwasTCzZd2l
OBd/lCVdWO5iLu/rXX5c7t57eW+tcYK+P8Ll+OT7vu/73ru7nA14zXZbQnGtQ6zRRe5qEo0l
BRD1E4kpKB5YRNQz3xZouuvuleceTYUbPgm4CLHUEep8Z/nY/u9eeDgT9X10TqKIo52B+wMv
DTS3ZaK/TZ8n81jqCH48ufz5H8772jOXfTPzZIxo/5E2/z3+Xx/pa8+EN7nJPECVQEyKSTW9
H0Cab9GGhQtzk0Cynri9qv8PRaSqoLd6kxCoP8yZhMjNoxZio3mYBT/GRkW2BjFMrTgWWWLE
0MXyjqOcXq4E8bnPYyIfDGHCPBaxIPD08DxIAbg4y4tBtjszc3yBV4xs65x9pbTNRO1bqnRs
xjYhXTrShVw8RFYhb0zk1ysUgd2LNjSOkCHnvjwoMEW6+5iPJwKNbCF3v4XHwMaSfogn4p2S
WhLxkshaRTJkjPZ7eoxiKwgpsPX5zW+34pmaxerF3YPtHzDFNxFvd6t1BVnE1dXo1we6sBgp
0xqTv11976dhrRdLptlP7ScgY+suiLXVJ2LDm1vxtY6g/EAhg5qFtIAoAgllkYZEiCkKjSpc
PKFiIksKp1XkCKGURbjRvegiVrIuHCURGzp+UqIJox7hIf8CNYYhTg2dfp8dY3Kve3KCKRbt
o18JTIHWgNyV+XHtVFkUqonXVOk1C6CImwuAHJgAQrBjaL0AW6jI1MvK62dtxBrTM3WWRbJr
MEYVQUOM7Lki4LOv93KiEuPlS4RA4wADRh726SjRS8pcsfTEZyptrfPrURlLVRHBBFgEovSS
wwVMMHvRxgJBTJA7ChdYpuPk+sB6WdCePZkio62g7bwY5YoZ90oyD9PfnpD+QYzFLKZIoVfd
UREu/YNYp5CoCCcp9HkReQJchkBOSh6aCFeEg5opNBhCpQh9nR6qiFlSaFUHvGLkvDiY4s/K
nqsiQJuXBFPoY0myhbYrtzOFXvVtTKHvfXamesVOY4JcQU6muKaNNsTs5cteUERMWGNcWtCe
pphC4opEL09oY1HwsRDXMYU9+6Y7kCHI+y13VzIF/78pFGM8bo0BpBD/o6Jccv08EmhiDaSc
HgqJ1WLc0h6GUJPn1cmWjsd+maKJazHvGyjd5z3+bEcXXcRjIw4k7/Ru/f0B+4MumrgR815H
P7+4d3ffjq53JZqI1DU2oS077T809HTZqQIaWkclQXYoLfpypIrzhW9y9dGWv6GqNbW2f0zo
U1eavhxNoGZPatE3/eZr7sWmH5s9NBHrz9z0nZnrmTm17+kLo1ThySxu6pztmZnbt+vCHopQ
Y57rJ31nZosxdtFEfX884Js+d2h66ZWLW56hiOwAfDGQnFPzHyayIKxQRC5Suswn/k7F/g3h
L1xz7hVGql6Dts3XzR8Bv62ufGYKPWWrHzMJoQbXwhqJGxJPxF08keILgfN0UXirwBEiT6hF
UW8SgAkoiGGTyFHFWOOBagKKon6wnSW0dnhoxsiXLp7kioNDM8Yx/Z3SQbvbOKa/l1JvVU5X
eS9l+uH63krSRS1i1OIdGz/GnRHAFet+yoGJwzbjCoP8w0qjWfwF9SJYDOcc3O4AAAAASUVO
RK5CYII=</binary>
 <binary id="img_28.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAUMAAAD9AQMAAAAcSof1AAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAGuElEQVR4nO3af0wTVxwA8FNw4ECvgTnrMF67FNQEWE0Y
YGJWKzGRGEb0H4POWDJ+aCgUI3/AQK8qa5fFATJTBaZlm7J/NkNYLQx1BSEwB6P8UH6kaCFg
uk20IDrKeu3b3ZVChbveAWMy029S78v5yXt37732vnktBFjGAPQfSYuIrbTqwCRLWZkvrONr
LlkSFdxdCOy/WeKH0vQecu1KmVRTnBus6ToYL0uuiK2lkdbru4t0mfmc7LBKn1IYTnpXXEMn
C5IVN5M0B3LDKnveiZcl8hJ/ouudl88LlGjasyFVwP54OHFrHK3MaT3RnKnDsMbCaBOC5ImM
VtqRtzkOD3UzpxjmiFGOU5yjlkOs5Vm2ElvpKxfyy70hqBl8S5wwptJJ6E1ICa0UQhVCaCro
evdifZ0NrKWVtaQKj/TIBUn91MuxAK1MEkPxf4kP0jE3Uul7XKm8RcixoU3H3cnqY4Lq6rWE
fN4SJnjurs20cpGClAMGyXsDbqRX2vcir5/ZyGy891w7Ic0J6VfbUDcSOq7MJTP9Cv9tejfS
PvNpdxt1HOnmyOUdzyDnxqKkBaIMyVzp8si0O5PLwE4hrRQSD/aSRyUnyKP5ZTkADaDOZwUm
0jtkPXFA4DFckhM5NGAX4b0/xfOJqVuZkv1YIt4eCpxywgyI56Y5ZnVvwuA5YcOa7ETNNgUh
1ZBAW9XmK4VxmVgQ2Z1Q/4WckHuOFieMRHQflOT8UCGtJqUxXPtNgWyL0Q4a00NUnQm/JQfi
d2Q+vOfivpGs9oMS69cVGUpS9oZrz6w9ysPl/XRFd/u+1k+DSOm7XkzIpNxzFx1tfmUI1ZZ+
+LEAlx3pgu4OcauAlLFH98eNeAeI12TzVMnkddb38uWl8b6E7IoTFAXGPhKUkXcky37wpDXq
tBpLrB4vJOQQZnaOvOGwnIMcGb9sBeWQY2gwcNt1jqYl/qfROfLAyG42y1nPO8X6ZN+mBdp+
aScK/FE8jSfPkFdNsZYw1mueJpblp/f/SOrmL60e6ZHLTtq+c2Z9IvcS83FmdUwSdmbEo82d
tC9AjgIGibCVYFqOMUmRM5lkkjpnwjhHs8MjPfK1lTRVOkVtM3djiwz2kqKyYi9t7Nu0/cUV
gYco4nLuVDRKJbHh1WpQ8oGrzCsSUcvHarABi4qJHlKXDWacqhaBk106CgmwpmwR6M17OyUg
FvbbIg2pOgLyMqnatGIHfBHQt+OetO6j1LvRxwTlMEA5lNK2rsUIDOX10rqN8C98X54cBWgL
Ve+Ttoi7arzNLukdrizN/4+KYj04aaC8TpsWH6X+U4EZ3BjYjzcSUoUAby7VKNHEouZoKSRN
7U2xPmfFVPVvW3bvzWUhdfOXjM9iZP6SsWZgX4csQDJVQTM1GHvJVK0tbf35L9ZLull/s5C2
V9rmq+2d/cizl0tx7+x7B0xydnikRy6hxHb8iUnOT1h3wlXQCojb0KUeLYegzVSSdc3gUocw
7Bayly7B2OYgcUB8bLg0onj6eNSuw+Uo8Qyzi0jUYCFlK5Hr0Ok96nET/v/lkCnV0RSxT60m
kkmg9uZpa/X+GUpy5xkxnhwsHMWlma/gaF5c4Yr9hRcVAbu48Tag7gvRVrUdC9XbQVNShaoz
dji4hJBXVQbti01dcelaZVFnSguKy/5w7ZmCFGKP+r60qbtT/PRRECGv3wxSrNvUc3iVFq7l
pPyOy4d94drPwyXEHnWH9I3ujt1PvUkZXmnQNuJtrtI215xP+RWXrX2C4lLZ1hA9IQWd+3YP
Cwy4fJZ0oUzjfaVdzOP0FH2ZwpXho9TPl5eCz0LwNg1xcigwbsybkJYxBWJH7b0Nlg2gCQFc
MzFHppmRn9TPjLyOzPTOzWhCuuxRu86R7uU5ovoWg3o22UvXtYS6lQuufp2x7N7Fr6ck3n6O
9WQjX2ZaaQLONWohX3paOVSYY2p2yLHC7ZaJQlrZki9s6CKzv4fzOZ+M0/duuHEvyCGfpVde
fCaml6dv1KlPk9loeuX60RP08nKWKsjx5Y8pVNOTGkUvb3kLGxzSLFdwTDp6ac+aTp2/JqGb
IxtrOTc80iOnY16/B2MraxqFdzi1vXmZZ7kxMKyI4vuJaHqPfEt1SFDbkhNRuneDTBb8Y1gZ
jZxUJfUi2WfkOyJrvB7AcGRAcCmNtKqS2xFJsZ8lsjatJB6OKOJfom3zQvehpNrYnNDivatl
qe9fuFaio7nO6EZOTGbtSB7ks3EIhrPWPTHQSOJLYTLakJlT7iXz78Gogp0kyimWbcKs5Qq6
tTT3mb3oNhdxneS9/wNhKWc0XoOkggAAAABJRU5ErkJggg==</binary>
 <binary id="img_29.png" content-type="image/png">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</binary>
 <binary id="img_30.png" content-type="image/png">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
AAAAAElFTkSuQmCC</binary>
 <binary id="img_31.png" content-type="image/png">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
IGYAAAAASUVORK5CYII=</binary>
 <binary id="img_32.png" content-type="image/png">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</binary>
 <binary id="img_33.png" content-type="image/png">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
AABJRU5ErkJggg==</binary>
 <binary id="img_34.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAPUAAABCAQMAAAC1sALrAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAACIUlEQVR4nN3Uz0sUURwA8DWWTEzQuogXCTwWpHmpVqYl
D7sH/4CCvUV0y0VlDrK9MKhLWpiYJLLtRS8uQT/oYKCx0CbqriCEUDCKh73EzsQSb5Z5+759
d34s7s68HaIu9T28N8yH77zv+87MC0DT0AP+vu3jmzhnG4H11nxr6vZA2JWYrHnm2fCs3Ojl
/fCDU5LpH18EO92uBWcjSWv9xT6PfO30VJvl2z19Z9y+O/w0annx6j3i2hjrvuas7xk8lMku
kGb9y0O5aX9VHzfjL7gWMKNF4Bs2MJ/n8/rbdnR7u+FcLPj4/m/7B8nbzVAg07GOc0mQ33ox
GsepcCjwW/OxOOYfhAVeeB4bx/UPWgT+6ezISdLEta/XcdS1S4L6R9vzAN+IJtr/I/xqVAlK
IlcAjp4AUHH/NLU6Cp1h9TwjdI6br3y54XbDdNqLw/Rc2MMV9B/VUyI22BWr91Ju7LGsMqJj
acWekWCXk18xnaUvpIGqeghLg4c3z4X2ZPSSQsGgVa+oE2lCVQ3X5qOvP5+fV6rlGPwoFy9Y
nkih6xIwtnxiczVrlmss0bdvVmx/RSjohJcTxSvRdWs7xg6dWJNN/1lJzUwqDHj+cvF+q71d
Iyd3WM6J3R8eGXr5nTqef5+wvNY/3h7qB6ddxs679NrKMcfn35Gg5sAnAZL1+XfhmDe+n7r/
9w/+r3/Nlf/ERe9vwzlqBQ5e4ece57d36L8ANNpXm6zLXQYAAAAASUVORK5CYII=</binary>
 <binary id="img_35.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAPMAAABBAQMAAAA+OgACAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAACGklEQVR4nMXUzYvTQBQA8KmL0sJKFw9ScKEepFDwppS9
SLqehFXwruChgkpYqiDmVBPawx48+LGXPbjs/yC4R9uyJ1FY8bSu1KTkUBCXRnKYhEzmOZPW
mG5m0tPiO4TM/ObNxyMZBJlhIQkQJfZvIvfKsW/SMXSOu/+2v7GsR75x5nMl5a5Xf5NXJl4r
VEbH/bdzs13YibxTy19Muf8q9s1a9dKD1P6W6u18OfKD2sNKyslKv1OarP9D+6ULTrgPfkZ9
5nsU/98NC0WRS3Z7KBF5lDMWwtdJd48+7mjwE8BAyrQrTDr++3JC7v5zXeRDwmag16Tep6xN
5Pl9OtqyS40Mx63D9YHUbeYHGY4p3t5rDuTnp/jIbGbmLzaaQ7kDfIhe5A5TF68vdKpkOykK
PLG+vy91t8udyjzE3APviZl2Yi+PSmOT5weX22kP7qhrrbHpPNf9PePdp1nvISUYqHdD5o91
cuHs7osr0XZm/Tp3tpOV9d1C9b2Z8nstHDm1zt+oX92adb+hqota1+uxP4ye+r56+pwOQcIn
h+6GFmEDArh1/7ae/L7j+nrAB8Dw6RexUx0Ir1b4Vez8QXnTA6lPBmQ4G1Bm5ePuj0UOgIrg
cscSB1R2XMCHjZcSB2MpF6pVrSlzMHKs6JpsfgCH+zO5W6s8Xz6/bYePVK0odXCpNt3JnPup
N/f+OhF3UCpm7vc42P0/9QWx/wHYSO5RLqOGKwAAAABJRU5ErkJggg==</binary>
 <binary id="img_36.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAPQAAADRAQMAAADBgA04AAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAEjklEQVR4nNXYXWgcRRwA8Esb7YMtObSYQMBUMG/SRigl
4sdxCkIowa+nVDANBnyxUGjEh1S2LbHxQaHEahCFgA99Egn1iFJNNiHQe2jtSYOXh0P3wgmH
NL299mhnN7Mz487O7vZ2dz5C1oe6kNshv5vP/87czmSI9GpmHlLHfXJHpRY5LXHbOnA+q0nc
frawJnPr4NQTOUn9t76fl+V3Pn36nKx+/HKdQOn4mAr3rpS+9TvfgX+3q3yvux08NW3dM6jf
1RK+ObgyPjZ1t/+rk9RrJGfGfHboteUC+Hw/9Wb1cm8+5usjj64Uuj/LUrc2v7v2UcKP/Hb4
Zufb1Lc2z/TEfabjyMbhnsLaceofzFTi/tJZlqDtb+293Bvzv8kiSyyz/uux/t0LEg/t/CG0
b5nMSb5bGXZ1DvC9FSTm/S9mug61O3jgGks4Y3InKOKQZpG47X7YQOiriNwhA0Whr6AbP178
5ieJN8hSReraJxKvIVNflDhA5pUvK9NiJ+Rjb0qInV3/K/fXbKEjQ+5QXD7QqVunOO6QJfCz
6XnrxHTS4dGRoyOm4T5REAwvVLSEG5PHiGk0sWav5Re+GNPi9f916xXqxzVncHjijeEPv447
fJe6O5PQxjNv5R95PFb+MTjqPi5N2hPcuT46/qQWbX+d1L8Ful1dPkQwIPnxKf74NMlyF02c
uMR3h7TYaj3PvGZEHbvjizOj4fiUYs5CnNnjftSBXXr+ah/PSXUXIXfA/YvlPxP5sZewMrlm
DSyVkcDpotpBfuU4CdJVgedCv/9HGSfaFy7TG6t2cnzcP8NPm4LnO3DR82+mdKDwcNkFW022
7A5EPJwg7hiz0WpGPDJHfQ+uDoG3/wen9DCAIs8pXFe4kdJNhdcVDhQOUzpK6VjlmtyJynMK
19u9lXSj3U2ix709QNrVF4HUGzdfjzuIeMWQ+1rCYcQrpbjbEe9NtK89QBpJ9t9ROFZ4e4B2
5LrC5xRuKNxM6WD7TngOFW4rHCk8CJBofxq4Y0Ia7OT7uebX4xw418PbHwZuX5q5xtt/6r5b
B888pnN8zvd/frhQ4eU3/PZdF9Rf8vvx1HvGC7z2mw8S3P0piHxR4oTv7QGEWtJZANk8queS
joLP2r7yO4n1JQiQfb47318eETpcWM1P7H+T4yxAsFDNT2TzYl+4MdQ/M8Rz3at/V9+r+2a5
Pue1f7dXEKd/LIBo0nPO+ER/43bgIKWHAdT4biscKRyn9HCJVDh8X+A6u1ldAjfYzdmp+wFy
+uSORe4HCOcEHrxlipwFMHj/TDryXiv3eOl5vofPCK98rHJN7kTlOql55wtA6CvI7SUUuUHP
D567zs4XOF6i5wOwwPb/HDdJDTUmxQ7o+cAvi3KveM5tH3S9MVisFAVuK8YHte8/OI637/zz
Jy10/vmipsivK3xO4YbCTYXXFQ4U/WuFfoHrUD6+BK3Ly3dm5Y675d7cjnvnB4L6qZtix7ut
YtY7PxCcL58FV8oNcX6y4vpthUvqJxuK8ltWcZ1I2hdeAlfEP7WH2/Ys3629xTrRzf/g/Phf
9WPYX2NmIyAAAAAASUVORK5CYII=</binary>
 <binary id="img_37.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAPEAAADTAQMAAABqYWd3AAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAEDklEQVR4nM3Y328URRwA8EtqIqTRGkywAtKSIBgkIKla
kzbdFGMCMdpnQoKQi0kxXHlA4hEse1abPphQYowQEO8vIMSgMab0BnoEabCulRgT2uvWXOLZ
h7oth50e++Pr3e5db3dv5vsVKsF5uJns577z43u7s5OLAFoSkeX7ggh+r/rfYNgNYZ/0+ciN
WDLs+vXGA497Pn97dEOtn939VmvFXxB435+dZb97+8XZGp86O1OJX1jTHWNhv/LZmys3I+vP
Xm+MNmD5cQAUwtUHzr+Ku1Px8xJPn/S+MSzxsU2rdzZ21e/tkfjk+1/0nOvqHn9b5kc/P1S/
Mz7+ZETso6+cOrR2V/yKzNOJVT3pzvd2bZL032We7nn69U9l84MuU3dri8iv/V/c///GFyNN
EvdKHej+yx9VfQvzWgEvR0UaEhFF1K0KPO52W3U96F4xpfH37cvt/2HP7778Q8I7wHvIJO6s
BxtsU+rWet4YPRNFHHozsQnMnxtF3OmAsZsBz4TnH9P9nksFXYF10VjWc1vbcXJzCrSge6Xk
977h33IHBmTOh6Cfp4Ku+v17YKgPwzBn8LHM7/0KP7Zk4KrMba85p0q83JxTcF9sIJyKJ8Zf
ZLjbSdwDD5DANYOI54TnCTcJt32uCNyhXMUdKGf4/PwJDLvVXNy/NaG7O/iGUt++BPvGr170
JTgRqa91fwLLO3yd3wtQW/xuE+4Q7k/gAzkjXCdcI9wgnBNuEi5IcMAFCQ64IMHB84GaJXwM
d5bCXe/AXTuBu6HgzpO4m9dU1AsFhrqdx93huMNxfHxoD99iIWencdf34m5Ecc+14s6/xN1M
q6jbAwrq1gIe78zpqMOxJO5D+PiQqsNdz+FujOHOLxGexd28qaJuW3i8FbrDw+7caUIdThzG
ffgi7l9puE+14G7kcOc/4J5fxN0s4G7nVdSd4A9Q6/Ek6hAfdKt5mTPmVtMyn/Ie8Z8gKXZ9
1KtGVhwYELnR7/nEwa2XRM6/9rxvf7PQ8xPe+PsObhe66b0lp0dWbhSOX36Jzf/RfmRQ5JWX
WPG4xYSuLjWFHjwlCJyVPgy560sfTqvIjSUvvvBlPrnj1acysSGRu6eE89te/mXPu20iNz3/
5JnON4Tu3sAT2679tvsxYf9ugn9uvrtqz0ahuwm+NcRfyvQK3U1gjpXmmRe6srQO4foDxzCR
64RrhBd3iJEmxIsTOxURnF8rxaxO0RS5TbhFuEM4qISzZbpOuEF4jnBOuEl4gXCLcIdwUAln
hCcJzxA+Q/g04Yll+rRS3iYkXvqvIW/I3Vp3BLKa3J0nLqz4YDsSXz/b+91hJH4t7tA2ewz1
y8/jnio+44OIs3L9qL3wP5/fo1pfAtu/3fLXs+l3Xiv6P4YpymuygDtmAAAAAElFTkSuQmCC
</binary>
 <binary id="img_38.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAZUAAAD7AQMAAACYHJvmAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAEpElEQVR4nO3af2gbVRwA8NvWzozFVkHmKsVECPUfN29l
lk0GZ8A/DE7G/iyU/iCbW1jjVGwthPVmJ+k/My2dVLZh+8/Av6T+IpV267ULOPeP+UMZDSGX
xMCyQWdSAvlh7u55yV3S3I9k+V5NqfXeH717ufd53+97ee84LsUQuGSxTRoKUZRKG5Lj0Gzh
hCOK9VVpHIpQ65g3Ql/C1V8rzTG7UcVw/Z93IvzuIB44RxuOBDzPPao0199bVMvtfW8adb7j
8AbOzNhuBSzT/ZVm0apqhsfSaLjHcMDa9PIFd3dzPCIxp9TNEm9C+7zWpt4Lnu4XZObinWqm
y/bEG7D0OjoCljFJbvnYVULFjP5yDJnta3jgKH0eX2k5+KjO73SORBn+kEOsbK5rlIkKs7rZ
tbNzDCYt8jbF8pZ5F2Z424hlSqZJbG0u/iUlJint8BnRSBvJqhnpxYxuUOq/YHLFQ7SKCRcO
nEmsSo2/ipkoHBhCZpKR8fZR3OfBfUPT95TG8FEinmNTMwRiyLJJr03ZTrhtFrdtaPKawnxj
MF+5a8BXjF0BA1EeT8S5sJ992NMc6W5+3qcwrzpu3aQfT/Wc9Z42bZg/BwXzMNB0+orC7HVc
pujoZG/zd9Z2opxbwjjluM3n9qDtzB8/yg338eOOm/TAVz1Or3XfRhw/Nm4cxWMe/LNPHiji
cCMY7vMZcSt24P75jTiIpoQGd1TmmmOWyURmZiR2/cXQGlWfQaz4ZefDaAev63/bpGoZpnCX
rGouizdlqckhOp/aqCbEpw8xt8PZMKdmxE6F6j2p4T98upmtx7BScxWfqzREPSbknQXHCc0L
JiWNk6nHgHKbh4+H/nQebEpFi0mBTUyDSWoxpjrHU/kM0VoRh6wzTmvN3Ap7bo/CkDX3nLAb
ZCZae28jtK40cQ1GcQ/Jy0xSg4nUYeS5aTHLJDw3TYaA55ZHjTHy3BoVZ6cZLfO2VUYYT5wq
NXKF640TLhnK5VcYRt14CL4RG6PtLfmEwrAppYm+dvbrJSKJOP+zh5YUJpdIxqIKk7j0bei2
iTd9LqVh3viy7feIwoSHBJPtGzm8hGSG/eun4OKy0pAFw5/2uZ6oG2WchFMwf7/rdE6rGuV4
ohNzdGHesnaP3U+GZSbtDV6ruQ4SpY/Lhjsyj3w117XSCI1qmXLZvMlvUZyn7J96ctONbiqN
7N2T1GSlF/eIhiPaMewof5IZyK33SftF7NqH2O7XIyd2YW9WvnsClW1oON3oRje62XITzsPN
F9+Pg83wBxTY7O0nwWb0ItxcmoPmFjHB5yBpEk8ghoCbrBZDbo1htJhywMbuhXLRTeNMlWe+
mqXKs6VudKMb3ehGN/9z0wo33DkNhjs44DbBDMMEDv1wChiH2f/Kbj/QJIPmLqjpWLV0AnND
LfA5KByKP2tCDYKacrWxJll4AQ834PEw8YHfSKDhVqxuqGHuD05Bc+NWTo5BDRM6uQA2fBwS
aFDp36+24drRTeMN54IbNl/l95+qQY7Hf45FYIZJ32gLAg2buRGCm5dCeahJL4SC6zDDTfIG
OtfHEYpv37WjG93sTKPl3ZOyDw3mH8sctJoKBBYWAAAAAElFTkSuQmCC</binary>
 <binary id="img_39.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAUsAAAAxAQMAAABptxnOAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAACl0lEQVR4nJ3VTWgTQRQA4IVUFKJYqIeAwnqwgmiQggol
4gg5FvHoRSilhxyKiR4khoAbSDGXFqEgFYwGQejBk7FQNaRJU01BMS0I6qF0WqQUCdlWAt0N
k+5z9jf713T1QTI/+Zi8nXm7y4ASAhwY64za/vJOlwF7pdVAzSttfF/bjwh2uoI5kZHDR0fI
SsvKD6d1+gNjovSakhQ5NWrAR/eWtMtIaZSf1imRfsdJw6BPinM2Cj+3kE5fQyFo0OIbMyUc
gJSjrUb9UOisWgyaqKQQZFC4aaENEx2vLKrXo9NhyN4x6PxARqYPFTqbf6ZM7hB6Zsva2pbA
7aMBJNNJnYZgIe1zpTD4JSfTzXyBNsrmC4MhF1rCECIK7X/1Vl3VDwJyo48xFEQlgYlPJZUm
KXVLYIHS1kbnCAD+EEqvISdNDdNcwUwFSt8jUIvGEixcPWmh9K+FNHJJoIxBStopLHEuNI3V
PKzU+DZHzju1F+F/0ZZ3uuikwryDbgAKE5+TVgIj4yyle/KoJFNx5GzfqNsRZIfOXcIpRmrR
21dCCm2cmYqIzPHtjBTLPhcgfLtnAEC8zr7wvzxE6ccwOwOiRnt6IyTtTOBp/cJlSmenhmag
rNIbJ75FiPNJhytHlFy/fqhf1Eq7dexKX8Rts4R3sMqlmM18PQY7CMxH0LbvK6FTKaY//zkG
bSvlO0zSqLyvE9VADIA10VWhBhKn0bVEtxo4n4w2g0idW+F3p7vQZDJK4iVlsHeL33U83kz0
wRhP7qqrtuP8jp026ael9ZIQJffV6fYcL9jpNh2rp38YEmNmShMoa7WgzlVRBumJtIVSZwcg
oXf0d0Ft/9eGnfL/QjHqCi0Ue6W9FdYjlbgtzis9wJmoh1j/C7h0Fjum1e2tAAAAAElFTkSu
QmCC</binary>
 <binary id="img_40.png" content-type="image/png">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</binary>
 <binary id="img_41.png" content-type="image/png">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
</binary>
 <binary id="img_42.png" content-type="image/png">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
x/4FTlX7IjqFLX4AAAAASUVORK5CYII=</binary>
 <binary id="img_43.png" content-type="image/png">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
AABJRU5ErkJggg==</binary>
 <binary id="img_44.png" content-type="image/png">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</binary>
 <binary id="img_45.png" content-type="image/png">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</binary>
 <binary id="img_46.png" content-type="image/png">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</binary>
 <binary id="img_47.png" content-type="image/png">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</binary>
 <binary id="img_48.png" content-type="image/png">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
xTj1fwC+IcjmfcOeHQAAAABJRU5ErkJggg==</binary>
 <binary id="img_49.png" content-type="image/png">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</binary>
 <binary id="img_50.png" content-type="image/png">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</binary>
 <binary id="img_51.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAWUAAADcAQMAAACS+SLxAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAFnElEQVR4nO3Zf0wbVRwA8COWTH6tyJINlayYMPePiUNh
oIMehaAw0rE5/1AJ0jro+MMAc8t0Ejl+mPGPP5ga5tikmPiPiSZTiJjpvDLUbSZSFIMjm9yZ
Qpq4xJbU2Ltc7573oxTWvvbea8LA6YOWy+snL9/7vn7vvTsIgNE44rZrBkeLTiz9FY4O7Xo2
awRdHz2wAzZ8HP2oxbAHWYvvWlrQxxb7rKnoGlwGH8C612Hm10oTy43jaX1Nfh/xJn1NrRwy
d4AW9TVHyu+0ehzS14Iincph8BvA6+rx0cwyw+w9sw03N3tyZT0ZPoFSP6WFSgk3VyIp+bil
fGCwevCgr+2GsErvKlT/XgZUwL0y9sCXx8ovnLcOHPBsdin6+lmx0xQiA8FxFUxI5px0Y0QP
lraUX3gkdbpO6JpXdYb1VWPzYMA7oAJXmfmlvfWRSLIfyCrbsvvpd+pA1/wJiiPmrtY+bqx+
r2PngF/9JuWZcv5oj+jC0TfMm47/sq3J4/TkyWPP3W1JN6Z2B7yntbG7zJdWtPo+TC1PE0e8
X2d9ob6492igRO2YBF056bfqryMZ5Qgiw5phLBzsKH1Z7XADao8J3KLBKl1OR3VRK4ex+mJU
z239xpqUN1EAYP61sPbKLxKiufz+3hq7ofhwd2WuNTPFRqifemVLQzRfMD5089Mdpw6POn6o
sY9XEKDzeKUvpf+a3QnRQkHfkOfD1Jknxx1XKux/E4T01Nb9vuaxs7XxdN0nxae2jTqu19i/
yCekb12Er23h7BEGov2bTw/l2g1FJecduY2ZJTZC+s51X7vj9fstsLH9JiBR4BylVqVTzqDk
mq2sN+w+CI0kSANJK3e1yfq5rZWbek7OWGA5Abc2Ts2gV8k3sga+VSqxjm7J69ChFy9t3zf9
BPMMiubSahzP513r/XkvhaCF37bsfMs815tdlUh7Izrbsd18r47uW9ZpVc1TTXOf/5RQPwYk
kxr3odY2Y/3cyZmEZ0mmVag66qM42tx6lcbQtgkMXdraiKH70iwY2g2CAF2H20bQflS9vDlJ
EUSTvpYvBftT2A5TN9JuRm1L0N512OFJvA9Dh3gvhub8Hgzt9/+OoVmWxdAu1kah626XjUTX
/op8jEj+6lzE0EHhTwztFSQM7RPkpQxDry44Pc3gahJdO4XIgoigaVzNoGsKV7sh3XAtyRpW
mHG0vOvH07AyjnMXLesgshZlHcDQgvyDqgV5bDzNI+uAbEVIP1wHsXUIWXvlyGFlDNfKrONo
EcAKE64ZJX94mkTVTkXTqJpG1PIC0q9AQb0dDvyYWF8BwKiELCjBsxUTifV8Q2PWZ3I6VO2d
b9LTtQUFsuaVpPuGdfRwg+XYK1JYs84qvbGrj9nkSeeVL8ui3thT09aihzQdBD69uFmX9c0R
MaxZlkms1abpAGCj6x6qefUXUsZQLeBqAcAKE6q1ixqG5gGsjKHaq2pIYUK1LxlNoWm3qiFl
DNUMrlbSR6NpJ5amNe1E06SmGTRNJaNj11eYlvA00HTsagzTobCOXY1hWkxKx67dMM2HdWxh
wrSQlI4tTJgOJKVjyzjRE22kuzpSmUM5LyzaE23vciQUg6Ank9Mkip4Iaw5J9+Bosjus/SYU
TaBrD6CCYS3p3c+ztgltdpA0P3UDQ4sMnsaJRGTcgEQ+S3GqCWfsBQZHL+FopW0gjZzBtdY0
3thYZ2nC0t1YVzYQvrJBthzxtBYJil7JyXrrf2vc/+v/qgYAevtyh+meybu0HUc5ij53xq3p
gugdOFQ7GW1nutCOoHsmNc2j6SVNcy0PI+gzF8ORLL6NEncPjp6fXNbRzx839syvtYa3eJqj
cLQfSweOoD9hlbV97NcBZB08MTbzEbq2G7JH0LV1bPZBZM0SRdP5yNpHgqXY/5LF1TSsl/sH
6ni6WVC5vvIAAAAASUVORK5CYII=</binary>
 <binary id="img_52.png" content-type="image/png">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
UkfE7k4AAAAASUVORK5CYII=</binary>
 <binary id="img_53.png" content-type="image/png">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</binary>
 <binary id="img_54.png" content-type="image/png">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</binary>
 <binary id="img_55.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAIcAAAB8AQMAAAB5fDGTAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAABhElEQVR4nJ2VsU7DMBRFTRbG9g+M+Ahgi/oZTBkY+ARG
FyH1HxCgzAws3RhQUjEigdSJBblShy4MICFwmtSPVmXA9walxEuko3fvfc92ayWwvPqLjIhc
EskG3aNOSIbjw+OQRHnvJPTZGydhzWjQ7YU+08eDqQ479CKmiWww13/Jeu38fNXWrorlWf1e
0ZKEyxGZtyK1Pk4j+SDVZ4zkVSOpqIbTIWtVwyqq8UkD4Slq07lDVvmafu7J55R87rBG31I/
fYM11I/FrLk9J2fM8jFnnZFqhllJBqRMULXQSCRGVd2u0v5QltNMKEtwN5zGrMpgljdP6EOz
VxtloUpSzBKLZ8E3QSyeu9g3UjWfoBPsx9M9lI4AqfYvgCy+6P4IT4HpBaUX8kAq6ie9wfT0
Gn1S3PnSUpbNcS7K8oZ+g/EVZaGqtI5qBAjfcG+aawrbTPgfoNpGIoJkQqoZ1bQj7MzpLck7
kQmR3ISkr9T6Qlcv4qM4c8v3NIe3UkC1enND55pX+BvZNl84UTE/PwAAAABJRU5ErkJg
gg==</binary>
 <binary id="img_56.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAaUAAABuAQMAAAC9RJ4uAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAD/UlEQVR4nO3YT4jcVBgA8JfY0rAHZyoVKYjJFqX1UKhF
kFolcRGvHUpP0h4UVPDSlpZly+w22bJC0bZT0R7UwvSiDkUEvexp9q142EsxxVKrhzbbap1L
mex02M3uvM37TDKZSWYyyeSlpQXpY3bJg/z2+96fL3k7CDK0VfTAys6kKAHrjMqswLCwzKqe
bp55jV2dJB/MYsyuNmZSU7NZMsyizpGzP7ErT2ZSVM2iUrWHrfRJtWnghpFbM0tJahW5TfR7
xgm5ZohNfTsxtycp4v5az/m900X8ryjeEWuNnDVUge73fi/if8rl+3qtdXghvbpRxHcNR+kt
szZMGV117URblVuH4zOkRU+JQYYTspdhrrUvfjbost6r/pxUV0S8Im4iufiZX182XVXuqqQW
qLqrPsukpjOpIEOKEltH2X3KK5q6DFSmRItTtD0b0KvA+WCAGUmRlFHnZofzXFiRBHUqX0GV
goaebQfiAyWX+lQtFGvk6qUDmoB4DUlj50OxwK8U3FYWhV+p+jeotwHboBUq8wKaUdwb8pqb
Z6DWnJ8WcivF/u0ohW+o+heo9wA3QNuBUIET0GYtv2MDQq/PD9zz9+dm6666CWrdi5XXtDH+
vCZpI7sQkrYqA5VVvUHhq0CVpdMIvYX4V/Y/r2j89AgarK4852YIXSW8igq8MHPkwsefCJxw
PZjDvXqwytayFRqXDV8vbbsqPbPx/Uv7vx2ZFnYFsWgzVCnWdz/X3Tn8BdRFV124nJfQqPI9
/5LyA8qjLVLfKvv1pe/x1qsJzsdRnyqVzZV3OZ4bk4TPlQ2RvVEeuDe+ROjlt0ed2znE8Siy
o7jBylnYLZIze1Jk93rqiwFKdlXcnifmGjDXlz8ug62WacOLBXFKHaz6KiVdLOh/YqdV7TdR
pliL/puIuusUuRyWIaVyV8np1WqgcrHKORv2qvlAcfFqSe/dveFdFK9ouFKc/mIQ66kk1bsP
Q+MS41XnOW90/kqg1HgVra+gxSsrUstpVC1BlWNVu1KMHkX9SPEq8k5x2gteFzPsQ1jfChOg
mu6DI0mtWRM4pOxDf+x21BJNjtWur86ZDeyDC+Proqf6WzTDfV1VMMdBNpcm5RSqW8v2Owvj
7rioyKQOzB4HMKNrnPy0IXgVIHKiHKbi28NSxiOMNUjRvv7jnY0n6nEp438a64kKK+ORxEo8
VcaqBzjpdb9zYFIZ59BMpRozcHH01jGdlFliWVV5Z/PHqVKT6XsAq4qnyEkbk96T3nBVJXME
E7ZYZ/GV4q1iiVFV8V1St+b29p70hqjam3iZrFjdDI106g35xeKHHzGOq2bDRfnce6WGybJe
jc4l4/9E8/41m7rtXzPU13+tk8UvjoVy0gAAAABJRU5ErkJggg==</binary>
 <binary id="img_57.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAATwAAACTAQMAAAAp9jfKAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAADJElEQVR4nO3YP2/TQBQA8IQMXUpdsRQE1Y2MZWK1CmLo
gPoJGBAgmMrAwILqbIxBnasGiYGRDkgVk8uExFKJL4AhlYIyJLZSYROud9x7ruJ/57wHAoRU
3nKJ89O9e3c5n5OG5kXY+D3QYUJ1nwu/Xultuxwoz62e9Vlwae1GlwOPllaXWfDuh7V7nNR6
YfHtvMuBeqyPNQvGXDiNvweTRikWa+D3wjtT0D4L6lMDn3PhC2iUQ8M30EwYMMDGpWGITYeG
GGqOmTqLeohbgFN1gi0DTrA94EJG1WkxezTEYE8Pp2r2PEbc1FIJ03ZDEn6SMIf+IT3G4xEU
M/RIKN/BGGmoVQteBILsERpO1QOcbD+g4ZMRwCEJA8msOlIHPKhxZTQ9xrTqFU3CgQK4N6Kh
dADSqb9I6NGhe5QAtaDhUfHCrAmHMc4FXgYfW+EAU/u51PjMMq7Az1W47umNOzWpndz0qEtX
rz94VIGmYgPzS6iWX19YqKYOVLlHfWv3jAX2qvDm1sWNKoy0a1p3nEF1e+uaBSa6a9pOkuvR
FtYx2mEpZn1xhWl7EQnlZrrWUxg0hGqbxxBRggp3YZxtrjSaDacEA1WC7slHoxJM4N5jUgsK
KrzSG5E9BngjXaFTB3ADUJezquvgCNZa7X0jYYRL+JLuUcLg8gdSHQx3SqeCXwP723DFoavW
uIQPOdMDxcSc6REG9iMG9KDqMQkxtRaV1HF1rV+Zdv09WYwCqPoDEh5yqx56glf10OtB1cql
YCDxZM3uuHUwwiWco7dCCt0M+nWpccK7dOqhNsXoDl0Mnuj5R4U6mM60T8P0kYuxZ1LYZ6ZW
MQ1xs6QPSRzYZKQWhQsztitEwqh6J+dnwjYX+n8GsovJgiiGA/U/Bh0bfGqaxMugf7KFKhC+
iqFbhC0LxIuhyOBuU8uWObGL57Xl74d2+mJe6GKoZgy/FLzNySa8xT80sm2ej+IY038+HBss
Vp3Crg1ifv2LsFMHZSm1dYw/BRO3CK3TY4H2ecSPRRG2mFByof5YB8vz+B+eZtixw/3yDWQa
JZi4cZy4z86b3wx1gwh/AM5amVlo0QWWAAAAAElFTkSuQmCC</binary>
 <binary id="img_58.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAZ8AAABtAQMAAABEAf5xAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAD10lEQVR4nO3XTWgUVxwA8DfxIyEK2YOIh4bEW8SDHlus
mW285CJGkIIHqRKKx64V4qYb88YPyCWanlIKMaNggx60XtqL3a5ioYcW10PB0lA22xy2h4TZ
MOjMMm/fv+/NJjPzZmd2900PIuTPsOxm3i/v8//mDQL5oOj9QZUkyCoBuSiJqEXhuCOJ6le+
PdEvixxr+WTeKciiP8/lE9T0d0EW1a4sn5NGFAi+JdsnHsRMgGC1vXlHC9ZGqM+/8VEZFgks
ZvFcFhencRzio72CNm/TyXUYtuHny3gxh/WpWNSIwuYNZQ1SBFIYa9NYi0eOgM7+DvcJ/DiN
56fw/FetkBVAa/CYwNIUzGP19ZfRiFzWHTd3PbQOSwxNwnxOfZ1RI1G9XBTRJ2W4S+BJBn+d
xa9iRq++bjD0q4e2NgxbKB6JvvFRiwihG0mQ6fcpo6Du7qf8FxtStkcZbLDYj++PNCF/yKt7
uv5BGoqJLVQ2hMm13GrZn2ruZV+/N5pCOx+Nfpf+oeeVN0+4JSKHxw4OpXo/fZTe1fvHkL8i
9DBygmifkj7TldLGtBGtF4mpwXKjgVSLDpi0z4G+GpTYRe5ntI/RoTRaUVDmw9CC/Rep7HN2
fMCiRYcaDv2iBjq7bLQfZdEgUtLsP6ciV/mh364yZNKKi0oc7dyLnvFx05CioUg0+RxzRBi6
WIMic/ZQWuthpmdEQzdDqN5A06eaEEJp1MU/GPSaR8bnGkPFEf7sSKB5DBUJb9jursbUDnqT
a/J52mig7LgeGj1WU4+3Ho56yDH85m1NLtvZdXeeOPJCQQKy78QuI96hFG8Z75SI+uNR5IIN
Na/K750/fQ0eOnmGnkQjU0Q1HVY/iMuMuNRoFf4WhufkEYR22E4RH6EdSWoibmqAO+TBEJ7f
sc2zNoLFih2i58FiL6IRvaSLa08JFnuWi0Tk86KfGizeirN5LBI1NssNv6ZrHdRUr4dSo9xB
n5pWuR0sFjN6daspNYIRg4ymzO0AlRMgAuIqb6A5/lyiahyiuOkBwGLYRbE1QXgZsXsH/jrA
EWmFeGoogdSgj1cnoGBUiSr2r7km5JegDyoTdMCoUr0dCibhUmUCsFGFkhR6MeE+1aVqerCc
5YcYqoIY/3tj2UbbaBtto/cY8QdAd5KaqjuSNM9WE6AEzat3iDZ+wbODsxdmzMBrQ1tk5Qt5
Z/hqwRTPRu1QiaHJQuhc3hYt5BZzuhy6VVpzXpoLOfFs1LamN85bS7Z5BYYqkugndfbY7Qsz
Uq8N3lFa7rWBbn6XQiub390XK9whEiJwNuocNR1PotB/JxBkI6v3vy4AAAAASUVORK5C
YII=</binary>
 <binary id="img_59.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAbgAAACSAQMAAAANRE0UAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAEx0lEQVR4nO2Yz4scRRTHVxNsb3PzFlphL0JCxJNEoRCE
HPdPWMTDKiSE4MFDDrWrmL3IBhWVjYa+CJ68eDCGsMwsexhDkBGCyGK0R1aykR12Jm5wurer
q3yvpqenf6deoZJACqZrpms+8771rVc/euaUXZmra5DMjot9Oy4Sb6+dt+Gi5+a/seGCE0ef
4XQu/GUw/60FF2+fmP+0FmsYh1deX3vBglNqqMb/K9dYHgKuP1csrhG3W7rjG3HDx1wd59px
0rHjQgZVgEEDRuGiHlT3EblL4g5R598cLgOSTl12MdQ+KZ4ugaum/esZckU/TTn9RUGPp1ah
kjPON+WuYo2cpMX7zI7TvshFvHgknVgEchbjELh2XKYY+9Kyi6e6I6j5Fvii3jPmYP1s/cGB
W0fuOiGedIYc8mwDdV4jcXeQW0dunaJT9V24LPPkYsydDFM/hxSdV238RG6fT/2k+YJ+Mu3n
hjGH46f9vEL3E/untqYXYw7zOllf2gROfgXux4ueUodtEvdzHw52iyvIrVC40330xUOdHoFT
C6BTYNcER53lnbSGKwQw5aSjda5gkq7Ucj5U4zy3it/kHuYZ6syGx51NMOBiT+XPSCnXruL+
hBcgwH3H1348XaeTFefD3rvfn3GQEy+dvbid1wnzFrnNKZft387lG8d91CkuvPnksQI3WT/v
wYvtFbjfLz/97G8cuZdffarAZXUW/bz70fNf3kY/489fe/HXQjz3AIccuiV037Lc4NRbx29r
P2+duVjkeiFKPMB9bKfgy/4pcVZzpZLq3Gwe9zKn561wt6r8rOdwX4mg7uKHS7uEeKAT4rW0
zqZ4ndyZHSKMYT2LfViXIu9ajhvlzvlHO1x5Wa4LXDjhNnLc1PfBHE/e+XmdLkiEXwvc5UpO
pP3LcmqM+8oqcKpF4lCn6oJOeSm/zjdzEz9n88GU01MhXIA3h16XEs+FPAsXcP308nn2gHir
EnVGOO63KDo37yO3jSvQxxSdx2Afk+wKTsJ8vsw4XuLGSvLZOaQ6X6o4XF/uYH6uY559WMOV
dUI8B+Zf6MP8i/wuRaeKkj9gAleR+vdXsk4IdpOkcwd+rv0TBG1/TeDUeA/i+Z9AJ706rkqn
Ogd5Fvc20Ze6PCtzMO5L4GfcQz+9LUI8OVYMfJE47oLCLfUZ5FkHP5nrhDZnF8fhg+l515jz
hun5rDo/ZTXXAj8lfx/aOUWnXMLnKdbH/WhE4SZ+Clw/SX6+gzVP7xj7gs85+tFPMBLnqeT8
GbrkeDGuRpFP4eTSlAs9EofN4ghcAofEvaFyxZh7AqqRq5LnJPM8U3ac7tV4RvzXXJh+xYKz
6J8tFxSe+005YckVy8PCuVWcNvWggsOVSue1bBU4faOPv9iv4LBBc9q+LKdvaK7Dy1xnygWO
P8qcW8eTKdthauAuXxjCnj07v4xK/1GzhrZW2hZ9cSS5h52c3Z+Uos580Q3IySInU65fxibP
9np/Z8Um7GyAb+5VcHE9lzZXcWEzVxsv5fQ/j+ZcYMA1+lLyc9LM6ziecKXxmzU3jl9QycFR
5EGcU25MuJus3NBhqmkcfmjDJapouNGecu0qTo97FTfLl0eFY49IvMfcv8uVFmgseGZSQV2D
LfcPqXhEIutyruwAAAAASUVORK5CYII=</binary>
 <binary id="img_60.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAaMAAABwAQMAAACJho2CAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAADj0lEQVR4nO3XvW/TQBQA8ICQgAHSkQXcf6ASFUNVUTiQ
WBkYmQAxMDGgDgiV6opAhAHRgQUE1BOMCAkJBgouH2qogHqlAnItASIEUT9S6pj7ePgjjePL
2bHTghjydIrsRD+989179TUDbcRsZs3KaktVCIwPplYU4I6dVrG9t09s42kVpzN3n3KSUlH6
vudp6lyUzpTTK04/taEY/tjzLvVzefJMOwoqCdB61OE6qDEOJcClZ6gIyClQ28ypVTXjBl5V
VWzux/PjWmlMs4heMbNqRd0PptXudlTw2wH8ah+Z1MhnTa9kUYwCUrvrsvBrhKeQ/kXTv2na
UjYuV13ds/AbhH8gVNb15cKxJfNUpKKSQm+e4bJuLheyS/MllaIm9fa4rpbcXFMDuDxmLpOs
OpfozTuqGKgHVfe5fg5oP3dpKxpRP5ewLEe9DBTEVH+gVlz1oUHFhKQutKWeN6gqMLZx69Hi
zYnNTslsygRx6HhYBSvPvN+PZ5TRXVe/rNAuc28If2AABHBJoRZLsmLeqClnjCgUyilyMY/U
cikU1DoF+crikGNwkUFeYCqwDYjHqAMZzbmau3qSw2MODxlcF7go8CIgG0a6MpmRJhXM8Ou5
x3dgD4O9DJ4I8NViRK5AlW70cVf1u6rFDAM1P2UlVNgMamP+u6P6nBkKuNWotjSt/KC7X2ZN
Xe395K2hcEZcLkHDncLBdJZ9deW5p7oj1PPI2nB2+UpULltSol4bERXlqRlFrriKkmfI1D2i
VMW0vSx1SkKFRttQ4C1gG8p/E2n+jeKvoaFU3gyFvqpw2LA4Vd8vJiuURC1ISmQVSvS7nSIC
NYKdq8sYdIz8XBtUasXdL9ag5IJQKuHWRjVQE/IMN0eqhhlWZaWpFJeUkBTDKuVWL9ve2Cly
qJQl5fqbaqHpTeTFPgDdaYgohfIqJbirWFQukCvK/WB7xnvdb3iM8jpFIw1PwnZeOw2YzPmV
rFZ2Y3/5CY9Mlxkic9yAUESfvjx1eLosMJlNq+6XvedKpw4+WgAYdToyjfLDBLkmk6jm6KiO
6qiO6qhm5b2J1nbSS6z+19WovkCT3ZO7c5V86MzWQtECMWzjvEHDZ7ZW6iMxqDFsQPhs00q9
ItOUDI+mVAWyYpeGdBQ+s7VWv+3KoDHk5iokVsLJZZ31VyP5DAWa7M/tzi2kUtXVy/y/qKjZ
2vU6/E+UWPm0LZUoZv8AH80377FmboAAAAAASUVORK5CYII=</binary>
 <binary id="img_61.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAaAAAABtAQMAAADd+SfEAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAADn0lEQVR4nO3XTWgTQRQA4IkWzEUa0EtBaCoePHgoCBZR
2aUnEUTx4knQkxdRqwd/WjuLta16sMVDb7Y99awIjSA0gmJuBumhFrGpP1gopZMlkOmyu/Oc
3SS6szsbd4KKhzyGNln2YyYz783sImghUODzTCuohJk5oYyAUaKIzJWBe32qiJQuz1OCVdH5
eeXhkdKVVtBFdWSWBqp9qr/Jh8rrlDj+OMrX/rmJkXP9NRyzYE8Zprfwo2FcHNKkaBV5geuo
sgyHTThaxQsUTw/h6TvynvwlYg00ssx6TMgynGHYGML3b+J4BA30fBlOunDAgTkHT93Gi7fk
wxOQy9E5Fy5ZMAd46hpewvk4VBHQSQsu2TBH8dRVvDgoQdY7jaOvv4Y38houuJAt46dbeHIY
f5ANjy7lBQTFNaC86zK8cMHiPTOQoFxeGF7TEFD5eguoEt+TUGdxw6ulR1zEIP6F+s32G58K
R4IKeWFxQ4hnuqNHUIX8FmUNxN0D1KH/XFymBRADwjTKG3TbQGyYcIE4HehMD9qnp7Z1yEqD
db3RiDNDGW+nbCjakHOh4JyYfIiy/T26gWSlwc5ODRK3RJ0SZV43NvR5KDOrowzSU/o9WZYz
+zEEUNHy0JqD9DRCaQPti/TkI2csiHg76KFs+pX/AzJpEXXWEFvwEeOt6KOch3Yjw0C9wSk3
/dLQamir0B2YiDpy0/osv39UH/3ZE/XXSfu1TtEpf38/g5CxLZXuEFFnk8V1j2f2IqQb/YE0
8tGaFFm1jED7vSUSci+E1nxRaaCYhPXRFcXS8FG3YuWGSiMRMoUsT4ggtMMmRcKpEY3AMRVA
fKfk81pHxAoZKkdiuX8OITMJGgmhcuBUi0MbqYXVnXjXTO2bnd1e3hlFlSVN2Pc2IlkgQdQW
Tw2yPTy8YxJEQzvs1zCSTUQYJZryonjURJ/GZKgUOQASoHwLyB6PHABeONTbc60YZIVKo45s
6l2hccMLp5H3Z3Xi2bjNr5Am6EsV4cazkR/r34/kONoRTl6hnlKnxXpaLwzeKAMe/xaPopW7
Pj94wxvephI69DLnXdlQQqMfx4FpYKqguGijNmqjNmojNbTR/ClMjj7z+w1V9F9OxNsM673b
9VZ7Jz6FNUcrVUY33SqQ0Lt7c0SHOWKKqA8PkDub2Ed24p5wjjxhlofKR5KjJfJpBdR6qkIL
yGS9m11j3UovKIX6h3/xgtJ41v8bbzVyVElwey1+AM5GYhfGG8c5AAAAAElFTkSuQmCC
</binary>
 <binary id="img_62.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAX0AAADrAQMAAAC//9T4AAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAIkklEQVR4nO3af0wb1x0A8HNwsCkmdhMpJSDFtubSTSKa
SWgirZAbIDWhDU22/kM0JYSSkqCGJA3SlpSEczEzzaQORqooLVkALUr/DKUMaMiCwW2gIosR
aNQsa840P9xuBsyP4aOc7+2dbTj/vHtP6qqqyvsDzoc/+r737n3fe3cHAfAKQ/yAwQQuOI8L
bIl3yhNwwCChNGAB21alzo4D+nHB0NbdBizg+K27fAYHxCz1uMCMC17ABaTK6FLjgJ0qTYEC
B/xibZPsORxAJJlkO3CAWbleJEKs63zJmBe/DbHAEnhAYgKAB2ChcEF4+T6BHQ98uWlQi5A+
AvhH5TSNFcF2blodMwIdD1DTYBIHDJ/7N3DGmsfiAQBcWiehAFElPoAdSxBqEFlEASAICv7m
Qpk44PyV4uzIAH6EIVgMAEywp9ghCg3wF44ltMB37wQ6AFYCLDkq+5CrBNud4NmQ9zx6BHhm
W23esxgAtvtfB+Ru5Crx7S7xHf4pKQVIIUWtCaD0HUnQJwCWIFmlFHhw00WuXmAY4hspMHzj
oJCiLKFmpQDd/YpdSFGnf9RKgPKQWYMTEiMu6H0LPBBOewgp8KDHpnWH5LRJLQH4MsMoos+L
AuDRRoUQB8C5+mGlFRIACPkfnHOkAOM/MvtDkCggEKKDAoH0bpEe3h7+u7f2gsAM0oCQQHx1
LK/wR7CjBhEAqwUg40X/kQz0o6RoP9zEjPuPTForoFkqCqjDAacFOxv9R06ZlaIZMhJwJ8IB
sAHuUoAShJZejgLAnXU7vQZm9QpgySCAeaGml/WdYyOc8hBRWje+2eYH715t+s3RzAwhH8zA
F6wdkUDPZ3aOjde8vOV9Vfb4wQt+8NBYO1xh0AmLolBtk4yeN9ZpiJqyhl4Vock3+9vw+ntp
/zmqk4dk3Oru00SUeA2do4lcWUqzKrupuNkfQaVpeqkws5EWAKNdBVqnsnNEc/+o/n29YZwI
VImbvZ2qOtYVuhPoD/6GbXDquh0O2l3VU/b7L24PBi8cF9atgWsRABTt6gP+MbssdGsMEFzk
4fK4WtEwAKKA2ETWFwusdlQ4YF2rgIwAK9ciDDCaQwzBpfCH3r4IsBIiDHBHMk4TvrXX2rqa
bT2D2vD9EkMFwCyTnlpq4VKtmgEH53p8mljqNJzv3ED3HqTDIwRD0AvzaUmGJm5gz8W0gRqX
E0bo0p1vN8PJOHKDFWgF7fGuSTZ17fQWXdqcAAFDsInl5zuS6e5TUZtEWxDkJ9d1XfEWNV5J
2Ol6DjZ6XXnr+uThG7+OrBLwJye9eOqtQoPxSn6RJc1WMwcBuAmnH9rV44reVTb4IzDLypOE
NnWiccBRw5wIgEDfRYHJYLcug2qhZWI7Y34IBgCFBmAImo44FW/wBQqrHcQDwGbDBIwMEwBT
CSawanEj4IIGGhOgrA9PwBMQCWLeiz4BEmAJ9nShOOCn65nFXDjlZXW/0M4UlF4vEwXsupN1
xlpL4z/35F7tkG1fLnz9Q1Ew43Nvv2NpXtdUXJhrWZOUWl0q1/1EFLCPLt4hNiWO/FKZe3GN
cn110rMWvXiVJsfdFse+C4X6m6Mv7d54Mul50xZx8Mh0TDNQrCwy5IwMP7XvpK5bolu5Bd3x
XNo3BY+9ILA7kADeGQp+gx/m8ysnW8RAxN8m/Vs4ETDjAqCVCCsK8YyrZ+B34KK5J104KQoS
ZcIHTzUtCdyhDwk9hdLga/9t0nLOtobenqe9Q9LgbgDktiUYm41zQyWSwO7f7zC5Dz+Rm3M8
FXulQeDM35yfEAnGqm/UiIC9aenZmN5VPdWACHy3jDf23WuvfmRHBBHlxw5irw8e8rsDsavk
7Q3cwPj4m3MKIYK3tUm1u6DC6NlqvSzXKBDAtQsVrROVhsWt717eYdmLAmrfaO9X6ry1KSY5
gQT+8Ebr/qPXFvOL2rMsdpQ2XKr4WV5Fq2euyCTX2BF6afJbljoCZn2wr/54BuVKu8I+/xDG
0v8fMAWV199ctH/msF3OBCht8OyvvK57WG8ayVPFBexTVQJYfFOlJ5yunsbita1xwfK2EJCn
1Mm+ct3QFKfEBdxyegjYX3HV8FX3hdFCfUZcsOllATjzKtv0i/WDDhuRGB+UCQCuk8Gj+Za4
4EyWACKOYoNzsQAQA8JJJBBagoBd/zs0wLXwP+cB++2OGMCfKiP8s9YRfgEmNHDZ1i6ezk/U
Z+fEBINwq0AQpCm4DSD5mzh6oSr1NeX2s2nPxACKqPtCfvBVvf0akXh2blc0uBO1EwmA9EMp
28/OHYkGQzHeuQB67nRBqR4HRHwOawPeeyAI5kF0EQUxvv+jB9/VVBnxJEwSLE1nhRJpsOj+
64dYwDv1RTcmmAgdlCjgz31YwDOdFfr/CN/zhVOD6CIGsDPubhWILmJgGhuc2oQJPI5deGDh
XhmILGLg7oKtXIsX4WK5AwfYOaJcQ2GAGeA8/nQBBrCBmWPWwogQYgDex9U8cyAZo0pONWAO
mLXogH+U+bliDwYAMlD7Yuj7b0nAkFdGP/85BgBmwDgycQBDMsmH+zAAqPcN5WVQaCDwxo90
F8m1GAAkAJ/8PkaVYCt82ZuFENIAvA3+e9+KA1iS3TxWggFAPcgZ248OZoCHYsd+pUYGbv6t
5EieAQEEuvXrWeABniG5HTXC5GM7UHgnDr9DIYLPXPX8C16lGhVMuTaS8HIPKc9uQwPg4Ro7
HFHcq+daEcFs9m0KziCjZzr70ACo+9QGL7evSteBCD61fnACpp7HUGtDXBRHa50UQ3p1HRta
0MDcB6pdrn7Ql/2n99AAN/B3s/OWGiyMfoS4Tj/+ct7e7YFDKgt1YZ8w2z8mYUPaUIHv4/6/
kEABppC3Dma6jQT8QxNUAOgtFJ/ednSQCQGnwHhbdhwC4JGhg2ke4L/LEgWxdmT1YlWKBcgo
8D8t19bpmWBuMAAAAABJRU5ErkJggg==</binary>
 <binary id="img_63.png" content-type="image/png">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
3EaIYA09SsgAAAAASUVORK5CYII=</binary>
 <binary id="img_64.png" content-type="image/png">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
D1siSlpBWXQAAAAAAElFTkSuQmCC</binary>
 <binary id="img_65.png" content-type="image/png">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
gg==</binary>
 <binary id="img_66.png" content-type="image/png">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
C7cYeESdxQ4vAAAAAElFTkSuQmCC</binary>
 <binary id="img_67.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAegAAAAkAQMAAAB4wzs0AAAACXBIWXMAAA7EAAAOxgGnHWyZ
AAAABlBMVEUAAAD///+l2Z/dAAAEYUlEQVR4nKWVQWwbRRSG30w2zWRbtZPgVlYkyKybEpOC
lBw5gMdLgqKoSJuQVFVUURMuFXBAhCIOgZ1FTggSCB8K4sZGakR6RbTiVicVVcWpByqhXkqv
kdpaVURDG3t4s17HayW+lLmsPd5v/jf//G8M+v8MiB/7D/oiQJbOLzdniKo//QRda0O3Hy20
u+iG4z0Aa9PC+nbuHu1iIKZCmhLgALCLjBtCuWCD8IgkLXTtC0FDe8VeZNP4jhe4cJyDDCgM
jUJeUSVTyEqYuMDGQum4Rfy2kKBfMMvB8AdSSAEkKu3WvY1VWhpAbbJcMtoEVOEQ61VTFiVA
F3LNyqkhSHauYiQ6AoAOGc4Ec6Ass9DKMosWPFUIYDLe98KRXVqBwAkG76pRfDyHdC+H/N9u
QC1BcS+QjggGJ4ECzgFkoKNZOYCQgNUpxrEqLpgCi1QCZoEVnxCUcEsMrYhWQe0EfVi4Cn8o
Cis7vXaM9L7iosDK27iGgNAYIUDxbvd720IVD51PVl57XYK0JIp/mHJ4Fx+wOXAmV2kq44WA
FlPL+HZpojiEsgEWAQt+k9aCOlF9+Bbj3RJF0cWPA/vAGhfRefAQAhpMGFdxMYDPk0l1I3ak
bk5K0Gi3iqkMEBo7FkLdArpvUlFhGHgjiegrZ8loqhjG90REXzP0Uz82PV2EWAUONBGzmwbV
GKZ4f6ff0E8i8SnBA9qZ7Ul7YC3zgamel2eyXv18YMDwItbE4hg+Lb96c2nkCGxvlH7NaUde
di+xADwnteZ9PbVKF4c8IU+gi2PhqLLNdvmyqT5rduV2gl991FcchO07pR/a9nf74VcrszAI
T+7wpWeir8+i9tPZ01d1zdf/XBm2Mrezf9pev67OXtncCqdPnDn6M1oSuIE9yazspNa/b3qh
VDSgzN/58QbuW/f/e1VrXz+er7BPNmc28yvn9E7VTp3nA6erBx9r/Zp0No6ymezZy1r/sYW2
eRaMWH4td1fn4rSgNnl/rHCLlLsV0kTdeA/yn75zeFBriTGxb5/k2R6ty9iu8pt0Spq03NX+
Lr1F6EP5F3/ju4v9egfuP9Td9/XzrAstmXdUKnfhpTME6YNRIDqBJLP2E2qbDNhwvJhF7VfT
kOFOX9rQh4xJrLDEHK2vk3p2Auk375ZaDum4TSCF2n1RYgnt/0rrU1EGqdWZ13q9bjiB9USP
VZCu5LHzRAlgCOnf3HGQGAzbaPNfgJu8BKhdxEnTQrLQpMtoQmUlDvIxpPnGOIxjzwO6Nur0
RgEluO91ZjUOnLTSjTbIGNeg3l+Ra4n3ywB76W2s/BE0+jemKZAvuaFVghZWvPBHfotrW6Qu
ziykvehjB6i6tox+QPrmrnTyPscTq5irF5dNn0faxNGCNxW0rTxxYiYtFYguoRTzjHZgE+Bd
YDUqV3to3UrL6B1UR9qR5gPO7NFmUL+9WmhoN2jycmv8ee+l39osAH7xH5TLn13TI6YMUlaw
/yDndJJ+xvEf6I8DIfS3hyEAAAAASUVORK5CYII=</binary>
 <binary id="img_68.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAPUAAADlAQMAAACxqSO9AAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAFyElEQVR4nO2Zz2skRRTHaxhwPO0ET17W3j/AQ247B6HP
QoL/QAY2qIcFAzl5Emfm5G3xxyEHo/M/7EEFQXvxEDzIBjxM0Mx2h0FykJDaDXEa7PTz1a+u
7qpXFRA8uXXJj0919atX3+97NQmD6CiY+e7Hl/xf8jzKV3CEX1dBfglP8OvPQX4BWQqwDPKb
jCXIr+chDhl+XXI2CfGfQK5PThD7e6rjm6Y0F0Pub+qvYPMnhj/Bya83wc2/O8E7H3YLh+kt
vO7FOdT9OIeyH+dQDeIceBLnUhIx3uQpqN/+LbwaxjkUSZxL2UX907+FYwjnSn/VJumfYrIq
2Br1e/MF7Y9ZUbA/IZvAZzSv2D3ln80lzAmO4bEnl1lafzy+JsQtudhoBWP4gfLPTPj7CKq/
3sb3Mw9nQ1Efcqg25P7cCZjibn460oRy4Oavbq9QEfltTah64HP1kBxToHgzQSvQP79yoEMP
cCnucghBjs9aD5L6yOwuSM7jHHXVj/F+y6EEz8wmaM5TBVKaV2bnjObNzlUIhaOJzH7Lh5Kf
oz/qkfaHLRwqhEvx/BPU30P5m3oA7TGDleDZ5GY91r/ojGqA/sFsJ9X9Mcxz9FuXY6IfMCmF
gzFnSZmAO9AfaxHzYgynrO9h5Z8juPlmG05Yz+fZvuyvFe7vOKm8CeXA5idLWuI18bekcDo0
2jRDqMzy/BKc/AlDt7gK2E7AstKRmt5Rar5J1Aa9LU1MbAD++qAVUHn1ueGy/5guRnCxdBMF
sT7+cqPZBfU8lsVJM5XifVupKJ61hEasLw3Ch6HnTeNJA9yc8ZTmTXtXIXDUH/aPZG3OL23C
lCFw6Y96Y1fxpi7JuRM459If8O1S8rorMAYrLv0B+4o7/qkHBVf++HAH/WGO3g7ONmX/qGEH
pkmZgDu0P+p1zD851C8ewkmP6FDSP2JfV8IfnhbLgc1fNnTLg5BiK7+ef8Qlw8k/b+dPmMnz
T9pg6QDv/GyBkz4In79Kps/1JrSTCX2WgpRB/anWZkoNqf+ZPUmS1yyFGK9eHcQ4qizqX5Ei
EwDBVQOahbj2VxmoP43E1TIetzXYqX+KNxdTNVX656nlreOXNQ79c4b3t8PRSvKO/cSP6J8L
KODuwR7kuWs/XO7eJnKWLBfbkM+dq70Mh7GLTPDxi0eJ176kf8T6+PxzJj5Gec9zdoXxifWP
2dDD1UDt7/TxnrjLpS7v6/zUH63gZOjFV3T8c+Tuz/dP1fafbBGu/1ohyLe552NDUNF652tC
0EfhcXNEWge+vlQIxsaEPsXH3aaNEhwN3FySSP3zxOaJ9Je1H/182otxUcMi/pRnUAbj12do
fMCZIZqb+VPLj1q8qeGmjMn70+HoXc8/qtRKnt79/ULyTvkVOb5EjvrPP70Q/nHshTGK/oUy
zB9vrz9J3OsTBnOm+uOzydbV9A2//7BiX+kf9vD+lni8ZuL+h3ev19AfiW9P1b9w3ncr9I/X
fpBvNvl9NoTSe0O/m3+3wU7zLncMhtl0zq9qZ0j84J4vt2+QH1K887dXANJ/9g0B/xkL84D/
9LrmrCh9izTG+leZ2DJA+qd4kMZ51D8YehXrb3zQumcH/NUIXfHccvPHFdbmX492zfk0b+61
+OF6W9dHKzGVQcVP7+8If7Sx6pDnmh/swHzexXL2cx3fQtzvCrdB8CFXfLnYwfub3z8KpvR9
+vkeHPvY+mMk/BHmcg2Kzzr9xRv8HfJ8zcB6EeX9gD70EG6McKmBMDf39xBvny/BdSkIcdOA
ArwppDS3lZK+/8X9UbXu4VT/a/8hifBH5xbv95/uJV/r93XdP6B0PgMoPv/yTH/+cT9ia31/
L/uHjw3f34I8Ae53CM0nW+gPSsGKY38h+0fDr/Zo/Rte7ka5nHMLz89v4RR+yf8X/NHoKsrf
++39KL9efBDlfyzuRPkvizuB/98o/tXizejzb/0af/8rTf+kOcDf/1V+/wFxAryf/0n8QgAA
AABJRU5ErkJggg==</binary>
 <binary id="img_69.png" content-type="image/png">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
rkJggg==</binary>
 <binary id="img_70.png" content-type="image/png">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</binary>
 <binary id="img_71.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAXIAAAAxAQMAAAD9UbA8AAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAC1ElEQVR4nJXV70sTYRwA8GfJNEOaFEQRYYHgS7eS0iKm
LwIlwj8himi9KX+l5vS8Nc16kT+wH4MiKiJ6JchGW0iimGxvglUvkljSBHVErlNG+8F29+3u
dj+e23Pz9MvGfbn73LPv82PPg0CIHOww5pF4YSG7K59hCnxKTdM6Pv3NGtL4pJom9NqfifzC
7gati7Z8lrONvh4/QfjszNEw5me/+6UfyE1RgYFx0nuzWMXw+ZD/bz5jV6jebrKe7ILGryX9
65JnBnu7OcJDtj4ESAoTJJNyPZLfRErkx26J/9qlNlgYOR2S+stujaVGAWJy+7zP/JNyWvFE
4P7IPYuQcfbinsF9ublMTC3F/Qruz16pEVNTcR91YH7yhgNcQs9ln0NElKmeW2huA6Do/YqX
2rcDTKOKNeQq2TyO+4P72sS0ZJt62rB65KiSfMagv1JwNCWsq8REO0V4JjYkXPpLy7D2gZoT
vOfMecID4xP9VQfuB+pbhr90eQJ6/lxFR7DK2ZnS+P6Wuh8JzzRZDzD0terJsNOpredUEzqW
8Ph01g8zaHa5GafTovG+C3Uv+Pr1fD3lOsB0dgrj0ydvBtSjS9XLaauuf25+5W49uUeYL9Hb
BR+ix5e59hRL7l6xTw9tQfv9Ut7PN5W/gbTgaekhu0V6ftLFfyVCE5Mt7yBK4/7udn7R+7uG
WJ8m/nPxg3BzL0LtlYsW+UWEVr1/agu8qRKhBy5kcinrGfPV7uZWw3oYOWlEwyOHHZAz6K/i
5fnSeHI8CQ9QsJ/MGXt1f8NeBnFvj8rPGjEPWRmtN/gbVu35fKxvuEsl87hX4vFT/+0nl/M5
9bYnbuR/vgzcmpX87JSO57T+Y21Y8Rs3ezYIX3A+sinVx+PXyfYz2lONs4Y7ZF9RO0KeX5mh
r9rzsVjI59F7zflo7OciYQOp9YFIykBqffDZrjwk7+yqv7Azrfqd9Zb3/wEJSsUEWF0hEwAA
AABJRU5ErkJggg==</binary>
 <binary id="img_72.png" content-type="image/png">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
igAAAABJRU5ErkJggg==</binary>
 <binary id="img_73.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAWUAAACKAQMAAABILskbAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAADsklEQVR4nO3ZXUgUQRwA8DE1j7SuEvp8OKGziMAuKit6
GEuIk4SCXsq6J02UhBBfoq/VCiUoBAX1Ml0fymfrISQ5LrkeivNQ6KFQw5NTzofsDE33uHOn
/fR2Z/ZsTjpKc+D2nNnf/u/Pzu5/ZxEgquZnxC0HDHal7PsCgH6HXxgoTjOBWiA2VrOHvwUy
hC2j1SHhc1Q4xi31xrRa/iI0QjP/sWYT0byZ1N8VHSZ1EIXcmP6m6GZCR/mtHhOj15OKbt3c
88yi19GWkiK3Xo97ivMaBN1QaW2HmN55NhPTgZGi/WOirsh5qNf8z88llZieHDbljAlXVWtF
dzOms7YWmqBeTwwffCHGbqrowjJBbDXi8DO4pyxP1Nz0qz5Gr9ECihCzw/PxZn4BGc5lMu+G
1XenydVHzZvVal4oMwDodBTITa4+UKuF/n07UcSCs2gK4L+pTUbXQkodXNfreg3pmU3w95oH
Sy2VQsc6kSTo/oR0rfQnS6k7Q9EQQx2788GHcsftC5T6+dvH1iJq3fnWlHNmk5lWN53vulJ6
mFbX2yscmTdpZ8ctd5KiUdL0uDpOpQcRTEB7qrJsXKH9rIVK+27ktszuvlo+RqW/HrLtqOmw
7aeL/bX9+HyNM3uCLnZ/m3V7pOhymZlKB55vOcIFqni6TBCEC1KHUjOJaLX9eZ1QZUNortSf
BkDKD3Xsr9fvVaYX/5lM1ojm4utF6bLWDfmloV1g0c2f2AChNjCwEOGl1TonHFAgXPL6FbVB
MupbBkR4W5FmkqgTyyRJepGzQVy7HqZKen6LBdPhj80soZ2nRc3Pn2tUH7uq9n4iteuUrOeD
eGzvO4ifQZcrVdEhQg8xZOwMWc9ZyEyIc9LhgsKbl6h1OwQXbcsmdH8dlGKXlyEsNn/yDqED
DW7pDG7MwmPHtjEtNnV23CvW0bh6KqHYtBolpAf/oA5g2i/VFyOtvI1jOYvFxGTwhh6nxa+D
63pZHYVBXHt4GE+HST0SP364ZxgU2AacjTHd/rSrPvXJ9AeDY8Ldn/IdLXbnAY2+vveNr2zA
a5BPpOtSr2PI7mzQ6PTT732Pip8Y6W7va0dLQZ0mdt/dvmO+Y2mZRjrf+/qa7eJLja6z9lk/
pueaDPTMtpHe0Wrkbo3plKcR1sOGyCUMQgsskpc2MX0PyTdTELdRZeWLjObST+QR22egLZiu
vb9LXc6aFR1b4AIzpqXBbYwSSNKjzFKhg5j2M9oe9j8BIm9uOR3Atf7pg2n9A4ho3C+dtnxk
ikNyJwAAAABJRU5ErkJggg==</binary>
 <binary id="img_74.png" content-type="image/png">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</binary>
 <binary id="img_75.png" content-type="image/png">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</binary>
 <binary id="img_76.png" content-type="image/png">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
996glexuOAAAAABJRU5ErkJggg==</binary>
 <binary id="img_77.png" content-type="image/png">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
SUVORK5CYII=</binary>
 <binary id="img_78.png" content-type="image/png">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
/wOcnxbKmCNhLwAAAABJRU5ErkJggg==</binary>
 <binary id="img_79.png" content-type="image/png">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</binary>
 <binary id="img_80.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAV8AAACyAQMAAADf1l4qAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAEc0lEQVR4nO3Yb2gbZRwH8IioA8W2r7RYTYc3X4UtXTs7
2ItDfC/awfpyKRNEVuzWTLuY0RbNEl8kja6wgebPhGx9o05ytZG9MDqw3aDJVSYrZVtuQ7q8
ye4W43bNktzPu1zS5pIn6e/YwirmeXHk4T78cnl+R+57jwEgCsghGSCpB9+hK+a5xjjt3l4x
X2uMs6+fq5jn6XpSrXzoQiV+riFePXQhb9gYT298fEqiq/HdHbs0lSfWPxfAW41vuzWYrsTJ
aqydr0ElFhtj0OCMHlzVIiIOwlU8jqrfj8Mdk++yv2ExdWqAQ1f+eXrgILry2en9NBqHmP00
+jJ+jB7E49IRiyU9uDiaiwWAV+X+SyJwaSMRS8aFJdc6tsk4L9/ZmQQR88AvC0WcnuqI9873
hI44aF9/YpyE7wA/WMRZke1zWn2nvdRsdLEOvlbG+YzQzYz5FwJUH9ycYcmY5juKWLLxFsa6
yvpMe+riS8CfUbHEJxnr/TMe6ieIReJE/HUZ5++l2l12pzlkdsiYvBoXgV9Sf6A8ZwulE+KN
Ok0RlENVBzNBdLt/hSy5g3BvglD5Sd51m2J+L6QXxBwOL3554n0mmcFh9/KJEWbhLxxOBcaH
XNjLSHVJw9NYzHdKwwy68ld6KpvfsThF+5Pu4AbW9WjT9dAU9OCgDlwbIRpVrhq1uGE4aVSZ
1YM3CSePsM6PPZy0cAvXw0FYxmNdwbV9colrTnA1nxowoit3Tw/ggyulJ7ieZQbxWGcW3aLB
Vcmi6csPSHgN6Etd1cGVSwaIuBd2c/uKODvVGd8XtxWD60jgIQn3wygnloKr+bTVN+ulQvQX
28k4Ake4o6XgSoXH/K6AqQ9inl+OE7D0BxxjR8vBNWz9fUEJrjEPvZeEX4Jj3INycA1b77s8
lIIDn5LwMnyiYiW4Ou0Oc6j7JMS+DfxNwIUkvMmNlpqShaxkl4pnyEunjGxtB306gitcJuPP
a7EytvLfV1Y6qkQNJC5QYMTitP/8VTTODe8EDosfPqMH237A49xQSAd+oec6GpdGC7fwY8LN
e0/5r7zUGBIlumel+lTrpWZTrCu49qoXi8OGyT/xwdUUPsChK5uZA/jgapobpDPolDunI4uK
6mo3J7iCDgyPirM0Irh2lrJopkvGgnw6r8E8PV/ecZVykl3ByQkQAwqmQVibqMTzkFrfcW13
9Dp6vrM46Jf7A+OGjNsuJXMafA1S6iZqQRTemBP954NUX3QlkpDxh29texu0mObfU7c604Il
LHq8PtPHsDITl/HhXbaRF6srlzZR87wQHlv1+sv41uGd9o/aNPizMs7/k/rAaT038z21A1aU
TdQrMh7RXsbFMs6mUx1sbqpn1nwSbkZuSIbb29qeH9L+wCiw6o6rpimiXzLIt9g3Ge06VzRl
fdyCOtvgJKyMrbzjytNGPF58rY11YbE7IXgZLI4lBBb9v6HgJBbflTGLxakEywbRS7f7lRh6
NZQR+V/kDcSzmyvj5m2ibo4nDZZnVWuvwf8CM/LHbvohFmMAAAAASUVORK5CYII=</binary>
 <binary id="img_81.png" content-type="image/png">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</binary>
 <binary id="img_82.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAYYAAAA1AQMAAAB/Z97JAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADQklEQVR4nKXV32vTQBwA8DIHExzkzQVE+yeoIDjYpFMU
3IvCcO5hYBlFZMpWxtA+zBoHg71M55Mb6Kg++WLxQXTMOeIIUhBs5kSKdu21FBdkNumo223m
x3m5NG2SNmzBL7TpNflw970f3/gQCYD2G4rPuE57FixiPAo6RHsUi8Exj2K++65HwQbvMR98
JCiXR3PG7WYsJNx8F2QYYNwRXIRkXEo+JJGfoVlzTeBeQgnpa6ExKGYXqqtQpWGO3J+sCWgR
oL4PUUyTn8tV8XCAKk+NtA7oLToR4NoVmrMIbVgExjT4zcyX5sPSULy3qI8WauBTaisiWzMP
i2mtoCB0vypYCIXr8d6M3opoINv2bWPROqrRS8AxKh5CORrvi+qtGQ1l4cffVlGdi1rmRQhV
LH6RxHEfsCh+bSRYoMYM8fLkdP7B8yukD3rdD2hpBy+rpFEOgYDKVmbXiF3yrZlTLilCnVDa
hyZWiGBq1BAJXSgnODpgF2ODpzLkwZ1A7e8S4YiXFPVxTzdrH1XH2eajbvuKw320nTtkF8qT
C50ZN5GTlPzbnluOPrjzExmcmiMOdOnfXZTW+mj1oj0PVZB4PeGf+KQdnDz8YgZvQ0bWnpkn
7dgIRzM2gcyJbDwq/ZaMGomES+buoqWh4Ot3CRaFQhOF/jQWeF/JMqIcYnm8qaV+rnxm5rnl
8QMOkVlKCmjbtQ9xTRh1CD6QvIHgpJuQQJ3IB5IUggU3UYoJLQ6hsslqLbGGbAgFCAG9+qjm
6QLo6RwRyRDYzTLvR/U/VbwLUMEQyWsCg8XUpj7CylxxRCzNH9+KG7UEdSSD2ZUVmpzaXJ6I
aPFIGFGGIIEFjMDbxWr1yaYXEqQybJaRvoJn1jbCit8meBhB1NZVcs5Pa2jdvzBorSWfsSj5
cdHk/aZQIUTRcl9cb6U09BcuDFtFEQtbTYQo/uYVir/uJVX0hwhWU19EaxVtHbwZ3rGPCjAx
/DFqded6QGKlXVsVRXMQ2UUljJU0aknKWeF4h2hQSxxCn7aysw9H2AVuybAi9n4P6pFovzwr
VN61LpvdfNf6DCF+v9MvuTzpiMr7XNzYlj0L5FGkPffRv614E9r+nv4v4SGUf9vqEUeG1JH6
AAAAAElFTkSuQmCC</binary>
 <binary id="img_83.png" content-type="image/png">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
yZoAAAAASUVORK5CYII=</binary>
 <binary id="img_84.png" content-type="image/png">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</binary>
 <binary id="img_85.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAWUAAACLAQMAAACDchq+AAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAEy0lEQVR4nO3Z/UsbZxwA8POlJmNVQwqt03bBmpX9sJfM
rXV9IbcqY1tYRqFQug7F4eY6iC9bi5bZek5dOvZCJRMVGYaN/QGjiIKjuTnXWFFmZsAarb1I
xDhwOWNcEmlyz+5dOb3nLmOFjfqA+OXuc1+/97zlLiJAZUOQNBBHwCLCNYMsbOEFomUu4Roh
qzEhWEZQ5SJ20ipy+9XoZs2mzlXUVKOozRpZJeaOHnv369drqlFaR5tH8mdmVzCIXp+2Ggue
fdW5jJjX+2ct+gtjKKTudY+1QZeWuY/Wf3XOrphKP4do8/rv1ssF2UcBo6dml02/PF1EQnJ/
Yy28aH3JwFTi6LXsv/AhKq/N6y+W36C7BmX0sZNPemZtmLzGEnxAj2WSDyGaSkkLQWqzass8
kc8tNlW52XY1pnLG0p2na9Wd5nTyu4GSDHjihLv3TDlXd9L2w3QdAJOyFgNJ/xDC64StXaek
3UMjgq7pnKqD90kCceSV8XU/M9CtoKnj15leYOu+AUqgleDsD6FydDD+CnVrHheCLRrWJ1s1
6YTrlrTnNPXcPshsiSb4rrlGn32MCeg9lm4PZKHYkrt6Vz8Seq8Og8oWLZKJiCstw8+EubIa
BwluV11GcOGYit2eWfOYoo7xhN4Ho8qaJaMYkzsaaDiwYf8IovG1wCePz7TrmNzh8eK7nRNG
mA6PFY9P2RwYnTvsO+yxz2TB9Op88Z3XbAcYvepLn8qayYFWMp8+Xma7y+i1d0qn98/bofoN
y53zNj2jY2UVHs+9Qph+UGXZSIL6zc/LIExzv2J0D+IpaFUjLxK/Sk1RL2OsbgOJg6ZmqGZI
B8pqJ0jODZqh2gkSY14nStLaVfTZXMzlhjyF4a7C9olI3zkNre1W48FGV78Toltt/Tn7+j52
0rpreM9kyfduAqL7KjNJb1seo12TR8e7jzggGrjqTv3hNftYvWDMMzXfhOrKzr0m8yFWR746
VQ9aDZBK8PBvKBOIo0OlNJap6X91nvxjTSnqPmqkoOwSq/OulOrdH0C19uphy1QRs/tQkaal
4ZtQTV06GdX2CHoxbUgD1z9FL/ZkMJVQgSajR0FXHR+w5HNae6Vab+9WqLukoPQse5eGBEDJ
GFTzrxmq+hsVgoc9luFAhiEM1+GFT3PPoKxenHBn/YhB9dJEsPEszmryfig6iEI1eZ9s8HI6
4AwtDcJzLxBktYerJO4MZStUEifIHA93l1RFKDkIv0uqgrzm5XToTUUdekvU/oXQhoL2L5DX
wEMZS/oPA4p+ytjjR8SvO+SfOFjCvCJp/1vPVbv6f6R/5qdZhhodFyZjXI2OiBGqQm+uClyF
3nw/JlRo4pHQvmBKenQDjKrWM7+aHKX+Eya7SZW+PaA/b3V0TXTtoFfZ+fb8E23gNmDDvMwj
+vLytp4/3a8g21qL9ED+U3bH24e+7KjZSQNp8xmRlrJbX2TaOgjpqfh2PWeuAgTea1p5QY0m
6LcCAqWDmDoNuK961GtJtF3j8jom1a05OZiMXq0cvi7RQ++tyOlI7beDUv0+kNU2PCbV2fK6
Eg/K5t62Q0Rq8UmpvhzktbgYhMsjtfecEn0iKxcTDrHtdLpwKlg5rJHoplwDrxNpBu4S4R8R
sWBA6MG/AZzMMoeM3g0CAAAAAElFTkSuQmCC</binary>
 <binary id="img_86.png" content-type="image/png">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</binary>
 <binary id="img_87.png" content-type="image/png">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
+33ONIhC5AAAAABJRU5ErkJggg==</binary>
 <binary id="img_88.png" content-type="image/png">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
OAAAAABJRU5ErkJggg==</binary>
 <binary id="img_89.png" content-type="image/png">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
QmCC</binary>
 <binary id="img_90.png" content-type="image/png">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
QmCC</binary>
 <binary id="img_91.png" content-type="image/png">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
N9SKEQMbAAAAAElFTkSuQmCC</binary>
 <binary id="img_92.png" content-type="image/png">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
gg==</binary>
 <binary id="img_93.png" content-type="image/png">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</binary>
 <binary id="img_94.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAdEAAACUAQMAAAD274GzAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADUklEQVR4nO3ZTUjbUBwA8IgyC9aPwUBhYCMTvchWtExk
Y7XqwQ6rsJ08eCg6th2KjIofqGudrtthfmBFRGc7duhpDJziQdEIHajMVSGX2q22Y2Anxdku
MA1N+pbE+dGqpS+dODH/Q5u85Nf/y8v7J9CHAN6hR45ppIM8bRDQNMVt+Xd/CCrvkP/gfBf4
CmEpa/ImbU0aM3jzfIouuzoFps+fNTTpqH3X780jVDJc9RTGLiU7SYcbnRKhRE3DSiqUdbbT
5HDdjanyt79rGvCKRji7QRur0P5FC6HIFqlzICwAPVo6TuI3eGdBaY+Y6IGyzFjPAIAB9g5t
Qd5fdn7sWdi5EW38YzuHhIQExuqRXPJjRkLWUiC7AkGuQVksdNclWMEKVrCCFaxgBStYwQpW
sHthvmAWE2zU1nxGeWOx5nOY96wsJtiorVmwgr3AVsLfUsuHmjA4S/9s+Q6cW7vpYS2Vpqiz
ayar+FhyQFmGa95wXTfD2pXqhDS1PpWHpTYHq6/gmv4tHn0OiqUdshXNpIRHXhDUdez/sQ5r
AZjZbwqzy+CkOMe1ENGepKOxnsj2xeDVrA8Zlbpj7XZkO25wZI8XlPGyWI8d7SzkaQ0L142F
xbyscWARnc4vgbYu5mt+JeCf3nEwNrBrD1YmI1pJSMMOSHQzn2KpnN2zmiNaKsxmJs7pAHXL
yNkRCWtDl66C2L71hdlL8bZakVyLm+yVpakBzrp1IVa3b8OWmhAk3qbM0Wl/3cbLlLmEmalB
wsOczYJgYJ5q+eKcFbcuN6+x1h2WNyXeVtMImnxt6QmKJg9ryfV6hSFJPTHUhnaK0NVkU7m0
K7/i1czR673DWS1OpSNxTcvsWFHuetXNT6qJkSLLsDJn9UHmhmXUVPFy5sg4kyCR6TNoMVJ4
sTK3l7PralXjgiIhrcg9JMopbsj0vh/tK154flwd2WpTQHMBZb9bm9rJViDpyVPlW1Wy+y2W
4ceW1bo1Jq+lRD92fA3K/26yA+QChPuRKtuqltnb0GfM9YpNP6Sv0Xt9HdHYbd+39m4XAajg
k17TQ+b4ToBqBdvd0dT+9uFjHm5MuNkCafciFgtOwWJnlPe/sSe9CU/3fRTL+xfK6mNY7w55
gEkvy0+2fwC+O1ChnURc4wAAAABJRU5ErkJggg==</binary>
 <binary id="img_95.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAZAAAACUAQMAAACHl+0tAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAET0lEQVR4nO3Yf0gbVxwA8Jt2JlJLUjYoZVtvBan/qEs7
d2VMPUL3x25QSv+UsT8E/UMWF920EQJ9XVbStbA6EmhaB55jxb9KcfnDP2TjBIcaus5t/aOz
6XLbYpu6amNbsAme9/reiz8u4V3yMsq6P+4dvtwd+dz33b33vfciB8su3PMhOtlwyZBaK03W
4e0BPe8SJYn215Uebers2FT3+EB7cLY3CEo3bPnEg/X5/Zfj3S/7O6Kx2nEWUoVI/0hbTZPf
MzH7RisLObm0Hu87cMsT8nui19hI6xXtUt3IFCKdAYmlYXD5fK/22eB3M92r/vbpmt7zDARH
+gFCFX2m0cbwkAmBmwT3ExMpXp4JmeQ2in2E43jSwFJkhuOc3HZxMJCCy6YtYhGLPAdypnxS
aTwjsxD90+97oO6bz13fBuFjlZdVIEIITMlJuyvsEdps+ISOQqYSxxQZfT2rmBI9JoS7Lhyv
2yR393ldX4BvphdlU5K5dyBgd7pJlBVEFobTTSHweqgI+eT+1XNd45IPiwpE7iTSvRFQHzUn
sMrODXkECUfJ2GyEDIRB/cQ11ZSorT8DHcA5tKvZBnHDvCduoCiSeRQIE2DjWHOgK9/l+bFZ
b32wuhjZKiu8Sj4V8qewkJSYIzIhIguRgZp/oiTRATMZ3DjMso7k5OKLF6/veFNEe6wkluwP
Hb30rUiisRE11ed0xwOIiMwk2bdHmj8lkiUeY8MW+345On9ZgQ/ZCBpZyaX+3/+43a/AGbiG
SVKVZZRia3CVTnjDkYzWoGiiT6m8QsgjQCOagaDRrPEoX/7meZSVwV93fU4lKwaClrlaFc5K
WRVCwBVviIg0MskVFJyVquyKgEPxTjoxRkHDJVNFiNocQVFMiJ5/L5lW0jBZCHmFeGeYSrZG
JC74iaHcf8CrKCuDe2teot5+3hHuF5nsKbgCsDR5DDd7n0Cgl04x9jG2XUQ60Y1rvwJiki+6
MVYBSZmQ5SXX1Me+3NMtICa5r+9aHo13CG11FII6k0rebRj9gHMet1EI5KkvJX2hGhM3lcyI
VHLny9FbnRckWsPgI36TACOpaRj7ukOQHDRC3vzJhANNr7DI9JpH8JSUGk7jRH7C9hrP2PDg
59MokU/vOKgwzS9krpTnhDAYvx5hInByI5FvgorYTQNZB6ZEJ0RsvgGaYsYo27+VqWRh2CH8
5h37aQ8h2sEzr7Yr72hvJTU0MaxRFlfoia3uc6BEnk4cFgmpDb1XG93Z7HQ7pbcP+U3XYzKp
Ccn+OWRvDP3TEvnQ17W7CMnVgJDJCXtj4F5L5DWfe3flTpZVX/b0kNQYjbVE2nzSCxUfsRBt
f8DeHt3b8tWRHnf1+0xRcHrfR/VDMv28wkYUUj9BGy5lrGBzJFv+ojcjlk94BrJinHsSmUqW
fkn8GK6cO7XFym+Y47+4/X/xkP/vP3ksYhGLWMQiFrGIRSzCRAr+y+J4FuQpyfAEFXnTdbsA
AAAASUVORK5CYII=</binary>
 <binary id="img_96.png" content-type="image/png">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
6jEAAAAASUVORK5CYII=</binary>
 <binary id="img_97.png" content-type="image/png">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
YII=</binary>
 <binary id="img_98.png" content-type="image/png">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
0xegUn1O544AAAAASUVORK5CYII=</binary>
 <binary id="img_99.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAXQAAACAAQMAAAAh2QCrAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAEnUlEQVR4nO3Wb2gbZRwH8MAK62ht1ld90c46OlFfqBnO
9oVgEN84EGXMEodEDXHMkqS3mhdnCPYRSVtEsb5oX20zgmgR3IKsIULzp4XVIq47wQ4m8/qH
OKOE9Fry4hLv7nm8ey53SZ9c2jxlgoM+r3LJJ7988zy/e56zIaqxZduv/43KA3C5gVAg6iO8
iEQAkg28DGGY8OvtntYXrf2NzojjG9JzR3jf6ect/VTXbGS8zrfz3ldGgZVPnT01/kWZ9PbT
Prt1/eS5Z8eTpF8/4hmyW+dP9kQiadIX/SE/Y+3HOhc/T5P5EcqqlSw9h2ZQ3Xzi8bGl174B
g1Z+l0GuL8XYss3bqgM0UEqNX6t5n/A1lWw7vWLpay7lnR5WQBrU+WK9v2b6VH19SfewOGn4
jVYPzNoxmCP8ze+XpGUn9sEZwwtneMgzGPxA5JntSkizGZwnLJp5XuPhgu7LhE+5BqR4xQuG
51xuuKb7uQzhzyWksYrnDF9weSDSveQkvDshpSo+av5f+QOY1f0VIs/04JI0jT0MZ8z8KG+A
NDE/nCQiTp/PsL3qRRMQHqFJ/aUMR1DVQ3tDL/fqvvLWrv1Wc7l/v0v/N6wvA6Xl1fr6jfuf
Nr96mWvg1f6PWt0v2hoQhwD2kral7PDqdg6zf7EaCDOy9xPvW6a/dZ2TkCTg/kfFVfC75jfn
eKj4Kl7ifUzU9FNdCTQW17xyHGx890SrQ/WrV9X+f133MYl/X1gzfcqVkOLXsC8AYSQ7qtVf
jbkh143zX4rJfKjWq/0//qXm5U0gBHPYb8Y8ULiEfT4G3aFcjVf7P83g/MOgGMw9bPZ/EufJ
M+hYbZ7pwQkpzWoePQXKlfrGnqGCIQa5fUzVL4ZEtLSNF+gzoIzkKz5ngJ5sydvWV/UIzegv
ZeUqKDz6bcXDwzULtHO9bla8PGLPfhroqOufKOH3uF+MXyJ/bh/9X3Jiv3Jo/qihCU/b/2G4
/t7g0ftxXhi+x13a26+3eEpKu/Yh8C2Xgm0v2E0PzfNF3a+2L/dFcb/9yisFH/bhnyTWNwBM
H+mc2J49qXnla/bek38zuJ9XeCWv++cWJDbMVr3a/2J8HHvhMeF8AWDvcitFHwZ3b5fY0oCz
6s8mxNvzmpeFw0Kg4MR5XEOKqPs8/4/48wBX9e6EuLyE8zMuoT2A82+shGS2B+fJX9HyC6aP
vzkhLifw/MRY4aGCoM9nWX1AxOBltzTgTVT94hmxLJ80fMDwWcTpPoPQV21idT61bcmN51OJ
s/fObzB77v8cSunzH0ZF+UK0rp/thN//eSE7Cf/hHvt/Bu1xv5y4+NGxhbc7HIcQ01Se+3Re
wF46L9P6taIfyc8ITfs7npcCLY9MNOuVu093P94/fL1p/4u9+3j/8Fzz9QNvvNPvb7p+qdXz
rr/f03T+MocPjMlmvXq+aj5D+TxD6xG1F5vxtM9LqPE48A+ezyD0B43vRfAWaN7DEBpdo/Cy
I+Kg8ZCdilD5C8mOqJPGp/6k8heTJ2jyoHLawdF4ddDMvzZuUHptHPgHy8P/2P/f/u+BP/DW
z9u7eG38CKCNNcujrX8Bj4Ne2aR69rwAAAAASUVORK5CYII=</binary>
 <binary id="img_100.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAADsAAABJAQMAAAC5Acw6AAAACXBIWXMAAA7GAAAOxAHY468Q
AAAABlBMVEUAAAD///+l2Z/dAAAAxklEQVR4nGP4DwEPGFAY/6CMnz31YEbT9XcQkVXX+yGM
1ebr0Blf2+whuirnQ0T+1GOaDGWcaGBgYFBgOMDAwPj2/38FhvsQqQVDnPFxTv3He/VAxvf1
9d/BIt//139/vx8n4/9+qGKISH/993YQ4+O/+o//7IGM31jtOg9m9P/JgYYhCCQAQ7UfJAUK
3vswxnwYAyh1HMZoBzPY//9vhjGYQYx/zAjGP0Yw4/b/f54wA9+TwjiPQ+SMAgM/mPGA+XA9
trQBAJn9+FIq3mkkAAAAAElFTkSuQmCC</binary>
 <binary id="img_101.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAVEAAACdAQMAAAA35dl6AAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAEd0lEQVR4nO3YTUwcVRwA8HGpuzTQLraJ+FWW2E2ricZi
zYJSmWq9bAy32oPtUg2m1lBtGwkeCnnDGsWTbRZNiB5IPOgRYg3gUjogMRyqbNsDjcZlISQu
MVt2ySburMzOcz533ny9eWPSpCb8E8Iw/PY///cxM+8tBYmDocxnMvphxcWyLKkdzQZ/zI5I
RylYXvsca899yB6KU766Z4bif185thtrz1xiW5OVZM9TrYmPDpzcwNrdop2qTNc2B+J7D57E
5+2/xEZeujtdF2796qfIm2ew9tnpsfH4rnhPeCjuuznox9oMzLCQXZC6AcBBHmvFGK0ezbiN
BTpuprBa57hv7SxF1ZBZjqICog4R2IJ6hgm4Wq569ZWgixWQQldovJ01/BNruRD6F2+swmQH
jZlWgLMtmCoUDH1htB9DUxTQxAbLmdKKgSY2WEtaY2LU8iGrhUhXoHbVhqInUWuZAFII+sUQ
a9MyKebt7JothRywseZpZTmvW/3zpqi2Tre2vSAFT1usUwkQXjZb3qkE/YpVu+lIq0VU7ZCz
1YrQrEBj7KbBVjgM1dqi2rLDoKkRRG3hMo6qw69a86PAFMqYahZgrTIxVTuLt+KrRrcMvgYl
VItvmtESxb21FTcG+Q9oYluBrGwf3BwBWCi0/PlIaly2b4zdxScVbv8ey0/I9sTYLpcK7jz2
W17Je+L8+27Wn85PkllhqSMNFdt3sR7g7Z6ldH5Ktv+4VKDdcJJ1791S1ZKGN6sGcDaCShiK
Vk7QzpavlX+J6woCW02vItaD9ZL3P9dAV7AWzSsAcsu3fAKI8+Z+IK9h49q9sbkZ8no3Z6ZJ
rRhbHqweKQ923sM8W9HuR6Pdml/fB0qF1Yx4DPg9i+JNuVU6Sltr0O4US4Q81ADI7ZyHtqXQ
tmXLWKs9l8qSzT+h2xJq5KWAoI7bQptkN5alY1qGWdQKELGZ67oV8944251rmPu0+8JzA/sP
7xxP1nU0+1+YAAYrqDb9+OlcIvZtZ+T8kejz0Ynk60eGw19+Z2/nRk4vNp568hX/2/3pvZ9F
4n+9XLx4KwCUtplqyHyd+aWxq7nT/05/zFd7ILE+U2yRLJI336XaVE8ml+g6G42cG4i+GmWS
0WvD4S8ajbZDtWs5sW3L73X3rQ7s79nJtD96vb0+Um1bk2TXOL1/s0VB67FFIPekoPbdAmce
41LJuCcGErSdvw6BWtbFKpdiRVsjT+iHaJwdhbyvXn5j7NhB+m7BPqaRCMnvN0ypSCjrkhCR
pT1YuQZkx4MJQbHOuwXUBsktL1vedi9msQGvtjJJYsuTnsdi227bbbtt/zfWcYNmY6Vtne1D
1sZKX3ccJLTS9zx9bVcePlp7zNWKG7pg7+3XlqJXO13tgvjT2/jrzdhkzNVKX1T13vn5Vuzq
PlcrLi6FcOL4cOz7qKsVg/e1/dEam5onskV5KZIhsVAZPVJrE/ePZez3FaZQ1jDLQGga5QPs
hQaq6ekHDlGHS2/VAFhcp96FnN/3YjuA31BMA4DMv8onH/T0xOTJAAAAAElFTkSuQmCC
</binary>
 <binary id="img_102.png" content-type="image/png">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
jmCZhZP7e0cAAAAASUVORK5CYII=</binary>
 <binary id="img_103.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAccAAACMAQMAAAAz2vDqAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAFdklEQVR4nO3ZbUwbZRwA8KdHhWNWe3ODbWHxDq3zJQaH
sMEykAImurgli18UlbCGGSQZMFxkIyjXVScxokxmFIOuS0Q+qcMys9hRwS2CCAg6RQzQAzEh
BpAtKG9t77yX3vXt+vR6iROiz4fSe/nd/3n5P8/dFcCoLDRYazJnSZ0863qoWJ1EiY83qZPb
zuveVSdfP1+/RZ00ffqJypgo0ZKmTjIuyLE1l0PWdSXhJXrpue5mOhnr1O1K5Ir/xurSyi/N
RZcqnEpkwCxdnVrp/aDoC6i0SnJsAfdVYFr/1TOUQjmd0neClORx8qdyarVEWczacY+vtiQ5
rqPspLJ21np8yU9XkONz1CXFskbaWC4hx2YeaS5WKGcapA0PI9RckWT7yH/DTXKfsEygoBf7
ZzJeiim70n+ppJ2y69Fak9S/KReilVJZF30rlWjb6RkVv7WTMkdt4aU7RvxmlpHu2PCSlmSX
3HVhMg4maYhkJDkpJ2+FSD1MMuolbB2S1jvZ8TyrZB2KOuMDZPOw6U/tHYPcZtMTTnyQ5Fcw
M4i+xPCSvfiQnnbvIfkItP8JWu4Dl6mQi5edAftoTR579gZBogA8TiiWBzUBUS3AqlTeawyQ
MUb8pEIpQbaR96DJbwP8PaXt9I9IIMlGq6Mh05I53pRv7SmiX1rc8ln36HVZ6V9XFLDXwR1a
bH9jIejVW9CsN1ozWix9RETJFSPuSP95biz3xa4SW2/2wIeVLb9uuF9eGgOjmnFHas/cEC/3
75moO0pYUGUxARfzSGPBkW9Ymd1xpmrHg70Gr/T4381pIlTGbp3dWai7oge/d2S1Hz1n6RVr
656Hx2RyGDd/7FjwqNDfjrhzdZXdunL7Ii4vhQeSmhD5FHYizUBYSlPs1yg5GT4Ttu+sufjs
+5bnCAumlxkViOxgpem0pbSl0Uhxo0Iojpm5qSbD1NpUmmK7XERj5ySDJCAgPi+MnOba+fyT
y8cPEV2z5fZEnE4Oqi03P3uSypiKxVpTw3Jxf0r3nbyUBkR85vbNFeBdE6wM0/hYmUNvyNqo
HdmnTbHcwsueHG8OSRI8ygUCZvZTI7az/bUyhzUu2/XbaPXuw/Z+XjqfDpmfqBRSkjbUdIqT
rySZqg7ZxwQZkrda/+qCh/natvWVpeoTHKOHrx7YXW4bCCPFjpECszGRm8tOlqRn7dUOaWLL
LZNwyRWU6yMjG1N8hbVO5uSIo+J0CXL52EVqJFgCBAObdbhfwi51kgGZwElT+tUhkpfagOE0
RFo1XdWGt04LEvHrWwxgZjyCNAEMk4lp9uXtqrxcOJD+w22k2E7MH4tyWV5OaNKHML6HUBBU
BGn6jizZNs28mTkVJKczvFcLM7MvaAyJRix/0oDlBsn5zhAptNfbQ5+7XvixyFYwd9dwdZCU
Ct9DqA9r8qzc7lOuyu+L2nInbxoOjumTutjNvppiqFGIOTC660yhrWB21zsF4WMaEODXS5qD
vOzfq03LZ2NqQfiYwKgFOv8R5eUgmcPO4fklZoIMK7l7NipNFSxOWPusDCkcnw8v+TuSCLeC
jQgvA0+SlwhLUSFrNSABSTTgTGgJcy/z9g9iJrT3GQFiVSrj2VBmubwNlew16XknSI0JOB9e
YmSe3hBhU82bq7h2U1HLQdWyR7WsgwqYfFm1jAndpVBqvH+pGyfpeNUx1WfCfOiudSCp/4iE
l//7dr31bfRSLJRa6Yb8EymM9L7e0fDVU05SDK1S1qXW849ZcaGHIsixj3ZcUyedf1Txv/U2
hB6KKBdvvPyrlX/WpaKWkaLBZKdwTPZXLLgUinsBlvwwWX856W51sqWx7wGVMdtmVMqvbWrl
hcZ9Ktv56pXtKiV8PaL/Bj3Uhz4XJCO6AAAAAElFTkSuQmCC</binary>
 <binary id="img_104.png" content-type="image/png">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</binary>
 <binary id="img_105.png" content-type="image/png">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</binary>
 <binary id="img_106.png" content-type="image/png">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
wAAAAABJRU5ErkJggg==</binary>
 <binary id="img_107.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAHQAAACpAQMAAADA9IuVAAAACXBIWXMAAA7FAAAOxQFHbOz/
AAAABlBMVEUAAAD///+l2Z/dAAABP0lEQVR4nO3WMUvDQBQH8Bu6ahUdpenQD3C4iIv9Npkz
6nTerhBw0C66+TVqyVSo+A2UIhicvDp4LZQ8Y6TJvT9tSlEL0t4Q8uN/l5eD5CWC2DACbcE6
PQ4dt4gG78pxZaduHWt50bDKCG+Sy+06dyg/FMX5/MrurXEcbR6003q5qZmdFFZgWvsv3Gty
n99wb4C7c3wP7nncj1Bv2fv9Z/bBAnwK1mVOAopcj33utBqv7yuwEdmoznCQrY/z+Sdgmuq3
ZXsEHsD99SF/hfwO8g7ko/L9LmI7JRdgTe3Ana+pxp3sMUd0VeqYQsl97eZDUS0cf9mb/fxa
8E/fBzv2uLF/ncF6yC32p4cn7nn96/u79Gv7w/Vrr5Yb4EX7e8d1cgz9XdIz8yU8fyF4P8j/
37LrHSli61/AW8R9yF0M8wnLMm4TzH7inAAAAABJRU5ErkJggg==</binary>
 <binary id="img_108.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAANwAAACXAQMAAABEC6dtAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAABmUlEQVR4nO3XMU/CQBQH8CMOTCq7gwuJq04MDP0Ifgbj
YExMwMF0MPEYjC5EXJyNH8DNwWhIHIyb4iwqJgwsxtYwtA3tPa8BpFfeu0SWluB/IC/8eI97
R0iAAR2b6dHX4TXA1xaFHsBDl0CxerR9QiEUry40WD98JfG2Xn3zyM6zqqbzPv9OoozQoqlD
8obo2KzCMjQuwweTWUTR6BcWjpYhyLEGcMFl5bUXEGzsAm+AA80dbKwJeyvgiOYnhhxKBbBE
6wXHjYIsmzf42JIZvmeZI6f9Dsc6oneArQKSwXE3sVX6BXFDAFlZdCgM4/wdZQxkrD/sDF+Q
HT3vskFyEs9B7ewNi9Pw+gJJDoLPzHgyAmhRaJV9XqMQzOAy2ukqyINHBYvRsQqKeXdubKzN
fqOg568pY5eiGIu7P4ajzq5yWjhWUN0z1hnFvPyekVjJsNhHFkH5EEB7vUZhLDOHd2nDWdlz
KtDTYepOm8QlTI6pO1ASe6butNOFwcSYulWmCye/+CRwVvb8x2SxhaIv/0EPgmH/p72b+wGR
n9WKDHFvRwAAAABJRU5ErkJggg==</binary>
 <binary id="img_109.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAR8AAAEWAQMAAABllgHGAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAECUlEQVR4nO2aQWsTURDH325WXGqVhSoNIroF0QoKSi+C
YJYKevHQr+ClWlB78OCt2ULBHpR6DTQGP4JnDyl6qLd48iSEWnAvbbUumIZN33NfNrvZJJuZ
KV1ilQy9tPzyn3/mzXtvdikTMuplAQRnwlsX/MYFBBJVwaeqCLQ5OnX/OQa5xbcaCnmF7UtO
BYW2KND2bA1NtzqBQzJcCrRbIkD1HAGCgwi1IgNCqkaALrIZHYWuKy2lc1C6wNMYb+DGT/A9
CFqSjJnhZwHIlIzOMvwL5GkpMM4/5wElywigr4DSSKvi/DbkySBUPEtZu6ztIwZTQOjaIw1X
aoUS/1uOArXiePen6olQqRPyEqFKCNVWuNjtVrL8H/O4cCKozPNODNrny5FSLYTWs3zsTgxq
LLBms2o+5IWQc2th+mHMU+NU21ME1d7zb/NxJRYodUMf5sUei4dPqRLaj0HT853pFENa6oBu
LmQ70p1ve4qg7oo3WsXUIUhwy0xQWuyCmIGmC7cZY5e7oXdxqMe43dPjnCkWm+6ARE9EH4Sh
nhL0VdIQyKKko3vCIbuznxIh2yJATAuWBYYYRUmGRYEQpaif6hQlEpRCxQ8CNTBI9hOnKPE8
ARIS4neTIT2Ecu4L0fieDM2EUNl586z4G0lX3dQfjGJQxS18evILgRy3sDb3E4cqqFLVLWyc
xDyVRNl9hUE5//etPnXqqPhOYsV1+tppI2AX6IbWXD24VXS9eeOhnakqKGQbcu8hkJnybrmH
QGagBBwYGgvOX/TokdcZemCY6l84MEDIGGS6YNZnKu7JRpSU5iBvx678JKXTPqNYNmZ80lJs
zPikv1sMUjE1REltTlnjiCfd8Oe1gbcK6b4j3cEuDJmW7AIHhgKlCgDpIVSClfxueQ0rRekg
Txrx28kYp9QJHo3MFBdYJyopR+x8CiF06hmcJ5uk1Cy4hW1zgpLKuh7d+imldreYKRfzCE3R
6kDTaX4pl+lKwCSmRKN22pP9U2xila9pZ+FhVJT9A7iBT/ajlMl+9crjxHn8TBty3OLa3F6y
kiZtKRLyih+Tx//gjYmtynRe8cfVRE+q5U90i8Fk7xU3XgLGlaAE/mTfB7Ka+Vrj/05iMdtX
WXNZRPKyHCO0iipfqZpYq2hBvnG4nzT55eCBJly7DArhzwiULdXzQigBsg6wgy1caQIbRlvH
SnqnCl7MVNOl5ykN4+FJR7rykePQZjYMmRpBSaWmIxhXdByyCMUMPYGQebC1Ay7q9pYCx9pw
7WoQFKYDRm0hbP+ZTEIrEBSEG0kfFqqlppReugF7GkLDih9N6PDLkv+vIUGBBqw0hP5dSAwW
InkaQkPogJBHgYbpYKXwn24gJdGe7AEofACIoD+tQSKy2UkzEQAAAABJRU5ErkJggg==</binary>
 <binary id="img_110.png" content-type="image/png">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</binary>
 <binary id="img_111.png" content-type="image/png">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</binary>
 <binary id="img_112.png" content-type="image/png">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
</binary>
 <binary id="img_113.png" content-type="image/png">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
YII=</binary>
 <binary id="img_114.png" content-type="image/png">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
RU5ErkJggg==</binary>
 <binary id="img_115.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAV4AAADDAQMAAADwautwAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAE6UlEQVR4nO3ZX0wjRRgA8Aq1QOgdRF6I5101XjCXaOzR
5FpCy4IvxgR9vwcTSHpwlzR6RiLUEpeCoZd4Xh9qFPTC+oRvxtgHe0CApg/tpSWr4QWC0K6L
t2dEWrIm27rbHbd/gFq2zIyhkTPMQ3dn+uv026/bndlZDUAvkub492vzrxYeCLTohWA5j+X2
fonx8HUw7Lpv+aVRMvemV0zsMBQ7nnmjEZitUsjDCjD84Y0vpudBe4cU9EQ5BOwOkBaTFDO5
aQR8cZ4gBqWfTW4KGvPN5p55or1nIVIjwrNR38oqYSwvMJNQDIAYP9yH4tLyf8HLmrLy1Enh
w5LBiVk8w2f4DJ/h6uIAo4ytokdMImC53ZH+zcebeRoFmx1p2s+bWBRcGIh/51kvCjZ2SCsT
US76KgrOD8RB2l2HFMZgDlNjSNjYfZ8Zd+lEDgVLDOBpUYeU539W/yu8h4MT1cFyfnRtQsMp
UumZCKJhLj8cSyQSpgobAgkXFY2CZfIgGjiWilseBe/PNUp+8sp4H2VRsPBvsIyClSxITg4R
K3+kjDF4mEIYFmevKzuIGCw6MbCVR8eyU8DAGRyc36lGzwCn59OBuaphFgeHqobdVcNjOFiD
g8HpwMITiEGVMdJVlCvGjHR9FnAwX8TSYdNxo1UBIw1A+6jk3vuERlissRsQhZiRZgWAFvKz
LwIJM0JuQoU4k5E0ZB8JEOdIhXUOxNmXkodU+XcdhzEWQ1gcHMLBQRzszuHSKTQMW/py91aT
hkkDBI+BjGx6J73iZ9vptjgEa3K4Kx31szwt0BCcC9hvlYMeUaDCFAQLSsyBDjnmEzlv2IuA
l0xyrFOkkzwsjBz2D8p/+MUZ2oiCA90zzKSrpgGaupLLwBJNoONCObH1OuEJxJgLktXCpyQb
At65kS0+yFjYk/fCYvg5CC4sECsXhMQEP66HpM55z8A3yZ3fpnfn2e9jkAN06lp7GsD8osz4
RP4uDH+w0TsNHljlxITIwfFqcIpY6JB3vxJpaBhDq29OLy355d1OcWYHdoDa+p4pItC3te1z
1cCy4bqjZ9fIhcTWXthVC8szyCQPq6f4rFuuza9s0EhDG9bjBrnQYQDz2URZOf4ATzmOEdmQ
V7hH7BLnLWSFTx/giCGz/hJ3mYpR9tZKX3WAH0aubg6HR+Ofxrd5OF6zrXOC3tBEbRtJWMwR
QzaYFNoM7y7FKCh+mLIxXmFoS0dScQKKQXadULAW2FZRcEgnaC/sPKttJmEHqBROB+Kd0oXz
tgr2xM46DufcSODglFkFy9aRPTbsvWQdSTH5reU9JuTxXjLWqfVMKFvaS0ijIs8p+CPlT527
K7jdpIYrhUFg4MSRluOXOtHx0SJpxKa/ytrK6yVYGEyXtRXrsioGodbXqWwSSLeVMN8H8vBo
BzdzsfR+9AA/sl97rX5CH4t0PX4+pG3un3NcGzGGdK3nTGr4xmeD0emd2CeL9Oy63dc7d+tz
5+z1to1zLxIqeLt5oHFar7/bVd+2+XFL93jDj86v39atvVCrhn9NDUSn7C9fWYzOrl/2vfXl
rZ+c32wObVxtUcF/alsGGseffuVK1+O2kN1nn3P4nN+FtLU2VfzIDAiPLemY2VkMa+v653bN
5INw5I7tB5UDzC/gUYVafL85LiZVU1dai5fuqP0oR5oqFzz8NzGNqRflcspCAAAAAElFTkSu
QmCC</binary>
 <binary id="img_116.png" content-type="image/png">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</binary>
 <binary id="img_117.png" content-type="image/png">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</binary>
 <binary id="img_118.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAeQAAAE/AQMAAACtmSJWAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAANR0lEQVR4nO3bDVATVx4A8JcPYUEwK1AvFoZsNH5VHUOP
YhjQBHAsSGeg1s4hnbFQaSsVipZOra12Q4ixXi1g9Kw9PZA7+uGN7XGItndFEmUOzrFVant3
eFKBgw7tHJUg1gTz8e5tEiDf2V2cs97xd7r7kuyP93bf27fvvaQATiEM4D7TEatFaFvNUj/b
OpW8B1KjuPjZ51nqz1OvlNT8hq1+V6kv7awIZamTW78suTL7Zyw1f3V0CV6xknV9y9E/eB+2
tbugbfIp5c1Wm2bUqxPUdSz1WHPDabWUrTY3njytlIrY6qZlj6jZa01J83F+D9trXkXe3n4Z
stUtpGPPUsOp6PG4N3qUtT4z1xSv//MBfiErvbDmqESpTdxHsNIhNTxJE1d67SQrHVPDm9cU
Tlxko80wURs7TxvOvyqG21hoqfDaK/pfd3YXQikLbe6B0IhSw2z0GLW1azkL3TeRDGWhKyeS
HOb6DpgMNpra/oLasNDmiokkm/MemUiKWGjrZJpNa7lb+r7q15QujQXgTLXOBAToKYqe/iSE
ncG10UNjAselo6cHnd0vFQZSZ5qfw0QPrNwMNXKnluvGDq9gpKMy+tTCDAGWoJGOvtOy8/fM
8tZmFDboswVC6SG8/J2zn+YyOu+BLhUnUS8SzJW2GMrrWmQM8xZW/hWAzJwwCd5ZLj9rimGW
d/KoHgjTBRxCjW/7+Vl7fdPXFtQytqH96M1LKa8aW1/lO1oLfW3/zxE6k2IFE+0eOpOS0VXz
1ApGJXePqd1jygg9CSuUnA8iRLIW5s9Q17iveqap6R6XtJWFRnW9ako6mbW+ivRcFZ7/lIiN
jtuJdKM2PzeHhe6Om4uGHGei8lFzZ671gAPhgubD6zPZ5G3XEilQZLLJuzsGlRy+odjWo2Nz
1fg7qd0wCVnV2LmxiTQNPWJw10ZGuq/f5YXOoS39Bpp6sK9x1fGutu9QV3iZmrFTeiysn27e
A8dXhZxPz8xUcEJNlL4gh+bkvkZUcQM1QfXQDdXuEF7ezMWxZYv0qCcM7SUh3D7wfjGEvcFH
XJYbql0hoXkz58WWhVJagLRl51BIIeocguuhAe0ujX7dknmxRYsMqOQQaat1aE4a0sH71L4R
3e6qrvNbXsnYXmahNLrcpr5vo5A20e2Re8yD1E7n1OUjOjRFMNFtaz3my+PJydZiYdGnTk1P
jrCZ3yVucX9oA/AO+s9vo2P0ooPwByZj5Eltc9UBRx6hk0lbaJ2ntjLQkUiPjtrKbfAY1PWj
mXNQLZNosMLajraIhPb4V1XCkIRya4oNLiV1PXRKLpOoMKK2si0CV8UDVRaILrfqbHAr0iIa
JU9WajMITWVbqTI+freW1xg96tTne+joA9o0Qknpmvhd2uxJfcclb73fkidoMwlVpZ6PSs7X
xjbNHi+5xeWq+dflZ7oLNR16fnIHfEyXcju53IKu+RJSd+1pGnrM8RCwTawyjNfYoHzymvvX
g479xN08CnvotzWvMLrqYCX3CgPAQByEq0EEADFgEVbC6B4zTekOdR8mjpfnf1f/IcBnrjrZ
4zO5fVtNbcaC530KwlTjsTIb+UH3jqqEz167vtS0+Vg5rJJ2yNuC68N/TxcqsBUlyfWYWC15
97Xawi0cbMnzCw9rRbzguv3r9dlPX5hffHb5jgWnjx94sPaHpZGFkY/uOQwsPGF7Ql1g/Tmu
mBkfRnBaSl8WNxMV4fFDb0UWRIbOOMy18UztCfvJgHroy9yLmdnLCltKxScTG+qf2fTD0cji
yEUSXNtT6QW99P6v0oXP8MRFb5RixCN/PBF+ve9oJDZ/kZLTLuoPrqFzCcrmrCs4XEdtx/7h
+swOqi3QeYbDjr/ivlwTQDOMac1a21b1X44jqZcdPg4Z6YBk/2UfH1DaJuyrTu3viN/WViXb
3jFL1r83bjtKUjvqlSauvCNe1l+FXkU4dujNjjjq6L69jFctvEs+rf9PtU3k+kLHUFvctNy+
q6Ovu8wycgjCuOeryz+DtrXfXzwyOLxfXCUfoaU71+ObtrzOxQqB+OGXjykK0rG0nOWSZlEb
Ld229uDa7OT8F5cUgqiX465m5G/JE0R+zIE8WrpjbbSCl5pfIC7icPlx30TnfSdEepWFs3+z
sF0eTJ9fczBz665/bl3+rDh8TdyFg/nZ2IoCSXMtylvoo4Nw12Nd63np3+3qwojC+nX8622f
PYVllAGOhs7ze6IE1HTHfri9rGY0ehihr8cmtGM/7HyfnqYf9PXURppmNEZG53CU+hIDjaGq
38S5HCUjDe0jbBpzg72TSVtIHcKsdQTSo2ZqZrGfpKdbFqoiCmofdcwFecKQ2SnWj+xzInqa
T80FM9rsMyoVppqdAs/a4DBNfbZGm0WoeH0SNBdMrQlrjDbDZBu8RFOjuWAWoeSdkyiPxO9y
6D/Rz1s4G80FlRnUXHAW/8GwJkfJ/01Tf5qsfaxA1aYHsg4yxCw3Jr/O5KqNzwUnVhXNts2o
xqj6Hgyurc7B5cQo0TyxckAjb6+4W3ow+CzWK3zdoeAn8xT8iWudS9rGWNe5pJmvK4pcNeNv
uKakbQKXF7TXcn1rpr8asLmsu9FZw3YPC9LGnnHNdHxu/QR17XXOF7eCr917hA7pI2hLfR/v
r7V8aiL96xFhRuQCwr+2PTzq6+1xHZtVukASQK/cljers6SweSGBVXrpW0+llUoC5A0/KX+8
9WCxRPL18qZob/1EVrGkIaCe3RrN+3jG397+xDvv218pwIzjAfSZ8qzWDPHJB758+1SGl0Zs
sAf60s7xUaMJ6SJx4tz6Ru+SOzrXANpqfwgMWl4f9Vwz0Lmk/WnnC6t3xQXWbtx7yUFOaY39
t9C+tMDzePcQULqhljTrfOkbQTSPUoPHooDIl+71+bxxCUp3tFwpq/OpBba269CiKWqPX9kD
ZehwkdvnHEobU/Uv+sw7WMlDKZ1kOVfiM+/h6sBaRGl1fHSJz7w9aswrqHELCrl9CB+4tfgK
nUv6HmsLGQz4DwOgviFlr8fMRUT7zCFWJUBaTxCqrF65oyHGboaXu3klNTHcRAUAvTk/coiA
DXfs8yUSLdYrLwB3CMAP1swdIcvFJnRYgxYj5Kjocisc4AIMB2uoD9QAEI8q7X9QiQ5WUO/h
VLM3OsaKcmq0N3Zun0SFAUrrrOge4wIOTuQCrjJTqcR5AIQsQCo87bgzs0lNOvS7De3CEpK8
RPUbjjvUng/g2LfYA+jMUQGU9i2Qu+cNL9fZrx95HTo0ojhBHV0AcC76G+GJAA/HPwQzuGqu
qyYd2lHhJJotoZKjLBR/+eXjnNz94T/OexLgFeseeAhfqjiRK+a+x+VjCk/tDPIYdd7fYhUY
54WYJRsk4b9KAfMVBLd+XcJDMW9uOJEreVAdHuOV97i+TukbSuzA4jWLc08o9oQ/Mm/DYrwC
O/0QwMCHG+oVMdSJe5y3wbnETNbax95ckMnHcCwG46oXA2z9h+n70vFwPhbz0XpUh5i3Flwc
rU4e1Dn0v5ThHHAI43+kqODGHI+ZD8QKMYauhRKvVKDK9NK32nh9avy8wFHyAS5fEYPlLRNW
AP46PcYnpEhwxSACSB317Zl3eJq4QRkrQBeh1WrsFaMWxsfiANiTibIKIQxKQHC3zdykbPSt
hZkcNF0c1zEoKz4GQkAFmMFbQ+D7QPNifgGoMijxCtR+FJ7adEGPmsW49nlX5KICTbbUQVdt
6e8F6Lzp3V2eAZ0/rKP2AZbp/fQO1NY0JQ2n9bSe1v8lzYw3eujBe6mZcLmHrtmx1++x3hHq
oZW3goyR3YLjofW++3N/IXXX5wyhnhkwyLvPyER7nne/0dc3g/5C5KFHmTU2L+3+sQUGDA/N
8Da5u9ojdIG1IYAePbJLDsfXpex1YXY/IKDuj02FGhLCFMvIJbMRxtlEvbCrfTvdvOtSE9Vi
WQ5oJtTSU03CjXMU+ThfSlObr+8+dPU9Wc5zpxsSNb/77ZWw5tj1q0sP09Xd52qu1gPBc3sa
EkN4qs60xliFtDiAHnNdITJftWqvNhArnvuioVkTrf1qY2NU5pxAeY+5/ozW3JWa1C3mlBV8
0aCWHjyCbWxSrFOUEP71yMgdoUZm2BzhegaOxRXrsOP/8zP6132GJx6O0r6Un0266rqJ0gTW
399+YiHQv5S3RQ7phIfWz8leCM7fzN9KC3tdtZvZSXjnS/nFpD8QSFtN1xo1MthOc1oaqLXc
V5pJj+qtmTzFvPTIrOQBnRHu+KaqMOjyhbfu5eP6Zb3vz8lSgxzmekh6rHbLwPtfZyXcHNc6
JrqmtqBX05mWcJPWk9hdDzRqtEUDh66kJZxZwVz3KTs7inobeU+qk8qYa8fO6vuHNDQ1/ZjW
03paT+tp7VcbRPdOM17Gv4vaMjVNTkWP/wKLpWaI769fS/wEtZwaY3V4zjTp6tUktfX7nXtg
bVsdyxFuOqVPw0gW2tK6Lil906mDWdls8rbgLyTp0yuPbpzJRtvii5PaMt86msdKQ11YEq44
qhey01BnlLXD3roUdtoeDGvsP45WbeuobzgNAAAAAElFTkSuQmCC</binary>
 <binary id="img_119.png" content-type="image/png">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
SUVORK5CYII=</binary>
 <binary id="img_120.png" content-type="image/png">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</binary>
 <binary id="img_121.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAeAAAADZAQMAAADCGHEHAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADRklEQVR4nO3bT08TQRQA8MEeiomGk4kHCSTeDYQDiUow
8dj4KTxwVLqAIjFTExMgeOCmCWoPHPkQ7VEP6JUETb31grSJJmC62+fuwv6defM63ULbMC9N
04H325lddt/ObhcGGYIB/Gu6bzZjOe99e0wLNxlzcZWxEbfFmAKfZf7yM0teJoMTgDZAzR0B
AAdo4Pgs001w/GbD4G4xaOJKFpyp56zrXO8bHoQNZrDBJG6A000xiJdy7aLvLwX8XoMBoZHK
NNhgg2V4kqUDk6V04ghjo/6H5+yWTWChF0kqx7C4SgYPMUZjsDE3+BIxGgb3HjPG5db2Lu3V
2PYvdGTxB+z0YtO4hU4gvwenKhyf+C9Z1MQxSXBLjis0bqAYxBWSYCR4Rxg5wV80Pl5bR/CL
g4ckXs19kmNr4/YzCk+vjSN455DseXrmpxwvbn6gcQ7By6MPyGFfn60huDBP4q8W0vNS4T2N
V5GeVw4PKKzaSZyL3j2R6Awjh2QHGFTFgMKZKkkrSw1z/DIpC3fM8wRuMzYhx83SFCewuPj4
oEiMhsEG9wRXDDbYYMlMf17eOPVutKqxHT/dJBp18WJPeaJLNGqaM/30tL9G45a8UdGcVqST
tWZDiQYfZHzcyo/HG3Nhw/oyQWKr+CRsHFnXbkZ48zM5cbWKv2O4uB82Frd+kHgmt/+XB/ip
FV5w8MWNOyTezX8M70zeuJt/F3x+tDx6n8R7C9E2OtpbiNZhqbBN4q3i66ixE9sAKwV6nQ9f
Rhv4aKcY/d1WCt+0ZvrDs3viGPQn61rFQFlJagRWFsAygRPV1omPtK4502+zyahxKt6FJ043
8b60z1Xc4GHB8TB4UHH4ldzEZePzTxEx2GCDO8PCN+nB4prR4y7nqWJ4OO89WDMH/N6snfgi
3UlMY6rslaSfsv87Lg7NSRelSjKzvzj1o77jRixdgcsY7n6DdYjlw4ZL6HlI8XbJPQCr3gG4
/lYTu/NoxqOH1fI4DjJH3oSPtbX8ZYWpJzhOZTbUGJS4rsCQEXc/bBL3r+cLw0LR1+w5Fb3E
9E4ykPgqrvNVxL04MNrcu4USDEZn2FjRF0PIZGBPc2+R/nirU6oHoYNoP/b/W2YM/gPU55MK
hibaNQAAAABJRU5ErkJggg==</binary>
 <binary id="img_122.png" content-type="image/png">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
wNMxAAAAAElFTkSuQmCC</binary>
 <binary id="img_123.png" content-type="image/png">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
</binary>
 <binary id="img_124.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAQ4AAABUAQMAAABJHUvDAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADv0lEQVR4nM3Vb0gbZxwH8KsOV6hbskJtamB21K5/ZnGg
L5TNHsw3s3nh6yJUJUwK1toVqtgYj1Ur+KZhtaPtjGQr9IUvVquvxqRNMcVObE1poSBWoj27
TMJx1us8s+dyvz7Pc0mb5P5BYbAjHEf48Ps99/vePceA3aEw/xEJh1c5OxKatyTJoprBT2KW
ZJ2pHnBGLUmcafvxcoi1JAWfT1y1Jn8VXpm4areWL2/vtib4H+CslwvwCuC+DcHHKtgS+B8Q
lSFHLz0XGhPlF4ZpYRgrkqLnOD0jKyJrRIqZkGvekHyXkpfjJqTF0Sg3cIQIncZE3bwlyu2U
pMzI60lxcwclkgmBrj8aZR8lcTNS1O+QtSpipelNi9o1WgmYkrI0efuvnmyxVkRhmFKGcVnG
qGgRuj77ASSLRiRG1j7GMBld0Httn+I1ijEgh7op6ThyWlDXjGP0eCj5/re2JVXQk/O3xM32
44SgjrEuI6KexzEWR2iV7V5MeD05M9Uo98Yo6UngRqK+UUF/QC7mNSLg5RoQgKh2jSS/cIlX
Ot8zxnX2HVE/zCfpiLJiDOjIyvqZAnFbyK4R2K8l/WQiKcAX+4OcnjjrHTN/85RUCj3CiL6K
+t0JEJuaKOkWfIbknzZRbKonBB1YcycWjMhZURxpplUc/FeJ6wbknM8hTmmETfgkg0awqz7K
/64tlxN6qowIzkj1QZoUd4cMye0+29FtcWmSfi3tYtQT+ktaNXqUlaQJWUKQimMVS5L9RV0B
iWx7OWRUglSUNCJEUDuRfDi/yqjk8t9vrYXHrR+FkZDC5KCObGz3z+7dA08aylm0kvIi+ZBu
LRs76uYrFuDp8SJOq/JtPnmOyZ+UtGGSrETysWyixEgVT90sIQ0fsEhA40jO3FEIB/dvDX4f
RpHbT6ostg6FET8Circ9TEicDale4GvxU32Hjk4/3WlXkfObsgghX+cS+i4SMv80UFLaPFwb
B7XXJMYXDy6VlMJUFY9JpgrKbbQeGXy2E4ar8F5aZ0KmnTudBdBPlyuRGJMq/l6RjYxVUg5J
gSATr0OuCESSmCxpMaZJH5zs4BJJ7lRmdFtRQnCMe/cMTrsvDrgx+WI/J6AMWeaUAJlug3+2
/PrkZc9P5SPSp7DvZhZ5F+PDioULF+YuMovSUaj/tSyfBDfmSEaTP8/drF7EjTxjuipBXAWT
G7jKACWHuLVkHgG3XxlfCMygR1FyR6c6PuaD3cFckj86FX93ch6phxzULG8tb1saAvJV4zPT
zSKruTHeS5M34F2xlQmqSCYAAAAASUVORK5CYII=</binary>
 <binary id="img_125.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAeIAAADYAQMAAAANsXKfAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAKqElEQVR4nO3af2wT1x0AcIcEHNSwC2USRIFdmEK7SisN
gUG6sl4tNEHURvtjf6UaKmhAisqWjsJY1DSXxFa8arQJVCspv5w/WP+atAiqjFKE7Xhagkhx
BH+sURl2GhYXNYsdzPC5Pr/v3vnH+b13P3Jx/tim9f1z9sUfv/fu3n3f9z3HBosoPpv6Mi4w
f0t7FqAfiOpLZxF6cqjakX35HKQU3bC717KOzQzcOJZ9KZbX9PAgH27c0Gldd5bndIfjwsUg
yG84yj6w3PJDfbfyumRgSRDSmx21VVZ1vOLkqpxuOHihHuv3mjZYrjvBD1XntDPT7/RI0zs5
PTWvJkqw/82bQuZVKnvi7YVoSAKi3p9dkGaLd2R+HTXW3dmL/5klHcoe4vn3Htv4Dg4frxel
79iGG5/Bx9tuOZ09g3jr2t/nc7Tg48hHo032zBk5yOi0sY67blcpYyHwUfdyT/bDs23v1pE6
ZaxBrszUfX3Q5shpqcb1+AuEjhd0UKPTU8pVm6jb0ZTTyVrXMlInTDQePCI+3KnblddfbrRZ
1pDVgNK5XsnXL/R1kzqK9YiBjgBTUHmNq9LHaKdVDc/v7dnE6o8ta3wqweopjRaMdFSrv+AZ
jbInEvkPeckvoDSu+PAoo+WgkRYZfR+gvlwgdEzIv1RbbqyjI+D63XqybjdAlWWdAHlVZizl
tQTJPssaOLT6JTuhgU+e/4lljQD97IigPmMJCMi2HVa1iATwH/EQT6gEBz6wqGcBf9q5F8jn
m4+d5q3pmSBuunc5EC2HEXSas6b/iZ+ROUgLZN0SXJyxpu9fURqJRCoy8Q++FGgdM9AXfbnn
hNABOcDoewZ6yxg+5QHqGZPaV62nxzky6PfDT51ZST7f6+IraC0ZaNR8kgckRKiIHJF2Exp/
veTU05Eoql6yDn/9FKUl5Z2qL4swls3NNBpSW3wiyHsojYRJsuV2GOvm9TW4Ak0AYZHU4I6T
OgaT9aN6/cZ6rL9JhLBA6TkgNQ5VZeOivpbqHcfAx1NaFqn7PQWu2z/X18npExtQFzHOldJL
aVlI/61EXwOcWTp7jNFT9P12w6kqzkB/veu1BKNlZpyL4XXPaHQwo9FXL25jNPB0RObCn4ka
HcrOofcr2ZZDIEHpyeiwoZZXarQUp55vWUCtRhrix+jMA4fKTfRc4gbtVfPmNGqhdR1AJx1b
MvGB0lFV4+ezoGWhBwe6cAulEUfrcKcTxLwOEc93su9lZeQ/SWmYpPXNY88iVXsInfrLcAX+
mJ3WssDoNpnIFQk9M7oPd5TROEh4Ce2bfqtQN6mT036lk25GS6KH0BPTbUDoEKHx3JIOaOK5
vZfQ56JtIOhpJXeQf6WdDSbdhD4bLS3cMVIrN2e7mNBkHqhUJO93EDwGuaKbR5u1eYtN1B+p
QOfIUzg4fK6dx6xpJfY/PKfRiNVRrU4AehMHjAfvaOtOzq8jsFYE9PLhCq2Oz69HcKI5u7p5
yfe1OjK/LsVXrPOWo2ajVgfn1Q9KAfr6d60oaddqcqTq61ip997Wqsee7pW1WhC18xhTd8nl
QN3EU3/SyVuQaKzzYy09WHXphVbQ0Um9OZSuOw6X1g3uBD0dt6Qbz0V1dcREq0/JJ+tBXwf1
5zFan1GrYrTHig4ZacFE51BCDZSsxgmlBZ2PCqxOGszfpNZZzXnVvyxCRyxoNZoxOq5c1qLr
jiipsLE2XsVmdEBZnRatq5QVQLEaVeAbVrw+HjfVIXNtjyxCf/FkcBEt93MeK9pgtIzyQl53
mbTcQH+3HfL6RyZav+Xo9WRBu2pc2zJbb1ZbLm+KF/QPLtW+36xEXbOdIlI7hUhBH3nu6LiD
09X6dW/yBAl95uitZkWnTDSZebwV8hZ0bXXt6szWWzKaXcHMo1E7Egv6uHB8206l358qqxoh
85mQWd3tMqgaH3PblP4o5CNPTheuGqm3J/W0r1VZNpIt199n6kpQGpQUSCn4uJfUMZy0ZLZb
qGseCLI6U/dGvPbZ3kvWzeU3Walsz6On/VE/xG1OUuMsntNqoaDnIudhOrOcD0ZlQWrcyhNa
FtPHNTp7w7I6NlxR1+3LnIjipN2/ZQKIO8bJoyKrszcsqx/1Hdwau5vTkji3X1ke53QcpmSu
hdVxUnfvqR1ek9Ngh3/8tYWoW4JKB6sjpD598I+XTuZ1DKRTOwiN+AdNrA6SerjC5hzPa+Ck
rdXUPlOsiWe0h9Aw2+HLfZuiY6mYhxypMWhmd6lEUiPxLhQ0XkmcoVYWwucttM5dcna0ZIfV
pLKXXNDg/nqfSLU8aaaVZR6p59Begao7bqbx9ErFNfS8stdP6EjKTEsCHRXfneiktCdsppVV
I6nj6R5KC9OmOiZGyLgmo43UXo8om2qwT1FRUQnfat29Mki9pjo8SemRGFH3QBLuuE012Cg9
lyT0NTuMfSiYah+fPRZ2qQr99oOvzLMADTzR70tnhXjtuKnupPUIqQU3LD3ZaqZjr2SPufbO
EXoAx5/0n3eYaXeI0niKIDSOPx9W8cZapncGle05VXeK+JEPr+GM9SQ7j00VrnmngENAuL/V
UOOpi9FSSq1bSZImo8PG/Y5p5lD0bGGkZnqmzG4G2q7NW3x0juxWhoC+xhOnJneI0XtcykcM
tDINMi2HGLPHZTfSSmjR6DCzSzVnpJWwpuk3q5WMQk/LvBJUWS2xO2RhaodM1fgBFDXaZ2P6
jSth9x0ULXPwnkbbSrWrWL9bR4ch5mRaHrbxOmtguVSjo8Ate7zES/9moWCdX1tKNDoY63i9
bjmh4zgVy7yaf5cKopwdtX1SCgUdUJ4pi3rWJqLkZZHQThsfgBin13I/q2fsgNr6gdA2Dt7u
rPHoXTWO1WEOaw+hY7gXV1fUhHS0XzPWlPFYCqQuweF/rCaos+/Aa0eqM7/tSmSa5w8t09Fh
k5hK3u/zh/qiGo1MoiKlx6YF0GgfWNTZTIHWmZTfkgYdrVRdtJY4tAjdlfnhoEgt8bC8eB2A
6ZeK1aK0du2SiiL1NdGZ7Ly1r0h99TdCasbHFakvl0Jq+rqnSD0gYK382FKUxpEqeVO0pHXW
/jidknkoVntD+XVyMS33qqeKqvv/QS/uqv1HdOh/tuXf6G/0f7WWucxRyuvUQjRIWSWrJxC3
AJ28OXW2DaVCoqpHFqJHS8prrty4VdBGv1Hp6htDDtfJXeMF/UhYkF7e112+sqAfdkYXog8O
nr5B6H9NuKot69Ft5U9cGf0pqet8r1jUgPI70mrLJ2yB3WLamgZ15y5XHlXVjTYeRaJFzZaU
q9Kx4b6F/5XUL2Obm8sOCurbq9xXC9Ey7N5XXtDXuMx2oa1csKQB0vt+W9DQ8IuP57wostkD
Wh0/KxBdy1f/PeLUDxtX3hv8TuSNoI6eLKsLcMCWNKEbHCcqT3Vwujq5f2hNq2k/Nv+45N5I
R1BXzy0dyu6Gq8XD6Iamp8IjHZEGXkdL+4f66brLS7X1BzsS7YKOnlw6SPcbHbiuvQ69uaPm
mp+ai3gp/Wv/HkFbu74ms6xsOeBbZoQt6G+N/nIBmi13/U9Q74cfe/r9m5Y1rp16d/q12t/3
tHxQxlvUdDn/au2J+p1drlBR+vSrF07U27vKitO++wPf7nH/wVacVgoe438vXqvt+Delzqq7
N1WdGAAAAABJRU5ErkJggg==</binary>
 <binary id="img_126.png" content-type="image/png">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
q5//f4eS9+HlxQmeAAAAAElFTkSuQmCC</binary>
 <binary id="img_127.png" content-type="image/png">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</binary>
 <binary id="img_128.png" content-type="image/png">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
qgAAAABJRU5ErkJggg==</binary>
 <binary id="img_129.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAMcAAABqAQMAAAAySde1AAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADnElEQVR4nNWWX2gURxzHt+ZExYhHFY1QmpwktojnaYlG
iGUJrWCMvlU0T+VAxT+JPhjrgySTmCAU2kSu4ltJS6WgqCkpClXjagLNQ4me/70H3ZPEbMt5
N4lHsnfu7f6c2bu9/U2SowpS6Q92b4fPfGe+v9/Mza4EBcKS/o3MsUgBItskMwPZGC4Jplq9
NQv8Y8dPdsiIzHpYVz7ZcK++Yt9o7+WhbkQ+O/33ar3hUdBzk14p21qKSPX6Awm94V5txabR
3ipBU7RmbvnLF+Ga2R42zxDWdG34KJimd7SuePpWJIMdWC2EAiRncG0B0Letzv+NFI4jsa8u
bLm05cHK7ft3nLv/h+xoUhIBExRbrsKgoSoukRkBMOwW1UHGJH3UaWmANcTW8FBZuWMCadZ1
aOGLoN4GEOZJq0DA4EqNJgXNBCXsSYarKlUNwfU/oKgQUy3VGoQWYbQJeAp8QIAemOIalB3d
LBnuTXBgmE7TpLqCybDJU8nmk8SEDOQIu+izbqyJTfTo1GBzsGu8BztIMk3C5CU1tKigYYTo
+3XK9mmsKYDnSZoWvJrgGksbXYpIKdO0sAEPAjUTerWYD9vufdwc0VCmGUkm2fVhphUKVKgb
J0zGKkdBR64Dah/rns1Wg0mhoqZi2ZXm2Sp5kuHka9scWw1tymqb2ZYZ47tKIGmglm7wLfcc
dDEf2xeP4WkrZ8d17s1EmQbMuOG0RAdTYha/fSn5bkgS73hMV8LRGyQVz0BCap0TzXbhJB+m
+8j+jYOIpN6QtCMyRt6MtGEiY/IBIlGBSAUJPgvFfN4/KRD/IcGrECX8nsqRIvbjrKtGsKa6
29XQwx0lA82Xc+RYcdm13x3SWHm6dvcpktPsPvPjLw7Z+nm4fvZihyxo7Wh3yaOI50OHNP/6
2w/50S7dXbYr5JDisrb8PMFvH7cdmXQyfeJa5dHn1sCEmeIdVjRDMBFezbbmu2CnPJD7DBmX
fP0lazvj7MXByApfufdm8SeBRkZGImdCf/kra2MyJ9/Eq0I79y0P1DKSiFzsn+/3bN6bI03e
yNzlvposCdX5Ns771B5tT3xdqH7eT6s25Uarq6j0n7VJZ9zjXdbg923jDiI//7noQtf5RiXr
WkZ+82fANEIF8vZ1wyfFGJQishAdHJp7ijHyMTLw7ESYuMSz5Is8Gen9HmmOnjqUJ4l+TJq8
iIS2IbI25JKRXkw6Frlk/MQQItO+KvOkQFivASEpHXqTDn+rAAAAAElFTkSuQmCC</binary>
 <binary id="img_130.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAQoAAABnAQMAAADCGJ66AAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAE7UlEQVR4nNXWf0wbVRwA8BqrsACj/mGAEEOnc8aEADOi
iywrZEbH/lBDjMqWgQkwIhrqMnRbynpzMJQsSsKS4UAHjgEziuAmwwG3B+JScfzKFJExaLfC
DuhoC6S+luu7r1fWXm/cFSSZib6E8B58+v1+3/f1Xk4Bq41OxT8mJjCuRhgYOE05oWM/QCHY
B2RJ5YOh21RH121ovKxKU30sm2hXT87pvkrtxtKtwy+NLMiR22k3HhqfrZza8PlW/OK6ItlE
e0c8USyx5zqGX3ddkE20l6/lscpg9Vd8LVGyUeZsQW5Kw2lYGijQyG4aBgYIALdSXwJ3bi0H
8F8n2EdMioDjAS+xALSiTDLPf2qBkFYz7K/2hSFewgB0ACwsAE0xcxdfZrgkGUJpABAiRmgd
aQGmCSTESpAGOKTHNtARboh57QXjcjL5Dr4MjXqamwVyiGax+lFJFFtEE03SkJbshndJXsut
nE+ktewcRuONbzTuQhw55PgMa4ckxDJkQbPXkI5QNHWY/cB8rFuaiBtC3NcZLAUc5SQsNlyR
JiKAbL9YIIwnrqgxg+kRudYh7MIZ1SyxESCMXbpphqMQwCTaNsvBwRtmzFHSWtxaRMNsRoUV
OMIdYOwaaSLnHqRryicUWLDT/QSujZFG+YtCKKNVbxwFltypMBuOS6N892a8mc4EKwY87Tbg
phGZvmgRBpTpYMHmchuYiSoZQqFpzLJWTBtdbsY0HxkjIXpAcAFqp5h5mO64hZ2J0ijQhFjK
YZmcwVTjNzjXTaQkH/gzTmv4tU6Lp1oWP6yR2TSxaFr16BVHPtbTi44sU7h003qSdIAev7LT
kUcOtsxgq1FCrHy5tL6VcuThxTFw1E6UGaWb9jQFwErAWKhbZCZkagH+4roEY3NggTFylTFF
yBLtOIBtHFN9/NU5B5JEnq/U8NJSB05ixUj6qPFRnrMBvQgzkE9Yh9nISqLI3AzJy24Gu0Ll
/6deJaY+4g3nHUiYMb4JT5xiEiPMzGsjdpHg/KRbTCgRCRemXSJiEhG3n3wkJhoRCRKmR0Sk
U0ScfqIQkQB9wf9fAmskMuPfJuJaTKK/e09aA94vQ/Vd0knxbxVeQmxLvxD/84Xv7YIn/dR6
IQq5s7koMu6i0cNKgvbocr1ke+xT3/rI9NnynrgznvjovCE16VUvSc9pOOcjloa2EPUSqf7R
lBIS7iUpBeoaEWHVRzM9tZzvTc2K95LMnPozQqJNbdtjyz2kqyQmPUTrqyVUXS+Uuy9JFdfm
KZcpE7eOurdnCISdByDLx304RvsWUyDiq9X5zNvJfuJepr7v39IfwzFNPDGvzypzmsfYJaZW
JnQrE76kc6FZXawoqeCY2zzpinxaqUoJvTqq4ftyYuPJ3Tn1p3jyQyFT9fhvHOOpZaw9dKLi
rezS6zxhRxP/3J6tLv00CH5//+fohwf1TMNdUhyRrAyO5ImrKvFaavamuroI6I1rqWr+Q4/j
edLTHvbs8I73QqI8idoLyndkNx+rex56a4qjjwzq7VqedLWnKIPSw4JTecLpCjb/dPiSowyD
8Iq+1BfxC7vwbFavTuAesvII/EyLyU3R0q1h5IiN06xQyRLpLvPf386E6+EypOfJU8LSdfKE
pBrPAUxG+8lgkSxR+gkpOS5H+rKrhKVbHS1HbipHhSW3z7hc3K+L7G+0n+ClvC5tsQAAAABJ
RU5ErkJggg==</binary>
 <binary id="img_131.png" content-type="image/png">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
tSQAAAAASUVORK5CYII=</binary>
 <binary id="img_132.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAXsAAAEYAQMAAACHkMS8AAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAEBUlEQVR4nO3aX2gcRRgA8A1S82BC+iBt1dajT0JfjKXU
aiHXQhAEgyC+VCootUIfQkIbYyRxp6TlfLFUFEpFcuuLPpdSIxjoHyipYPEOCwvV7G2OhZZy
2duWQjahN/N1b/d2b/du7jIf9uSwM3B3k2F/933759u5u4kC9UZd4r8qSo/3/Csk29yy5j0v
K9HAHaXeenzGbxH4k8RieY/Yn7F2tQ4uew8TD0wTBV6CFxZJElyDdGvA8swwrySBAeBADhgX
UJ0WzHQA0iHI5Gd3Zj75nXBBgeYLtQgDIdg7d3FlfviPdIsIephSTwie3TFbXhz+mgvYN0zX
Na+/5p2gKMKHXoTnn+MCUGFfOZ2McDJjzBZ//mkXH9yv9WP7cDoHOUuDNe5RAjgVgfAoVYed
aKMmUI8QP9NoEGv/CiTrQduwHi7H3pJWrFe5AeIVl0iJOWNCwGQECaIIlhBgm2HqqwBQIUB1
WP0OBQowvhCAUdEIr1/xQeWIJgTy4AaAv3UTYC/XLmNREN0bNgQVV/PBuiYGKC2ZjQXUFqzP
hUAwpeIIba6HdmDJ9m6IQxvNDwlAgalk2b3+kXL01qkgwpvvK8OZL7S++xxg2I4JQw0pkYZk
WAzc7q/ttCkILNvlgLOtARyGVFAP7LU6eKUNqLZqBJ2xEKRUdfNvVy8Nrn2c2WdZmtWncgAt
sPEQ9Kpk9wcLl85NH73496F7/ce+H+IBHeZD0KOSyff2/7I0M3ph64PcxMEsF+RB9/vV+UGd
nnxr/5m/pkYvuNdyE2+f5wH2IhuMIpATk58u3Dx3fHT+1iH7s5EfeCBeDwOkd7z8z83BqQOZ
bQeL/Tu5EWA9AsybH1ynOun6s2SrwxqvBw/kYM47mW0BxEGyPQEwFmxCvMFqPaQTUxUPNBTQ
yTYF1JwSewj81ny7z/mg4ggDJ4ggBtg29obzpQ8sMaAzWv7WB7YQ8OphzdZ90OrO1DShzK9s
wQAddHvVByWxfTjP9pQD8LkQ8E7/tP+pU3hCeVDrCwPR+aEO0BFQgCBBpqEeNgbolCSQQAIJ
niZAK3dTOPDIRX5/GEAC410kWHJck6mdBMYYMqV7Zx/iAKyAhgMp6LZrSQIJJPjvAem+lLoP
ZLHfH+4QKFFY/3HTnj5lkwhgsWGhlCSQ4IkB0n0pSSCBBBL8P8CqiY2ABtiUJJBAAgmEAek4
QKckgQQtAOk46HhKi9hfHbLYlLrwp5ZuBKTjAJ0SGqBT6jyIDUvQEpDuS6lrAK1YWUu7K774
RWlp5hFBrMcVBwyGAvaYoboYsOQYWTeNWFM0bKPszGDAiAeIK35Yi07pnVwKswi54v/LKQKk
al15eUsggQRPH0CvKd5QlGeCHtve2xo8BoQAasdAfV85AAAAAElFTkSuQmCC</binary>
 <binary id="img_133.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAU0AAAB5AQMAAACHouGIAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAETklEQVR4nO3W329TVRwA8KpjbqEZ/khgBPXSWOIDTCc/
KkrIdfaBEP8AMvuwNYBgBNclMhoauIMNpw9QU8wC4aEuJvpKtqhNJpSlhGWsdQTIXrbmrt6H
xhhcSR29zb27h3Pa3l+He+45xkRfvMl2z9399Ox77vneb78ewHrInn+JVpPslGenESU5xkbl
gfufTLDSe54djAEE7kRZ6fezYcYAlC8WWhkp6fif/hN609M4BCqd0888lYr6IP3f0Wl2epad
Xv8bdNj3BisNZOOsdCCbJNJ1OCXOqh0xrpOINhd+INK/3p709p+XJQBGIAUXy4BEVe3Q5H5/
LrEfJpao3yHMqkWHPntzcUMXzEBOvxN3po97zx1FFIDBHv3Od87042Pjs/78KAzgrBFAypGC
tt5Uiy//dQb+2xpV4M8N3pHClRWL9cUYNI0Ha+7Wcv0EafvtEszDtESkQKda5aOFSUhlFhoK
IarxTlTG6B5EjZfXRqs4XUD00VP0c1/bwO0Xi1LbbqmxrEwKLQuoAk53TfjXBwNDM/6XMnUK
RGTSAHA4Hav0f8NFrk61zlcbdLFBp3H6VaX/LheZnHpmfgrY68AjnF74+XAiNLBpZstoN0Y1
AaOL73gDwUhmyru5C6MgiVERND78p4DTIkY5nS6js1FfeWDbGkLVLusDtLeqxzjcKHpr9J1c
XXKnZcumO9KCMVLrac5C4ZrdZ71pDucos1pomZ2qZ9zpoGCOX4u5UsUyvvWuKy1bxqVN7nRV
NOqCvJaNCnU64hLr5WD24DrJ+9srMIDnANjhMuvx3NZYn/hpLCbUAo+1h4d5Aj2d2/16nxg7
iKgEtDOzHX6RSKvLfWI0JEN6BdK1Y00kejyv/A5nXYAPWINbcOqPjp0kuu20Go5L0eCXAqjC
zHq2JdyUJFDBHBfhzCPtUpawLMVCkyhfFZTj5ByQefRbE6r6H2g5UKW8BRZapKS2hcYps5r5
CmuGcVFypzKtDpgBSDRqzhqnlQxbeWOkqGgyxiqxU/TlLRPrq61q89Y7rk2p/bvblZbZ6Rw7
tYXqSlVApXpXXKbT67VGgNBlYHTY9xaiPAMN5GBDewNrMkhdMQfAL1jrQuq14aypaRbaXICx
pjh3KiKankOP6RrvSJ/XLzlzC751pJreTagWOugYgKb6JlrC5y9nlix0CDhRVTs2/qE/n+i2
9tppR6ppkXNZ1GuXGrOmyfTxocQMpN0aZ++IHGhz77WWzflEF2ysIC0d6CRSkJRS3rG83Og0
V06Okynsd5bNXntl309u1NqWr/RQKDDpAWa6uq9CpLKd1g8HGvBdFCocyoKoRKE7L/nXf/BC
prDX25mh0B+VEw96tm98uLdjtEqhF5QTd3s6Xy2saZ2nzZq7uutSKLDx4ZbDo1cAVjQx+use
78vBzmBhTVOt1156H9Xc1VPvCU9TsVYa0JPs7cPvYtRoteu9thtNA3dh0id8UVvLwtOHFgAA
AABJRU5ErkJggg==</binary>
 <binary id="img_134.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAANUAAACOAQMAAACcx9/0AAAACXBIWXMAAA7FAAAOxQFHbOz/
AAAABlBMVEUAAAD///+l2Z/dAAADSElEQVR4nO3Wb0gTYRwH8NusmGw2e7exiRCTehPMAnUE
TXozZQm9kyFFUl0vilpIliy6cC80iIwECX2xoH/v+rNACYyRNzZB2MrEUGkGwqYpW7rcn3Z3
T3dL593tfoehFFQPjL348Nxzv+d5vscPQ+DIYJuwbqxoqNZNXTxvdN1cxTb/S0bAxlTCRmmq
w5Blq0w9oJ0d9oBW9gA0qvQRz2gfwy5fqMHCn0cTzGoCqJ26zERBWxqMw/YsnqCuSZ4R3RqK
gvY8BL4L/TKyDBlCESJvCULCjhCznEWljBuspcb1IzqSIHVHDcf3uIX21f+u96CDuDeG25vx
AaFlw8Y5p0PnVUe/2HC9yPxGt3OqwZuLzgfFlpvo6203aL2N+LEPeK/Q5sZ1bueAtsR40Tjj
7JK8ny7E0Ah5gLvLZGET1y6RlfnNZMyC2Cvb39mMfOvTfA3bljE5s8JGlWXhjJnMcMaqhj2w
vQCNKnvCMzZjbWDGXEwutmG5V7waqCuDfNsYnMVl7FQgF2NzpGRQ5gaBUOyhFfmy1Yr6bna9
SyF2HpCxp5EcmKP3chk7ae0BM+b7uS8xKUNoLWO6Tt1bws1lrJwUfpNXApPJGjZjdtxuw2eE
8zIhI2l2qNiMLRRljPZHBmqnWr2L0aUgbhDlb0LpNldolaVsxs6IMpaM1IUznsoSI2OcWQYz
JspRB68wsVmAPePGrWIr1J6RyVhWZl5OZj2JeVs2ufV+97v8DfbH9pMekjHf9q/33/4p475n
B25iWEtwcv9udNpfju0q9AWjMt+6UplndmzVfLAxWthok7kLsuzjPg9s90GjdnpBY/T8fgIt
I2t4o4bXvD7kG0rfuRuTrn01nE58Sktbsu8jbN6hxJu0dM+Q7BxKuNJUfp7p8F7uL8n+PhPs
ka76auM54Jkpz9WldN4SRYaCbQFr3iR6Boq9YkHOUoQlWkEicpjwfNfHhHu2Mq4bMTcSIyql
upzrJ/iWCYaSB5uIaXvqvA13CC0b0JDmJtW0OrVg2ycyeiJkOHTiXP9izQUSbxFachxz17H9
hKZdM2YmxedHcCVaEHMdoVmoZ+iAbe2jv6l7XcceiUKJ1WNOTIHtEObodtHxKQomMTI/APNG
FTajylj6AAAAAElFTkSuQmCC</binary>
 <binary id="img_135.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAd8AAACrAQMAAAAdCgR4AAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAHh0lEQVR4nO3af1AUVRwAcEgbrjrCGWccFOEgU8eRSUxH
dNA2csbEPKamCSJShoDIwR85VuigLhIzToXSWJrhj+vX4B+NhpBng8KJVCh24o+ZFBRWPGtV
Dha7yb1rd+/13u4d7t69271WM21c5+Cx9z73ffv2fXffuzUC6N+8EWFVGyS1cSR8VeOqXQoD
z4KvibhqzKoYIGjhqqQppVicb1hySQtPsMfg8bId45xa+L1fTHhs3vF1vzYOEdm8Y5xdC0/o
nYLvsPztk7o18JzNDgZ7qoCLx+wMc5Dgt/8NvoKpxZM2APabVHHvLrjjJInBqRAfpFRxP8IU
BgsbNk3dnKqOncbEzSlY/GXl3r0auHfnnolP4jCf+r1zw54OVezYdXD4UxYCg9kDvUkamNl1
cM4+C67Z7AFmvEaHMVs/m45ttnDjwG+VGsfM2G34DgMc/MeqY3HrwWG0uSza+McQFngIbfwP
tlu4POLWRsqrDMreiFLFkeJPRQNZWZkOgW2yvfKyAsvLciyPdnexHDzAD/A9hon/DMvBnY3s
JrRxbyjssWljO+OxYDFX1zhuowpGwF5xykhgsXXL8YlauPHcbzYsrs26oYZRxO7GkcPxkWvn
tURp4YYZhVjsmvCzZrPLN6Y+bMHhwciBlhUaeHc1MVRWYLatY6gcCjc3k3gMWIsmBs2ysgJj
ync0n6lQmLsd7NLE/SCKL10FZ6mBWNh2xBF9bGmDSQU7rcMeeSLJeiwYj/oko/BEcj2lgq9+
37XkqznWj4JxSkP8O1XJX+Wp4Evl56+VJ1kNQdhtbYgvfCz5wxgVfKW+a8kX0daMIMxbt2RE
T06sUYvsrB9mGB9RMTe42TePOOJXv90Y+pgJ+Kr2ANYFsKeKBF6gcZ490ngIwhzE/nIojCvf
4/cqObDowN71Y1tNxbjIGytGPHdKHfMJY557tBWHU7fYa+pJVezZffbNY8dxeFpNf2OKBu45
W3h1Qwhc84QKJuAiBkZu78ThqBrzmTPqkflnzr85Zi4OVx+KbzsP7vVB8gDf05i4P7Ec3N3I
9ymWAwUO/zuDiODvDMLBYTfbHSamgGOnEnexlNsUgMc6lMdckkdKmFkcgM9RoEOO4S2/rAzh
wwHYfcHCLEeY9X8y2w4xLce/X6QkvGsITxYxl12yYjmq6yJC4q60XAl3uosNBdEIfyvhTGY5
gyJzCwzPGlpIHO4pK5HwidxhS4+PR7jPh40Mg+oKb7TnNJgRPnNONiVC+OLMYohhyxwJ06pa
CmEd2wkJZ/HMClSXp435dYtisDiNljAb+7TRjlYo/si5ixlxssofzcxoWFSMentEqxK3v1q6
mkOYfjl24fEiC8S8A51MNxAYHtUVrhvS66Kj/EsGOW4DjMmBcMf8ccbL7xBDg4QDN2gB1fWA
ZPpwtwmHKfHBh/9UAqCVVYreDgZ3F0eEnZJsMC6P+gm++xLgQX+vArOyptx+s4Mi08p8LskT
f8OUnB2ckoGRZ7sCMenDzcqUxGFSHGGBKenOi1shYtcQbg8c262vU2vFy1Cnf78/n5/vI5tR
II4ce9nYC3ex188FHHNXWsl6TkzJ+QWGgocRfsGfkkCM7FlgSI/NwOLu1QjDyI64NGdnkRyv
lY6Zf/7kynqzCbo/ApvdnUZLkenYacYWMSV9ONuP2x5Pr1sU5cO0l5DhOFbCbNzMpZSYkm/N
NaEO4+KZBCmfS+CVBOUzSGkDNF8t67DzpWvLpJR8yHhEkZJ/OtziMbcb0n+Ij/EPEsG5EnQM
+lOSMYmDhHLThAOugG9htMjCjLCqxO2Vp3b6MLqM22R15GUc3vTNB9M7d+oc2/1ffDB89Ibw
b7GRSbdSUrDXlk/v3K/rFsvyo+sqKzvL9V5JBPGvO5LPdw0PEreB4dreoRuz6CajF9PeMlsK
Gs4DejCfWPFiRa55TaaOGSDt2VcjbDlhnrVbB2Y9Az8I5Yb0SH14X6O93mWemqCn2Xzi0fiK
kebI5eFiqwwDDwMvAWTg9NH7SSh8U9Zs/zt65tuYu+T9gP/DY+ZTcpZdI3RiYVtOkZPUeczC
9nRjMtCZz4I1p6iK1Ik9H+cUTtLZbNo9Gqwt0dtsjpL+0jN9DHGevb6wGpGD8T9othILMK4c
HP61SfztphOKlXjWd6QCU6BtZgGlaK0fc+wAUE4f5yixQNkoWxmlWNGJ2Pva5T7UC4rpoxiZ
T6mYvlHEf2Xn5tlKRdzJs5+XxiCchfC1C7lsDyeu6NKMjhj0JIzNRlioXbi1QcTcvL68Zpby
Tx/noulj0yiE+1pz2SYRCyXz4fSRgPgViFlhz7LTrRLOfBQ0MSJ2mHzTx6ZtYmRqB7tOxHzx
yTX1ZoSLRFy77LQvciYPDkl4aPoo4S7q0E1OxF4aTh/NUT4ME8P56fsSzloHGiBGnzty5tLu
IvgpTacrCeAd4zh0OU6KfN0/ffTmV4MOYf/AjGoRuy/Eg1hABU8fYUevRrdxNH28iqaPvuWg
/zGjGBmu+dDgFHex0hs+7MWPsKENjTDpqQ4h22uTV/nXv6FJk4lA3PNuD3pCBzjlfx7Tk88Y
rGPz/g2DtLnMq5JRjQAAAABJRU5ErkJggg==</binary>
 <binary id="img_136.png" content-type="image/png">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
AAAASUVORK5CYII=</binary>
 <binary id="img_137.png" content-type="image/png">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
NGzh42rC8i8AAAAASUVORK5CYII=</binary>
 <binary id="img_138.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAeIAAABEAQMAAABnFaSnAAAACXBIWXMAAA7EAAAOxgGnHWyZ
AAAABlBMVEUAAAD///+l2Z/dAAAEGUlEQVR4nO2XXWzTVhTHbz9Eg2hxp0koWyZSpLI+kAck
kAgCzUEgqmpUlcbLNqliXTe6SYNSVQLKHsz4SNGkydBJjIKYmfbACwKpbAGBRBosAV0HqZaH
NQTihLQ1VTecEoSTufHdPY7jlFJ3lcMDlXYefK59/fP/3HvOvbaRH1s3D/qffsnUriTjNO9W
gGbnoGXMmdNpQt8wf/pUR5L/YrM5TXvQVXM63TJ2f1sYWv6ZXRJ0Ux50DplZyT9fjn1aGU+a
3lA2p3YSPf/YNWAeGjXnuNWupz1fhc1pZs45x0yG3x0w7c28vrX2mtNFwLh/IdOcTGN1CzMh
1tv06v8EIZsHVZ04qC2F2B7TZeKZcX581sVWSrPj56qXHB2UWLRola3c+Y1ysB/dcyPEwBaD
sUAWBc41wWTdS7oXph2xseEsPFrIlhVFr3+ZZuYdeXZ93GtvsUiTyP/2hRqtzlp2aY2vv8ky
Tf/l42doMypWlAroTsnPKPCs9NgJntLpIZocVaeQpWOlH8C4v37g3oAxLYg63Qm3JeRnmuo6
aRw4tV2nQ5yfnAWFLE6UulOE7oqcua7T+Eg9ofkjbh5or72VkgldWc1TwTz94zsrvMsljrQP
YMjY/lDk+k17izBWo+I+gUQe/uWPjyByX6SxTSSRf37yVDubj/y9AVdftIkjSqpGHwjd2XB6
cJcw6lPxNUIvbfl1aAeh7d5ICROUHncc9SEsCVhtAO1Hiy8iTHH67JFxb7+j3EggYWRZTpsZ
WBsCGhNthpPG8a6Tb7e35SNXBy/6okGhQL8ZUU4PUsLocxV7dxA6UN7zPtC19k11FYRGZNyU
QaNa73LnNLrXodx0rBRGSOQsoyh4BLlFyPcxW4KtkCbw6jdEjjPy3fkzpv1RZ5xJUXERMkYr
4sZOAfLlh9Sre3LVkp3IVYvy5/RqkS8RH/2woXVFZW33i9Xin0etwZVoqHHVxrDrirVVEg1V
XagZdnVbpRs5V+TCDxbp4WauZrjSYuQCbG2q1b2FeBq8ZRq/Yhr/Fx00o4Pz0c7TnEEXZ7gI
W9BfHq+Uzk4x2lezksaMdmFS9Rd65fiUdlXi0zrNGBmHZibNcBody9WyHFfpAi3qKQ9yujYU
SYo0iSI8N5NMtLeCZOLWUD3xidvfDXUb9G1+yU/wjmK9jk3OvPY0evLp5i1k68bxWM+J74nn
Ayt7rxh097cNa9qIv9Rzf2fTLJGPyh1bM6Q58OTRWXho6O6oQzTo873H1kAkddeq97Gz0DG5
ZBH8TQSeHGo+TPzh38sdhcjLhm2X64ivuBreS82gYQqmUm+9C/9BmbG+5ssw7rufRdYVZu23
Bo2WjvfuDWo0DT8ohnY2HdgJ2srkoYegnYqXRwracqLKD7Mm3XLo2tatSPpfGbT5yrDXI/cA
AAAASUVORK5CYII=</binary>
 <binary id="img_139.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAeMAAABJAQMAAAA0SdxHAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADzElEQVR4nO3XXWjTQBwA8Eznx7DSojCcDqowtpeh8xtR
V9QHHU598NGHVfBrVHR9kekGV1dpn7SjlenmR32Q+eo3OtGqRaNsrOpAh5VlpdY6lKX7YDEm
y3l3SbaoyZBG0IkH66V3+93/7vLvpaWgidJLMSb0JVPa84/qB6KDNu4dxTpt3L+Oh6xxrwSQ
9k+gv+y+aTPWVg/1boZxdxtfeuWIcXeOh3pBGRYxKFy9UiMa9udMGFuk6i6H9hnHnoHWbazh
Rb4otMdY25Ge4J48EHdH3MZTA39vrv3d2gRGJ9N/Pdm0x/h0+IXiUMYB+JCE5BREl4LSqh6Z
6uHIaF5xrk2sOV0d09P85NBeE5qRjpuJLV3IRCV31rGbh9stC7OfuRh3Wo21ZMWNNCfacR1g
P5P/Y9SZh21x5xKsnfi9BOre1a6BMMKiaEACwiB5xIS49+TulLB9YcLU2OEP8erLSEcOoVDo
uXC0u6AAQgfRTzcLgxxtcdP3uBTbOS8Z4Ng+5tR62h4Y05kcy1Ki62v8hfWgNh5vThYuZbky
CIJI7w/lFYWGUOzpcypKaBS7tPHVDisztvq7gNwx59GylopyUNvVXvC2YiE7bAOghREGLUnX
pUiae2//0LUBpNmPq5/cngJ3MGRD8Wruy3pR56o7syRwbOeztT2fkM5HukoYrJ/qao3EuNQX
35sNAM18YFrTfAnHdn2XqYefFbdtRbs2t53vSco6iDRsqS6+EeZScMWrbSWr2T64t6nlsLJr
Ws0XBfPKQV3zOaG7oogdfg2Az460h6qhvUhTVCIQQJqyRe06WqT8hQDWnV8r4F0bQbHDeHHi
gJwto0k5DQQa6p4OPY4fsoVo8odzbUjR3JiLWZzXGn1lUTnXIj/nmjK6fqZ2Vhffaqps2vVd
pgq/rF2tp8925G/K7lPS42p9ml/5Opqlrl4cO1Hpi2d5OhzcHnvZX3wu61ORXGStw6Y0/B3a
XEGJXG7w80CI6LePlcn83eEPa/1fB6NKLSn1gBTR9PKqZvy8Vij5wmeAHTcJGUjqoYTkGMfp
qAhkHXYA0uKQR4zh5xK65OQsGGLZr7hO0gFOo5kwL2t77ir8VQAPjQ/CAH4uocvM4y141GTC
9xDX0UfTGsc17c3LkXXVytOsJrYy89TIsUrc+qn/+klcd3WkFozrM83PlxMtWWtsOrqX22jB
rY/6G2bj2tuRq9H+CzNV7Q7q6ZH5pbJeto2sv2NPfFx7uw/cUPacmqOjRzMNB8me9zbIOpGr
0dzj2aruhKrW3DH1UywIDDQqJnPtGz7MVk9QgD6pAAAAAElFTkSuQmCC</binary>
 <binary id="img_140.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAO4AAACLAQMAAABcGTeIAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADUElEQVR4nO3Xb0gTYRgA8LPMkiz91pbJtLS+DJT8k2J5
2IfQRPyaiKSg1vKmSX8grExzi0BLNBTtz0Rs0TcN/9BsDjdxmq1Fn8R9uMw5DedO29ocu93b
bXPkbfceEWVf9sLxvPDb89ye58bLDQFci0T+J+t9my8A9UaLwCGgwyNUiwOwYURGfbxJ4d74
AUwV0WEE75ABYJxETvnYvcUtYMoTrPgYXYScRFJ97KIE3mhGTZ6NQ2Clg9MYyARu8hQnZKaL
dLbNz1YS9zNBB1z/MRoAyub/ao6te6/JvNkvcBldzWhDtH72Fbeg3sYiBPc9jdl2cuYdLpTJ
9xCOFY6otj5IbEXX9mxbEDv/kH2DgRanirjZnGOI5mBd/3wRJ2v0cCZ1vU0cxd26vuDi4952
wA8CbIaNzAdlv/P90J0EcC6zNKY0Z6lXUTBOABfBxu4B9ZUScSJkqEq33JA3K473szuA13oN
F2axPijLnx6evdwHed7j/IEwBS9STpCLfE3LjcfBYwlTgWVAbOYjM1jCiWAmUXpHULeqiq8f
08KGSuVeonkvjF38qpJKCUtxH2/mXu2OSodmszemspK1BxssADIW1UK4RpgilcHYWdkteqJQ
EYBleVjSLWpToLB7r1UaRJ2/sgN5YY8aS5HixLM6QB2ppeLYG7PkJ2W8KRDLSyFMJGf0TSfJ
DRBeSc5oWimTxkK4WpjWPot1lkCYl4D08LAhWPEG+pBkaczPgJN/8+j5qxx4Ku5mz2ZdIQ7x
DjMZzc3L3GxM0aTGnK1X82pae4eXlIMRKQxe6iw+WThXbyoQvsweNp55nfa5YTubDuUePTAX
azLH67Kbj99+m/6JWbyzeJd7rsC0mmgcG2q+m5XOzF5qnzhf+L7AlCfsHxuS5GBpbdvZbY7R
NsRpYymepdU1lDVedzOTORYHfeHe0wMAf4AxGcScY/m3bC0f1USVPW9OUK6r9/P3lYejDP4u
fmX4drp7BFPae6qLq2YqVQxeFz8wRA48jKiR2BWiXOlEBTN7Q9w1L+pvzKkZtCuw8ipdAFsL
u+Yj4yXSskZ7E+Y8N13BLO4qVRkyrzn0i632KTOI/nonuDHPOwcBGO8e29nNzRxjCXGIQxzi
QOb+m/oTYb3etLDQrToAAAAASUVORK5CYII=</binary>
 <binary id="img_141.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAMAAAAApAQMAAABZWbQOAAAACXBIWXMAAA7EAAAOxgGnHWyZ
AAAABlBMVEUAAAD///+l2Z/dAAABOUlEQVR4nGP4jwMw4JRoYMAKmBn2g6R/gxXtR9Lwm1iJ
x7gkTuKSuHu7/079/eOYEh9nq9w0uzkZU+Jnc8sNs4vNmBJ3mtkkPxycj5D4Yw+RODtb/abZ
WYhR/5iB1M/9UOfOZr9Tdhdq+X2QxI7IGnt0D97dAJTYFJk6H0PC7P/v39sD2eSxS7iqYOi4
sw0oMd0X0yiQxAf2+BJMo779//39PsS5B3kMzhxRbMf0+c3UbWc2pWzGIsF64URz3mFsOtoO
7M/DpoNFIag5BUvo/t9T/P/8434sEn+B+P1/ElIJksR8XBIHcCXR/zgAhgQ/koQ9iPEdwv/n
j5D4B5Z4DpV4Y/dbHruE2q54bEb9Obtu8nwUic8Qib8n17TIY5P4eVt3K6oOqFEfmBNN4DqQ
wffzODwIThUgAADpX078VjBllgAAAABJRU5ErkJggg==</binary>
 <binary id="img_142.png" content-type="image/png">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</binary>
 <binary id="img_143.png" content-type="image/png">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</binary>
 <binary id="img_144.png" content-type="image/png">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
AAAAAElFTkSuQmCC</binary>
 <binary id="img_145.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAKcAAAB1AQMAAAARLmOLAAAACXBIWXMAAA7FAAAOxAFed92+
AAAABlBMVEUAAAD///+l2Z/dAAACmklEQVR4nN3WX0hTURwH8GUThAb1EvSSU6KnhOxdGnsO
BpJkozSCFc79AcGiJXRG4HwJg5BgWc6npCdJNheueTcr54ss6G3uNmroSlk3ucFpXLdf9x73
5+7c4yqkiO7DPZcPZ+f8ft97NqYDxlXS/b4iboWpU7Ru64cM7Rpdb86f1KrQkz8xs4hoveBy
zJo02tt+dVYz90PPkO20Zl2AIgBDswD3mB2vMhX+isZ0tctU1QySB68pdvj7AQhUlVN9MkOp
yFSJUvn2+kxCSppojfaGpSCn0fNYCmlWiFqxNKrZLdoflqKauW+6E/Rc8oSZXdxn6o6R0srT
r2hAEIsVDVRVnbrxZyuQUSTVpXO3cAc8hqlavgLA+8/nXH4UsZ5VdPm5nG9B0byjL8JFLt1R
VgjJ+Ybma+poJZn1Ymn+qaLZFj7CvRi0EJXz9U0rKtyWNfLxSyXfRUR0RFkh51Z0VM43fEPW
tDDyLIomsYXOF5fkcXGPfIHON06fVGruHunsJ1+uge62kc46UUvfMURUKCRFGAElSRe4+S1E
apg4OiMGg0TtsjoQ2U0+1Ti0TPQissQHy2rFeDWmaHbN1Mp/Kp/fy2G8mlRUmOQs/OZub6H+
MZEPE/Wj6m7JAi6kPUpl8BK5i8ZKvWNwnNHFjrFY1i1mx/FGOQBTvdV0vap8vczUAw3y/W8V
MzXOVD4J40MK1WlBf9DQuRSgdHvdb594ZKK02Oy3v12glb/mtz9cMNI1NC85O+9eoet9kgNI
7KfjfasIa9yrYhYySKVimy91qGnFbHtQp11zKcfNcY/tiFq/ti2kBq4bzMP12jGXcm6csg53
q/XbtK/JsDFg1r9Tq/wTUuoC8vbqlCt5GPqHcvhXNcBU9nee+a/iB+01epG/1FYcAAAAAElF
TkSuQmCC</binary>
 <binary id="img_146.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAUAAAABsAQMAAAAhR/kQAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADWUlEQVR4nO3WX0hTURgA8BXkXA422ENGhZNi9kdioTh6
uqgPIdVTT3uQGCq6DH1IivDhCjrFB0sY9OJKExkIQ2fqRMa85aAFapMGucucU3yQiFDwYY39
OW33nHsvZ3PzTFYg9D1s95z99n2H795z75UAwpCcHIZAgsYmEgjuoqFiBx5YKAuFwdhdCNfQ
2PsLfl8BF/ES0Rkchn7Abz/tTyuNMgbRmEGlWYalckNUOsSwDF57ES9NIRhIhxEEd3iIDjbp
TXyNsZcQEsbpgD2S40L6USJRSACD/hLh/7ufniwMwCgQYbwgsIwUKghhghhKCw1j5iwwmAaT
CIezCLJZ4AGF4AKCvtHdywZXBOyB1G4WYZzPOFVKc9Bzf1ymdg33Lvs0tRgMKCB8tQLhZ2NN
u83ybmTLpzFg0NcJoRXBb21J2Kd0b2nVeEY3gg4E2fYaY+V8fyCgvaHH4AqCH5QQBi855A9M
wyPu6sm0NSLorKJQw9Edjc7SRxBm+DNDi/womOUU/gVIvBVIM4YBGfR8itFE0BwizGh2l5w5
4k6xlw4XPOxPHdG9xxk8rxMzCpFZ2ulvWyCCvcsyMriP9TEHTH0UGjKkcJQUhvIt7UqNaRFu
w+PXGaUXPVQKWl4guN8EItzGooAXyzjTr6rzl9wqrlcg2CDXTpS7Wh2qOwNYe+bnfB3mJ1dX
2U4EGw2OQZvr3rzv2iyWcWhO2aFqq5CzrTzsqhkcM0lN62ontsahZMZHxptPA3zGh82ayamZ
C3Prt2cx+PasqsMvuy5382s0yHom1Cado9o+gPexm2sPk3xCwT6Gy77CDkUZ/Mx0oz7SfMP5
pgImr3MdOySE2APp30LsMVwYSHyF5wXzfu/JHtn29X94IjhwLDyAMOOxkhExCDOugYyIQ+gV
JqS5YUiY0GWBUQgZYeJ5adM5W592acP+u7592iDMH6bDroC+xbb2ZmnDGtUbpxuEedQeSoQr
jc3qL0V9yrGoobi8Vpjfg5AWoU/fMr5mtX/XHOqNlY+Fea4vkoQwTpxV1bVYV4t61u0HtcYx
MaM3HVbRqdV0etnIdgX9/pnwQ4iDcRFGxWLiekBqC+IwazAcjB6jAIB9IYHcOiTcG3POSHDX
wOnYM6cBEtxxISQMYvgHkVdJmlsjy2AAAAAASUVORK5CYII=</binary>
 <binary id="img_147.png" content-type="image/png">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
eqHSafovNniaFoTPDE8AAAAASUVORK5CYII=</binary>
 <binary id="img_148.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAQ0AAABzAQMAAAC4g8VOAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADuklEQVR4nMXXXUhTURwA8CsaGhqLemghodEyiEqjhzSo
9UF+EBQRQRHVwF760t4kDO5kZQTFQl+CHpKKegkyC1aNusuV5ue13sTGNlaO0qV2Je+6dzud
e+65unvO2L1oH//B7s7dj///3P/Ozu4YYBQiQ56ZMCZRYzJjghxhjcjPMYpIJMknyCRNJIKE
KDLTaEhAETAkZJgj8cxiAhJ/ZhKBJNugjoMRGcMQs+8JwPIDfAGjFsB2ZMmJCydHnUsLmEou
Z4lKJJdhISmSmYTNXXRmkdpdOlmUIMmEHiVnl+gceUWta4oc77Wkvi+H8xqsrJ4Ehut1hYLC
1rMk8RNE8uSTJEhm8TRS5JyOTAgNBIELQ8QvgogIInsbEGQ2gupBe2NhBMwRgRDuYFHqMJJm
ebs+2VOHkyHmF7m8fT49KTazvA2zpFne9FzMXBEhABfUj821zphQggx6U/2fRLy0LjREfy5l
8KFGHhN3MkxOrk9uimcnmVyQyLLw8Hvoy2OqJotkxoe2IJxvrj+8etC+wKF/SGQWkyaNWDAJ
ayRux+QVJoP1mIxrRGrvdwxaRyC5bq3rZIKAH99s3dy8IwoGmvtKaxWStN2ytSw7BIntY+Wx
LScAf+3BPtuzm5B09JXWoOmWex+9XFEFydtPp7I8uwHfXzK26cqHKOjvzFuzCxVqe4pJzpDj
tqcaDIYV8iYKAi01Gw8gct+7oaMTFWpxHO2A5HTJPls7LPT+bt9GVEgue76/C033cvkZf3sn
yxfYlz9pXhYFzmZpTa2+dTI8CCmte013VyEzmcnCPsZpS1wl40XKMx93bXclWUgu8MUsJvyU
SmK8gMiLiheI1FWVaCTyBBN/LSL2lc/PKaS+q1Qji1fjQoVnEWEPuk4r5EBXm0ZW1WCybY+a
5bC7GmXpvadNV96JC4lvEKnYy1ntCql5OEvwFcVk9Ypaz3N3lCwOhaC7oOkk7kssgQjoTnBA
IRcBl3qjtJDumshiqpCJ3RsYBX13SO2YIkNu3rNzmSNTFHHPg4QpcpUkIYrkksQHprrDSQ6N
BNQv+BuG9xyNsJ9beaCe+iqPwN95SOL2VOJkk17/DScaDYjDkGSRRAKyt2etensQEAMAtU5f
SAJxb+BdoUqmMSGzCN5AjkoGmKMqkTg9ibT2nFILxcSR9FlC3ZFFI2gUkSPp50JGUD+cJ6EW
w98qNB9CCTL+FKHnMkESE1kEN3lusNitJ9JWksS+eQhC/Zf8W4Sa7vf1j/VE2TfJ4AiSJkwQ
XYi/AcM0tZI3uwPtAAAAAElFTkSuQmCC</binary>
 <binary id="img_149.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAIEAAABgAQMAAAAAdoM0AAAACXBIWXMAAA7FAAAOxQFHbOz/
AAAABlBMVEUAAAD///+l2Z/dAAAB2ElEQVR4nM3Uv0sCURwA8NcQNFhJCBESVtBmGC41JFIN
FdRU0NCSkw2KoUs0dNcNEfQHNIS6tUVRgUVDBkGD0Y0ZmXr5o0hDycig09e95wm99/pFEPVd
Tj73/fXuDgGkggcfip+RmHot8UD9Ge3lKIlcMKKnq65eaGlYs1KixgsS8a3ISAyEtPIgTEot
D4KAiBpFmJwIKRalcyMzi9iH3fAX5bO381fCAyqCHVZ0i1NTyqB6ip+KVJWwVZV4VS73oXRm
ueRg9gTCAq5qW7YtaccVETImG5Jo30GXeXVIkYcR0zDO2drZMDcrcirUtffjPus7XebzSUW2
Q0YnntW2N7akneIgL2TacZ+IR65M90FYJDf0fXwK+M65LI+GApK53FZF0pqkKCFx27s5LMls
MrGJxGXvsWJJycehRSzpiogpeaKlE4npWq1KlXSyA+fcBXBnMZnSyU4k9pmAOqt+t4xnzTor
koOH1IY5ZmfxG3JLfwjgqXpXjfe+nyKUKSlIeWggJHFfvHnmCJHiwjAhkpR2jxJVR9n0gIaQ
YPapyUgIL+UFBymeEvHvgjZkd/7vAr8nfqYPR+V4B+lZ03oDJfNeF1W14M0xQuc0rFB9oJ94
8HjD2FeneAWQYHP+1i1RqAAAAABJRU5ErkJggg==</binary>
 <binary id="img_150.png" content-type="image/png">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</binary>
 <binary id="img_151.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAOkAAACPAQMAAAAlVG7nAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAEaElEQVR4nO3XbUgbZxwA8GhCzdA1qfuSYZZUCKGDVuKX
6UDyMmGsVGFfxrQDqdBmSgh0bli2lZ3pnE6IrUMm67zGdogUP4yOdrMlY9csUJvGrmVlozEx
KqHLxELPZDSRXO7ZvTzJveY69wJj7C943vPL/7nn3YsKKMWQaidMvqnIRE6B7aCYSyJCdnA3
51za3D7unhRx/Kf+E/0Y9yQJexZqOZZmexber8yx17wnFJhAjGAGiJjngN8rguG2imyi2cZn
fhC7aFYIhqnstGy2Rrlyk6RpAv6A6RhWgYviYRHFTnkbFBWYLII8osgFeWYqJZ+7YWYYkeeV
5R6gxN+vKHIy/he4+Eu+QtMIXocQKW9xDHRS9vFYLWGCXjx6H1xF8sxGlww/ofLiOo/NO+4Y
4IdDmZ+Q/Y8y4FimnIv/Pi/AK7amVu+hpjw7pKpSPri0f3K+/za2ybCN4xcQKTNlBZZdqlBd
86yQ2/VhbeMcc6Z2D3Yfmx0XcFvMvdy//zTN9l531ztNfgFTZYeP7A7Q3HLQ3XXs9DggzYxk
AbWdqLLDvXVMts018KzF8jUg70C+TbXcdXwaPrs1fXzG/9ZvgLy519A8Uj9Qqwag9cG7wZms
cFjOWKOWK9czHpf8qC1ZN5uGQ5mNTlkmN+Y2myauZx5KGaFZaz00e6o+U7fB8CJkbGnPTXOY
qtz46JmvRhykKUsz6vPB7EgscideHnOSnTEUvQazo1vji68rMO6vbxfPN4p+WOLMxfdiYk6g
ZyBHbh3IyzBWavnn3nyEEHHIV+Kkw1tMiTm1KDxTRVweNUyGMTFXXMgYdldtVx05dQW/gbzN
brpqBzBj8OWgvAm1/O3npG7H/tD+fiQpxTi2O3Tr5ZajSKlpKcjGAMJ17KyjxJEyG7yaRvhv
vYFhesmHIe+2H3o+ZmGZfAMTc13g133xCzjL0yzrqHc3yE8n1y3xWZyplahlLxSvQNYEnJbY
3jW2TZ+xXANACPIuh+FAonfNzNwmmGfka0pvWUMq9rx7uMoblULpDFSc0P9ZtJB5DLc0/C0K
OjtPXQs5fvUkx6AQpJn6keXW6gbqmnc38PT+aJmD83cRKnuwD+H452CZFy6mGN6sxGklDs4v
S3jqcpmvkgb6j0Ejr2kGrmmlUeGSATdsHIMKrBT/Is474DeYCccon+FsFzD4uUlsQY4vLauc
+qWzo5N+nRzP3fuuY6pjLD2xv0aOz4evdt5/ZWz0kwctstnRy50/On0tk1tyjNvilzr0r35U
M5kdkTAB8GmwnWjuob4wFn7gMzNhhDk3U94RpJSZ1vI3TJmZU7wKiEKYrRZYCguLns0ZDogQ
GhIyQp39sItpQJxDEwIuArznZX27V2Nq8DReQDERA5DpGQh0e48mox7r+LfYxyER4y+5De3e
YeNmX5O/DZtfFWd3Ddw76LWaon1fDp9EvxAz7qzWu6o0ScNT56dOhr4R83Y6/WK46tPVVt21
x0jq8WrlpUjwR23HzMTQ7w27c7RMtdlPAAAAAElFTkSuQmCC</binary>
 <binary id="img_152.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAARsAAACdAQMAAAC+fU4TAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAEF0lEQVR4nO3Wf2gTVxwA8IDBtqyxQYQG/zGTQFuoWxRL
ZVqyWIQp9n+jjC2rUgRbmVq64uzVtf5A7B/+QDFWozCFQe22WlKxSKz5oxYL3V/akaZpyR+R
Rnq6VJNw13t7d+9ekst9e5eNULrhg7x8c/fh3fe97927GJB+mzH8YzSSD+pMRzwjfaUA1DvK
uxMIRbziD4ERMI7ygVx02WKt8U3uLh2L7bB313SX+5K79zXmosexN/d8N9ttY3N190O/b70+
GA+trQXQA5/FaSyurOsPXTlhHkiEzE2qy8X6egYOuE5V/SKN9HJL/OsDKnS2eO/46kmnqbKq
7rfxmm6zL1n/VS5CeFphGgvSKkRVS4D8kstt6rL480FA+7doGjEk4BCOcIe+hZDcUkhgpUCJ
AremWs5NB4J848XG9698SWHUktgW2KZEL46MVT+cDtanDm9qaH01+FfHPNc2PBVUouDB59bI
dLCJG2n53nhpML44zx0dEtgcdKLkzhUJrXba1m4hiM9BNw6VVD8cD9ZzpipTDa4ug5GfjynR
6Z7qlq7Z4P4fnUfcrX/4klzE8s2jjn1K9ET+ZtJH8DIJuiueF5Laykejeshg+NLUNWMwFGkg
oehpsaFzDfJ2OjRQ+pB/WRCzAi/3ERUQoXwQQ0UhUK+71KGLQmeatUcSt5tg4njB0Cl9NFWu
kzjZAsWuMAj9T1Fed+ZKTPy/jCZwP6GHzuG+c5UmMtDm0EDklQi+FrNRGH/e6aEN+PNWBwki
SjpQHEABEkQQL6Ey+s89no1uIb59VFyBpIx68V0aRaxiJIftjf0yd9SFZuTLeQcOmkdO/lqt
RA39npMY8Rl0zdVeUalA6/Z+5rG4nQQtepH3J1u5q9hqzUZP1m2673nRsEs8j1EYTV0wvnQd
7rNlo7GtfZu/+MTdIP03RTjhEc9V842Su4qcojQkKIoSwDqlw0RWr4M4bUSWL5UPWtRGYakX
lkQCEiIUMSy5aVSIt/78Kb23mcD6nSBK9fXcpMjvef2nA0Tz57fTB897+3UXjPofDFMU9syF
QYRz2k6nxT7bUw8i4V1m7lHWH0UQEhtdRaB4agQUL4PoSaB4agQUL4NoLkDxAMRooPTTqIUm
aODXQGFVACCvakgApa/CaiAHDbSeO4YG0CYmB5k1VBcPQOq6pFHmFJ8PUhcvjbIyYZZEs5lj
G2D0lrxVxJBEZQCKovCsnAwjjcQCiM1kTBL6iECElhex6DZH0Q+4mwDRB+OxIp4ge5OdKYLR
wrG2JEEVTbUOBkQL/HetkwR9Hqk1lYGJL/AbjVdlNGdvrgDRB/5Cxx2KhmJtMFrwdNjlnCqH
4kugUg+zQ55d81AcnB3ZUwiSHsAVULtlRonlHalwSNzErHqbWNZOhyD0N3WdeKXAExmsAAAA
AElFTkSuQmCC</binary>
 <binary id="img_153.png" content-type="image/png">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</binary>
 <binary id="img_154.png" content-type="image/png">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
oTO3rQAAAABJRU5ErkJggg==</binary>
 <binary id="img_155.png" content-type="image/png">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
+htRUkWGFnsAAAAASUVORK5CYII=</binary>
 <binary id="img_156.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAbQAAAEaAQMAAABD7DCbAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAH7ElEQVR4nO3ba2wURRwA8CuUhwmlgEqIFFa01ZD4aAQp
WMqCCskZG0xMjGlCkJyIBiokDVEp7bao+KVYAj5CKR4hRsGESGpErIVrxbRgwUMqj7aQu3oJ
Z+ToI5d4V+5ux9nHzM7soze7AVLIbdN9zM1v/zOzc7uzQ3EB3TJQ0+pyucbqk3XLoAstWfIx
OkrveHUvJa8F9ShxzziUk3bDDt3oq1/GmbvFHOm2cqxuUYB077O7U0IHh13l5dT2aTt7Xi5O
704/savs9VVvliiux930+9Ey9y/pXefBhsi80rX7FNd7X1Pk4ecnMsTr3N8QKVz24izVjWv6
p6hnLku8/IZT97sfVOvXu7zppZ1lbzPEO7XhuYdeWTVfjXe5dN707oBYmdbxsMl9cSBUyceC
egEY+ouaJWXXAcqBjLsdLv1999bGu8cdy7hg8tB2F9emui1BeViw4tW0LvmIa8rf2ngiF45E
0hjFSeswleZndh1UWodpzlvpTlJpHzI7Oie7q6XSspgdnZPducgksZzZ0Vd65Ote69D97NAd
H1/kqeMduPm73XN8DtyzXcs+cFLOzV0rnbiS/K7SAiflnLzw0TrBvgM8/HFQTkk5ciCdS9l0
kYq6NZM86CfPk1eRF5oRgr9hDv7ADNeTIehCC0MTPXmVeZ5GT10F/F1Tt85LnUc7igPoPY2h
RskBTooXAqBREMP1t6B+o8O1OXSNDl1Le57gKN55N+/EtTZc9zlxZ5qducbm047K2TLGWbuA
LZA5cdXAmQMZl3EZl3EZl3Gj2w2kpQYXl9+Lcxmc8gbNoYQcaR22FtgpwQJqQhQ4czFpxVS/
gRgc0QZwvAR8D2d1AuX2VdQzuahY2MFhFy9vOLyRqX7//bR5OhHvcPNxVle1CLsEdC1s9TO4
4xtZXTF2scShm4dWsrlv176G2wXE14Goj8kNBeC1R07pL0xO3sHlvMPOab+26WLY9fMxMMzk
Lgt+wkVgFVNM9fsBfnMS2J15qubXSUzuSO/YumLsLuc/1rCByTWVuQv2Ipfo/nzBtn4mV1R2
dlw3jtf954ITETa34uzmPcjFru45sIutfk1vubNx/WJXimvbS5iuw7Hw2LpK5BIDiZiYYnK+
AX8qiuMNxGJigqmcAb8/ha+70q8d3ncdfB+AwUnTG8Q0uE/+Nf0+HKGeR0mAviXy4gXSFIzi
xIna88jwGBMFqr4BOQU/x3jtE7+uLjyVFJZLYPa81c+3eqnaR+XasszT+pXGUJdhmZq5T3Qu
rE2jA7mQYbb53SgQiSPYKH7z+V39HPswPUPFSxVmmd9N0rP8Xm3ejcop6p0oxMhDv3RBTeZ3
jZ2Tp1LCUgdiGmd5A+RRdJjV+X3k0XCUxSXRXyrIfTaOjqakcykqNQF7TrW018o8jlSdugTT
uQSVyu6GqVTsnJYzrdO3C2s5LeI5rd9taxfm6yDSN0Lrcg7VkOP5uGvMBPJEtIsS7ppMAmqC
/vlgXc6RnyvW7aJ3Ig98tOuQ7p/YhaW7ohwvCW90hINSex7A/iK2SXci5MTaJK/GS1Aub9kO
nqqfeIVyjSmfEk8s3DaNcO9eWO/TuaNEOcV9hWo5k70HL1o6IJXzR9LV7vep5ew91E646r/W
81S7iIGPOMJ5D3nVdtHHe0dXv4CfrJ93B3bfkW7yhUZBF89PltN7hlfbhf93IfFkhLUeub+I
gml/SeukxaS/4EwmjuzXjM5PHbH3a9oZy+mT1htZnHQgYldCO4tyQtdPu01C26YXnjY6Q3v2
t97Y1jd+NXJvLCit6v2eIV6ky92UKMKu/LPSnG4Wd2PvsqMz39PcudICIh7dLuT1u7F3+dFe
Ld6G3aUFJ60cFW/38mdmEvWbNyunw6RdjPVrLa/mZweQ2wpXAZPrZ2xP6WAJdi20o8YFgzcn
wfWxxzUnYKedXFnEip1jpDuKtODhBFzSj+fjE4x7LO8BSfyUGdT2sPMTLkgNzOMc2qvBe9CF
k5/C4s7IRQmGAX07j06wVSAcHM9nuXKX4lOBuG7gOhvlnpDEaYNoCBnUXBLXXjmsRucTtHEv
dnH8h5JApB3uk+R4HbvkEi03Xc56dGbezAF+EKfXkUxEydTLgua82uj8K/LMcVQgMpFwMAdq
fXQt5SWkbttIRjkQRDvEaxvg1HCctQOoabTrBJKCWTidQ02D2wKAITWFDqdzuGnqcYrain00
0zvU9UM4haM2Vg4EBXmDGh/VdFDHDA71FR5QgSfochldXCkRqqCyjQtpHWgl46gNq38DNXOi
XFL19qfUk+5iFk4pqaiUTBmnnTTkMXOgRlp55V1ODsexOTEXRVI6WZsxi6kDwdVKBWNDVuHM
ndTdpFDSX1OkDF1sBCd1Ny/smzyMK+aa5TB3UncLKw3UFzTNYOFgdxtS+lyraThLF+eTgtwD
asw/t3IgC3DJLOntxZ6r0Z5ztpxX3YrGrwKbM/844+y6iLyJ2Xan5c2wbdf58bm+qtgJ2+5k
/sXyqi+/tu9+m1G+eKp915k9rTz7gQP226XgfOnaL+y7M+POXc3Z+c2duu6+O+y8d41rzvPs
uOTA9bjn5jtyoTnZUx24duimCPbdtc4n519y4PpnrMk+56Cc/fLWvvvDoQMZZ7L47nC8muzt
8qDArjvlkl5El1oNJ6zHLwC9Io9+N9I/Ao6mcmbc3e3uln5275YzSMwDmv+PKXM3mN79D28t
nVJUSqLRAAAAAElFTkSuQmCC</binary>
 <binary id="img_157.png" content-type="image/png">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
dYGtwrcFIkwAAAAASUVORK5CYII=</binary>
 <binary id="img_158.png" content-type="image/png">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</binary>
 <binary id="img_159.png" content-type="image/png">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</binary>
 <binary id="img_160.png" content-type="image/png">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</binary>
 <binary id="img_161.png" content-type="image/png">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
AABJRU5ErkJggg==</binary>
 <binary id="img_162.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAP4AAAA+AQMAAAAxsDSmAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAACa0lEQVR4nGP4jx/8YSCo4AGUdRxdih0s+ofhXT0Ovelg
PUAFbtKOHPqtOpvRFaSZTZJes/kPw4FNt8wyl+/NWIyuIPXYpEyNxX8Y3u+6Yca5YE9GM5r8
v9QLTRkKzX8Yzu+6WVa5bGvKZAwF92ZlLpkMNGESZznngtY16Ar+sMkd4gYp+H+G//tsbAHw
uf6fPTig/r7H6ss/3///qycqJClU8J2QgudQBf/KMeVmPK6zPH8cpKDF4G4PI4b8PyHPlAmT
wVZsuR6piunVP5djU8T3QxXEsmJVUBIOV1C7DtMKoAnTdkMUCBxsMcRUcCGbzeA4WMEf+7/Y
fPj5//f/kID68x+Hgv//qRUXNdeACYvBHs2NDBDAwvABSCYw/GiQR9MJpb8wfIey7pOp4APZ
Cv4RqwCnG7CZ8A+c4f/L41YAY4JksXnz3zeBYzMOV533yG3jx+qGf99ubtoWXXVeWvfbeewK
vpzdZe7MxS+j2IZLwd1d5pFZ+tK6ZhAF7/fOnI9qxd1Jk7y58r10WyAKzttuuQ/2xW9Qmr7/
+d93+/M3j+9//xOo+PO/+uc2t1LXQRR8fv/7/39+lHLwww/7S7eCPPXACv4JcSTU/2NnR1bw
Q8L+zOFjkX7zwQqundYBp6ADCQwIYP/0+rHbOfvBCkwu+P7/z8CgcZTh/wFhqBUc9huvid3O
BSv423I59v9/PlQraurbLDYdTgYr+KMmAVRwH8WRH8EB9a/eHuwLcOFw/zOygu8QBXX2RKSo
z4QUUDPJkauAoCMpV0C5G/4TUvCzgQWcQOTRFMBTzg8ojabg/5+Z3z/et///BwCm4ePI9PKk
VgAAAABJRU5ErkJggg==</binary>
 <binary id="img_163.png" content-type="image/png">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</binary>
 <binary id="img_164.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAJgAAAC6AQMAAABoaBZfAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAAC9klEQVR4nM2WT2jTUBzHI452MrW7LR6kFubYoWp78c8p
4BAPHjwrCitMqLCBIEykQoTJ5qHQibAhQypOJoLCTvUy6dqiFRxs6EEkIugOPYims0KTJc3P
NE2b915e2leY4gttw4ff7/v9vdffewkHkJLK8VsnuTGu3w/W4KDMtcbuJqtCa1T+AdP+okdZ
cHtUHgLUZDyuMpsbjxQINnfhytwCweYPxgfSZNzliXkybjbXQ+rB1uaMWiJYtVG5hjPPdWFd
K9bcnWatWmh9pazyx2AFkME1fnQaE91MoTGBjZWDbOxb2s0MSn07zYAx7v9gyVAyZl6hZPRO
NBlNWUykxAkdmezJgJF19vD2ZfUQrXu8Nxp6eA/hDK2v2ZOoB401+x7NxfeCzcAVt31J1FE9
bA/arOIIac1cjEFb9pXIhSZTEWYmyCunrKpRD3lfJB8fxPV+TixdPPeEYD+WTvc9cnJFO+7q
Y9xX3rOcHx/CGWRVubu50eK814q+fjXYMoLKdZ+47njob0cz95XQ3SP+FtP0d88zH7SwtFhy
1krvezG9kdgvLc6gLLz2PhH+9OyE41sb5tY2ErHPqB7A6rp1n0bn9iVNmVuWmIdGmRuFCRiz
PShnu7B9OzAgQL31NKyf675FgHZMwBjgbMqLEXq72jPRZsawxQw/qmeFgHFDqLjYJB8jmAw+
aa/jYbPjUtLFJs04Uu9aL6kHxu9eshYZat/BpWeurJsBnREejMzR0yhM8bs99BZz9PSAmxkB
cOkZwc6sXgYIlJpJZnCc7+gox519CqLNsgrl/GPs+yyt7yksy6jH6tHZ19Eznx5pkqmvSrpY
wj3U5cJYZArf02rm5pkhCffQXo70HHqA52qZA4enF3CPcn9uENWrs1/3zI2fwj2qH827NDEP
4t3Wa24sjHo2dbF+LIz2/woK5fwzv16DEfMZYvPFodG7rYE+4zszAVyDgXXnQasPZ8DIGrkF
Pm9eb/gCP2J+ztu+slo0WdEkm7z1sPoD5IKbmhRHEbcAAAAASUVORK5CYII=</binary>
 <binary id="img_165.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAJcAAADwAQMAAAA3oGTYAAAACXBIWXMAAA7FAAAOxAFed92+
AAAABlBMVEUAAAD///+l2Z/dAAADzklEQVR4nOWXX0hTURjARwv0YbLemgZNs+ilLUVwSg8n
k/6IhNBDUC+xLEhsD2JUbwMdGf1RMPDBBhIEPYmjxXy8lTB72gIfMpJm+WBgstnAO7nXnc69
d/fPuee72zUNgu5wjh/nfL/znX3nu3cOXHA4MXUVHYRpl1tlPPmXlT5/vZZWWV6flS3HeJtM
sBnP7jgLRw6ZGLnyHMv4WPLnwBg9l0+M9x09Tnv5RN2tk4j28om2HxST5072vQyYWGxu/8CI
KTcer5rXouZSdg8ghnfB7DrsfkekrhDrLTK1Jl/bhhWpTARYAWJhluUghli2DDEvy6D1/T9s
tHmMZQNPgq7e6l6P9HqPsPF7i65ESU1zMuOAeBBDFOOAtViz3Tg4gFl716dYB11DNOMMXrUm
jfEgVqCYEo+ue4XR5wPJvV1lpbmC3t3dqoM6q9o4A8OWDBG2Acx9J4EVnUmOtBimxkl9I+1p
iCxeMcwlLLV24s1CF+1NrfkjBztorzTu01nakXLWRz5epBnmNtKL2zvJjdMcZgbtFcy2l77d
QbnVWuzVHYWHwRuB3JkDl3q0PPLisO/Y7Gbt7Pm0vgei0zcdI+yc2xCvs+ZpgrDL2jhSj/dP
f45tdsauujUH2b+lKSzfQIwMo73IrRSvUGrVNGOeGWRlmPxlpA+l3FQ2pjMMMG2cZjLEq7Ji
9Nyik3VATFwA4mWsGGfJsE1W2WHtteug2d7kJvSwrOBm40FM9LIOM5NHo3LMsGYzk2pSWfMc
Uh05Y02W4lHPhBDDECNLJfEIKwoa28qobFCLt7XYOyozcXAqO6KwQm2oVY4nXnf5HihMPBS6
q7AjoelEyfGFMETOqvho/UWrV3HM+VoVx2R/vDUjzxXeNo4q4547WpRxHOZn1OewfFzLLavm
wb8y5oaAfBGwL7IXKafS2LNFpJxK4/5FB5KHg2thav9uN427WrqR7iBzgxPf++J1iPLenNgX
fOZBlDc0Md4X70KUw9U07Grpp5n5uRhZMSA3pg7ylZgSj8vZ63UaGyvPsMoMfQ1i6tyioddp
zMkyEeh1f7tH/CsObgcOwcSkd72H6Q5zX5Pe9R6me6FeVzQx+R2xDogxuQH3Wg6oXYiB/Q+o
cds9ouKZsXZwkJewAsJes0M4Nb9lzk2oH/oQdNMOwf/rcVeAZnn/UHXHPdqR97/urgnQLNcw
dKGjivYuN6Yxb3KszmD5/kPllv3D/at4P4JYaf8gB7D3th1AX8PMpf52US9uRwxXZIqD/IKc
9yQ9ySbyao4gmUVXJNKebEs2J9tT8rjfIrYOytdsTbYAAAAASUVORK5CYII=</binary>
 <binary id="img_166.png" content-type="image/png">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</binary>
 <binary id="img_167.png" content-type="image/png">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
SUVORK5CYII=</binary>
 <binary id="img_168.png" content-type="image/png">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
SUVORK5CYII=</binary>
 <binary id="img_169.png" content-type="image/png">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
/w915lyzJXke0wAAAABJRU5ErkJggg==</binary>
 <binary id="img_170.png" content-type="image/png">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</binary>
 <binary id="img_171.png" content-type="image/png">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</binary>
 <binary id="img_172.png" content-type="image/png">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</binary>
 <binary id="img_173.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAeMAAAAvAQMAAADq5DAAAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAADg0lEQVR4nOWWb0gTYRzHrzTZaLpM0onlaQolCmno9kY9
xTeFSERvGjFSEtxyusqywGjTDfcqBJW0/DMpsgiKcnPkHzzJcC8E7UWoC68tMAwNTQabMr2n
u3u23Z26dPkm6Dvu7nnueT7P9/c8z+/hhugOIsgBBPmOQBXwHrso6Y+tul15DEBpAVdetoiD
oNIhfurv6P15B6Hn6DsWAr0+RT1cPnqWupHiUOgmL4k5qQJJ07aEeuWhVkOWcY+0V5TcFzsq
P1ckocrTzcWy4pxjVnXbXr1LUs23zfIr0iUMeMebBbL07Bhj9dAe6bWSJP0N80VJ7RIOvJOD
KoqObqs270BrNzl0vgfSE2fMt1pKrtTS3t1U5Mrwz0Zf5APQwE+vszRJrMDIR5PNtaMlSqmA
mjcIS8DJPLfxJly1d1PamY2roLMCgLWVjURyHAt4220YM7BriN6xTfCezpY6APcbZ+j+xriC
9sPpgkKx9VODRJSqQ1l62cTQHhzut5eba5B+bnmkbr6eUk3IeonmiTIDbgrQXSsOXq55t2fq
a8sRdYzyWRRx13CiYan05AjrTfxEd8vzruxp9SXVq2p7RSsxOFH2tJ319q0aFLYjPRwRq54V
vowYE1sJat7SjwFvklj20y6wEcfSJBagyRrqorug9GvMNzDjjdOGdFOnpvH+6kKilqHXygw9
9Rj3lGjhRQbi5J9QYcbD3LPFIgwunKJ/nsBCON/a047hNrkK9dPOuVDoe0lzeTMKpd/7TuMc
HgJdmWqnvKtMkC4NTyJCoQUZs/mZY2roDdw1zMrvmWZFc6QH7IP26Z+l9ydm7F/b3QNyBfd2
IsHbdtf/S6/b2FojypYXxNs6ry5so1db2JrFwZbHNWTdls7LlJFni3fTmKBIlGmka56vkhRR
pp5pcaPh0QbJNW4E32x6QaEok0fr7YuXq3rNTLTdlWllVpgebrGsq/+JXMOh5190LMqrRnj0
kD1KEfkGeuers8qlY5DWZMdbjiq43sunHosUkW95tPmLRi7sjaFrfQ8qB8qlHT46p9Maq6jg
0mHtGnlkL482fhAWqXqYtbMkCtMie8KYFo+YmrfuPNd7fqEBKVIiXBpM2XJtP+LpfzxgMvG4
C3VegLQJBU3OCpRDu4Ax17YWw6OBg/kxoj4X2hWtrw0DeKDskwk4vA4+DQDnRbBzCuXgdd1v
pv4GAw4jgh1Wr2kAAAAASUVORK5CYII=</binary>
 <binary id="img_174.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAWAAAAAzAQMAAACTwkDiAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAABcklEQVR4nGP4TwJgIEnxcXsSFB9gIBowMtzHZsRvKP0Z
RfQvFRV/J0XxbxRRajpjsCnmhyr+2FOIpOzjOUOI4mMGxyGKf4L47H8git+uXYuk+NvWbRDF
975DTf75+fyf38UwxVvxK/72kE2iuuAGXsXHDKHO+PbW5PJvtktQxV/X4zX5Qa6B8O86gXqi
nPHe1+Rydb0BVPHarUiKv2zDUMwAdHO9HcQZn/uK//0Hhnb9/d//6///PFeJHs5z7KE8YiLl
HimK4WBoKKZdtiLJZIqcQVJZ9x9U7vwFGvYn7NlB+5/z5//+/+Afw/s7////Y2A9yyDTwBHA
wFDFzNBg0cBOWvk8qhib4o/HH6LKnJOHM9vk0RQ/m30eVXHPfThz2300xV92H94ij6z4DYL5
HV3xh91n1epRFCO0HkdX/Gz3XXZUxQhnfLbHcPNdcVyKN9WjKX5w+aU5iuJ3CGe0oSv++Pzx
fhTF7+3hTFgoDpLoBgC2SRCy4k5VEQAAAABJRU5ErkJggg==</binary>
 <binary id="img_175.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAV4AAAAvAQMAAACR7DCJAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAABZ0lEQVR4nGP4Tzz4w0CS4gYGogEPw35sZnyH0u9RRN8M
DsVPBoczaKr485zPyMrmfIcovlc8H6L4OUiZ/GeI4te82UiKX8dmQxSvzeVHKH57Hqr4bXYu
kuL32fkQxblr8yGKT7dJPH57PBmb4rfZtWiKb2+77flG2Bmi+G72WyTFd7Pr0RQf3HIg/o3U
ZYjivygmQ3jIihu3XI5/+6MRovg9ATfvMZns+fYP1IOPeaD+///93//9r31hoZEIDY19DBbJ
b//+hkYKSrR83wcN5/v/oeH8vwApUv5jASgx+IcUxXAwqhgZkJS7SVRMUsGIzWAcgMTyeZAp
/o5PGbrix/2HUaV21MNY/yTq0RSfNZ+PqtgaofgyuuKX5W0v6pHU/tuAYN9HV/y4fNYZFJM3
ILTKYTijfBcPiuLN9nAm1BSE4rvlu3RwKP53El3xyeVNOSiKkUJDAMPN/cdRE+QeBNMCXTER
4A8A0dZf3WgXBekAAAAASUVORK5CYII=</binary>
 <binary id="img_176.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAVsAAAAwAQMAAACFRUuDAAAACXBIWXMAAA7EAAAOxwG+Bl3Y
AAAABlBMVEUAAAD///+l2Z/dAAACQUlEQVR4nGP4Tzx4wNDAgBVw1Jn9YWDxQBZiTmDofSj/
//+PxhYGBqaWFKAQCwuDkxBIzoEhEkiCTLzw/8dEtf9ZTAoM+2GW7Mew9icafwE+xb9JUQwz
GaaJkOK/50lR3GO4S/7B/3f19+qJULxGYZaZQ6aGIo89YQ/+1V3QbBKtKW+UOp8Ik3MXNBtE
q8qYEaP4z8tls42jVTm3pe4HKd5XfK74XOE9+//7n0s8hyn7cdwS4ow/LwRmGzvkahhD3Vz7
vvp/2bv9//ffjo6GKf517R5E8f/P80E2/Pu/9z9Ycf376vdlb4GKb92GK/526xpU8f/7EPov
QnHd2/8oir//uv4fa9qof1//vu4vSPFvJMVwk9EVA53xF6czIJqRFZe+B3nw9m2Y5D+4B/+D
3fscqnifxTHDNstzkKD7CySBDnr/47gFTPFtZ7kPlb/nQE2GmQZnwAHIg/9mp3kkGEziqCdK
8Ywy7rUtk+qIUvxHoIy7t6HrB0HFIDf/8S7z1m3rSibOGZfLJBIMZjPYE6MYRP2FxDZRin//
hfKpVxRQqBitYGxvPABjsi1Ek1MAlnzzgZ79wMAS+3/vd5AsEw/zDgYGxg32wCKWAWrm3/l/
gGQCw3/iwYMhqvidPT+KmBy6IjkkxWfm1SNL/bNDU/vvXj1Cce89+YXIcn/qURX/+YOk+O09
T2NkxRLoiiVQFL9AVvz/FLriU3gUP0BXfMAeofjNPI/N2PwDFziHZPI7e0kUxXJoiv/LISkm
GtBQMQC4aiiGmCJY9wAAAABJRU5ErkJggg==</binary>
 <binary id="img_177.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAYcAAAAtAQMAAAB/IDUBAAAACXBIWXMAAA7EAAAOxgGnHWyZ
AAAABlBMVEUAAAD///+l2Z/dAAACdklEQVR4nN3UX2hSURgAcM0yhaKXGkbEEKIggjBjIwil
XmI9RI8RjQrmwyAEGRUhzJig9ZCLBT3cwNseqpeQ8O56g5XWhA1qKfgQ0w1PY4GLshJpKTf9
uv+859jk3vk06EP0/Pl+fOd8Xq7htkEMo/QxvDHIYek9d8ZD9wI0AOqm31ByQ+gSD03wfT2s
pBgNGw83KKEOxODxEEFb1DZTFMXvlCKW1ImGkJLobsRCev/kmhmdtq0IYiFtnRwyF2ZsaQ2R
mxlgDky8Wzw2IIhcYZjxPKSEiYb4WLA4t1F3KtygIKRJ/G66f1BDzErC8lYSs4Vbzj3xC49k
4RqJjhR9UUFkfFkxuZ7xCSJDDTO7J87m4+KpMkErc7znYlA5lb/oR9dRANxPWE4Uv6ZYsbtG
63SfefHUy7QgikY6dcOMsjX55n7kRzdFwSYlsSZCHk4o3UWCgNdKd5EqRiURU0Sy9Z+rAv4V
4G2rEdN5SkQx2iaSGxDSzVlWEWxHgZpuWbgcIUfEERFExDHOQyBbyTg6CB6y8qLG016zh2xj
SBblhD1k2vkirC34I4mcl5bFKjdE5T0HGR0Rm/rZEp/CV6ilrUeDOuJ5ouq9LIvPnJfKX3uq
V+PxlvMtUeaujpl23NerUTWd/A5qr8LbgUfaAsAvDWUBtPK7qW8fDdF9wLr4Mv4qFFi3ingo
NYSCzQ5CO/4nMe/ehReWw3jMzxGZlRIW96LERpkjkhhyo03ss6sbDVKkCPFjDosPxfdOnEWc
qk6MYfkZIaKr/R1r1MlTfTvUJnDaHychpklhwmLetRcLslf1B+SpiJs3A8QNq1kii+xuFbDo
Lpp/AchWSeMcsHh9AAAAAElFTkSuQmCC</binary>
 <binary id="img_178.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAASsAAABrAQMAAAAVRTYvAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAADe0lEQVR4nOXWf0gTURwA8KNME00HBv5TTUsQ/ynR0H+i
a1iJhJT/BP4xVJoTIf0jf6RZXYZoEKQ4SyFwSEb/RSI4UeqyQIzSSYIlYTsztz+yNqfuNu7H
67b3bje3eTvKsOzB2OO9z77f771393YYUNIobGsZS8Ce4zrsMG02dRDiT1CYB0f9RshWyXFz
EGN7KIwhEUN+0TgekjOAsW6UNMYVnBQckhiHojG0yx7MegMY6oRjn6VL8DAoWro15BJ6pQVh
xNra6eDa+JNbvQv/D2OxyC2GwjjxF/6dxEOC0b/BiD/AiEjMV5tLIRM+vBoYI7CV17rU1qS5
eDwCm9d/z5mZKyfl2dp89PKVaU1UhGgurX45a6ZYF46xZj+jNfqFluniM4FJm0XmuzBxKb6Z
xY1DzCay4QWhQwZXITLrBA5ZTSUI10T2hoQst0qWxSJmKgxIGsoyEDMUyLJKyLiPzRLjQph0
Yc+B/+51jOgm8E1YYKb3OjOpgM2ejpeLJoY42xUnFw3NOaz3MxRE4+yABZEZkI5gWWZRxoA8
w7eUhU2q8HxjN/7QcZjwfTs3SyrTdgADf3Ft/waT1RJz44rYKomGcO+Dh96ILB406L976Y6W
zJLLmWBcVRyXCvbirybKwPo9FZz0CEzt69keTBpKyoeE46qoKRvogfVtFVism4VR3aUYepBs
3VNJebG3CKuq6Fo2qAfWu2UC64GMF14wYI/unjQUl7WBtZmChkEh2o+4QrAgRmP9zJ31Llmj
ywRdqsLYQZAArOc05FLKfjjJURgPq2Qb+auLNxvBVCMwksK/Lc/YSHDHACeZkOc0oJGAsfiZ
ws3yRFZexoTkItEa0f4hO4XRwWwo3pnorWT4UgfRCofMQcxbcs7XLzZvJYNHDPgQGqYwdKy5
3Hlj8SktfbfVtax+YcClqTA9Sn6aSPgmjX621lQxV/XweHWnuZbT73ns0uY/WVSlxUBGUhg6
1h016fN1n2qrRxPruYsH+ta0+aalC+u5kOEUNgaZE7KjI+fr1zv5Uoc219T/YQUx6W3Q2VAx
rxuN6h9JrN/Xyd9wHkx/ljaz2gaZtAs0CywJTeaB0WO48QXgaAsxTFiYKcjAhj1F9QYsELov
wjEQiW3aZG+kX2FK7rdtSbpdC7ITdkHBm8Nu4XyzY9G+vgoOUS+lae/QLuwU1k79BKUcWcrg
6qwiAAAAAElFTkSuQmCC</binary>
 <binary id="img_179.png" content-type="image/png">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
WnZdaugbAAAAAElFTkSuQmCC</binary>
 <binary id="img_180.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAeQAAAG1AQMAAAASshXoAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAATV0lEQVR4nO3cfVwU1d4A8EFIVslYtXyBZNnn+oYJyCej
T9dkFMtELPngvd7bUheIPoGo4Uv3KullQBfR0CDDlx7SlZtcJR98uWL03NJZwczUhCCBQJwl
TCqlXdhkd93dOc/M7M6+zgwzs0+P9dT5A2bOznd/55w5835mIeBD0kE/d21qzODQH6kG0a0J
XFrGrY1jczl0wSCxjVI9h948iDbt7uHQg9Z792JfdKsfl0a5NVv6+fc1dp0P2RPizLMnmJq1
jD8AQZvyXRZw01LXZYmkobWMmnV8PRzsrVF60qG19ARG/VXRs6hXlxGkDSI05pP2LfZv2qBD
AGgQra0NABsycAyBgfZDEXpXQMmCliOKMTsnSkToc+mBzyaVJzdtuTpChC5LD06YUR6v3vSF
KA1F+8ceWThmzNt8NeaiP91f0vC4MTQWn7WP1sTEXAvCd31fQQACcNvRgdSfwWCRmVMbUKe2
JYNDfz+yaFE4uyb+Et8N2Ppat/LQ3Kmc2vSU3FOnE3WltCF/yuJiTm25U+2pw62wTd9UTln8
DgJQDn0jktI/lqx7aczZtNiXRpa8atba9JUZSM41t9g9CGgkOrNNqwh91Ra7b2q/omV35KFh
Ew+lHUDY1vcAAprzVE5tOjfCprf2v9TUsmOLPKo2vQpm0/1b5zWHoN7ru2+qTtGk3Ldr2OTa
6G2s2ngksfHPMi9t/tHvr/FSv84yv5iinGs5lFZ5a9OFlC/qMC9tJP7goEdly7S1mj046qq7
U5pvumjEmoETS+gpbSAXtCFCx0g+jTnwylhXrd86vvmms+S34c65RoS5r00cl/zgwbqxmIsm
IpCJ1r079r1gZump08tTpFvqvnDTwEVjhH4nIcseG7N/1uPQigfHXPySQ9+elDLviF0HnzAj
ZG6qXcvLA0onqxsxlEUTJe8PSi0mtfGw4j5lQVqGdJdqiV1/BPbPAj1611bz1PpLqXtJbShP
Lo2qThgfptxMa3uf4tBaosksgNLxjVGRQROkSijRrull2bU9oYaBYkXZsfSIBfNPKEXovsPt
gSDDmCE9fSJFuCbn8BwTnSHiGIoDZ/qZnPU4Jn6yo/+GlBmO5QXH7ntEIRWvzYfjxxQJKznm
1KZiRUutMO0au1wxVGBsF03kOJYSoR3TorRH0kGOeojS3tclHlcWaq4rC6AHntUhTyxM7rNE
eUoRxHMxfrqH1CovTGg9sza6znaTulyA1nnpPWL1WdaSG5i1xnVZ33S+YA17asZ69zDrz1x1
GWubs2ir+yybZukt3pqx3rx7qhDtW+x7WW9v/c49jM2oB09+Q6BwCApUhRyZIxOu6b2cY0/n
UXKG5NFbiDPdfTBLvRmSR70Jzdpq/wd63z2M/WvV5b/Y2D9Zb3HZUv2F675phcMfnLr0/T9U
Jb/HqmE23Y8WWxswcH1lJ2hm03msWjdnZPHVIeOikiRzTrLofQjGpvX5oYEhEBSWFQ4tYS05
ylrvAmXA24vqpcGB97PF3s92xkXoOQFDOxfppq2bon6ULfZGT0xqM/m/XwlNab+s+VtOph9r
yf/OpGeR1e5Le+s/9lyuf3/p5ADGNQaTsRlKbiFLqusvnBpQci5oafaqEYyxg1l6i4bSj0re
emPCm6OPLp06nzH2ZhZtO0f2n/CIIeRhNGzZ0lNJUsh/CljdZ/tA+uz7T3QTMwhSwqTVcYFG
Qi8Ku90JwO1U8Nq3jMchhDm2Dib/zQhAuztT/jt7Mf5js9dCRCpgKTlO6f6zpj2XijLzosvj
GLdYtlajruL0esnqoNaMc2/8ZfxsRg2zaSr1Hw9/6GIX1DId8mPeW3DqvqxJ067envv+H/Ih
s3Dd3yf545vn5EsjpRXOVrOiRIW7+cReDy1/98bcH15QQkecGgbmzEu0zueI/V34uuBzfoo8
yL59U3r19g0ZGK3V7FqPGG5jqjN4Dp7U7fjQsrLqiRHzeGjd3Fu/D5a8+MNTeJ+zzS2rv/to
9vO01nhhp/bcI1Mlj1w2ayMf7UiuxbPmQ7l5topslcHEVZLFo/DcT9fwVfYJo71cgUK046xN
7zHPT9OJfjJmEaXpRyw4IkBrjtmv5VV0jhCtPQYwKixK56Bunw+iK7beTz0BhsXorspHIqLJ
CYTOUQmJXT0s3F1jgnSEXLzW7CixZJITG2glRDtSrsonXaqseWHEXmW6OL2/9NSRrCsJkZR2
3PPhqdcnfXjqSNrWxGpKZwrUryeXnnovrS4hgtKvLBuHxwjRjVsC1r0sWWTbRz71D8XO4wI0
AlRWsrZaSj80cHHTg4I0atU69GP/OF9aKkjbE6VfmwpJhcV21QA5kIcI06ZAvUMDre3rBOh1
Ltqe+GtzeoYvekSiXZdQWeSDQQF641y7Rqis6SYh2pRnq7dFnvZU/OhFJSvSBJU8z0xpa8Wz
pzqaFvSkHxYS25YIXTlC3S6dU5RzRJyOLG3/MmlKdDFvDdMTKDCOXllw/qElgeRDUJ4adU6Y
Vc61LlTbCiFQ04sDxOVRDc5X0zfFcYDbti5ymrd2njvg6x06jq822gtcD/A0Og9/gK/G6TtV
5BRszxvCVxMJvgi7fg+ZBGhge+TpUnIhsUEHQhlnq4UhQmLbtHON5QmIjdhLjjhyeK9vSiMe
OcI07JUnuM1FaZzWxNWpPzlw7Closj9/HUxqNQQ9cN+0fMG9BUfIemPERA4A5HUPJrzVSPLX
zYZbX3anYvGnX+Wvo8negh0HeRv9u5o08wIxSMJ7CwV4NFlvbHde3Mszzp1cvTdTteisAH2M
0rWj4jaG5+tNqinYZTN/DYJRUh//Y9zGAKjJpApWXdYJ0CpK156DX4uaqF+/d4PqcpdFwPpG
yVar/RTeeDp87XdnZz13OVHANqai2rxWm7cRXm3u25zXesXvLn+NAvv6Rqy3mkGrNRVs4N9b
cFur2bZvM6BGOQiIvRjFqBv/pNYDavTKAH8to2PnwKe7Oy1mTG/4G/+SLwbthO7pASuj4H6r
BWD6Hv6xcS24SuguJdig/J3eAskwJSQg9jxyj4zdqMQReZHeUiXDoMrf89f+VL1vPoZvOHFM
f1oJYzVTYwWsb1qvmxFotlYjqhkV/GODaJsuxJGZUXpr/iyi3oK20E6YWs0IsJ609oHyBouA
vhZt76ngRTN+8nqPVoUQHUfY9t3g8mhd+BGY2CMHA9Af3R+nztSsjxKwR0ap4xjqlivkaOCD
th+JxGnCYXEyzzwhe4c4THRssqei4jVRb8SHNu+MA+JbDWCzRWsL0WpPIqI1grbComOTPVW8
Jo9EpM6HXJOUp7aWMJ1xNQpoNfGa7Of3TvtSbyKpfdGob9ptxItw7Z0noN467ywBus8XjZrI
G+EyUZq+BHR/O4d3P6e/BRGjHQ9wYDHacRtTJUZ7jIgTq7WuuXy14+0KURqjJ9zezPkl6Ou0
Eqftd55Rcfpl+bJR/vGQVJx+unJZdvvand2Zrrm8ddn0lVlta0bdEqd3VUYmfJW0M16Uxmrk
fhM6XgkVV2+MWFXk+4pmkVoLNpATbqNMBfe1e6PxsxZfdKfRF93WxqnNMOiXfcqq65M49V0E
9GvfZtWYlFMb66X9t7wG/jr1k4Br+zaak9cM38yuUe7Ydc1rjOVs2pbYY99VNK2xsNbbltj3
yOY5UL+Vuc357M8t3YAlCTyWuDyMJ9eCo7oYf33fwremPTSVfJbL5xha7/xmA3VXhXpcThaW
z/G7PtVRNMO3CBp3e5X1pHwzqfmcO1x/xjjBXjTDBVj1cdqMXH14AdVQ0FeAPOvhOm+pSxw1
PNWuL21Tzezak2uY/g6lde6DGZj0JcWQBbS+MEwW29U0W/9AuW1dD36GjSmGLLTrAfU02dFl
pbn6KP8cnro+JH6B/W3rbxLVwbFdo63dE8sXc2sVPXcWkdGNqz/7teqoZKxRF77H1r3Q68za
IvPKBuDOJUNH2Li1nVvTzdz11jFpomsaTtt7Sw95S41Nq5lGPwXiWvA4TumGEvL0mk1rZCbP
bBO1WUSjyGkEgB+IzUQHs2mLzGsYkRUY9GYM3r9PB/IQDQQdgNiv5pj0HXNz9eNFKVf//VoO
WbMZ7Bpj0AO6+Fi0bqRRN+eZ20TJV7GW3GX7cyn5XekReO88693kbrLVVnP0NSZ9IuROddsu
oz6+nlxj6wTqf0sBvHeUtX+UP9nXEIH67hxw+j/HWo1zxOgBQzao7kBNVi0mQt/J/lE7Bn2d
HPzXIFx77JEFavvD29FlPz4ORGj3RGrUJ+1T7A6ftI/19km3+6RbfdL3st7/zzTfFKCCvN7x
4K8hWT6DtpXcYzifY/t2lFxLPnVgrffgugP1RXO1uaf2qAq35lFvNm3wqeQ9PumGwbTJpr04
pY8NpgcoHYcxa2J36a4/h92a0WqLjTLqEjXioTXumrPkWo2Xds/gjA00XgNP3XUvqdWzReou
Sj/pvoxD67w0w/pmvRPc57mwkUl7Ybs2eS7slUFo1jvBvLYSxjvB+fSeB6bzHCcdMmrWcQrN
tMZU9KRDe1w1YnR5UNdXoO3a8YWOCdZ3sL3G8wvSXrv9e6nV91ALL7nrrQNhscm3SVy1sNgW
cL2hIPMkCH09disiXNe9cWjS/KCIxOSlE4WX3NqfXrQyaZg8K/mL+4SX3FqXXrtyd9Ck7IVf
zRYRu+mNooLd4ZFBC8fvEKHrsIYe1AIDfBag9TXbQFcemvoUJpa0/9oPqT8nfwGHU5tRp3ZN
pL4wvigqhrPkXLq77FD4Ac7Y1vnhnvoAUXtK66Ha3xXDnL/Xk7SF1vqPrhrABiv4599Rmx7Y
Tf0CDofGb0ymtLFwbcTwpOPJcmnRP2fm2PR56ePR19xKbkBAGyJz0Y1KSvfVrInJbjla/8QH
tQfKULb1TehLcalObaqLsmmlTp699Eq9/IPaQ5+z6rvb0zvGwQxbaOVrwxZIL1/c9q89hy7D
bNrYFXEhW+aljXcLka5xI7F2+Ycl6A+ZlDZ4a8uFiI6vG7w1+RcxqGwLa1007qqtFcM6vnbW
m1i1VFdm6i03Hi2Ok+/Nc9X67S92GJwl/0b1+QoTi+6VPzLq7YV/QVy0PTk09vmra+29ha7H
LVpvj4gPCXqGQ994sDrtMOKuDQ49UV2+fDSXvhORUwWTfe1g6pDSswczJO2tVbQ+MFEX6h/P
VfK+oBxyQzDrt4W0726prE5UNG+z6x5iR4ug5zn6+TfmW+SYbdRsKN7U/qfYwh2SeKncrm2/
aYAPsm/ByVbTF0va/zRzV3WGoqXSrulluTWgtrGDzw9tjamRSxSN1cI1NbfOeaknXOOID9ot
/XKO/rp1HeMcs4JjmypDFKmitfW/oOHPiNfbJPHOFyEF68+GDREf2/1piWCt8kl7JLXz/Hxw
7XWGrWa4NtCwXRt4nd2r+Y9QYUi/bj3YL95aG9O5tGwQ3RrJoTcPpnfGcOhNKm5tkeZy6A8H
i926kkMP2mqtci7NjYEV8/pBKxctNv2mf9O/6d/0r0jbRqvUA4/xNDw19XMY+PMW+yu8ti/r
cftVaQ7dMX7c/JijL3yLj3wTseVcgVeYgeuYIw6tT16oUIbNXYCX7SIuRFqrOoYqo5cvMfLU
ppDvQx4IeyYJP/+BPDt+dFX9+U8q/Jd4/HgYq25buFyx5YFUKd5yoiIrPkleBxVWLk+kY/d+
PIVzFFhdkH/8yKOpZ65JleHZiqZJbc9VVmQlutWbx9j3zlBcswLoqjvCdpEni65tLuCNAYb0
U2vmIzeXpn7Lxp6oE2PyfgXD/XMGjT/6eoNlA355bcfHMfcjoPoWTDYURn2UoRpUh2cXftx2
/qC8uaw6YlhG1dct740IWVWcHKk+fyoieDANViScimuS3lfVfPXEVD+o6mZjgSQ799GnqwbK
8iN56G01cTfLHqu49El1xGfxk2421kDf5q56tmBgRkXv4Dr9e+WZm2UFVZe+hVYkpE36fkyR
JCF3ytMHDbE1I5YPqvXaBuLa5BaxhaPUfKzWGoqUJE0yxOIeT23Z1jfVH1UemYjnUkL6Gu6V
85P01DSPZze2hPLUr1o2Zjzcsz3j5uVMl4fZeX3XeenpB3PTFy4plM8/GC/V3T4YGfpS4ye9
q96NOSjno+WHn1uZJTtRlXS+rgloCqsWZi/d1btG9djxaD46Z2tC5PLQE9Xjdl9s02kqKyQp
0hq55OHIU4v56Ogr64dJ5tWM2L9F3QRuTJq0LCX2VHXaC1Ahr5IDzJI/gXoNaICYMUYG+R2f
GTn8GvTuWl6a2BkigH7qZUYBORrMfa/BrT0SwpT5v9/XvAaK4kVElzGUmEEbsodTrz8TuQKc
R4hvOINg9U80bH8l1NoTV24CpjtTukAHXM6pR5XtyEo5J1Fq5tfIg+ukRX7ykZL4sA+/0vT1
xhzr7ggN5dRjA/yClvRuDdcnKSOj21qK5Dsmj07Zv+kquNMLVdxqyx7PqcdI31o+r/dAuF6q
rA5ua6mV74garQjbX6np6h1a3d3xNbees3NCVuLtJ4fqiX3YI+27i8LTZ0pC9qPhQNMbt/K7
5h7uelsBYgE/IK0AxXOWA7jBaOjrPZOKzgbW3rj07g5DB6d2JNj2zzkW0Xbi5H7Dhn9vYToa
+tjX/gehgrjFq8EtngAAAABJRU5ErkJggg==</binary>
 <binary id="img_181.png" content-type="image/png">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
gg==</binary>
 <binary id="img_182.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAUAAAABFAQMAAAAB5K3uAAAACXBIWXMAAA7EAAAOxQGMMD9a
AAAABlBMVEUAAAD///+l2Z/dAAAC90lEQVR4nNXVf0gTURwA8LcWTBk2oT+aEGx/ZP7RH15E
GQWdf9Qf0R/9m4gihJjgkPAPHam3qQ2kSC0qSuL6Qyb9pUkljWzIRYrCJgVF+WMnBiskNhl1
k27v9e7dudvubuv+MKI3bhzvPny/773vu3cAmWzgb8PwLsObRyfMwRmWNQdXoyZTf4+ajPh8
2uQYI95pc7BY+98gg69fuwnht7LJz2VmIOc5vDxjBsY9bo5AVu4QxQJwoy04SqBN7kg7KcYQ
fm0LktRQUDo6po1hm+fp8rZ0A09yzZOheaZTMITZ5YGZD/XnxkKLdGfSGGZDZ5bqz7r7SujO
LcPJqPDHx+XKYOgI7Lj/B7i33ILHeKW3q6Y4RKx3Bg8W4p8h9AGpyWuYUyCedFty4L4m/wA4
VqUNz70+FUaBbgx/ygMjq4OQoIVR8p8BKCM/grQR5DiUIDciQKXXhwi05cI52UVq/QrEEVsr
q3DdHkDLDoQuXKBt6JVg44qa+nRzizwxBxmlgF7Yu6nb5fPuRy+vqRCnXvC0kFtbE6IJDC70
BO+caXg1OILhbBbCC63tZIyuXoeLwMBCz+N+qm54cMQeaWTViBV2GTLMuo3Ae5+8wRsn6sb8
/suRlZgKs42BVgKfLXZRo9Ql990ndn42pi6PCpFFWZ50Bb6kLmmbGUAIaGUdYwht51cmD4qO
foMSGqVGaTpVoNYaiPq39BHTjAFMU0Iq37EJgTaAyFfD5cO5qLAVcHI6mDxgzYezifiXqXfn
9RDktD0Hpb0S50PlDTrIA+sSfkr5SnZ0uzA+9X5VB2GNNjVKHQroU4fXhQ3NZBLpq7f0k6Ed
guZwZhPSVzyayYfQyRiVMIximoiitdDrqq21Sw+RAmEGP0sokHcU3j1vvWwcOWTIgyJwmCsd
F2Xos6GhwnDt4eZE0qK82Kjg2SOCN6HN6iSoLQE+BhWFx6cWq5XUxeFAn/MiMgPx+YOyy1Mc
7rR/CEHOVyGn8cpOV3vkz4dLD23A6tv/GwmMpqw+mQ/6AAAAAElFTkSuQmCC</binary>
 <binary id="img_183.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAQwAAABNAQMAAABpMcitAAAACXBIWXMAAA7FAAAOxAFed92+
AAAABlBMVEUAAAD///+l2Z/dAAACR0lEQVR4nGP4Twh8YKCqkveElHw+l09IyfetKwkq+fqc
kJKfhJX83fqYkJKvfWsJKfmNUwHVQ5eaSuqxKrlPWMm9w+/K2h8xOPOYNVnE78eqZNNhH7Pz
B408U7e8uYxDyc4bHgJvZm6ObTF5I+yMXcnGWxwKbyQ3x7IYvfnsiV3Jrluay97f3OyZOunN
ZRxKZknnqL0/vNmZxeydOQ6L7kMYEMqesJJ6rEqwCNJSSf39/fsxJH/s///vPiNYyQMGMEBX
8Q8sygiWgGTWfzgsOoBHyR/CSv4SVkKERUSYQh0lRLhliCn597+dgJKf//4XE1Dyx/5fvURi
jzw+Jfz/6jx1Te4jK2FAA4z/6rgXsqIoqQcS35FMYf1f9VvV9j4+i+T/q3lmquBzy4////uB
zq3Ho+Q3GP8lFLogJQ/whi4YoJnyGSrbeJwfrgTNlAMwJc3MuJTc+7+n/v/u/++fWxfjUnL/
EI/OLO417d/rcSqZP8t3zSRPjc3f69lxmrKLc0ETh0Lz8/p6XD46tyt70azXKtufW9bjMuVh
M4PaLG6V9ufNOJX8f//JnkDQoaQGXEr+k60ES3H474+NPTylommFFnB/nnD0Q0VQiuZ/h/OP
2yfKuMvf3r/n6dvz2JQ8OeTfJp3MIeQ5cTouJV9u+W/qKj85++RE8T1P38hjU/LgFvsWiXLO
XRyCAvuevv2PTcmjW+rbTpplzvIKmGb35ixWJc+k85mkyzjbuIIm7OORxaoECIDp8f98VCF0
JdVAvB+/EizgAwD3DF+IfFZuPQAAAABJRU5ErkJggg==</binary>
 <binary id="img_184.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAQoAAABNAQMAAABkL7jqAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAACb0lEQVR4nGP4TwgcYCBeyX4qKDk2YTkhJfOu9xNScvb6
fEJKvhI25dv19YSUHLP4TkgJPjC4lLxH4r3HruQ+Eg+rzw8w3EnmsTz+8Jx4NEcy72fsSmZw
qkyZ/aTtUJTPzdTf2JXsPliiIJVjdOo0z8ESaxxKTpa0zdI1uvHI537qbuxKZnOqiEm/2XDn
l8+JFGPsSm4k8hi0v225+5v/UQ0OJff/1/+f////TxAPh6fPQ5R8xqMEmyitlDxgkNt/MpEf
Q5L9xgYGBgYHkJIGBhBgRlfxgeEAB1CQAaRkP1gEI4KgkYpPyXfCSogwZVQJ2UqIiAAsSn6i
W/TnPLqSwxhK5qMrQU9Tn//siO25gKzkBwMaYP5THWlyE0UJ4///2/++//99/29TtU+lDPyf
/1g7skiiWNSMYdFuV5OZKEoeYiiZ7KuCYtHvv+hK/l7O7b9Ng9AF4vNQ2Xobe1zRyA4R/cfG
IY9LSfX+j2D2nLfncSj5V8djsOmFzib5uX9xK3lpsshTZcH5uX/k4e5Hs6juhVoTh0ID/1yk
MEJTUvRyTdfpNRPuz3n7H5dz23gYZr3QmSQ/j/c/Lovu7zsAEd+P05T//+8hHEFEDsBuEU4l
WIvDD4iUCuLqcRxQf78e2QGfJxTU/6r/D8oUqEUz/3/5/32F84Cs79PN7aGCKEome9pJ5HQo
caj5zPs+fTt2JZl2tzXuq71WcwEq2VCPTUlzht0FhTtqLxRcmj5v3/4fm5LujH23VW4ue63g
NAuXklmZezhSbqhJaAY1/TAox6rk//8//P///asBpuF/Bf9xKAHm1////qMCLFUWhhIAwSsv
ZobzS7IAAAAASUVORK5CYII=</binary>
 <binary id="img_185.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAALcAAAA9AQMAAADVi2piAAAACXBIWXMAAA7EAAAOxgGnHWyZ
AAAABlBMVEUAAAD///+l2Z/dAAAB5UlEQVR4nK3UzUobURgG4KBBs0jxBtKK+0rNRqqkhi69
hNBNCoZSsKgo1rSLI/QCuuxGnBso3RlBSsafRVbGopuQgBH8GaXWGIJO0mTO23fITJOZjNqK
BwL5Hr6ceb8zmfHBc0nfQ3sw6u3vlOX3xVqnl5YDA8GhQjCSe8mftnxnbGokkcq//r4bSyht
fhI/D88Nb8YjP2LT7f0T8u2LCbN/L5Zw5BkLDDxafBWM+B772/uX0gL9B0JiG5+F7SUzmoAo
Caur2nLHsl1zcs32Xlf7QtOP+lweaPqBr2M13b2P5Rduf25dN+NyO4/u9Podc3Ws3/f7P1zy
yDVroF5VbzztuX1eM49hflGtonpvFwbqJctly7NRA+UMVBlm8VX89RXVwPUqVCPF4gLefu3h
aRbzN7hXf8qZJ0W/+sI8Kyy+tfJkmb+2Q0+y2Bd3zKv95/nc1M/7suh1X9BH7s41JcBPz4O/
N/7VK7d6A1uAUhWooAjo9MNMaCaKxscCfZauK8Ah/dfu+CAf2GSdwwmBstYP/DQ9f/qMD0ic
+00+ETje4j5n9PW1syme0xBfYf4QT+oTfYO+v2buUxmpQb5JAuua7V2hGRW6UoDUy0DWzLNE
l6OCOfWiBlm5FLy19LQ57wfPuf4A9jO6goLW/xUAAAAASUVORK5CYII=</binary>
 <binary id="img_186.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAIwAAAAjAQMAAAB8RHBGAAAACXBIWXMAAA7FAAAOyAHywpKy
AAAABlBMVEUAAAD///+l2Z/dAAAA/0lEQVR4nL3OMUoDQRTG8bGKhZALiPEaqdaDiGBpEYIE
DGkcwQNYW+0FDNYiol06GwsbdbUKFiGBLTayO/P3m4kihFXEwgev+c178z4DbtVsmf7c/FQJ
MFWXaqz6xthf0WIxFeUZtsoD+Z7ozJa8nmDdXlh0l6LJdcmkK+qEqUidDyqH9WS/m6r5a6iL
L+ei3RhiX/QkUjjrv6LWpP8D1dSRMSt6M8040GI6MyzXP1AKFQUkMxj75JMGI9g4gNFDGkgh
3wYFfucQ7rNIXdFmH7+9DneNVqAGzNuaWmvDcTMTeVF+UVDd9uD0MZKOFWmBGz/DVfw+kBCH
QrhFiOWo7/zr+NQbs5rCAAAAAElFTkSuQmCC</binary>
 <binary id="img_187.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAI4AAAAjAQMAAAB4saB7AAAACXBIWXMAAA7FAAAOyAHywpKy
AAAABlBMVEUAAAD///+l2Z/dAAABB0lEQVR4nLXQsUrDUBQG4AtJZ6FTt/YRdCmUDrFv4Bv0
FboGhwod4iDoAwh5Cx0sxNKhON1VEZLBIYPQVAotkpv7+5+o1BqHOvTAuZCPc09+rsLvMupM
bap2z8NVrZMrtzc7Hyh1LFpne5zN2IVcitiLnalBSpv8VfpN9pQUDwvkGSLbETJHpAuvwFtA
issp2RVzV64RmcdtSkjtChWrkvIfZA8rU/9Kvyul21QpV/3x9vumNTzYMhqGX5TaPgymgLYk
LfSSJTD+E7/GpEuh1yDEu58DQUgKhB5GpNYSuDkg3QpNtMaiw/VORHKErucayzsNdIW6Jdkm
1uEI8CWE/xlVXul5E/UDSugNo2vM9icAAAAASUVORK5CYII=</binary>
 <binary id="img_188.png" content-type="image/png">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</binary>
 <binary id="img_189.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAREAAACSAQMAAABYCWwPAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADnklEQVR4nO3Wy08TQRgA8CVIKgm1GDxgAFcTNB5QiSQG
I6YauFTpX2A4NB7QxEc8GGOMaQ2IFwykJEp9UL1zIGAiQZSWJhDCowcOtrzahgT0IKzpgSVs
93NmttTddmYWD1yEDek++OV7zO7MrgCmm0fYiYn9Fya1ayawA7Nk2UEuWSRHMaNJGOvxkCO/
0fwyGpVkEy0GM200INkBVH8z10ALgNIj8g0qWznWuMU1EATo+fFYuyQJAsr9U2+CeKfY4Nv6
OX2cZb1ZsOF9ANQrVR06A3oTi7vRXhWh5PQ400CeVpHcEGMbBWdTRHByDJBsQUjxDB5DHM3P
MzIOlLnTjOewRWuNa2S7LhDreQ5qFXGNqgvEnBehvxUx49TZ05Bj/KFMRcxcEdJagGtKHmUq
YprXLgUHSnBMqquJVIxuP9t018dJRUFOXxfK1rVA9hSzHsmX0G5/i/JUu/Iix2xGpzrIdJbL
L6PfJPnLMhCa+Twi4UCeCnxmg61cA1OtMcjHgQrQT+w+1SQqO0lFMp4no81Us1VZ6saFkrk8
RzfQ6kPADTLuXWYYBRmwaWMYwfVIEkluWHu/4HHSzDzua0ULZjAkuk3BZtWPzsIn2kof5Lty
1/D49jgjU9k/W9k9kWvUooqMOTRQ0h5JUN4FcWvGnO2/0R4JUoySt21W7nzyVUUcXCMl2spd
85SaQc7UAzAC2b2n/5d+xmSO2X5WFXf6nGOo92v3TMdY7XL7Ra6ZtnQWOw50ClyzOpyfV+Dl
mqBluEQo8OQamy5Oc9+s4+SrbKO4RGKGk6SetuKrRdXZNQtVMWJqXVrvSXSWZX6/P06MWuNk
mY21hVNhEmfAyhjD8eW1aU/aFNFNb1dwpfGwNk8HnFTj621JxtfcoOI1YYhqQoUf3iY3cWs4
16CLvIuMZu56w3kH6d7OWuv6rkWt3npCWGvmWFl19OF3jTCM5J3sczxHBwFEqCahHvUWfsQf
t3ERGObl6GLg1pkelEj7TKKZd28Wolb0PeaxA9sMTYW+QoZQzdLQTLojjvFYQBHcwDMz9zbg
me4azYR1tbAMGjoRzIxkIDQzAllftRRjJ59IXLNQZwMzE8kDU2Ps6V/M4E1DPdnlEHNpQm9i
YYpRqxcNJpuQOH1NXCMdQbNvhG9kPJdNDMnV7zA3g8vmRr/tm32zB4xamjQ3k7e5hrxzQ07z
emat5qZrB6bGPJf65K6pUQ66zHPpt71tcsi+0Zs/wGKSnGWdVG4AAAAASUVORK5CYII=</binary>
 <binary id="img_190.png" content-type="image/png">iVBORw0KGgoAAAANSUhEUgAAAdoAAACNAQMAAAAq1ikXAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAABlBMVEUAAAD///+l2Z/dAAADjklEQVR4nO3Y30tTURwA8A2TISwm+KAgcc2H6KWwzDL8
cfFRYn+C6IiUQvDBh4qya860h1SIaGGCFtGztCghbYr0C1IffHAzzQ2hPW66yDt3d7+dc7ep
29y853sgo3Ye7u79bh+/55x73PfcGfohtUXTIhnatGFM4sDvLRzY5RXpiX+3AywYDPTko4DD
Cu34i9UkLOvFECQnp9xJOCjpxUBS5y/tfJzikH7sBfiwdFvcg7f0Y1WAOcctFw7DDCgnbiRi
rJhMeGunuKaFggaDBJsMmIw6GHrm105lw08JZBasCuFQnWu327paApNRR4xOLFYE2VouIjHM
wkxxMxbL0sZ5CYvJGm3F4yCogMZg4cFe4MCqhQODjwcrIgcmC4UDywIHJv8eHFgROLCWGo3p
hKMx2HmwLEbxGGaVOB5CYLm2jv6J0fiKYcPQfYwcPlkggsFyPjl870Big1mCHiTWys12Gw6H
6WxvIzNri2SY4nAYFAmBZ+lse33ZC14mTFoEAh2BypClE4cXjvcV2u43ILHZuWh2h7H4VdGg
24fDgfbxb2Z3Iw57bX0lNg9yzGEYJEc/BtPtZ/qberHEgfFr+7Dx0ILiBwmH50w9hQ3mCiT2
Ty42XnmcMXVWPGOaLG7In9aPNy0antIyt00uWrscWbotJl3LtlENV9u0MfcVNhyp0J25oH2N
YrXGqt2qwBkYuZklcxL2PWnRMDibYvc5lK1mJuPAV/fpGH7blFgkWbG4e7GwPu8po1/64k7m
rG1v5mi/fX5FBNVIu21lw5F255Z3jWxBu8jFhI0MVz/+UXrSvrVBrIWOUglB+iN9OhZjJ796
Ppe/I69yHsueJIaDj5YLnpPNjJLHvqFRz9qWr76E2GSx4geOluWj5LVbYsURGOstvmYUyGSJ
wIrtKzWviy6RbYzXBMz44r2n3e5mV8KyzXZdledhKblJccuGp6o89ggE8xIxNnxugNxo406M
DfdatMWBwiMDAbi7J8aER6PxG4zBZMBJv0yxYTkvKcaCRfpogMT1YlBE49o7ppQYA64wpMa0
inFZFy4T9sFqzRddOKVYxXHl6j4fTscV+2EY14frhdQY3am6mnThtMRaucFirdtvMm9XD8YT
6xxYV8vhHM7hHP4HsVqd+Xn9QKwMI7+3tYqxhK0YtFY5eMrNBQ6sXufASqeVA5fkCt3fh12H
ljmH/yROj/2PWHeb/g28L7CvSIyCUQAAAABJRU5ErkJggg==</binary>
</FictionBook>
