Руководство beej по сетевому программированию

Beej’s Guide to Network Programming

This is the source for Beej’s Guide to Network Programming.

If you merely wish to read the guide, please visit the Beej’s Guide to
Network Programming website.

This is here so that Beej has everything in a repo and so translators
can easily clone it.

Build Instructions

Dependencies

  • Gnu make (XCode make works, too)
  • Python 3+
  • Pandoc 2.7.3+
  • XeLaTeX (can be found in TeX Live)
  • Liberation fonts (sans, serif, mono)

Mac dependencies install (reopen terminal after doing this):

xcode-select --install                  # installs make
brew install python                     # installs Python3
brew install pandoc
brew install mactex --cask              # installs XeLaTeX
brew tap homebrew/cask-fonts
brew install font-liberation            # installs sans, serif, and mono

Build

  1. Type make from the top-level directory.

    If you have Gnu Make, it should work fine. Other makes might work as
    well. Windows users might want to check out Cygwin.

  2. Type make stage to copy all the build products and website to the
    stage directory.

  3. There is no step three.

You can also cd to the src directory and make.

make clean cleans, and make pristine cleans to «original» state.

To embed your own fonts in the PDFs, see the src/Makefile for examples.

The upload target in the root Makefile demonstrates the build steps
for a complete release. You’ll need to change the UPLOADDIR macro in
the top-level Makefile to point to your host if you want to use that.
You’re free to upload whatever versions you desire individually, as
well.

Build via Docker

If you don’t want to mess with a local setup, you can build via Docker.

  1. Run docker build -t beej-bgnet-builder . from the top-level directory.

  2. Run docker run --rm -v "$PWD":/guide -ti beej-bgnet-builder.

    This will mount the project where the image expects it, and run make pristine all stage, leaving your ./stage directory ready to be published.

Pull Requests

Please keep these on the scale of typo and bug fixes. That way I don’t
have to consider any copyright issues when merging changes.

TODO

Content

  • File transfer example maybe in son of data encapsulation
  • Multicast?
  • Event IO?

Bug fixes

  • When pandoc 2.8 comes up, switch all man page subheaders to h3 and supress
    them from the table of contents.

Руководство Beej по сетевому программированию, используя интернет-сокеты

Дата публикации 2 ноя 2005

Руководство Beej по сетевому программированию, используя интернет-сокеты — Архив WASM.RU

Содержание:

  • 1. Интро
    • 1.1 Аудиенция
    • 1.2 Платформа и Компилятор
    • 1.3 Официальная страничка
    • 1.4 Замечание для Solaris/SunOS программеров
    • 1.5 Замечание для Windows программеров
    • 1.6 Замечания по обратной связи
    • 1.7 Зеркала
    • 1.8 Замечание для переводчиков
    • 1.9 Копирайт и Распространение
  • 2 Что такое сокет?
    • 2.1 Два Вида Интернет Сокетов
    • 2.2 Low level Nonsense and Network Theory
  • 3 struct и Управление Данными
    • 3.1 Сконвертируй Natives!
    • 3.2 IP адреса и Как Ими Управлять
  • 4 Системные Вызовы
    • 4.1 socket() — Получи Файловый Дескриптор!
    • 4.2 bind() — На каком я порту?
    • 4.3 connect() — Эй, ты!
    • 4.4 listen() — Кто-нибудь мне позвонит?
    • 4.5 accept() — Спасибо, что обратились к порту 3490
    • 4.6 send() и recv() — Поговори со мной, крошка!
    • 4.7 sendto() и recvfrom() — Поговори со мной в DGRAM-стиле
    • 4.8 close() и shutdown() — Убирайся прочь!
    • 4.9 getpeername() — Кто ты?
    • 4.10 gethostname() — Кто я?
    • 4.11 DNS — Ты говоришь «whitehouse.gov», я же говорю «198.137.240.92»
  • 5 Технология Клиент-Сервер
    • 5.1 Простой Поточный Сервер
    • 5.2 Простой Поточный Клиент
    • 5.3 Datagram Сокеты
  • 6 Более Продвинутые Техники
    • 6.1 Блокировка
    • 6.2 select() — Синхронный I/O мультиплексинг
    • 6.3 Управление Частичными send()
    • 6.4 Сын Инкапсуляции Данных
  • 7 Частые Вопросы
  • 8 Дисклэймер и Помощь

1. Интро

Эй! Программирование сокетов тебя утомляет? Ты считаешь его чересчур сложным, чтобы изучать его по man страницам?
Ты хочешь круто программировать под Инет, но у тебя нет времени, чтобы разобраться со всеми этими структурами,
пытаясь выяснить, должен ли ты вызвать bind() перед connect(), итд, итд?

Что ж, знаешь что? Я уже сделал все эти мерзкие дела, и я хочу поделиться этой информацией со всеми! Ты пришел по
нужному адресу. Этот документ даст среднему компетентному Си программеру необходимый ему/ей уровень, чтобы
разобраться со всем этим сетевым шумом.

1.1 Публика

Этот документ был написан как туториал. Вероятнее всего, он наиболее подойдет тем, кто только начал разбираться с
программированием сокетов и ищет надежную основу. Но это не полноценное руководство.
Надеюсь, однако, что этого будет достаточно для того, чтобы понять все, что вы узнавали из man страниц…:smile3:

1.2 Платформа и Компилятор

Код, приведенный в этом документе, был скомпилирован на Linux PC используя GNU gcc компилятор. Он подойдет и к
любой иной платформе, которая использует gcc. На самом деле это не относится к тебе, если ты программируешь под
Windows — если так, то смотри раздел «Замечание для Windows программеров».

1.3 Официальная Страничка

Этот документ расположен в Калифорнийском Университете, Чико, по адресу
http://www.ecst.csuchico.edu/~beej/guide/net/ .

1.4 Замечание для Solaris/SunOS программеров

Когда компилируете под Solaris или SunOS, вам необходимо указывать некоторые дополнительные ключи в командой
строке для линковки с нужными библиотеками. Для того чтобы добиться этого, просто добавьте «-lnsl -lsocket
-lresolv» к концу команды компилеру, как показано ниже:

$ cc -o server server.c -lnsl -lsocket -lresolv

Если вы все еще получаете ошибки, попробуйте далее добавить «-lxnet» к концу командной строки. Я не знаю в
точности, для чего это, но некоторые нуждались в этом.
Другая область, где вы можете натолкнуться на проблемы — это вызов setsockopt(). Его прототип различается от
прототипа на моем Линуксе, поэтому вместо

int yes=1;

попробуйте ввести этот вариант:

char yes=’1′;

Т.к. у меня нету Sun дистрибутива, то я не тестировал вышенаписанное — это всего лишь советы, которые я получал
по эл.почте.

1.5 Замечание для Windows Программистов

Я очень не люблю Виндоус, и советую тебе пересесть на Linux, BSD, или Unix. Тем не менее, как уже было сказано, ты
все же можешь использовать весь этот материал и под Винды.
Для начала забудь про большинство системных заголовочных файлов, о которых я далее буду упоминать. Все что тебе
потребуется — это:

#include «winsock.h»

Стоп! Тебе также понадобится обратиться к WSAStartup() перед началом работы с этой библиотекой сокетов. Это
сделать очень просто:

  1.   #include «winsock.h»
  2.         WSADATA wsaData;   // если это не пойдет,
  3.         //WSAData wsaData; // то попробуй этот вариант
  4.         if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
  5.             fprintf(stderr, «WSAStartup failed.n»);

Также тебе нужно будет сообщить своему компилятору, чтобы он слинковал Winsock Библиотеку, обычно называемую
wsock32.lib, winsock32.lib, или еще как-нибудь. Под VC++ это можно сделать из раздела Settings в Project menu…
Кликни вкладку Link, и найди «Object/library modules». Попросту добавь туда «wsock32.lib».

Наконец, тебе понадобится вызвать WSACleanup() когда ты закончишь работать с этой библиотекой. За подробностями
обращайся к онлайн хелпу.

Когда ты завершишь все эти приготовления, ты сможешь компилировать все примеры ниже, за редкими исключениями. К
примеру, если ты не можешь сделать close() чтобы закрыть сокет, попробуй closesocket() вместо этого. Также, функция
select() работает только с дескрипторами сокета, а не с файловыми дескрипторами (как 0 в stdin).

За большей информацией по Winsock читай Winsock FAQ.

Далее, я слышал, что Windows не имеет системного вызова fork(), к которому, к сожалению, я обращаюсь в некоторых
своих примерах. МОжет быть тебе стоит слинковать библиотеку POSIX или еще что-нибудь, чтобы заставить fork()
работать, или же просто использовать CreateProcess() вместо этого. fork() не принимает аргументов, в то время как
CreateProcess() принимает около 48 миллиардов аргументов. Добро пожаловать в великолепный мир Win32
программирования.

1.6 Замечания по обратной связи

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

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

Теперь, объяснив вам как задавать мне вопросы, я хотел бы сказать вам, что я в полной мере ощущаю полезность этого
своего туториала за все эти годы. Это большая моральная поддержка и я очень рад слышать, что этот туториал
оказывается полезным! Спасибо вам!

1.7 Зеркала

Вы без проблем можете создавать зеркала этого сайта (в нашем случае — документа — прим. перев.), как приватные,
так и публичные. Если вы выкладываете публичное зеркало этого сайта, и хотите чтобы я поместил на вас ссылку на
своей страничке, черкните мне пару строк на это мыло beejATpiratehaven.org

1.8 Замечания для переводчиков

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

1.9 Копирайты и Распространение

Руководство Beej по Программированию Сокетов защищено правами. Copyright © 1995-2001 Brian «Beej» Hall.
Руководство может быть сводобно перепечатано с учетом соблюдения копирайтов.

2. Что такое сокет?

Ты слышишь разговоры о сокетах все время, и, вероятно, ты задумываешься о том, что же это такое. Что ж, сокеты —
это просто способ общаться программам, используя стандартные Юниксовые дескрипторы файлов.
Что?

Ок — ты наверно слышал на каком-нибудь хакерском сайте утверждения в духе: «Эй, в Юниксе все — это файл!». О чем
это они? Эти люди имели ввиду факте, согласно которому когда Юниксовые программы манипулируют с Вводом/Выводом, они
делают это через чтение или запись в файловый дескриптор. Файловый дескриптор — это просто целое число,
ассоциируемое с открытым файлом. Но такой файл может быть и сетевым соединением, и FIFO, и pipe, и терминалом, и
обычным файлом на диске, итд. Все в Юниксе — это файл! Поэтому когда ты хочешь обменяться данными с др. программой
по Инету, тебе придется делать это через файловый дескриптор. В это время ты поймешь, о чем я.
«Откуда же я получу этот файловый дескриптор для коммуникации с сетью, Мр. Умные-Штаны (в оригинале: «Mr.
Smarty-Pants»)» — вероятно, это последний вопрос, будоражащий твой ум на этот момент. Я отвечу на него следующим
образом: Ты обращаешься к системной функции socket(), она возвращает файловый дескриптор, и затем ты можешь
общаться с сетью при помощи специальных системных сокетных вызовов send() и recv().
«Но…эй!» — удивитесь вы. «Раз это всего лишь файловый дескриптор, то какого фига я не могу обойтись стандартными
read() и write() для коммуникации через сокет?». Короткий ответ: «Ты можешь!». Длинный ответ: «Ты можешь, но send()
и recv() предоставляют гораздо больший контроль над передачей твоих данных.»
Что дальше? Как насчет этого: существуют все виды сокетов. Есть DARPA Интернет адреса (Интернет Сокеты), имена
путей на локальной машине (Юникс Сокеты), CCITT X.25 адреса (X.25 Сокеты, которые ты можешь проигнорировать), и,
вероятно, множество других, зависящих от Юникс дистрибутива, который у тебя стоит. Этот документ описывает работу
только с первыми: Интернет Сокетами.

2.1 Два вида Интернет Сокетов

Что это? Существует два вида Интернет сокетов? Да. Эээ, нет. Я лгу. Существует больше, но я не хочу пока что тебя
ими пугать. Я только собираюсь рассказать тебе о двух их типах. Но я хотел бы сказать, что «Raw Сокеты» также очень
мощные и я бы советовал тебе глянуть на них.

Ладно, что это за два вида? Первый — это «Stream сокеты»; другие — это «Datagram сокеты», которые также называются
«SOCK_STREAM» и «SOCK_DGRAM» соответственно. Datagram сокеты иногда называются «connectionless sockets».
Stream сокеты — это надежные двухсторонне-соединенные коммуникационные потоки. Если ты отправляешь два элемента в
сокет в порядке «1,2», то они прибудут на др. сторону в порядке «1,2». Они также будут без ошибок. Всякие ошибки,
которые ты обнаружишь — это вымысел твоего расстроенного мозга, и они не будут здесь обсуждаться.

Что используют stream сокеты? Что ж, ты наверно уже слышал о telnet приложениях? Так вот, они используют stream
сокеты. Все символы, которые ты набираешь, должны быть переданы в том же самом порядке на др. сторону, правильно?
Также, веб-браузеры используют HTTP протокол, который использует stream сокеты для того, чтобы получать странички.
Правда, если ты зателнетишься на вебсайт на порт 80 и введешь «GET /», telnet-приложение сдампит тебе весь HTML код!

Каким образом stream сокеты достигают этого высокого уровня при трансмиссии данных? Они используют протокол,
получивший название «The Transmission Control Protocol», проще говоря, «TCP» (смотри RFC-793 для более подробной
информации по этому протоколу). TCP проверяет, доставлены ли твои последовательные данные, и не произошли ли
ошибки. Вероятно ты слышал о «TCP» ранее как о наиболее лучшей части «TCP/IP», где «IP» обозначает «Internet
Protocol» (смотри RFC-791). IP главным образом обслуживает Internet роутинг и не предназначен для интеграции данных.

Круто. А как насчет Datagram сокетов? Почему они называются «connectionless»? Каково их предназначение? Почему они
ненадежные? Что ж, вот некоторые факты: если ты отправляешь датаграмму, она может прибыть до назначения, хотя
порядок датаграммы может быть изменен. Если датаграмма приходит, то данные этого пакета будут без ошибок.

Сокеты датаграмм также используют IP для роутинга, но они не могут применять TCP; они используют вместо этого
«User Datagram Protocol», т.е. «UDP» (смотри RFC-768).

