在socket编程之并发回射服务器3篇文章中,提到了3种设计范式:
多进程
父进程阻塞于accept调用,然后为每个连接创建一个子进程。
多线程
主线程阻塞于accept调用,然后为每个连接创建一个子线程。
I/O复用
主进程阻塞于select调用,select负责监听listenfd和connfd。
本文描述第4种设计范式:prefork。
prefork是指父进程预先派生若干个子进程,然后每个子进程阻塞于accept调用。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <sys/errno.h> #define MAXLINE 4096 #define LISTENQ 10 #define PORT 8888 int tcp_listen(const char *port); pid_t child_make(int i, int listenfd); void child_main(int i, int listenfd); void doEcho(int sockfd); void sig_int(int signo); static int nchildren; static pid_t *pids; int main(int argc, char **argv) { if (argc != 2) { printf("Usage: a.out nchildren "); exit(1); } int listenfd = tcp_listen("8888"); nchildren = atoi(argv[1]); pids = (pid_t*)calloc(nchildren, sizeof(pid_t)); for (int i = 0; i < nchildren; i++) { pids[i] = child_make(i, listenfd); } signal(SIGINT, sig_int); for (; ; ) { pause(); } } int tcp_listen(const char *port) { int listenfd; struct sockaddr_in servaddr; if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket error"); return -1; } bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(PORT); if ( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) { perror("bind error"); return -1; } if ( listen(listenfd, LISTENQ) < 0) { perror("listen error"); return -1; } return listenfd; } pid_t child_make(int i, int listenfd) { pid_t pid; if ( (pid = fork()) > 0) { return pid; } child_main(i, listenfd); } void child_main(int i, int listenfd) { int connfd; struct sockaddr cliaddr; socklen_t clilen; printf("child %ld starting ", (long)getpid()); for (; ; ) { clilen = sizeof(cliaddr); if ( (connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen)) < 0) { if (errno == EINTR) { continue; } else { perror("accept error"); exit(1); } } doEcho(connfd); close(connfd); } } void doEcho(int sockfd) { char buff[MAXLINE]; while (true) { memset(buff, 0, sizeof(buff)); int n = read(sockfd, buff, MAXLINE); if (n < 0) { perror("read error"); exit(1); } else if (n == 0) { printf("client closed "); break; } fputs(buff, stdout); write(sockfd, buff, n); } } void sig_int(int signo) { for (int i = 0; i < nchildren; i++) { kill(pids[i], SIGTERM); } while (wait(NULL) > 0) ; if (errno != ECHILD) { perror("wait error"); exit(1); } exit(0); }
多个进程在同一个listenfd上调用accept,会产生"惊群"问题:
一个连接到来,所有进程都被唤醒,但只有一个进程能够成功获得连接。
还有一点需要补充的是:
父进程应该监视闲置子进程个数,随着所服务客户数的变化动态增减子进程个数。