网络编程中的惊群效应——1

网络编程中的惊群效应——1

参考博客:https://www.cnblogs.com/Anker/p/7071849.html
在这里插入图片描述
  惊群效应:简单的说就是多个进程同时等待网络的连接事件,当真正来了一个连接的时候会把所有监听的进程都唤醒,而最终只有一个进程能处理事件成功,其他的进程在处理该事件失败后重新休眠或其他。这样的现象带来最主要的问题是造成性能浪费。

  打个比方,比如说fork4个进程,这4个进程都在各自的进程工作函数里使用父进程的socket文件描述符调用accept函数,当来了一个客户端的连接请求后,4个进程都会accpet到请求,但是只有一个能成功,其他进程都会返回-1,显然造成了性能的浪费,因为本来只要唤醒一个进程处理就行了,而此时却唤醒了4个。
  不过呢,据参考博客里的描述,Linux2.6版本以后,内核内核已经解决了accept()函数的“惊群”问题,大概的处理方式就是,当内核接收到一个客户连接后,只会唤醒等待队列上的第一个进程或线程。所以,如果服务器采用accept阻塞调用方式,在最新的Linux系统上,已经没有“惊群”的问题了。
  因此,我在实际运行代码的时候也并没有看到惊群效应,应该是系统进行了优化吧。

  PS:以下代码来自参考博客,非原创。推荐手敲以下代码,个人觉得照着手敲一遍印象和理解都更深刻些。

accept惊群效应

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

#define IP   "127.0.0.1"
#define PORT  8888
#define WORKER 4

int worker(int listenfd, int i)
{
    while (1) {
        printf("I am worker %d, begin to accept connection.\n", i);
        struct sockaddr_in client_addr;
        socklen_t client_addrlen = sizeof( client_addr );
        int connfd = accept( listenfd, ( struct sockaddr* )&client_addr, &client_addrlen );
        if (connfd != -1) {
            printf("worker %d accept a connection success.\t", i);
            printf("ip :%s\t",inet_ntoa(client_addr.sin_addr));
            printf("port: %d \n",client_addr.sin_port);
        } else {
            printf("worker %d accept a connection failed,error:%s", i, strerror(errno));
            close(connfd);
        }
    }
    return 0;
}

int main()
{
    int i = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton( AF_INET, IP, &address.sin_addr);
    address.sin_port = htons(PORT);
    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);

    int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    for (i = 0; i < WORKER; i++) {
        printf("Create worker %d\n", i+1);
        pid_t pid = fork();
        /*child  process */
        if (pid == 0) {
            worker(listenfd, i);
        }

        if (pid < 0) {
            printf("fork error");
        }
    }

    /*wait child process*/
    int status;
    wait(&status);
    return 0;
}

运行代码,客户端直接使用以下命令即可。

telnet 127.0.0.1 8888

在这里插入图片描述

epoll惊群效应

  epoll的思路差不多。
  第一阶段,在main函数里将服务器端的文件描述符sfd初始化绑定端口等操作,并设置为非阻塞模式,然后添加到epoll的红黑树上,准备工作到此结束。
  第二阶段,创建4个进程,并分别在子进程的工作函数woker中调用epoll_wait监听,当来消息后,epoll_wait会返回,进行accept调用,同样只有一个能成功,其他三个均失败,实验测试也符合实际情况。

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <unistd.h>


#define IP   "127.0.0.1"
#define PORT  8888
#define PROCESS_NUM 4
#define MAXEVENTS 64

static int create_and_bind() {
    int fd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, IP, &serveraddr.sin_addr);
    serveraddr.sin_port = htons(PORT);
    bind(fd, (struct sockaddr *) &serveraddr, sizeof(serveraddr));
    return fd;
}

static int make_socket_non_blocking(int sfd) {
    int flags, s;
    flags = fcntl(sfd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl");
        return -1;
    }
    flags |= O_NONBLOCK;
    s = fcntl(sfd, F_SETFL, flags);
    if (s == -1) {
        perror("fcntl");
        return -1;
    }
    return 0;
}

int worker(int sfd, int efd, struct epoll_event *events, int k) {
    /* The event loop */
    while (1) {
        int n, i;
        n = epoll_wait(efd, events, MAXEVENTS, -1);
        sleep(1);
        printf("worker %d return from epoll_wait!\n", k);
        for (int i = 0; i < n; i++) {
            /* 文件描述符发生错误,被挂断,不可读 */
            if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) {
                fprintf(stderr, "epoll error\n");
                close(events[i].data.fd);
                continue;
            }
                /* 到来一个连接请求 */
            else if (sfd == events[i].data.fd) {
                struct sockaddr in_addr;
                socklen_t in_len;

                char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
                in_len = sizeof(in_addr);
                int infd = accept(sfd, &in_addr, &in_len);
                if (infd == -1) {
                    printf("woker %d accept failed!\n", k);
                    break;
                }
                printf("woker %d accept successed!\n", k);
                /* 将connfd设置为非阻塞并加入到epoll的监听树上 */
                close(infd);
            }
        }
    }
    return 0;
}

int main() {
    int sfd, s;
    int efd;
    struct epoll_event event;
    struct epoll_event *events;
    sfd = create_and_bind();
    if (sfd == -1) {
        abort();
    }
    s = make_socket_non_blocking(sfd);
    if (s == -1) {
        abort();
    }
    s = listen(sfd, SOMAXCONN);
    if (s == -1) {
        perror("listen");
        abort();
    }

    efd = epoll_create(MAXEVENTS);
    if (efd == -1) {
        perror("epoll_create");
        abort();
    }

    event.data.fd = sfd;
    event.events = EPOLLIN; /* 读事件 */
    s = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
    if (s == -1) {
        perror("epoll_ctl");
        abort();
    }

    events = calloc(MAXEVENTS, sizeof(event));
    for (int i = 0; i < PROCESS_NUM; i++) {
        printf("Create worker %d\n", i + 1);
        int pid = fork();
        if (pid == 0) { /* 子进程 */
            printf("I am %dth sub process,pid = %d!", i, pid);
            worker(sfd, efd, events, i);    /* 新进程开始epoll监听 */
        }
    }

    int status;
    wait(&status);
    free(events);
    close(sfd);
    return EXIT_SUCCESS;
}

在这里插入图片描述
不过在linux 4.5之后已经部分解决了epoll的惊群问题,出现EPOLLEXCLUSIVE选项,详细实验我在《网络编程中的惊群效应——2》这篇博文里写了。下面直接给出实验结果:

  • 不添加 EPOLLEXCLUSIVE
项目LT模式ET模式
共享epoll_fd存在不存在
独享epoll_fd存在存在
  • 添加EPOLLEXCLUSIVE
项目LT模式ET模式
共享epoll_fd存在不存在
独享epoll_fd不存在不存在

其他

  之前的一些面经上看到不少问惊群效应和雪崩的,比较典型的是accept、epoll、select、poll等,这都和上面的实验比较类似。
  不过nginx的惊群效应和处理方式也是很重要的,此外还有个雪崩效应一般这两个词配套出现。

推荐的相关博文

http://blog.csdn.net/russell_tao/article/details/7204260

http://pureage.info/2015/12/22/thundering-herd.html

http://blog.chinaunix.net/uid-20671208-id-4935141.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值