ГЛАВА 5 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.).

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

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

Рис. 5.2. Взаимоотношения поставщика и пользователя транспортных услуг.

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

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

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

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

Рис. 5.3. Реализация TLI

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

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

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

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

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


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

Рис. 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; /*указатель адреса*/ };

Рис. 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.

sructype Тип структуры

T_BIND

struct t_bind

T_CALL

struct t_call

T_DIS

struct t_diskon

T_INFO

struct t_info

T_OPTMGMT

struct t_optmgt

T_UNITDATA struct t_unitdata

T_UDERROR

struct t_udder

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

fields Поля структур netbuf

резервированные и инициализированные

T_ALL

все

T_ADDR

addr

T_OPT

opt

T_UDATA

udata

- Создание точки доступа транспортной службы
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_CONNECT

подтверждение соединения

T_DATA

обычные данные

T_DISCONNECT

разъединение

T_ERROR

сигнализацияфатальной ошибки

T_EXDATA

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

T_LISTEN

сигнализация соединения

T_OPRDREL

сигнализация согласованного разъединения

T_UDERR

сигнализация ошибки в дейтаграмме

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


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

ПРОГРАММА 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);
}
- Модификация  предыдущего примера с использованием примити-
вов 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 состоит из головного модуля и конечного модуля (как правило, драйвера).

Рис. 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\n", filename);
                                ioct1(tfd,  I_POP, (char *) 0);

                }
                fflush(stdout);
}
/* полученный результат  */
                module = timod

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

ПРОГРАММА 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();
}

Существует определенное число системных вызовов и макросов 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 и сокетов.

TLI Сокеты Описание
t_open socket Возвращает дескриптор
t_bind bind Связывает имя с десктиптором
t_optmgmt setsocket Выставляет опции транспорта
t_unbind Уничтожает точку доступа транспорта
t_close close Уничтожает ресурсы, связанные с точкой доступа транспорта
t_getinfo getsockpoint Возвращает информацию о транспорте
t_getstate ioctl,fcntl,stat Возвращает сосотояние точки доступа траспорта
t_alloc

t_free

Выделяет или освобождает память
t_look oictl Читает событие, связанное с точкой доступа транспорта
t_error perror Выдает сообщение об ошибке в незалодированном виде
t_connect connect Устпнвливает соединение с удаленным устройством
t_listen listen Установка в состояние ожидания запросов на соединение
t_accept accept Согласие на на входящее соединение
t_snd

putmsg

send

sendto

sendmsg

Запись данных в режиме соединения
t_rcv

getmsg

recv

recvfrom

recvmsg

Чтение данных в режиме соединения
t_snudata sendto

sendmsg

Запись данных в режиме отсутствия соединения
t_rcvudata recvfrom

recvmsg

Чтение данных в режиме отсутствия соединения
read

write

read

write

Чтение и запись данных в режиме соединения.Для TLI необходимо

ввести модуль tirdwr в STREAM

t_snddis

t_rcvdis

Отсоедтняет точку доступа транспорта
t_sndrel

t_rcvrel

shutdown Освобождает точку доступа транспорта

Следует отметить, что имеется большое сходство в использовании сокетов и библиотеки 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).