《unix环境高级编程》--- 高级进程间通信[2]

时间:2022-08-08 04:44:34

当一个进程向另一个级才能传送一打开的文件描述符时,希望发送进程和接收进程共享同一文件表项。
发送进程实际上向接收进程传送一个指向一打开文件表项的指针。该指针被分配存放在接收进程的第一可用描述符项中,即发送进程和接收进程的描述符编号一般不同。
当发送继承将描述符传送给接收进程后,通常关闭该描述符。发送进程关闭该描述符不造成关闭该文件或设备,因为描述符对应的文件仍被视为由接收进程打开(即接收进程尚未收到该描述符)。

s_pipe函数的套接字版本
得到全双工管道。

#include "apue.h"
#include <sys/socket.h>

/* Returns a full-duplex "stream" pipe (a UNIX domain socket)
   with the two file descriptors returndd in fd[0] and fd[1]. */

int s_pipe(int fd[2])
{
    /*
       int socketpair(int domain, int type, int protocol, int sockfd[2]);
       返回值:成功返回0,出错返回-1
    */
    return (socketpair(AF_UNIX, SOCK_STREAM, 0, fd));
}

UNIX域套接字的send_fd函数

经由fd代表的s管道发送描述符fd_to_send
先发送两个0字节,然后是实际描述符。

#include "apue.h"
#include <sys/socket.h>

/* size of control buffer to send/recv one file descriptor */
#define CONTROLLEN CMSG_LEN(sizeof(int))

static struct cmsghdr *cmptr = NULL;  /* malloc'ed first time */

/* Pass a file descriptor to another process. If fd < 0, then -fd is sent back instead as the error status */
int send_fd(int fd, int fd_to_send)
{
    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_to_send < 0)
    {
        msg.msg_control = NULL;
        msg.msg_controllen = 0;
        buf[1] = -fd_to_send;  /* 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 = cmptr;
        msg.msg_controllen = CONTROLLEN;
        *(int *)CMSG_DATA(cmptr) = fd_to_send;  /* the fd to pass */
        buf[1] = 0;   /* zero status means OK */
    }
    buf[0] = 0;  /* null byte flag to recv_fd() */
    if(sendmsg(fd, &msg, 0) != 2)
        return (-1);
    return (0);
}

UNIX域套接字的recv_fd函数

#include "apue.h"
#include <sys/socket.h> /* struct msghdr */

/* size of control buffer to send/recv one file descriptor */
#define CONTROLLEN CMSG_LEN(sizeof(int))

static struct cmsghdr *cmptr = NULL;  /* malloc'ed first *? /* Receive a file descriptor from a server process. Also, any data received is passed to (*userfunc)(STDERR_FILENO, buf, nbytes). We have a 2-byte protocl for receiving the fd from send_fd(). */

int recv_fd(int fd, size_t (*userfunc)(int, const void *, size_t))
{
    int newfd, nr, status;
    char *ptr;
    char buf[MAXLINE];
    struct iovec iov[1];
    struct msghder 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 = cmptr;
        msg.msg_controllen = CONTROLLEN;
        if((nr = recvmsg(fd, &msg, 0)) < 0)
            err_sys("recvmsg error");
        else if(nr == 0)
        {
            err_ret("connection closed by server");
            return (-1);
        }
        /* See if this is the final data with null & staatus. Null is next to last byte of buffer; status byte is last byte. Zero status means there is a file descriptor to receive. */
        for(ptr = bur; ptr < &buf[nr]; )
        {
            if(*ptr++ == 0)
            {
                if(ptr != &buf[nr-1])
                    err_dump("message format error");
                status = *ptr & 0xFF;   /* prevent sign extension */
                if(status == 0)
                {
                    if(msg.msg_controllen != CONTROLLEN)
                        err_dump("status = 0 but no fd");
                    newfd = *(int *)CMSG_DATA(cmptr);
                }
                else
                    newfd = -status;
                nr -= 2;
            }

        }
        if(nr > 0 && (*userfunc)(STDERR_FILENO, buf, nr) != nr)
            return (-1);
        if(status >= 0)   /* final data has arrived */
            return (newfd);  /* descriptor, or -status */
    }

}

send_err函数
用fd发送msg及随后的errcode。
将出错消息写到STREAMS管道后,即调用send_fd函数。

#include "apue.h"

/* Used when we had planned to send an fd using send_fd(), but encontered an error instead. We send the error back using the send_fd()/recv_fd() protocol. */
int send_err(int fd, int errocde, const char *msg)
{
    int n;

    if((n = strlen(msg)) > 0)
        if(writen(fd, msg, n) != n)  /* send the error message */
            return (-1); 

    if(errcode >= 0)
        errcode = -1;  /* must be negative */

    if(send_fd(fd, errcode) < 0)
        return(-1);

    return(0);
}

