1.reactor实现
对epoll多路复用实现的TCP服务器中的各功能进行封装,从而提高其代码可读性,可扩展性,可维护性,复用性
关键之处在于从对io操作的管理转向对事件的管理,实现"事件注册->等待->分发->响应->更新"的流程
1.1全局变量
epoll实例的全局变量,各函数中操作同一个epoll
            
            
              cpp
              
              
            
          
          int epfd = 0;
        声明处理事件的回调函数类型,统一接口便于分发事件
            
            
              cpp
              
              
            
          
          typedef int (*RCALLBACK)(int fd);
        具体回调函数的声明
            
            
              cpp
              
              
            
          
          int recv_cb(int fd);
int accept_cb(int fd);
int send_cb(int fd);
        连接信息结构体,将不同fd的数据与行为进行封装
            
            
              cpp
              
              
            
          
          struct conn{
    //套接字,客户端fd或者监听fd
    int fd;
    
    //读写数据的缓冲区数组和大小
    char rbuffer[BUFFER_LENGTH];
    int rlength;
    char wbuffer[BUFFER_LENGTH];
    int wlength;
    //把回调函数的指针加入结构体
    RCALLBACK send_callback;
    //互斥状态的两个回调函数指针,共同体的形式加入结构体(客户端fd调用recv_callback,监听fd调用accept_callback)
    union{
        RCALLBACK recv_callback;
        RCALLBACK accept_callback;
    } r_action;
};
        共同体,所有成员共用同一块内存空间,节省内存
用数组存储所有连接
            
            
              cpp
              
              
            
          
          struct conn conn_list[CONNECTION_SIZE] = {0};
        1.2封装函数
创建服务器,创建套接字,绑定地址,启动监听fd
            
            
              cpp
              
              
            
          
          int init_server(unsigned short port){
    //创建TCP流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    //设置服务器地址信息,IPv4,绑定所有本地网卡,绑定端口
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0
    servaddr.sin_port = htons(port); //0-1023
    //绑定套接字到服务器地址和端口
    if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
        printf("bind failed: %s\n", strerror(errno));
    }
    //开启监听套接字,最大等待队列为10
    listen(sockfd, 10);
    printf("listen finished: %d\n", sockfd);
    return sockfd;
}
        添加/修改epoll事件
            
            
              cpp
              
              
            
          
          int set_event(int fd, int event, int flag){
    //flag非零时,添加epoll事件
    if(flag){//no-zero add
        //创建epoll_event类型变量
        struct epoll_event ev;
        //设定关注的事件类型
        ev.events = event;
        //将当前fd存入ev的data.fd对象中
        ev.data.fd = fd;
        //添加到epfd的epoll实例中
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    }else{//zero mod
        struct epoll_event ev;
        ev.events = event;
        ev.data.fd = fd;
        //修改epfd中的epoll实例
        epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
    }
}
        接收客户端连接请求,创建客户端fd并完成初始化
            
            
              cpp
              
              
            
          
          //listen(sockfd) --> EPOLLIN --> accept_cb
