文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>第十五章 高级进程间通信

第十五章 高级进程间通信

时间:2005-08-31  来源:mantou


15.1 引言
上一章说明了各种UNIX系统提供的IPC经典方法,包括:管道、FIFO、消息队列、信
号量和共享存储。本章介绍某些高级的IPC以及它们的应用方法,包括:流管道和命
名流管道。使用这些机制,我们可以在进程间传送打开文件描述符。在分别为每一
个客户进程提供一个通道的系统中,这些通信机制使客户进程能与精灵服务进程会
合。4。2BSD和SVR3。2最早提供这些高级形式的IPC,但是至今尚未广泛使用,也缺
少参政文献。本章中很多思想来自Pressotto和Ritchie[1990]的论文。
15.2 流管道
流管道是一个双向(全双工)管道。单个流管道就能向父、子进程提供双向的数据流
。图15。1显示了观察流管道的两种方式。它与图14.2的唯一区别是双向箭头连线
,这是因为流管道是全双工的。
实例
我们用一个流管道再次实现了程序14.9的协作进程实例。程序15.1是新的main函数
。add2协作进程与程序14.8中的相同。程序15.1调用了创建一个流管道的新函数s
_pipe。(在下面将说明该函数的SVR4和4.3+BSD版本。)
图15.1 观察流管道的两种方式
#include
#include "ourhdr.h"
static void sig_pipe(int); /* our signal handler */
int
main(void)
{
int n, fd[2];
pid_t pid;
char line[MAXLINE];
if (signal(SIGPIPE, sig_pipe) == SIG_ERR)
err_sys("signal error");
if (s_pipe(fd) < 0) /* only need a single stream pipe */
err_sys("pipe error");
if ( (pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0) { /* parent */
close(fd[1]);
while (fgets(line, MAXLINE, stdin) != NULL) {
n = strlen(line);
if (write(fd[0], line, n) != n)
err_sys("write error to pipe");
if ( (n = read(fd[0], line, MAXLINE)) < 0)
err_sys("read error from pipe");
if (n == 0) {
err_msg("child closed pipe");
break;
}
line[n] = 0; /* null terminate */
if (fputs(line, stdout) == EOF)
err_sys("fputs error");
}
if (ferror(stdin))
err_sys("fgets error on stdin");
exit(0);
} else { /* child */
close(fd[0]);
if (fd[1] != STDIN_FILENO) {
if (dup2(fd[1], STDIN_FILENO) != STDIN_FILENO)
err_sys("dup2 error to stdin");
}
if (fd[1] != STDOUT_FILENO) {
if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO)
err_sys("dup2 error to stdout");
}
if (execl("./add2", "add2", NULL) < 0)
err_sys("execl error");
}
}
static void
sig_pipe(int signo)
{
printf("SIGPIPE caught ");
exit(1);
}
程序15.1 用流管道驱动add2过滤进程的程序
父程序只使用fd[0],子程序只使用fd[1] 。因为流管道的每一端都是全双工的
,所以父进程读、写fd[0],而子程序将fd[1]复制到标准输入和标准输出。图15.2显
示了由此构成的各描述符。
图15.2 为协作进程安排的各描述符
s_pipe函数定义为与标准pipe函数类似。它的调用参数与pipe相同,但返回的描述
符以读 写方式打开。
实例一SVR4下的s_pipe函数
程序15.2是s_pipe函数的SVR4版本。它只是调用创建全双工管道的标准pipe函数
#include "ourhdr.h"
int
s_pipe(int fd[2]) /* two file descriptors returned in fd[0] & fd[1] */
{
return( pipe(fd) );
}
程序15.2 s_pipe函数的SVR4版本
在系统V的早期版本中也可以创建流管道,但要进行的处理较多。有关SVR3.2下创建
流管道的详细情况,请参阅Stevens[1990]。
图15.3显示SVR4之下管道的基本结构。它主要是两个相互连接的流首。
图15.3 在SVR4之下的管道
因为管道是一种流设备,我们可将处理模块压入管道的任一端。在15.5.1节,我们将
用此技术提供一个可以装配的命名管道。
实例一4.3+BSD之下的s_pipe函数
程序15.3是s_pipe函数的BSD版本。此函数在4.2BSD及以后的各版本中起作用。它
创建一对互连的UNIX域流套接口。
自4.2BSD开始,常规的管道已用此方式实现。但是,当调用pipe时,第一个描述
符的写端和第二个描述符的读端都被关闭。为获得全双工管道,必须直接调用sock
etpair。
#include
#include
#include "ourhdr.h"
int
s_pipe(int fd[2]) /* two file descriptors returned in fd[0] & fd[1] */
{
return( socketpair(AF_UNIX, SOCK_STREAM, 0, fd) );
}
程序15.3 s_pipe函数的BSD版本。
15.3 传送文件描述符
在进程间传送打开文件描述符的能力是非常有用的。用此可以对客户/服务器
应用进行不同的设计。它允许一个进程(典型地是一个服务器)处理与打开一个文件
有关的所有操作(涉及的细节可能是:将网络名翻译为网络地址、拨号调制解调器、
协商文件锁等。)以及向调用进程返回一描述符,该描述符可被用于以后的所有I/O
函数。打开文件或 设备的所有细节对客户而言都是透明的。
4.2BSD支持传送打开描述符,但其实施中有些错误。4.3BSD排除了这些错
误。3.2
及以上版本都支持传送打开描述符。
下面进一步说明"从一个进程向另一个进程传送一打开文件描述符"的含义。回忆图
3.3,其中显示了两个进程,它们打开了同一文件。虽然它们共享同一v_node,但每个
进程都有它自己的文件表项。当从一个进程向另一个进程传送一打开文件描述符时
,我们想要发送进程和接收进程共享同一文件表项。图15.4示出了所希望的安排。
在技术上,发送进程实际上向接受进程传送一个指向一打开文件表项的指针。该指
针被分配存放在接收进程的第一个可用描述符项中。(注意,不要得到错觉以为发送
进程和接收进程中的描述符编号是相同的,通常它们是不同的。)这种情况与在for
k之后,父、子进程完全共享一个打开文件表项相同(回忆图8.1)。
当发送进程将描述符传送给接收进程后,通常它关闭该描述符。发送进程关闭该描
述符并不造成关闭该文件或设备,其原因是该描述符对应的文件仍需为接收进程打
开(即使接收进程尚未接收到该描述符)。
图15.4 从上一进程传送一个打开文件至下一进程
下面我们定义本章使用的三个函数(在第十八章也使用)以发送和接收文件描述符。
本节将会给出对于SVR4和4.3+BSD的这三个函数的不同实现。
_______________________________________________________________________
______
#include "ourhdr."
int send_fd(int spipefd, int filedes);
int send_err(int spipefd,int status, const char *errmsg);
两个函数返回:若成功为0,出错为-1
int recv_fd(int spipefd, ssize_t (*userfunc)(int , const void *, size-t
));
返回:若成功为文件描述符,出错<0
_______________________________________________________________________
______
当一个进程(通常是一个服务器)希望将一个描述符传送给另一个进程时,它调用se
nd_fd或send_err。等待接收描述符的进程(客户)调用recv_fd。
Send_fd经由流管道spipefd发送描述符filedes。send_err 经由流管道spipefd发
送errmsg和status字节。status的值应在-1至-225之间
客户调用secv_fd接收一描述符。如果一切正常(发送者调用了send_fd),则作为函
数值返回非负描述符。否则,返回值是由send_err发送的status(在-1和-255之间的
一个负值)。另外,如果服务器发送了一条出错消息,则客户调用它自己的userfunc
处理该消息。userfunc的第一个参数是常数STDERR_FILENO, 然后是指向出错消息
的指针及其长度。客户常将userfunc指定为UNIX的Write函数。
我们实现了用于这三个函数的我们自己制定的协议。为发送一描述符,send_fd先发
送两个0字节,然后是实际描述符。为了发送一条出错消息,send_err 发送errmsg,
然后是1个0字节,最后是status字节的绝对值(1-255)。recv_fd读流管道中所有字
节直至null字符。在null字符之前的所有字符都送给调用者的userfunc。recv_fd
读到的下一个字节是status字节.若status字节为0,那么一个描述符已传送,否
则表示没有接收到描述符.
Send_err函数在将出错消息写到流管道后,即调用send_fg函数.这示于程序15
.4中。
#include "ourhdr.h"
/* Used when we had planned to send an fd using send_fd(),
* but encountered an error instead. We send the error back
* using the send_fd()/recv_fd() protocol. */
int
send_err(int clifd, int errcode, const char *msg)
{
int n;
if ( (n = strlen(msg)) > 0)
if (writen(clifd, msg, n) != n) /* send the error message */
return(-1);
if (errcode >= 0)
errcode = -1; /* must be negative */
if (send_fd(clifd, errcode) < 0)
return(-1);
return(0);
}
程序15.4 send_err函数
15.3.1 SVR4
在SVR4之下,文件描述符用两条ioctl命令在一流管道中交换,这两条命令是:I_
SENDFD 和I_RECVFD。为了发送一描述符,我们将ioctl的第三个参数设置为实际描
述符。这示于程序15.5中。
#include
#include
#include "ourhdr.h"
/* Pass a file descriptor to another process.
* If fd<0, then -fd is sent back instead as the error status. */
int
send_fd(int clifd, int fd)
{
char buf[2]; /* send_fd()/recv_fd() 2-byte protocol */
buf[0] = 0; /* null byte flag to recv_fd() */
if (fd < 0) {
buf[1] = -fd; /* nonzero status means error */
if (buf[1] == 0)
buf[1] = 1; /* -256, etc. would screw up protocol */
} else {
buf[1] = 0; /* zero status means OK */
}
if (write(clifd, buf, 2) != 2)
return(-1);
if (fd >= 0)
if (ioctl(clifd, I_SENDFD, fd) < 0)
return(-1);
return(0);
}
程序15.5  SVR4下的send_fd函数
当接收一描述符时,ioctl的第三个参数是一指向strrecvfd结构的指针。
_______________________________________________________________________
______
struct strrecvfd {
int fd; 新描述符
uid_t uid; 发送者的有效用户ID
gid_t gid; 发送者的有效组ID
char fill[8];
}
_______________________________________________________________________
______
recv_fd读流管道直到接收到双字节协议的第一个字节(null字节).当发出带I_
RECVFD命令的ioctl时,在流读首处的第一条消息应当是一个描述符,它是由I_SE
NDFD发来的,或者得到一条出错消息。这示于程序15.6中。
#include
#include
#include "ourhdr.h"
/* Receive a file descriptor from another process (a server).
* In addition, any data received from the server is passed
* to (*userfunc)(STDERR_FILENO, buf, nbytes). We have a
* 2-byte protocol for receiving the fd from send_fd(). */
int
recv_fd(int servfd, ssize_t (*userfunc)(int, const void *, size_t))
{
int newfd, nread, flag, status;
char *ptr, buf[MAXLINE];
struct strbuf dat;
struct strrecvfd recvfd;
status = -1;
for ( ; ; ) {
dat.buf = buf;
dat.maxlen = MAXLINE;
flag = 0;
if (getmsg(servfd, NULL, &dat, &flag) < 0)
err_sys("getmsg error");
nread = dat.len;
if (nread == 0) {
err_ret("connection closed by server");
return(-1);
}
/* See if this is the final data with null & status.
Null must be next to last byte of buffer, status
byte is last byte. Zero status means there must
be a file descriptor to receive. */
for (ptr = buf; ptr < &buf[nread]; ) {
if (*ptr++ == 0) {
if (ptr != &buf[nread-1])
err_dump("message format error");
status = *ptr & 255;
if (status == 0) {
if (ioctl(servfd, I_RECVFD, &recvfd) < 0)
return(-1);
newfd = recvfd.fd; /* new descriptor */
} else
newfd = -status;
nread -= 2;
}
}
if (nread > 0)
if ((*userfunc)(STDERR_FILENO, buf, nread) != nread)
return(-1);
if (status >= 0) /* final data has arrived */
return(newfd); /* descriptor, or -status */
}
}
程序15.6  SVR4下的recv_fd函数
15.3.2 4.3BSD
不幸,对于4.3BSD以及在其基础上构造的SunOS和Ultrix,以及从4.3BSD
Reno开始的后续版本我们必须提供不同的实现。
为了交换文件描述符,调用sendmsg(2)和recvmsg(2)函数。这两个函数的参数中都
有一个指向msghdr的指针,该结构包含了所有关于要发送和接收消息的信息。该结
构定义在〈sys/socket.h〉 头文件中,在BSD4.3之下,其样式是:
_______________________________________________________________________
______
strcut msghdr {
caddr_t msg_name; 可选的地址
int msg_namelen; 地址长度
struct iovec msg_iov; 散布/聚集数组
int msg_iovlen; 在msg_iov数组中的元素数
caddr_t msg_accrights; 存取权发送/接收到
int msg-accrightslen; 存取权缓存的长度
}
_______________________________________________________________________
______
头两个元素通常用于在网络连接上发送数据报文,在这里,目的地址可以由每个数
据报文指定。下面两个元素使我们可以指定缓存的数组(散布读和聚集写),这如
同我们对readv和writev函数(12.7节)的说明一样。最后两个元素处理存取权的
传送和接收。当前唯一定义的存取权是文件描述符。存取权仅可跨越一个UNIX域套
接口传送(亦即,在4.3BSD之下作为流管道我们所使用的)。为了发送或接收一文
件描述符,将msg_accrights设置为指向该整型描述符,将msg_accrightslen设置
为描述符的长度(亦即,整型的长度)。仅当此长度非0时,才传送或接收描述符