————————————————————————————————————————————-

open服务器版本1
open服务器:一个由一个进程执行已打开一个或几个文件的程序。该服务器不是将文件送回调用进程,而是送回一个打开文件描述符。
从客户进程到服务器进程传送文件名和打开模式,从服务器进程到客户进程返回描述符。文件内容不需要用IPC传送。
服务器被设计成一个单独的可执行程序,由客户进程执行。
客户进程创建一个s管道,然后调用fork和exec以调用服务器进程。客户进程经s管道发送请求,服务器进程经s管道回送响应。
客户进程和服务器进程间的应用程序协议如下:
1)客户进程经过s管道向服务器进程发送”open pathname openmode\0” 形式的请求。
2)服务器进程调用send_fd或sned_err回送一打开描述符或一条出错消息。


客户端

open.h

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

#define CL_OPEN "open" /* client's request for server */

int csopen(char *, int);

open.c
与open服务器联系。
opend是服务器代码生成的可执行文件。
先创建一个s管道,然后进行服务器进程的fork和execl操作。
子进程关闭管道一端,父进程关闭另一端。子进程成为服务器进程,并将管道一端复制到其标准输入和标准输出。
父进程将请求发送给服务器进程,请求中包含路径名和打开模式。最后,父进程调用recv_fd返回描述符或错误消息。如果服务器进程返回一错误消息,父进程调用write向标准出错输出该消息。

#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 descriptro 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)
    {
        if(fd_pipe(fd) < 0)
            err_sys("fd_pipe error");
        if((pid = fork()) < 0)
            err_sys("fork error");
        else if(pid == 0) 
        {
            close(fd[0]);
            if(fd[1] != STDIN_FILENO && 
                dup2(fd[1], STDIN_FILENO) != STDIN_FILENO)
                err_sys("dup2 error to stdin");
            if(fd[1] != STDOUT_FILENO &&
                dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO)
                err_sys("dup2 error to stdout");
            if(execl("./opend", "opend", (char*)0) < 0)
                err_sys("execl error");
        }
        close(fd[1]); /* parent */
    }
    sprintf(buf, " %d", oflag);  /* oflag to acsii */
    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)
        err_sys("writev error");

    /* read descriptor, returned errors handled by write() */
 return(recv_fd(fd[0], write));
}

客户进程的main函数版本1
先从标准输入读一个路径名 ,然后将该文件复制至标准输出。调用csopen域open服务器联系。

#include "open.h"
#include <fcntl.h>

#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)
    {
        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)
                err_sys("write error");
        if(n < 0)
            err_sys("read error");
        close(fd);
    }

    exit(0);
}

服务器端
以下文件将生成可执行文件 opend,供客户端调用execl打开

opend.h

#include "apue.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函数版本1

#include "opend.h"

char errmsg[MAXLINE];
int oflag;
char *pathname;

int main(void)
{
    int nread;
    char buf[MAXLINE];

    for(;;)
    {
        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, STDOUT_FILENO);
    }
    exit(0);
}

request.c
调用buf_args将客户进程请求分解成标准argv型的参数表,然后调用cli_args处理客户进程的参数。如果一切正常,则调用open打开相应文件,接着调用send_fd,经由s管道(它的标准输出)将描述符送给客户进程。如果出错则调用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 */
        err_sys("send_fd error");
    close(newfd);  /* we're done with descriptor */
}

buf_arg.c
客户进程请求是一个以null结尾的字符串,所包含的各参数由空格分隔。buf_args函数将字符串分解成标准argv型参数表,并调用optfunc,即cli_args,处理参数。

#include "apue.h"

#define MAXARGC 50 /* max number of arguments in buf */
#define WHITE " \t\n" /* white space for tokenizing arguments */

/*  
   buf[] contians white-space-separated arguments. We convert it to an
   argv-style array of pointers, and call 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 function can just copy the pointers, even
           though argv[] array will disapper on return.
    */
 return ((*optfunc)(argc, argv)); }

cli_args.c
验证客户进程发送的参数个数是否正确,然后将路径名和打开模式存放在全局变量中。

#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: <pathname> <oflag>\n");
 return (-1);
    }
    pathname = argv[1];  /* save ptr to pathname to open */
    oflag = atoi(argv[2]);
 return (0);
}

《unix环境高级编程》--- 高级进程间通信[2]

————————————————————————————————————————————-

UNIX域套接字的serv_listen函数
调用socket创建一个UNIX域套接字。然后将欲赋予套接字的众所周知的路径名填入sockaddr_un结构。该结构是调用bind的参数。最后调用listen以通知内核该进程将作为服务器进程等待客户进程的连接请求。

#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define QLEN 10