int accept_cb(int fd){
    //定义客户端地址结构体,计算长度
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    //调用accept函数,从监听fd接收数据并创建对应地址的客户端fd
    int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
    printf("accept finished: %d\n", clientfd);
    //调用event_register函数初始化连接信息,关注可读事件
    event_register(clientfd, EPOLLIN);
    return 0;
}
        为epoll实例中添加新的客户端连接
            
            
              cpp
              
              
            
          
          int event_register(int fd, int event){
    //初始化连接信息,绑定fd,设置回调函数
    conn_list[fd].fd = fd;
    //添加连接,共同体中选择recv_cb回调函数
    conn_list[fd].r_action.recv_callback = recv_cb;
    conn_list[fd].send_callback = send_cb;
    //初始化缓冲区
    memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH);
    conn_list[fd].rlength = 0;
    memset(conn_list[fd].wbuffer, 0, BUFFER_LENGTH);
    conn_list[fd].wlength = 0;
    //调用set_event函数添加事件,并监控可读事件
    set_event(fd, EPOLLIN, 1);
}
        客户端fd触发EPOLLIN事件的回调函数,接收客户端发送的数据
            
            
              cpp
              
              
            
          
          int recv_cb(int fd){
    //调用recv接收客户端数据,存入该连接的接收缓冲区,通过接收数据长度判断接收状态
    int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);
    //状态为零时客户端主动断开连接
    if(count == 0){
        printf("clientfd disconnect: %d\n", fd);
        //关闭断开的客户端fd
        close(fd);
        //将该fd从epfd的epoll实例中删除,不再监控
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);// 无需ev结构体
        return 0;
    }
    //存储数据长度
    conn_list[fd].rlength  = count;
    //打印接收数据
    printf("RRECV: %s\n", conn_list[fd].rbuffer);
#if 1 //echo  回声模式开关,1开启
    //将接收缓冲区的数据和数据长度存储到发送缓冲区中,用于send函数
    conn_list[fd].wlength = conn_list[fd].rlength;
    memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, conn_list[fd].wlength);
#endif
    //调用set_event函数修改该fd的关注事件为EPOLLOUT可写
    //让epoll后续触发可写事件,调用send_cb发送缓冲区中数据
    set_event(fd, EPOLLOUT, 0);
    return count;
}
        客户端fd触发可写事件后,发送缓冲区中的数据
            
            
              cpp
              
              
            
          
          int send_cb(int fd){
    //调用send函数将发送缓冲区中的数据发送至客户端fd
    int count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
    //调用set_event函数将该fd连接信息中的关注事件修改为可读,从而再次接收数据
    set_event(fd, EPOLLIN, 0);
    return count;
}
        1.3主函数
设置端口
            
            
              cpp
              
              
            
          
          unsigned short port = 2000;
        初始化服务器,创建套接字,绑定地址,开始监听,返回监听套接字
            
            
              cpp
              
              
            
          
          int sockfd = init_server(port);
        调用epoll_create函数创建一个epoll实例
            
            
              cpp
              
              
            
          
          epfd = epoll_create(1);
        将监听套接字存入连接列表中
            
            
              cpp
              
              
            
          
          conn_list[sockfd].fd = sockfd;
        设置accept_cb为共同体回调函数的处理,即监听到可读事件后调用accept_cb函数
            
            
              cpp
              
              
            
          
          conn_list[sockfd].r_action.recv_callback = accept_cb;
        调用set_event函数将监听套接字加入到epoll实例中,监控其可读事件
            
            
              cpp
              
              
            
          
          set_event(sockfd, EPOLLIN, 1);
        主循环,处理新连接,收发客户端数据
            
            
              cpp
              
              
            
          
          while(1){
    ...
}
        创建数组存储就绪事件,初始化为0
            
            
              cpp
              
              
            
          
          struct epoll_event events[1024] = {0};
        调用epoll_wait阻塞等待,直到监控的fd触发了就绪事件,并统计数量
            
            
              cpp
              
              
            
          
          int nready = epoll_wait(epfd, events, 1024, -1);
        循环遍历所有就绪事件(时间复杂度为O(k))
            
            
              cpp
              
              
            
          
          int i = 0;
for(i = 0;i < nready;i ++){
    ...
}
        获取当前就绪事件对应的fd,存入connfd中,简化操作
            
            
              cpp
              
              
            
          
          int connfd = events[i].data.fd;
        当就绪事件触发可读事件时,执行连接列表中该fd的recv_callback回调函数,添加新连接或读取数据;当就绪事件触发可写事件时,执行连接列表中该fd的send_callback回调函数,将发送缓冲区的数据发送至客户端
            
            
              cpp
              
              
            
          
          if(events[i].events & EPOLLIN){
    conn_list[connfd].r_action.recv_callback(connfd);
}
if(events[i].events & EPOLLOUT){
    conn_list[connfd].send_callback(connfd);
}
        1.4方法总结