Почему они «connectionless»? Что ж, главным образом это потому, что ты не поддерживаешь открытое соединение, как
ты делал это при работе с сокетами потоков. Ты просто создаешь пакет, вносишь в IP заголовок (header) информацию о
месте назначения, и отправляешь его. Не нужно никаких соединений. Они главным образом используются для передач
информации от пакета к пакету. Примеры некоторых приложений: tftp, bootp, итд.

«Хватит» — крикнешь ты. «Как могут эти программы работать, если датаграммы могут быть потеряны?!» Что ж, мой друг,
каждая датаграмма имеет свой собственный протокол в верхушке из UDP. К примеру, протокол tftp говорит, что для
каждого пакета, который будет отправлен, получатель должен отправить пакет, который скажет «Я получил его!» (т.н.
«ACK» пакет). Если же отправитель исходного пакета не получит ответа, скажем, в течение 5 секунд, он переотправит
пакет заново, пока не получит ACK. Эта допущенная процедура очень важна, когда разрабатываются SOCK_DGRAM
приложения.

2.2 Low level Nonsence and Network Theory

Т.к. я упомянул о слоях протоколов, то настало время поговорить об основных принципах работы сетей, и привести
некоторые примеры создания SOCK_DGRAM пакетов. Что касается практики, то вы можете пропустить эту секцию. Хотя, это
хорошее введение.

Эй, детишки, пришло время изучить Инкапсуляцию Данных! Это очень очень важно. Это настолько важно, что ты можешь
поступить на курсы по сетям, для того чтобы изучить все это, здесь, в Штате Чико (Chico). Главным образом
инкапсуляция данных говорит о том, что пакет был создан и перенесен («инкапсулирован») в заголовок (и реже в низ —
«footer») первым протоколом (скажем, TFTP), затем весь пакет (включая TFTP заголовок) инкапсулируется снова
следующим протоколом (скажем, UDP), затем снова следующим (IP), и, наконец, финальным протоколом, уже на хардварном
(физическом) уровне (скажем, Ethernet).

Когда др. компьютер получает пакет, железо «разбирает» Ethernet заголовок, ядро «разбирает» IP и UDP заголовки,
программа TFTP — TFTP заголовок, и, наконец, получает данные.

Теперь я могу наконец-то поговорить о неизвестной Layered Network Model. Эта Сетевая Модель описывает систему
функциональностей сети, которая имеет множество преимуществ перед др. моделями. Например, ты можешь написать
программу с сокетами, которые в точности те же самые, не забивая себе голову о том, «как физически передаются
данные» (Ethernet, AUI, итд), потому что это все обрабатывают программы на более низких уровнях. Действительное
сетевое железо и топология остаются, таким образом, прозрачными для программера сокетов.

Ниже я привожу такую полноценную модель. Запомни ее для экзаменов на уроках по сетям:

  • Приложение (Application)
  • Представление (Presentation)
  • Сессия (Session)
  • Передача (Transport)
  • Сеть (Network)
  • Связь Данных (Data Link)
  • Физический уровень (Physical)

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

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

Уровень приложения (Application Layer) — telnet, ftp, итд

Уровень передачи данных от хоста к хосту (Host-to-Host Transport Layer) (TCP, UDP)

Интернет уровень (Internet Layer) (IP и routing)

Уровень Интернет доступа (Network Access Layer) (Ethernet, ATM, or whatever)

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

Видишь как много нужно сделать чтобы построить простой пакет? А ты должен набрать заголовки пакета самостоятельно,
используя «cat»! Шучу. Все что тебе нужно сделать для сокетов потока — это вызвать send(). Для датаграмм сокетов
тебе нужно инкапсулировать пакет любым методом и применить sendto(). Ядро выстроит Уровень Передачи и Интернет
Уровень для тебя, а железо обеспечит Network Access Layer. Ах, современная технология!

Итак, заканчиваем наше введение в теорию сетей. О да, я забыл рассказать тебе о роутинге! Это так, я вообще не
рассказал об этом. Роутер разделяет пакет на IP заголовок, вносит коррективы по своей роутинг таблице, бла бла
бла… Глянь в IP RFC если ты очень этим интересуешься. Если ты никогда не изучал это, что ж, ты выживешь.

3. структуры и Управление Данными

Что ж, наконец-то мы здесь. Пришло время поговорить о программировании. В этом разделе я освещу различные типы
данных, используемые для интерфейса сокетов, т.к. некоторые из них по-настоящему скучные для рассмотрения.

Для начала самый простой: дескриптор сокетов. Дескриптор сокетов имеет тип int. Обычный int.

Вся фишка начинается отсюда, поэтому читай все и разбирайся вместе со мной. Запомни это: существует два порядка
байтов: либо сначала первый наиболее значимый байт (иногда называемый «октет»), либо последний. Последний наз.
«Сетевой Байтовый Порядок» («Network Byte Order»). Некоторые машины хранят свои числа в «Network Byte Order», а
некоторые — нет. Когда я говорю, что что-то должно быть в в «Network Byte Order», ты должен вызвать функцию (такую
как htons()) для того, чтобы поменять это что-то с «Host Byte Order»а. Если же я не упоминаю «Network Byte Order»,
то ты должен оставить значение в «Host Byte Порядке».

Для любопытных, «Network Byte Order» также известен как «Big-Endian Byte Order».

Моя первая StructTM — struct sockaddr. Эта структура содержит информацию об адресе сокета для большинства сокетов:

  1.         unsigned short    sa_family;    // адрес семейства, AF_xxx
  2.         char              sa_data[14];  // 14 байт адреса протокола

sa_family может быть различным, но у нас будет только AF_INET на протяжении всего документа. sa_data содержит
адрес назначения и номер порта для сокета.

Чтобы общаться с struct sockaddr, программеры создали параллельную структуру: struct sockaddr_in («in» для
«Internet»).

  1.         short int          sin_family;  // адрес семейства
  2.         unsigned short int sin_port;    // номер порта
  3.         struct in_addr     sin_addr;    // Internet адрес
  4.         unsigned char      sin_zero[8]; // такого же размера, как и struct sockaddr

Эта структура позволяет легко ссылаться на элементы адреса сокета. Заметь, что sin_zero (который включен чтобы
совместить структуру по длине со структурой sockaddr) должен быть выставлен в нули через функцию memset(). Также,
указатель на структуру sockaddr_in может быть приведен к указателю на struct sockaddr, и наоборот. Поэтому, даже
если socket() «хочет» структуру sockaddr*, ты все же можешь использовать struct sockaddr_in и перевести ее в
последнюю минуту! Заметь далее, что sin_family обращается к sa_family в struct sockaddr и должна быть выставлена в
«AF_INET». Наконец-то, sin_port и sin_addr должны быть в Network Byte order!

«Но» — возразишь ты, — «как может целая структура — struct in_addr sin_addr быть в Network Byte Order?» Этот
вопрос требует внимательного изучения структуры struct in_addr, одного из наихудших объединений в мире:

  1.    // Internet адрес (структура чисто по историческим причинам)
  2.         unsigned long s_addr; // 32-бита, т.е. 4 байта

Что ж, раньше она была объединением (union), но в наши дни не используется. Хорошее избавление. Поэтому, если ты
объявил ina как struct sockaddr_in, то ina.sin_addr.s_addr ссылается на 4-байтный адрес (в Network Byte порядке).
Заметь, что даже если твоя система все еще использует богопротивный union для struct in_addr, ты тем не менее
можешь обращаться к 4-байтному IP адресу в точности также, как я делал это выше (это из-за #defines).

3.1 Сконвертируй Natives!

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

Ладно. Есть два вида, которые ты можешь сконвертировать: short (два байта) и long (четыре байта). Эти функции
также работают и с беззнаковыми варициями. Скажем, ты хочешь перевести short с Host Byte Order в Network Byte
Order. Начни название функции с «h» (для «host»), добавь «to», затем «n» (от «network»), и «s» (т.е. «short»).
Получим h-to-n-s, т.е. htons().
Это очень просто…

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

  • htons() — «Host to Network Short»
  • htonl() — «Host to Network Long»
  • ntohs() — «Network to Host Short»
  • ntohl() — «Network to Host Long»

Ты наверно уже подумал, что разобрался с этим вопросом. Ты можешь спросить: «А как же мне к примеру изменить
порядок байтов на символ?». Наверно, ты думаешь, что это невозможно. Также ты наверно думаешь, что твой компьютер
уже использует Network Byte Order, и поэтому тебе не нужно вызывать htonl() для своего IP адреса. Ты был бы прав,
но если бы ты обратился к машине, имеющей обратный порядок байтов, то твоя программа, вероятнее всего, вылетела.
Будь портируемым! Это мир Unix! (настолько, насколько Б.Гейтс думает с точностью до наоборот). Помни: переводи свои
байты в Network Byte Order перед тем, как отправлять их в сеть.

Последний вопрос: почему sin_addr и sin_port должны быть размещены в Network Byte Order в struct sockaddr_in, но
не должны в sin_family? Ответ: sin_addr и sin_port инкапсулируются в пакет в IP и UDP разделы соответственно. Таким
образом, они должны быть в Network Byte Order. Однако, поле sin_family используется только лишь ядром для
определения типа адреса, который содержит структура, поэтому оно должно быть в Host Byte Order. Также, т.к.
sin_family не отправляется в сеть, то это поле опять таки может быть в Host Byte Order.

3.2 IP адреса и Как Ими Управлять

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

Для начала, скажем, что у тебя есть struct sockaddr_in ina, и у тебя есть IP «10.12.110.57», который ты хочешь
сохранить в этой структуре. Для этого тебе понадобится функция inet_addr(), которая конвертирует IP адрес,
записанный в привычном нам виде цифр, разделенных точками, в unsigned long. Это можно, к примеру, сделать следующим
образом:

  1.  ina.sin_addr.s_addr = inet_addr(«10.12.110.57»);

Обрати внимание на то, что inet_addr() возвращает адрес уже в Network Byte Order, посему тебе не нужно далее
вызывать htonl().

Строчка кода выше не очень грамотна, т.к. отсутствует проверка на ошибки. Функция inet_addr() возвращает -1 в
случае ошибки. Помнишь бинарные числа? Беззнаковое -1 в этом случае обозначает 255.255.255.255! А это уже
широковещательный адрес! Неправильно!! Поэтому не забывай самостоятельно делать проверку на ошибки.

Вообще-то существует более ясный интерфейс, который ты можешь применять вместо inet_addr(). Это — функция
inet_aton() («aton» обозначает «ascii to network»).

Пример объявления:

  1.     #include «sys/socket.h»
  2.     #include «netinet/in.h»
  3.     #include «arpa/inet.h»
  4.     int inet_aton(const char *cp, struct in_addr *inp);

А вот пример простейшего использования этой функции:

  1.     struct sockaddr_in my_addr;
  2.     my_addr.sin_family = AF_INET;         // Host Byte Order
  3.     my_addr.sin_port = htons(MYPORT);     // короткое целое, в Network Byte Order
  4.     inet_aton(«10.12.110.57», &(my_addr.sin_addr));
  5.     memset(&(my_addr.sin_zero), », 8); // обнуляем всю оставшуюся структуру

inet_aton(), в отличии от практически всех остальных связанных с сокетами функций, возвращает не нулевое значение
в случае успеха, и нуль при ошибке. (Если кто-то знает почему, пожалуйста, разъясните.)

К сожалению, не все платформы поддерживают inet_aton(), поэтому, хоть и ее использование предпочтительно, в этом
руководстве мы все же будем использовать более старую общую функцию inet_addr().

Хорошо, теперь ты можешь конвертировать строковый IP адрес в их бинарное представление. А как насчет других
способов? Что если у тебя есть struct in_addr и ты захочешь напечатать ее в стандартном виде? В этом случае тебе
понадобится обратиться к функции inet_ntoa() («ntoa» обозначает «network to ascii») например так:

  1. printf(«%s», inet_ntoa(ina.sin_addr));

Эта строчка выведет IP адрес. Заметь, что inet_ntoa() в качестве своего аргумента принимает struct in_addr, а не
long. Также заметь, что она возвращает указатель на char. Он указывает на статически сохраненный char массив в
inet_ntoa(), поэтому каждый раз, когда ты вызываешь inet_ntoa(), функция перезаписывает последний IP адрес.
Например:

  1.     a1 = inet_ntoa(ina1.sin_addr);  // это     192.168.4.14
  2.     a2 = inet_ntoa(ina2.sin_addr);  // а это   10.12.110.57
  3.     printf(«address 1: %sn»,a1);
  4.     printf(«address 2: %sn»,a2);

напечатает

Поэтому, если тебе нужно сохранить адрес, скопируй его через strcpy() в свой символьный массив.
Это все по этому вопросу. Далее ты научишься конвертировать строки типа «whitehouse.gov» в их соответствующий IP
адрес (смотри DNS ниже).

4. Системные вызовы

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

Главная проблема, возникающая у большинства, — это порядок, в котором вызывать эти функции. В этом случае не
помогут man’ы, как ты уже наверно выяснил. Что ж, для того чтобы помочь в этой страшной ситуации, я попробую
расположить эти системные вызовы в следующих разделах в том же самом порядке их вызова, который и нужен для твоих
программ.

4.1 socket() — Получи Файловый Дескриптор!

Я полагаю, что я больше не могу откладывать этот разговор на будущее. Я должен объяснить вам работу с socket().
Вот ее объявление:

  1.     #include «sys/types.h»
  2.     #include «sys/socket.h»
  3.     int socket(int domain, int type, int protocol);

Но что это за аргументы? Первый — тип домена; должен быть выставлен в «AF_INET», также как и в struct sockaddr_in
(выше). Следующий аргумент говорит ядру о том, какой это сокет: SOCK_STREAM или SOCK_DGRAM. Последний должен быть
установлен в «0», чтобы сокет мог выбрать корректный протокол, базируясь на типе. Заметь, что есть гораздо больше
доменов и типов, чем я здесь перечислил. Посмотри socket() man страницу. Также, есть более «хороший» способ получиь
протокол — смотри getprotobyname() man страницу.

socket() просто возвращает тебе сокетовый дескриптор, который ты далее можешь использовать в системных вызовах,
или же -1 в случае ошибки. Глобальная переменная errno устанавливается в значение ошибки (смотри perror() man
страницу).

В некоторых документациях упоминается мистическое «PF_INET». Этот странный зверь редко встречается в природе, но я
мог бы немного рассказать о нем здесь. Когда-то, много лет назад, думали, что семейство адреса (для которого служит
«AF» в «AF_INET») может поддерживать несколько протоколов, которые были в struct sockaddr_in и PF_INET в твоем
вызове socket(). Ты можешь использовать AF_INET повсюду. И, т.к. W.Richard Stevens использовал его в своей книге,
то и я буду в этом туториале.

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

4.2 bind() — На каком я порту?

После того, как у тебя есть сокет, ты можешь захотеть «связать» этот сокет с портом на своей локальной машине.
Обычно так делают, когда хотят «прослушать» (listen() ) входящие соединения на указанный порт. Номер порта
используется ядром, чтобы ассоциировать полученный пакет с сокетовым дескриптором определенного процесса. Если же
ты собираешься просто подсоединиться через connect(), то тогда bind() здесь будет лишней. В любом случае изучи ее.

Вот ее объявление:

  1.     #include «sys/types.h»
  2.     #include «sys/socket.h»
  3.     int bind(int sockfd, struct sockaddr *my_addr, int addrlen);  

sockfd — это сокетовый дескриптор файла, полученный от socket(). my_addr — это указатель на struct sockaddr,
которая содержит информацию о твоем адресе, о порте, и о IP. addrlen() может быть выставлена в sizeof(struct
sockaddr).
Фьюю…Вот небольшой пример для большего понимания:

  1.     #include «string.h»
  2.     #include «sys/types.h»
  3.     #include «sys/socket.h»
  4.     #include «netinet/in.h»
  5.         struct sockaddr_in my_addr;
  6.         sockfd = socket(AF_INET, SOCK_STREAM, 0); // сделай проверку на ошибки!
  7.         my_addr.sin_family = AF_INET;         // Host Byte Order
  8.         my_addr.sin_port = htons(MYPORT);     // short, Network Byte Order
  9.         my_addr.sin_addr.s_addr = inet_addr(«10.12.110.57»);
  10.         memset(&(my_addr.sin_zero), », 8); // обнулим все оставшееся у структуры
  11.         // не забудь о проверке на ошибки!
  12.         bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

Вот несколько вещей, о которых нужно здесь рассказать: my_addr.sin_port и my_addr.sin_addr.s_addr находятся в
Network Byte Order. Также стоит обратить внимание на заголовочные файлы, которые могут различаться на разных
системах. ЧТобы быть уверенным, тебе потребуется проверить это по своим man страницам.
Наконец, стоит добавить, что некоторые процессы получения твоего собственного IP адреса и/или порта могут быть
автоматизированы:

  1.        my_addr.sin_port = 0; // возьмем неиспользуемый порт, чтобы bind() сама взяла порт
  2.        my_addr.sin_addr.s_addr = INADDR_ANY;  // используем мой IP адрес

Видишь, при помощи установки my_addr.sin_port в нуль ты говоришь функции bind(), чтобы она выбрала за тебя порт
самостоятельно. Похожим образом при установке my_addr.sin_addr.s_addr в INADDR_ANY ты говоришь функции, чтобы она
самостоятельно заполнила IP адрес компьютера, на котором запущен этот процесс.

Если ты немного поразмышляешь, то ты можешь заметить, что я не поместил INADDR_ANY в Network Byte Order! ПОругай
меня. Однако, у меня есть внутренняя информация: INADDR_ANY в действительности нуль! Нуль все еще имеет нуль в
битах, даже при изменении порядка байтов.

Сейчас мы так портабельны, как только ты можешь себе это представить! Я просто хотел обратить на это внимание,
т.к. бОльшая часть кода, с которым ты встретишься, не будет содержать INADDR_ANY в htonl().

bind() также возвращает -1 при ошибках и выставляет errno в значение этой ошибки.

Другая вешь, на которую стоит обратить внимание при вызове bind() — это то, что не стоит перебарщивать с номером
порта. Все порты ниже 1024 ЗАРЕЗЕРВИРОВАНЫ! Ты можешь иметь любой номер порта выше них до 65535.

Иногда ты заметишь, что при перезапуске сервера bind() вылетает с ошибкой, заявляя, что «Address already in use».
Что это значит? Что ж, часть сокета, которая была соединена, все еще «висит» в ядре, тем самым, удерживая порт. Ты
можешь либо подождать, пока она очистится (около минуты), или же добавить код в свою программу, позволяющий
переиспользовать порт. Например, следующим образом:

  1. //char yes=’1′; // под «Solaris» используем этот вариант
  2.     if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {

Последнее замечание о bind(): бывают моменты, когда ты не совсем хочешь вызывать bind(). Если ты подсоединен
(через connect() ) к удаленному компьютеру, и ты не беспокоишься о том, какой у тебя локальный порт (как в случае
telnet, где тебе важен номер удаленного порта), ты можешь просто вызвать connect(). Она проверит, назначен ли
сокет, и забиндит его через bind() на неиспользуемый локальный порт, если это необходимо.

4.3 connect() — Эй, ты!

Давайте на несколько минут представим, что мы — telnet приложение. Юзеры командуют нам (прям как в фильме TRON),
чтобы мы передали им сокетовый файловый дескриптор. Мы подчиняемся и вызываем socket(). Затем юзер говорит нам,
чтобы мы подсоединились к «10.12.110.57» на «23» порт. Йоу! Что нам тогда делать?

К счастью для тебя, программы, ты сейчас изучаешь раздел, посвященный connect(). Поэтому читай внимательно! Не
теряй времени!

Объявление connect():

  1.     #include «sys/types.h»
  2.     #include «sys/socket.h»
  3.     int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd — это наш дружественный сосед сокетового файлового дескриптора, который мы получили после вызова socket(),
serv_addr — это struct sockaddr, содержащая порт назначения и IP адрес, а addrlen может быть выставлена в
sizeof(struct sockaddr).

Разве все это не проясняет все? Давайте посмотрим на пример:

  1.     #include «string.h»
  2.     #include «sys/types.h»
  3.     #include «sys/socket.h»
  4.     #include «netinet/in.h»
  5.     #define DEST_IP   «10.12.110.57»
  6.         struct sockaddr_in dest_addr;   // будет содержать addr назначения
  7.      sockfd = socket(AF_INET, SOCK_STREAM, 0); // проверь на ошибки!
  8.         dest_addr.sin_family = AF_INET;          //Host Byte Order
  9.         dest_addr.sin_port = htons(DEST_PORT);   // short, Network Byte Order
  10.         dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
  11.         memset(&(dest_addr.sin_zero), », 8);  //обнулим оставшуюся часть struct
  12.         // не забудь проверить эту connect() на ошибки!
  13.         connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));

