在通过多进程构建并发服务器时,重要的一点就是父进程对子进程的回收,以节省资源。在一个子进程结束时,内核会产生SIGCHLD信号通知父进程,父进程要捕获该信号,并调用信号处理函数回收子进程的资源,这里用到了signal函数和waitpid函数,waitpid函数和wait函数的最大区别就是waithpid函数可以使父进程处于非阻塞状态,即如果没有子进程结束,waitpid函数可以不阻塞父进程。
accept函数会阻塞父进程,它是慢系统调用,当阻塞于某个慢系统调用(accept)的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用(accept)可能返回一个EINTR错误,有些内核自动重启某些被中断的系统调用,但是为了便于移植,我们最好自己编写重启系统调用。
server.h
#ifndef SERVER_H #define SERVER_H #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <signal.h> #include <unistd.h> #include <sys/wait.h> #define PORT 3333 void str_echo(int sockfd); #endif // SERVER_H
server.c
#include "server.h" #define MAXLINE 1024 void sig_child(int signo); int main(void) { pid_t pid; int listenfd, connfd; struct sockaddr_in serveraddr, clientaddr; socklen_t clientlen = 0; if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { perror("socket error"); exit(1); } bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(PORT); serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); if( bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0 ) { perror("socket error"); exit(1); } if( listen(listenfd, 5) < 0 ) { perror("socket error"); exit(1); } /*捕获SIGCHLD信号,回收子进程*/ signal(SIGCHLD, sig_child); for( ; ; ) { clientlen = sizeof(clientaddr); if( (connfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientlen)) < 0 ) { /*如果是中断错误,要重启accept函数*/ if(errno == EINTR) continue; else { perror("socket error"); exit(1); } } if( (pid = fork()) < 0 ) { perror("socket error"); exit(1); } else if(pid == 0) /*创建子进程执行处理程序*/ { close(listenfd); /*关闭监听套接字描述符*/ str_echo(connfd); exit(0); /*子进程退出时关闭所有的套接字描述符*/ } close(connfd); /*父进程关闭内核新产生的用于连接的套接字*/ } exit(0); } void sig_child(int signo) { pid_t pid; int stat; while( (pid = waitpid(-1, &stat, WNOHANG)) > 0 ) /*非阻塞的捕获结束的子进程*/ printf("child %d terminated\n", pid); return; } void str_echo(int sockfd) { ssize_t n; char buf[MAXLINE]; while(1) { if( (n = read(sockfd, buf, MAXLINE)) < 0 ) { if(errno == EINTR) continue; else { perror("str_echo: read error"); exit(1); } } if(n == 0) break; if( write(sockfd, buf, n) != n ) { perror("str_echo: write error"); exit(1); } } }
client.h
#ifndef CLIENT_H #define CLIENT_H #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <signal.h> #include <unistd.h> #include <sys/wait.h> #include <arpa/inet.h> #define PORT 3333 void str_cli(FILE *, int sockfd); #endif // CLIENT_H
client.c
#include "client.h" #define MAXLINE 1024 int main(int argc, char **argv) { int i, sockfd[5]; struct sockaddr_in serveraddr; if(argc != 2) { perror("usage: client"); exit(1); } for(i=0; i < 5; i++) { if( (sockfd[i] = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket error"); exit(1); } bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(PORT); inet_pton(AF_INET, argv[1], &serveraddr.sin_addr); if( connect(sockfd[i], (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0 ) { perror("connect error"); exit(1); } } str_cli(stdin, sockfd[0]); exit(0); } void str_cli(FILE *fp, int sockfd) { int n; char sendline[MAXLINE], recvline[MAXLINE]; while( fgets(sendline, MAXLINE, fp) != NULL ) { if( write(sockfd, sendline, strlen(sendline)) != strlen(sendline) ) { perror("str_cli: write error"); exit(1); } if( (n = read(sockfd, recvline, MAXLINE)) < 0 ) { perror("str_cli: read error"); exit(1); } recvline[n] = '\0'; fputs(recvline, stdout); } }