将epoll模式的TCP服务器中多个关键函数进行封装处理:init_server处理服务器的创建,套接字的创建,绑定地址,开启监听和返回监听fd;set_event处理epoll的添加修改;accept_cb接收新连接并创建客户端fd;event_register完善客户端的连接信息结构体形式,并添加到epoll实例中;recv_cb接收并存储客户端发送的数据,处理断开连接的客户端;send_cb实现向客户端回发数据操作;主函数中的循环通过epoll_wait实现对所有fd的监控,根据关注事件类型调用对应的回调函数。
epoll作为事件多路分发器,conn_list作为事件处理器,主循环是事件分离器,契合reactor模式的一个分发器+多个处理器的模式
具体流程为:init_server->主函数循环
若为可读事件
若为监听fd->accept_cb->event_register->set_event
若为客户端fd->recv_cb->set_event
若为可写事件->send_cb->set_event
1.5完整代码
            
            
              cpp
              
              
            
          
          #include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<pthread.h>
#include<unistd.h>
#include<poll.h>
#include<sys/epoll.h>
//宏定义设定缓冲区和连接列表的大小
#define BUFFER_LENGTH       1024
#define CONNECTION_SIZE          1048576
//声明处理事件的回调函数类型,统一接口便于分发时间
typedef int (*RCALLBACK)(int fd);
//epoll实例的全局变量,各函数中操作同一个epoll
int epfd = 0;
//具体回调函数的声明
int recv_cb(int fd);
int accept_cb(int fd);
int send_cb(int fd);
//连接信息结构体
struct conn{
    //套接字,客户端fd或者监听fd
    int fd;
    
    //读写数据的缓冲区数组和大小
    char rbuffer[BUFFER_LENGTH];
    int rlength;
    char wbuffer[BUFFER_LENGTH];
    int wlength;
    //把回调函数的指针加入结构体
    RCALLBACK send_callback;
    //互斥状态的两个回调函数指针,共同体的形式加入结构体(客户端fd调用recv_callback,监听fd调用accept_callback)
    //共同体,所有成员公用同一块内存空间,节省内存
    union{
        RCALLBACK recv_callback;
        RCALLBACK accept_callback;
    } r_action;
};
//用数组存储所有连接
struct conn conn_list[CONNECTION_SIZE] = {0};
//添加/修改epoll事件
int set_event(int fd, int event, int flag){
    //flag非零时,添加epoll事件
    if(flag){//no-zero add
        //创建epoll_event类型变量
        struct epoll_event ev;
        //设定关注的事件类型
        ev.events = event;
        //将当前fd存入ev的data.fd对象中
        ev.data.fd = fd;
        //添加到epfd的epoll实例中
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    }else{//zero mod
        struct epoll_event ev;
        ev.events = event;
        ev.data.fd = fd;
        //修改epfd中的epoll实例
        epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
    }
}
//为epoll实例中添加新的客户端连接
int event_register(int fd, int event){
    //初始化连接信息,绑定fd,设置回调函数
    conn_list[fd].fd = fd;
    //添加连接,共同体中选择recv_cb回调函数
    conn_list[fd].r_action.recv_callback = recv_cb;
    conn_list[fd].send_callback = send_cb;
    //初始化缓冲区
    memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH);
    conn_list[fd].rlength = 0;
    memset(conn_list[fd].wbuffer, 0, BUFFER_LENGTH);
    conn_list[fd].wlength = 0;
    //调用set_event函数添加事件,并监控可读事件
    set_event(fd, EPOLLIN, 1);
}
//listen(sockfd) --> EPOLLIN --> accept_cb
//接收客户端连接请求,创建客户端fd并完成初始化
int accept_cb(int fd){
    //定义客户端地址结构体,计算长度
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    //调用accept函数,从监听fd接收数据并创建对应地址的客户端fd
    int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
    printf("accept finished: %d\n", clientfd);
    //调用event_register函数初始化连接信息,关注可读事件
    event_register(clientfd, EPOLLIN);
    return 0;
}
//客户端fd触发EPOLLIN事件的回调函数,接收客户端发送的数据
int recv_cb(int fd){
    //调用recv接收客户端数据,存入该连接的接收缓冲区,通过接收数据长度判断接收状态
    int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);
    //状态为零时客户端主动断开连接
    if(count == 0){
        printf("clientfd disconnect: %d\n", fd);
        //关闭断开的客户端fd
        close(fd);
        //将该fd从epfd的epoll实例中删除,不再监控
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);// 无需ev结构体
        return 0;
    }
    //存储数据长度
    conn_list[fd].rlength  = count;
    //打印接收数据
    printf("RRECV: %s\n", conn_list[fd].rbuffer);
