epoll惊群测试

一、udp惊群测试

1、模型

epoll惊群测试_第1张图片

2、代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PROCESS_NUM 2
#define MAXEVENTS 1000

int sock_create_bind(int port){
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in addr;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(port);
    addr.sin_family = AF_INET;

    if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0){
        return -1;
    }

    return fd;
}

int make_noblocking(int fd){
    int val = fcntl(fd, F_GETFL);
    val |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, val) < 0){
        perror("fcntl set");
        return -1;
    }
    return 0;
}

int main(int argc, char const *argv[])
{
    int udp_fd, i;
    struct epoll_event *events;

    struct iovec iov[1];
    struct msghdr msg;
    struct sockaddr sa;
    char buf[1024];

    if (argc < 2){
        printf("Input port\n");
        exit(1);
    }

    if ((udp_fd = sock_create_bind(atoi(argv[1]))) < 0){
        perror("socket_create_bind error");
        exit(errno);
    }

    if (make_noblocking(udp_fd) < 0){
        perror("make_noblocking error");
        exit(errno);
    }

    events = malloc(MAXEVENTS * sizeof(struct epoll_event));
    if (!events){
        perror("malloc err");
        exit(errno);
    }

    for (i = 0; i < PROCESS_NUM; i ++){
        
        int pid = fork();
        
        if (pid == 0){
            int epoll_fd = epoll_create(MAXEVENTS);
            if (epoll_fd < 0){
                perror("Create epoll err");
                exit(errno);
            }

            struct epoll_event event;
            event.events = EPOLLIN | EPOLLET;
            event.data.fd = udp_fd;
            if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, udp_fd, &event) < 0){
                perror("epoll_ctl err");
                exit(errno);
            }

            while (1){
                int num;
                num = epoll_wait(epoll_fd, events, MAXEVENTS, -1);

                // sleep(1);
                printf("process %d get %d events\n", getpid(), num);


                for (i = 0; i < num; i ++){
                    if (!(events[i].events & EPOLLIN)){
                        printf("get error event\n");
                        continue;
                    }else if (events[i].data.fd == udp_fd){
                        while (1){
                            iov[0].iov_base = buf;
                            iov[0].iov_len = sizeof(buf);

                            msg.msg_name = &sa;
                            msg.msg_namelen = sizeof(sa);
                            msg.msg_iov = iov;
                            msg.msg_iovlen = 1;
                            msg.msg_control = NULL;
                            msg.msg_controllen = 0;

                            int n = recvmsg(events[i].data.fd, &msg, 0);

                            if (n < 0){
                                if (errno == EAGAIN){
                                    printf("recvmsg() not ready\n");
                                    break;
                                }else{
                                    perror("recvmsg err");
                                    break;
                                }
                            }

                            printf("read %d bytes: %s\n", n, buf);
                        }
                    }
                }
            }
        }
    }

    for (i = 0; i < PROCESS_NUM; i ++){
        wait(0);
    }

    free(events);
    close(udp_fd);

    return 0;
}

3、测试结果

3.1 客户端发一个包(5B)

3.1.1 内核版本3.10测试结果:两个进程均被唤醒

epoll惊群测试_第2张图片

3.1.2 内核版本4.18测试结果:仅有一个进程被唤醒。

在这里插入图片描述

如果在epoll_wait之后sleep一秒,结果如下
在这里插入图片描述
两个进程均被唤醒,总共收到一个包。

小结:较新版本的内核做了一些优化,不一定会唤醒所有的进程。

二、tcp惊群测试

1、模型

epoll惊群测试_第3张图片

2、代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PROCESS_NUM 2
#define MAXEVENTS 1000

int listen_sock_create_bind_listen(int port){
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(port);
    addr.sin_family = AF_INET;

    if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0){
        return -1;
    }

    if (listen(fd, 100) < 0){
        return -1;
    }

    return fd;
}

int make_noblocking(int fd){
    int val = fcntl(fd, F_GETFL);
    val |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, val) < 0){
        perror("fcntl set");
        return -1;
    }
    return 0;
}

