TLI и STREAMS


5.1. ВВЕДЕНИЕ

TLI (Transport Level Interface) был введен в 1986 году фир-
мой AT&T вместе с системой UNIX System V Release 3.0.
До TLI только сокеты могли предоставлять механизмы, необхо-
димые для меж-процессных взаимодействий между удаленными
машинами. TLI функционирует подобно сокетам, предоставляя дос-
туп к виртуальной транспортной службе, способной адаптировать-
ся к транспортным протоколам как OSI, так и TCP или UDP.
TLI основан на применении нового системного механизма, также
внедренного фирмой AT&T - STREAMS.


5.2. ФУНКЦИОНАЛЬНЫЕ ВОЗМОЖНОСТИ

5.2.1. Предоставляемые услуги

TLI представляет собой библиотеку функций, позволяющих двум
удаленным программам вступить в связь между собой и обмени-
ваться данными.
Как указывает его название, TLI представляет собой интерфейс
"транспортного" уровня. Можно выбирать между TLI-надстройкой
над транспортными уровнями OSI и TCP/UDP. Интерфейс TLI маскиру-
ет особые механизмы, реализуемые транспортной службой. Прик-
ладные программы, таким образом, оказываются независимыми от
транспортного уровня, при условии использования механизма
транспортного отбора ("network selection") и определения адре-
сов ("name-to-address mapping"). Эти механизмы включены в UNIX
System V Release 4.
Использование TLI - надстройки над транспортными уровнями
OSI (в режиме виртуального соединения и в режиме отсутствия
соединения) позволяет воспользоваться всеми потенциальными
возможностями этих стандартизованных уровней:
- согласование качества услуг;
- запрос на соединение;
- отказ в соединении;
- передача обычных и экспресс-данных;
- синхронизованное рассоединение...

5.2.2. Механизмы реализации

Модель "клиент-сервер"
----------------------

Подобно сокетам, TLI позволяет конструировать распределенные
прикладные программы в соответствии с моделью "клиент-сервер"
(рис. 5.1.).

+
---------------¬ --------------¬
¦ Программа ¦ ¦ Программа ¦
¦ клиент ¦ ¦ сервер ¦
L------^-------- L------^-------
¦ ¦
¦ ¦
¦ ¦
--------v--------¬ --------v--------¬
¦ TLI ¦ ¦ TLI ¦
L-----------^----- L----^------------
¦ ¦
-----v-------------v--¬
¦Транспортный протокол¦
+---------------------+
¦ Сетевой протокол ¦
+---------------------+
¦ Сетевой драйвер ¦
L----------------------

Рис. 5.1. Распределенная обработка с использованием TLI.


TLI и транспортные службы
-------------------------

TLI вводит два понятия:
- transport endpoint (точка доступа транспортной службы):
точка доступа в канал связи между транспортным уровнем и
пользовательским процессом;
- transport provider ("поставщик" транспортных служб): про-
токольный элемент уровня 4 в системе OSI, который предос-
тавляет услуги, с которыми сопрягается TLI.
На рис. 5.2. представлены взаимоотношения транспортной служ-
бы и пользовательского процесса:
- пользовательский процесс посылает запросы транспортной
службе через интерфейс;
- транспортная служба подтверждает интерфейсу получение со-
бытий (данных для считывания...).

+ -----------------------¬
¦ Пользователь трансп. ¦
¦ услуг ¦
¦ ¦
L----T------^-----------
¦ ¦
Запросы ¦ ¦ Точка доступа
¦ ¦ транспортной службы
v ¦ ----------------------¬
-----------+------+------T------+ Транспортный ¦
¦ ^ ¦ интерфейс ¦
¦ ¦ L----------------------
¦ ¦ События и
¦ ¦ сигналы
-----------v------+---¬
¦ Поставщик трансп. ¦
¦ услуг ¦
L----------------------
Рис. 5.2. Взаимоотношения поставщика и пользователя транс-
портных услуг.

Связь между клиентом и сервером
-------------------------------

Клиент и сервер явно указывают используемый протокол. Сервер
связывает свою службу с адресом ("binding" или связывание),
затем переходит в состояние ожидания запросов от клиентов.
Клиент адресует свои запросы, включая в них идентификатор
сервера (сетевой адрес машины, где находится сервер и прог-
раммный селектор, уникальным образом идентифицирующий сервер).
Считывание и запись данных, при использовании режима логи-
ческого соединения, осуществляются через буфер (см. предыдущую
главу). Кроме того, можно осуществить запись экспресс-данных.
Как правило, программа из библиотеки TLI включает один или
несколько системных вызовов, относящихся к STREAMS и к типу
используемого протокола (TCP,IP...).

5.2.3. Реализация TLI

TLI представляет собой библиотеку функций Си, обеспечивающую
сопряжение с различными сетевыми протоколами, реализованными в
ядре (рис. 5.3.), с использованием механизма STREAMS, который
будет описан в разделе 5.4.
+
-------------------------¬
¦ ¦
¦ Пользовательский ¦
¦ процесс ¦
+------------------------+
¦ Библиотека TLI ¦
L------------^------------
¦
------------------+------------------¬
¦ ------------v-----------¬ ¦
¦ ¦ Головной модуль ¦ ¦
¦ ¦ Stream ¦ ¦
¦ Я L-----------^------------ ¦
¦ Д ¦ ¦
¦ Р ¦ ¦
¦ О ------------v------------¬ ¦
¦ ¦ Модуль Stream ¦ ¦
¦ ¦ протоколы ¦ ¦
¦ L-----------^------------- ¦
¦ ¦ ¦
¦ ------------v------------¬ ¦
¦ ¦Модуль Stream драйвер ¦ ¦
¦ L------------------------- ¦
L-------------------------------------
Рис. 5.3. Реализация TLI


5.3. ПРИМЕНЕНИЕ

5.3.1. Текущее применение

Принципы применения
-------------------

Библиотека TLI требует особого редактирования связей с по-
мощью программы (-lnsl). Различные структуры и постоянные опи-
саны во включаемом файле /usr/include/tiuser.h.
Применение библиотеки различается, в зависимости от того, в
каком режиме используется транспортная служба (с установлением
соединения и без такового).