#if 1 //echo  回声模式开关,1开启
    //将接收缓冲区的数据和数据长度存储到发送缓冲区中,用于send函数
    conn_list[fd].wlength = conn_list[fd].rlength;
    memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, conn_list[fd].wlength);
#endif
    //调用set_event函数修改该fd的关注事件为EPOLLOUT可写
    //让epoll后续触发可写事件,调用send_cb发送缓冲区中数据
    set_event(fd, EPOLLOUT, 0);
    return count;
}
//客户端fd触发可写事件后,发送缓冲区中的数据
int send_cb(int fd){
    //调用send函数将发送缓冲区中的数据发送至客户端fd
    int count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
    //调用set_event函数将该fd连接信息中的关注事件修改为可读,从而再次接收数据
    set_event(fd, EPOLLIN, 0);
    return count;
}
//创建服务器,开启监听fd
int init_server(unsigned short port){
    //创建TCP流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    //设置服务器地址信息,IPv4,绑定所有本地网卡,绑定端口
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0
    servaddr.sin_port = htons(port); //0-1023
    //绑定套接字到服务器地址和端口
    if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
        printf("bind failed: %s\n", strerror(errno));
    }
    //开启监听套接字,最大等待队列为10
    listen(sockfd, 10);
    printf("listen finished: %d\n", sockfd);
    return sockfd;
}
int main(){
    //设置端口
    unsigned short port = 2000;
    //初始化服务器,创建套接字,绑定地址,开始监听
    //返回监听套接字
    int sockfd = init_server(port);
    //调用epoll_create函数创建一个epoll实例
    epfd = epoll_create(1);
    //将监听套接字存入连接列表中
    conn_list[sockfd].fd = sockfd;
    //设置accept_cb为共同体回调函数的处理,即监听到可读事件后调用accept_cb函数
    conn_list[sockfd].r_action.recv_callback = accept_cb;
    //调用set_event函数将监听套接字加入到epoll实例中,监控其可读事件
    set_event(sockfd, EPOLLIN, 1);
    //主循环,处理新连接,收发客户端数据
    while(1){//mainloop
        //创建数组存储就绪事件,初始化为0
        struct epoll_event events[1024] = {0};
        //调用epoll_wait阻塞等待,直到监控的fd触发了就绪事件,并统计数量
        int nready = epoll_wait(epfd, events, 1024, -1);
        //循环遍历所有就绪事件(时间复杂度为O(k))
        int i = 0;
        for(i = 0;i < nready;i ++){
            //获取当前就绪事件对应的fd,存入connfd中,简化操作
            int connfd = events[i].data.fd;
            //当就绪事件触发可读事件时,执行连接列表中该fd的recv_callback回调函数,添加新连接或读取数据
            if(events[i].events & EPOLLIN){
                conn_list[connfd].r_action.recv_callback(connfd);
            }
            //当就绪事件触发可写事件时,执行连接列表中该fd的send_callback回调函数,将发送缓冲区的数据发送至客户端
            if(events[i].events & EPOLLOUT){
                conn_list[connfd].send_callback(connfd);
            }
        }
    }
}
        2.实现百万并发
