Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1800263
  • 博文数量: 438
  • 博客积分: 9799
  • 博客等级: 中将
  • 技术积分: 6092
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-25 17:25
文章分类

全部博文(438)

文章存档

2019年(1)

2013年(8)

2012年(429)

分类: 系统运维

2012-04-03 13:54:26

在前一节,我们开发了一个被客户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。代码如下(本章自此往下的代码都是拷贝过来的,我没有亲自码字):



  1. #include    "open.h"
  2. #include    <sys/uio.h>        /* struct iovec */

  3. /*
  4.  * Open the file by sending the "name" and "oflag" to the
  5.  * connection server and reading a file descriptor back.
  6.  */
  7. int
  8. csopen(char *name, int oflag)
  9. {
  10.     int                len;
  11.     char            buf[10];
  12.     struct iovec    iov[3];
  13.     static int        csfd = -1;

  14.     if (csfd < 0) {        /* open connection to conn server */
  15.         if ((csfd = cli_conn(CS_OPEN)) < 0)
  16.             err_sys("cli_conn error");
  17.     }

  18.     sprintf(buf, " %d", oflag);        /* oflag to ascii */
  19.     iov[0].iov_base = CL_OPEN " ";    /* string concatenation */
  20.     iov[0].iov_len = strlen(CL_OPEN) + 1;
  21.     iov[1].iov_base = name;
  22.     iov[1].iov_len = strlen(name);
  23.     iov[2].iov_base = buf;
  24.     iov[2].iov_len = strlen(buf) + 1;    /* null always sent */
  25.     len = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len;
  26.     if (writev(csfd, &iov[0], 3) != len)
  27.         err_sys("writev error");

  28.     /* read back descriptor; returned errors handled by write() */
  29.     return(recv_fd(csfd, write));
  30. }


从客户到服务器的协议保持不变。

接下来,我们将看下服务器。头文件opend.h包含标准头文件和全局变量的声明,和函数原型。



  1. #include "apue.h"
  2. #include <errno.h>

  3. #define    CS_OPEN "/home/sar/opend"    /* well-known name */
  4. #define    CL_OPEN "open"                /* client's request for server */

  5. extern int     debug;        /* nonzero if interactive (not daemon) */
  6. extern char     errmsg[];    /* error message string to return to client */
  7. extern int     oflag;        /* open flag: O_xxx ... */
  8. extern char    *pathname;    /* of file to open for client */

  9. typedef struct {    /* one Client struct per connected client */
  10.   int    fd;            /* fd, or -1 if available */
  11.   uid_t    uid;
  12. } Client;

  13. extern Client    *client;        /* ptr to malloc'ed array */
  14. extern int         client_size;    /* # entries in client[] array */

  15. int         cli_args(int, char **);
  16. int         client_add(int, uid_t);
  17. void     client_del(int);
  18. void     loop(void);
  19. void     request(char *, int, int, uid_t);


