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