/* Create a server endpoint of a connection. Return fd if all OK, <0 on error */
int serv_listen(const char *name)
{
    int fd, len, err, rval;
    struct sockaddr_un un;

    /* create a UNIX domain stream socket */
    if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return (-1);

    unlink(name);  /* in case it already exists */

    int reuse = 1;
    if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) < 0)
        {
        perror("setsockopet error\n");
            goto errout;
        }

    /* fill in socket address structure */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);

    /* bind the name to the descriptor */
    if(bind(fd, (struct sockaddr *)&un, len) < 0)
    {
        rval = -2;
        goto errout;
    }

    if(listen(fd, QLEN) < 0)
    {
        rval = -3;
        goto errout;
    }

    return (fd);

 errout:
    err = errno;
    close(fd);
    errno = err;
    return (rval);
}

UNIX域套接字的serv_accept函数
当收到一个客户进程的连接请求后,服务器进程调用serv_accept函数。
服务器进程在调用serv_accept中阻塞以等待一客户进程调用cli_conn。从accept返回时,返回值为连接客户进程的崭新的描述符。
另外,accept也经由其第二个参数返回客户继承赋予其套接字的路径名(包含客户进程ID的名字)。接着,程序在此路径名尾处填null字符,然后调用stat函数。然后验证该路径名确实是一个套接字,其权限仅允许用户-读、用户-写及用户-执行。也验证域套接字相关联的3各时间不比当前时间早30秒。
通过以上检验,可认为客户进程为该套接字的所有者。

#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <errno.h>

#define STALE 30 /* client's name can't be older than this (sec) */

/* Wait for a client connection to arrive, and accept it.
   We also obtain the client's user ID from th pathname
   that it must bind before calling us.
   Returns new fd if all OK, <0 on error */