因为服务器处理所有的客户,所以它必须保持每个客户连接的状态。这由在opend.h头文件里声明的client数组完成。下面的代码定义了三个处理这个数组的函数:


  1. #include    "opend.h"

  2. #define    NALLOC    10        /* # client structs to alloc/realloc for */

  3. static void
  4. client_alloc(void)        /* alloc more entries in the client[] array */
  5. {
  6.     int        i;

  7.     if (client == NULL)
  8.         client = malloc(NALLOC * sizeof(Client));
  9.     else
  10.         client = realloc(client, (client_size+NALLOC)*sizeof(Client));
  11.     if (client == NULL)
  12.         err_sys("can't alloc for client array");

  13.     /* initialize the new entries */
  14.     for (i = client_size; i < client_size + NALLOC; i++)
  15.         client[i].fd = -1;    /* fd of -1 means entry available */

  16.     client_size += NALLOC;
  17. }

  18. /*
  19.  * Called by loop() when connection request from a new client arrives.
  20.  */
  21. int
  22. client_add(int fd, uid_t uid)
  23. {
  24.     int        i;

  25.     if (client == NULL)        /* first time we're called */
  26.         client_alloc();
  27. again:
  28.     for (i = 0; i < client_size; i++) {
  29.         if (client[i].fd == -1) {    /* find an available entry */
  30.             client[i].fd = fd;
  31.             client[i].uid = uid;
  32.             return(i);    /* return index in client[] array */
  33.         }
  34.     }

  35.     /* client array full, time to realloc for more */
  36.     client_alloc();
  37.     goto again;        /* and search again (will work this time) */
  38. }

  39. /*
  40.  * Called by loop() when we're done with a client.
  41.  */
  42. void
  43. client_del(int fd)
  44. {
  45.     int        i;

  46.     for (i = 0; i < client_size; i++) {
  47.         if (client[i].fd == fd) {
  48.             client[i].fd = -1;
  49.             return;
  50.         }
  51.     }
  52.     log_quit("can't find client entry for fd %d", fd);
  53. }


第一次调用client_add时,它调用client_alloc,这个函数调用malloc来在数据里分配10个项的空间。在 这10个项都被使用时,稍后的client_add导致realloc来分配额外的空间。通过以这种方式动态分配空间,我们在编译时没有限制client 数据的尺寸为某个我们猜想并放入一个头文件的值。这些函数调用log_函数,如果有错误发生,因为我们假定服务器是一个守护进程。

main函数(下面的代码)定义了全局变量,处理命令行选项,并调用函数loop。如果我们用-d选项调用服务器,那么服务器交互地运行而不是作为一个守护进程。这在测试服务器时使用。



  1. #include    "opend.h"
  2. #include    <syslog.h>

  3. int         debug, oflag, client_size, log_to_stderr;
  4. char     errmsg[MAXLINE];
  5. char    *pathname;
  6. Client    *client = NULL;

  7. int
  8. main(int argc, char *argv[])
  9. {
  10.     int        c;

  11.     log_open("open.serv", LOG_PID, LOG_USER);

  12.     opterr = 0;        /* don't want getopt() writing to stderr */
  13.     while ((c = getopt(argc, argv, "d")) != EOF) {
  14.         switch (c) {
  15.         case 'd':        /* debug */
  16.             debug = log_to_stderr = 1;
  17.             break;

  18.         case '?':
  19.             err_quit("unrecognized option: -%c", optopt);
  20.         }
  21.     }

  22.     if (debug == 0)
  23.         daemonize("opend");

  24.     loop();        /* never returns */
  25. }



函数loop是服务器的无限循环。我们将展示这个函数的两个版本。下面展示了使用select的版本。待会展示一个使用poll的版本。


  1. #include    "opend.h"
  2. #include    <sys/time.h>
  3. #include    <sys/select.h>

  4. void
  5. loop(void)
  6. {
  7.     int        i, n, maxfd, maxi, listenfd, clifd, nread;
  8.     char    buf[MAXLINE];
  9.     uid_t    uid;
  10.     fd_set    rset, allset;

  11.     FD_ZERO(&allset);

  12.     /* obtain fd to listen for client requests on */
  13.     if ((listenfd = serv_listen(CS_OPEN)) < 0)
  14.         log_sys("serv_listen error");
  15.     FD_SET(listenfd, &allset);
  16.     maxfd = listenfd;
  17.     maxi = -1;

  18.     for ( ; ; ) {
  19.         rset = allset;    /* rset gets modified each time around */
  20.         if ((n = select(maxfd + 1, &rset, NULL, NULL, NULL)) < 0)
  21.             log_sys("select error");

  22.         if (FD_ISSET(listenfd, &rset)) {
  23.             /* accept new client request */
  24.             if ((clifd = serv_accept(listenfd, &uid)) < 0)
  25.                 log_sys("serv_accept error: %d", clifd);
  26.             i = client_add(clifd, uid);
  27.             FD_SET(clifd, &allset);
  28.             if (clifd > maxfd)
  29.                 maxfd = clifd;    /* max fd for select() */
  30.             if (i > maxi)
  31.                 maxi = i;    /* max index in client[] array */
  32.             log_msg("new connection: uid %d, fd %d", uid, clifd);
  33.             continue;
  34.         }

  35.         for (i = 0; i <= maxi; i++) {    /* go through client[] array */
  36.             if ((clifd = client[i].fd) < 0)
  37.                 continue;
  38.             if (FD_ISSET(clifd, &rset)) {
  39.                 /* read argument buffer from client */
  40.                 if ((nread = read(clifd, buf, MAXLINE)) < 0) {
  41.                     log_sys("read error on fd %d", clifd);
  42.                 } else if (nread == 0) {
  43.                     log_msg("closed: uid %d, fd %d",
  44.                      client[i].uid, clifd);
  45.                     client_del(clifd);    /* client has closed cxn */
  46.                     FD_CLR(clifd, &allset);
  47.                     close(clifd);
  48.                 } else {    /* process client's request */
  49.                     request(buf, nread, clifd, client[i].uid);
  50.                 }
  51.             }
  52.         }
  53.     }
  54. }



这个函数调用serv_listen来为客户连接创建服务器的端点。函数剩余部分是一个循环,从一个select调用开始。在select返回时有两个条件会成立。

1、 描述符listenfd准备好读,这表示一个新的客户已经调用了cli_conn。为了处理这个,我们调用serv_accept,然后更新client 数组并为这个新客户关联记录信息。(我们保持select最一个参数的最高描述符号的跟踪。我们同样保持在client数据里被使用的最高索引。)


2、一个存在的客户连接可以准备好读。这意味着客户已经终止或发送一个新请求。我们通过read返回0(文件末尾)发现一个客户的终止。如果read返回一个比0大的值,那么有一个新请求要处理,我们通过调用request来处理它。


我们保持跟踪在allset描述符集里哪些描述符当前正被使用。由于新客户连接到服务器,在这个描述符集里恰当的位被打开。当客户终止时恰当的位被关闭。


我们一直知道一个客户何时终止,自愿或不自愿地,因为所有客户描述符(包含对服务器的连接)都被内核自动关闭。这和XSI IPC机制不同。


使用poll的loop函数如下所示:



  1. #include    "opend.h"
  2. #include    <poll.h>
  3. #if !defined(BSD) && !defined(MACOS)
  4. #include    <stropts.h>
  5. #endif

  6. void
  7. loop(void)
  8. {
  9.     int                i, maxi, listenfd, clifd, nread;
  10.     char            buf[MAXLINE];
  11.     uid_t            uid;
  12.     struct pollfd    *pollfd;

  13.     if ((pollfd = malloc(open_max() * sizeof(struct pollfd))) == NULL)
  14.         err_sys("malloc error");

  15.     /* obtain fd to listen for client requests on */
  16.     if ((listenfd = serv_listen(CS_OPEN)) < 0)
  17.         log_sys("serv_listen error");
  18.     client_add(listenfd, 0);    /* we use [0] for listenfd */
  19.     pollfd[0].fd = listenfd;
  20.     pollfd[0].events = POLLIN;
  21.     maxi = 0;

  22.     for ( ; ; ) {
  23.         if (poll(pollfd, maxi + 1, -1) < 0)
  24.             log_sys("poll error");

  25.         if (pollfd[0].revents & POLLIN) {
  26.             /* accept new client request */
  27.             if ((clifd = serv_accept(listenfd, &uid)) < 0)
  28.                 log_sys("serv_accept error: %d", clifd);
  29.             i = client_add(clifd, uid);
  30.             pollfd[i].fd = clifd;
  31.             pollfd[i].events = POLLIN;
  32.             if (i > maxi)
  33.                 maxi = i;
  34.             log_msg("new connection: uid %d, fd %d", uid, clifd);
  35.         }

  36.         for (i = 1; i <= maxi; i++) {
  37.             if ((clifd = client[i].fd) < 0)
  38.                 continue;
  39.             if (pollfd[i].revents & POLLHUP) {
  40.                 goto hungup;
  41.             } else if (pollfd[i].revents & POLLIN) {
  42.                 /* read argument buffer from client */
  43.                 if ((nread = read(clifd, buf, MAXLINE)) < 0) {
  44.                     log_sys("read error on fd %d", clifd);
  45.                 } else if (nread == 0) {
  46. hungup:
  47.                     log_msg("closed: uid %d, fd %d",
  48.                      client[i].uid, clifd);
  49.                     client_del(clifd);    /* client has closed conn */
  50.                     pollfd[i].fd = -1;
  51.                     close(clifd);
  52.                 } else {        /* process client's request */
  53.                     request(buf, nread, clifd, client[i].uid);
  54.                 }
  55.             }
  56.         }
  57.     }
  58. }