Еще раз, не забудь проверь возвращенное connect() значение. Она вернет -1 при ошибке.

Также заметь, что мы не вызываем bind(). Вообще, мы не беспокоимся по поводу нашего локального номера порта; нам
нужен только порт назначения (удаленный порт). Ядро выберет для нас локальный порт, и сайт, на который мы
подсоединяемся, автоматически получит от нас эту информацию. Нечего переживать.

4.4 listen() — Кто-нибудь мне позвонит?

Окей, подходящее время настало. Что если ты не хочешь подсоединяться к удаленному хосту? Скажем, ты всего лишь
хочешь подождать входящего соединения и обработать их каким-нибудь образом? Процесс разделяется на два этапа:
сначала ты слушаешь — listen(), затем принимаешь — accept() (смотри ниже).

Вызов listen() довольно простой, но требует некоторых объяснений:

  1.     int listen(int sockfd, int backlog);

soskfd — это обыкновенный сокетовый файловый дескриптор от socket(), backlog — это кол-во соединений, разрешенный
во входящей очереди. ЧТо все это значит? Что ж, входящие соединения станут ждать этой очереди пока ты не примешь (
accept() ) их. Это лимит того, сколько очередь может вмещать. Большинство систем ограничивают это число двадцатью.

Опять же, как и обычно, listen() возвращает -1.

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

  1.   /* здесь идет accept() */

Я просто покажу все это далее в разделе accept(), т.к. код очень простой. В действительности, самая хитрая часть
во всем этом колдовсттве — это вызов accept();

4.5 accept() — Спасибо, что обратились к порту 3490

Будь готов — вызов accept() немного странный! Что случится здесь, так это вот что: кто-нибудь очень далекие
попробует подсоединиться через connect() к твоему компьютеру на порт, который ты слушаешь через listen(). Их
соединение будет ждать определенное время для соединения. Ты вызовешь accept() и ты скажешь функции, чтобы она
получила ожидающее соединение. Она возвратит тебе нвый сокетный файловый дескриптор. Таким образом, ты будешь иметь
два сокетных файловых дескриптора по цене одного! Исходный дескриптор все еще прослушивает твой локальный порт, а
новый созданный сокет уже готов для операций получения и отправки.
Объявление следующее:

  1.     #include «sys/socket.h»
  2.      int accept(int sockfd, void *addr, int *addrlen);

sockfd — Это прослушивающий сокетный дескриптор. Вполе просто. addr обычно будет указателем на локальную struct
sockaddr_in. Это то место, куда попадет информация о входящих соединениях (и с этим указателем ты сможешь
определить, какой хост тебя вызвал, и с какого порта). addrlen — это локальная переменная целого типа, которая
должна была быть выставлена в sizeof(struct sockaddr_in) перед тем, как передавать ее адрес в accept(). accept() не
будет передавать байт больше, чем под них отведено места в addr. Если же она передаст меньше, то она изменит
величину addrlen для соответствия.

accept() возвращает -1 и выставляет errno в значение ошибки если произойдет ошибка.
Как и раньше, вот наглядный пример фрагмента кода, который демонстрирует все вышеописанное:

  1.     #include «string.h»
  2.     #include «sys/types.h»
  3.     #include «sys/socket.h»
  4.     #include «netinet/in.h»
  5.     #define MYPORT 3490    // порт, на который будут соединяться юзеры
  6.     #define BACKLOG 10     // сколько ожидающих соединений будет вмещать очередь
  7.         int sockfd, new_fd;  // sockfd для listen, new_fd для нового соединения
  8.         struct sockaddr_in my_addr;    // информация о моем адресе
  9.         struct sockaddr_in their_addr; // информация о адресе подсоединяющегося
  10.         sockfd = socket(AF_INET, SOCK_STREAM, 0); // не забудь о проверке на ошибки!
  11.         my_addr.sin_family = AF_INET;         // Host Byte Order
  12.         my_addr.sin_port = htons(MYPORT);     // short, Network Byte Order
  13.         my_addr.sin_addr.s_addr = INADDR_ANY; // автозаполнение с моим IP
  14.         memset(&(my_addr.sin_zero), », 8); // обнуляем оставшуюся часть структуры
  15.         //не забудь про поверку на ошибки!
  16.         bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
  17.         sin_size = sizeof(struct sockaddr_in);
  18.         new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);

Опять таки, заметь, что мы будем использовать сокетный дескриптор new_fd для вызова send() и recv(). Если жы ты
только получаешь единственное соединение, то ты можешь закрыть слушающий sockfd через close() для того, чтобы
предотвратить подрубление новых соединений на тот же самый порт.

4.6 send() и recv() — Поговори со мной, крошка!

Эти две функции отвечают за общение через stream сокеты или через подсоединенные датаграмм сокеты. Если ты хочешь
использовать регулярные неподрубленные датаграмм сокеты, то тебе потребуется обратиться к разделу о sendto() и
recvfrom() ниже.

Вызов send():

  1.   int send(int sockfd, const void *msg, int len, int flags);

sockfd — это дескриптор сокета, на который ты хочешь отправить данные (это либо полученный через socket(), или же
дескриптор, полученный через accept() ). msg — это указатель на данные, которые ты хочешь отослать, а len — это
длина этих данных в байтах. Просто поставь флаги в 0.
Вот наглядный примерчик:

  1.     char *msg = «Beej was here!»;
  2.     bytes_sent = send(sockfd, msg, len, 0);

sendto() возвращает количество байтов, которые в действительности были отправлены. Оно может быть меньше, чем
количество, которое ты хотел отослать! Смотри, иногда ты говоришь функции отправить целый блок данных, а она
попросту не может его обработать. Она отправит такое количество байт, какое только сможет, и предупредит тебя,
чтобы ты отослал оставшееся после. Запомни, что если значение, возвращенное send() не совпадает с величиной len,
то отправка всех оставшихся данных полностью возлагается на твои плечи. Хорошая новость — если пакет маленький
(меньше 1K, или около того), то скорее всего функция сможет справиться с данными и отправит их одним махом. опять
же, возвращенное -1 сигнализирует о ошибке, посмотреть которую можно в errno.

Вызов recv() похож на многие другие:

  1.     int recv(int sockfd, void *buf, int len, unsigned int flags);

sockfd — это дескриптор сокета, из которого нужно читать данные, buf — это буфер, в который будем читать
информацию, len — это максимальная длина буфера, а флаги могут быть опять выставлены в 0.
recv() возвращет число байт, которые были получены в действительности, или -1 при ошибке.

Подожди! recv() может вернуть 0. Это будет значить, что удаленная сторона закрыла для тебя соединение. Таким
образом, возвращенный 0 сообщит тебе о том, что это произошло.
Йиии! Ты Unix Network Программер!

4.7 sendto() и recvfrom() — Поговори со мной в DGRAM-стиле

Я слышу твое: «Это все круто, но что делать с несоединенными датаграмм сокетами?». Нет проблем, амиго. Мы сейчас с
этим всем и разберемся.
Т.к. датаграмм сокеты не соединены с удаленным хостом, то угадай, что нам нужно знать перед тем как отправить
пакет? Правильно! Адрес назначения! Вот прототип:

  1.    int sendto(int sockfd, const void *msg, int len, unsigned int flags,
  2.                const struct sockaddr *to, int tolen);

Как видишь, этот системный вызов практически такой же как и вызов send(), только с добавочной информацией: to —
это указатель на struct sockaddr, которая содержит IP адрес назначения и порт; tolen — может быть просто равен
sizeof(struct sockaddr).

Также как и send(), sendto() возвращает число отосланных байт (которое, опять таки, может быть меньше чем число
байт, которые ты в действительности хотел отослать!), или -1 при ошибке.
Вызов recvfrom() такой же как и recv():

  1.  int recvfrom(int sockfd, void *buf, int len, unsigned int flags,
  2.                  struct sockaddr *from, int *fromlen);

from — это указатель на локальную struct sockaddr, в который запишется IP адрес и порт компьютера. fromlen — это
указатель на int, который должен быть проинициализирован в sizeof(struct sockaddr). При выходе из функции fromlen
будет содержать длину адреса, действительно сохраненного в from.