reactor实现部分作为作为服务器的服务端,再开启三个虚拟机作为客户端,并运行客户端代码(见2.)
2.1启动服务器和客户端
修改服务器的连接上限
            
            
              cpp
              
              
            
          
          #define CONNECTION_SIZE          1048576
        修改客户端的端口数量,先测试一个端口
            
            
              cpp
              
              
            
          
          #define MAX_PORT		1
        启服务器端,输出
listen finished: 3
启动客户端,输出
connections: 999, sockfd:1002, time_used:1936
socket: Too many open files
error : Too many open files
2.2调试代码
服务器端显示1031个连接成功
尝试修改客户端主机open files的打开上限
ulimit -a 查看 显示数据为
open files (-n) 1024
ulimit -n 1048576 进行修改
查看修改结果
open files (-n) 1048576
增加服务器端代码的返回值判断
            
            
              cpp
              
              
            
          
          int event_register(int fd, int event){
    if(fd < 0) return -1;
    ...
}
int accept_cb(int fd){
    
    ...
    printf("accept finished: %d\n", clientfd);
    if(clientfd < 0){
        printf("accept error: %d\n", errno);
        return -1;
    }
    event_register(clientfd, EPOLLIN);
    return 0;
}
        再次运行
客户端达到28000连接,报错提示客户端端口耗尽,五元组不足
connections: 27999, sockfd:28002, time_used:6554
connect: Cannot assign requested address
error : Cannot assign requested address
服务器端建立20个端口
            
            
              cpp
              
              
            
          
          #define MAX_PORT            20
        主函数中添加对端口的循环初始化
            
            
              cpp
              
              
            
          
          for(int i = 0;i < MAX_PORT;i ++){
    
    //新的端口
    int sockfd = init_server(port + i);
    conn_list[sockfd].fd = sockfd;
    conn_list[sockfd].r_action.recv_callback = accept_cb;
    set_event(sockfd, EPOLLIN, 1);
}
        简化服务器端send输出,每一千条打印一次数据和所耗时间,不再显示recv和listen结果
创建开始时时间结构体变量
            
            
              cpp
              
              
            
          
          struct timeval begin;
        获取开始时的时间
            
            
              cpp
              
              
            
          
          gettimeofday(&begin,NULL);
        获取每建立1000个连接时的时间
            
            
              cpp
              
              
            
          
          struct timeval current;
gettimeofday(¤t, NULL);
        宏定义耗时计算方法
            
            
              cpp
              
              
            
          
          #define TIME_SUB_MS(tv1, tv2)   ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
        计算耗时并更新每次时间开始值
            
            
              cpp
              
              
            
          
          int time_used = TIME_SUB_MS(current, begin);
memcpy(&begin, ¤t, sizeof(timeval));
        打印结果
            
            
              cpp
              
              
            
          
          printf("accept finished: %d, time_used: %d\n", clientfd, time_used);
        再次运行
服务器端连接提高至132000
accept finished: 132000, time_used: 787
客户端在达到45000左右时报错
connections: 46999, sockfd:47002, time_used:2254
RecvBuffer:Hello Server: client --> 46998
data from 4
Error clientfd:43002, errno:11
connect: Connection refused
error : Connection refused
尝试调大服务器TCP队列参数,提高监听队列
全连接队列上限 sudo sysctl -w net/core/somaxconn=4096
半连接队列上限 sudo sysctl -w net.ipv4.tcp_max_syn_backlog=4096
            
            
              cpp
              
              
            
          
          listen(sockfd, 4096);
        再次运行
