Структуры адресов сокетов являются неотъемлемой частью каждой сетевой программы. Мы выделяем для них место в памяти, заполняем их и передаем указатели на них различным функциям сокетов. Иногда мы передаем указатель на одну из этих структур функции сокета, и она сама заполняет поля структуры. Мы всегда передаем эти структуры по ссылке (то есть передаем указатель на структуру, а не саму структуру) и всегда передаем размер структуры в качестве дополнительного аргумента. Когда функция сокета заполняет структуру, длина также передается по ссылке, и ее значение может быть изменено функцией, поэтому мы называем такой аргумент «значение-результат» (value-result).
Структуры адресов сокетов являются самоопределяющимися, поскольку они всегда начинаются с поля family
, которое идентифицирует семейство адресов, содержащихся в структуре. Более новые реализации, поддерживающие структуры адресов сокетов переменной длины, также содержат поле, которое определяет длину всей структуры.
Две функции, преобразующие IP-адрес из формата представления (который мы записываем в виде последовательности символов ASCII) в численный формат (который входит в структуру адреса сокета) и обратно, называются inet_pton
и inet_ntop
. Эти функции являются зависящими от протокола. Более совершенной методикой является работа со структурами адресов сокетов как с непрозрачными (opaque) объектами, когда известны лишь указатель на структуру и ее размер. Мы разработали набор функций sock_
, которые помогут сделать наши программы не зависящими от протокола. Создание наших не зависящих от протокола средств мы завершим в главе 11 функциями getaddrinfo
и getnameinfo
.
Сокеты TCP предоставляют приложению поток байтов, лишенный маркеров записей. Возвращаемое значение функции read может быть меньше запрашиваемого, но это не обязательно является ошибкой. Чтобы упростить считывание и запись потока байтов, мы разработали три функции readn
, writen
и readline
, которые и используем в книге. Однако сетевые программы должны быть написаны в расчете на работу с буферами, а не со строками.
Упражнения
1. Почему аргументы типа «значение-результат», такие как длина структуры адреса сокета, должны передаваться по ссылке?
2. Почему и функция readn
, и функция writen
копируют указатель void*
в указатель char*
?
3. Функции inet_aton
и inet_addr
характеризуются традиционно нестрогим отношением к тому, что они принимают в качестве точечно-десятичной записи адреса IPv4: допускаются от одного до четырех десятичных чисел, разделенных точками; также допускается задавать шестнадцатеричное число с помощью начального 0x
или восьмеричное число с помощью начального 0 (выполните команду telnet 0xe
, чтобы увидеть поведение этих функций). Функция inet_pton
намного более строга в отношении адреса IPv4 и требует наличия именно четырех чисел, разделенных точками, каждое из которых является десятичным числом от 0 до 255. Функция inet_pton
не разрешает задавать точечно- десятичный формат записи адреса, если семейство адресов — AF_INET6
, хотя существует мнение, что это можно было бы разрешить, и тогда возвращаемое значение было бы адресом IPv4, преобразованным к виду IPv6 (см. рис. А.6). Напишите новую функцию inet_pton_loose
, реализующую такой сценарий: если используется семейство адресов AF_INET
и функция inet_pton
возвращает нуль, вызовите функцию inet_aton
и посмотрите, успешно ли она выполнится. Аналогично, если используется семейство адресов AF_INET6
и функция inet_pton
возвращает нуль, вызовите функцию inet_aton
, и если она выполнится успешно, возвратите адрес IPv4, преобразованный к виду IPv6.
Глава 4
Элементарные сокеты TCP
4.1. Введение
В этой главе описываются элементарные функции сокетов, необходимые для написания полностью работоспособного клиента и сервера TCP. Сначала мы опишем все элементарные функции сокетов, которые будем использовать, а затем в следующей главе создадим клиент и сервер. С этими приложениями мы будем работать на протяжении всей книги, постоянно их совершенствуя (см. табл. 1.3 и 1.4).
Мы также опишем параллельные (concurrent) серверы — типичную технологию Unix для обеспечения параллельной обработки множества клиентов одним сервером. Подключение очередного клиента заставляет сервер выполнить функцию fork
, порождающую новый серверный процесс для обслуживания этого клиента. Здесь применительно к использованию функции fork
мы будем рассматривать модель «каждому клиенту — один