2. Интерфейс транспортного уровня

Интерфейс транспортного уровня (TLI) был разработан как альтернатива более раннему socket-интерфейсу. Он базируется на средстве ввода-вывода STREAMS, первоначально реализованном в версиях System V операционной системы UNIX. Основное достоинство STREAMS заключается в гибкой, управляемой пользователем многослойности модулей, по конвейерному принципу обрабатывающих информацию, передаваемую от прикладной программы к физической среде хранения/пересылки и обратно. Это делает STREAMS удобным инструментом для реализации стеков протоколов сетевого взаимодействия различной архитектуры (OSI, TCP/IP, DECnet, SNA, XNS и т.п.).

Хотя все современные реализации и версии ОС UNIX поддерживают socket-интерфейс по крайней мере для TCP/IP, для вновь разрабатываемых сетевых приложений настоятельно рекомендуется использовать TLI, что обеспечит их независимость от используемых сетевых протоколов.

С точки зрения прикладного программиста логика TLI очень похожа на логику socket-интерфейса (даже имена функций первого образованы от имен системных вызовов второго добавлением префикса "t_"). TLI реализован в виде библиотеки функций языка программирования СИ, разделенных (как и в случае с socket-интерфейсом) на четыре группы:

    1. локального управления;
    2. установления связи;
    3. обмена данными (ввода/вывода);
    4. закрытия связи.

Основу концепции TLI составляют три базовых понятия:

Поставщиком транспортных услуг (transport provider) называется набор модулей, реализующих какой-либо конкретный стек протоколов сетевого взаимодействия (в данном учебном пособии - TCP/IP) и обеспечивающий сервис транспортного уровня модели OSI [REF].

Пользователем транспорта (transport user) является любая прикладная программа, использующая сервис, предоставляемый ПТС на локальном узле сети.

Транспортная точка (transport endpoint) - абстрактное понятие (аналогичное socket'у), используемое для обозначения канала связи между пользователем транспорта и поставщиком транспортных услуг на локальном узле сети. Транспортная точка имеем уникальный для всей сети транспортный адрес (для сетей TCP/IP этот адрес образуется триадой: адрес узла сети, номер порта, используемый протокол транспортного уровня). Для ссылки на транспортные точки в функциях TLI используются их дескрипторы, подобные дескрипторам обычных файлов и socket'ов ОС UNIX.

Ниже рассматривается подмножество функций TLI и приводится пример его использования для создания учебного приложения, функционирующего согласно модели "клиент-сервер" в режиме без установления соединения.

2.1. Структуры данных TLI

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

Ниже дается описание некоторых структур данных, используемых TLI.

2.1.1. Структура данных netbuf

Структура netbuf служит составляющей более сложных ("законченных") структур данных TLI. Она используется как для передачи данных в функции TLI, так и для размещения в ней возвращаемой из функций информации. Эта структура имеет следующий вид

    struct netbuf {
        unsigned int maxlen;
        unsigned int len;
        char *buf;
        };

Поле buf указывает на область оперативной памяти (буфер), предназначенную для размещения в ней данных, передаваемых в функцию TLI или получаемых от нее. Семантика этих данных зависит от типа "вмещающей" структуры (см. ниже).

Поле len в ситуации, когда netbuf используется для передачи информации в функцию TLI, должно содержать длину (в байтах) данных, указываемых полем buf.

Поле maxlen в ситуациях, когда netbuf используется для получения информации от функции TLI, должно содержать длину (в байтах) области памяти, отводимой для этой цели и указываемой полем buf. Len после завершения функции будет содержать действительную длину данных, размещенных в буфере.

2.1.2. Структура данных t_bind

Структура t_bind определена в файле tiuser.h следующим образом

    struct t_bind {
        struct netbuf addr;
        unsigned int qlen;
        };

Поле addr типа struct netbuf используется для размещения транспортного адреса транспортной точки.

Назначение поля qlen зависит от использующей эту структуру функции TLI.

2.1.3. Структура данных t_call

Структура t_call определена в файле tiuser.h следующим образом

    struct t_call {
        struct netbuf addr;
        struct netbuf opt;
        struct netbuf udata;
        int sequence;
        };

Поле addr типа struct netbuf используется для размещения транспортного адреса транспортной точки.

Поле opt типа struct netbuf используется для размещения необязательной информации, модифицирующей или описывающей характеристики используемого конкретного поставщика транспортных услуг. Приложения, проектируемые как независимые от поставщика транспортных услуг, этим полем структуры t_call пользоваться не должны.

Поле udata типа struct netbuf используется для размещения передаваемых к партнеру или принимаемых от него в ходе взаимодействия через сеть данных.

Назначение поля sequence зависит от использующей эту структуру функции TLI.

2.1.4. Структура данных t_unitdata

Структура t_unitdata определена в файле tiuser.h следующим образом

    struct t_unitdata {
        struct netbuf addr;
        struct netbuf opt;
        struct netbuf udata;
        };

Поле addr типа struct netbuf используется для размещения транспортного адреса транспортной точки.

Поле opt типа struct netbuf используется для размещения необязательной информации, модифицирующей или описывающей характеристики используемого конкретного поставщика транспортных услуг. Приложения, проектируемые как независимые от поставщика транспортных услуг, этим полем структуры t_unitdata пользоваться не должны.

Поле udata типа struct netbuf используется для размещения передаваемых к партнеру или принимаемых от него в ходе взаимодействия через сеть данных.

Структура данных типа struct t_unitdata используется в функциях посылки/приема данных в режиме взаимодействия без установления соединения.

2.2. Функции локального управления

К функциям локального управления относятся функции создания/удаления транспортной точки (t_open/t_close), назначения/снятия транспортного адреса для транспортной точки (t_bind/t_unbind), выделения/освобождения оперативной памяти под структуры данных, используемые TLI (t_alloc/t_free) и другие.

2.2.1. Выделение памяти под TLI-структуры

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

    #include <tiuser.h>
    char *t_alloc (fd, structType, fields)
        int fd;
        int structType;
        int fields;

Аргумент fd задает дескриптор ранее созданной функцией t_open транспортной точки.

Аргумент structType задает тип структуры данных, под которую необходимо выделить память, и может принимать следующие значения:

Каждая из указанных структур (исключая struct t_info) содержит одно или несколько полей типа struct netbuf. Для каждого из таких полей можно также потребовать динамического выделения памяти. Аргумент fields конкретизирует это требование, допуская задание fields в виде побитового ИЛИ из следующих значений:

При успешном завершении функция возвращает указатель на размещенную структуру данных, в противном случае - NULL.

2.2.2. Освобождение памяти

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

    #include<tiuser.h>
    int t_free (ptr, structType)
        char *ptr;
        int structType;

Аргумент ptr указывает освобождаемую область памяти.

Аргумент structType задает тип структуры данных, занимающей память. Этот аргумент может принимать те же значения, что и аналогичный аргумент функции t_alloc.

Функция t_free освобождает оперативную память, занятую собственно структурой и всеми ее буферами типа struct netbuf.

При успешном завершении функция t_free возвращает ноль, иначе - число "-1" и устанавливает код ошибки в глобальной переменной t_errno.

2.2.3. Создание транспортной точки

Создание транспортной точки осуществляется функцией t_open, имеющей следующий вид

    #include <tiuser.h>
    #include <fcntl.h>
    int t_open (path, oflags, info)
        char *path;
        int oflags;
        struct t_info *info;

Аргумент path задает имя файла (располагающегося, как правило, в каталоге /dev), определяющего используемого поставщика транспортных услуг. Для стека протоколов TCP/IP такими файлами могут быть /dev/tcp (режим с установлением логического соединения) и /dev/udp (режим без установления логического соединения).

Аргумент oflags задает флаги открытия транспортной точки. Допустимые значения флагов - те же, что и для обычного системного вызова open. Если транспортная точка создается для двустороннего обмена информацией через нее, то значением oflags должно быть O_RDWR.

Аргумент info должен указывать на структуру данных типа struct t_info, поля которой заполняются функцией t_open при ее удачном завершении информацией о характеристиках используемого поставщика транспортных услуг. Если info задан как NULL, то информация о протоколе не возвращается. Для выделения памяти под структуру удобно использовать функцию t_alloc [REF].

При успешном завершении функция t_open возвращает дескриптор транспортной точки, используемый для ссылки на нее в большинстве функций TLI. Дескриптор транспортной точки аналогичен дескриптору socket'а. При обнаружении ошибки в ходе своей работы функция возвращает число "-1" и устанавливает код ошибки в глобальной переменной t_errno.

2.2.4. Назначение транспортного адреса

Для назначения транспортного адреса транспортной точке и ее активизации используется функция t_bind, имеющая следующий вид

    #include <tiuser.h>
    int t_bind (fd, req, ret)
        int fd;
        struct t_bind *req;
        struct t_bind *ret;

Аргумент fd задает дескриптор транспортной точки, созданной ранее с помощью функции t_open.

Аргумент req указывает на структуру t_bind, которая должна определять требуемый транспортный адрес для точки (поле req->addr) и максимальное количество запросов на соединение (поле req->qlen), одновременно обрабатываемых программой. Для программы-клиента поле req->qlen должно быть нулевым, а для программ-серверов, работающих в режиме с установлением логического соединения, оно, как правило, содержит 1 (необходимо учитывать, что не все поставщики транспортных услуг могут обеспечивать одновременную обработку сразу нескольких соединений к одной транспортной точке). Для программ-серверов, функционирующих в режиме без установления логического соединения, поле req->qlen смысла не имеет.

Если аргумент req имеет значение NULL, то функция t_bind сама назначит произвольный транспортный адрес для точки.

Аргумент ret должен указывать на область памяти под структуру t_bind, в которой после успешного выполнения функции будет размещена информация о транспортном адресе, назначенном транспортной точке. Если этот аргумент равен NULL, то информация о назначенном транспортном адресе возвращена не будет.

При успешном завершении функция t_bind возвращает ноль, иначе - число "-1" и устанавливает код ошибки в глобальной переменной t_errno.

2.2.5. Снятие транспортного адреса

Для снятия транспортного адреса у транспортной точки используется функция t_unbind, имеющая следующий вид

    #include <tiuser.h>
    int t_unbind (fd)
        int fd;

Аргумент fd задает дескриптор транспортной точки, которой ранее с помощью функции t_bind был назначен транспортный адрес.

После выполнения функции t_unbind для транспортной точки обмен данными через нее становится невозможным.

При успешном завершении функция t_unbind возвращает ноль, иначе - число "-1" и устанавливает код ошибки в глобальной переменной t_errno.

2.2.6. Удаление транспортной точки

Удаление транспортной точки осуществляется функцией t_close, имеющей следующий вид

    #include <tiuser.h>
    int t_close (fd)
        int fd;

Аргумент fd задает дескриптор транспортной точки, созданной ранее с помощью функции t_open.

При успешном завершении функция t_close возвращает ноль, иначе - число "-1" и устанавливает код ошибки в глобальной переменной t_errno.

2.3. Функции установления связи

Для установления логического соединения "клиент-сервер" в TLI используются функции t_listen, t_accept (на стороне сервера), t_connect (на стороне клиента), а также ряд других.

2.3.1. Ожидание запроса на соединение

Ожидание в программе-сервере запроса от клиента на соединение реализуется функцией t_listen, имеющей следующий вид

    #include <tiuser.h>
    int t_listen (fd, call)
        int fd;
        struct t_call *call;

Аргумент call должен указывать на область памяти под структуру t_call, в которой после успешного выполнения функции будет размещена следующая информация: транспортный адрес (call->addr) транспортной точки програм- мы-клиента, через которую она делает запрос на установление соединения; необязательные характеристики соединения (call->opt); необязательные данные (call->udata), передаваемые клиентом серверу вместе с запросом на соединение (однако, не любой поставщик транспортных услуг обеспечивает возможность передачи данных вместе с запросом на соединение); уникальный идентификатор соединения (call->sequence), имеющий смысл для программы-сервера только, если она допускает обслуживание одновременно нескольких соединений с нею.

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

При успешном завершении функция t_listen возвращает ноль, иначе - число "-1" и устанавливает код ошибки в глобальной переменной t_errno.

Примечание. Обратите внимание: схожие по названию функция t_listen и системный вызов listen из socket-интерфейса имеют различный смысл.

2.3.2. Прием запроса на соединение

Прием в программе-сервере запроса от клиента на соединение, "услышанного" функцией t_listen, реализуется функцией t_accept, имеющей следующий вид

    #include <tiuser.h>
    int t_accept (fd, resfd, call)
        int fd;
        int resfd;
        struct t_call *call;

Аргумент fd задает дескриптор транспортной точки, через которую ранее выполненная функция t_listen получила запрос на соединение.

Аргумент resfd задает дескриптор еще одной транспортной точки, созданной с теми же свойствами, что и точка, задаваемая аргументом fd, но имеющей другой транспортный адрес.

Аргумент call указывает на структуру данных типа t_call, поля которой должны содержать следующую информацию: транспортный адрес (call->addr) транспортной точки программы-клиента, через которую она сделала запрос на установление соединения; необязательные характеристики соединения (call->opt); необязательные данные (call->udata), возвращаемые сервером клиенту вместе с подтверждением установления соединения (однако, не любой поставщик транспортных услуг обеспечивает возможность такой передачи данных); уникальный идентификатор (call->sequence), присвоенный соединению функцией t_listen.

После успешного выполнения в программе-сервере функции t_accept устанавливается логическое соединение с клиентом и становится возможным обмен данными с ним через дескриптор resfd.

В типичной программе сервере транспортная точка с дескриптором resfd создается и активизируется после успешного завершения функции t_listen с помощью функций t_open и t_bind. Допустимой является ситуация, когда resfd = fd, но тогда программа-сервер до момента закрытия соединения с клиентом теряет возможность получать и ставить в очередь запросы на соединение от других клиентов.

При успешном завершении функция t_accept возвращает ноль, иначе - число "-1" и устанавливает код ошибки в глобальной переменной t_errno.

Программа-сервер может отказаться от установления соединения с клиентом, используя функцию t_snddis.

Примечание. Обратите внимание: схожие по названию функция t_accept и системный вызов accept из socket-интерфейса имеют различный смысл.

2.3.4. Отвергнуть запрос на соединение

Программа-сервер может отвергнуть запрос клиента на соединение, "услышанный" функцией t_listen, используя функцию t_snddis, имеющую следующий вид

    #include <tiuser.h>
    int t_snddis (fd, call)
        int fd;
        struct t_call *call;

Аргумент fd задает дескриптор транспортной точки, через которую ранее выполненная функция t_listen получила запрос на соединение.

Аргумент call указывает на структуру данных типа t_call, поля которой должны содержать следующую информацию: уникальный идентификатор (call->sequence), присвоенный соединению функцией t_listen; необязательные данные (call->udata), возвращаемые сервером клиенту вместе с информацией об отклонении запроса на соединения (однако, не любой поставщик транспортных услуг обеспечивает возможность такой передачи данных); поля call->addr и call->opt не используются.

При успешном завершении функция t_snddis возвращает ноль, иначе - число "-1" и устанавливает код ошибки в глобальной переменной t_errno.

Примечание. Функция t_snddis используется также для "экстренного" закрытия ранее установленного соединения, при этом аргумент call формируется несколько иначе.

2.3.5. Запрос на установление соединения

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

    #include <tiuser.h>
    int t_connect (fd, sndcall, rcvcall)
        int fd;
        struct t_call *sndcall;
        struct t_call *rcvcall;

Аргумент fd задает дескриптор транспортной точки, созданной ранее с помощью функции t_open и активизированной функцией t_bind.

Аргумент sndcall указывает на структуру данных типа t_call, в которой функции передается следующая информация: транспортный адрес (sndcall->addr) транспортной точки программы-сервера, к которой клиент делает запрос на установление соединения; необязательные характеристики соединения (sndcall->opt); необязательные данные (sndcall->udata), передаваемые клиентом серверу вместе с запросом на соединение (однако, не любой постав- щик транспортных услуг обеспечивает возможность передачи данных вместе с запросом на соединение).

Поле sndcall->sequence не используется и может принимать произвольное значение.

Аргумент rcvcall должен указывать на область памяти под структуру t_call, в которой после успешного выполнения функции будет размещена следующая информация: транспортный адрес (rcvcall->addr) транспортной точки в программе-сервере, с которой установлено соединение; необязательные характеристики соединения (rcvcall->opt); необязательные данные (rcvcall->udata), передаваемые клиенту сервером (посредством функции t_accept) вместе с подтверждением соединения (однако, не любой поставщик транспортных услуг обеспечивает возможность такой передачи данных); поле rcvcall->sequence не используется.

При успешном установлении соединения функция t_connect возвращает ноль. Если же сервер отверг запрос на соединение, то t_connect возвращает "-1" и устанавливает код ошибки TLOOK в глобальной переменной t_errno.

2.4. Функции обмена данными

В режиме с установлением логического соединения для обмена данными используются функции t_snd и t_rcv.

В режиме без установления логического соединения для обмена данными используются функции t_sndudata и t_rcvudata.

2.4.1. Посылка данных в режиме с установлением соединения

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

    #include <tiuser.h>
    int t_snd (fd, buf, len, flags)
        int fd;
        char *buf;
        unsigned int len;
        int flags;

Аргумент fd задает дескриптор транспортной точки, через которую посылаются данные.

Аргумент buf указывает на область памяти, содержащую передаваемые данные.

Аргумент len задает длину (в байтах) передаваемых данных.

Аргумент flags модифицирует исполнение функции t_snd. При нулевом значении этого аргумента функция t_snd полностью аналогична системному вызову write.

При успешном завершении t_snd возвращает количество переданных из области, указанной аргументом buf, байт данных. Если канал данных, определяемый дескриптором fd, оказывается "переполненным", то t_snd переводит программу в состояние ожидания до момента его освобождения.

2.4.2. Прием данных в режиме с установлением соединения

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

    #include <tiuser.h>
    int t_rcv (fd, buf, len, flags)
        int fd;
        char *buf;
        unsigned int len;
        int flags;

Аргумент fd задает дескриптор транспортной точки, через которую принимаются данные.

Аргумент buf указывает на область памяти, предназначенную для размещения принимаемых данных.

Аргумент len задает длину (в байтах) этой области.

Аргумент flags модифицирует исполнение системного вызова recv. При нулевом значении этого аргумента вызов t_rcv полностью аналогичен системному вызову read.

При успешном завершении t_rcv возвращает количество принятых в область, указанную аргументом buf, байт данных. Если канал данных, определяемый дескриптором fd, оказывается "пустым", то t_rcv переводит программу в состояние ожидания до момента появления в нем данных.

2.4.3. Посылка данных в режиме без установления соединения

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

    #include <tiuser.h>
    int t_sndudata (fd, unitdata)
        int fd;
        struct t_unitdata *unitdata;

Аргумент fd задает дескриптор транспортной точки, через которую посылаются данные.

Аргумент unitdata указывает на структуру данных типа t_unitdata, в которой функции передается следующая информация: транспортный адрес (unitdata->addr) транспортной точки программы-партнера по взаимодействию, которой посылается дейтаграмма; необязательные характеристики соединения (unitdata->opt); собственно данные (unitdata->udata), составляющие дейтаграмму, передаваемую партнеру по взаимодействию.

Если канал данных, определяемый дескриптором fd, оказывается "переполненным", то t_sndudata переводит программу в состояние ожидания до момента его освобождения.

При успешном выполнении функция t_sndudata возвращает ноль, в противном случае - число "-1" и устанавливает код ошибки в глобальной переменной t_errno.

2.4.4. Прием данных в режиме без установления соединения

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

    #include <tiuser.h>
    int t_rcvudata (fd, unitdata, flags)
        int fd;
        struct t_unitdata *unitdata;
        int *flags;

Аргумент fd задает дескриптор транспортной точки, через которую посылаются данные.

Аргумент unitdata указывает на структуру данных типа t_unitdata, в которой функции передается следующая информация: транспортный адрес (unitdata->addr) транспортной точки программы-партнера по взаимодействию, которой посылается дейтаграмма; необязательные характеристики соединения (unitdata->opt); собственно данные (unitdata->udata), составляющие дейтаграмму, передаваемую партнеру по взаимодействию.

Аргумент unitdata должен указывать на область памяти под структуру t_unitdata, в которой после успешного выполнения функции будет размещена следующая информация: транспортный адрес (unitdata->addr) транспортной точки в программе-партнере по взыимодействию, отправившей дейтаграмму; необязательные характеристики соединения (unitdata->opt); собственно данные (unitdata->udata), составляющие дейтаграмму, принимаемую от партнера по взаимодействию.

Аргумент flags должен указывать область памяти (типа int), в которой функция t_rcvudata может установить флаг T_MORE, сигнализирующий о том, что в канале передачи остались еще данные, составляющие дейтаграмму. Такая ситуация может возникнуть в случае, если размер буфера в unitdata->udata недостаточен для размещения в нем сразу всей дейтаграммы.

Если канал данных, определяемый дескриптором fd, оказывается "пустым", то t_rcvudata переводит программу в состояние ожидания до момента появления в нем данных.

При успешном выполнении функция t_rcvudata возвращает ноль, в противном случае - число "-1" и устанавливает код ошибки в глобальной переменной t_errno.

2.5. Функции закрытия соединения

TLI поддерживает две процедуры закрытия связи в режиме с установлением логического соединения: упорядоченную и экстренную.

Упорядоченная процедура реализуется парой функций t_sndrel и t_rcvrel и обеспечивает надежную доставку к партнеру по взаимодействию всех данных, планируемых для передачи. Однако не все поставщики транспортных услуг поддерживают эту процедура закрытия связи.

Экстренная процедура закрытия логического соединения реализуется функциями t_snddis и t_rcvdis.

В данном учебном пособии процедуры закрытия логического соединения не рассматриваются.

2.6. Пример использования TLI

В данном разделе рассматривается использование TLI в режиме взаимодействия без установления логического соединения на очень простом примере взаимодействия двух программ (сервера и клиента), функционирующих на разных узлах сети TCP/IP.

Содержательная часть программ примитивна:

  1. клиент направляет серверу вопрос "What must I do?";
  2. сервер, получив вопрос, выводит его в стандартный вывод и возвращает клиенту ответ "Continue" или "Cancel";
  3. клиент выводит в стандартный вывод ответ сервера и прекращает свою работу, если ответом было "Cancel", или выполняет действия первого пункта.

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

2.6.1. Программа-сервер

Текст программы-сервера на языке программирования СИ выглядит следующим образом

 1  #include <tiuser.h>
 2  #include <fcntl.h>
 3  #include <stdio.h>
 4  #include <sys/socket.h>
 5  #include <netinet/in.h>
 6  #include <netdb.h>
 7  #include <memory.h>
 8  #include <sys/time.h>

 9  #define SRV_PORT 1234
10  #define CONT_TXT "Continue\n"
11  #define CANC_TXT "Cancel\n"

12  main () {
13    int fd;
14    int flags;
15    time_t secs;
16    struct t_bind *bind;
17    struct t_unitdata *ud;
18    struct sockaddr_in *p_addr;
19    extern int t_errno;

20    fd = t_open("/dev/udp", O_RDWR, NULL);
21    bind = (struct t_bind *) t_alloc (fd, T_BIND, T_ADDR);
22    memset (bind->addr.buf, '\0', bind->addr.maxlen);
23    p_addr = (struct sockaddr_in *) bind->addr.buf;
24    p_addr->sin_family = AF_INET;
25    p_addr->sin_addr.s_addr = INADDR_ANY;
26    p_addr->sin_port = SRV_PORT;
27    bind->addr.len = sizeof(struct sockaddr_in);
28    bind->qlen = 0;
29    t_bind (fd, bind, bind);
30    ud = (struct t_unitdata *)t_alloc(fd,T_UNITDATA,T_ALL);

31    while (1) {
32      t_rcvudata (fd, ud, &flags);
33      write (1, ud->udata.buf, ud->udata.len);
34      secs = time (NULL);
35      if (secs % 3) {
36        strcpy (ud->udata.buf, CONT_TXT);
37        ud->udata.len = sizeof(CONT_TXT);
38        }
39       else {
40        strcpy (ud->udata.buf, CANC_TXT);
41        ud->udata.len = sizeof(CANC_TXT);
42        };
43      t_sndudata (fd, ud);
44      };
45    }

Строки 1...8 описывают включаемые файлы, содержащие определения для всех необходимых структур данных и символических констант.

Строка 9 приписывает целочисленной константе 1234 символическое имя SRV_PORT. В дальнейшем эта константа будет использована в качестве номера порта сервера. Значение этой константы должно быть известно и программе-клиенту.

Строки 10 и 11 приписывают последовательностям символов, составляющих тексты возможных ответов клиенту, символические имена CONT_TXT и CANC_TXT. Последним символом в последовательностях является символ перехода на новую строку '\n'. Сделано это для упрощения вывода текста ответа на стороне клиента.

В строке 20 создается (открывается) транспортная точка для организации режима взаимодействия без установления логического соединения с помощью протокола транспортного уровня UDP в сети TCP/IP (/dev/udp). Транспортная точка будет использоваться для двустороннего обмена информацией (O_RDWR). Третий аргумент функции t_open задан как NULL, поскольку данную программу особые характеристики поставщика транспортных услуг не интересуют.

В строке 21 выделяется оперативная память под структуру данных типа struct t_bind и под буфер данных, определяемый полем addr этой структуры (размер памяти, выделяемой под этот буфер, функция t_alloc вычисляет самостоятельно на основе информации о конкретном поставщике транспортных услуг). Поле addr этой структуры в своем буфере будет содержать транспортный адрес транспортной точки, который для поставщиков транспортных услуг UDP и TCP имеет тот же формат, что и адрес socket'а.

В строках 22...27 сначала обнуляется структура данных типа struct sockaddr_in, на которую указывает bind->addr.buf а затем заполняются ее отдельные поля. Использование константы INADDR_ANY упрощает текст программы, избавляя от необходимости использовать функцию gethostbyname для получения адреса локального узла, на котором запускается сервер.

В строке 28 переменной bind->qlen присваивается значение 0, поскольку наш сервер предназначен для работы в режиме без установления соединения.

Строка 29 посредством функции t_bind привязывает к транспортной точке транспортный адрес, описанный в структуре, на которую указывает bind. T_bind завершится успешно при условии, что в момент его выполнения на том же узле уже не функционирует программа, использующая этот же транспортный адрес.

Строка 31 служит заголовком бесконечного цикла обслуживания запросов от клиентов.

В строке 32 с помощью функции t_rcvudata читается запрос клиента.

В строке 33 текст запроса направляется в стандартный вывод, имеющий дескриптор файла номер 1. Так как строка ответа содержит в себе символ перехода на новую строку, то текст ответа будет размещен на отдельной строке дисплея.

Строка 34 содержит обращение к функции time, возвращающей количество секунд времени, прошедших с 1 января 1970 г. до текущего момента. Это значение используется в программе-сервере для выбора варианта ответа клиенту.

В строке 43 сервер с помощью функции t_sndudata отправляет клиенту ответ на его запрос, выбранный из двух возможных вариантов псевдослучайным образом.

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

2.6.2. Программа-клиент

Текст программы-клиента на языке программирования СИ выглядит следующим образом

 1  #include <tiuser.h>
 2  #include <fcntl.h>
 3  #include <stdio.h>
 4  #include <sys/socket.h>
 5  #include <netinet/in.h>
 6  #include <netdb.h>
 7  #include <memory.h>

 8  #define SRV_HOST "delta"
 9  #define SRV_PORT 1234
10  #define ASK_TXT "What must I do?\n"
11  #define CONT_TXT "Continue\n"
12  #define CANC_TXT "Cancel\n"

13  main () {
14    int fd;
15    int flags;
16    struct t_unitdata *ud;
17    struct sockaddr_in *p_addr;
18    struct hostent *hp;
19    extern int t_errno;

20    fd = t_open ("/dev/udp", O_RDWR, NULL);
21    t_bind (fd, NULL, NULL);
22    ud = (struct t_unitdata *) t_alloc (fd, T_UNITDATA, T_ALL);
23    memset (ud->addr.buf, '\0', ud->addr.maxlen);
24    p_addr = (struct sockaddr_in *) ud->addr.buf;
25    hp = gethostbyname (SRV_HOST);
26    p_addr->sin_family = AF_INET;
27    memcpy((char *)&(p_addr->sin_addr),hp->h_addr,hp->h_length);
28    p_addr->sin_port = SRV_PORT;
29    ud->addr.len = sizeof(struct sockaddr_in);
30    while (1) {
31      strcpy (ud->udata.buf, ASK_TXT);
32      ud->udata.len = sizeof(ASK_TXT);
33      t_sndudata (fd, ud);
34      t_rcvudata (fd, ud, &flags);
35      write (1, ud->udata.buf, ud->udata.len);
36      if ( strcmp(ud->udata.buf, CONT_TXT) )
37        break;
38      };
39    t_free ((char *) ud, T_UNITDATA);
40    t_close (fd);
41    exit (0);
42    }

В строках 8 и 9 описываются константы SRV_HOST и SRV_PORT, определяющие имя удаленного узла, на котором функционирует программа-сервер, и номер порта, к которому привязана транспортная точка сервера.

В строках 20 и 21 создается транспортная точка, имеющая не интересующий нас в этой программе транспортный адрес.

В строке 22 выделяется оперативная память под структуру данных типа struct t_unitdata и под три буфера, определяемых полями addr, opt и udata этой структуры (размер памяти, выделяемой под эти буфера, функция t_alloc вычисляет самостоятельно на основе информации о конкретном поставщике транспортных услуг).

В строке 25 посредством библиотечной функции gethostbyname транслируется символическое имя удаленного узла (в данном случае "delta"), на котором должен функционировать сервер, в адрес этого узла, размещенный в структуре типа hostent.

В строке 27 адрес удаленного узла копируется из структуры типа struct hostent в соответствующее поле структуры типа struct sockaddr_in, которая размещена в буфере ud->addr.

В строках 31 и 32 заполняются поля структуры ud->udata передаваемой серверу информацией и длиной этой информации.

В строке 33 с помощью функции t_sndudata посылается запрос серверу.

В строке 34 с помощью функции t_rcvudata принимается ответ от сервера. При этом транспортный адрес транспортной точки отправителя ответа (сервера) размещается функцией в ud->addr, а сами данные, составляющие ответ, - в ud->udata.

В строке 39 освобождается оперативная память, занимавшаяся структурой типа struct t_unitdata.

Строка 40 посредством функции t_close закрывает (удаляет) транспортную точку.