编程接口指南

第 8 章 使用 XTI 和 TLI 编程

本章介绍传输层接口 (Transport Layer Interface, TLI) 和 X/Open 传输接口 (X/Open Transport Interface, XTI)。诸如异步执行模式的高级主题将在高级 XTI/TLI 主题中介绍。

XTI 接口的附加功能中介绍 XTI 的某些最新附加功能,如分散/集中数据传送。

OSI(开放系统互联)模型的传输层(第 4 层)是模型的最底层,为应用程序和更高的层提供端对端服务。该层会针对用户隐藏底层网络的拓扑和特性。该传输层还定义一组通用于许多当前协议套件(包括 OSI 协议、传输控制协议和 TCP/IP Internet 协议套件、Xerox 网络系统 (Xerox Network Systems, XNS) 以及系统网络体系结构 (Systems Network Architecture, SNA))的服务。

TLI 模型是根据行业标准传输服务定义 (ISO 8072) 建立的。它还可用于访问 TCP 和 UDP。XTI 和 TLI 这组接口构成网络编程接口。XTI 是从 SunOS 4 平台上可用的旧 TLI 接口演变而来的。虽然 XTI 代表这组接口的未来方向,但是 Solaris 操作系统仍同时支持这两种接口。Solaris 软件使用 STREAMS I/O 机制将 XTI 和 TLI 实现为用户库。

什么是 XTI 和 TLI?


注 –

本章中介绍的接口具有多线程安全性。这意味着可以在多线程应用程序中随意使用包含 XTI/TLI 接口调用的应用程序。由于这些接口调用不可重复执行,因此它们不提供线性可伸缩性。



注意 – 注意 –

在异步环境中,XTI/TLI 接口行为尚未有明确的规定。请不要从信号处理程序例程使用这些接口。


TLI 是随 AT&T System V Release 3 在 1986 年引入的。TLI 当时提供一个传输层接口 API。ISO 传输服务定义提供了 TLI 所基于的模型。现在,TLI 提供了 OSI 传输层和会话层之间的 API。TLI 接口在 UNIX 的 AT&T System V Release 4 版本中得到了进一步发展,并在 SunOS 5.6 操作系统接口中可用。

XTI 接口由 TLI 接口演变而来,代表该系列接口的未来方向。使用 XTI 与使用 TLI 接口的应用程序相兼容,因此无需立即将TLI 应用程序移植到XTI。新应用程序可以使用 XTI 接口,而且可以在必要时将较旧的应用程序移植到 XTI。

TLI 实现为应用程序链接到的库 (libnsl) 中的一组接口调用。XTI 应用程序是使用 c89 前端编译的,并且必须与 xnet 库 (libxnet) 链接。有关使用 XTI 进行编译的其他信息,请参见 standards(5) 手册页。


注 –

使用 XTI 接口的应用程序使用 xti.h 头文件,而使用 TLI 接口的应用程序包含 tiuser.h 头文件。


与第 4 章中介绍的某些其他接口和机制一起使用时,XTI/TLI 代码可以独立于当前的传输提供器。SunOS 5 产品将某些传输提供器(例如,TCP)作为基本操作系统的一部分。传输提供器执行服务,而传输用户请求服务。传输用户向传输提供器发出服务请求,例如,通过 TCP 和 UDP 连接传送数据的请求。

通过利用以下两个组件,XTI/TLI 还可以用于独立于传输的编程:

XTI/TLI 读/写接口

用户可能要对现有程序(如 /usr/bin/cat)使用 exec(2) 来建立传输连接,以便处理通过该连接收到的数据。现有程序使用 read(2)write(2)。XTI/TLI 并不直接支持传输提供器的读/写接口,但是有一个接口可用。利用该接口,可以在数据传送阶段通过传输连接发出 read(2)write(2) 调用。本节介绍 XTI/TLI 连接模式服务的读/写接口。该接口不适用于无连接模式服务。


示例 8–1 读/写接口

#include <stropts.h>

   .

   ./*

     Same local management and connection establishment steps.

     */

   .

   if (ioctl(fd, I_PUSH, "tirdwr") == -1) {

      perror(“I_PUSH of tirdwr failed”);

      exit(5);

 	}

   close(0);

   dup(fd);

   execl(“/usr/bin/cat”, “/usr/bin/cat”, (char *) 0);

   perror(“exec of /usr/bin/cat failed”);

   exit(6);

}

客户机通过将 tirdwr 推送到与传输端点关联的流来调用读/写接口。有关 I_PUSH 的说明,请参见 streamio(7I) 手册页。tirdwr 模块将位于传输提供器之上的 XTI/TLI 转换为纯读/写接口。使用该模块后,客户机将调用 close(2)dup(2) 来建立传输端点作为其标准输入文件,并使用 /usr/bin/cat 来处理输入。