Использование в режиме соединения

Клиент:
- открывает точку доступа транспортной службы;
- присваивает услуге адрес ("binding");
- устанавливает соединение с сервером, выдавая адрес сервера
и адрес службы;
- считывает или осуществляет запись в канале связи;

Сервер:
- открывает точку доступа транспортной службы;
- связывает адрес с услугой ("binding");
- устанавливается в режим ожидания входящих соединений, соз-
давая очередь.
Для каждого входящего соединения:
- дает согласие на соединение, если это возможно
(новое соединение открывается с теми же характеристи-
ками;
- считывает или осуществляет запись в созданном таким
образом канале связи.
Инициатива закрытия соединения зависит от семантики задачи.
На рис. 5.4. показаны вызовы, используемые в режиме установ-
ления соединения.


----------------¬ ----------------¬
¦ t_open () ¦ ¦ t_open () ¦
+---------------+ +---------------+
¦ t_bind () ¦ ¦ t_bind () ¦
+---------------+ +---------------+
¦ t_alloc () ¦ ¦ t_alloc () ¦
L-------T-------- L-------T--------
¦ --------+-------¬
г=======¦=======¬ ¦ t_listen () ¦
¦ t_connect () ¦-----¬ L-------T--------
L=======T=======¦ L---------->-------+-------¬
¦ Установление ¦ t_open () ¦
¦ соединения +---------------+
+<--------------------¬ ¦ t_bind () ¦
г=======¦=======¬ ¦ +---------------+
¦ t_snd () ¦-----------¬ ¦ ¦ t_accept () ¦
L=======T=======- ¦ ¦ L-------T--------
¦ Передача ¦ L----------+
г=======¦=======¬ данных ¦ г=======¦========¬
¦ t_rcv () ¦<--------¬ L--->¦ t_rcv () ¦
L=======T=======- ¦ L=======T========-
--------+-------¬ ¦ г=======¦========¬
¦ t_sndrel () +-----¬ L------¦ t_snd () ¦
L-------T--------Синхронизирован.L=======T========-
¦ отсоединение----------->+
+<----------------¬ --------+--------¬
--------+-------¬ ¦ ¦ t_rcvrel () ¦
¦ t_rcvrel () ¦ ¦ L-------T---------
L---------------- ¦ --------+--------¬
L------+ t_sndrel () ¦
L-----------------
г====¬ блокирующиеся вызовы
L====-
-----¬ не-блокирующиеся вызовы
L-----

Рис. 5.4. Использование TLI в режиме соединения.

Некоторые вызовы являются блокирующимися:
Клиент:
- t_connect () до тех пор,пока сервер осуществит не t_accept
();
- t_snd () при переполнении буфера передачи;
- t_rcv () до тех пор, пока вследствие t_snd () сервера не
будет получен хотя бы один символ.
Сервер:
- t_listen () до тех пор, пока одним из клиентов не будет
получен запрос на входящее соединение;
- t_rcv () до тех пор, пока вследствие t_snd () клиента не
будет получен хотя бы один символ;
- t_snd () при переполнении буфера передачи;

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

Использование в режиме без установления логического соеди-
нения

Клиент:
- открывает точку доступа транспортной службы;
- присваивает службе адрес ("binding" - связывание);
- считывает или осуществляет запись в точке доступа транс-
портной службы.
Сервер:
- открывает точку доступа в транспортную услугу;
- связывает адрес с услугой ("binding");
- считывает или осуществляет запись в точке доступа транс-
портной службы.
На рис. 5.5. показаны вызовы, используемые с транспортной
службой в режиме без установления

Адресация
---------

TLI не навязывает структуру транспортного адреса. Родовой
адрес (структура netbuf) определен в файле <tiuser.h>.

#include <tiuser.h>
struct netbuf {
unsigned int maxlen; /*максимальная длина адреса*/
unsigned int len; /*эффективная длина адреса*/
char *buf; /*указатель адреса*/
};

+
------------------¬ ----------------¬
¦ t_open () ¦ ¦ t_open () ¦
+-----------------+ +---------------+
¦ t_bind () ¦ ¦ t_bind () ¦
+-----------------+ +---------------+
¦ t_alloc () ¦ ¦ t_alloc () ¦
L-------T---------- L-------T--------
¦ ¦
--------+---------¬ ¦
¦ t_sndudata () +---¬ ¦
L-------T---------- ¦ г=======¦=======¬
¦ L------->¦ t_rcvudata ()¦
¦ L=======T=======-
¦ --------+-------¬
г=======¦=========¬ --------+ t_sndudata ()¦
¦ t_rcvudata () ¦<---- L----------------
L=================-

г====¬ блокирующиеся вызовы
L====-
-----¬ не-блокирующиеся вызовы
L-----
Рис. 5.5. Использование TLI в режиме отсутствия соединения.


Включаемый файл
---------------

Файл tli.h содержит файлы,обычно необходимые для кодирования
программ TLI:

ПРОГРАММА 38

/* файл tli.h
#include <stdio.h>
#include <tiuser.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/conf.h>
#include <stropts.h>


5.3.2. Примитивы

Основные примитивы
------------------

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

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

- Управление ошибками

void t_error (message)
char *message;

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

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

char *t_alloc (fd, structype, fields)
int fd; /*точка доступа транспортной службы*/
int structype; /*тип, связанный со структурой*/
int fields; /*поля структур netbuf*/

int t_free (ptr, structype)
char *ptr; /*указатель структуры*/
int structype; /*тип, связанный со структурой*/

Значения, связанные с параметром structype определены в таб-
лице 5.1.
Значения, связанные с параметром fields определены в таблице
5.2. Можно сделать дизъюнкцию всех этих значений, самое прос-
тое - это использовать T_ALL.

+
Таблица 5.1. Значения параметра structype.
-----------------------T---------------------------¬
¦ structype ¦ Тип структуры ¦
+----------------------+---------------------------+
¦ T_BIND ¦ struct t_bind ¦
+----------------------+---------------------------+
¦ T_CALL ¦ struct t_call ¦
+----------------------+---------------------------+
¦ T_DIS ¦ struct t_discon ¦
+----------------------+---------------------------+
¦ T_INFO ¦ struct t_info ¦
+----------------------+---------------------------+
¦ T_OPTMGMT ¦ struct t_optmgt ¦
+----------------------+---------------------------+
¦ T_UNITDATA ¦ struct t_unitdata ¦
+----------------------+---------------------------+
¦ T_UDERROR ¦ struct t_uderr ¦
L----------------------+----------------------------


Таблица 5.2. Значения параметра fields

---------------------T---------------------------------------¬
¦ fields ¦ Поля структур netbuf ¦
¦ ¦ резервированные и инициализированные ¦
+--------------------+---------------------------------------+
¦ ¦ ¦
¦ T_ALL ¦ все ¦
¦--------------------+---------------------------------------+
¦ T_ADDR ¦ addr ¦
+--------------------+---------------------------------------+
¦ T_OPT ¦ opt ¦
+--------------------+---------------------------------------+
¦ T_UDATA ¦ udata ¦
¦ ¦ ¦
L--------------------+----------------------------------------
- Создание точки доступа транспортной службы
int t_open (path, oflag, pinfo)
char *path; /*имя, связанное с транспортной службой*/
int oflag; /*флаг: аналогично файлу open ()*/
struct t_info *pinfo /*информация о трансп. службе*/

Структура t_info cодержит, при возврате, следующую информа-
цию:

struct t_info {
long addr; /*максимальный адрес транспортной службы*/
long options; /*максимальная величина транспортных опций*/
long tsdu; /*максимальная длина TSDU = transport service
data unit*/
long etsdu; /*максимальная длина ETSDU = expedited
transport data unit*/
long connect; /*максимальная длина данных, которую можно
передать при запросе соединения*/
long discon; /*максимальная длина данных, которую можно
передать при рассоединении*/
long servtypr; /*тип поддерживаемой службы, либо
T_COS: услуги, ориентированные на соединение
с согласованным разъединением.
T_COS_ORD: услуги ориентированные на соединение
с согласованным разъединением.
T_CLTS: услуги без соединения.*/
};

В качестве параметра выдается ссылка на специальный файл
UNIX (из каталога /dev) , через который можно обеспечить дос-
туп к отдельной службе (пример: /dev/tcp для транспортной
службы TCP).
Можно задать значение NULL в параметре pinfo, если нет необ-
ходимости в получении информации об использованной транспорт-
ной службе.
Примитив посылает дескриптор точки доступа в транспортную
службу, которую могут использовать другие функции.

ПРОГРАММА 39

/* опции, управляемые транспортным уровнем dev/tcp
#include "tli.h"

/* обращение к программе транспортной службы и вывод на экран
параметров конфигурации */
main()
{
char name[] = "/dev/tcp" ;
int tfd; /* дескриптор доступа к службе
struct t_info info; /* информационная структура
/* создание точки входа в транспортный уровень
tfd = t_open(name, O_RDWR, &info);
/* вывод на экран параметров конфигурации : последовательность
printf(), выводящих на экран элементы структуры info
..................
}


- Binding (связывание)

int t_bind (fd, prequest, preturn)
int fd; /* точка доступа транспортной
службы*/
struct t_bind *prequest /* посылаемый адрес*/
struct t_bind *preturn /* возвращаемый адрес*/

Структура t_bind cодержит следующую информацию:
struct t_bind {
struct netbuf addr; /*адрес*/
unsigned int qlen; /*максимальное число одновременных
соединений*/
};

Эта функция связывает адрес с точкой доступа транспортной
службы, с учетом его использования для установления соедине-
ния, либо для обмена сообщениями в режиме без установления со-
единения. Именно этот адрес будет использоваться удаленной
программой во время связи.
Если в параметре prequest задается значение NULL, транспорт-
ная служба выбирает адрес, который будет возвращен в параметре
preturn.
Параметр qlen применяется только в транспортных программах в
режиме соединения и является эквивалентным параметру примитива
listen () сокетов.

- Закрытие точки доступа транспортной службы
int t_close (fd)
int fd; /*точка доступа транспортной службы*/

Этот примитив освобождает ресурсы, выделенные транспортной
службой. При использовании транспортной службы в режиме соеди-
нения, он резко обрывает соединение.

- Просмотр событий
int t_look (fd)
int fd; /*точка доступа транспортной службы*/

Эта программа позволяет восстановить событие, происшедшее в
точке доступа транспортной службы. Она, например, используется
в случае ошибки при вызове функции (переменная ошибки t_errno
установлена в TLOOK) для того, чтобы узнать, какое событие
связано с ошибкой. Возвращенные значения целого приведены в
таблице 5.3.

Таблица 5.3. Значения, возвращаемые функцией t_look ().

------------------T----------------------------------------------¬
¦ Событие ¦ Описание ¦
+-----------------+----------------------------------------------+
¦ T_CONNECT ¦ подтверждение соединения ¦
+-----------------+----------------------------------------------+
¦ T_DATA ¦ обычные данные ¦
+-----------------+----------------------------------------------+
¦ T_DISCONNECT ¦ разъединение ¦
+-----------------+----------------------------------------------+
¦ T_ERROR ¦ сигнализация фатальной ошибки ¦
+-----------------+----------------------------------------------+
¦ T_EXDATA ¦ экспресс-данные ¦
+-----------------+----------------------------------------------+
¦ T_LISTEN ¦ сигнализация соединения ¦
+-----------------+----------------------------------------------+
¦ T_ORDREL ¦ сигнализация согласованного разъединения ¦
+-----------------+----------------------------------------------+
¦ T_UDERR ¦ сигнализация ошибки в дейтаграмме ¦
L-----------------+-----------------------------------------------

Функции в режиме установления соединения

- Запрос на соединение, сформулированный клиентом
int t_connect (fd, psendcall, precvcall)
int fd; /*точка доступа транспортной службы*/
struct t_call *psendcall;/*адрес сервера*/
struct t_call *precvcall;/*возвращаемая информация*/

Структура t_call содержит следующую информацию:
struct t_call {
struct netbuf addr; /*адрес*/
struct netbuf opt; /*опции*/
struct netbuf udata; /*пользовательские данные*/
int sequence; /*используется t_listen*/
};

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

- Перевод сервера в состояние ожидания

int t_listen (fd, pcall)
int fd; /*точка доступа транспортной службы*/
struct t_call *pcall; /*адрес процесса-клиента*/

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

- Согласие сервера на соединение

int t_accept (fd, connfd, pcall).
int fd; /*точка доступа транспортной службы*/
int connfd; /*новая точка транспортной службы*/
struct t_call *pcall; /*адрес процесса-клиента*/

Сервер дает согласие на установление соединения. Примитив
связывает текущую точку доступа транспортной службы с новой
точкой доступа (connfd) полученной посредством вызова t_open
(), с последующим t_bind (), для того, чтобы присвоить ему ад-
рес.
Для последующих обменов данными сервер может использовать
текущую точку доступа (итеративный сервер) или новую (парал-
лельный сервер). Иcходя из выбранной точки доступа и протекают
эти обмены данными.

- Отправление и получение данных

t_snd (), t_rcv ()

Можно передать экспресс-данные , установив флаг T_EXPEDITED,
или данные в записанной форме: каждое сообщение (кроме послед-
него) содержит опцию T_MORE.

- Закрытие соединения

t_snddis (), t_rcvdis, t_sndrel, t_rcvrel ()

Две первые программы соответствуют резкому разъединению (со-
бытие T_DISCONNECT, связанное с функцией t_look ()). Две пос-
ледние - упорядоченному разъединению, при котором все еще не
переданные данные маршрутизируются перед закрытием соединения
(событие T_ORDREL, связанное с функцией t_look ()).


Функции в режиме отсутствия соединения

- Отправление и прием сообщений

t_sndudata (), t_rcvudata ()

Сигнализация об ошибке, относящейся к предыдущему сообщению,
может быть обеспечена с помощью примитива t_rcvudata (). Пере-
менная t_errno, в этом случае, устанавливается в TLOOK. Прими-
тив t_rcvuderr () позволяет определить причину ошибки.

Несколько дополнительных программ
---------------------------------

- Считывание состояния точки доступа транспортной службы

t_getstate ()

Этот вызов возвращает текущее состояние локальной службы
(например: соединение в режиме ожидания, установленное соеди-
нение...).

- Считывание и установка опций точки доступа к транспортной
службе

t_getinfo ()

Этот примитив позволяет получить информацию о параметрах ра-
боты используемых транспортных служб.

t_optmgmt ()

Этот вызов позволяет согласовать, изменить или проконтроли-
ровать значения параметров связи между двумя процессами.


5.3.3. Рабочие характеристики

Управление ошибками
-------------------

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


5.3.4. Применяемые опции

Мультиплексирование
-------------------

Программа poll (), уже рассмотренная в гл. 1, позволяет ор-
ганизовать состояние ожидания по нескольким событиям.

Не-блокирующие вызовы
---------------------

Для использования функций TLI в не-блокирующем режиме можно
использовать:

- t_open () с флагом O_NDELAY;
- fcntl () (флаг O_NDELAY или O_NONBLOCK).

Функционируют функции следующим образом:
- t_listen () завершает работу немедленно при отсутствии
запросов на соединение и посылает ошибку TNODATA;
- t_connect () завершает работу немедленно при невозможности
соединения и посылает TNODATA;
- t_rcv () или t_rcvudata () возвращают -1 при отсутствии
данных для считывания и посылают TNODATA;
- t_snd () возвращает -1 при невозможности записи (перепол-
нение буфера) с ошибкой TFLOW.

Асинхронный ввод-вывод
----------------------

Опцию асинхронной работы можно установить принудительно
(флаг I_SETSIG в ioctl ()). При этом сигнал SIGPOLL посылается
процессу при каждом изменении состояния, что вызывает активи-
зацию соответствующей обработки.

Экспресс-данные
---------------

В режиме соединения можно послать данные вне потока. Для
этого, передающая машина указывает соответствующее значение в
качестве параметра функции t_snd () (флаг T_EXPEDITED). У по-
лучателя тот же флаг будет установлен при возврате вызова
t_rcv ().

5.3.5. Примеры

- Эхо-функция цепочки символов

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

ПРОГРАММА 40

------------------------------------------------------------¬
¦/* функция "эхо" *****************************/ ¦
¦/* полный программный код содержится в параграфе 3.1. */ ¦
¦ ¦
¦/* включаемый файл tli.h *******************/ ¦
¦#include "commun.h" ¦
¦#include <tiuser.h> ¦
¦#include <stropts.h> ¦
¦#include <sys/socket.h> ¦
¦#include <netinet/in.h> ¦
¦#define SERV_PORT 6006 /* номер порта ¦
¦ ¦
¦/* файл client.c ***************************/ ¦
¦#include "tli.h" ¦
¦ ¦
¦clientipc() ¦
¦{ ¦
¦ int fd; /* дескриптор TLI ¦
¦ struct t_call *callptr; /* структура адреса TLI¦
¦ struct sockaddr_in serv_addr; /* адрес сервера ¦
¦ ¦
¦/* создание точки входа в транспортный уровень ¦
¦ fd = t_open("/dev/tcp", O_RDWR, NULL); ¦
¦/* назначение какого-нибудь адреса ¦
¦ t_bind(fd, NULL, NULL); ¦
¦/* установка параметров адреса сервера ¦
¦ bzero(&serv_addr, sizeof(serv_addr)); ¦
¦ serv_addr.sin_family = AF_INET; ¦
¦ bcopy((char *) hp->h_addr, (char *) &serv_addr.sin_addr, ¦
¦ hp->h_length); ¦
¦ serv_addr.sin_port = htons(SERV_PORT); ¦
¦/* присваивание структуры t_call, используемой функцией ¦
¦ t_connect */ ¦
¦ callptr = (struct t_call *) t_alloc(fd, T_CALL, T_ADDR); ¦
¦/* присоединение к серверу ¦
¦ callptr->addr.len = sizeof(serv_addr); ¦
¦ callptr->addr.maxlen = sizeof(serv_addr); ¦
¦ callptr->addr.buf = (char *) &serv_addr; ¦
¦ callptr->opt.len = 0; ¦
¦ callptr->udata.len = 0; ¦
¦ t_connect(fd, callptr, NULL); ¦
¦/* обращение к "эхо"-функции ¦
¦ client(fd); ¦
¦/* закрытие точки входа в транспортный уровень ¦
¦ t_close(fd); ¦
¦} ¦
¦ ¦
¦/* функция приема-передачи ¦
¦client(fd) ¦
¦int fd; /* дескриптор TLI ¦
¦{ ¦
¦/* программный код, идентичный программному коду для ¦
¦сокетов (см. параграф 4.3.5.) с заменой reads() и writes() ¦
¦на readt() и writet(), определенные в файле tli.c */ ¦
¦ ...................... ¦
¦} ¦
¦ ¦
¦/* файл serveur.c ¦
¦#include "tli.h" ¦
¦ ¦
¦serveuripc() ¦
¦{ ¦
¦ int fd; /* дескриптор TLI ¦
¦ int nfd; /* дескриптор TLI ¦
¦ struct t_call *callptr; /* структура TLI ¦
¦ struct t_bind *reg; /* структура TLI ¦
¦ struct sockaddr_in serv_addr; /* адрес сервера ¦
¦ ¦
¦/* создание точки входа в транспортный уровень */ ¦
¦ fd = t_open("/dev/tcp", O_RDWR, NULL); ¦
¦/* присваивание значения адресу и связывание ¦
¦ bzero(&serv_addr, sizeof(serv_addr)); ¦
¦ serv_addr.sin_family = AF_INET; ¦
¦ serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); ¦
¦ serv_addr.sin_port = htons(SERV_PORT); ¦
¦ req = (struct t_bind *) t_alloc(fd, T_BIND, T_ALL); ¦
¦ req->addr.len = sizeof(serv_addr); ¦
¦ req->addr.maxlen = sizeof(serv_addr); ¦
¦ req->addr.buf = (char *) &serv_addr; ¦
¦ req->qlen = 1; ¦
¦ t_bind(fd, req, NULL); ¦
¦/* распределение структуры t_call, используемой функциями ¦
¦ t_listen() и t_accept() */ ¦
¦ callptr = (struct t_call *) t_alloc(fd, T_CALL, T_ADDR); ¦
¦/* бесконечный цикл ожидания привходящих связей ¦
¦ for (;;) { ¦
¦ t_listen(fd, callptr); ¦
¦/* получение новой точки входа в транспортный уровень, ¦
¦ используемой для обмена */ ¦
¦ nfd = accept_call(fd, callptr); ¦
¦/* обращение к службе "эхо" ¦
¦ serveur(nfd); ¦
¦/* закрытие используемой точки входа ¦
¦ t_close(nfd); ¦
¦ } ¦
¦} ¦
¦ ¦
¦/* прием запроса на связь. Возвращает новую точку входа ¦
¦ или - 1 в случае ошибки */ ¦
¦int accept_call(lfd, callptr) ¦
¦int lfd ; /* дескриптор TLI ¦
¦struct t_call *callptr; /* структура TLI ¦
¦{ ¦
¦ int nfd ; /* дескриптор TLI ¦
¦/* открытие новой точки входа в транспортный уровень ¦
¦ nfd = t_open("/dev/tcp", O_RDWR, NULL); ¦
¦/* ей присваивается какой-нибудь адрес ¦
¦ t_bind(nfd, NULL, NULL); ¦
¦/* связывание старой и новой точек входа */ ¦
¦/* в нашем примере (итеративный сервер) можно было бы ¦
¦ сохранить текущий дескриптор lfd для обмена данными с ¦
¦ клиентом */ ¦
¦ if (t_accept(lfd, nfd, callptr) < 0) { ¦
¦ if (t_errno == TLOOK) { ¦
¦/* если ошибка : связь разорвана ? */ ¦
¦ t_rcvdis(lfd, NULL); ¦
¦/* тогда закрываем точку входа */ ¦
¦ t_close(nfd); ¦
¦ return(-1); ¦
¦ } ¦
¦ err_sys(" t_accept echoue"); ¦
¦ } ¦
¦ return(nfd); ¦
¦} ¦
¦ ¦
¦/* функция приема-передачи ¦
¦serveur(nfd) ¦
¦int nfd; /* дескриптор TLI */ ¦
¦{ ¦
¦/* обработка, идентичная обработке для сокетов с заменой ¦
¦ reads() и writes() на readt() и writet() */ ¦
¦ ....................... ¦
¦/* результатом операции может быть отрицательное значение, ¦
¦ если клиент разорвал связь */ ¦
¦ if (rval < 0) { ¦
¦/* выход, если получено T_DISCONNECT */ ¦
¦ if (t_look(nfd) == T_DISCONNECT) return; ¦
¦ err_sys ("pb lecture TLI "); ¦
¦ } ¦
¦} ¦
¦ ¦
¦/* файл tli.c *****************************/ ¦
¦/* содержит процедуры, используемые, как клиентом, так и ¦
¦ сервером : * writet() : запись в точку входа блоками ¦
¦ размером по 4К (как показывает тест (на Sun OS 4.1.) ¦
¦ нельзя послать блок данных, размер которого превосходит ¦
¦ это значение). ¦
¦ * readt() : считывает информацию из точек входа до тех ¦
¦ пор, пока не считает требуемое количество символов. ¦
¦ * err_sys() : выводит на экран сообщение об ошибке и ¦
¦ кончает работу. */ ¦
¦#include <stdio.h> ¦
¦#include <tiuser.h> ¦
¦/* запись буфера, состоящего из пос байт в точку входа */ ¦
¦int writet(fd, pbuf, noc) ¦
¦register int fd; /* дескриптор TLI */ ¦
¦register char *pbuf; /* буфер */ ¦
¦register int noc; /* число записываемых байт */ ¦
¦{ ¦
¦/*то же, что и writes(), но write() заменяется на t_snd()*/¦
¦ ........... ¦
¦} ¦
¦ ¦
¦/* считывание буфера, состоящего из пос байт из точки входа¦
¦int readt(fd, pbuf, noc) ¦
¦register int fd; /* дескриптор TLI */ ¦
¦register char *pbuf; /* буфер */ ¦
¦register int noc; /* число считываемых байт */ ¦
¦{ ¦
¦/* то же, что и reads() (Глава 4), но read() заменяется ¦
¦ на t_snd() */ ¦
¦ ........... ¦
¦} ¦
¦ ¦
¦/* процедура обработки ошибок */ ¦
¦err_sys(mes) ¦
¦char *mes; /* сообщение пользователя */ ¦
¦{ ¦
¦ t_error(mes); ¦
¦ exit(1); ¦
¦} ¦
L------------------------------------------------------------


- Модификация предыдущего примера с использованием примити-
вов read () и write () на сервере.
На сервере создается модуль Stream tirdwr, позволяющий ис-
пользовать вызовы read (), write () и close (). Эта операция
осуществляется в процедуре accept_call (), вследствие этого
измененной.

ПРОГРАММА 41

/* модификация процедуры accept_call(), обеспечивающая
возможность использования read(),write() и close() */

/* прием запроса на связь: создание дескриптора файла для этой
связи. Возвращает или новый дескриптор или, в случае сбоя, -1. */
accept_call(lfd, callptr)
int lfd; /* дескриптор TLI */
struct t_call *callptr; /* структура TLI */
{

int nfd; /* дескриптор TLI */
/* начало такое же, как и в предыдущем примере */
.............................
/* выполняется "push" для модуля "tirwdr" для нового потока,
чтобы обеспечить использование read() и write(). Надо
сначала выполнить "pop" для модуля "timod" /*
ioct1(nfd, I_POP, (char *)0);
ioct1(nfd, I_PUSH, "tirdwr");
return(nfd); /* возвращает новый дескриптор */
}


5.4. STREAMS

Примечание: далее по тексту мы исходим из следующего:
- "STREAMS" обозначает совокупность механизмов;
- "Stream" обозначает частный случай операции, реализованной
с помощью STREAMS.

5.4.1. Функциональные особенности и механизмы реализации

STREAMS представляют собой совокупность средств разработки
коммуникационных услуг системы UNIX. Данные средства взаимо-
действия можно реализовать как между процессом и пилотным пе-
риферийным устройством (например, администратор терминалов в
символьном режиме), так и между программами, исполняемыми на
удаленных машинах и требующих протоколов типа TCP/IP.
Механизмы STREAMS состоят из совокупности ресурсов ядра
UNIX, внутренних активных элементов ядра (модулей) и особых
системных вызовов, используемых программами.
Механизмы STREAMS гибки и построены на модульном принципе:
на основе минимального драйвера, принадлежащего ядру, можно
ввести модули, позволяющие особые виды обработки. Таким обра-
зом, можно изменить программную составляющую, даже если она
является частью ядра UNIX, не пересматривая всей архитектуры
системы.
Stream представляет собой дуплексный канал коммуникации,
позволяющий вести обмен данными между пользовательским процес-
сом и драйвером в ядре: драйвером, связанным с физическим ин-
терфейсом или псевдо-драйвером, связанным с программным ресур-
сом ядра.
Stream представляет собой, таким образом, путь передачи
данных между программой и ресурсом (рис. 5.6.).
Минимальный Stream состоит из головного модуля и конечного
модуля (как правило, драйвера).

+
--------------------------¬
¦Пользовательский процесс ¦
L-T-----------------^------
¦ ¦
------------+-----------------+-------------¬
¦ ---------v-----------------+----------¬ ¦
¦ ¦ Головной модуль ¦ ¦
¦ L--------T-----------------^----------- ¦
¦ ¦ ¦ ¦
¦ ---------v-----------------+----------¬ ¦
¦ ¦ Модуль 1 ¦ ¦
¦ L--------T-----------------^----------- ¦
¦ ¦ ¦ ¦
¦ ---------v-----------------+----------¬ ¦
¦ ¦ Модуль 2 ¦ ¦
¦ L--------T-----------------^----------- ¦
¦ ¦ ¦ ¦
¦ ---------v-----------------+----------¬ ¦
¦ ¦ Конечный модуль (драйвер) ¦ ¦
¦ L--------T-----------------^----------- ¦
L-----------+-----------------+--------------
---v-----------------+-----¬
¦ Ресурс ¦
L---------------------------

Рис. 5.6. Пользовательский процесс и Stream

Головной модуль обеспечивает интерфейс между Stream (под уп-
равлением ядра) и пользовательским процессом. Его роль состоит
в обработке системных вызовов, относящихся к STREAMS , и в пе-
редаче данных между областью адресации пользовательского про-
цесса и пространством ядра.
Каждый промежуточный модуль обеспечивает особую обработку.
Он может динамически вводиться и выводиться из Stream.
Конечный модуль управляет ресурсом; он задает, как правило,
работу периферийных устройств.
Каждый модуль обменивается структурированными сообщениями со
смежными модулями. Сообщения передаются через очереди (одна
для считывания и одна для записи). Они обладают типом, позво-
ляющим интерпретировать их содержание. STREAMS позволяют конт-
ролировать поток сообщений.

5.4.2. Использование

Системные вызовы, позволяющие программировать средства
STREAMS, аналогичны обращениям, управляющим файлами.

- Открытие Stream

open ()

Открывается специальный файл. При возврате получают дескрип-
тор, используемый для операций со Stream, выделенным ядром.

- Считывание и запись

read (), write (), putmsg (), getmsg ()

Два последних вызова характерны для STREAMS и позволяют уп-
равлять контрольной информацией одновременно с данными. Кроме
того, функции putmsg () и getmsg () открыто манипулируют дан-
ными, структурированными в сообщения, в отличие от read () и
write (), работающих только с байтами.

- Контрольные операции

ioctl ()

Этот вызов позволяет, в частности, ввести модуль в Stream
(опция I_PUSH) или вывести модуль из Stream (опция I_POP).
В случае транспортного соединения, построенного на Stream
интерфейсом TLI, можно ввести модуль tirdwr, позволяющий ис-
пользовать функции read () и write () для считывания или запи-
си в точке доступа транспортной службы (см. пример раздела
5.3.5).
Этот примитив позволяет также определить модули в Stream
(опция I_LOOK).

ПРОГРАММА 42

/* получение списка модулей Stream */

#include "tli.h"
main()
{
char name[] ="/dev/tcp";
int tfd;
char filename[FMNAMESZ + 1]; /* FMNAMESZ определяется в
<sys/conf.h> */
struct t_info info;
/* создание точки входа в транспортный уровень */
tfd = t_open(name, O_RDWR, &info);
/* поиск и вывод на экран списка модулей Stream */
for (;;) {
ioct1(tfd, I_LOOK, filename);
printf(" module = %s ", filename);
ioct1(tfd, I_POP, (char *) 0);
}
fflush(stdout);
}

/* полученный результат */
module = timod


- Одновременное ожидание на нескольких дескрипторах вво-
да-вывода

Системный вызов poll () позволяет организовать состояние
ожидания событий на нескольких дескрипторах Streams.
Этот примитив блокирует программу до получения одного из
ожидаемых событий или до истечения временной задержки.
Пример использования показан в гл. 1.

- Мультиплексорный модуль

Один из модулей может принимать данные из нескольких источ-
ников (мультиплексорный модуль), благодаря функции ioctl () c
опцией I_LINK.
Таким образом, можно, например, обладать несколькими драйве-
рами - надстройками над уровнем IP (рис. 5.7.).

----------------------¬
¦ Транспортный ¦
¦ протокол ¦
L-----------^----------
¦
-------------------------v----------------------¬
¦ Протокол Internet ¦
¦ (IP) ¦
¦ мультиплексор ¦
L------^------------------^----------------^-----
¦ ¦ ¦
¦ ¦ ¦
-------v-------¬ ------v------¬ ------v------¬
¦ Драйвер ¦ ¦ Драйвер ¦ ¦ Драйвер ¦
¦ TOKEN RING ¦ ¦ CSMA/CD ¦ ¦ X25 ¦
L--------------- L------------- L-------------
Рис. 5.7. Мультиплексорный модуль IP.

ПРОГРАММА 43
------------------------------------------------------------¬
¦/* мультиплексная работа модуля IP с тремя драйверами : ¦
¦ Token ring, Ethernet и Х25 **********************/ ¦
¦ ¦
¦/* *******************/ ¦
¦#include <stdio.h> ¦
¦#include <fcntl.h> ¦
¦#include <sys/conf.h> ¦
¦#include <stropts.h> ¦
¦main() ¦
¦{ ¦
¦ int fd_ip, fd_ether, fd_token, fd_x25, fd_tp; ¦
¦/* создание точек входа в транспортный уровень */ ¦
¦ fd_ip = open("/dev/ip", O_RDWR); ¦
¦ fd_ether = open("/dev/ether", O_RDWR); ¦
¦ fd_token = open("/dev/token", O_RDWR); ¦
¦ fd_x25 = open("/dev/x25", O_RDWR); ¦
¦/* связывание IP с драйверами */ ¦
¦ ioct1(fd_ip, I_LINK, fd_ether); ¦
¦ ioct1(fd_ip, I_LINK, fd_token); ¦
¦ ioct1(fd_ip, I_LINK, fd_x25); ¦
¦/* связывание транспортного уровня с IP */ ¦
¦ fd_tp = open("/dev/tip", O_RDWR); ¦
¦ ioct1(fd_tp, I_LINK, fd_ip); ¦
¦/* затем надо с помощью fork создать демона */ ¦
¦ switch (fork()) { ¦
¦ case 0 : ¦
¦ break ; /* порожденный процесс */ ¦
¦ case -1 : ¦
¦ err_sys("fork echoue"); ¦
¦ default: ¦
¦ exit(0) ; /* отец оставляет сына */ ¦
¦ } ¦
¦/* надо закрыть дескрипторы */ ¦
¦ close(fd_ip); ¦
¦ close(fd_ether); ¦
¦ close(fd_token); ¦
¦ close(fd_x25); ¦
¦/* демон ждет */ ¦
¦pause(); ¦
¦} ¦
L------------------------------------------------------------

Существует определенное число системных вызовов и макросов
STREAMS, позволяющих конструировать модули Stream (putq,
putbq, getq...).


5.5. СОПОСТАВЛЕНИЕ TLI И СОКЕТОВ

Таблица 5.4. устанавливает соответствие между программами
TLI и примитивами сокетов (по [AAT&T 90]).
Вызовы TLI покрывают все вызовы сокетов. Таким образом, мож-
но эмулировать сокет-интерфейс с библиотекой TLI.
Необходимо, однако, отметить, что некоторые примитивы, ука-
занные в таблице вместе, функционируют по-разному: например,
listen () и t_listen, accept () и t_accept ()...


Таблица 5.4. Примитивы TLI и сокетов.
-------------------T--------------T-------------------------------¬
¦ TLI ¦ Сокеты ¦ Описание ¦
+------------------+--------------+-------------------------------+
¦ t_open ¦ socket ¦ Возвращает дескриптор ¦
+------------------+--------------+-------------------------------+
¦ t_bind ¦ bind ¦ Связывает имя с дескриптором ¦
+------------------+--------------+-------------------------------+
¦ t_optmgmt ¦ setsocket ¦ Выставляет опции транспорта ¦
+------------------+--------------+-------------------------------+
¦ t_unbind ¦ ¦ Уничтожает точку доступа ¦
¦ ¦ ¦ транспорта ¦
+------------------+--------------+--------------------------------
¦ t_close ¦ close ¦ Уничтожает ресурсы, связанные с
¦ ¦ ¦ точкой доступа транспорта ¦
+------------------+--------------+-------------------------------+
¦ t_getinfo ¦ getsockopt ¦ Возвращает информацию о ¦
¦ ¦ ¦ транспорте ¦
+------------------+--------------+-------------------------------+
¦ t_getstate ¦ioctl, fcntl, ¦ Возвращает состояние точки ¦
¦ ¦ stat ¦ доступа транспорта ¦
+------------------+--------------+-------------------------------+
¦ t_alloc ¦ ¦ Выделяет или освобождает ¦
¦ t_free ¦ ¦ память ¦
+------------------+--------------+-------------------------------+
¦ t_look ¦ oictl ¦ Читает событие, связанное с ¦
¦ ¦ ¦ точкой доступа транспорта ¦
+------------------+--------------+-------------------------------+
¦ t_error ¦ perror ¦ Выдает сообщение об ошибке ¦
¦ ¦ ¦ в незакодированном виде ¦
+------------------+--------------+-------------------------------+
¦ t_connect ¦ connect ¦ Устанавливает соединение с ¦
¦ ¦ ¦ удаленным устройством ¦
+------------------+--------------+-------------------------------+
¦ t_listen ¦ listen ¦ Установка в состояние ожидания¦
¦ ¦ ¦ запросов на соединение ¦
+------------------+--------------+-------------------------------+
¦ t_accept ¦ accept ¦ Согласие на входящее соединениe
+------------------+--------------+-------------------------------+
¦ t_snd ¦ send ¦ Запись данных в режиме ¦
¦ putmsg ¦ sendto ¦ соединения ¦
¦ ¦ sendmsg ¦ ¦
+------------------+--------------+-------------------------------+
¦ t_rcv ¦ recv ¦ Чтение данных в режиме ¦
¦ getmsg ¦recvfrom ¦ соединения ¦
¦ ¦ recvmsg ¦ ¦
+------------------+--------------+-------------------------------+
¦ t_snudata ¦ sendto ¦ Запись данных в режиме ¦
¦ ¦ sendmsg ¦ отсутствия соединения ¦
+------------------+--------------+-------------------------------+
¦ t_rcvudata ¦recvfrom ¦ Чтение данных в режиме ¦
¦ ¦ recvmsg ¦ отсутствия соединения ¦
+------------------+--------------+-------------------------------+
¦ read ¦ read ¦ Чтение и запись данных ¦
¦ write ¦ write ¦ в режиме соединения. ¦
¦ ¦ ¦ Для TLI необходимо ввести модуль
¦ ¦ ¦ tirdwr в Stream ¦
+------------------+--------------+-------------------------------+
¦ t_snddis ¦ ¦ Отсоединяет точку доступа ¦
¦ t_rcvdis ¦ ¦ транспорта ¦
+------------------+--------------+-------------------------------+
¦ t_sndrel ¦ shutdown ¦ Освобождает точку доступа ¦
¦ t_rcvrel ¦ ¦ транспорта ¦
L------------------+--------------+--------------------------------


В таблицах 5.5. и 5.6. показан способ использования сокетов
и TLI в режиме установления соединения. Эти таблицы позволяют
установить аналогию между двумя программными средствами.

Таблица 5.5.- Принцип использования сокетов в режиме
соединения.

.+
------------------------------T------------------------------¬
¦ Клиент ¦ Сервер ¦
+-----------------------------+------------------------------+
¦ fd1=socket (...); ¦ fd2=socket (...); ¦
¦ ¦ bind (fd2, adr_serv); ¦
¦ ¦ listen (fd2, ...); ¦
¦ connect (fd1, adr_serv); ¦ fd3=accept (fd2, ...); ¦
¦ ¦ ¦
¦ ¦ ¦
L-----------------------------+-------------------------------
Таблица 5.6.- Принцип использования TLI в режиме
соединения.

----------------------------T--------------------------------¬
¦ Клиент ¦ Сервер ¦
+---------------------------+--------------------------------+
¦ fd1=t_open (...); ¦ fd2=t_open (...); ¦
¦ t_bind (fd1, NULL, NULL); ¦ t_bind (fd2, adr_serv, O); ¦
¦ t_connect (fd1, adr_serv);¦ t_listen (fd2, ...); ¦
¦ ¦ fd3=t_open (...); ¦
¦ ¦ t_bind (fd3, NULL, NULL); ¦
¦ ¦ t_accept (fd2, fd3, ...); ¦
L---------------------------+---------------------------------

Следует отметить, что имеется большое сходство в использова-
нии сокетов и библиотеки TLI, причем сложность TLI несколько
больше, вследствие ее большей ориентированности на требования
стандартов транспортной службы ISO.
В System V Release 4 услуги, оказываемые сокетами, реализу-
ются в форме библиотеки-надстройки над STREAMS. Системные вы-
зовы сокетов системы BSD становятся здесь библиотечными прог-
раммами. Отсюда некоторые семантические различия, описанные в
документации AT&T, которые, при небрежной обработке, рискуют
вызвать сбои при переносе программы с одной системы на другую.


5.6. ИТОГИ

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

Ввод и вывод модулей делают механизм STREAMS хорошо приспо-
собленным к разработке средств коммуникации и ,особенно, сете-
вых протоколов. Один протокол можно легко заменить на другой,
в той степени, в какой эти протоколы имеют равнозначный интер-
фейс и семантику услуг. Один и тот же модуль может быть пов-
торно использован при конструировании различных служб.
Библиотека TLI более полная, чем сокет-интерфейс, но и нес-
колько более сложная в реализации. Она интересна тем, что
позволяет писать программы, независимые от используемой транс-
портной службы. Кроме того, она освобождает программиста от
системного интерфейса STREAMS и от вызовов, контролирующих
протекание процесса (в частности, сигналов). Ее использование
будет расширяться в зависимости от желания разработчиков соот-
ветствовать стандарту. TLI использовалась группой X/Open под
именем XTI (X/Open Transport Interface).

Обновлено: 12.03.2015