使用文件描述符的传递,我们现在开发一个打开服务器:一个被一个进程执行来打开一个或多个文件的程序。但是服务器并非把文件内容发送回给调用进程,
而是把一个打开文件的描述符发送回去。这让服务器可以和任何类型的文件一起工作(比如一个设备或一个套接字)而不是简单的普通文件。它也意味着使用IPC
交换最少的信息:客户向服务器文件名和打开模式,服务器向客户返回描述符。文件的内容没有使用IPC交换。
把服务器设计为独立的应用程序(或者是被客户执行的程序,如我们在本节开发的,或者是一个我们在下节开发的守护进程)有几个好处。
1、服务器可以简单地被任何客户联系,和客户服用一个库函数相似。我们不在应用里硬编码一个特定的服务,而是设计其它人可以使用的通用设施。
2、如果我们需要改变服务器,只有一个程序被影响。相比之下,更新一个库函数可能要求所有调用这个函数的程序都要被更新(也就是说,用链接器重链接)。共享库可以简化这个更新(7.7节)。
3、服务器可以是一个设置用户ID程序,提供它一个客户没有的额外权限。注意一个库函数(或共享库函数)不能提供这种能力。
客户进程创建一个s-pipe(一个基于STREAMS的管道或一个UNIX域套接字对)然后调用fork和exec来调用这个服务器。客户通过s-pipe发送请求,服务器通过s-pipe发回响应。
1、客户通过s-pipe向服务器发送一个形式为“open \0”的请求。是以ASCII十进制形式的open函数的第二个参数的数值。这个请求由一个空字节终止。
2、服务器发送回一个打开的描述符或一个错误,通过调用send_fd或send_err。
这是一个进程发送给它的父进程一个打开描述符的一个例子。在17.6节,我们将修改这个例子来使用单个守护服务器,这里服务器把一个描述符发送给一个完全无关的进程。
我们首先有头文件,open.h(下面的代码),它包含标准头文件并定义函数原型。
- #include <errno.h>
- #define CL_OPEN "open" /* client's request for server */
- int csopen(char *, int);
main函数(下面的代码)是一个循环,从标准输出读取输入并把文件拷贝到标准输出。这个函数调用csopen来联系打开的服务器并返回一个打开的描述符。
- #include <fcntl.h>
- #include <stdio.h>
- #include <unistd.h>
- #define BUFFSIZE 8192
- #define MAXLINE 4096
- 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) {
- if (line[strlen(line) - 1] == '\n')
- 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) {
- printf("write error\n");
- exit(1);
- }
- if (n < 0) {
- printf("read error\n");
- exit(1);
- }
- close(fd);
- }
- exit(0);
- }
函数csopen(下面的代码)执行服务器的fork和exec,在创建s-pipe之后。
- #include "open.h"
- #include <sys/uio.h> /* struct iovec */
- #include <fcntl.h>
- #include <unistd.h>
- /*
- * 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) {
- printf("s_pipe error\n");
- exit(1);
- }
- if ((pid = fork()) < 0) {
- printf("fork error\n");
- exit(1);
- } else if (pid == 0) { /* child */
- close(fd[0]);
- if (fd[1] != STDIN_FILENO &&
- dup2(fd[1], STDIN_FILENO) != STDIN_FILENO) {
- printf("dup2 error to stdin\n");
- exit(1);
- }
- if (fd[1] != STDOUT_FILENO &&
- dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) {
- printf("dup2 error to stdout\n");
- exit(1);
- }
- if (execl("./opend", "opend", (char *)0) < 0) {
- printf("execl error\n");
- exit(1);
- }
- }
- close(fd[1]); /* parent */
- }
- sprintf(buf, " %d", oflag); /* oflag to ascii */
- iov[0].iov_base = CL_OPEN " "; /* string concatenation */
- 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) {
- printf("writev error\n");
- exit(1);
- }
- /* read descriptor, returned errors handled by write() */
- return(recv_fd(fd[0], write));
- }
子进程关闭了管道的一端,父进程关掉另一端。对于它执行的服务器,子进程也复制管道的它的那端到它的标准输入和标准输出上。(另一个选项会是把描述符fd[1]的ASCII表示作为服务器的一个参数。)
父进程发送给服务包含路径名和打开模式的请求。最后,父进程调用recv_fd来返回描述符或个错误。如果一个错误由服务器返回,write被调用来向标准错误输出。
现在让我们看下打开服务器。它是上面代码里客户执行的程序opend。首先,我们有opend.h(下面的代码),它包含标准头文件并声明全局变量和函数原型。
- #include <errno.h>
- #define CL_OPEN "open" /* client's request for server */
- 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 */
- int cli_args(int, char **);
- void request(char *, int, int);
main函数(下面的代码)在s-pipe(它的标准输入)上从客户端读取请求并调用函数request。
- #include "opend.h"
- #include <unistd.h>
- #define BUFFSIZE 8192
- #define MAXLINE 4096
- char errmsg[MAXLINE];
- int oflag;
- char *pathname;
- int
- main(void)
- {
- int nread;
- char buf[BUFFSIZE];
-
- for ( ; ; ) { /* read arg buffer from client, process request */
- if ((nread = read(STDIN_FILENO, buf, MAXLINE)) < 0) {
- printf("read error on stream pipe\n");
- exit(1);
- } else if (nread == 0)
- break; /* client has closed the stream pipe */
- request(buf, nread, STDOUT_FILENO);
- }
- exit(0);
- }
下面代码的request函数做了所有的工作。它调用函数buf_args来把客户的请求分解为argv风格的参数列表并调用函数
cli_args来处理客户的参数。如果所有的事情都成功,那么open被调用来打开文件,然后send_fd把描述符通过s-pipe(它的标准输出)
发送回给客户。如果碰到错误,send_err被调用来把一个错误消息发送回去,使用我们之前描述的客户-服务器协议。
- #include "opend.h"
- #include <fcntl.h>
- void
- request(char *buf, int nread, int fd)
- {
- int newfd;
- if (buf[nread-1] != 0) {
- sprintf(errmsg, "request not null terminated: %*.*s\n",
- nread, nread, buf);
- send_err(fd, -1, errmsg);
- return;
- }
- if (buf_args(buf, cli_args) < 0) { /* parse args & set options */
- send_err(fd, -1, errmsg);
- return;
- }
- if ((newfd = open(pathname, oflag)) < 0) {
- sprintf(errmsg, "can't open %s: %s\n", pathname, strerror(errno));
- send_err(fd, -1, errmsg);
- return;
- }
- if (send_fd(fd, newfd) < 0) { /* send the descriptor */
- printf("send_fd error\n");
- exit(1);
- }
- close(newfd); /* we're done with descriptor */
- }
客户的请求是一个null终止的空白字符分隔的参数字符串。下面代码里的函数buf_args把这个字符串分解成标准的argv风格的参数列
表并调用一个用户函数来处理参数。我们将使用本章后面的buf_args函数。我们使用ISO C函数strtok来语素化字符串为分隔的参数。
- #include <stddef.h>
- #include <string.h>
- #define MAXARGC 50 /* max number of arguments in buf */
- #define WHITE " \t\n" /* white space for tokenizing arguments */
- /*
- * buf[] contains white-space-separated arguments. We conver it to an
- * argv-style array of pointers, and call the user's function (optfunc)
- * to process the array. We return -1 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;
- /*
- * Since argv[] pointers point into the user's buf[],
- * user's funciton can just copy the pointers, event
- * though argv[] array will disappear on return.
- */
- return((*optfunc)(argc, argv));
- }
被buf_args调用的服务器的函数是cli_args(下面的代码)。它验证客户发送了正确数量的参数并把路径名和打开模式存储在全局变量里。
- #include "opend.h"
- #include <string.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.
- */
- cli_args(int argc, char **argv)
- {
- if (argc != 3 || strcmp(argv[0], CL_OPEN) != 0) {
- strcpy(errmsg, "usage: \n");
- return(-1);
- }
- pathname = argv[1]; /* save ptr to pathname to open */
- oflag = atoi(argv[2]);
- return(0);
- }
这完成了客户通过fork和exec调用的打开服务器。单个s-pipe在fork之前被创建,并用来在客户和服务器之间通信。使用这种布署,我们为每个客户都有一个服务器。
阅读(1454) | 评论(0) | 转发(0) |