tirdwr 推送到传输提供器会强制 XTI/TLI 使用 read(2)write(2) 语义。使用 readwrite 语义时,XTI/TLI 不保留消息边界。请从传输提供器弹出 tirdwr 以恢复 XTI/TLI 语义(有关 I_POP 的说明,请参见 streamio(7I) 手册页)。


注意 – 注意 –

仅当传输端点位于数据传送阶段时,才将 tirdwr 模块推送到流。推送该模块之后,用户不能调用任何 XTI/TLI 例程。如果用户调用 XTI/TLI 例程,则 tirdwr 会在流上生成致命的协议错误 EPROTO,表明该例程不可用。如果随后将 tirdwr 模块弹出该流,则该传输连接将异常中止。有关 I_POP 的说明,请参见 streamio(7I) 手册页。


写入数据

使用 write(2) 通过传输连接发送数据之后,tirdwr 将数据传递到传输提供器。如果发送的数据包长度为零(该机制允许),则 tirdwr 会放弃该消息。如果传输连接异常中止,则会在流上产生挂起状态,后续 write(2) 调用将失败,并且 errno 被设置为 ENXIO。例如,远程用户使用 t_snddis(3NSL) 异常中止连接时可能出现此问题。挂起之后,仍可以检索任何可用数据。

读取数据

使用 read(2) 接收到达传输连接的数据。tirdwr 传递来自传输提供器的数据。tirdwr 模块可处理从提供器传递至用户的任何其他事件或请求,如下所示:

关闭连接

利用流上的 tirdwr,在连接有效期间内,可以通过传输连接发送和接收数据。用户可通过关闭与传输端点关联的文件描述符来终止连接,也可通过将 tirdwr 模块弹出流来终止连接。在上述任一情况下,tirdwr 都会执行以下操作:

tirdwr 推送到流之后,进程便不能启动顺序释放。如果位于传输连接另一端的用户启动了顺序释放,tirdwr 将处理该释放。如果该部分中的客户机与服务器程序进行通信,则该服务器会使用顺序释放请求终止数据的传送。然后,该服务器将等待客户机的相应请求。此时,该客户机将退出并关闭传输端点。关闭文件描述符之后,tirdwr 便会启动来自连接客户端的顺序释放请求。该释放会生成阻塞服务器的请求。

某些协议(如 TCP)要求此顺序释放以确保数据完整地传送。

高级 XTI/TLI 主题

本节介绍其他 XTI/TLI 概念:

异步执行模式

许多 XTI/TLI 库例程会阻塞以等待传入事件。但是,无论出于何种原因,都不应阻塞某些时间关键应用程序。应用程序可以在等待某些异步 XTI/TLI 事件时进行本地处理。

应用程序可以通过组合异步功能和 XTI/TLI 库例程的非阻塞模式来访问 XTI/TLI 事件的异步处理。有关使用 poll(2) 系统调用和 I_SETSIG ioctl(2) 命令来异步处理事件的信息,请参见《ONC+ Developer’s Guide》

可以在特定的非阻塞模式中运行每个由于某个事件而阻塞的 XTI/TLI 例程。例如,t_listen(3NSL) 通常会由于连接请求而阻塞。服务器通过在非阻塞(或异步)模式中调用 t_listen(3NSL),可以定期针对已排队的连接请求轮询传输端点。可通过在文件描述符中设置 O_NDELAYO_NONBLOCK 来启用异步模式。在调用 XTI/TLI 例程之前,借助 t_open(3NSL) 或者通过调用 fcntl(2) 将这些模式设置为标志。使用 fcntl(2) 可随时启用或禁用该模式。本章中的所有程序示例都使用缺省的同步处理模式。

使用 O_NDELAYO_NONBLOCK 会以不同方式影响每个 XTI/TLI 例程。需要针对特定例程确定 O_NDELAYO_NONBLOCK 的确切语义。

高级 XTI/TLI 编程示例

示例 8–2 说明了两个重要概念。第一个是服务器管理多个未完成连接请求的功能。第二个是以事件驱动方式使用 XTI/TLI 和系统调用接口。

使用 XTI/TLI,一台服务器可以管理多个未完成的连接请求。接收数个同时连接请求的一个原因是设置客户机的优先级。一台服务器可以接收数个连接请求,并基于每台客户机的优先级按顺序接受这些请求。

处理数个未完成连接请求的第二个原因是克服单线程处理的限制。根据传输提供器,服务器正在处理一个连接请求时,其他客户机会将该服务器视为处于忙状态。如果同时处理多个连接请求,则该服务器仅在超过最大数量的客户机同时尝试呼叫该服务器时处于忙状态。

该服务器的示例是事件驱动的:进程针对传入的 XTI/TLI 事件轮询传输端点,并针对接收到的事件执行相应操作。以下示例说明针对传入事件轮询多个传输端点的功能。


示例 8–2 端点建立(可转换为多个连接)

#include <tiuser.h>

#include <fcntl.h>

#include <stdio.h>

