📄 15.htm
字号:
<p>return(-3); </p>
<p>return(fd); </p>
<p>} </p>
<p>程序15.22 用于4.3+BSD的serv_listen函数 </p>
<p>首先,调用socket函数创建一个UNIX域套接口。然后,填充sockeraddr_un结
</p>
<p>构,将一个众所周知的路径名赋与该套接口。该结构是调用bind函数的一个参数。
</p>
<p>然后调用listen以通知核心:本服务器正等待来自客户的连接。(listen的第二个
</p>
<p>参数是5,它是最大的未决连接请求数,核心将这些请求对该描述符进行排队。大
</p>
<p>多数实现强制该值的上限为5。) </p>
<p>客户调用cli_conn函数(程序15.23)起动与服务器的连接。 </p>
<p>#include <sys/types.h> </p>
<p>#include <sys/stat.h> </p>
<p>#include <fcntl.h> </p>
<p>#include "ourhdr.h" </p>
<p>/* Create a client endpoint and connect to a server. */ </p>
<p>/* Create a client endpoint and connect to a server. */ </p>
<p>int /* returns fd if all OK, <0 on error */ </p>
<p>cli_conn(const char *name) </p>
<p>{ </p>
<p>int fd; </p>
<p>/* open the mounted stream */ </p>
<p>if ( (fd = open(name, O_RDWR)) < 0) </p>
<p>return(-1); </p>
<p>if (isastream(fd) == 0) </p>
<p>return(-2); </p>
<p>return(fd); </p>
<p>} </p>
<p>程序15.23 用于4.3+BSD的cli_conn函数 </p>
<p>我们调用socket函数以创建客户端的UNIX域套接口,然后客户专用的名字填入soc
</p>
<p>ketaddr_un结构。该路径名的最后5个字符是客户的进程ID。(我们可以查证此结
</p>
<p>构的长度是14个字符,以避免UNIX域套接口早期实现的某些错误。)在路径名已经
</p>
<p>存在的情况下unlink,然后再调用bind将一名字赋与客户的套接口,这就创建了在
</p>
<p>文件系统中的路径名,该文件的类型是套接口。接着调用chmod,它关闭除user_r
</p>
<p>ead,user_write和user_execute以外的存取权。在serv_accept中,服务器检查该
</p>
<p>套接口的这些许可权和用户ID,以验证用户的身份。 </p>
<p>然后,我们应当以服务器众所周知的路径名填充另一个socketaddr_un结构。
</p>
<p>最后,connect函数起动与服务器的连接。 </p>
<p>创建每个客户与服务器的唯一连接是在serv_accept函数中调用accept函数实现的
</p>
<p>(程序15.24)。 </p>
<p>#include <sys/types.h> </p>
<p>#include <sys/stat.h> </p>
<p>#include <stropts.h> </p>
<p>#include "ourhdr.h" </p>
<p>/* Wait for a client connection to arrive, and accept it. </p>
<p>* We also obtain the client's user ID. */ </p>
<p>int /* returns new fd if all OK, -1 on error */ </p>
<p>serv_accept(int listenfd, uid_t *uidptr) </p>
<p>{ </p>
<p>struct strrecvfd recvfd; </p>
<p>if (ioctl(listenfd, I_RECVFD, &recvfd) < 0) </p>
<p>return(-1); /* could be EINTR if signal caught */ </p>
<p>if (uidptr != NULL) </p>
<p>*uidptr = recvfd.uid; /* effective uid of caller */ </p>
<p>return(recvfd.fd); /* return the new descriptor */ </p>
<p>} </p>
<p>程序15.24 用于4.3+BSD的serv_accept函数 </p>
<p>服务器在调用accept中堵塞以等待一客户调用cli_conn。当accept返回时,
</p>
<p>其返回值是连向客户的全新的描述符。(这类似于SVR4中,connld模块所做的。)
</p>
<p>另外,accept也通过其第二个参数(指向socketaddr_un结构的指针)返回客户赋
</p>
<p>与其套接口的路径名(它包含客户的进程ID)。用NULL字节结束此路径名,然后调
</p>
<p>用stat。这使我们可以验证此路径名确实是一个套接口,其许可权user_read,us
</p>
<p>er_write和user_execute。我们也验证与该套接口相关的三个时间不超过30秒。(
</p>
<p>time函数返回自UNIX纪元经过的时间和日期,它们都以秒计。)如若所有这些检查
</p>
<p>都通过我们就认为该客户的身份(其有效用户ID)是该套接口的属主。虽然这种检
</p>
<p>查并不完善,但却是现有系统所能做得最好的。(如果核心能象SVR4
I_RECVFD做 </p>
<p>的那样,将有效用户ID返回给accept,那就更好一些。) </p>
<p>图15.7显示了cli_conn调用返回后的这种连接,我们假定服务器的众所周知名字是
</p>
<p>/tmp/servi。请将此与图15.6相比较。 </p>
<p>图15.7 在UNIX域套接口上客户-服务器连接 </p>
<p>15.6 OPEN服务器,版本2 </p>
<p>在15.4节,客户调用fork和exec构造了一个Open服务器,它说明了如何从子程
</p>
<p>序向父程序传送文件描述符。在本节开发一个精灵进程样式的OPEN服务器。一个服
</p>
<p>务器处理所有客户的请求。我们期望,由于避免使用了fork和exec,所以这一设计
</p>
<p>会是更有效的。在客户和服务器之间仍将使用上一节说明的三个函数:serv_list
</p>
<p>en、serv_accept和cli_conn。这一服务器将表明:一个服务器可以处理多个客户
</p>
<p>,为此使用的技术是12.5节中说明的select和poll函数。 </p>
<p>本节所述的客户类似于15.4节中的客户。确实,文件main.c是完全相同的(程
</p>
<p>序15.12)。在open.h头文件(程序15.11)中则加了下面1行: </p>
<p>#define CS_OPEN "/home/stevens/open" /*服务器的众所周知名 </p>
<p>字*/ </p>
<p>因为在这里调用的是cli_conn而非fork和exec,所以文件open.c与程序15.13完全
</p>
<p>不同。这示于程序15.25。 </p>
<p>#include "open.h" </p>
<p>#include <sys/uio.h> /* struct iovec */ </p>
<p>/* Open the file by sending the "name" and "oflag" to the </p>
<p>* connection server and reading a file descriptor back. */ </p>
<p>int </p>
<p>csopen(char *name, int oflag) </p>
<p>{ </p>
<p>int len; </p>
<p>char buf[10]; </p>
<p>struct iovec iov[3]; </p>
<p>static int csfd = -1; </p>
<p>if (csfd < 0) { /* open connection to conn server */ </p>
<p>if ( (csfd = cli_conn(CS_OPEN)) < 0) </p>
<p>err_sys("cli_conn error"); </p>
<p>} </p>
<p>sprintf(buf, " %d", oflag); /* oflag to ascii */ </p>
<p>iov[0].iov_base = CL_OPEN " "; </p>
<p>iov[0].iov_len = strlen(CL_OPEN) + 1; </p>
<p>iov[1].iov_base = name; </p>
<p>iov[1].iov_len = strlen(name); </p>
<p>iov[2].iov_base = buf; </p>
<p>iov[2].iov_len = strlen(buf) + 1; </p>
<p>/* null at end of buf always sent */ </p>
<p>len = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len; </p>
<p>if (writev(csfd, &iov[0], 3) != len) </p>
<p>err_sys("writev error"); </p>
<p>/* read back descriptor */ </p>
<p>/* returned errors handled by write() */ </p>
<p>return( recv_fd(csfd, write) ); </p>
<p>} </p>
<p>程序15.25 csopen函数 </p>
<p>从客户到服务器之间使用的协议仍然相同。 </p>
<p>让我们先查看服务器。头文件open.h(程序15.26)包括了标准头文件,并且
</p>
<p>说明了全局变量和函数原型。 </p>
<p>因为此服务器处理所有客户,所以它必须保存每个客户连接的状态。这是用定义在
</p>
<p>opend.h头文件中的client数组实现的。程序15.27定义了三个处理此数组的函数。
</p>
<p> </p>
<p>#include <sys/types.h> </p>
<p>#include <errno.h> </p>
<p>#include "ourhdr.h" </p>
<p>#define CS_OPEN "/home/stevens/opend" /* well-known name */ </p>
<p>#define CL_OPEN "open" /* client's request for server */ </p>
<p>/* declare global variables */ </p>
<p>extern int debug; /* nonzero if interactive (not daemon) */ </p>
<p>extern char errmsg[]; /* error message string to return to client */ </p>
<p>extern int oflag; /* open flag: O_xxx ... */ </p>
<p>extern char *pathname; /* of file to open for client */ </p>
<p>typedef struct { /* one Client struct per connected client */ </p>
<p>int fd; /* fd, or -1 if available */ </p>
<p>uid_t uid; </p>
<p>} Client; </p>
<p>extern Client *client; /* ptr to malloc'ed array */ </p>
<p>extern int client_size; /* # entries in client[] array */ </p>
<p>/* (both manipulated by client_XXX() functions) */ </p>
<p>/* function prototypes */ </p>
<p>int cli_args(int, char **); </p>
<p>int client_add(int, uid_t); </p>
<p>void client_del(int); </p>
<p>void loop(void); </p>
<p>void request(char *, int, int, uid_t); </p>
<p>程序15.26 open.h头文件 </p>
<p>#include "opend.h" </p>
<p>#define NALLOC 10 /* #Client structs to alloc/realloc for */ </p>
<p>static void </p>
<p>client_alloc(void) /* alloc more entries in the client[] array */ </p>
<p>{ </p>
<p>int i; </p>
<p>if (client == NULL) </p>
<p>client = malloc(NALLOC * sizeof(Client)); </p>
<p>else </p>
<p>client = realloc(client, (client_size + NALLOC) * sizeof(Client)); </p>
<p>if (client == NULL) </p>
<p>err_sys("can't alloc for client array"); </p>
<p>/* have to initialize the new entries */ </p>
<p>for (i = client_size; i < client_size + NALLOC; i++) </p>
<p>client[i].fd = -1; /* fd of -1 means entry available */ </p>
<p>client_size += NALLOC; </p>
<p>} </p>
<p>/* Called by loop() when connection request from a new client arrives * </p>
<p>/ </p>
<p>int </p>
<p>client_add(int fd, uid_t uid) </p>
<p>{ </p>
<p>int i; </p>
<p>if (client == NULL) /* first time we're called */ </p>
<p>client_alloc(); </p>
<p>again: </p>
<p>for (i = 0; i < client_size; i++) { </p>
<p>if (client[i].fd == -1) { /* find an available entry */ </p>
<p>client[i].fd = fd; </p>
<p>client[i].uid = uid; </p>
<p>return(i); /* return index in client[] array */ </p>
<p>} </p>
<p>} </p>
<p>/* client array full, time to realloc for more */ </p>
<p>client_alloc(); </p>
<p>goto again; /* and search again (will work this time) */ </p>
<p>} </p>
<p>/* Called by loop() when we're done with a client */ </p>
<p>void </p>
<p>client_del(int fd) </p>
<p>{ </p>
<p>int i; </p>
<p>for (i = 0; i < client_size; i++) { </p>
<p>if (client[i].fd == fd) { </p>
<p>client[i].fd = -1; </p>
<p>return; </p>
<p>} </p>
<p>} </p>
<p>log_quit("can't find client entry for fd %d", fd); </p>
<p>} </p>
<p>程序15.27 处理client数组的三个函数 </p>
<p>第一次调用client_add时,它调用client_alloc、client_alloc又调用mallo </p>
<p>c为该数组的10个登记项分配空间。在这十个登记项全部用完后,再调用client_a
</p>
<p>dd,然后是realloc以分配附加空间。依靠这种动态空间分配,我们没有在编译时
</p>
<p>限制client数组的长度。 </p>
<p>如若出错,那么因为假定服务器是精灵进程,所以这些函数调用log_函数(见附
</p>
<p>录B)。 </p>
<p>main函数(程序15.28)定义全局变量,处理命令行选择项,然后调用loop函
</p>
<p>数。如若以一d选择项调用服务器,则它以交互方式运行而非精灵进程。当测试些
</p>
<p>服务器时,使用交互运行方式。 </p>
<p>#include "opend.h" </p>
<p>#include <syslog.h> </p>
<p>/* define global variables */ </p>
<p>int debug; </p>
<p>char errmsg[MAXLINE]; </p>
<p>int oflag; </p>
<p>char *pathname; </p>
<p>Client *client = NULL; </p>
<p>int client_size; </p>
<p>int </p>
<p>main(int argc, char *argv[]) </p>
<p>{ </p>
<p>int c; </p>
<p>log_open("open.serv", LOG_PID, LOG_USER); </p>
<p>opterr = 0; /* don't want getopt() writing to stderr */ </p>
<p>while ( (c = getopt(argc, argv, "d")) != EOF) { </p>
<p>switch (c) { </p>
<p>case 'd': /* debug */ </p>
<p>debug = 1; </p>
<p>break; </p>
<p>case '?': </p>
<p>err_quit("unrecognized option: -%c", optopt); </p>
<p>} </p>
<p>} </p>
<p>if (debug == 0) </p>
<p>daemon_init(); </p>
<p>loop(); /* never returns */ </p>
<p>} </p>
<p>程序15.28 main函数 </p>
<p>loop函数是服务器的无限循环。我们将示出该函数的两种版本。程序15.29是使用
</p>
<p>select的一种版本。(在4.3+BSD和SVR4之下工作),程序15.30是使用poll(用于
</p>
<p>SVR4)的另一种版本。 </p>
<p>#include "opend.h" </p>
<p>#include <sys/time.h> </p>
<p>void </p>
<p>loop(void) </p>
<p>{ </p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -