本节介绍针对实时应用程序使用 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); }