#include <poll.h>

#include <stropts.h>

#include <signal.h>



#define NUM_FDS 1

#define MAX_CONN_IND 4

#define SRV_ADDR 1                 /* server's well known address */



int conn_fd;                       /* server connection here */

extern int t_errno;

/* holds connect requests */

struct t_call *calls[NUM_FDS][MAX_CONN_IND];



main()

{

   struct pollfd pollfds[NUM_FDS];

   struct t_bind *bind;

   int i;



   /*

    * Only opening and binding one transport endpoint, but more can

    * be supported

    */

   if ((pollfds[0].fd = t_open(“/dev/tivc”, O_RDWR,

         (struct t_info *) NULL)) == -1) {

      t_error(“t_open failed”);

      exit(1);

   }

   if ((bind = (struct t_bind *) t_alloc(pollfds[0].fd, T_BIND,

         T_ALL)) == (struct t_bind *) NULL) {

      t_error(“t_alloc of t_bind structure failed”);

      exit(2);

   }

   bind->qlen = MAX_CONN_IND;

   bind->addr.len = sizeof(int);

   *(int *) bind->addr.buf = SRV_ADDR;

   if (t_bind(pollfds[0].fd, bind, bind) == -1) {

      t_error(“t_bind failed”);

      exit(3);

   }

   /* Was the correct address bound? */

   if (bind->addr.len != sizeof(int) ||

      *(int *)bind->addr.buf != SRV_ADDR) {

      fprintf(stderr, “t_bind bound wrong address\n”);

      exit(4);

   }

}

t_open(3NSL) 返回的文件描述符存储在针对传入数据控制传输端点轮询的 pollfd 结构中。请参见 poll(2) 手册页。本示例中只建立了一个传输端点。但是,编写示例的其余部分是为了管理多个传输端点。通过对示例 8–2 进行少量更改即可支持数个端点。

该服务器针对 t_bind(3NSL)qlen 设置为大于 1 的值。该值指定服务器应对多个未完成的连接请求进行排队。该服务器会先接受当前的连接请求,然后再接受其他连接请求。本示例最多可以对 MAX_CONN_IND 个连接请求进行排队。如果传输提供器不支持 MAX_CONN_IND 个未完成的连接请求,则它可以对 qlen 的值进行协商,使其更小。

在服务器绑定其地址并且可以处理连接请求之后,其行为如以下示例所示。


示例 8–3 处理连接请求

pollfds[0].events = POLLIN;



while (TRUE) {

	if (poll(pollfds, NUM_FDS, -1) == -1) {

   perror(“poll failed”);

   exit(5);

	}

	for (i = 0; i < NUM_FDS; i++) {

   switch (pollfds[i].revents) {

      default:

         perror(“poll returned error event”);

      exit(6);

      case 0:

         continue;

      case POLLIN:

         do_event(i, pollfds[i].fd);

         service_conn_ind(i, pollfds[i].fd);

   	}

   }

}

pollfd 结构的 events 字段设置为 POLLIN,用以通知服务器任何传入的 XTI/TLI 事件。然后,服务器会进入针对事件轮询传输端点的死循环,并在事件发生时对其进行处理。

poll(2) 调用将针对传入事件进行无限阻塞。返回时,服务器将针对新事件检查每项(一个传输端点为一项)的 revents 值。如果 revents0,则端点未生成任何事件,服务器将继续检查下一个端点。如果 reventsPOLLIN,则端点上有事件。服务器会调用 do_event 来处理该事件。revents 中的任何其他值指示端点上存在错误,并且服务器将退出。由于存在多个端点,因此服务器应关闭该描述符并继续检查。

每次服务器迭代循环时,都会调用 service_conn_ind 来处理任何未完成的连接请求。如果还有其他暂挂的连接请求,service_conn_ind 将保存新的连接请求,并在以后对其做出响应。

服务器会调用以下示例中的 do_event 来处理传入事件。


示例 8–4 事件处理例程

do_event( slot, fd)

int slot;

int fd;

{

   struct t_discon *discon;

   int i;



   switch (t_look(fd)) {

   default:

      fprintf(stderr, "t_look: unexpected event\n");

      exit(7);

   case T_ERROR:

      fprintf(stderr, "t_look returned T_ERROR event\n");

      exit(8);

   case -1:

      t_error("t_look failed");

      exit(9);

   case 0:

      /* since POLLIN returned, this should not happen */

      fprintf(stderr,"t_look returned no event\n");

      exit(10);

   case T_LISTEN:

      /* find free element in calls array */

      for (i = 0; i < MAX_CONN_IND; i++) {

         if (calls[slot][i] == (struct t_call *) NULL)

            break;

      }

      if ((calls[slot][i] = (struct t_call *) t_alloc( fd, T_CALL,

               T_ALL)) == (struct t_call *) NULL) {

         t_error("t_alloc of t_call structure failed");

         exit(11);

      }

      if (t_listen(fd, calls[slot][i] ) == -1) {

         t_error("t_listen failed");

         exit(12);

      }

      break;

   case T_DISCONNECT:

      discon = (struct t_discon *) t_alloc(fd, T_DIS, T_ALL);

      if (discon == (struct t_discon *) NULL) {

         t_error("t_alloc of t_discon structure failed");

         exit(13)

      }

      if(t_rcvdis( fd, discon) == -1) {

         t_error("t_rcvdis failed");

         exit(14);

      }

      /* find call ind in array and delete it */

      for (i = 0; i < MAX_CONN_IND; i++) {

         if (discon->sequence == calls[slot][i]->sequence) {

            t_free(calls[slot][i], T_CALL);

            calls[slot][i] = (struct t_call *) NULL;

         }

      }

      t_free(discon, T_DIS);

      break;

   }

}

