epoll实现同时承载100w客户端的数量

概念

先表明,这里是让epoll能够同时承受100w的连接,不针对业务处理。

对于百万并发的业务处理,其前提条件就是要同时承受住100w的连接。

程序源码

  epoll的源码直接给出来

/*
    支持百万并发的 reactor
    1.其主要限制在于Linux系统的限制,需要修改一些参数

    测试: 3个标准
    1.wrk       --> qps 一秒钟能处理的请求量
    2.并发连接量 --> 
    3.iperf     --> 测试带宽(硬件能力)

    // gcc reactor.c -oreactor -mcmodel=medium -g
*/

#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>

#define PORT 2480
#define MAX_EVENTS 1024

#define MAX_BUFFER_SIZE 1024

// 事件结构 -- 封装 读写缓冲区,读写事件处理方法
typedef int (*RCALLBACK)(int fd);

typedef struct _CONN_ITEM_{

    int fd;
    int rLen;
    int wLen;
    char readBuffer[MAX_BUFFER_SIZE];
    char writeBuffer[MAX_BUFFER_SIZE];

    // 函数指针 -- 这里我们根据 epoll返回的特性分为读事件和写事件
    RCALLBACK send_cb;
    RCALLBACK recv_cb;

}CONN_ITEM, LPTIEM;

CONN_ITEM connLists[1048576] = {0};
int epfd;

// 读事件
int send_cb(int fd){

    int Len = connLists[fd].wLen;
    int count = send(fd, connLists[fd].readBuffer, Len, 0);


    struct epoll_event evnet;
    evnet.data.fd = fd;
    evnet.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &evnet);

}

// 写事件
int recv_cb(int fd){

    char *buffer = connLists[fd].readBuffer;
    int rLen = connLists[fd].rLen;

    int count = recv(fd, buffer + rLen, MAX_BUFFER_SIZE - rLen, 0);
    if(0 == count){
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
        close(fd);
    }
    else
    {
        connLists[fd].rLen += count;

        // 回写数据
        memcpy(connLists[fd].writeBuffer, connLists[fd].readBuffer, connLists[fd].rLen);
        connLists[fd].rLen -= 0;
        connLists[fd].wLen = connLists[fd].rLen;

        // 将事件修改为触发写事件
        struct epoll_event event;
        event.data.fd = fd;
        event.events = EPOLLOUT;               
        epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);
    }
}


// 连接事件 - 属于读事件
int accept_cb(int fd){

    int ret = 0;

    struct sockaddr_in clienAddr;
    socklen_t len = sizeof(clienAddr);
    int connfd = accept(fd, (struct sockaddr*)&clienAddr, &len);
    if(connfd){
        // 将新的 fd加入到epoll中
        struct epoll_event event;
        event.data.fd = connfd;
        event.events = EPOLLIN;
        connLists[connfd].send_cb = send_cb;
        connLists[connfd].recv_cb = recv_cb;

        connLists[connfd].rLen = 0;
        connLists[connfd].wLen = 0;
        memset(connLists[connfd].readBuffer, 0, MAX_BUFFER_SIZE);
        memset(connLists[connfd].writeBuffer, 0, MAX_BUFFER_SIZE);

        ret = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &event);
        if(-1 == ret){
            perror("epoll_ctl");
        }
    }

}

int main(){

    int ret = 0;
    int i = 0;

    // TCP
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd){
        perror("socket");
        return -1;
    }

    // addr
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(PORT);

    ret = bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr_in));
    if(-1 == ret){
        perror("bind");
        return -1;
    }

    // 这个地方的第二个参数,表示未连接的个数能容纳几个,防止syn泛洪
    listen(sockfd, 10);

    // 建立 epoll,参数以无意义(内核>=2.6.8) > 0
    epfd =  epoll_create(10);
    if(-1 == ret){
        perror("epoll_create");
        return -1;
    }

    // 将sockfd加入到 epoll中
    struct epoll_event event;
    event.data.fd = sockfd;
    event.events = EPOLLIN;           // 指定监控的事件
    connLists[sockfd].fd = sockfd;
    //connLists[sockfd].accept_cb = accept_cb;
    connLists[sockfd].recv_cb = accept_cb;
    connLists[sockfd].send_cb = send_cb;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
    if(-1 == ret){
        perror("epoll_ctl");
        return -1;
    }

    // 不断的监视消息 - 可能会触发多次
    struct epoll_event events[MAX_EVENTS];
    while(1){

        int ready = epoll_wait(epfd, events, MAX_EVENTS, -1);
        if(-1 == ret){
            perror("epoll_wait");
            return -1;
        }

        for(i = 0; i < ready; ++i){
            
            int fd = events[i].data.fd;
            if(events[i].events & EPOLLIN){
                // 读事件
                ret = connLists[fd].recv_cb(fd);
            }
            else if(events[i].events & EPOLLOUT){
                // 写事件
                ret = connLists[fd].send_cb(fd);
            }
        }
    }
    
    close(epfd);
    close(sockfd);

    return 0;
}



测试收发数据:

 测试程序

        逻辑,这里我们没有那么多IP进行测试,只能不断的建立连接,断开连接。在实际的应用过程中也是这样的,在后续的工作中,需要测试并发量的情况下,一下代码程序依旧适用。

正片开始

多试几次,发现每次都在一千左右崩溃掉了。

为什么会出现这种情况?

        其实不难理解,我们能够知道一个socket会对于一个fd(伪文件),对于一个进程而言,能够建立的连接数量是有限的,我们可以进行修改。

ulimit -n 1048576(一百万) --> 1024 *1024

修改完测试:

这里的情况是,在Linux中默认允许分配端口大概是2万多,我们改下配置文件。

客户端需要做更改,客户端的端口是随机分配的,到两万左右就嗝屁了。(这个根据内核版本决定)。

增加一行:

fs.file-max = 1048576 一个进程能打开的最哒的文件描述符的大小

server端直接崩了,出现这种情况大概率是内存不足的问题。换句话说,当协议盏暂用内存的比例到达一定比例的时候,进程会被操作系统强制终止。我们需要调整配置...

sudo modprobe ip_conntrack

调整完再测试下:

现在能跑到五十多万,显示连接被拒绝,说明服务端断掉了,我们继续调整配置。

接下来就是不断的调整这些数值...实在跑不上去,有可能是网络问题或者虚拟机内存问题,直接把虚拟机内存再加大。

再调整...mmp今天一定把他弄上去

调试小技巧:

现在真的有点烦了,愣是上不去...(淡定、淡定)

经验总结:

客户端尽量把缓冲区调小,服务端缓冲区要适当。

缓冲区越小,建立连接的速度越慢。

总要破百了,呜呜...感动落泪

程序员都是孤独虫....

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@陈一言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值