zoukankan      html  css  js  c++  java
  • TCP并发服务器(二)——预创建子进程,accept不上锁

    TCP并发服务器(二)——预创建子进程,accept不上锁

    1.说明

    这是预创建进程中速度最快的。

    适用于源自BSD的内核,但是accept是一个库函数的System V可能不允许这么做(因此需要accept上锁保护)。

     

    2.优缺点

    无需引入父进程执行fork的开销就能处理新的客户。缺点时必须在启动阶段猜测需要预创建多少子进程。

    如果某个时刻客户数恰好等于子进程总数,新的客户将被忽略直到至少一个子进程重新可用。

    在实际上客户没有被忽略,内核为每个新客户完成三路握手,直到listen调用的backlog数为止。然后accept在已连接队中取出描述符。

    尽管connect可能立即返回,但是请求可能在一段时间以后才被服务器处理。

     

    3.解决办法

    可以监控闲置子进程数,动态增减子进程数。

     

    4.代码

    #include "unp.h"
    #include <vector>
    #include <sys/mman.h>        //for mmap()
    
    using std::vector;
    
    //共享内存首地址
    static long *cptr;;
    static int nchildren;
    static vector<int> pids;
    
    void sig_int(int signo)
    {
        for (int i = 0; i < nchildren; ++i) {
            kill(pids[i], SIGTERM);
        }
        while (wait(NULL) > 0) {
            ;
        }
        if (errno != ECHILD) {
            err_sys("wait error");
        }
    
        DPRINTF("The number of child process accept.");
        for (int i = 0; i < nchildren; ++i) {
            DPRINTF("%ld", cptr[i]);
        }
    
        void pr_cpu_time(void);
        pr_cpu_time();
    
        exit(0);
    }
    
    int main(int argc, char *argv[])
    {
        int listenfd;
        socklen_t addrlen;
        if (argc == 3) {
            listenfd = Tcp_listen(NULL, argv[1], &addrlen);
        } else if (argc == 4) {
            listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
        } else {
            err_quit("Usage: a.out [ <host> ] <port#> <#children>");
        }
    
        nchildren = atoi(argv[argc - 1]);
        pids.resize(nchildren);
        //查看连接在子进程的分布
        //使用共享内存未每个子进程的连接计数
        //fork之后父进程和子进程共享cptr
        long *meter(int);
        cptr = meter(atoi(argv[argc - 1]));
        //cptr = (long*)Calloc(nchildren, sizeof(long));
    
        //创建子进程
        pid_t child_make(int i, int listenfd, int addrlen);
        for (int i = 0; i < atoi(argv[argc - 1]); ++i) {
            pids[i] = child_make(i, listenfd, addrlen); 
        }
    
        //SIGCHLD处理函数
        void sig_chld(int);
        Signal(SIGCHLD, sig_chld);
        Signal(SIGINT, sig_int);
    
        for ( ; ;) {
            ;             //child does everything
        } //end for(;;)
    
        return 0;
    }
    
    void sig_chld(int)
    {
        static int cnt = 0;
        pid_t pid;
        int stat;
        //param1: 想要等待的PID;-1: 等待第一个终止的子进程
        //param2: 子进程的终止状态(整数)
        //param3: 附加选项;WNOHANG:没有子进程终止时,不阻塞
        while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) {    //成功:返回进程ID > 0, 出错:0或-1
            DPRINTF("Waitpid for %d child process
    ", ++cnt);
            ;
        }
    
        return;
    }
    
    pid_t child_make(int i, int listenfd, int addrlen)
    {
        void child_main(int, int, int);
        pid_t pid = Fork();
        if (pid > 0) {        //parent
            return pid;
        }
        child_main(i, listenfd, addrlen);
    }
    
    void child_main(int i, int listenfd, int addrlen)
    {
        DPRINTF("child %ld starting
    ", (long)getpid());
        void web_child(int);
        for ( ; ; ) {
            DPRINTF("Wait for a connection");
            int connfd = Accept(listenfd, NULL, NULL);
            //共享内存计数增加
            ++cptr[i];
            DPRINTF("Accept a connection, total accept %ld connection", cptr[i]);
    
            web_child(connfd);
            Close(connfd);
        }
    }
    
    long *meter(int nchildren)
    {
    #ifdef MAP_ANON            //匿名映射
        long *ptr = (long*)Mmap(0, nchildren * sizeof(long), PROT_READ | PROT_WRITE, 
                        MAP_ANON | MAP_SHARED, -1, 0);
    #else
        int fd = Open("/dev/zero", O_RDWR, 0);
        long *ptr = (long*)Mmap(0, nchildren * sizeof(long), PROT_READ | PROT_WRITE,
                         MAP_SHARED, fd, 0);
        //已完成文件到进程地址空间的映射,可以关闭原文件
        Close(fd);
    #endif
    
        return ptr;
    }

    5.惊群

    由于子进程阻塞在accept上,但是当一个连接到达时只会有一个子进程获得连接。但是所有的子进程都被唤醒了,这种现象就是惊群,会影响程序性能。

    当子进程过多时惊群现象会更加严重。

    我们可以不阻塞于accept,而是select上。

    但是多个进程在引用同一个套接字描述符上调用select会发生select冲突。此时会增加CPU时间,因为增加了系统调用。

    结论:如果多个进程阻塞在引用同一个实体(如:套接字或普通文件)的描述符上,最好直接阻塞于诸如accept之类的函数,而不是select。

     

  • 相关阅读:
    LeetCode Single Number
    Leetcode Populating Next Right Pointers in Each Node
    LeetCode Permutations
    Leetcode Sum Root to Leaf Numbers
    LeetCode Candy
    LeetCode Sort List
    LeetCode Remove Duplicates from Sorted List II
    LeetCode Remove Duplicates from Sorted List
    spring MVC HandlerInterceptorAdapter
    yum
  • 原文地址:https://www.cnblogs.com/hancm/p/3864238.html
Copyright © 2011-2022 走看看