21.1.2 Ориентация на Unix
Исходный вариант интерфейса socket был разработан для Unix. Архитектура этой операционной системы позволяет единообразно обращаться к файлам, терминалам и вводу/выводу. Операции с файлами предполагают использование одного из следующих вызовов:
descriptor = open(filename, readwritemode)
read(descriptor, buffer, length)
write(descriptor, buffer, length)
close(descriptor)
Когда программа открывает файл, вызов создает в памяти область, называемую управляющим блоком файла (file control block) и содержащую сведения о данном файле (например, имя, атрибуты и место размещения).
Вызов возвращает небольшое целое число, именуемое дескриптором файла (file descriptor). Дескриптор используется в программе для идентификации файла в последующих операциях. При чтении или записи в файле специальный указатель из дескриптора отслеживает текущее положение внутри файла
Похожие методы используются в socket для TCP/IP. Главным отличием между программным интерфейсом socket и файловой системой Unix является то, что в socket применяется несколько дополнительных предварительных вызовов, необходимых для сбора всех сведений перед формированием соединения. Не считая дополнительной работы при запуске, для чтения или записи, в сети применяются те же самые операции.
21.2 Службы socket
Программный интерфейс socket обеспечивает работу трех служб TCP/IP: потокового обмена, обмена датаграммами в UDP и пересылки необработанных данных непосредственно на уровень IP. Все эти службы показаны на рис. 21.1.
Рис. 21.1. Программный интерфейс socket
Вспомним, что API интерфейса socket разрабатывался не только для TCP/IP. Исходная цель заключалась в создании единого интерфейса для различных коммуникационных протоколов, в том числе и для XNS (Xerox Network Systems).
Результат получился несколько странным. Например, некоторые вызовы socket содержат необязательные параметры, не имеющие никакого отношения к TCP/IP — они необходимы в других протоколах. Кроме того, иногда программист обязан указывать длину для параметров фиксированного размера, например для адресов IP версии 4. Смысл этого в том, что, хотя длина адреса в IP версии 4 всегда равна 4 байт, в программных интерфейсах для других протоколов могут использоваться адреса другой длины.
21.3 Блокированные и неблокированные вызовы
Когда программа читает данные из сетевого соединения, трудно предсказать заранее, как долго будет продолжаться эта операция. Программист может только дождаться полного завершения чтения или перейти на другое место в программе и периодически проверять значение переменной статуса соединения, либо разрешить программное прерывание по окончании операции.
■ Вызов с последующим ожиданием называется блокированным (blocking) или синхронным (synchronous).
■ Вызов с переходом на выполнение других операций называется неблокированным (nonblocking) или асинхронным (asynchronous).
В программном интерфейсе socket вызовы могут быть блокированными или неблокированными, а программист способен управлять поведением вызова.
21.4 Вызовы socket
Вызовы socket подготавливают сетевое взаимодействие путем создания блоков управления пересылкой (Transmission Control Block — TCB). В некоторых изданиях процесс создания TCB называется созданием socket. Вызов socket возвращает небольшое целое число, называемое дескриптором и используемое для идентификации соединения во всех последующих запросах.
В TCB используется множество параметров. Перечисленные ниже параметры предоставляют информацию, необходимую для создания сеанса TCP:
■ Локальный IP-адрес
■ Локальный порт
■ Протокол (например, TCP или UDP)
■ Удаленный IP-адрес
■ Удаленный порт
■ Размер выходного буфера
■ Размер приемного буфера
■ Текущее состояние TCP
■ Усредненное время цикла пересылка-получение
■ Отклонение от усредненного времени цикла пересылка-получение
■ Текущее время тайм-аута повторной пересылки
■ Количество выполняемых повторных пересылок
■ Текущий размер окна отправки
■ Максимальный размер отправляемого сегмента
■ Порядковый номер последнего подтвержденного по ACK байта
■ Максимальный размер получаемого сегмента
■ Порядковый номер следующего отправляемого байта
■ Разрешение/запрещение отслеживания
21.5 Программирование работы TCP socket
Рассмотрим вызовы из программ к socket, используемые при взаимодействии с TCP. Для упрощения не будем указывать в вызовах параметры ввода/вывода и сконцентрируемся на более важных функциях и их взаимоотношениях. Детали формирования параметров описаны ниже.
21.5.1 Модель сервера TCP
Типичный сценарий для взаимодействия с сервером TCP предполагает наличие главного процесса, который большую часть времени отслеживает запросы от клиентов. Когда клиент соединяется с сервером, сервер обычно создает новый дочерний процесс, который будет реально выполнять всю работу для клиента. Сервер передает клиента этому дочернему процессу и снова возвращается к отслеживанию запросов от других клиентов.
Иногда клиенты появляются быстрее, чем их может обслужить главный процесс. Как поступить в этом случае? Стандартный механизм заключается в том, что при запуске главного процесса в TCP создается очередь, которая способна хранить несколько запросов на соединение. Запросы клиентов, которые нельзя обслужить сразу, помешаются в очередь и обрабатываются в порядке этой очереди. Предположим, что очередь заполнена до конца и поступает запрос от очередного клиента. В этом случае соединение с новым клиентом не будет создано.
21.5.2 Пассивное открытие сервера TCP
Сервер готовится к принятию запроса на соединение и пассивно ожидает обращения клиентов. При подготовке он выполняет ряд запросов:
socket() | Сервер идентифицирует тип связи (в данном случае TCP). Локальная система создает соответствующую структуру данных TCB для взаимодействия с сервером и возвращает дескриптор socket. |
bind() | Сервер устанавливает локальный IP-адрес и порт, которыми он будет пользоваться. Вспомним, что хост может иметь несколько IP-адресов. Сервер может применять один IP-адрес или указать, что желает принимать соединения от любого локального IP-адреса. Он может запросить определенный порт или разрешить связывание запроса с одним из доступных свободных портов. |
listen() | Сервер устанавливает длину очереди для клиентов. |
accept() | Сервер готов принимать соединения от клиентов. Если очередь не пуста, принимается первый полученный клиентский запрос. Запрос accept() создает новый TCB, который будет использоваться для соединения этого клиента и возвращать новый дескриптор соединения серверу. |