服务器端连接提高至666000
accept finished: 666000, time_used: 5542
客户端连接提高至201999
connections: 201999, sockfd:202002, time_used:2326
此时连接断开,受硬件设备限制,暂无法达到百万并发
2.3完整代码
服务器端
            
            
              cpp
              
              
            
          
          #include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<pthread.h>
#include<unistd.h>
#include<poll.h>
#include<sys/epoll.h>
#include<sys/time.h>
//宏定义设定缓冲区和连接列表的大小
#define BUFFER_LENGTH       1024
#define CONNECTION_SIZE     1048576
#define MAX_PORTS            20
#define TIME_SUB_MS(tv1, tv2)   ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
//声明处理事件的回调函数类型,统一接口便于分发时间
typedef int (*RCALLBACK)(int fd);
//epoll实例的全局变量,各函数中操作同一个epoll
int epfd = 0;
//具体回调函数的声明
int recv_cb(int fd);
int accept_cb(int fd);
int send_cb(int fd);
//创建开始时时间结构体变量
struct timeval begin;
//连接信息结构体
struct conn{
    //套接字,客户端fd或者监听fd
    int fd;
    
    //读写数据的缓冲区数组和大小
    char rbuffer[BUFFER_LENGTH];
    int rlength;
    char wbuffer[BUFFER_LENGTH];
    int wlength;
    //把回调函数的指针加入结构体
    RCALLBACK send_callback;
    //互斥状态的两个回调函数指针,共同体的形式加入结构体(客户端fd调用recv_callback,监听fd调用accept_callback)
    //共同体,所有成员公用同一块内存空间,节省内存
    union{
        RCALLBACK recv_callback;
        RCALLBACK accept_callback;
    } r_action;
};
//用数组存储所有连接
struct conn conn_list[CONNECTION_SIZE] = {0};
//添加/修改epoll事件
int set_event(int fd, int event, int flag){
    //flag非零时,添加epoll事件
    if(flag){//no-zero add
        //创建epoll_event类型变量
        struct epoll_event ev;
        //设定关注的事件类型
        ev.events = event;
        //将当前fd存入ev的data.fd对象中
        ev.data.fd = fd;
        //添加到epfd的epoll实例中
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    }else{//zero mod
        struct epoll_event ev;
        ev.events = event;
        ev.data.fd = fd;
        //修改epfd中的epoll实例
        epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
    }
}
//为epoll实例中添加新的客户端连接
int event_register(int fd, int event){
    if(fd < 0) return -1;
    //初始化连接信息,绑定fd,设置回调函数
    conn_list[fd].fd = fd;
    //添加连接,共同体中选择recv_cb回调函数
    conn_list[fd].r_action.recv_callback = recv_cb;
    conn_list[fd].send_callback = send_cb;
    //初始化缓冲区
    conn_list[fd].rlength = 0;
    conn_list[fd].wlength = 0;
    //调用set_event函数添加事件,并监控可读事件
    set_event(fd, EPOLLIN, 1);
}
//listen(sockfd) --> EPOLLIN --> accept_cb
//接收客户端连接请求,创建客户端fd并完成初始化
int accept_cb(int fd){
    //定义客户端地址结构体,计算长度
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    //调用accept函数,从监听fd接收数据并创建对应地址的客户端fd
    int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
    //printf("accept finished: %d\n", clientfd);
    if(clientfd < 0){
        printf("accept error: %d\n", errno);
        return -1;
    }
    //调用event_register函数初始化连接信息,关注可读事件
    event_register(clientfd, EPOLLIN);
    if(clientfd % 1000 == 0){
        
        //获取每建立1000个连接时的时间
        struct timeval current;
        gettimeofday(¤t, NULL);
        //计算耗时
        int time_used = TIME_SUB_MS(current, begin);
        //更新每次时间开始值
        memcpy(&begin, ¤t, sizeof(struct timeval));
        printf("accept finished: %d, time_used: %d\n", clientfd, time_used);
    }
    return 0;
}
//客户端fd触发EPOLLIN事件的回调函数,接收客户端发送的数据
int recv_cb(int fd){
    memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH );
    //调用recv接收客户端数据,存入该连接的接收缓冲区,通过接收数据长度判断接收状态
    int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);
    //状态为零时客户端主动断开连接
    if(count == 0){
        printf("clientfd disconnect: %d\n", fd);
        //关闭断开的客户端fd
        close(fd);
        //将该fd从epfd的epoll实例中删除,不再监控
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);// 无需ev结构体
        return 0;
    }
    //存储数据长度
    conn_list[fd].rlength  = count;
    //打印接收数据
    //printf("RRECV: %s\n", conn_list[fd].rbuffer);
