在前一节,我们开发了一个被客户fork和exec的调用的打开服务器,证明了我们如何从一个子进程传递文件描述符到一个父进程。在本节,我们开发
一个作为守护进程的打开服务器。一个服务器处理了所有的客户。我们期望这个被设计为更高效的,因为一个fork和exec被避免。我们仍然在客户和服务器
之间使用一个s-pipe并展示在无关进程之间传递文件描述符。我们将使用17.2.2节引入的三个函数serv_listen、serv_accept
和cli_conn。服务器也展示了单个服务器如何使用14.5节的select和poll函数处理多个客户。
客户和17.5节的那个相似。事实上,main.c文件是相同的。我们在open.h头文件里加上以下这行:
#define CS_OPEN "/home/sar/opend" /* server's well-known name */
文件open.c也有所改变,因为我们现在调用cli_conn而不是执行fork和exec。代码如下(本章自此往下的代码都是拷贝过来的,我没有亲自码字):
- #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 " "; /* 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; /* null 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));
- }
从客户到服务器的协议保持不变。
接下来,我们将看下服务器。头文件opend.h包含标准头文件和全局变量的声明,和函数原型。
- #include "apue.h"
- #include <errno.h>
- #define CS_OPEN "/home/sar/opend" /* well-known name */
- #define CL_OPEN "open" /* client's request for server */
- 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 */
- 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);
因为服务器处理所有的客户,所以它必须保持每个客户连接的状态。这由在opend.h头文件里声明的client数组完成。下面的代码定义了三个处理这个数组的函数:
- #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");
- /* 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);
- }
第一次调用client_add时,它调用client_alloc,这个函数调用malloc来在数据里分配10个项的空间。在
这10个项都被使用时,稍后的client_add导致realloc来分配额外的空间。通过以这种方式动态分配空间,我们在编译时没有限制client
数据的尺寸为某个我们猜想并放入一个头文件的值。这些函数调用log_函数,如果有错误发生,因为我们假定服务器是一个守护进程。
main函数(下面的代码)定义了全局变量,处理命令行选项,并调用函数loop。如果我们用-d选项调用服务器,那么服务器交互地运行而不是作为一个守护进程。这在测试服务器时使用。
- #include "opend.h"
- #include <syslog.h>
- int debug, oflag, client_size, log_to_stderr;
- char errmsg[MAXLINE];
- char *pathname;
- Client *client = NULL;
- 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 = log_to_stderr = 1;
- break;
- case '?':
- err_quit("unrecognized option: -%c", optopt);
- }
- }
- if (debug == 0)
- daemonize("opend");
- loop(); /* never returns */
- }
函数loop是服务器的无限循环。我们将展示这个函数的两个版本。下面展示了使用select的版本。待会展示一个使用poll的版本。
- #include "opend.h"
- #include <sys/time.h>
- #include <sys/select.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, &uid)) < 0)
- log_sys("serv_accept error: %d", clifd);
- i = client_add(clifd, uid);
- FD_SET(clifd, &allset);
- if (clifd > maxfd)
- maxfd = clifd; /* max fd for select() */
- if (i > maxi)
- maxi = i; /* max index in client[] array */
- log_msg("new connection: uid %d, fd %d", uid, clifd);
- continue;
- }
- for (i = 0; i <= maxi; i++) { /* go through client[] array */
- if ((clifd = client[i].fd) < 0)
- continue;
- if (FD_ISSET(clifd, &rset)) {
- /* read argument buffer from client */
- if ((nread = read(clifd, buf, MAXLINE)) < 0) {
- log_sys("read error on fd %d", clifd);
- } else if (nread == 0) {
- log_msg("closed: uid %d, fd %d",
- client[i].uid, clifd);
- client_del(clifd); /* client has closed cxn */
- FD_CLR(clifd, &allset);
- close(clifd);
- } else { /* process client's request */
- request(buf, nread, clifd, client[i].uid);
- }
- }
- }
- }
- }
这个函数调用serv_listen来为客户连接创建服务器的端点。函数剩余部分是一个循环,从一个select调用开始。在select返回时有两个条件会成立。
1、
描述符listenfd准备好读,这表示一个新的客户已经调用了cli_conn。为了处理这个,我们调用serv_accept,然后更新client
数组并为这个新客户关联记录信息。(我们保持select最一个参数的最高描述符号的跟踪。我们同样保持在client数据里被使用的最高索引。)
2、一个存在的客户连接可以准备好读。这意味着客户已经终止或发送一个新请求。我们通过read返回0(文件末尾)发现一个客户的终止。如果read返回一个比0大的值,那么有一个新请求要处理,我们通过调用request来处理它。
我们保持跟踪在allset描述符集里哪些描述符当前正被使用。由于新客户连接到服务器,在这个描述符集里恰当的位被打开。当客户终止时恰当的位被关闭。
我们一直知道一个客户何时终止,自愿或不自愿地,因为所有客户描述符(包含对服务器的连接)都被内核自动关闭。这和XSI IPC机制不同。
使用poll的loop函数如下所示:
- #include "opend.h"
- #include <poll.h>
- #if !defined(BSD) && !defined(MACOS)
- #include <stropts.h>
- #endif
- void
- loop(void)
- {
- int i, maxi, listenfd, clifd, nread;
- char buf[MAXLINE];
- uid_t uid;
- struct pollfd *pollfd;
- if ((pollfd = malloc(open_max() * sizeof(struct pollfd))) == NULL)
- err_sys("malloc error");
- /* obtain fd to listen for client requests on */
- if ((listenfd = serv_listen(CS_OPEN)) < 0)
- log_sys("serv_listen error");
- client_add(listenfd, 0); /* we use [0] for listenfd */
- pollfd[0].fd = listenfd;
- pollfd[0].events = POLLIN;
- maxi = 0;
- for ( ; ; ) {
- if (poll(pollfd, maxi + 1, -1) < 0)
- log_sys("poll error");
- if (pollfd[0].revents & POLLIN) {
- /* accept new client request */
- if ((clifd = serv_accept(listenfd, &uid)) < 0)
- log_sys("serv_accept error: %d", clifd);
- i = client_add(clifd, uid);
- pollfd[i].fd = clifd;
- pollfd[i].events = POLLIN;
- if (i > maxi)
- maxi = i;
- log_msg("new connection: uid %d, fd %d", uid, clifd);
- }
- for (i = 1; i <= maxi; i++) {
- if ((clifd = client[i].fd) < 0)
- continue;
- if (pollfd[i].revents & POLLHUP) {
- goto hungup;
- } else if (pollfd[i].revents & POLLIN) {
- /* read argument buffer from client */
- if ((nread = read(clifd, buf, MAXLINE)) < 0) {
- log_sys("read error on fd %d", clifd);
- } else if (nread == 0) {
- hungup:
- log_msg("closed: uid %d, fd %d",
- client[i].uid, clifd);
- client_del(clifd); /* client has closed conn */
- pollfd[i].fd = -1;
- close(clifd);
- } else { /* process client's request */
- request(buf, nread, clifd, client[i].uid);
- }
- }
- }
- }
- }
为了允许尽可能多的打开描述符,我们动态为pollfd结构体的数组分配空间。(回想第二章的open_max函数。)
我
们使用client数组的第一项(index
0)作为listenfd描述符。通过这种方法,在client数组里的一个客户的索引和我们在pollfd数组里使用的索引一样。新客户连接的到来由
POLLIN在listenfd描述符上指定。和原来一样,我们调用serv_accept来接受连接。
为了一个存在的客户,我们必须在
poll里处理两个不同的事件:由POLLHUP指定的一个客户终止,和由POLLIN指定的已存在客户发出的一个新请求。挂起消息可以在流头到达,当在
流上有仍可读的数据时。通过使用一个管道,我们想在处理这个挂起之间读取所有数据。但是对于这个服务器,当我们从客户端接收到挂起时,我们可以close
这个到客户的连接(流),有效地抛弃任何仍在流上的数据。没有理由处理任何仍在流上的数据,因为我们不能发送回任何响应。
和这个函数的select版本一样,一个客户的新请求通过调用request函数被处理。这个函数和前面的版本相似。它调用相同的函数buf_args,调用cli_args,但是因为它在一个守护进程上运行,所以它记录错误消息而不是把它们打印到标准错误流上。
- #include "opend.h"
- #include <fcntl.h>
- void
- request(char *buf, int nread, int clifd, uid_t uid)
- {
- int newfd;
- if (buf[nread-1] != 0) {
- sprintf(errmsg,
- "request from uid %d not null terminated: %*.*s\n",
- uid, nread, nread, buf);
- send_err(clifd, -1, errmsg);
- return;
- }
- log_msg("request: %s, from uid %d", buf, uid);
- /* parse the arguments, set options */
- if (buf_args(buf, cli_args) < 0) {
- send_err(clifd, -1, errmsg);
- log_msg(errmsg);
- return;
- }
- if ((newfd = open(pathname, oflag)) < 0) {
- sprintf(errmsg, "can't open %s: %s\n",
- pathname, strerror(errno));
- send_err(clifd, -1, errmsg);
- log_msg(errmsg);
- return;
- }
- /* send the descriptor */
- if (send_fd(clifd, newfd) < 0)
- log_sys("send_fd error");
- log_msg("sent fd %d over fd %d for %s", newfd, clifd, pathname);
- close(newfd); /* we're done with descriptor */
- }
这完成了打开文件的第二个版本,使用单个守护进程处理所有客户请求。
阅读(1374) | 评论(0) | 转发(0) |