本节介绍其他 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 可接受每个连接请求。