int serv_accept(int listenfd, uid_t *uidptr)
{
    int clifd, len, err, rval;
    time_t staletime;
    struct sockaddr_un un;
    struct stat statbuf;

    len = sizeof(un);
    if((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
 return (-1);  /* often errno = EINTR, if signal caught */

    /* obtain the client's uid from its calling address */
    len -= offsetof(struct sockaddr_un, sun_path);  /* len of pathname */
    un.sun_path[len] = 0;  /* null terminate */

    if(stat(un.sun_path, &statbuf) < 0)
    {
        rval = -2;
        goto errout;
    }
 #ifdef S_ISSOCK /* not defined for SVR4 */
    if(S_ISSOCK(statbuf.st_mode) == 0)
    {
        rval = -3;  /* not a socket */
        goto errout;
    }
 #endif
    if((statbuf.st_mode & (S_IRWXG | S_IRWXO)) || 
           (statbuf.st_mode & S_IRWXU != S_IRWXU))
    {
        rval = -4;   /* is not rwx----*/
        goto errout;
    }

    staletime = time(NULL) - STALE;
    if(statbuf.st_atime < staletime || statbuf.st_ctime < staletime || 
           statbuf.st_mtime < staletime)
    {
        rval = -5;  /* i-node is too old */
        goto errout;
    }

    if(uidptr != NULL)
        *uidptr = statbuf.st_uid;  /* return uid of caller */
    unlink(un.sun_path);  /* we're done with pathname now */
 return (clifd);

 errout:
    err = errno;
    close(clifd);
    errno = err;
 return (rval);
}

用于UNIX域套接字的cli_conn函数
客户进程调用cli_conn函数,对连向服务器进程的连接进行初始化。
调用socket函数创建UNIX域套接字的客户进程端,然后用客户进程专有的名字填入sockaddr_un结构。
为了不让系统选择一个默认地址,以至于不能区分各个客户进程,我们绑定自己的地址,开发使用套接字的客户端程序时通常不采用这一步骤。
绑定的路径名的最后5个字符来自客户进程ID。调用unlink,以防止路径名已存在。然后调用bind将名字赋予客户进程套接字。接着,调用chmod关闭用户-读、用户-写及用户-执行以外的其他权限。在serv_accept中,服务器进程检验这些权限以及套接字用户ID以验证客户进程的身份。
然后采用服务器进程众所周知的路径名填充另一个sockaddr_un结构,最后调用connect函数初始化服务器进程的连接。

#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */
#define CLI_PERM S_IRWXU /* rwx for user only */

/* Create a client endpoint and conncet to a server. Returns fd if all OK, <0 on error. */
int cli_conn(const char *name)
{
    int fd, len, err, rval;
    struct sockaddr_un un;

    /* create a UNIX domain stream socket */
    if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return (-1);

    /* fill socket address structure with our address */    
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
    len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

    unlink(un.sun_path); /* in case it already exists */

    if(bind(fd, (struct sockaddr *)&un, len) < 0)
    {
        rval = -2;
        goto errout;
    }

    if(chmod(un.sun_path, CLI_PERM) < 0)
    {
        rval = -3;
        goto errout;
    }

    /* fill socket address structure with server's address */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
    if(connect(fd, (struct sockaddr *)&un, len) < 0)
    {
        rval = -4;
        goto errout;
    }

    return (fd);

 errout:
    err = errno;
    close(fd);
    errno = err;
    return (rval);
}

————————————————————————————————————————————-

open服务器版本2

以守护进程方式运行open服务器,用一个服务器进程处理所有客户进程的请求。因没有使用fork和exec,仍使用s管道,可在无关进程间传送文件描述符。

客户端
main.c与版本1一样

open.h

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

#define CL_OPEN "open" /* client's request for server */
#define CS_OPEN "/home/yjp/apue/17IPC2/opend2/a" /* server's well-kown name */

int csopen(char *, int);

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 descriptro back */
int csopen(char *name, int oflag)
{
    int len;
    char buf[10];
    struct iovec iov[3];
    static int csfd = -1;

    if(csfd < 0)  /* open conncetion to conn server */
    {
        if((csfd = cli_conn(CS_OPEN)) < 0)
            err_sys("cli_conn error");
    }   

    sprintf(buf, " %d", oflag);  /* oflag to acsii */
    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(csfd, &iov[0], 3) != len)
        err_sys("writev error");

    /* read descriptor, returned errors handled by write() */
 return(recv_fd(csfd, write));
}

服务器端

open.h

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

#define CL_OPEN "open"  /* client's request for server */
#define CS_OPEN "/home/yjp/apue/17IPC2/opend2/a"  /* server's well-kown name */

extern int debug;       /* zonzero 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);

操纵client数组的三个函数
因为服务器处理所有客户进程,所以必须保存每个客户进程连接的状态。在opend.h中什么的client数组实现。以下3个函数操作此数组。
因为服务器进程是守护进程,所以调用log_函数

#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 conncetion request form 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);
}

服务器进程main函数版本2
定义全局变量,处理命令行选项,然后调用loop函数。如果以选项-d “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的loop函数
调用serv_listen创建服务器继承对于客户进程连接的端点。其余部分是一个循环,select返回后可能有如下2种情况:
1、描述符listenfd可能准备好读,意味着客户进程已调用了cli_conn。为此调用serv_accept,然后为新客户进程更新client数组及相关记录。(跟踪记录作为select第一个参数的最高描述符编号,也记下client数组种用到的最大下标。)
2、一个现存的客户进程的连接可能准备好读。意味着客户进程已经终止,或已经发送了一个新请求。如果read返回0(文件结束),可认为一客户进程终止,否则调用request处理新的客户进程请求。
allset描述符集跟踪当前使用的描述符。当新的客户进程连接至服务进程时,此描述符集的某个适当位被打开。当客户进程终止时,这个位就被关闭。因为客户进程的所有描述符(包括与服务器进程的连接)都由内核自动关闭,所以总能知道什么时候一客户进程终止,该终止是否自愿。

#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)
    {
        printf("%s\n", CS_OPEN);
        log_sys("serv_listen error");
    }
    FD_SET(listenfd, &allset);
    maxfd = listenfd;
    maxi = -1;

    for(;;)
    {
        rset = allset;   /* rset gets modifeid 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++)
        {
            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);
                }
            }
        }
    }
}

二、使用poll的loop函数
为使客户进程数量能与打开描述符的数量相当,动态为pollfd结构数组分配空间。
client数组种下标号为0的登机项用于listenfd描述符。于是,client数组种的客户进程下标号与pollfd数组中所用的下标号相同。
新客户连接的到达由listenfd描述符中的POLLIN指示。调用serv_accept接收该连接。
对于一个现存的客户进程,处理来自poll的两个不同事件:由POLLHUP指示的客户进程终止;由POLLIN指示的来自现存客户进程的一个新请求。
调用request处理来自客户进程的新请求。

#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 request 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("mew 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);
                    pollfd[i].fd = -1;
                    close(clifd);
                }
                else  /* process client's request */
                    request(buf, nread, clifd, client[i].uid);
            }
        }
    }
}

request函数版本2
处理客户进程的新请求。调用buf_args,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("requet: %s, from uid %d", buf, uid);

    /* parse the arguments, set options */
    if(buf_args(buf, cli_args) < 0)  /* parse args & set options */
    {
        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;
    }

    if(send_fd(clifd, newfd) < 0)  /* send the descriptor */
        err_sys("send_fd error");
    log_msg("send fd %d over fd %d for %s", newfd, clifd, pathname);
    close(newfd);  /* we're done with descriptor */
}

《unix环境高级编程》--- 高级进程间通信[2]
《unix环境高级编程》--- 高级进程间通信[2]

《unix环境高级编程》--- 高级进程间通信[2]