#if 1 //echo  回声模式开关,1开启
    //将接收缓冲区的数据和数据长度存储到发送缓冲区中,用于send函数
    conn_list[fd].wlength = conn_list[fd].rlength;
    memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, conn_list[fd].wlength);
#endif
    //调用set_event函数修改该fd的关注事件为EPOLLOUT可写
    //让epoll后续触发可写事件,调用send_cb发送缓冲区中数据
    set_event(fd, EPOLLOUT, 0);
    return count;
}
//客户端fd触发可写事件后,发送缓冲区中的数据
int send_cb(int fd){
    //调用send函数将发送缓冲区中的数据发送至客户端fd
    int count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
    //调用set_event函数将该fd连接信息中的关注事件修改为可读,从而再次接收数据
    set_event(fd, EPOLLIN, 0);
    return count;
}
//创建服务器,开启监听fd
int init_server(unsigned short port){
    //创建TCP流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    //设置服务器地址信息,IPv4,绑定所有本地网卡,绑定端口
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0
    servaddr.sin_port = htons(port); //0-1023
    //绑定套接字到服务器地址和端口
    if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
        printf("bind failed: %s\n", strerror(errno));
    }
    //开启监听套接字,最大等待队列为10
    listen(sockfd, 4096);
    //printf("listen finished: %d\n", sockfd);
    return sockfd;
}
int main(){
    //设置端口
    unsigned short port = 2000;
    //调用epoll_create函数创建一个epoll实例
    epfd = epoll_create(1);
    for(int i = 0;i < MAX_PORTS;i ++){
    
        //初始化服务器,创建套接字,绑定地址,开始监听
        //返回监听套接字
        int sockfd = init_server(port + i);
        //将监听套接字存入连接列表中
        conn_list[sockfd].fd = sockfd;
        //设置accept_cb为共同体回调函数的处理,即监听到可读事件后调用accept_cb函数
        conn_list[sockfd].r_action.recv_callback = accept_cb;
        //调用set_event函数将监听套接字加入到epoll实例中,监控其可读事件
        set_event(sockfd, EPOLLIN, 1);
    }
    //获取开始时的时间
    gettimeofday(&begin,NULL);
    //主循环,处理新连接,收发客户端数据
    while(1){//mainloop
        //创建数组存储就绪事件,初始化为0
        struct epoll_event events[1024] = {0};
        //调用epoll_wait阻塞等待,直到监控的fd触发了就绪事件,并统计数量
        int nready = epoll_wait(epfd, events, 1024, -1);
        //循环遍历所有就绪事件(时间复杂度为O(k))
        int i = 0;
        for(i = 0;i < nready;i ++){
            //获取当前就绪事件对应的fd,存入connfd中,简化操作
            int connfd = events[i].data.fd;
            //当就绪事件触发可读事件时,执行连接列表中该fd的recv_callback回调函数,添加新连接或读取数据
            if(events[i].events & EPOLLIN){
                conn_list[connfd].r_action.recv_callback(connfd);
            }
            //当就绪事件触发可写事件时,执行连接列表中该fd的send_callback回调函数,将发送缓冲区的数据发送至客户端
            if(events[i].events & EPOLLOUT){
                conn_list[connfd].send_callback(connfd);
            }
        }
    }
}
        客户端
            
            
              cpp
              
              
            
          
          #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/time.h> 