示例 8–4 中的参数为一个编号 (slot) 和一个文件描述符 (fd)。slot 为全局数组 calls 的索引,它针对每个传输端点都具有一项。每一项都是用于保存端点的传入连接请求的 t_call 结构的数组。

do_event 模块会调用 t_look(3NSL) 以标识 fd 指定端点上的 XTI/TLI 事件。如果该事件是连接请求(T_LISTEN 事件)或断开请求(T_DISCONNECT 事件),则会处理该事件。否则,服务器会列显错误消息并退出。

对于连接请求,do_event 会扫描未完成连接请求的数组,以查找第一个可用项。将为该项分配 t_call 结构,并由 t_listen(3NSL) 接收连接请求。该数组很大,足以容纳最大数量的未完成连接请求。将延迟连接请求的处理。

断开请求必须对应于先前的连接请求。do_event 模块会分配 t_discon 结构来接收请求。此结构具有以下字段:

struct t_discon {

 	struct netbuf udata;

 	int reason;

 	int sequence;

}

udata 结构包含使用断开请求发送的所有用户数据。reason 的值包含特定于协议的断开原因代码。sequence 的值标识与断开请求匹配的连接请求。

服务器会调用 t_rcvdis(3NSL) 来接收断开请求。将扫描连接请求的数组,以查找序列号与断开请求中 sequence 编号值匹配的连接请求。找到连接请求时,将释放其结构并将该项设置为 NULL

在传输端点上找到事件时,将调用 service_conn_ind 来处理该端点上的所有已排队连接请求,如以下示例所示。


示例 8–5 处理所有连接请求

service_conn_ind(slot, fd)

{

   int i;



	for (i = 0; i < MAX_CONN_IND; i++) {

      if (calls[slot][i] == (struct t_call *) NULL)

         continue;

      if((conn_fd = t_open( “/dev/tivc”, O_RDWR,

            (struct t_info *) NULL)) == -1) {

         t_error("open failed");

         exit(15);

      }

      if (t_bind(conn_fd, (struct t_bind *) NULL,

            (struct t_bind *) NULL) == -1) {

         t_error("t_bind failed");

         exit(16);

      }

      if (t_accept(fd, conn_fd, calls[slot][i]) == -1) {

         if (t_errno == TLOOK) {

            t_close(conn_fd);

            return;

         }

         t_error("t_accept failed");

         exit(167);

      }

      t_free(calls[slot][i], T_CALL);

      calls[slot][i] = (struct t_call *) NULL;

      run_server(fd);

   }

}

对于每个传输端点,将扫描未完成连接请求的数组。对于每个请求,服务器都会打开一个响应传输端点,将地址绑定到该端点,并在该端点接受连接。如果在接受当前请求之前有其他连接请求或断开请求到达,则 t_accept(3NSL) 将失败,并将 t_errno 设置为 TLOOK。如果传输端点上存在任何暂挂连接请求事件或断开请求事件,则无法接受未完成的连接请求。

如果出现该错误,则会关闭响应传输端点,service_conn_ind 会立即返回,并保存当前连接请求以便进行后续处理。该活动将导致进入服务器的主处理循环,并通过下一个 poll(2) 调用发现新事件。这样,用户便可以对多个连接请求进行排队。

最终,会处理所有事件,而 service_conn_ind 可接受每个连接请求。

异步网络

本节介绍针对实时应用程序使用 XTI/TLI 的异步网络通信技术。SunOS 平台使用 STREAMS 异步功能和 XTI/TLI 库例程非阻塞模式的组合来提供对 XTI/TLI 事件的异步网络处理的支持。

网络编程模型

与文件和设备 I/O 类似,网络传送可以通过进程服务请求同步或异步完成。

同步网络的执行与同步文件和设备 I/O 类似。与 write(2) 接口类似,发送请求会在缓冲消息之后返回,但是如果缓冲区不立即可用,则可能暂停调用过程。与 read(2) 接口类似,接收请求仅在数据到达满足请求后才执行调用过程。由于不存在针对传输服务的保证绑定,因此同步网络不适用于必须具有与其他设备相关的实时行为的进程。