程序15.7是4.3BSD下的send_fd函数
#include
#include /* struct msghdr */
#include /* struct iovec */
#include
#include
#include "ourhdr.h"
/* Pass a file descriptor to another process.
* If fd<0, then -fd is sent back instead as the error status. */
int
send_fd(int clifd, int fd)
{
struct iovec iov[1];
struct msghdr msg;
char buf[2]; /* send_fd()/recv_fd() 2-byte protocol */
iov[0].iov_base = buf;
iov[0].iov_len = 2;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
if (fd < 0) {
msg.msg_accrights = NULL;
msg.msg_accrightslen = 0;
buf[1] = -fd; /* nonzero status means error */
if (buf[1] == 0)
buf[1] = 1; /* -256, etc. would screw up protocol */
} else {
msg.msg_accrights = (caddr_t) &fd; /* addr of descriptor */
msg.msg_accrightslen = sizeof(int); /* pass 1 descriptor */
buf[1] = 0; /* zero status means OK */
}
buf[0] = 0; /* null byte flag to recv_fd() */
if (sendmsg(clifd, &msg, 0) != 2)
return(-1);
return(0);
}
程序15.7 4.3BDS下的send_fd函数
在sendnisg调用中,发送双字节协议数据(null和status字节)和描述符。
为了接收一文件描述符,我们从流管道读,直至读到null字节,它位于最后的sta
tus字节之前。在此null字节之前的是一条出错消息,它来自发送者。这示于程序
15.8。
#include
#include /* struct msghdr */
#include /* struct iovec */
#include
#include "ourhdr.h"
/* Receive a file descriptor from another process (a server).
* In addition, any data received from the server is passed
* to (*userfunc)(STDERR_FILENO, buf, nbytes). We have a
* 2-byte protocol for receiving the fd from send_fd(). */
int
recv_fd(int servfd, ssize_t (*userfunc)(int, const void *, size_t))
{
int newfd, nread, status;
char *ptr, buf[MAXLINE];
struct iovec iov[1];
struct msghdr msg;
status = -1;
for ( ; ; ) {
iov[0].iov_base = buf;
iov[0].iov_len = sizeof(buf);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_accrights = (caddr_t) &newfd;/* addr of descriptor */
msg.msg_accrightslen = sizeof(int); /* receive 1 descriptor */
if ( (nread = recvmsg(servfd, &msg, 0)) < 0)
err_sys("recvmsg error");
else if (nread == 0) {
err_ret("connection closed by server");
return(-1);
}
/* See if this is the final data with null & status.
Null must be next to last byte of buffer, status
byte is last byte. Zero status means there must
be a file descriptor to receive. */
for (ptr = buf; ptr < &buf[nread]; ) {
if (*ptr++ == 0) {
if (ptr != &buf[nread-1])
err_dump("message format error");
status = *ptr & 255;
if (status == 0) {
if (msg.msg_accrightslen != sizeof(int))
err_dump("status = 0 but no fd");
/* newfd = the new descriptor */
} else
newfd = -status;
nread -= 2;
}
}
if (nread > 0)
if ((*userfunc)(STDERR_FILENO, buf, nread) != nread)
return(-1);
if (status >= 0) /* final data has arrived */
return(newfd); /* descriptor, or -status */
}
}
程序15.8 4.3BSD下的recv_fd函数
注意,该程序总是准备接收一描述符(在每次调用recvmsg之前,设置msg_accrig
hts和msg_accrightslen ),但是仅当在返回时msg_accrightslen非0,我们才确实
接收到一描述符。
15.3.3 4.3+BSD
从4.3BSD Reno开始,更改了msghdr结构的定义。在以前版本中被称之为"存取权"
的最后两个元素改称为"辅助数据"。另外,在该结构结束处增加了一个新成员msg
_flags。
strcut msghdr {
caddr_t msg_name; 可选的地址
int msg_namelen; 地址长度
struct iovec msg_iov; 散布/聚集数组
int msg_iovlen; 在msg_iov数组中的元素数
caddr_t msg_control; 辅助数据
int msg-controllen; 辅助数据的长度
int msg_flags; 接收到消息的标志
}
现在,msg_control字段指向一个cmsghdr(控制消息头)结构。
struct cmsghdr {
u_int cmsg_len; 数据的字节数,包括头
int cmsg_level; 初始的协议
int cmsg_type; 协议细节的类型
下接真正的控制消息数据
}
为了发送一文件描述符,将cmsg_len设置为cmsghdr结构长度加一个整型(描述符

)的长度。将cmsg_level设置为SOL_SOCKET,cmsg_type设置为SCM_RIGHTS,这表明
正在传送的是存取权。("SCM"表示套接口级控制消息,"socket-level control
message"。)实际描述符的存放位置紧随cmsy_type字段之后,使用CMSG_DATA宏以
获得指向该整型数的指针。程序15.9示出了4.3BSD Reno之下的send_fd函数。
#include
#include /* struct msghdr */
#include /* struct iovec */
#include
#include
#include "ourhdr.h"
static struct cmsghdr *cmptr = NULL; /* buffer is malloc'ed first time
*/
#define CONTROLLEN (sizeof(struct cmsghdr) + sizeof(int))
/* size of control buffer to send/recv one file descriptor */
/* Pass a file descriptor to another process.
* If fd<0, then -fd is sent back instead as the error status. */
int
send_fd(int clifd, int fd)
{
struct iovec iov[1];
struct msghdr msg;
char buf[2]; /* send_fd()/recv_fd() 2-byte protocol */
iov[0].iov_base = buf;
iov[0].iov_len = 2;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
if (fd < 0) {
msg.msg_control = NULL;
msg.msg_controllen = 0;
buf[1] = -fd; /* nonzero status means error */
if (buf[1] == 0)
buf[1] = 1; /* -256, etc. would screw up protocol */
} else {
if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)
return(-1);
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
cmptr->cmsg_len = CONTROLLEN;
msg.msg_control = (caddr_t) cmptr;
msg.msg_controllen = CONTROLLEN;
*(int *)CMSG_DATA(cmptr) = fd; /* the fd to pass */
buf[1] = 0; /* zero status means OK */
}
buf[0] = 0; /* null byte flag to recv_fd() */
if (sendmsg(clifd, &msg, 0) != 2)
return(-1);
return(0);
}
程序15.9 4.3BSD之下的send_fd函数
为了接收一描述符(程序15.10),我们为cmsghdr结构和一描述符分配足够的存储
区,设置msg_control使其指向所分配到的存储区,然后调用recvmsg。
#include
#include /* struct msghdr */
#include /* struct iovec */
#include
#include "ourhdr.h"
static struct cmsghdr *cmptr = NULL; /* malloc'ed first time */
#define CONTROLLEN (sizeof(struct cmsghdr) + sizeof(int))
/* size of control buffer to send/recv one file descriptor */
/* Receive a file descriptor from another process (a server).
* In addition, any data received from the server is passed
* to (*userfunc)(STDERR_FILENO, buf, nbytes). We have a
* 2-byte protocol for receiving the fd from send_fd(). */
int
recv_fd(int servfd, ssize_t (*userfunc)(int, const void *, size_t))
{
int newfd, nread, status;
char *ptr, buf[MAXLINE];
struct iovec iov[1];
struct msghdr msg;
status = -1;
for ( ; ; ) {
iov[0].iov_base = buf;
iov[0].iov_len = sizeof(buf);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)
return(-1);
msg.msg_control = (caddr_t) cmptr;
msg.msg_controllen = CONTROLLEN;
if ( (nread = recvmsg(servfd, &msg, 0)) < 0)
err_sys("recvmsg error");
else if (nread == 0) {
err_ret("connection closed by server");
return(-1);
}
/* See if this is the final data with null & status.
Null must be next to last byte of buffer, status
byte is last byte. Zero status means there must
be a file descriptor to receive. */
for (ptr = buf; ptr < &buf[nread]; ) {
if (*ptr++ == 0) {
if (ptr != &buf[nread-1])
err_dump("message format error");
status = *ptr & 255;
if (status == 0) {
if (msg.msg_controllen != CONTROLLEN)
err_dump("status = 0 but no fd");
newfd = *(int *)CMSG_DATA(cmptr); /* new descriptor */
} else
newfd = -status;
nread -= 2;
}
}
if (nread > 0)
if ((*userfunc)(STDERR_FILENO, buf, nread) != nread)
return(-1);
if (status >= 0) /* final data has arrived */
return(newfd); /* descriptor, or -status */
}
}
程序15.10 4.3BSD之下的recv_fd函数
15.4 Open服务器,版本1
使用文件描述符传送技术,现在我们开发一个open服务器:它是一个可执行程
序,
由一个进程执行以打开一个或多个文件。该服务器不是将这种文件送回调用进程,
而是送回一个打开文件描述符。这使该服务器对任何类型的文件(例如调制解调器
线,或一网络连接)而不单是普通文件都能起作用。这也意味着,用IPC交换最小
量的信息--从客户到服务器传送文件名和打开方式,而从服务器到客户返回描述符
。文件内容则不需用IPC传送。
将服务器设计成一个单独的可执行程序有很多优点:
1. 任一客户都易于和服务器联系,这类似于客户调用一库函数。我们不需要将一
特定服务编码在应用程序中,而是设计一种可供重用的设施。
2. 如若需要更改服务器,那么也只影响一道程序。相反,更新一库函数可能要更
改调用此库函数的所有程序(用连编程序重新连接)。共享库函数可以简化这种更
新。
3. 服务器可以是设置__用户__ID程序,于是使其具有客户没有的附加许可权。注
意,一个库函数(或共享库函数)不能提供这种能力。
客户进程创建一流管道,然后调用fork和exec以调用服务器。客户经流管道发
送请求,服务器经管道回送响应。我们定义客户和服务器间的协议如下。
1. 客户经流管道向服务器发送下列形式的请求:
open o
是open函数的第二个参数,以十进制表示。该请求字符串以null字节结
尾。
2. 服务器调用send_fd 或send_err回送一打开描述符或一条出错消息。这是一个
进程向其父进程发送一打开描述符的实例。在15.6节,我们将修改此实例,其中使
用了一个精灵服务器,它将一个描述符发送给完全无关的进程。
程序15.11是头文件open.h,它包括标准系统头文件,并且定义了各个函数原
型。
#include
#include
。文件内容则不需用IPC传送。
将服务器设计成一个单独的可执行程序有很多优点:
1. 任一客户都易于和服务器联系,这类似于客户调用一库函数。我们不需要将一
特定服务编码在应用程序中,而是设计一种可供重用的设施。
2. 如若需要更改服务器,那么也只影响一道程序。相反,更新一库函数可能要更
改调用此库函数的所有程序(用连编程序重新连接)。共享库函数可以简化这种更
新。
3. 服务器可以是设置__用户__ID程序,于是使其具有客户没有的附加许可权。注
意,一个库函数(或共享库函数)不能提供这种能力。
客户进程创建一流管道,然后调用fork和exec以调用服务器。客户经流管道发
送请求,服务器经管道回送响应。我们定义客户和服务器间的协议如下。
1. 客户经流管道向服务器发送下列形式的请求:
open o
是open函数的第二个参数,以十进制表示。该请求字符串以null字节结
尾。
2. 服务器调用send_fd 或send_err回送一打开描述符或一条出错消息。这是一个
进程向其父进程发送一打开描述符的实例。在15.6节,我们将修改此实例,其中使
用了一个精灵服务器,它将一个描述符发送给完全无关的进程。
程序15.11是头文件open.h,它包括标准系统头文件,并且定义了各个函数原
型。
#include
#include
#include "ourhdr.h"
#define CL_OPEN "open" /* client's request for server */
/* our function prototypes */
int csopen(char *, int);
程序15.11 open.h头文件
程序15.12是main函数,其中包含一个循环,它先从标准输入读一个路径名,然后
将该文件复制至标准输出。它调用函数csopen以与open 服务器联系,从其返回一
打开描述符。
#include "open.h"
#include
#define BUFFSIZE 8192
int
main(int argc, char *argv[])
{
int n, fd;
char buf[BUFFSIZE], line[MAXLINE];
/* read filename to cat from stdin */
while (fgets(line, MAXLINE, stdin) != NULL) {
line[strlen(line) - 1] = 0; /* replace newline with null */
/* open the file */
if ( (fd = csopen(line, O_RDONLY)) < 0)
continue; /* csopen() prints error from server */
/* and cat to stdout */
while ( (n = read(fd, buf, BUFFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if (n < 0)
err_sys("read error");
close(fd);
}
exit(0);
}
程序15.12 main 函数
程序15.13是函数csopen,它先创建一流管道,然后进行服务器的fork和exec操作

#include "open.h"
#include /* struct iovec */
/* Open the file by sending the "name" and "oflag" to the
* connection server and reading a file descriptor back. */
int
csopen(char *name, int oflag)
{
pid_t pid;
int len;
char buf[10];
struct iovec iov[3];
static int fd[2] = { -1, -1 };
if (fd[0] < 0) { /* fork/exec our open server first time */
if (s_pipe(fd) < 0)
err_sys("s_pipe error");
if ( (pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) { /* child */
close(fd[0]);
if (fd[1] != STDIN_FILENO) {
if (dup2(fd[1], STDIN_FILENO) != STDIN_FILENO)
err_sys("dup2 error to stdin");
}
if (fd[1] != STDOUT_FILENO) {
if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO)
err_sys("dup2 error to stdout");
}
if (execl("./opend", "opend", NULL) < 0)
err_sys("execl error");
}
close(fd[1]); /* parent */
}
sprintf(buf, " %d", oflag); /* oflag to ascii */
iov[0].iov_base = CL_OPEN " ";
iov[0].iov_len = strlen(CL_OPEN) + 1;
iov[1].iov_base = name;
iov[1].iov_len = strlen(name);
iov[2].iov_base = buf;
iov[2].iov_len = strlen(buf) + 1; /* +1 for null at end of buf */
len = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len;
if (writev(fd[0], &iov[0], 3) != len)
err_sys("writev error");
/* read descriptor, returned errors handled by write() */
return( recv_fd(fd[0], write) );
}
程序15.13 csopen函数
子进程关闭管道的一端,父进程关闭另一端。子进程也为它所执行的服务器将管道
它使用的一端复制到其标准输入和标准输出0(另一种可选择的方案是将描述符fd
[1]的ASCII 表示形式作为一个参数传送给服务器。)
父进程将请求发送给服务器,请求中包含路径名和打开方式。最后,父进程调用r
ecv-fd以返回描述符或错误消息。如若服务器返回一错误消息则调用write,向标
准出错输出该消息。
现在,观察open服务器。其程序是opend,它由子进程执行(见程序15.13)。先观
察opend.h头文件(程序15.14),它包括了系统头文件,并且说明了全局变量和函
数原型。
#include
#include
#include "ourhdr.h"
#define CL_OPEN "open" /* client's request for server */
/* declare global variables */
extern char errmsg[]; /* error message string to return to client */
extern int oflag; /* open() flag: O_xxx ... */
extern char *pathname; /* of file to open() for client */
/* function prototypes */
int cli_args(int, char **);
void request(char *, int, int);
程序.15.14 opend.h头文件
#include "opend.h"
/* define global variables */
char errmsg[MAXLINE];
int oflag;
char *pathname;
int
main(void)
{
int nread;
char buf[MAXLINE];
for ( ; ; ) { /* read arg buffer from client, process request */
if ( (nread = read(STDIN_FILENO, buf, MAXLINE)) < 0)
err_sys("read error on stream pipe");
else if (nread == 0)
break; /* client has closed the stream pipe */
request(buf, nread, STDIN_FILENO);
}
exit(0);
}
程序15.15 main函数
#include "opend.h"
#include
void
request(char *buf, int nread, int fd)
{
int newfd;
if (buf[nread-1] != 0) {
sprintf(errmsg, "request not null terminated: %*.*s ",
nread, nread, buf);
send_err(STDOUT_FILENO, -1, errmsg);
return;
}
/* parse the arguments, set options */
if (buf_args(buf, cli_args) < 0) {
send_err(STDOUT_FILENO, -1, errmsg);
return;
}
if ( (newfd = open(pathname, oflag)) < 0) {
sprintf(errmsg, "can't open %s: %s ",
pathname, strerror(errno));
send_err(STDOUT_FILENO, -1, errmsg);
return;
}
/* send the descriptor */
if (send_fd(STDOUT_FILENO, newfd) < 0)
err_sys("send_fd error");
close(newfd); /* we're done with descriptor */
}
程序15.16 request函数
主函数(程序15.15)经流管道(它的标准输入)读来自客户的请求,然后调
用函数request。
程序15.16中的request 函数承担全部工作。它调用函数buf_args将客户请求分解
成标准argv型的参数表,然后调用函数cli_args以处理客户的参数。如若一切正常
,则调用open 以打开相应文件,接着调用send_fd,经由流管道(它的标准输出)
将描述符回送给客户。如若出错则调用send_err回送一则出错消息,其中使用我们
在前面说明了的客户/服务器协议。
#include "ourhdr.h"
#define MAXARGC 50 /* max number of arguments in buf */
#define WHITE " " /* white space for tokenizing arguments */
/* buf[] contains white-space separated arguments. We convert it
* to an argv[] style array of pointers, and call the user's
* function (*optfunc)() to process the argv[] array.
* We return -1 to the caller if there's a problem parsing buf,
* else we return whatever optfunc() returns. Note that user's
* buf[] array is modified (nulls placed after each token). */
int
buf_args(char *buf, int (*optfunc)(int, char **))
{
char *ptr, *argv[MAXARGC];
int argc;
if (strtok(buf, WHITE) == NULL) /* an argv[0] is required */
return(-1);
argv[argc = 0] = buf;
while ( (ptr = strtok(NULL, WHITE)) != NULL) {
if (++argc >= MAXARGC-1) /* -1 for room for NULL at end */
return(-1);
argv[argc] = ptr;
}
argv[++argc] = NULL;
return( (*optfunc)(argc, argv) );
/* Since argv[] pointers point into the user's buf[],
user's function can just copy the pointers, even
though argv[] array will disappear on return. */
}
程序15.17 buf_args函数
  buf_args调用的服务器函数是cli_args(程序15.18)。它验证客户发送的参
数数是否正确,然后将路径名和打开方式存放在全局变量中。
  这样也就完成了open服务器,它由客户执行fork和exec而调用。在fork之前创
建了一个流管道,然后客户和服务器用其进行通信。在这种安排下,每个客户都有
一服务器。
在下一节观察了客户一服务器连接后,我们将在15.6节重新实现一个open服务器,
其中用一个精灵进程作为服务器,所有客户都与其进行联系。
#include "opend.h"
/* This function is called by buf_args(), which is called by
* request(). buf_args() has broken up the client's buffer
* into an argv[] style array, which we now process. */
int
cli_args(int argc, char **argv)
{
if (argc != 3 || strcmp(argv[0], CL_OPEN) != 0) {
strcpy(errmsg, "usage: ");
return(-1);
}
pathname = argv[1]; /* save ptr to pathname to open */
oflag = atoi(argv[2]);
return(0);
}
程序15.18 cli_args函数
15.5 客户-服务器连接函数
对于相关进程(例如,父进程和子进程)之间的IPC,流管道是非常有用的。
前节所述的open服务器使用末命名的流管道能从子进程向父进程传送文件描述符。
但是当我们处理无关进程时(例如,若服务器是一精灵进程),则需要使用有名的
流管道。
我们可以先构造一末名流管道(用s_pipe 函数),然后对每一端加上一文件
系统路径名。一精灵服务器进程将只创建流管道的一端,并对该端加上一名字。这
样,无关的客户可以向服务者的流管道端发送消息,从而与精灵进程会聚。这类似
于图14.12中所示的情况,在该图中客户使用FIFO发送它们的请求。
一种更好的方法是:服务器创建一名字公开的流管道的一端,然后客户连接
_______________________________________________________________________
______
listenfd是serv_listen返回的描述符。在一客户连接到服务器的众所周知的
名字上之前,此函数并不返回。当客户连接至服务器时,自动创建一条全新的流管
道,其新描述符作为该函数的值返回。另外,客户的有效用户ID通过指针uidptr存
储。
客户为与一服务器连接只需调用cli_conn函数。
_______________________________________________________________________
______
#include "ourhdr,h"
int cli_conn(const char *name);
返回:若成功返回为文件描述符,出错<0
_______________________________________________________________________
______
客户指定的name应当与服务器调用serv_listen时宣布的相同。返回的描述符引用
连接至服务器的流管道
使用上述三个函数,就可编写服务器精灵进程,它可以管理任意数量的客户。唯一
的限制是单个进程可用的描述符数,服务器对于每一个客户连接都需要一个描述符
。因为这些函数处理的都是普通文件描述符,所以服务器使用SELECT或POLL就可在
所有客户间多路转接I/O请求。最后,因为客户一服务器连接都是流管道,所以可
以经由连接传送打开描述符。
在下面二节中,将说明在SVR4和4.3+BSD之下这三个函数的实现。在第十八章中当
我们开发一个通用的连接服务器时,也将使用这三个函数。
15.5.1 SVR4
SVR4提供装配的流以及一个名为connld的流处理模块,用其可以提供与服务器
有唯一连接的命名流管道。
装配流和connld模块是由Presotto和Ritchie[1990]为RESEARCH UNIX系统
开发
的,后来由SVR4采用。
首先,服务器创建一末名流管道,并将流处理模块connld压入一端。图15.5 显示
了这一处理结果。
图15.5 在一端压入connld模块后的流管道
然后,使压入connld的一端具有一路径名。SVR4提供fattach函数实现这一点。任
一进程(例如客户)打开此路径名就引用该管道的命名端。程序15.19使用了二十
余行代码实现serv_listen函数。
#include
#include
#include
#include "ourhdr.h"
#define FIFO_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
/* user rw, group rw, others rw */
int /* returns fd if all OK, <0 on error */
serv_listen(const char *name)
{
int tempfd, fd[2], len;
/* create a file: mount point for fattach() */
unlink(name);
if ( (tempfd = creat(name, FIFO_MODE)) < 0)
return(-1);
if (close(tempfd) < 0)
return(-2);
if (pipe(fd) < 0)
return(-3);
/* push connld & fattach() on fd[1] */
if (ioctl(fd[1], I_PUSH, "connld") < 0)
return(-4);
if (fattach(fd[1], name) < 0)
return(-5);
return(fd[0]); /* fd[0] is where client connections arrive */
}
程序15.19 SVR4之下的serv_listen函数
当另一进程对管道的命名端(connld模块压入端)调用open时,发生下列处
理过程:
1. 创建一个新管道。
2. 该新管道的一个描述符作为open的返回值回送给客户。  
3. 另一个描述符在命名管道的另一端(亦即不是压入connld的端)传送给服务器  
。服务器以带I-RECVED命令的ioctl接受该新描述符。  
      假定,服务器用fattach函数加到其管道的众所周知是/tmp/serv1.图15.6显  
示了客户调用  
      fd=open("/tmp/serv1",O_RDWR);  
并返回后产生的结果。  
图15.6 客户-服务器在一命名管道上的连接  
在客户和服务器之间的管道是open创建的,被打开的路径名实际上是一命名管道,  
其中压入了connld模块。客户得到由open返回的文件描述符fd。在服务器处的新文  
件描述符是clifdl,它是由服务器在描述符fd[1]上以I_RECVFD命令调用ioctl而接  
收到的。一旦服务器在fd[1]上压入了connld模块,并对fd[1]附接上一个名字,它  
就不再使用fd[1]。  
服务器调用程序15.20中的serv_accept函数等待客户连接到达。  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <stropts.h>  
#include "ourhdr.h"  
/* Wait for a client connection to arrive, and accept it.  
 * We also obtain the client's user ID. */  
int   /* returns new fd if all OK, -1 on error */  
serv_accept(int listenfd, uid_t *uidptr)  
{  
 struct strrecvfd recvfd;  
 if (ioctl(listenfd, I_RECVFD, &recvfd) < 0)  
  return(-1);  /* could be EINTR if signal caught */  
 if (uidptr != NULL)  
  *uidptr = recvfd.uid; /* effective uid of caller */  
 return(recvfd.fd); /* return the new descriptor */  
}  
程序15.20 用于SVR4的serv_accept函数  
    在图15.6中,serv_accept的第一个参数应当是描述符fd[0],serv_accept的  
返回值是描述符clifdl。  
客户调用程序15.21中的cli_conn函数起动对服务器的连接。  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include "ourhdr.h"  
/* Create a client endpoint and connect to a server. */  
int   /* returns fd if all OK, <0 on error */  
cli_conn(const char *name)  
{  
 int  fd;  
    /* open the mounted stream */  
 if ( (fd = open(name, O_RDWR)) < 0)  
  return(-1);  
 if (isastream(fd) == 0)  
  return(-2);  
 return(fd);  
}  
程序15.21 对于SVR4的cli_conn函数  
我们对返回的描述符是否引用一个流设备进行了两次检查,以便处理服务器没有起  
动,但该路径名却存在于文件系统中的情况。(在SVR4下,几乎没有什么理由去调  
用cli_conn,而不是直接调用open。在下一节我们将看到,在BSD系统之下,cli_  
conn函数要复杂得多,因此编写cli_conn函数就是必要的了。)  
15.5.2 4.3+BSD  
在4.3+BSD之下,为了用UNIX域套接口连接客户和服务器,我们需要有一套不同的  
操作函数。因为应用socket、bind、listen、accept和connect函数的大部分细节  
与其它网络协议有关(参见Stevens[1990]),所以此处不详细展开。  
        因为SVR4也支持UNIX域套接口,所以本节所示代码同样可在SVR4之下工作  
。  
    程序15.22包含了serv_listen函数。它是服务器调用的第一个函数  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/un.h>  
#include "ourhdr.h"  
/* Create a server endpoint of a connection. */  
int   /* returns fd if all OK, <0 on error */  
serv_listen(const char *name)  
{  
 int     fd, len;  
 struct sockaddr_un unix_addr;  
     /* create a Unix domain stream socket */  
 if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)  
  return(-1);  
 unlink(name); /* in case it already exists */  
     /* fill in socket address structure */  
 memset(&unix_addr, 0, sizeof(unix_addr));  
 unix_addr.sun_family = AF_UNIX;  
 strcpy(unix_addr.sun_path, name);  
#ifdef SCM_RIGHTS /* 4.3BSD Reno and later */  
 len = sizeof(unix_addr.sun_len) + sizeof(unix_addr.sun_family) +  
    strlen(unix_addr.sun_path) + 1;  
 unix_addr.sun_len = len;  
#else    /* vanilla 4.3BSD */  
 len = strlen(unix_addr.sun_path) + sizeof(unix_addr.sun_family);  
#endif  
     /* bind the name to the descriptor */  
 if (bind(fd, (struct sockaddr *) &unix_addr, len) < 0)  
  return(-2);  
 if (listen(fd, 5) < 0) /* tell kernel we're a server */  
  return(-3);  
 return(fd);  
}  
程序15.22 用于4.3+BSD的serv_listen函数  
    首先,调用socket函数创建一个UNIX域套接口。然后,填充sockeraddr_un结  
构,将一个众所周知的路径名赋与该套接口。该结构是调用bind函数的一个参数。  
然后调用listen以通知核心:本服务器正等待来自客户的连接。(listen的第二个  
参数是5,它是最大的未决连接请求数,核心将这些请求对该描述符进行排队。大  
多数实现强制该值的上限为5。)  
客户调用cli_conn函数(程序15.23)起动与服务器的连接。  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include "ourhdr.h"  
/* Create a client endpoint and connect to a server. */  
int   /* returns fd if all OK, <0 on error */  
cli_conn(const char *name)  
{  
 int  fd;  
    /* open the mounted stream */  
 if ( (fd = open(name, O_RDWR)) < 0)  
  return(-1);  
 if (isastream(fd) == 0)  
  return(-2);  
 return(fd);  
}  
程序15.23 用于4.3+BSD的cli_conn函数  
我们调用socket函数以创建客户端的UNIX域套接口,然后客户专用的名字填入soc  
ketaddr_un结构。该路径名的最后5个字符是客户的进程ID。(我们可以查证此结  
构的长度是14个字符,以避免UNIX域套接口早期实现的某些错误。)在路径名已经  
存在的情况下unlink,然后再调用bind将一名字赋与客户的套接口,这就创建了在  
文件系统中的路径名,该文件的类型是套接口。接着调用chmod,它关闭除user_r  
ead,user_write和user_execute以外的存取权。在serv_accept中,服务器检查该  
套接口的这些许可权和用户ID,以验证用户的身份。  
    然后,我们应当以服务器众所周知的路径名填充另一个socketaddr_un结构。  
最后,connect函数起动与服务器的连接。  
创建每个客户与服务器的唯一连接是在serv_accept函数中调用accept函数实现的  
(程序15.24)。  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <stropts.h>  
#include "ourhdr.h"  
/* Wait for a client connection to arrive, and accept it.  
 * We also obtain the client's user ID. */  
int   /* returns new fd if all OK, -1 on error */  
serv_accept(int listenfd, uid_t *uidptr)  
{  
 struct strrecvfd recvfd;  
 if (ioctl(listenfd, I_RECVFD, &recvfd) < 0)  
  return(-1);  /* could be EINTR if signal caught */  
 if (uidptr != NULL)  
  *uidptr = recvfd.uid; /* effective uid of caller */  
 return(recvfd.fd); /* return the new descriptor */  
}  
程序15.24 用于4.3+BSD的serv_accept函数  
     服务器在调用accept中堵塞以等待一客户调用cli_conn。当accept返回时,  
其返回值是连向客户的全新的描述符。(这类似于SVR4中,connld模块所做的。)  
另外,accept也通过其第二个参数(指向socketaddr_un结构的指针)返回客户赋  
与其套接口的路径名(它包含客户的进程ID)。用NULL字节结束此路径名,然后调  
用stat。这使我们可以验证此路径名确实是一个套接口,其许可权user_read,us  
er_write和user_execute。我们也验证与该套接口相关的三个时间不超过30秒。(  
time函数返回自UNIX纪元经过的时间和日期,它们都以秒计。)如若所有这些检查  
都通过我们就认为该客户的身份(其有效用户ID)是该套接口的属主。虽然这种检  
查并不完善,但却是现有系统所能做得最好的。(如果核心能象SVR4 I_RECVFD做  
的那样,将有效用户ID返回给accept,那就更好一些。)  
图15.7显示了cli_conn调用返回后的这种连接,我们假定服务器的众所周知名字是  
/tmp/servi。请将此与图15.6相比较。  
图15.7 在UNIX域套接口上客户-服务器连接  
15.6 OPEN服务器,版本2  
    在15.4节,客户调用fork和exec构造了一个Open服务器,它说明了如何从子程  
序向父程序传送文件描述符。在本节开发一个精灵进程样式的OPEN服务器。一个服  
务器处理所有客户的请求。我们期望,由于避免使用了fork和exec,所以这一设计  
会是更有效的。在客户和服务器之间仍将使用上一节说明的三个函数:serv_list  
en、serv_accept和cli_conn。这一服务器将表明:一个服务器可以处理多个客户  
,为此使用的技术是12.5节中说明的select和poll函数。  
    本节所述的客户类似于15.4节中的客户。确实,文件main.c是完全相同的(程  
序15.12)。在open.h头文件(程序15.11)中则加了下面1行:  
           #define  CS_OPEN  "/home/stevens/open"   /*服务器的众所周知名  
字*/  
因为在这里调用的是cli_conn而非fork和exec,所以文件open.c与程序15.13完全  
不同。这示于程序15.25。  
#include "open.h"  
#include <sys/uio.h>  /* struct iovec */  
/* Open the file by sending the "name" and "oflag" to the  
 * connection server and reading a file descriptor back. */  
int  
csopen(char *name, int oflag)  
{  
 int    len;  
 char   buf[10];  
 struct iovec iov[3];  
 static int  csfd = -1;  
 if (csfd < 0) {  /* open connection to conn server */  
  if ( (csfd = cli_conn(CS_OPEN)) < 0)  
   err_sys("cli_conn error");  
 }  
 sprintf(buf, " %d", oflag);  /* oflag to ascii */  
 iov[0].iov_base = CL_OPEN " ";  
 iov[0].iov_len  = strlen(CL_OPEN) + 1;  
 iov[1].iov_base = name;  
 iov[1].iov_len  = strlen(name);  
 iov[2].iov_base = buf;  
 iov[2].iov_len  = strlen(buf) + 1;  
       /* null at end of buf always sent */  
 len = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len;  
 if (writev(csfd, &iov[0], 3) != len)  
  err_sys("writev error");  
     /* read back descriptor */  
     /* returned errors handled by write() */  
 return( recv_fd(csfd, write) );  
}  
程序15.25  csopen函数  
    从客户到服务器之间使用的协议仍然相同。  
    让我们先查看服务器。头文件open.h(程序15.26)包括了标准头文件,并且  
说明了全局变量和函数原型。  
因为此服务器处理所有客户,所以它必须保存每个客户连接的状态。这是用定义在  
opend.h头文件中的client数组实现的。程序15.27定义了三个处理此数组的函数。  
   
#include <sys/types.h>  
#include <errno.h>  
#include "ourhdr.h"  
#define CS_OPEN "/home/stevens/opend" /* well-known name */  
#define CL_OPEN "open"      /* client's request for server */  
   /* declare global variables */  
extern int  debug;  /* nonzero if interactive (not daemon) */  
extern char  errmsg[]; /* error message string to return to client */  
extern int  oflag;  /* open flag: O_xxx ... */  
extern char *pathname; /* of file to open for client */  
typedef struct { /* one Client struct per connected client */  
  int fd;  /* fd, or -1 if available */  
  uid_t uid;  
} Client;  
extern Client *client;  /* ptr to malloc'ed array */  
extern int   client_size; /* # entries in client[] array */  
     /* (both manipulated by client_XXX() functions) */  
   /* function prototypes */  
int   cli_args(int, char **);  
int   client_add(int, uid_t);  
void  client_del(int);  
void  loop(void);  
void  request(char *, int, int, uid_t);  
程序15.26    open.h头文件  
#include "opend.h"  
#define NALLOC 10  /* #Client structs to alloc/realloc for */  
static void  
client_alloc(void)  /* alloc more entries in the client[] array */  
{  
 int  i;  
 if (client == NULL)  
  client = malloc(NALLOC * sizeof(Client));  
 else  
  client = realloc(client, (client_size + NALLOC) * sizeof(Client));  
 if (client == NULL)  
  err_sys("can't alloc for client array");  
   /* have to initialize the new entries */  
 for (i = client_size; i < client_size + NALLOC; i++)  
  client[i].fd = -1; /* fd of -1 means entry available */  
 client_size += NALLOC;  
}  
/* Called by loop() when connection request from a new client arrives *  
/  
int  
client_add(int fd, uid_t uid)  
{  
 int  i;  
 if (client == NULL)  /* first time we're called */  
  client_alloc();  
again:  
 for (i = 0; i < client_size; i++) {  
  if (client[i].fd == -1) { /* find an available entry */  
   client[i].fd = fd;  
   client[i].uid = uid;  
   return(i); /* return index in client[] array */  
  }  
 }  
   /* client array full, time to realloc for more */  
 client_alloc();  
 goto again;  /* and search again (will work this time) */  
}  
/* Called by loop() when we're done with a client */  
void  
client_del(int fd)  
{  
 int  i;  
 for (i = 0; i < client_size; i++) {  
  if (client[i].fd == fd) {  
   client[i].fd = -1;  
   return;  
  }  
 }  
 log_quit("can't find client entry for fd %d", fd);  
}  
程序15.27    处理client数组的三个函数  
    第一次调用client_add时,它调用client_alloc、client_alloc又调用mallo  
c为该数组的10个登记项分配空间。在这十个登记项全部用完后,再调用client_a  
dd,然后是realloc以分配附加空间。依靠这种动态空间分配,我们没有在编译时  
限制client数组的长度。  
 如若出错,那么因为假定服务器是精灵进程,所以这些函数调用log_函数(见附  
录B)。  
    main函数(程序15.28)定义全局变量,处理命令行选择项,然后调用loop函  
数。如若以一d选择项调用服务器,则它以交互方式运行而非精灵进程。当测试些  
服务器时,使用交互运行方式。  
#include "opend.h"  
#include <syslog.h>  
   /* define global variables */  
int   debug;  
char  errmsg[MAXLINE];  
int   oflag;  
char *pathname;  
Client *client = NULL;  
int   client_size;  
int  
main(int argc, char *argv[])  
{  
 int  c;  
 log_open("open.serv", LOG_PID, LOG_USER);  
 opterr = 0;  /* don't want getopt() writing to stderr */  
 while ( (c = getopt(argc, argv, "d")) != EOF) {  
  switch (c) {  
  case 'd':  /* debug */  
   debug = 1;  
   break;  
  case '?':  
   err_quit("unrecognized option: -%c", optopt);  
  }  
 }  
 if (debug == 0)  
  daemon_init();  
 loop();  /* never returns */  
}  
程序15.28  main函数  
loop函数是服务器的无限循环。我们将示出该函数的两种版本。程序15.29是使用  
select的一种版本。(在4.3+BSD和SVR4之下工作),程序15.30是使用poll(用于  
SVR4)的另一种版本。  
#include "opend.h"  
#include <sys/time.h>  
void  
loop(void)  
{  
 int  i, n, maxfd, maxi, listenfd, clifd, nread;  
 char buf[MAXLINE];  
 uid_t uid;  
 fd_set rset, allset;  
 FD_ZERO(&allset);  
    /* obtain fd to listen for client requests on */  
 if ( (listenfd = serv_listen(CS_OPEN)) < 0)  
  log_sys("serv_listen error");  
 FD_SET(listenfd, &allset);  
 maxfd = listenfd;  
 maxi = -1;  
 for ( ; ; ) {  
  rset = allset;  /* rset gets modified each time around */  
  if ( (n = select(maxfd + 1, &rset, NULL, NULL, NULL)) < 0)  
   log_sys("select error");  
  if (FD_ISSET(listenfd, &rset)) {  
     /* accept new client request */  
   if ( (clifd = serv_accept(listenfd, &

相关阅读 更多 +
排行榜 更多 +
毒药轮盘手机版下载

毒药轮盘手机版下载

休闲益智 下载
剑侠情缘零b服手游下载

剑侠情缘零b服手游下载

角色扮演 下载
惊魂动物园游戏手机版下载

惊魂动物园游戏手机版下载

冒险解谜 下载