#include <unistd.h> 
#define MAX_BUFFER		128
#define MAX_EPOLLSIZE	(384*1024)
#define MAX_PORT		100
#define TIME_SUB_MS(tv1, tv2)  ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
int isContinue = 0;
static int ntySetNonblock(int fd) {
	int flags;
	flags = fcntl(fd, F_GETFL, 0);
	if (flags < 0) return flags;
	flags |= O_NONBLOCK;
	if (fcntl(fd, F_SETFL, flags) < 0) return -1;
	return 0;
}
static int ntySetReUseAddr(int fd) {
	int reuse = 1;
	return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
}
int main(int argc, char **argv) {
	if (argc <= 2) {
		printf("Usage: %s ip port\n", argv[0]);
		exit(0);
	}
	const char *ip = argv[1];
	int port = atoi(argv[2]);
	int connections = 0;
	char buffer[128] = {0};
	int i = 0, index = 0;
	struct epoll_event events[MAX_EPOLLSIZE];
	
	int epoll_fd = epoll_create(MAX_EPOLLSIZE);
	
	strcpy(buffer, " Data From MulClient\n");
		
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(struct sockaddr_in));
	
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(ip);
	struct timeval tv_begin;
	gettimeofday(&tv_begin, NULL);
	while (1) {
		if (++index >= MAX_PORT) index = 0;
		
		struct epoll_event ev;
		int sockfd = 0;
		if (connections < 340000 && !isContinue) {
			sockfd = socket(AF_INET, SOCK_STREAM, 0);
			if (sockfd == -1) {
				perror("socket");
				goto err;
			}
			//ntySetReUseAddr(sockfd);
			addr.sin_port = htons(port+index);
			if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
				perror("connect");
				goto err;
			}
			ntySetNonblock(sockfd);
			ntySetReUseAddr(sockfd);
			sprintf(buffer, "Hello Server: client --> %d\n", connections);
			send(sockfd, buffer, strlen(buffer), 0);
			ev.data.fd = sockfd;
			ev.events = EPOLLIN | EPOLLOUT;
			epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
		
			connections ++;
		}
		//connections ++;
		if (connections % 1000 == 999 || connections >= 340000) {
			struct timeval tv_cur;
			memcpy(&tv_cur, &tv_begin, sizeof(struct timeval));
			
			gettimeofday(&tv_begin, NULL);
			int time_used = TIME_SUB_MS(tv_begin, tv_cur);
			printf("connections: %d, sockfd:%d, time_used:%d\n", connections, sockfd, time_used);
			int nfds = epoll_wait(epoll_fd, events, connections, 100);
			for (i = 0;i < nfds;i ++) {
				int clientfd = events[i].data.fd;
				if (events[i].events & EPOLLOUT) {
					sprintf(buffer, "data from %d\n", clientfd);
					send(sockfd, buffer, strlen(buffer), 0);
				} else if (events[i].events & EPOLLIN) {
					char rBuffer[MAX_BUFFER] = {0};				
					ssize_t length = recv(sockfd, rBuffer, MAX_BUFFER, 0);
					if (length > 0) {
						printf(" RecvBuffer:%s\n", rBuffer);
						if (!strcmp(rBuffer, "quit")) {
							isContinue = 0;
						}
						
					} else if (length == 0) {
						printf(" Disconnect clientfd:%d\n", clientfd);
						connections --;
						close(clientfd);
					} else {
						if (errno == EINTR) continue;
						printf(" Error clientfd:%d, errno:%d\n", clientfd, errno);
						close(clientfd);
					}
				} else {
					printf(" clientfd:%d, errno:%d\n", clientfd, errno);
					close(clientfd);
				}
			}
		}
		usleep(1 * 1000);
	}
	return 0;
err:
	printf("error : %s\n", strerror(errno));
	return 0;
	
}