异步网络通过非阻塞服务请求提供。此外,可能建立连接时,可能发送数据时,或可能接收数据时,应用程序可以请求异步通知。

异步无连接模式服务

通过在传送数据时针对非阻塞服务配置端点,以及针对异步通知轮询或接收异步通知来执行异步无连接模式网络。如果使用异步通知,则通常在信号处理程序中执行数据的实际接收。

使端点异步

使用 t_open(3NSL) 建立端点,并使用 t_bind(3NSL) 建立其标识之后,可以针对异步服务配置该端点。使用 fcntl(2) 接口在端点上设置 O_NONBLOCK 标志。这样,无立即可用缓冲区的 t_sndudata(3NSL) 调用将返回 -1,且 t_errno 设置为 TFLOW。同样,无可用数据的 t_rcvudata(3NSL) 调用将返回 -1,且 t_errno 设置为 TNODATA

异步网络传送

虽然应用程序可以使用 poll(2) 来定期检查数据是否到达或等待在端点上接收数据,但是可能需要在数据到达时接收异步通知。结合使用 ioctl(2)I_SETSIG 命令可请求在端点收到数据时将 SIGPOLL 信号发送到该进程。应用程序应检查多条消息导致单个信号的可能性。

在以下示例中,protocol 为应用程序选择的传输协议的名称。

#include <sys/types.h>

#include <tiuser.h>

#include <signal.h>

#include <stropts.h>



int				fd;

struct t_bind				*bind;

void				sigpoll(int);



	fd = t_open(protocol, O_RDWR, (struct t_info *) NULL);



	bind = (struct t_bind *) t_alloc(fd, T_BIND, T_ADDR);

	...     /* set up binding address */

	t_bind(fd, bind, bin



	/* make endpoint non-blocking */

	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);



	/* establish signal handler for SIGPOLL */

	signal(SIGPOLL, sigpoll);



	/* request SIGPOLL signal when receive data is available */

	ioctl(fd, I_SETSIG, S_INPUT | S_HIPRI);



	...



void sigpoll(int sig)

