C/S程序设计范式

socket编程之并发回射服务器3篇文章中,提到了3种设计范式:

多进程

    父进程阻塞于accept调用,然后为每个连接创建一个子进程。

多线程

    主线程阻塞于accept调用,然后为每个连接创建一个子线程。

I/O复用

    主进程阻塞于select调用,select负责监听listenfd和connfd。

本文描述第4种设计范式:prefork

    prefork是指父进程预先派生若干个子进程,然后每个子进程阻塞于accept调用。

#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);
}
View Code

多个进程在同一个listenfd上调用accept,会产生"惊群"问题:

    一个连接到来,所有进程都被唤醒,但只有一个进程能够成功获得连接。

还有一点需要补充的是:

    父进程应该监视闲置子进程个数,随着所服务客户数的变化动态增减子进程个数。

原文地址:https://www.cnblogs.com/gattaca/p/6421290.html