TCP并发服务器程序,每个客户一个子进程
传统上并发服务器调用fork派生一个子进程来处理每个客户。这使得服务器能够同时为多个客户服务,每个进程一个客户。客户数目的唯一限制是操作系统对以其名义运行服务器的用户ID能够同时有多个子进程的限制。并发服务器的问题在于为每个客户现场fork一个子进程比较耗费CPU时间。
本函数为每个客户连接fork一个子进程并处理来自垂死的子进程的SIGCHLD信号。我们还捕获由键入终端中断按键产生的SIGINT信号。在客户运行完毕之后我们键入该键以显示服务器程序运行所需的CPU时间。下面给出了SIGINT信号处理函数。这是一个信号处理函数不返回而直接终止进程。
getrusage函数被调用了两次,分别返回调用进程(RUSAGE_SELF)和它的所有已终止子进程(RUSAGE_CHILDREN)的资源利用统计。所显示的值包括总的系统时间(内核在代表调用进程执行系统调用上耗费的CPU时间)。
客户在建立与服务器的连接之后通过该连接写出一行文本,指出需由服务器发送多少字节的数据给客户。这一点与HTTP有些类似:客户发送一个小请求,服务器响应以所期望的信息(例如一个HTML文件或一副GIF图片),在HTTP应用系统中,服务器通常在发送回所请求的数据之间就关闭连接,不过较新的版本允许使用持续连接,为在某个时限以内到达的额外客户请求继续保持TCP连接开发一段时间。
在web_child函数中,服务器允许来自客户的额外请求。
#include "unp.h" void pr_cpu_time(void) { double user, sys; struct rusage myusage, childusage; if (getrusage(RUSAGE_SELF, &myusage) < 0) err_sys("getrusage error"); if (getrusage(RUSAGE_CHILDREN, &childusage) < 0) err_sys("getrusage error"); user = (double) myusage.ru_utime.tv_sec + myusage.ru_utime.tv_usec/1000000.0; user += (double) childusage.ru_utime.tv_sec + childusage.ru_utime.tv_usec/1000000.0; sys = (double) myusage.ru_stime.tv_sec + myusage.ru_stime.tv_usec/1000000.0; sys += (double) childusage.ru_stime.tv_sec + childusage.ru_stime.tv_usec/1000000.0; printf(" user time = %g, sys time = %g ", user, sys); } void sig_int(int signo) { void pr_cpu_time(void); pr_cpu_time(); exit(0); } void sig_chld(int signo) { pid_t pid; int stat; while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) printf("child %d terminated ", pid); return; } #define MAXN 16384 /* max # bytes client can request */ void web_child(int sockfd) { int ntowrite; ssize_t nread; char line[MAXLINE], result[MAXN]; for ( ; ; ) { if ( (nread = Readline(sockfd, line, MAXLINE)) == 0) return; /* connection closed by other end */ /* 4line from client specifies #bytes to write back */ ntowrite = atol(line); if ((ntowrite <= 0) || (ntowrite > MAXN)) err_quit("client request for %d bytes", ntowrite); Writen(sockfd, result, ntowrite); } } int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; void sig_chld(int), sig_int(int), web_child(int); socklen_t clilen, addrlen; struct sockaddr *cliaddr; if (argc == 2) listenfd = Tcp_listen(NULL, argv[1], &addrlen); else if (argc == 3) listenfd = Tcp_listen(argv[1], argv[2], &addrlen); else err_quit("usage: serv01 [ <host> ] <port#>"); cliaddr = Malloc(addrlen); Signal(SIGCHLD, sig_chld); Signal(SIGINT, sig_int); for ( ; ; ) { clilen = addrlen; if ( (connfd = accept(listenfd, cliaddr, &clilen)) < 0) { if (errno == EINTR) continue; /* back to for() */ else err_sys("accept error"); } if ( (childpid = Fork()) == 0) { /* child process */ Close(listenfd); /* close listening socket */ web_child(connfd); /* process request */ exit(0); } Close(connfd); /* parent closes connected socket */ } }