recvfrom() возвращает число полученных байт, или же -1 при ошибке.

Помни, что если ты используешь в connect() датаграмм сокет, то ты попросту можешь обойтись одними send() и recv()
для всех своих транзакций. Сокет сам по себе — это датаграмм сокет, а пакеты все еще используют UDP, но интерфейс
сокетов сам автоматически за тебя добавит всю необходимую информацию об адресе назначения и адресе отправителя.

4.8 close() и shutdown() — Убирайся прочь!

Фьюю! Наверно ты уже вдоволь наотправлял и наполучал данных при помощи send() и recv(). Пришло время закрыть
соединение. Это очень просто. Для этого тебе нужно вызвать функцию close. Вот ее описание:

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

В случае если ты хочешь еще более проконтролировать закрытие твоего сокета тебе стоит взглянуть на функцию
shutdown(). Она позволит тебе обрубить все указанные тобой соединения:

  1.   int shutdown(int sockfd, int how);

sockfd — это сокетный файловый дескриптор, который ты хочешь закрыть. а how может быть следующей:

  • 0 — запретить дальнейшее получение данных
  • 1 — запретить дальнейшую отпавку данных
  • 2 — запретить и получение, и отправку данных (как в close() ).

shutdown() возвращает 0 при успехе, и -1 в случае ошибки.

Если ты попробуешь применить shutdown() к уже отсоединенному датаграмм сокету, т он попросту станет недоступным
для дальнейших send() и recv() функций (помни, что чтобы их использовать, тебе нужно сперва вызвать connect() для
твоего датаграмм сокета).

Важно помнить, что shutdown() в действительности не закрывает файловый дескриптор — она попросту меняет его
«юзабилити». Чтобы освободить сокетный дескриптор нужно вызвать close().

4.9 getpeername() — Кто ты?

Это очень простая функция.ВОт она.
Функция getpeername() сообщит тебе, кто находится на другой стороне соединного stream сокета. Ее объявление:

  1.     #include «sys/socket.h»
  2.     int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

sockfd 0 Это дескриптор соединенного stream сокета, addr — это указатель на struct sockaddr (или на struct
sockaddr_in), в который далее поместится информация о другой стороне соединения, а addrlen — это указатель на int,
который должен быть проинициализирован в sizeof(struct sockaddr).

Функция возвращает -1 при ошибке.

Теперь, когда у тебя есть их адреса, ты можешь использовать inet_ntoa() или gethostbyaddr() для того, чтобы
вывести больше информации. Нет, ты не получишь их логины (Ок, ок. Если на другой стороне запущен ident даемон, то
это возможно. Это, однако, за пределами данного документа).

4.10 gethostname() — Кто я?

Еще проще чем getpeername(). Она возвращает имя компьютера, на котором запущена твоя программа. Имя может
применяться в gethostbyname(), которая будет рассмотрена ниже, для того, чтобы определить IP адрес твоей локальной
машины.

Что может быть более интересным? Я могу придумать несколько вещей, но они не относятся к сокет программингу. В
любом случае, вот ее объявление:

  1.    #include «unistd.h»
  2.     int gethostname(char *hostname, size_t size);

Аргументы: hostname — это указатель на массив из char, в который далее закинется информация о имени хоста в ходе
работы этой функции. size — это длина в байтах этого массива.

4.11 DNS — Ты говоришь «whitehouse.gov», я же говорю «198.137.240.92»

В том случае, если ты не знаешь что такое DNS, я поясню. DNS Обозначает «Domain Name Service». В консоли ты
говоришь этому сервису о имени сайта в человеко-понятной форме, а этот сервис дает тебе его IP адрес (так, что ты
теперь сможешь использовать bind(), connect(), sendto(), и все что тебе далее понадобится.). Т.е. когда кто-нибудь
вводит:

telnet может разузнать, что ему нужно сделать connect() к «198.137.240.92».

Но как это все работает? Тебе понадобится вызвать функцию gethostbyname():

  1.     #include «netdb.h»
  2.     struct hostent *gethostbyname(const char *name)

Как видишь, она возвращает указатель на struct hostent, которая выглядит след. образом:

  1.     #define h_addr h_addr_list[0]

А вот и описания полей этой структуры:

  • h_name — официальное имя хоста
  • h_aliases — массив альтернативных имен хоста, заканчивающийся NULL
  • h_addrtype — тип используемого адреса; обычно AF_INET
  • h_length — длина адреса в байтах
  • h_addr_list —массив сетевых адресов для хоста, завершающийся нулем. Хост адреса находятся в Network Byte Order.
  • h_addr — первый адрес в h_addr_list.

gethostbyname() возвращает указатель на заполненную структуру hostent, или NULL при ошибке.

Но как ее применять? Иногда (как мы видим из различных компьютерных мануалов), просто выдать читателю информацию
зачастую недостаточно. Эта функция гораздо проще применять, чем ее вид.