int main(int argc, char const *argv[])
{
    int listen_fd, conn_fd, i;
    struct epoll_event *events;

    struct sockaddr sa;
    char buf[1024];

    if (argc < 2){
        printf("Input port\n");
        exit(1);
    }

    if ((listen_fd = listen_sock_create_bind_listen(atoi(argv[1]))) < 0){
        perror("socket_create_bind error");
        exit(errno);
    }

    conn_fd = accept(listen_fd, NULL, NULL);

    if (conn_fd < 0){
        perror("accept err");
        exit(errno);
    }

    if (make_noblocking(conn_fd) < 0){
        perror("make_noblocking error");
        exit(errno);
    }

    events = malloc(MAXEVENTS * sizeof(struct epoll_event));
    if (!events){
        perror("malloc err");
        exit(errno);
    }

    for (i = 0; i < PROCESS_NUM; i ++){
        
        int pid = fork();
        
        if (pid == 0){
            int epoll_fd = epoll_create(MAXEVENTS);
            if (epoll_fd < 0){
                perror("Create epoll err");
                exit(errno);
            }

            struct epoll_event event;
            event.events = EPOLLIN | EPOLLET;
            event.data.fd = conn_fd;
            if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &event) < 0){
                perror("epoll_ctl err");
                exit(errno);
            }

            while (1){
                int num;
                num = epoll_wait(epoll_fd, events, MAXEVENTS, -1);

                // sleep(1);
                printf("process %d get %d events\n", getpid(), num);


                for (i = 0; i < num; i ++){
                    if (!(events[i].events & EPOLLIN)){
                        printf("get error event\n");
                        continue;
                    }else if (events[i].data.fd == conn_fd){
                        while (1){

                            int n = recv(events[i].data.fd, buf, sizeof(buf), 0);

                            if (n < 0){
                                if (errno == EAGAIN){
                                    printf("recv() not ready\n");
                                    break;
                                }else{
                                    perror("recv err");
                                    break;
                                }
                            }else if (n == 0){
                                printf("client close connection\n");
                                break;
                            }

                            printf("read %d bytes: %s\n", n, buf);
                        }
                    }
                }
            }
        }
    }

    for (i = 0; i < PROCESS_NUM; i ++){
        wait(0);
    }

    free(events);
    close(conn_fd);
    close(listen_fd);

    return 0;
}

3、测试结果

内核版本为3.10和4.18结果一致,唤醒两个进程
epoll惊群测试_第4张图片

三、tcp连接惊群

1、模型

epoll惊群测试_第5张图片

2、代码


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PROCESS_NUM 2
#define MAXEVENTS 1000

int listen_sock_create_bind_listen(int port){
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(port);
    addr.sin_family = AF_INET;

    if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0){
        return -1;
    }

    if (listen(fd, 100) < 0){
        return -1;
    }

    return fd;
}

int make_noblocking(int fd){
    int val = fcntl(fd, F_GETFL);
    val |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, val) < 0){
        perror("fcntl set");
        return -1;
    }
    return 0;
}

int main(int argc, char const *argv[])
{
    int listen_fd, i;
    struct epoll_event *events;

    struct sockaddr sa;
    char buf[1024];

    if (argc < 2){
        printf("Input port\n");
        exit(1);
    }

    if ((listen_fd = listen_sock_create_bind_listen(atoi(argv[1]))) < 0){
        perror("socket_create_bind error");
        exit(errno);
    }

    if (make_noblocking(listen_fd) < 0){
        perror("make_noblocking error");
        exit(errno);
    }

    events = malloc(MAXEVENTS * sizeof(struct epoll_event));
    if (!events){
        perror("malloc err");
        exit(errno);
    }

    for (i = 0; i < PROCESS_NUM; i ++){
        
        int pid = fork();
        
        if (pid == 0){
            int epoll_fd = epoll_create(MAXEVENTS);
            if (epoll_fd < 0){
                perror("Create epoll err");
                exit(errno);
            }

            struct epoll_event event;
            event.events = EPOLLIN;
            event.data.fd = listen_fd;
            if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) < 0){
                perror("epoll_ctl err");
                exit(errno);
            }

            while (1){
                int num;
                num = epoll_wait(epoll_fd, events, MAXEVENTS, -1);

                // sleep(1);
                printf("process %d get %d events\n", getpid(), num);


                for (i = 0; i < num; i ++){
                    if (!(events[i].events & EPOLLIN)){
                        printf("get error event\n");
                        continue;
                    }else if (events[i].data.fd == listen_fd){
                        while (1){

                            // int n = recv(events[i].data.fd, buf, sizeof(buf), 0);
                            int conn_fd = accept(listen_fd, NULL, NULL);

                            if (conn_fd < 0){
                                if (errno == EAGAIN){
                                    printf("accept() not ready\n");
                                    break;
                                }else{
                                    perror("accept err");
                                    break;
                                }
                            }
                            close(conn_fd);

                            printf("process %d accept successful: \n", getpid());
                        }
                    }
                }
            }
        }
    }

    for (i = 0; i < PROCESS_NUM; i ++){
        wait(0);
    }

    free(events);
    close(listen_fd);

    return 0;
}

3、测试结果

3.1 客户端新建tcp连接

3.1.1 内核版本3.10测试结果

epoll惊群测试_第6张图片
唤醒两个进程

3.1.2 内核版本为4.18测试结果

大多数情况下唤醒一个进程
在这里插入图片描述
少数情况下唤醒两个进程
在这里插入图片描述

四、总结

1、无论哪种情况,3.10版本的内核都会造成惊群。
2、4.18内核对epoll惊群有所优化,但是仍然存在惊群现象。

猜测:
1、由于udp报文不可分割,新版内核一般只唤醒一个进程。
2、由于tcp采用字节流的方式,所以内核唤醒多个进程。

总之,新版内核对于epoll惊群做了一些优化,具体细节需要再跟进。

你可能感兴趣的:(Linux编程,Nginx,网络编程)