本章介绍传输层接口 (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 接口行为尚未有明确的规定。请不要从信号处理程序例程使用这些接口。
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 的接口。请参见第 9 章,传输选择和名称到地址的映射。
使用 TLI 的程序应该通过在编译时指定 -l nsl 选项与 libnsl 网络服务库链接。
使用 XTI 的程序应该通过在编译时指定 -l xnet 选项与 xnet 库链接。
定义传输例程调用顺序的状态转换规则。有关状态转换规则的更多信息,请参见状态转换。状态表基于事件的状态和处理方式来定义库调用的合法序列。这些事件包括用户生成的库调用,以及提供器生成的事件指示。XTI/TLI 程序员在使用接口之前应了解所有状态转换。
用户可能要对现有程序(如 /usr/bin/cat)使用 exec(2) 来建立传输连接,以便处理通过该连接收到的数据。现有程序使用 read(2) 和 write(2)。XTI/TLI 并不直接支持传输提供器的读/写接口,但是有一个接口可用。利用该接口,可以在数据传送阶段通过传输连接发出 read(2) 和 write(2) 调用。本节介绍 XTI/TLI 连接模式服务的读/写接口。该接口不适用于无连接模式服务。
#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) 语义。使用 read 和 write 语义时,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 模块可处理从提供器传递至用户的任何其他事件或请求,如下所示:
read(2) 无法识别发送给用户的加速数据。如果 read(2) 接收到加速数据请求,则 tirdwr 将在流上生成一个致命的协议错误 EPROTO。该错误将导致后续系统调用失败。请勿使用 read(2) 接收加速数据。
tirdwr 会放弃异常断开请求并在流上生成挂起状态。后续 read(2) 调用检索所有剩余数据,然后针对所有后续调用返回零值,指示文件结束。
tirdwr 会放弃顺序释放请求并向用户发送一条长度为零的消息。如 read(2) 手册页中所述,该消息通过返回 0 来通知用户文件结束。
如果 read(2) 接收到任何其他 XTI/TLI 请求,则 tirdwr 将在流上生成一个致命的协议错误 EPROTO。该错误将导致后续系统调用失败。如果用户在连接建立之后将 tirdwr 推送到流,则 tirdwr 不会生成请求。
利用流上的 tirdwr,在连接有效期间内,可以通过传输连接发送和接收数据。用户可通过关闭与传输端点关联的文件描述符来终止连接,也可通过将 tirdwr 模块弹出流来终止连接。在上述任一情况下,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_NDELAY 或 O_NONBLOCK 来启用异步模式。在调用 XTI/TLI 例程之前,借助 t_open(3NSL) 或者通过调用 fcntl(2) 将这些模式设置为标志。使用 fcntl(2) 可随时启用或禁用该模式。本章中的所有程序示例都使用缺省的同步处理模式。
使用 O_NDELAY 或 O_NONBLOCK 会以不同方式影响每个 XTI/TLI 例程。需要针对特定例程确定 O_NDELAY 或 O_NONBLOCK 的确切语义。
示例 8–2 说明了两个重要概念。第一个是服务器管理多个未完成连接请求的功能。第二个是以事件驱动方式使用 XTI/TLI 和系统调用接口。
使用 XTI/TLI,一台服务器可以管理多个未完成的连接请求。接收数个同时连接请求的一个原因是设置客户机的优先级。一台服务器可以接收数个连接请求,并基于每台客户机的优先级按顺序接受这些请求。
处理数个未完成连接请求的第二个原因是克服单线程处理的限制。根据传输提供器,服务器正在处理一个连接请求时,其他客户机会将该服务器视为处于忙状态。如果同时处理多个连接请求,则该服务器仅在超过最大数量的客户机同时尝试呼叫该服务器时处于忙状态。
该服务器的示例是事件驱动的:进程针对传入的 XTI/TLI 事件轮询传输端点,并针对接收到的事件执行相应操作。以下示例说明针对传入事件轮询多个传输端点的功能。
#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 的值进行协商,使其更小。
在服务器绑定其地址并且可以处理连接请求之后,其行为如以下示例所示。
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 值。如果 revents 为 0,则端点未生成任何事件,服务器将继续检查下一个端点。如果 revents 为 POLLIN,则端点上有事件。服务器会调用 do_event 来处理该事件。revents 中的任何其他值指示端点上存在错误,并且服务器将退出。由于存在多个端点,因此服务器应关闭该描述符并继续检查。
每次服务器迭代循环时,都会调用 service_conn_ind 来处理任何未完成的连接请求。如果还有其他暂挂的连接请求,service_conn_ind 将保存新的连接请求,并在以后对其做出响应。
服务器会调用以下示例中的 do_event 来处理传入事件。
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 来处理该端点上的所有已排队连接请求,如以下示例所示。
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) 来获取该文件描述符。
在以下示例中,父进程输出有关测试文件的信息,并创建一个管道。然后,父进程会创建一个子进程,该子进程可打开该测试文件并通过管道将打开的文件描述符传递回父进程。随后父进程会显示关于新文件描述符的状态信息。
#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 状态转换中使用的状态以及服务类型。
表 8–1 XTI/TLI 状态转换和服务类型
状态 |
说明 |
服务类型 |
---|---|---|
T_UNINIT |
未初始化-接口的初始状态和最终状态 |
T_COTS、T_COTS_ORD、T_CLTS |
T_UNBND |
已初始化但未绑定 |
T_COTS、T_COTS_ORD、T_CLTS |
T_IDLE |
未建立连接 |
T_COTS、T_COTS_ORD、T_CLTS |
T_OUTCON |
针对客户机暂挂的传出连接 |
T_COTS、T_COTS_ORD |
T_INCON |
针对服务器暂挂的传入连接 |
T_COTS、T_COTS_ORD |
T_DATAXFER |
数据传送 |
T_COTS、T_COTS_ORD |
T_OUTREL |
传出顺序释放(等待顺序释放请求) |
T_COTS_ORD |
T_INREL |
传入顺序释放(等待发送顺序释放请求) |
T_COTS_ORD |
下表中介绍的传出事件与指定传输例程(发送请求或响应传输提供器的例程)返回的状态相对应。在该表中,某些事件(例如 "accept")根据发生它们的上下文来区分。上下文基于以下变量的值:
ocnt-未完成连接请求的计数
fd-当前传输端点的文件描述符
resfd-接受连接的传输端点的文件描述符
事件 |
说明 |
服务类型 |
---|---|---|
opened |
成功返回 t_open(3NSL) |
T_COTS、T_COTS_ORD、T_CLTS |
bind |
成功返回 t_bind(3NSL) |
T_COTS、T_COTS_ORD、T_CLTS |
成功返回 t_optmgmt(3NSL) |
T_COTS、T_COTS_ORD、T_CLTS |
|
unbind |
成功返回 t_unbind(3NSL) |
T_COTS、T_COTS_ORD、T_CLTS |
closed |
成功返回 t_close(3NSL) |
T_COTS、T_COTS_ORD、T_CLT |
connect1 |
在同步模式下成功返回 t_connect(3NSL) |
T_COTS、T_COTS_ORD |
connect2 |
异步模式下 t_connect(3NSL) 上的 TNODATA 错误,或者由于传输端点上到达断开请求而产生的 TLOOK 错误 |
T_COTS、T_COTS_ORD |
accept1 |
t_accept(3NSL) 成功返回,且 ocnt == 1、fd == resfd |
T_COTS、T_COTS_ORD |
accept2 |
t_accept(3NSL) 成功返回,且 ocnt== 1、fd!= resfd |
T_COTS、T_COTS_ORD |
accept3 |
t_accept(3NSL) 成功返回,且 ocnt > 1 |
T_COTS、T_COTS_ORD |
snd |
成功返回 t_snd(3NSL) |
T_COTS、T_COTS_ORD |
snddis1 |
t_snddis(3NSL) 成功返回,且 ocnt <= 1 |
T_COTS、T_COTS_ORD |
snddis2 |
t_snddis(3NSL) 成功返回,且 ocnt > 1 |
T_COTS、T_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_COTS、T_COTS_ORD |
rcvconnect |
成功返回 t_rcvconnect(3NSL) |
T_COTS、T_COTS_ORD |
rcv |
成功返回 t_rcv(3NSL) |
T_COTS、T_COTS_ORD |
rcvdis1 |
成功返回 t_rcvdis(3NSL) rcvdis1t_rcvdis()、onct <= 0 |
T_COTS、T_COTS_ORD |
rcvdis2 |
成功返回 t_rcvdis(3NSL)、ocnt == 1 |
T_COTS、T_COTS_ORD |
rcvdis3 |
t_rcvdis(3NSL) 成功返回,且 ocnt > 1 |
T_COTS、T_COTS_ORD |
rcvrel |
成功返回 t_rcvrel(3NSL) |
T_COTS_ORD |
rcvudata |
成功返回 t_rcvudata(3NSL) |
T_CLTS |
rcvuderr |
成功返回 t_rcvuderr(3NSL) |
T_CLTS |
pass_conn |
接收传递的连接 |
T_COTS、T_COTS_ORD |
状态表描述 XTI/TLI 状态转换。每个框都包含下一个状态,假设已给定当前状态(列)和当前事件(行)。空框表示无效状态/事件组合。每个框还可具有一个操作列表。必须按照框中指定的顺序完成操作。
研究状态表时,应了解以下内容:
t_close(3NSL) 针对面向连接的传输提供器终止已建立的连接。连接将按顺序终止或异常终止,具体取决于传输提供器支持的服务类型。请参见 t_getinfo(3NSL) 手册页。
如果传输用户发出无序接口调用,该接口将失败并将 t_errno 设置为 TOUTSTATE。状态不会更改。
t_connect(3NSL) 之后的错误代码 TLOOK 或 TNODATA 可以导致状态更改。状态表假设正确使用 XTI/TLI。
任何其他传输错误都不会更改状态,除非接口的手册页另行指出。
可从状态表中排除支持接口 t_getinfo(3NSL)、t_getstate(3NSL)、t_alloc(3NSL)、t_free(3NSL)、t_sync(3NSL)、t_look(3NSL) 以及 t_error(3NSL),因为它们并不影响状态。
下面各表中列出的某些状态转换提供传输用户必须执行的操作。每项操作都由按以下方法得出的数字表示:
将未完成连接请求的计数设置为零
递增未完成连接请求的计数
递减未完成连接请求的计数
将连接传递到其他传输端点,如 t_accept(3NSL) 手册页中所示
下表给出了端点建立状态。
表 8–4 连接建立状态
事件/状态 |
T_UNINIT |
T_UNBND |
T_IDLE |
---|---|---|---|
opened |
T_UNBND |
|
|
bind |
|
T_IDLE[1] |
|
|
|
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 |
|
|
|
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 |
|
| |
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 服务。如果软件必须在多种协议环境中运行,请仅使用常见服务。
以下列出了可能不通用于所有传输协议的服务。
在连接模式服务中,并非所有传输提供器都支持传输服务数据单元 (transport service data unit, TSDU)。请不要对保留连接的逻辑数据边界做出假设。
t_open(3NSL) 和 t_getinfo(3NSL) 例程返回协议和实现特定的服务限制。使用这些限制可分配缓冲区以存储特定于协议的传输地址和选项。
请勿将用户数据与连接请求和断开请求(例如 t_connect(3NSL) 和 t_snddis(3NSL))一起发送。并非所有传输协议都可以使用该方法。
t_call 结构中用于 t_listen(3NSL) 的缓冲区必须足够大,以便保留客户机在建立连接期间发送的任何数据。使用 t_alloc(3NSL) 的 T_ALL 参数可设置用于存储当前传输提供器的地址、选项以及用户数据的最大缓冲区大小。
请勿在客户端端点上的 t_bind(3NSL) 上指定协议地址。传输提供器应该为传输端点指定适当的地址。对于 t_bind(3NSL),服务器应该以不要求了解传输提供器名称空间的方法来检索其地址。
请勿对传输地址的格式做出假设。传输地址在程序中不应为常量。第 9 章,传输选择和名称到地址的映射包含有关传输选择的详细信息。
与 t_rcvdis(3NSL) 关联的原因代码与协议相关。如果协议独立性很重要,请不要解释这些原因代码。
t_rcvuderr(3NSL) 错误代码与协议相关。如果需要考虑协议独立性,请不要解释这些错误代码。
请勿将设备名称编码到程序中。设备节点标识特定的传输提供器,不独立于协议。有关传输选择的详细信息,请参见第 9 章,传输选择和名称到地址的映射。
请勿在用于多个协议环境的程序中使用由 t_sndrel(3NSL) 和 t_rcvrel(3NSL) 提供的连接模式服务的可选顺序释放功能。并非所有基于连接的传输协议都支持此功能。使用此功能可防止程序与开放系统成功通信。
XTI/TLI 和套接字是处理相同任务的不同方法。虽然它们提供功能上类似的机制和服务,但并不提供例程或低级服务的一对一兼容性。决定移植应用程序之前,请查看 XTI/TLI 接口和基于套接字的接口之间的相似之处与不同之处。
以下问题与传输独立性相关,并且可能对 RPC 应用程序产生某些影响:
特权端口-特权端口是 TCP/IP Internet 协议的 Berkeley 软件分发 (Berkeley Software Distribution, BSD) 实现的产物。这些端口不可移植。独立于传输的环境中不支持特权端口概念。
不透明地址-不能以独立于传输的方式分隔命名某主机的地址部分和命名该主机上服务的地址部分。请确保更改假设能分辨网络服务的主机地址的任何代码。
下表给出了 XTI/TLI 接口和套接字接口之间的近似等效项。注释字段介绍不同之处。如果注释列为空,则这些接口类似或不存在等效接口。
表 8–8 TLI 和套接字等效功能
TLI 接口 |
套接字接口 |
注释 |
---|---|---|
|
||
– |
|
|
t_bind(3NSL) 可设置被动套接字的队列深度,bind(3SOCKET) 则不会这样做。对于套接字,在 listen(3SOCKET) 的调用中指定队列长度。 |
||
t_optmgmt(3NSL) 只管理传输选项。getsockopt(3SOCKET) 和 setsockopt(3SOCKET) 可以管理传输层的选项,也可管理套接字层和任意协议层的选项。 |
||
– |
|
|
|
||
t_getinfo(3NSL) 返回有关传输的信息。getsockopt(3SOCKET) 可以返回有关传输和套接字的信息。 |
||
- |
|
|
- |
|
|
- |
|
|
- |
|
|
- |
带有 SO_ERROR 选项的 getsockopt(3SOCKET) 返回的错误信息类型与 t_look(3NSL)t_look() 返回的相同。 |
|
|
||
在调用 connect(3SOCKET) 之前,无需绑定本地端点。在调用 t_connect(3NSL) 之前绑定端点。可以在无连接端点上使用 connect(3SOCKET) 设置数据报的缺省目标地址。可以使用 connect(3SOCKET) 发送数据。 |
||
- |
|
|
t_listen(3NSL) 等待连接指示。listen(3SOCKET) 设置队列深度。 |
||
|
||
|
||
|
|
|
|
sendto(3SOCKET) 和 sendmsg(3SOCKET) 在连接模式和数据报模式下进行操作。 |
|
|
||
|
|
|
|
recvfrom(3SOCKET) 和 recvmsg(3SOCKET) 在连接模式和数据报模式下进行操作。 |
|
- |
|
|
- |
|
|
|
||
- |
|
|
|
||
|
|
|
- |
|
|
在 XTI/TLI 中,必须在调用 read(2) 或 write(2) 之前推送 tirdwr(7M) 模块。在套接字中,调用 read(2) 或 write(2) 已足够。 |
XNS 5 (UNIX03) 标准引入了一些新的 XTI 接口。下面进行简要介绍。可以在相关手册页中找到详细信息。 TLI 用户不能使用这些接口。分散/集中数据传送接口为:
XTI 实用程序接口 t_sysconf(3NSL) 可获取可配置的 XTI 变量。t_sndreldata(3NSL) 接口可启动并响应带有用户数据的顺序释放。t_rcvreldata(3NSL) 可接收包含用户数据的顺序释放指示或确认。
附加接口 t_sndreldata(3NSL) 和 t_rcvreldata(3NSL) 只与称为最小 OSI 的特定传输一起使用,该特定传输在 Solaris 平台上不可用。这些接口不能与 Internet 传输(TCP 或 UDP)一起使用。