Linux 多进程监听 socket

首先来看代码

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/wait.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1 ) {
        perror("socket");
        return -1;
    }
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
        perror("bind");
        return -1;
    }
    if (listen(sockfd, 20) == -1) {
        perror("listen");
        return -1;
    }
    for (int i = 0; i < 2; i++) {
        pid_t pid = fork();
        if (pid < 0) {
            perror("fork");
        } else if (pid == 0) {
            // child process
            while (1) {
                struct sockaddr_in cli_addr;
                memset(&cli_addr, 0, sizeof(cli_addr));
                socklen_t cli_addr_len = 0;
                int connfd = accept(sockfd, (struct sockaddr*)&cli_addr, &cli_addr_len);
                if (connfd == -1) {
                    perror("accept");
                    return -1;
                }
                char *cli_addr_str = inet_ntoa(cli_addr.sin_addr);
                int cli_addr_port = ntohs(cli_addr.sin_port);
                printf("pid: %d, cli addr: %s:%d\n", getpid(), cli_addr_str, cli_addr_port);
                char buff[4096] = {0};
                if (recv(connfd, buff, sizeof(buff), 0) == -1) {
                    perror("recv");
                    return -1;
                }
                char html_resp[] = "HTTP/1.1 200 OK\r\n\r\nhello";
                if (send(connfd, html_resp, strlen(html_resp), 0) == -1) {
                    perror("send");
                    return -1;
                }
                close(connfd);
            }        
        } else if (pid > 0) {
            // parent process
            printf("forked pid: %d\n", pid);
        }
    }
    while (1) {
        pid_t end_pid = wait(NULL);
        if (end_pid == -1) {
            perror("wait");
            return -1;
        }
        printf("child process %d end\n", end_pid);
    }
    close(sockfd);
    return 0;
}

编译一下

g++ -Wall -g main.cpp -o addrreuse.exe

多次模拟请求

wget http://localhost:8080/123

服务端的输出如下:

forked pid: 23278
forked pid: 23279
pid: 23278, cli addr: 0.0.0.0:0
pid: 23279, cli addr: 0.0.0.0:0
pid: 23278, cli addr: 0.0.0.0:0
pid: 23279, cli addr: 0.0.0.0:0

事实证明,是可以多个进程来 accept 一个 sockfd 的,(好吧,高手当我在说废话就行了),然后注意到有一个东西叫惊群效应,http://blog.csdn.net/chenxinl/…

惊群效应:指一个fd的事件被触发后,等候这个fd的所有线程/进程都被唤醒。

低版本的linux内核在accept这边有所谓的“惊群效应”,是指一个链接过来,会把堵塞在accept上的所有子进程都唤醒;但是只有一个子进程都读取到链接,其他子进程唤醒后发现没有数据,继续睡觉去了;这样就产生了大量的进程切换开销。 但目前的内核版本已经修复了这个问题,一个链接过来,内核只会唤醒一个子进程出来accept,这样就不用担心惊群效应了。

但是对于其他类型的fd,惊群效应还是存在的,而且是必须的;比如多个进程wait在一个pipe的读事件上,有数据过来后,多个进程都会被唤醒,因为内核也不知道这些数据就给一个进程读的,还是每个进程都读一点,这个逻辑是应用层控制的;而accept也相当于一个读事件,但内核确认一个进程接受一个链接就可以了。

同理,如果多个子进程把同一个listen的fd添加到各自的epoll_wait中,有链接过来会导致所有的子进程唤醒,这也会导致惊群效应;但这个是应用层面需要解决的问题。

关于httpserver惊群效应和如何处理:http://bbs3.chinaunix.net/viewthread.php?action=printable&tid=1676724

但是另外看到这里,http://www.iteye.com/topic/561…

在2.6内核下,内核只会wakeup一个进程。就是说,2.6的内核下面,不会出现惊群的现象。

Leave a Reply

Your email address will not be published. Required fields are marked *