为了允许尽可能多的打开描述符,我们动态为pollfd结构体的数组分配空间。(回想第二章的open_max函数。)


我 们使用client数组的第一项(index 0)作为listenfd描述符。通过这种方法,在client数组里的一个客户的索引和我们在pollfd数组里使用的索引一样。新客户连接的到来由 POLLIN在listenfd描述符上指定。和原来一样,我们调用serv_accept来接受连接。


为了一个存在的客户,我们必须在 poll里处理两个不同的事件:由POLLHUP指定的一个客户终止,和由POLLIN指定的已存在客户发出的一个新请求。挂起消息可以在流头到达,当在 流上有仍可读的数据时。通过使用一个管道,我们想在处理这个挂起之间读取所有数据。但是对于这个服务器,当我们从客户端接收到挂起时,我们可以close 这个到客户的连接(流),有效地抛弃任何仍在流上的数据。没有理由处理任何仍在流上的数据,因为我们不能发送回任何响应。


和这个函数的select版本一样,一个客户的新请求通过调用request函数被处理。这个函数和前面的版本相似。它调用相同的函数buf_args,调用cli_args,但是因为它在一个守护进程上运行,所以它记录错误消息而不是把它们打印到标准错误流上。



  1. #include    "opend.h"
  2. #include    <fcntl.h>

  3. void
  4. request(char *buf, int nread, int clifd, uid_t uid)
  5. {
  6.     int        newfd;

  7.     if (buf[nread-1] != 0) {
  8.         sprintf(errmsg,
  9.          "request from uid %d not null terminated: %*.*s\n",
  10.          uid, nread, nread, buf);
  11.         send_err(clifd, -1, errmsg);
  12.         return;
  13.     }
  14.     log_msg("request: %s, from uid %d", buf, uid);

  15.     /* parse the arguments, set options */
  16.     if (buf_args(buf, cli_args) < 0) {
  17.         send_err(clifd, -1, errmsg);
  18.         log_msg(errmsg);
  19.         return;
  20.     }

  21.     if ((newfd = open(pathname, oflag)) < 0) {
  22.         sprintf(errmsg, "can't open %s: %s\n",
  23.          pathname, strerror(errno));
  24.         send_err(clifd, -1, errmsg);
  25.         log_msg(errmsg);
  26.         return;
  27.     }

  28.     /* send the descriptor */
  29.     if (send_fd(clifd, newfd) < 0)
  30.         log_sys("send_fd error");
  31.     log_msg("sent fd %d over fd %d for %s", newfd, clifd, pathname);
  32.     close(newfd);        /* we're done with descriptor */
  33. }


这完成了打开文件的第二个版本,使用单个守护进程处理所有客户请求。
阅读(1374) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~