{

	int					flags;

	struct t_unitdata					ud;



	for (;;) {

		... /* initialize ud */

		if (t_rcvudata(fd, &ud, &flags) < 0) {

			if (t_errno == TNODATA)

				break;  /* no more messages */

			... /* process other error conditions */

	}

	... /* process message in ud */

}

异步连接模式服务

对于连接模式服务,应用程序不仅可以安排数据传送,还可以对异步完成的建立连接本身进行安排。操作顺序取决于该进程是尝试连接到其他进程还是等待连接尝试。

异步建立连接

进程可以尝试某个连接并异步完成该连接。进程将首先创建连接端点,然后使用 fcntl(2) 针对非阻塞操作配置该端点。与无连接数据传送一样,还可以针对连接完成时的异步通知和后续数据传送来配置该端点。然后,连接进程将使用 t_connect(3NSL) 开始进行传送设置。随后将使用 t_rcvconnect(3NSL) 来确认连接的建立。

异步使用连接

要异步等待连接,进程应首先建立一个绑定到服务地址的非阻塞端点。当 poll(2) 的结果或异步通知指示连接请求到达时,该进程可以使用 t_listen(3NSL) 来获取连接请求。要接受该连接,进程可以使用 t_accept(3NSL)。必须针对异步数据传送分别配置响应端点。

以下示例说明如何异步请求连接。

#include <tiuser.h>

int             fd;

struct t_call   *call;



	fd = .../* establish a non-blocking endpoint */



	call = (struct t_call *) t_alloc(fd, T_CALL, T_ADDR);

	.../* initialize call structure */

	t_connect(fd, call, call);



	/* connection request is now proceeding asynchronously */



	.../* receive indication that connection has been accepted */

	t_rcvconnect(fd, &call);

以下示例说明如何异步侦听连接。

#include <tiuser.h>

int             fd, res_fd;

struct t_call   call;



	fd = ... /* establish non-blocking endpoint */



	.../*receive indication that connection request has arrived

*/

	call = (struct t_call *) t_alloc(fd, T_CALL, T_ALL);

	t_listen(fd, &call);



	.../* determine whether or not to accept connection */

	res_fd = ... /* establish non-blocking endpoint for response

*/

	t_accept(fd, res_fd, call);

异步打开

有时,可能要求应用程序动态打开从远程主机挂载的文件系统中的常规文件,或者其初始化可能延迟的设备上的常规文件。但是,正在处理此类打开文件的请求的同时,应用程序不能实现对其他事件的实时响应。SunOS 软件通过以下方法解决了此问题:让另一个进程处理文件的实际打开操作,然后将文件描述符传递到实时进程。

传送文件描述符

SunOS 平台提供的 STREAMS 接口提供了一种将打开的文件描述符从一个进程传递到另一个进程的机制。带有打开文件描述符的进程使用带有 I_SENDFD 命令参数的 ioctl(2)。另一个进程通过调用带有 I_RECVFD 命令参数的 ioctl(2) 来获取该文件描述符。

在以下示例中,父进程输出有关测试文件的信息,并创建一个管道。然后,父进程会创建一个子进程,该子进程可打开该测试文件并通过管道将打开的文件描述符传递回父进程。随后父进程会显示关于新文件描述符的状态信息。


示例 8–6 文件描述符传送

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stropts.h>

#include <stdio.h>



#define TESTFILE "/dev/null"

main(int argc, char *argv[])

{

	int fd;

	int pipefd[2];

	struct stat statbuf;



	stat(TESTFILE, &statbuf);

	statout(TESTFILE, &statbuf);

	pipe(pipefd);

	if (fork() == 0) {

		close(pipefd[0]);

		sendfd(pipefd[1]);

	} else {

		close(pipefd[1])

		recvfd(pipefd[0]);

	}

}



sendfd(int p)

{

	int tfd;



	tfd = open(TESTFILE, O_RDWR);

	ioctl(p, I_SENDFD, tfd);

}



recvfd(int p)

{

	struct strrecvfd rfdbuf;

	struct stat statbuf;

	char			fdbuf[32];



	ioctl(p, I_RECVFD, &rfdbuf);

	fstat(rfdbuf.fd, &statbuf);

	sprintf(fdbuf, "recvfd=%d", rfdbuf.fd);

	statout(fdbuf, &statbuf);	

}



statout(char *f, struct stat *s)

{

	printf("stat: from=%s mode=0%o, ino=%ld, dev=%lx, rdev=%lx\n",

		f, s->st_mode, s->st_ino, s->st_dev, s->st_rdev);

	fflush(stdout);

}

状态转换

以下各节中的表介绍与 XTI/TLI 关联的所有状态转换。

XTI/TLI 状态

下表定义 XTI/TLI 状态转换中使用的状态以及服务类型。

表 8–1 XTI/TLI 状态转换和服务类型

状态 

说明 

服务类型 

T_UNINIT

未初始化-接口的初始状态和最终状态 

T_COTST_COTS_ORDT_CLTS

T_UNBND

已初始化但未绑定 

T_COTST_COTS_ORDT_CLTS

T_IDLE

未建立连接 

T_COTST_COTS_ORDT_CLTS

T_OUTCON

针对客户机暂挂的传出连接 

T_COTST_COTS_ORD

T_INCON

针对服务器暂挂的传入连接 

T_COTST_COTS_ORD

T_DATAXFER

数据传送 

T_COTST_COTS_ORD

T_OUTREL

传出顺序释放(等待顺序释放请求) 

T_COTS_ORD

T_INREL

传入顺序释放(等待发送顺序释放请求) 

T_COTS_ORD

传出事件

下表中介绍的传出事件与指定传输例程(发送请求或响应传输提供器的例程)返回的状态相对应。在该表中,某些事件(例如 "accept")根据发生它们的上下文来区分。上下文基于以下变量的值:

表 8–2 传出事件

事件 

说明 

服务类型 

opened

成功返回 t_open(3NSL)

T_COTST_COTS_ORDT_CLTS

bind

成功返回 t_bind(3NSL)

T_COTST_COTS_ORDT_CLTS

optmgmt

成功返回 t_optmgmt(3NSL)

T_COTST_COTS_ORDT_CLTS

unbind

成功返回 t_unbind(3NSL)

T_COTST_COTS_ORDT_CLTS

closed

成功返回 t_close(3NSL)

T_COTST_COTS_ORDT_CLT

connect1

在同步模式下成功返回 t_connect(3NSL)

T_COTST_COTS_ORD

connect2

异步模式下 t_connect(3NSL) 上的 TNODATA 错误,或者由于传输端点上到达断开请求而产生的 TLOOK 错误

T_COTST_COTS_ORD

accept1

t_accept(3NSL) 成功返回,且 ocnt == 1fd == resfd

T_COTST_COTS_ORD

accept2

t_accept(3NSL) 成功返回,且 ocnt== 1fd!= resfd

T_COTST_COTS_ORD

accept3

t_accept(3NSL) 成功返回,且 ocnt > 1

T_COTST_COTS_ORD

snd

成功返回 t_snd(3NSL)

T_COTST_COTS_ORD

snddis1

t_snddis(3NSL) 成功返回,且 ocnt <= 1

T_COTST_COTS_ORD

snddis2

t_snddis(3NSL) 成功返回,且 ocnt > 1

T_COTST_COTS_ORD

sndrel

成功返回 t_sndrel(3NSL)

T_COTS_ORD

sndudata

成功返回 t_sndudata(3NSL)

T_CLTS

传入事件

传入事件与指定例程的成功返回相对应。这些例程返回来自传输提供器的数据或事件信息。不与例程的返回直接关联的唯一传入事件是 pass_conn,该传入事件在连接传送到其他端点时发生。虽然未在连接传送到的端点上调用 XTI/TLI 例程,但是会在该端点上发生事件。

在下表中,rcvdis 事件根据 ocnt 值(端点上未完成的连接请求计数)来区分。

表 8–3 传入事件

事件 

说明 

服务类型 

listen

成功返回 t_listen(3NSL)

T_COTST_COTS_ORD

rcvconnect

成功返回 t_rcvconnect(3NSL)

T_COTST_COTS_ORD

rcv

成功返回 t_rcv(3NSL)

T_COTST_COTS_ORD

rcvdis1

成功返回 t_rcvdis(3NSL) rcvdis1t_rcvdis()onct <= 0

T_COTST_COTS_ORD

rcvdis2

成功返回 t_rcvdis(3NSL)ocnt == 1

T_COTST_COTS_ORD

rcvdis3

t_rcvdis(3NSL) 成功返回,且 ocnt > 1

T_COTST_COTS_ORD

rcvrel

成功返回 t_rcvrel(3NSL)

T_COTS_ORD

rcvudata

成功返回 t_rcvudata(3NSL)

T_CLTS

rcvuderr

成功返回 t_rcvuderr(3NSL)

T_CLTS

pass_conn

接收传递的连接 

T_COTST_COTS_ORD

状态表

状态表描述 XTI/TLI 状态转换。每个框都包含下一个状态,假设已给定当前状态(列)和当前事件(行)。空框表示无效状态/事件组合。每个框还可具有一个操作列表。必须按照框中指定的顺序完成操作。

研究状态表时,应了解以下内容:

下面各表中列出的某些状态转换提供传输用户必须执行的操作。每项操作都由按以下方法得出的数字表示:

下表给出了端点建立状态。

表 8–4 连接建立状态

事件/状态 

T_UNINIT

T_UNBND

T_IDLE

opened

T_UNBND

 

 

bind

 

T_IDLE[1]

 

optmgmt(仅适用于 TLI)

 

 

T_IDLE

unbind

 

 

T_UNBND

closed

 

T_UNINIT

 

下表给出了连接模式下的数据传送。

表 8–5 连接模式状态-第 1 部分

事件/状态 

T_IDLE

T_OUTCON

T_INCON

T_DATAXFER

connect1

T_DATAXFER

 

 

 

connect2

T_OUTCON

 

 

 

rcvconnect

 

T_DATAXFER

 

 

listen

T_INCON [2]

 

T_INCON [2]

 

accept1

 

 

T_DATAXFER [3]

 

accept2

 

 

T_IDLE [3] [4]

 

accept3

 

 

T_INCON [3] [4]

 

snd

 

 

 

T_DATAXFER

rcv

 

 

 

T_DATAXFER

snddis1

 

T_IDLE

T_IDLE [3]

T_IDLE

snddis2

 

 

T_INCON [3]

 

rcvdis1

 

T_IDLE

 

T_IDLE

rcvdis2

 

 

T_IDLE [3]

 

rcvdis3

 

 

T_INCON [3]

 

sndrel

 

 

 

T_OUTREL

rcvrel

 

 

 

T_INREL

pass_conn

T_DATAXFER

 

 

 

optmgmt

T_IDLE

T_OUTCON

T_INCON

T_DATAXFER

closed

T_UNINIT

T_UNINIT

T_UNINIT

T_UNINIT

下表给出了连接模式下的连接建立/连接释放/数据传送。

表 8–6 连接模式状态-第 2 部分

事件/状态 

T_OUTREL

T_INREL

T_UNBND

connect1

 

 

 

connect2

 

 

 

rcvconnect

 

 

 

listen

 

 

 

accept1

 

 

 

accept2

 

 

 

accept3

 

 

 

snd

 

T_INREL

 

rcv

T_OUTREL

 

 

snddis1

T_IDLE

T_IDLE

 

snddis2

 

 

 

rcvdis1

T_IDLE

T_IDLE

 

rcvdis2

 

 

 

rcvdis3

 

 

 

sndrel

 

T_IDLE

 

rcvrel

T_IDLE

 

 

pass_conn

 

 

T_DATAXFER

optmgmt

T_OUTREL

T_INREL

T_UNBND

closed

T_UNINIT

T_UNINIT

 

下表给出了无连接模式状态。

表 8–7 无连接模式状态

事件/状态 

T_IDLE

snudata

T_IDLE

rcvdata

T_IDLE

rcvuderr

T_IDLE

协议独立性指导

通用于许多传输协议的 XTI/TLI 服务集对应用程序提供协议独立性。并非所有传输协议都支持所有的 XTI/TLI 服务。如果软件必须在多种协议环境中运行,请仅使用常见服务。

以下列出了可能不通用于所有传输协议的服务。

XTI/TLI 与套接字接口

XTI/TLI 和套接字是处理相同任务的不同方法。虽然它们提供功能上类似的机制和服务,但并不提供例程或低级服务的一对一兼容性。决定移植应用程序之前,请查看 XTI/TLI 接口和基于套接字的接口之间的相似之处与不同之处。

以下问题与传输独立性相关,并且可能对 RPC 应用程序产生某些影响:

套接字到 XTI/TLI 的等效项

下表给出了 XTI/TLI 接口和套接字接口之间的近似等效项。注释字段介绍不同之处。如果注释列为空,则这些接口类似或不存在等效接口。

表 8–8 TLI 和套接字等效功能

TLI 接口 

套接字接口 

注释 

t_open(3NSL)

socket(3SOCKET)

 

socketpair(3SOCKET)

 

t_bind(3NSL)

bind(3SOCKET)

t_bind(3NSL) 可设置被动套接字的队列深度,bind(3SOCKET) 则不会这样做。对于套接字,在 listen(3SOCKET) 的调用中指定队列长度。

t_optmgmt(3NSL)

getsockopt(3SOCKET)

setsockopt(3SOCKET)

t_optmgmt(3NSL) 只管理传输选项。getsockopt(3SOCKET)setsockopt(3SOCKET) 可以管理传输层的选项,也可管理套接字层和任意协议层的选项。

t_unbind(3NSL)

 

t_close(3NSL)

close(2)

 

t_getinfo(3NSL)

getsockopt(3SOCKET)

t_getinfo(3NSL) 返回有关传输的信息。getsockopt(3SOCKET) 可以返回有关传输和套接字的信息。

t_getstate(3NSL)

-

 

t_sync(3NSL)

-

 

t_alloc(3NSL)

-

 

t_free(3NSL)

-

 

t_look(3NSL)

-

带有 SO_ERROR 选项的 getsockopt(3SOCKET) 返回的错误信息类型与 t_look(3NSL)t_look() 返回的相同。

t_error(3NSL)

perror(3C)

 

t_connect(3NSL)

connect(3SOCKET)

在调用 connect(3SOCKET) 之前,无需绑定本地端点。在调用 t_connect(3NSL) 之前绑定端点。可以在无连接端点上使用 connect(3SOCKET) 设置数据报的缺省目标地址。可以使用 connect(3SOCKET) 发送数据。

t_rcvconnect(3NSL)

-

 

t_listen(3NSL)

listen(3SOCKET)

t_listen(3NSL) 等待连接指示。listen(3SOCKET) 设置队列深度。

t_accept(3NSL)

accept(3SOCKET)

 

t_snd(3NSL)

send(3SOCKET)

 

 

sendto(3SOCKET)

 

 

sendmsg(3SOCKET)

sendto(3SOCKET)sendmsg(3SOCKET) 在连接模式和数据报模式下进行操作。

t_rcv(3NSL)

recv(3SOCKET)

 

 

recvfrom(3SOCKET)

 

 

recvmsg(3SOCKET)

recvfrom(3SOCKET)recvmsg(3SOCKET) 在连接模式和数据报模式下进行操作。

t_snddis(3NSL)

-

 

t_rcvdis(3NSL)

-

 

t_sndrel(3NSL)

shutdown(3SOCKET)

 

t_rcvrel(3NSL)

-

 

t_sndudata(3NSL)

sendto(3SOCKET)

 

 

recvmsg(3SOCKET)

 

t_rcvuderr(3NSL)

-

 

read(2)write(2)

read(2)write(2)

在 XTI/TLI 中,必须在调用 read(2)write(2) 之前推送 tirdwr(7M) 模块。在套接字中,调用 read(2)write(2) 已足够。

XTI 接口的附加功能

XNS 5 (UNIX03) 标准引入了一些新的 XTI 接口。下面进行简要介绍。可以在相关手册页中找到详细信息。 TLI 用户不能使用这些接口。分散/集中数据传送接口为:

t_sndvudata(3NSL)

从一个或多个非连续缓冲区发送数据单元

t_rcvvudata(3NSL)

将数据单元接收到一个或多个非连续缓冲区

t_sndv(3NSL)

通过连接发送来自一个或多个非连续缓冲区的数据或加速数据

t_rcvv(3NSL)

通过连接接收数据或加速数据并将该数据放入一个或多个非连续缓冲区

XTI 实用程序接口 t_sysconf(3NSL) 可获取可配置的 XTI 变量。t_sndreldata(3NSL) 接口可启动并响应带有用户数据的顺序释放。t_rcvreldata(3NSL) 可接收包含用户数据的顺序释放指示或确认。


注 –

附加接口 t_sndreldata(3NSL)t_rcvreldata(3NSL) 只与称为最小 OSI 的特定传输一起使用,该特定传输在 Solaris 平台上不可用。这些接口不能与 Internet 传输(TCP 或 UDP)一起使用。