Вот пример программы:

  1.     ** getip.c — a hostname lookup demo
  2.     #include «stdio.h»
  3.     #include «stdlib.h»
  4.     #include «errno.h»
  5.     #include «netdb.h»
  6.     #include «sys/types.h»
  7.     #include «sys/socket.h»
  8.     #include «netinet/in.h»
  9.     #include «arpa/inet.h»
  10.     int main(int argc, char *argv[])
  11.         if (argc != 2) {  // проверь на ошибки данные, переданные в командной строке
  12.             fprintf(stderr,»usage: getip addressn»);
  13.         if ((h=gethostbyname(argv[1])) == NULL) {  //получаем инфу о хосте
  14.         printf(«Host name  : %sn», h->h_name);
  15.         printf(«IP Address : %sn», inet_ntoa(*((struct in_addr *)h->h_addr)));

С этой функцией ты не можешь использовать perror() чтобы вывести сообщение об ошибке (т.к. errno не используется).
Вместо этого вызывай herror().
Это очень удобно. Ты просто передаешь строчку, которая содержит имя машины («whitehouse.gov») в нашу функцию
gethostbyname(), а она грабит информацию из возвращенной struct hostent.

Единственная возможная странность может быть при печати IP адреса выше. h->h_addr — это char*, но inet_ntoa()
требует, чтобы ей была передана struct in_addr. ПОэтому я перевожу h->h_addr в struct in_addr*, а затем
разыменовываю ее для получения данных.

5 Технология Клиент-Сервер

Это клиент-сервер мир, детка. Все вокруг в сети работает с клиентными процессами, обращающимися к серверам, и
наоборот. Возьми например telnet. Когда ты подсоединяешься с ним к удаленному хосту на порт 23 (клиент), программа
на том хосте (называемая telnetd — сервером) приступает к своей работе. Она управляет входящим telnet соединением,
запрашивает у тебя логин, итд.

Обмен информацией представлен на рис 1.

Рис 1. Взаимодействие клиента и сервера

Обрати внимание на то, что пара клиент-сервер может «говорить» на SOCK_STREAM, SOCK_DGRAM, и на любом другом
«языке». Хорошие примеры пар клиент-сервер — это telnet/telnetd, ftp/ftpd, bootp/bootpd. Каждый раз, когда ты
используешь ftp, имеется удаленная программа, ftpd, которая и обслуживает тебя.

Зачастую, имеется только лишь один сервер на машине, и ое оперирует множеством клиентов через fork(). Принцип
работы этой базовой функции следующий: сервер ждет соединения, принимает его через accept(), и делает fork(). Это
то, что наш простой пример сервера будет делать в следующем разделе.

5.1 Простой Поточный Сервер

Все что делает этот сервер — это отправка строчки «Hello, World!n» через stream соединение. Все что тебе
потребуется для тестирования этого сервера работает в одном окне, и телнетится к нему из другого при помощи:

  1.   $ telnet remotehostname 3490

, где remotehostname — это имя машины, на которой ты работаешь.

Код сервера:

  1.     ** server.c — a stream socket server demo
  2.     #include «stdio.h»
  3.     #include «stdlib.h»
  4.     #include «unistd.h»
  5.     #include «errno.h»
  6.     #include «string.h»
  7.     #include «sys/types.h»
  8.     #include «sys/socket.h»
  9.     #include «netinet/in.h»
  10.     #include «arpa/inet.h»
  11.     #include «sys/wait.h»
  12.     #include «signal.h»
  13.     #define MYPORT 3490    // порт, на который будут коннектиться юзеры
  14.     #define BACKLOG 10     // максимум соединений
  15.     void sigchld_handler(int s)
  16.         int sockfd, new_fd;  // sock_fd для слушанья, new_fd для нового соединения
  17.         struct sockaddr_in my_addr;    // инфа о моем адресе
  18.         struct sockaddr_in their_addr; // инфа о адресе подсоединившегося
  19.         if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  20.         if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
  21.         my_addr.sin_family = AF_INET;         // Host Byte Order
  22.         my_addr.sin_port = htons(MYPORT);     // short, network byte order
  23.         my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
  24.         memset(&(my_addr.sin_zero), », 8); // zero the rest of the struct
  25.         if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))  == -1) {
  26.         if (listen(sockfd, BACKLOG) == -1) {
  27.         sa.sa_handler = sigchld_handler; // рипаем все мертвые процессы
  28.         sigemptyset(&sa.sa_mask);
  29.         sa.sa_flags = SA_RESTART;
  30.         if (sigaction(SIGCHLD, &sa, NULL) == -1) {
  31.         while(1) {  // главный accept() цикл
  32.             sin_size = sizeof(struct sockaddr_in);
  33.             if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1)
  34.             printf(«server: got connection from %sn»,
  35.                                                inet_ntoa(their_addr.sin_addr));
  36.             if (!fork()) { // это child process
  37.                 close(sockfd); // child doesn’t need the listener
  38.                 if (send(new_fd, «Hello, world!n», 14, 0) == -1)
  39.             close(new_fd);  // родителю он не нужен

Если ты любопытен, то я поместил весь код в одну большую функцию main() просто для наглядности. Можешь свободно
разбить его на меньшие функции, если так тебе будет проще и яснее.

Также, что касается всей sigaction, которая может быть для тебя незнакомой — не беспокойся, все ОК. Этот код
отвечает за рипанье зомби процессов, которые находятся после за-fork()-нутых child процессов. Если ты создашь кучу
зомби и не погрохаешь их, твой сисадмин будет не очень этому рад.

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

5.2 Простой Поточный Клиент

Этот парень даже проще, чем его клиент. Все что он делает — это подсоединение к хосту, который ты указал ему в
командной строке, на порт 3490. Он получает строчку, которую отправляет сервер.

Исходник клиента:

  1.     ** client.c — a stream socket client demo
  2.     #include «stdio.h»
  3.     #include «stdlib.h»
  4.     #include «unistd.h»
  5.     #include «errno.h»
  6.     #include «string.h»
  7.     #include «netdb.h»
  8.     #include «sys/types.h»
  9.     #include «netinet/in.h»
  10.     #include «sys/socket.h»
  11.     #define PORT 3490 // порт, на который будет коннектиться клиент
  12.     #define MAXDATASIZE 100 // максимум байтов, которые мы можем получить за раз
  13.     int main(int argc, char *argv[])
  14.         struct sockaddr_in their_addr; // инфа об адресе коннектора
  15.             fprintf(stderr,»usage: client hostnamen»);
  16.         if ((he=gethostbyname(argv[1])) == NULL) {  // получим инфу хоста
  17.         if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  18.         their_addr.sin_family = AF_INET;    // Host Byte Order
  19.         their_addr.sin_port = htons(PORT);  // short, Network Byte Order
  20.         their_addr.sin_addr = *((struct in_addr *)he->h_addr);
  21.         memset(&(their_addr.sin_zero), 8);  // обнулим оставшуюся структуру
  22.         if (connect(sockfd, (struct sockaddr *)&their_addr,
  23.                                               sizeof(struct sockaddr)) == -1) {
  24.         if ((numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
  25.         printf(«Received: %s»,buf);

Заметь, что если ты не запустишь сервер перед тем как запустить клиент, то connect() возвратит «Connection
refused». Очень полезно.

5.3 Datagram Сокеты

У меня не так много сказать на этот счет, поэтому я просто представлю тебе пару простых программ: talker.c и
listener.c

listener сидит на компьютере и ждет приходящего пакета на порт 4950. talker отправляет пакет на этот порт на
указанную машину, которую указывает юзер в командной строке.

Вот исходник listener.c:

  1.     ** listener.c — a datagram sockets «server» demo
  2.     #include «stdio.h»
  3.     #include «stdlib.h»
  4.     #include «unistd.h»
  5.     #include «errno.h»
  6.     #include «string.h»
  7.     #include «sys/types.h»
  8.     #include «sys/socket.h»
  9.     #include «netinet/in.h»
  10.     #include «arpa/inet.h»
  11.     #define MYPORT 4950    // порт, на который будут коннектиться юзеры
  12.         struct sockaddr_in my_addr;    // инфа о моем адресе
  13.         struct sockaddr_in their_addr; // инфа об адресе коннектора
  14.         if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
  15.         my_addr.sin_family = AF_INET;         // Host Byte Order
  16.         my_addr.sin_port = htons(MYPORT);     // short, Network Byte Order
  17.         my_addr.sin_addr.s_addr = INADDR_ANY; // автоматически заполнит моим IP
  18.         memset(&(my_addr.sin_zero), », 8); // обнуляем остаток структуры
  19.         if (bind(sockfd, (struct sockaddr *)&my_addr,
  20.                                               sizeof(struct sockaddr)) == -1) {
  21.         addr_len = sizeof(struct sockaddr);
  22.         if ((numbytes=recvfrom(sockfd,buf, MAXBUFLEN-1, 0,
  23.                            (struct sockaddr *)&their_addr, &addr_len)) == -1) {
  24.         printf(«got packet from %sn»,inet_ntoa(their_addr.sin_addr));
  25.         printf(«packet is %d bytes longn»,numbytes);
  26.         printf(«packet contains «%s»n»,buf);

Заметь, что в нашем вызове socket() мы наконец-то использовали SOCK_DGRAM. Также, обрати внимание что нам не нужно
в этом случае использовать listen() или accept(). Это одна из фич использования неподсоединенных датаграмм сокетов!

Далее следует исходный код для talker.c:

  1.     ** talker.c — a datagram «client» demo
  2.     #include «stdio.h»
  3.     #include «stdlib.h»
  4.     #include «unistd.h»
  5.     #include «errno.h»
  6.     #include «string.h»
  7.     #include «sys/types.h»
  8.     #include «sys/socket.h»
  9.     #include «netinet/in.h»
  10.     #include «arpa/inet.h»
  11.     #include «netdb.h»
  12.     #define MYPORT 4950    //порт, на который будут подсоединяться юзеры
  13.     int main(int argc, char *argv[])
  14.         struct sockaddr_in their_addr; // инфа об адресе коннектора
  15.             fprintf(stderr,»usage: talker hostname messagen»);
  16.         if ((he=gethostbyname(argv[1])) == NULL) {  // получаем инфу о хосте
  17.         if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
  18.         their_addr.sin_family = AF_INET;     // Host Byte Order
  19.         their_addr.sin_port = htons(MYPORT); // short, Network Byte Order
  20.         their_addr.sin_addr = *((struct in_addr *)he->h_addr);
  21.         memset(&(their_addr.sin_zero), », 8); // обнуляем остаток структуры
  22.         if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0,
  23.              (struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) {
  24.         printf(«sent %d bytes to %sn», numbytes, inet_ntoa(their_addr.sin_addr));

И это все на этот счет. Запусти listener на компьютере, затем запусти talker на другой. Посмотри, как они будут
взаимодействовать!

6 Более Продвинутые Техники

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

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

6.1 Блокировка

Блокировка. Ты слышал об этом, но не знал что же это такое. В консоли, «block» — это жаргонный аналог «sleep».
Наверно ты заметил, что когда работал с listener (рассмотренный выше), то он попросту сидел и ждал пока к нему
прилетит пакет. Что происходило — это то, что он вызывал recvfrom(). recvfrom() сообщалось о том, чтобы она уснула
до тех пор, пока не придут данные.

Многие функции могут выполнять блокировку. Им просто разрешено ее выполнять. Когда ты вначале создаешь сокетный
дескриптор через socket(), то ядро устанавливает его в блокировку. Если ты не хочешь, чтобы сокет был заблокирован,
то ты вызываешь fcntl():

  1.     #include «tunistd.h»
  2.     #include «fcntl.h»
  3.     sockfd = socket(AF_INET, SOCK_STREAM, 0);
  4.     fcntl(sockfd, F_SETFL, O_NONBLOCK);

Устанавливая сокет в незаблокированное состояние, ты можешь эффективнее обращаться с ним.
Говоря главным образом, однако, этот тип состояния — плохая идея. Если ты изменишь свою программу на
«занятую-ожидающую данные от сокета», то ты повесишь процессорное время. Более элегантное решение для проверки
того, есть ли данные, которые ожидают своего чтения, описывается в разделе, посвященном функции select().

6.2 select() — Синхронный I/O мультиплексинг

Эта функция странная, но очень полезная. Возьмем след. ситуациж: ты — сервер, и ты хочешь прослушать входящие
соединения, а также продолжать читать данные из соединений, которые у тебя, скорее всего, есть.
«Нет проблем», — скажешь ты. — «Просто используй accept() и пару recv()». Не так быстро, торопыга! А что, если ты
заблокируешь вызов accept()? Как ты тогда будешь получать данные через recv() в это время? «Используй неб кируемые
сокеты!» Ни в коем случае! Ты же не хочешь отжирать CPU? Что же делать?

select() дает тебе возможность мониторить несколько сокетов в одно и то же время. Я расскажу тебе попозже, какие
готовы для чтения, какие готовы для записи, а какие вызвали исключения, если ты этого захочешь.

Без всяких дальнейших рассуждений, я просто покажу тебе объявление select():

  1.        #include «sys/time.h»
  2.        #include «sys/types.h»
  3.        #include «unistd.h»
  4.        int select(int numfds, fd_set *readfds, fd_set *writefds,
  5.                   fd_set *exceptfds, struct timeval *timeout);

Функция мониторит набор файловых дескрипторов; в частности — readfds, writefds и exceptfds. Если ты хочешь
выяснить, можешь ли ты читать со стандартного ввода и некоторых сокетных дескрипторов, просто добавь файловые
дескрипторы 0 и sockfd в перечень readfds. Параметр numfds должен быть установлен в размер самого большого
файлового дескриптора плюс один. В этом примере он должен быть установлен в sockfd+1, т.к. он больше чем
стандартный ввод(0).

После работы select() readfds будет изменен так, чтобы отражать то, какие дескрипторы из тех, что ты выбрал,
готовы для чтения. Ты можешь проверить их с macro FD_ISSET, ниже.

Перед тем как углубляться в детали, я расскажу о том, как управлять этими перечнями (set). Каждый перечень имеет
тип fd_set. Следующий макрос оперирует этим типом:

  • FD_ZERO(fd_set *set) — очищает перечень файловых дескрипторов
  • FD_SET(int fd, fd_set *set) — добавляет в перечень fd
  • FD_CLR(int fd, fd_set *set) — удаляет из перечня fd
  • FD_ISSET(int fd, fd_set *set) — проверяет, находится ли fd в перечне

Наконец, что же странного в struct timeval? Что ж, иногда ты не хочешь ждать бесконечно того, что тебе пошлют
данные. Может быть, каждые 96 секунд ты решишь выводить: «Still going…» на терминал, даже несмотря на то, что
ничего не происходит. Эта структура времени позвоит тебе указать период таймаута. Если это время истечет, а
select() все еще не получила никакого файлового дескриптора, то она завершится, и тем самым ты сможешь продолжать
работу своей программы.

Структура struct timeval имеет след. поля:

  1.         int tv_usec;    // микросекунды

Просто поставь tv_sec в число секунд, которые ты хочешь ждать, и установи tv_usec в число микросекунд. Да, это
микросекунды, а не миллисекунды. В одной миллисекунде 1000 микросекунд, а в одной секунде 1000 миллисекунд. Таким
образом, в секунде 1000000 микросекунд. А почему микросекунды обозначаются «usec»? Эта «u» выглядит очень похоже на
греческую букву µ («мю»), которую мы используем для «микро». Также, когда функция возвращает значение, то таймаут
может быть обновлен для того, чтобы отразить все еще оставшееся время. Это зависит от дистрибутива Unix.

Йоу! У нас есть таймер с точностью дл микросекунды! Что ж, не ручайся на него. Стандартный Unix «timeslice» около
100 миллисекунд, поэтому, вероятно, ты будешь должен ждать это время, независимо от того, как мало время в твоей
struct timeval.

Еще одна интересная фишка: если ты установишь свои поля в struct timeval в 0, то select() сразу же сработает. Если
ты установишь параметр timeout в NULL, то он никогда не сработает, и будет ждать до тех пор, пока не станет готов
первый файловый дескриптор. Наконец-таки, если ты не очень беспокоишься о том, сколько ждать для каждого перечня,
ты просто можешь установить таймаут в NULL в вызове select();

Следующий код ждет 2,5 секунды, ожидая появления чего-нибудь на стандартном вводе.

  1.     ** select.c — a select() demo
  2.     #include «stdio.h»
  3.     #include «sys/time.h»
  4.     #include «sys/types.h»
  5.     #include «unistd.h»
  6.     #define STDIN 0  // файловый дескриптор для стандартного ввода
  7.         // не заморачиваемся по поводу writefds and exceptfds:
  8.         select(STDIN+1, &readfds, NULL, NULL, &tv);
  9.         if (FD_ISSET(STDIN, &readfds))
  10.             printf(«A key was pressed!n»);

Теперь некоторые из вас могут решить, что это великолепный способ ожидания данных в датаграмм сокетах, и вы
будете правы: возможно, это так. Некоторые Юниксы могут действовать в этом духе, а некоторые — нет. Тебе нужно
проверить это в своем мане, если ты хочешь использовать такой метод.

Некоторые Юниксы обновляют время в твоей локальной struct timeval для того , чтобы отражать количество времени,
оставшееся до таймаута. Но другие так не действуют. Не полагайся на это, если ты хочешь быть портируемым. Применяй
gettimeofday() если тебе надо учитывать пройденное время.

А что случится, если сокет в состоянии чтения закроет соединение? Что ж , в этом случае select() возвратится с
сокетным дескриптором, выставленным в «ready to read». Когда ты на самом деле далее вызовешь recv(), то recv()
вернет 0. Это способ, по которому ты можешь узнать, что клиент закрыл соединение.

Еще одна интересная вещь, связанная с select(): если у тебя есть сокет, который прослушивает через listen(), то ты
можешь проверить, появилось ли новое соединение при помощи помещения этого сокетного файлового дескриптора в
readfds set.

Вот и все вкратце, мои друзья, о всемогущей функции select().

Но, по многочисленным запросам я все же покажу подробный пример.

Эта программа действует как простой многопользовательский чат сервер. Запусти его в одном окне, затем зателнеться
на него («telnet hostname 9034») со многих других окон. Когда ты введешь что-нибудь в одной telnet сессии, эта
строчка должна отображаться во всех других.

  1.     ** selectserver.c — a cheezy multiperson chat server
  2.     #include «stdio.h»
  3.     #include «stdlib.h»
  4.     #include «string.h»
  5.     #include «unistd.h»
  6.     #include «sys/types.h»
  7.     #include «sys/socket.h»
  8.     #include «netinet/in.h»
  9.     #include «arpa/inet.h»
  10.     #define PORT 9034   // порт, который мы прослушиваем
  11.         fd_set master;   // master file descriptor list
  12.         fd_set read_fds; // temp file descriptor list for select()
  13.         struct sockaddr_in myaddr;     // server address
  14.         struct sockaddr_in remoteaddr; // client address
  15.         int fdmax;        // maximum file descriptor number
  16.         int listener;     // listening socket descriptor
  17.         int newfd;        // newly accept()ed socket descriptor
  18.         char buf[256];    // buffer for client data
  19.         int yes=1;        // for setsockopt() SO_REUSEADDR, below
  20.         FD_ZERO(&master);    // clear the master and temp sets
  21.         if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  22.         // lose the pesky «address already in use» error message
  23.         if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes,
  24.         myaddr.sin_family = AF_INET;
  25.         myaddr.sin_addr.s_addr = INADDR_ANY;
  26.         myaddr.sin_port = htons(PORT);
  27.         memset(&(myaddr.sin_zero), », 8);
  28.         if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) {
  29.         if (listen(listener, 10) == -1) {
  30.         // add the listener to the master set
  31.         FD_SET(listener, &master);
  32.         // keep track of the biggest file descriptor
  33.         fdmax = listener; // so far, it’s this one
  34.             read_fds = master; // copy it
  35.             if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
  36.             // run through the existing connections looking for data to read
  37.             for(i = 0; i <= fdmax; i++) {
  38.                 if (FD_ISSET(i, &read_fds)) { // we got one!!
  39.                         // handle new connections
  40.                         addrlen = sizeof(remoteaddr);
  41.                         if ((newfd = accept(listener, (struct sockaddr *)&remoteaddr,
  42.                             FD_SET(newfd, &master); // add to master set
  43.                             if (newfd > fdmax) {    // keep track of the maximum
  44.                             printf(«selectserver: new connection from %s on »
  45.                                 «socket %dn», inet_ntoa(remoteaddr.sin_addr), newfd);
  46.                         // handle data from a client
  47.                         if ((nbytes = recv(i, buf, sizeof(buf), 0)) <= 0) {
  48.                             // got error or connection closed by client
  49.                                 printf(«selectserver: socket %d hung upn», i);
  50.                             FD_CLR(i, &master); // remove from master set
  51.                             // we got some data from a client
  52.                             for(j = 0; j <= fdmax; j++) {
  53.                                 if (FD_ISSET(j, &master)) {
  54.                                     // except the listener and ourselves
  55.                                     if (j != listener && j != i) {
  56.                                         if (send(j, buf, nbytes, 0) == -1) {

Заметь что у меня два списка файловых дескрипторов в коде: master и read_fds. Первый содержит все сокетные
дескрипторы, которые в данный момент подсоединены, также как и сокетный дескриптор, который прослушивает новые
соединения.

Причина, по которой я использую mastel set — это то, что select() изменяет список, который ты передаешь ей, чтобы
отразить какие сокеты готовы для чтения. Т.к. я хочу следить за соединениями от одной select() к другой, то я
должен осторожненько сохранить их куда-нибудь еще. В последний момент я копирую master в read_fds, и затем вызываю
select().

Но значит ли это, что каждый раз, когда я получаю новое соединение, я должен добавить его к master set? Йееп! И
каждый раз, когда соедиение закрывается, я должен удалять его из моего master set? Да, должен.

Если клиентская recv() вернет не ноль, я буду знать, что некоторые данные были получены. Поэтому я получаю их, а
затем просматриваю master list и отправляю эти данные всем остальным клиентам.

И это, мои друзья, наиболее простой обзор всемогущей функции select().

6.3 Управление Частичными send()

Возвращаясь к предыдущему разделу о send() выше, помнишь, что я говорил о том, что send() может и не отослать все
байты, которые ты хотел отослать? Т.е. допустим, что ты хотел отослать 512 байт, а функция вернула только 412. Что
же случилось с остальной сотней байт?

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

Ты мог бы написать функцию примерно в духе вот этой, чтобы разрулить эту ситуацию:

  1.     #include &laquo;sys/types.h&raquo;
  2.     #include &laquo;sys/socket.h&raquo;
  3.     int sendall(int s, char *buf, int *len)
  4.         int total = 0;        // сколько байт мы отправили
  5.         int bytesleft = *len; // сколько еще сталось для отправки
  6.             n = send(s, buf+total, bytesleft, 0);
  7.         *len = total; // возвращает число в действительности отосланных байт
  8.         return n==-1?-1:0; // вернет-1 при неудаче, 0 при успехе

В этом примере s — это сокет, в который ты хочешь послать данные; buf — это буфер, содержащий данные; len —
указатель на int, содержащий число байт в буфере.

Функция вернет -1 при ошибке. Также, число действительно посланных байт возвращается в len. Это будет такое же
число байт, которые ты попросил функцию отослать, если не произошла ошибка. sendall() сделает все наилучшим образом.

Для завершенности, вот простой пример к функции:

  1.     if (sendall(s, buf, &len) == -1) {
  2.         printf(«We only sent %d bytes because of the error!n», len);

Что произойдет на стороне получателя, если прибудет часть пакета? Если пакеты разной длины, то как получатель
узнает, когда заканчивается первый пакет и начинается следующий? Ты, наверно, должен воспользоваться инкапсуляцией
(помнишь про нее из раздела про инкапсуляцию данных в самом начале?). Прочти его!

6.4 Сын Инкапсуляции Данных

Что это значит — инкапсулировать данные? В самом простом случае это обозначает, что ты снабжаешь заголовок
(header) либо с идентифицирующей его информацией, либо же с длиной пакета, или и с тем, и с другим.

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

Вау. Это непонятно.

Окей. Например, давай скажем, что у тебя есть многопользовательская чат программа, использующая SOCK_STREAM
сокеты. Когда юзер печатает («говорит») что-нибудь, то два куска информации должны быть переданны на сервер: чтО
было сказано, и ктО сказал это.

И все так просто? «А какие могут быть проблемы» — спросишь ты.

Проблема в том, что сообщения могут быть разной длины. Один человек по имени «tom», может сказать «Hi», а другой
по имени «Benjamin» скажет «Hey guys what is up?».

Поэтому если ты отправишь через send() все данные как они есть к клиентам, то твой выходящий поток данных будет
выглядеть примерно так:

  1.    t o m H i B e n j a m i n H e y g u y s w h a t i s u p ?

И так далее. Как клиент сможет узнать, где заканчивается одно сообщение и начинается другое? Ты мог бы, если
конечно захочешь, делать все сообщения одинаковой длины и просто вызвать sendall(), которую мы написали выше. Но
это будет нерационально! Мы не хотим слать через send() 1024 байта для того, чтобы отправить всего лишь то, что
«tom» сказал «Hi».

Поэтому мы инкапсулируем данные в маленьком заголовке и структуре пакета. Клиент и сервер оба знают, как
запаковать и распаковать эти данные (это иногда называют «marshal» и «unmarshal»). Мы начиннаем определять
протокол, который бы описывал способ, с помощью которого общаются клиент и сервер!

В этом случае давайте положим, что имя юзера имеет фиксированную длину в 8 символов, завершающуюся ». И затем
давай положим, что данные имеют различную длину, но не больше 128 символов. Давай возьмем для рассмотрения кусок
структуры пакета, который мы бы могли использовать в этой ситуации:

  • len (1 byte, unsigned) —Полная длина пакета, учитывая 8-ибайтовое имя юзера и данные чата
  • name(8 byte) — имя юзеа, заканчивающееся NULL, если необходимо
  • chatdata(n-byte) — сами данные, но не более чем 128 байт. Длина пакета должна быть посчитана как длина его данных
    плюс 8 (длина поля для имени выше)

Почему я выбрал 8-ибайтовое и 128-ибайтовое ограничения для полей? Я лишил их воздуха, пологая что они будут
достаточно длинными. Может быть, хотя, 8 байт — очень мало для твоих нужд, и ты можешь иметь 30-ибайтовое поле под
имя, или еще что-нибудь. Дело выбора остается за тобой.

Используя расписанное выше описание пакета, первый пакет мог бы состоять из след. информации (в hex и ASCII):

  1.  0A     74 6F 6D 00 00 00 00 00      48 69
  2.    (length)  T  o  m    (padding)    H  i

А следующий:

  1.       14     42 65 6E 6A 61 6D 69 6E      48 65 79 20 67 75 79 73 20 77 …
  2.    (length)  B  e  n  j  a  m  i  n       H  e  y     g  u  y  s     w  …

Длина сохранена в Network Byte Order, конечно же. В этом случае это только один байт, поэтому это не важно, но, в
общем говоря, ты захочешь, чтобы твои бинарные целые числа были сохранены в Network Byte Order в твоих пакетах.

Когда ты отправляешь эти данные, тебе нужно быть осторожным и использовать команды в духе sendall() выше, чтобы ты
всегда знал, что твои данные были отосланы, даже если это займет несколько вызовов send(), чтобы получить их все.

Похожим образом, когда ты получаешь данные, тебе нужно проделать немного дополнительной работы. Чтобы быть
осторожным, тебе нужно понимать, что ты можешь получить частичные данные (например, мы могли бы получить 00 14 42
65 6E» от Benjamin выше). Нам нужно вызывать recv() опять и опять, пока мы не получим полностью весь пакет.

Но как? Что ж, мы занем суммарное число байт для пакета, которые надо получить. Нам также известен и максимальный
размер пакета, который равен 1+8+128 = 137 байт (следуя из нашего описания пакета).

Мы можем объявить достаточно большой массив для двух пакетов. Это твое дело — куда ты перебросишь данные
полученных пакетов.

Каждый раз, когда ты получаешь через recv() данные, ты отправляешь их в рабочий буфер и смотришь, не закончился ли
пакет. Таким образом, число байт в буфере больше или равно длине, указанной в заголовке (+1, т.к. длина в буфере не
включает байт на саму длину). Если число байт в буфере меньше чем 1, то очевидно, что пакет не завершен. Ты должен
предусмотреть это, т.к. первый байт — это мусор, и ты не можешь полагаться на него как на корректную длину пакета.

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

Фьюю! Ты отложил что-нибудь в своей голове? Ты также можешь прочесть конец первого пакета в буфер, а за ним и
начало следующего через один вызов recv(). Т.о. у тебя будет рабочий буфер с одним полноценным пакетом, и с
частичкой следующего! Bloody heck! Именно поэтому ты и сделал свой буфер достаточно большим, чтобы поместить в него
аж 2 пакета.

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

Некоторые из вас могут решить, что перемещение частичные пакеты в начало рабочего буфера занимает много времени, и
что программу можно написать при помощи другого метода. Можно, но рассмотрение таких методов выходит за рамки этой
документации. Если вам все еще любопытно — достаньте какую-нибудь книжку по структурам данных.

Я никогда не говорил, что это было легко. ОК, я сказал, что это было легко:smile3: И вот что: вам нужно просто
практиковаться и очень скоро ты все поймешь. Клянусь Экскалибуром!

7. Частые Вопросы

Q: Где я могу взять эти заголовочные файлы
A:Если у тебя нет их, то тебе скорее всего они не нужны. Посмотри в мануале по твоей платформе. Если ты под
Виндами, то тебе нужно только #include «winsock.h»

Q: Что мне делать когда bind() возвращает: «Address already in use»?
A: Тебе надо использовать setsockopt() с опцией SO_REUSEADDR для слушающего сокета. Глянь в секцию bind() и в
секцию про select().

Q: Как мне получить список открытых сокетов на моей системе?
A: Используй netstat.

Q: Как я могу просмотреть routing table?
A: Выполни команду rout (в /sbin на большинстве Линуксов) или команду netstat -r.

Q: Как я могу запустить клиент и сервер программы если у меня только один компьютер? Разве мне не нужна сеть для
написания сетевых программ?
A: К счастью для тебя, виртуально все машины описывают loopback network девайс, который сидит в ядре и заменяет
собой сетевую карточку.

Q: Как я могу узнать, что удаленная сторона закрыла соединение?
A: Ты можешь узнать это, если recv() вернет 0.

Q: Как мне написать утилиту ping? Что такое ICMP? Где мне узнать больше о raw сокетах и SOCK_RAW?
A: Сначала удали Винды и поставь Линух или BSD :smile3: Нет, на самом деле, просто загляни в раздел, посвященный Винде
в самом начале.

Q: Как мне писАать под Solaris/SunOS? Я продолжаю получать ошибки линковщика при компиляции.
A: Линкер выдает ошибки, потому что операционка Sun не автоматически компилирует в сокет библиотеки. Посмотри
раздел по Solaris/SunOS в начале.

Q: Почему select() вылетает при сигнале?
A: Сигналы заставляют блокируемые системные вызовы возвращать -1 с errno, поставленным в EINTR. Когда ты
настраиваешь signal handler с sigaction(), ты можешь установить flag SA_RESTART, который предназначен для рестарта
системного вызова после того, как он был прерван. Но это не всегда работает.
Мое любимое решение этой поблемы — использовать goto:

  1.     if ((err = select(fdmax+1, &readfds, NULL, NULL, NULL)) == -1) {
  2.             // какой-то сигнал прервал нас, поэтому рестартим…
  3.         // обрабатываем настоящие ошибки здесь:

Конечно же, тебе не нужно использовать goto в твоем случае; ты можешь применить другие структуры для контроля. Но
я думаю, что goto гораздо яснее.

Q: Как я могу описать таймаут для вызова recv()?
A: Юзай select()! Он позволит тебе указать таймаут параметр для сокетного дескриптора, из которого ты собираешься
читать. Или же, ты можешь вынести всю функциональность в единственную функцию, типа этой:

  1. #include &laquo;unistd.h&raquo;
  2. #include &laquo;sys/time.h&raquo;
  3. #include &laquo;sys/types.h&raquo;
  4. #include &laquo;sys/socket.h&raquo;
  5. int recvtimeout(int s, char *buf, int len, int timeout)
  6.     // настраиваем  file descriptor set
  7.     // настраиваем время на таймаут
  8.     // ждем таймаута или полученных данных
  9.     n = select(s+1, &fds, NULL, NULL, &tv);
  10.     if (n == 0) return -2; // timeout!
  11.     if (n == -1) return -1; // error
  12.     // данные должны быть здесть, поэтому делаем обычный recv()
  13.     return recv(s, buf, len, 0);
  14. // простой вызов recvtimeout():
  15.     n = recvtimeout(s, buf, sizeof(buf), 10); // 10 second timeout

Заметь, что recvtimeout() возвращает -2 в случае таймаута. Почему не 0? Если ты вспомнишь, возвращаемый recv() 0
значит, что удаленная сторона прикрыла соединение.

Q: Как мне зашифровать или сжать данные перед отправкой через сокет?
A: Самый легкий способ — использовать SSL (secure socket layer)

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

  • сервер читает данные с файла (или еще с чего-нибудь)
  • сервер шифрует данные (ты это сам напишешь)
  • сервер шлет зашифрованные данные

Другая сторона:

  • клиент получает зашифрованные данные
  • клиент расшифровывает данные (это ты пишешь сам)
  • клиент пишет данные в файл ( или еще куда-нибудь)

Ты также можешь сжать данные по такой же схеме. Или же и сжать, и зашифровать! Просто помни, что сжимать данные
нужно перед шифрованием:smile3:

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

Q: Что такое «PF_INET», который я заметил? Он связан с «AF_INET»?
A: Да, да, это так. Глянь раздел про socket() для подробностей.

Q: Как я могу написать сервер, который бы принимал шелл команды от клиента и выполнял их?
A: Для простоты, скажем, что клиент connect(), send() и close() соединение.
Схема, по которой будет действовать клиент, следующая:

  • connect() к серверу
  • send («/sbin/ls > /tmp/client.out»)
  • close() соединение

Немного погодя сервер выполняет команды:

  • accept() соединение от клиента
  • recv(str) командную строку
  • close() соедиение
  • system(str) выполняем комаду

Будь осторожен! Иметь сервер, выполняющий клиентские команды — это то же самое, что дать удаленный шелл доступ.
Подумай, что если клиент отправит «rm -rf ~»? Он удалит все в твоем аккаунте, вот что!

Поэтому, будь умным, и запрещай клиентам использовать все кроме нескольких утилит, которые для тебя неопасны,
например след. образом:

  1.  if (!strcmp(str, «foobar»)) {
  2.         sprintf(sysstr, «%s > /tmp/server.out», str);

Но ты и сейчас не секьюрен: что будет, если клиент введет «foobar; rm -rf ~»? Самая безопасная вещь, которую можно
сделать, — это написать маленькую функцию, которая будет добавлять эскейп («») символ впереди всех не
буквенно-циферных символов (включая пробелы) в аргументы команды.

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

Q: Когда я отсылаю 1460 байта, то я получаю только 536 из них за раз. Но если я запущу эту свою программу на моем
компьютере, то я получаю все данные в то же время. Что происходит?
A: Ты превысил MTU — максимум размера, который может обработать физический medium. На твоем компьютере ты
используешь loopback девайс, который может оперировать 8К или даже большими без проблем. Но на ethernet, который
может оперировать только 1500 байтами с заголовком, ты превышаешь лимит. Через модем с 576 MTU (опять таки, с
заголовком), ты превысишь даже меньший лимит.

Ты должен убедиться, что все данные были отосланы. Когда ты убедился в этом, тебе нужно будет recv() в цикле, пока
не отправятся все твои данные.

Q: Я под Виндой и у меня нет функции fork(). Как быть?
A: Если она есть, то она должна быть в POSIX библиотеках, которые могли идти с твоим компилером. Т.к. у меня нету
Винды, то я не могу тебе точно сказать ответ. В крайнем случае можно заменить fork() Виндовым эквивалентом
CreateProcess().

8. Дисклеймер и Помощь.

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

Поэтому, пусть это предупредит тебя! Я извиняюсь за все неточности.
Кроме всего, я провел много часов, разбирая и просматривая весь этот документ, описал несколько TCP/IP сетевых
утилит для работы, написал мультиплеерный игровой движок итд. Но я не бог сокетов. Я просто парень.

Кстати, если у кого-нибудь есть конструктивные(или деструктивные) высказывания по этому документу, пожалуйста,
шлите их на beej @ piratehaven.org и я попробую приложить усилия чтобы со всем разобраться.

Если ты удивляешься тому, а зачем же я написал все это, что ж, я сделал это за деньги. Ха! Нет, я написал все это
потому что много людей задавали мне вопросы про сокеты, и, когда я сказал им, что я собираюсь поместить все ответы
в документ по сокетам, они сказали «Круто!». Кроме этого, я чувствую, что все эти с трудом добытые знания
утратятся, если я не поделюсь ими с другими. Веб просто стал идеальным двигателем. Я призываю всех делиться
информацией, когда это только случается возможным.

Хватит об этом — назад к кодингу! :smile3:

© Брайан «Beej» Холл, пер. varnie


archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532


WASM

Brian «Beej Jorgensen» Hall
beej@beej.us
Version 3.0.14
September 8, 2009
Copyright © 2009 Brian «Beej Jorgensen» Hall
Оригинал материала: http://beej.us/guide/bgnet/

1. Введение
1.1 Аудитория
1.2 Платформа и компилятор
1.3 Официальная страница и книги
1.4 Программистам Solaris и SunOS
1.5 Программистам Windows
1.6 E-Mail политика
1.7 Зеркалирование
1.8 Copyright и распространение

2. Что такое сокет?
2.1 Два типа Интернет-Сокетов
2.2 Теория сетей и низкие уровни

3. IP-адреса, структуры, и передача данных
3.1 IP-адреса, версии 4 и 6
3.2 Порядок следования байт
3.3 Структуры
3.4 IP-адреса, часть два

4. Переход от IPv4 к IPv6

5. Системные вызовы
5.1 getaddrinfo() — готовимся к запуску!
5.2 socket() — создаём дескриптор файла!
5.3 bind() — на каком я порту?
5.4 connect() — Эй, ты!
5.5 listen() — Кто-нибуть может мне позвонить?
5.6 accept() — спасибо за звонок на порт 3490.
5.7 send() и recv() — поговори со мной, детка!
5.8 sendto() и recvfrom() Поговрии со мной дейтаграммами!
5.9 close() и shutdown() — Уйди с глаз моих!
5.10 getpeername() — ты кто такой?
5.11 gethostname() — а я кто такой?

6. Взаимодействие Клиент-Сервер
6.1 Простой TCP-сервер
6.2 Простой TCP-клиент
6.3 UDP-сокеты

7. Чуть более продвинутая техника
7.1 Блокирование
7.2 select() — мультиплексирование синхронного I/O
7.3 Обработка частичного send()
7.4 Сериализация — как упаковывать данные
7.5 Сын инкапсуляции данных
7.6 Бродкаст пакеты — Hello, World!

8. Общие вопросы

Введение

Эй! Программирование сокетов тебя достало? Оно слишком заковыристо, чтобы изучить его по манам? Вы хотите писать сетевые программы, но у вас нет времени, чтобы разбираться в дебрях документации, чтобы всего лишь узнать, что перед connect() нужно вызывать bind() и т.д. и т.п.?

Знаете что? Я уже проделал эту грязную работу и горю желанием поделиться со всеми полученными знаниями! Вы сюда удачно зашли! Этот документ даст среднему программисту на C достаточно знаний, чтобы начать писать сетевые программы на C.

И ещё: я наконец нашел время и дополнил гайд информацией о программировании IPv6! Наслаждайтесь!

Аудитория

Этот документ написан в качестве учебника, а не справочного материала. Наверно, лучше всего он будет восприниматься теми, кто только начинает изучать программирование сокетов и ищет точку опоры. Это не полное глобальное руководство по программированию сокетов.
Надеюсь, однако, его будет достаточно, чтобы понять суть и смысл сокетов… 🙂

Платформа и компилятор

Код, приведённый в этом документе, был скомпилирован на компьютере под управлеием GNU/Linux компилятором GNU GCC. Однако, он будет работать и на любой другой платформе, использующей gcc. Естественно, это не касается windows — смотрите раздел Программистам Windows.

Официальный сайт и книги

Официальное и изначальное местонахождение этого документа — http://beej.us/guide/bgnet/. Там вы также найдете примеры кода и переводы руководства на различные языки.

Чтобы купить красиво переплетенные копии этого документа (некоторые называют их «книги»), посетите http://beej.us/guide/url/bgbuy. Я ценю заказы своих книг, поскольку они помогают поддерживать мой документо-писательский образ жизни!

Программистам Solaris и SunOS

При компиляции под Solaris/SunOS вам нужно указать компилятору дополнительные библиотеки для линковки. Для этого просто добавьте в строку компиляции: «-lnsl -lsocket -lresolv» , как-то так:

$ cc o server server.c lnsl lsocket lresolv

Если всё ещё возникают ошибки, добавьте ещё и «-lxnet». Я не знаю, что это такое, но у некоторых людей это решало проблему.

Другое узкое место — вызов setsockopt(). Прототип отличается от такогого в Linux, так что вместо

int yes=1;

используйте:

char yes=‘1’;

Так как у меня нет соляриса, я не тестировал ничего из этого. Всё это мне пришло в отзывах на e-mail.

Программистам Windows

Исторически этот гайд не рассчитан на windows. Просто потому, что windows я не люблю. Но стоит быть действительно честным и признать, что у windows есть огромная база программ и пользователей, и в сущности это прекрасная ОС.

Я всё ещё надеюсь, что вы попробуете Linux, BSD или любой другой Unix.
Но людям нравится то, что им нравится, и ребята под windows имеют право на свою долю информации. Этот документ полезен и им тоже, с небольшими изменениями в коде.

Во-первых, вы можете поставить такую штуку, как Cygwin. Это коллекция инструментов Unix под Windows. Насколько я понимаю, это позволит оставить код без изменений.

Но некоторые из вас, возможно, захотят писать код под чистый windows. Это очень плохо, и вы должны немедленно поставить Unix!
Нет-нет, шучу. Нужно быть windows-терпеливым ближайшие дни…

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

#include <winsock.h>

Стоп! Ещё вы должны вызывать WSAStartup() перед всем остальным кодом, относящимся к сокетам. Выглядит это примерно так:

#include <winsock.h>

{
WSADATA wsaData;   // if this doesn’t work
//WSAData wsaData; // then try this instead

// MAKEWORD(1,1) for Winsock 1.1, MAKEWORD(2,0) for Winsock 2.0:

if (WSAStartup(MAKEWORD(1,1), &wsaData) != 0) {
fprintf(stderr, «WSAStartup failed.n»);
exit(1);
}

Также вам нужно прилинковать к проекту библиотеки: обычно это wsock32.lib или winsock32.lib или ws2_32.lib для winsock 2.0. В VC++ это может быть сделано через меню Проект, в меню Настройки… Выберите вкладку компилятор->линковка или что-то вроде того, и добавьте «wsock32.lib» (или какой-то другой похожый .lib).

Ну, я слышал, что это примерно так делается.

В конце работы с сокетами вы должны вызывать WSACleanup().

Если вы сделаете всё это, остальные примеры из этого учебника должны, по идее, работать, хоть и с некоторыми исключениями.
Во-первых, вместо close() вам нужно использовать closesocket(). select() работает только с дескриптором сокета, с дескриптором файла, как в unix, не работает.

Существует так же класс для работы с сокетами, CSocket, можете поискать его в учебниках.

Чтобы получить больше информации о Winsock, читайте Winsock FAQ.

Наконец, насколько я знаю, в windows, к сожалению, нет системы fork(), использующейся в некоторых примерах. Может быть, вы сможете прилинковать для форка библиотеку POSIX, или использовать вместо него CreateProcess(). Форк не принимает аргументов, а CreateProcess принимает миллиарды. Если вам не хочется в них разбираться, используйте CreateThread, это немного проще… К сожалению, дискуссия о многопоточности выходит за рамки этого документа.

1.6 E-Mail политика

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

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

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

Теперь, когда я загрузил вас мыслями о том, писать мне или не писать, я просто хочу, чтобы вы знали, что я в полной мере оценил все похвалы руководства, которые получил за эти годы. Знание, что книга используется для создания полезных и хороших вещей — реальная поднимает боевой дух, и радует! 🙂 Спасибо!

1.7 Зеркалирование

Зеркалирование этого материала более чем приветствуется, будь то государственные или частные сайты. Если вы опубликовали зеркало и хотите, чтобы я разместил ссылку на него на главной странице, напишите мне на beej@beej.us.

Авторские права и распространение

Руководство Beej по сетевому программированию © 2009 Brian «Beej Jorgensen» Hall.

За некоторыми исключениями исходного кода и переводов, ниже, эта работа под лицензией Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License. Чтобы просмотреть копию данной лицензии, посетите http://creativecommons.org/licenses/by-nc-nd/3.0/ или отправьте письмо в Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.

Одним из конкретных исключений «No Derivative Works» части лицензии является следующее: это руководство может быть свободно переведено на любой язык при условии, что перевод является точным, и руководство перепечатано в полном объеме. Такие же ограничения лицензии относятся как переводу, так и к исходному руководству. Перевод может также включать имя и контактную информацию переводчиков.

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

Педагогам свободно предлагается рекомендовать или предоставить копии этого руководства своим студентам.

Свяжитесь с beej@beej.us для получения дополнительной информации.

Beej’s Guide to Network Programming

This is the source for Beej’s Guide to Network Programming.

If you merely wish to read the guide, please visit the Beej’s Guide to
Network Programming website.

This is here so that Beej has everything in a repo and so translators
can easily clone it.

Build Instructions

Dependencies

  • Gnu make (XCode make works, too)
  • Python 3+
  • Pandoc 2.7.3+
  • XeLaTeX (can be found in TeX Live)
  • Liberation fonts (sans, serif, mono)

Mac dependencies install (reopen terminal after doing this):

xcode-select --install                  # installs make
brew install python                     # installs Python3
brew install pandoc
brew install mactex --cask              # installs XeLaTeX
brew tap homebrew/cask-fonts
brew install font-liberation            # installs sans, serif, and mono

Build

  1. Type make from the top-level directory.

    If you have Gnu Make, it should work fine. Other makes might work as
    well. Windows users might want to check out Cygwin.

  2. Type make stage to copy all the build products and website to the
    stage directory.

  3. There is no step three.

You can also cd to the src directory and make.

make clean cleans, and make pristine cleans to «original» state.

To embed your own fonts in the PDFs, see the src/Makefile for examples.

The upload target in the root Makefile demonstrates the build steps
for a complete release. You’ll need to change the UPLOADDIR macro in
the top-level Makefile to point to your host if you want to use that.
You’re free to upload whatever versions you desire individually, as
well.

Build via Docker

If you don’t want to mess with a local setup, you can build via Docker.

  1. Run docker build -t beej-bgnet-builder . from the top-level directory.

  2. Run docker run --rm -v "$PWD":/guide -ti beej-bgnet-builder.

    This will mount the project where the image expects it, and run make pristine all stage, leaving your ./stage directory ready to be published.

Pull Requests

Please keep these on the scale of typo and bug fixes. That way I don’t
have to consider any copyright issues when merging changes.

TODO

Content

  • File transfer example maybe in son of data encapsulation
  • Multicast?
  • Event IO?

Bug fixes

  • When pandoc 2.8 comes up, switch all man page subheaders to h3 and supress
    them from the table of contents.

Подборка по базе: МЕТОДИЧЕСКИЕ РЕКОМЕНДАЦИИ ДЛЯ ВЫПОЛНЕНИЯ ПРАКТИЧЕСКИХ РАБОТ ПО Д, 02 DnDestiny Architects Guide v1.0.1_compressed.pdf, Курсовая программирование .docx, 6-синф программирование мисаллар.docx, Практическое задание Программирование.docx, Методические указания по курсовой работе — дисц. Программирован, Технологическая карта урока. Информатика . 8 В класс. ФГОС. Форм, ФОС МДК 03.01. Технология разработки программного обеспечения дл, Neon Light Pro Game Player Guide Presentation.pdf, 1 урок Программирование на языке Python (1).docx


Beej’s Guide to Network Сетевое программирование от Биджа Использование Интернет Сокетов
Брайан “Beej Jorgensen” Холл Версия 3.0.15 Июль 3, 2012
Copyright © 2012 Brian “Beej Jorgensen” Hall Перевод Андрей Косенко aikos55@gmail.com
i

Beej’s Guide to Network Programming
!
Beej’s Guide to Network Programming!
Using Internet Sockets
Brian “Beej Jorgensen” Hall
beej@beej.us
Version 3.0.15
July 3, 2012
Copyright © 2012 Brian “Beej Jorgensen” Hall
ii

Beej’s Guide to Network Спасибо всем, кто помогал мне в написании этого документа в прошлом и будущем. Спасибо Ashley за уговоры превратить дизайн обложки в лучший образчик программистского художества, на которое я способен. Спасибо всем, кто делает Свободные программы и пакеты, которые я использовал при создании этого Руководства
GNU, Linux, Slackware, vim, Python, Inkscape, Apache FOP, Firefox, Red Hat и многие другие. И наконец, большое спасибо буквально тысячам из вас, кто присылал свои предложения по улучшению руководства и слова ободрения. Я посвящаю это руководство моим величайшим героями вдохновителям в мире компьютеров Дональду Кнуту (Donald Knuth), Брюсу Шнайеру (Bruce Schneier), Ричарду
Стивенсу (W. Richard Stevens), Стиву Возняку (The Woz), моим Читателями всему Free and Open Source Software Community. Англоязычный вариант этой книги написан автором на XML в редакторе vim нас инструментами GNU. Картина обложки и диаграммы сделаны на
Inkscape. XML преобразован в HTML и XSL-FO пользовательскими скриптами Python. XSL-
FO вывод затем был преобразован Apache FOP в PDF документы с использованием шрифтов Liberation. Все инструменты это 100% Free and Open Source Software. За исключением взаимно достигнутого участниками данной работы согласия, автор предлагает всё как есть и не делает никаких заявлений и не даёт никаких гарантий относительно данной работы, выражений, предположений, предписаний или чего-либо иного, включая, без ограничений, гарантии прав собственности, возможности сбыта, применимость для определённых целей, ненарушение законов, отсутствие скрытых или иных дефектов, точность, наличие или отсутствие обнаруживаемых или не обнаруживаемых ошибок. За исключением области действия применимого законодательства, автор не несёт ответственности низа какие события, происходящие в силу правовых предположений по любым особым, случайным, косвенным, штрафным или характерным сбоям, возникшим при использовании данной работы, особенно если автор предупреждало возможности возникновения таких сбоев. Этот документ может свободно распространяться под лицензией Creative Commons
Attribution-Noncommercial-No Derivative Works 3.0 License. Смотрите раздел Копирайт и распространение.
!
!
!
Copyright © 2012 Brian “Beej Jorgensen” Hall
!
iii
Перевод книги подготовлен на системе Mac OS X с использованием стандартных инструментов

Beej’s Guide to Network Содержание. Введение
1
1.1. Для кого
1 1.2. Платформа и компилятор
1 1.3. Официальная страница и книги
1 1.4. Для программистов Solaris/SunOS
1 1.5. Для программистов Windows
2 1.6. Политика Email
3 1.7. Зеркалирование
3 1.8. Замечания для переводчиков
3 1.9. Копирайт и распространение
3
2. Что такое сокет?
5
2.1. Два типа интернет сокетов
5 2.2. Низкоуровневый Вздор и Теория сетей
6
3. IP адреса, структуры и повреждение данных
9
3.1. IP адреса, версии 4 и 6 9
3.2. Порядок байт
11 3.3. Структуры
12 3.4. IP адреса, Часть Вторая
15
4. Прыжок изв. Системные вызовы или Облом
18
5.1. getaddrinfo() — К старту — товсь!
18 5.2. soket() — Получи дескриптор файла
21 5.3. bind() — На каком я порте
22 5.4. connect() — Эй, вы там
23 5.5. listen() — Позвони мне, позвони 5.6. accept() — Спасибо за звонок на порт 3490.”
25 5.7. send() and recv() — Поговори со мною, бэби!
26 5.8. sendto() и recvfrom() — Поговори со мной, стиль
27 5.9. close() и shutdown() — Прочь сглаз моих
28 5.10.getpeername() — Кто вы
28 5.11.gethostname() — Кто Я
29
6. Архитектура Клиент-­‐Сервер
30
6.1. Простой потоковый сервер
30 6.2. Простой потоковый клиент
33 6.3. Дейтаграммные сокеты
34
7. Немного продвинутая техника
38
7.1. Блокировка
38 7.2. Мультиплексирование синхронного ввода/вывода
38 7.3. Обработка незавершённых send()
44 iv

Beej’s Guide to Network Programming
7.4. Сериализация — Как упаковать данные
45 7.5. Дитя Инкапсуляции Данных
53 7.6. Широковещательные пакеты — Hello, world!
55
8. Общие вопросы
59
9. Man Страницы
65
9.1. accept()*
66*
9.2. bind()*
68*
9.3. connect()*
70*
9.4. close()*
72*
9.5. getaddrinfo(), freeaddrinfo(), gai_strerror()*
73*
9.6. gethostname()*
76*
9.7. gethostbyname(), gethostbyaddr()*
77*
9.8. getnameinfo()*
80*
9.9. getpeername()*
81*
9.10. errno*
82*
9.11. fcntl()*
83*
9.12. htons(), htonl(), ntohs(), ntohl()*
84*
9.13. inet_ntoa(), inet_aton(), inet_addr*
86*
9.14. inet_ntop(), inet_pton()*
87*
9.15. listen()*
89*
9.16. perror(), strerror()*
90*
9.17. poll()*
91*
9.18. recv(), recvfrom()*
93*
9.19. select()*
95*
9.20. setsockopt(), getsockopt()*
97*
9.21. send(), sendto()*
99*
9.22. shutdown()*
101*
9.23. socket()*
102*
9.24. struct sockaddr сотоварищи. Дополнительные ссылки
105 Книги
105 10.2.Web ссылки
105 10.3.RFC
106 Предметный указатель

108
v

Beej’s Guide to Network Programming
1. Введение Люди Программирование сокетов вас убивает Слишком трудно выискать что-то в
man страницах Вы хотите писать крутые Интернет программы, ноу вас нет времени продираться сквозь завалы struct-ур пытаясь определить нужно ли вам вызывать bind() перед connect() и т.д. и т.п.? Догадываетесь Я уже сделал эту грязную работу и умираю от желания поделиться со всеми Вы попали в нужное место. Этот документ должен дать С-программисту средней руки край, за который ему(ей) можно ухватиться в этом сетевом сумбуре. И поверьте, я полностью увлечен будущими настоящим тоже) и обновил это руководство для IPv6! Наслаждайтесь
1.1. Для кого Этот документ был написан как учебное пособие, а не как полное описание. Может быть лучше всего его читать людям, только начинающим программировать с сокетами, чтобы обрести точку опоры. В любом случае это неполное и исчерпывающее описание программирования сокетов. Надеюсь, однако, что этого будет достаточно, чтобы начать чувствовать)
1.2. Платформа и компилятор Код, приведенный в этом документе, проверенна с компилятором GNU Однако он должен строиться на любой платформе, использующей
gcc
. Естественно, он неприменим, если вы программируете для Windows. Смотрите раздел 1.5. Для программистов Windows.
1.3. Официальная страница и книги Официальное расположение этого документа http://beej.us/guide/bgnet/. Здесь вы также найдёте примеры кода и переводы на разные языки. Чтобы купить прекрасно переплетённые печатные копии (их называют книгами) посетите http://beej.us/guide/url/bgbuy. Я буду признателен, поскольку это поможет поддержать мой книгописательский стиль жизни.
1.4. Для программистов Solaris/SunOS Компилируя для Solaris или Sun OS вам нужно указать дополнительные ключи в командную строку для включения правильных библиотек. Для этого просто добавьте

-lnsl -lsocket -lresolv

вконец команды компилятора. Как здесь
$ cc -o server server.c -lnsl -lsocket -lresolv Если всё равно получаете ошибки, можете попробовать добавить ещё “-lxnet” вконец этой строки. Я точно не знаю, что это делает, но некоторым людям кажется, что так надо.
Ещё одно место, где могут обнаружиться проблемы, это вызов setsockopt(). Прототип отличается от моего на е, так что вместо int yes=1; введите char yes=’1’; Поскольку у меня нет Sun, я приведённую выше информацию не проверял. Это люди просто сказали мне по email.
!
!
1

Beej’s Guide to Network Programming
1.5. Для программистов Windows Вот здесь, исторически, я с удовольствием немного попеняю напросто потому что я её очень не люблю. Ноя должен быть честными сказать вам, что у Windows громадная база установок иона совершенно прелестная операционная система. Говорят разлука заставляет сердце любить сильнее и я верю, что это так. (А может это возраст. Но после десяти с гаком лет неиспользования Windows в моей работе я могу сказать. Я намного счастливее Так что я могу присесть и спокойно сказать Конечно, пользуйтесь Windows!” Ладно, стисну зубы искажу. Так что я до сих пор поддерживаю ваши попытки использовать вместо этого Linux ,
2
BSD или какую-нибудь Unix. Но люди любят то, что они любят, и Windows народ с удовольствием узнает, что в общем виде эта информация пригодна и для них, конечно с небольшими изменениями. Что вы можете круто сделать, это установить Cygwin , что есть набор Unix инструментов для Windows. За бокалом вина я слышал, что это позволяет всем нашим программам компилироваться незменёнными. Но некоторые из вас могут возжелать делать всё в Чисто Windows Стиле. Это очень отважный поступок ивам надо немедленно бежать и брать Unix. Нет, нет, я шучу Я хочу немного побыть дружественным(…нее) к Вот что вам нужно сделать (кроме установки Cygwin!): первое, игнорировать значительное множество упомянутых мною системных заголовочных файлов. Вам нужно включить только
#include Подождите Вам также нужно вызвать WSAStartup() перед тем как использовать что- либо в библиотеке сокетов. Код может выглядеть примерно так
#include
{
WSADATA wsaData;
// если это не работает
//WSAData wsaData;
// попробуйте так
// MAKEWORD(1,1) for Winsock 1.1, MAKEWORD(2,0) для Winsock 2.0: if (WSAStartup(MAKEWORD(1,1), &wsaData) != 0) { fprintf(stderr, «WSAStartup failed.n”); exit(1);
} Вам также нужно сказать компилятору компоновать с библиотекой Winsock, обычно называемой wsock32.lib, или winsock32.lib, или ws2_32.lib для Winsock 2.0. Под
VC++ это можно сделать вменю под Settings…. Кликните пункт Link и ищите окошко “Object/library modules”. Добавьте в этот список

wsock32.lib

(или что вы там предпочитаете. Или я так слышал В конце, после всей работы с библиотекой сокетов вам нужно вызвать WSACleanup(). Подробности смотрите в online help. Когда вы это сделали, то остальные примеры в этом пособии в общем должны работать, за некоторыми исключениями. Первое, вместо close() нужно использовать
closesocket(). Кроме того, select() работает только с дескриптором сокета, а нес дескриптором файла (типа 0 для stdin).
2
http://www.linux.com/
2
http://www.bsd.org/
3
http://www.cygwin.com/
4

Beej’s Guide to Network Также вы можете использовать класс CSocket. За информацией обращайтесь в страницы помощи компилятора. Информацию почитайте в Winsock FAQ и выходите отсюда. Последнее, я слышал, что в Windows нет системного вызова fork(), который як сожалению, использовал в некоторых примерах. Может быть нужно подключить библиотеку POSIX или что-то ещё чтобы он работал, или вы можете использовать
CreateProcess(). У вызова fork() нет аргументов, а CreateProcess() имеет около
48 миллиардов аргументов. Если вы до этого недоросли, то CreateThread() немного легче переваривать, но, к сожалению дискуссия о многопоточности выходит за рамки этого документа. Вы же понимаете, я могу только разглагольствовать об этом
1.6. Политика Email
Вообще-то я могу помочь с вопросами по email, так что пишите, не стесняйтесь, ноя не могу гарантировать ответа. Я живу весьма насыщенной жизнью и бывают времена, когда я не могу отвечать на ваши вопросы. В этом случае я удаляю послания. Ничего личного, просто у меня не всегда есть время для детального ответа на ваш запрос. Как правило, чем сложней вопрос, тем маловероятней я отвечу. Если вы сумеете сузить вопрос и включить в него дополнительную информацию (как платформа, компилятор, полученные сообщения об ошибках и всё, что по вашему мнению может мне помочь, тотем вероятнее получите ответ. Подсказки прочтите в ESR документе How To Ask Questions
The Smart Way . Если вы не получили ответа поковыряйтесь ещё немного, попробуйте найти решение и если оно до сих пор не найдено, напишите мне снова, приложив найденную информацию, и надеюсь, её будет достаточно чтобы я вам помог. И когда я уже извёл вас как писать и не писать мне, хотелось бы сказать, что я очень благодарен за всю хвалу документу, полученную мной за эти годы. Это настоящая моральная поддержка и мне радостно услышать, что он был использован надело добра :-) Спасибо
1.7. Зеркалирование
Зеркалирование этого сайта более чем приветствуется, и публичное и приватное. Если вы захотите публично зеркалировать сайт и иметь ссылку на него на главной странице, черкните мне на beej@beej.us
1.8. Замечания для переводчиков Если вы хотите перевести руководство на другой язык напишите мне на beej@beej.us и я поставлю ссылку на ваш перевод на главной странице. Не смущайтесь добавить ваше имя и контакты в перевод. Пожалуйста, обратите внимание на лицензионные ограничения в разделе 1.9.
Копирайт и распространение ниже. Если вы хотите разместить переводу меня, просто попросите. Я также поставлю на него ссылку. Любой способ замечателен.
1.9. Копирайт и распространение
Beej’s Guide to Network Programming is Copyright © 2012 Brian “Beej Jorgensen” Hall.
3
http://tangentsoft.net/wskfaq/
5
http://www.catb.org/

esr/faqs/smart-­‐questions.html
6

Beej’s Guide to Network За особыми исключениями для исходного кода ниже и переводов эта работа лицензирована Creative Commons Attribution-Noncommercial-No Derivative Works 3.0
License. Чтобы посмотреть копию этой лицензии посетите http://creativecommons.org/licenses/
by-nc-nd/3.0/ или напишите в Creative Commons, 171 Second Street, Suite 300, San
Francisco, California, 94105, USA. Одно особое исключение для части “No Derivative Works” лицензии таково это руководство может быть свободно переведено на любой язык при соблюдении точности и полноты перевода. Одинаковые ограничения налагаются и на переводи на исходное руководство. Перевод может также включать имя и контактную информацию переводчика. Представленный в этом документе исходный C код предоставляется в публичное пользование и полностью свободен от каких-либо лицензионных ограничений. Преподаватели могут свободно рекомендовать или предоставлять копии этого руководства своим студентам. Для информации свяжитесь с beej@beej.us
4


Beej’s Guide to Network Programming
2. Что такое сокет? Вы всё время слышите разговоры о “сокетах” и наверное даже удивляетесь что же они такое А они это способ общения с другими программами с использованием стандартных дескрипторов файлов Unix. Что Хорошо, может быть вы слышали утверждение некоторых хакеров от Unix, Здорово В
Unix всё — это файл. Всё, о чём они говорят, это факт, когда программа выполняет любую операцию ввода/вывода, она делает это чтением или записью в дескриптор файла. Дескриптор файла это обыкновенное целое число, связанное с открытым файлом. Но (и здесь прикол, этим файлом может быть сетевое подключение, FIFO, конвейер, терминал, реальный файл на диске и всё что угодно. В Unix всё есть файл Так что лучше поверьте, если вы захотите связаться с другой программой через Интернет, это надо делать через дескриптор файла. Где мне взять этот дескриптор файла, мистер Умник сейчас это, наверное, последний вопросу вас на уме, ноя на него отвечу. Вы вызываете системную программу
socket(). А она возвращает дескриптор сокета ивы связываетесь через него, используя специальные вызовы сокета send() и recv() (man send, man recv). Вы можете воскликнуть — Постойте Если это дескриптор файла, то почему, именем Нептуна, я не могу воспользоваться для связи нормальными вызовами read() и
write()?” Короткий ответ — Можете. Ответ подлиннее — Можете, но send() и предоставляют намного больше возможностей управления передачей данных. Что дальше Как насчёт такого сокеты бывают самые разные. Есть DARPA Интернет Адреса (Internet Sockets), путевые имена в локальном узле (Unix Sockets), CCITT X.25 адреса (X.25 Sockets которые вы можете спокойно игнорировать) и множество других в зависимости от вида используемой Unix. Этот документ касается только первого вида интернет сокетов.
2.1. Два типа интернет сокетов Что такое Есть два типа интернет сокетов? Да. Ладно, нет, вру. Существует множество типов, ноя не хочу вас пугать. Я собираюсь здесь говорить только о двух из них. Помимо этого я хочу сказать, что Сырые Сокеты” (Raw Sockets) также очень мощные ивы должны их просмотреть. Хорошо, начинаем. Что за два типа Один это Потоковые сокеты” (Stream Sockets), другой — “Дейтаграммные сокеты” (Datagram Sockets), на которые можно ссылаться как
“SOCK_STREAM” и, соответственно. Дейтаграммные сокеты иногда называют “неподключаемыми” (без установки логического соединения, хотя если очень хочется их можно подключить ом. (См. ниже. Потоковые сокеты это надёжные подключаемые двунаправленные потоки связи. Если вы отправите в сокет два послания в порядке “1, 2”, тона другой стороне они также появятся в порядке “1, 2”. Они также будут свободными от ошибок (error-free). Я в этом так уверен, что могу заткнуть себе уши пальцами и напевать ля-ля-ля если кто-нибудь будет утверждать обратное. Что использует потоковые сокеты? Ну, вы наверное слышали о

Понравилась статья? Поделить с друзьями:
  • Комфодерм мазь инструкция по применению взрослым цена отзывы аналоги
  • Газовый духовой шкаф maunfeld mgog 673s инструкция
  • Бинастим инструкция по применению цена отзывы
  • Имодиум для детей инструкция 8 лет
  • Нексиум пеллеты инструкция по применению цена