reator是高并发网络编程的经典事件驱动模式,核心思想是 事件监控与业务处理解耦:

  • reactor线程(主线程):仅做一件事,即通过epoll_wait监控fd的事件(EPOLLIN/EPOLLOUT),不处理具体的业务;
  • 回调函数:事件就绪后,主线程调用绑定的回调函数处理具体业务;
  • 事件驱动:业务处理完成后,通过set_event切换事件类型(比如读完以后切成写入事件),让reactor线程继续监控;
  • 连接管理:统一管理每个连接的fd、缓冲区、回调函数、状态,实现连接的一站式管理。

核心结构体

首先封装一个连接管理结构体

struct conn{
    int fd;

    char rbuffer[BUFFER_LEN];
    int rlen;
    char wbuffer[BUFFER_LEN];
    int wlen;

    RCALLBACK send_callback;
    union{
    RCALLBACK recv_callback;//clientfd
    RCALLBACK accept_callback;//listenfd
    }r_action;

    int status;

};

fd:连接对应的fd 监听fd 客户端fd

rbuffer[BUFFER_LEN]:读缓冲区 存储客户端请求数据

wbuffer[BUFFER_LEN]:写缓冲区:存储服务器响应数据

RCALLBACK send_callback:写事件(EPOLLOUT)回调函数,所有连接共用send_cb

union{

RCALLBACK recv_callback;//clientfd

RCALLBACK accept_callback;//listenfd

}r_action :客户端fd可读事件和监听fd可读事件EPOLLIN的回调函数联合体,

status:连接状态 0=空闲/完成、1=发送中、2=等待发送 控制写事件的执行逻辑,比如是否继续发送、是否切换回读事件

多端口高并发支持HTTP的reactor模式TCP服务器

  • 高并发处理:基于epoll的边缘触发模式
  • 事件驱动回调:监听 fd 的 EPOLLIN 事件 → 触发accept_cb(接新连接);
                           客户端 fd 的 EPOLLIN 事件 → 触发recv_cb(读请求);
                            客户端 fd 的 EPOLLOUT 事件 → 触发send_cb(发响应);

main函数 reactor主线程入口

main函数只做事件监控和事件分发,完全不管业务逻辑

int main(){
    unsigned short port = 2000;
    
    //1.创建epoll实例 
    //这是reactor事件管理器
    epfd = epoll_create(1);
    //2.初始化监听端口
    int i = 0;
    for(i = 0;i < MAX_PORTS; 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);

    }
    gettimeofday(&begin, NULL);
    //3.mainloop reactor主循环
    while(1){
        struct epoll_event events[1024] = {0};
        //阻塞等待epoll事件就绪 实现事件监控
        int nready = epoll_wait(epfd, events, 1024, -1);
        //4. 遍历就绪事件 触发对应的回调 实现事件分发
        int i = 0;
        for(i = 0; i<nready; i++){
            int connfd = events[i].data.fd;
            //读写同时并存
            //读事件就绪
            //新客户端连接or客户端发数据->调用recv_callback(accept_cb/recv_cb)
            if(events[i].events & EPOLLIN){
                conn_list[connfd].r_action.recv_callback(connfd);
            }
            //写事件就绪
            //客户端请求数据,可发响应->调用send_callback(send_cb)
            if(events[i].events & EPOLLOUT){
                conn_list[connfd].send_callback(connfd);
            }
        }
    }
}

回调函数 业务处理

主要涉及以下五个函数

  1. init_server:基础TCP初始化
  2. accept_cb:新客户端连接事件回调
  3. recv_cb:读事件回调 处理客户端数据(请求数据)
  4. send_cb:写事件回调 发送响应(发给客户端)
  5. set_event:epoll 事件管理封装
  • init_server:基础TCP初始化

主要作用是初始化监听端口

int init_server(unsigned short port){
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in servaddr;

    servaddr.sin_family = AF_INET;
    /*
    绑定本地地址 0.0.0.0
    三个端口都能接收
    */  
    servaddr.sin_addr.s_addr = htons(INADDR_ANY);
    servaddr.sin_port = htons(port);
    if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
        printf("bind failed: %s\n", strerror(errno));
    }
    /*
    tcp server
    两种fd 两种socket
    监听fd listen
    客户端fd recv
    */
    listen(sockfd, 10);
    //printf("listen finished: %d\n", sockfd);
    return sockfd;

}
  • accept_cb:新客户端连接事件回调

客户端fd的回调函数绑定为recv_cb(读数据)和send_cb(发响应);

启用边缘触发(EPOLLET):相比水平触发(LT),ET 模式只会在 “事件状态变化” 时触发一次,减少不必要的回调。

int accept_cb(int fd){
    //1.accept新连接
    //fd是监听fd 这里是有新的客户端连接
    struct sockaddr_in  clientaddr;
    socklen_t len = sizeof(clientaddr);
    int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
    //printf("accept finished: %d\n",clientfd);
    if(clientfd < 0)
    return -1;

    //这个fd是clientfd 以下为设置新加入的这个fd
    //2.初始化客户端连接 依然是用fd作为数组索引
    conn_list[clientfd].fd = clientfd;
    //读事件绑定recv_cb
    conn_list[clientfd].r_action.recv_callback = recv_cb;
    //写事件绑定send_cb
    conn_list[clientfd].send_callback = send_cb;
    //清空读写缓冲区
    memset(conn_list[clientfd].rbuffer, 0, BUFFER_LEN);
    conn_list[clientfd].rlen = 0;
    memset(conn_list[clientfd].wbuffer, 0, BUFFER_LEN);
    conn_list[clientfd].wlen = 0;

    //3.设置客户端fd的epoll事件
    //默认是水平触发,| EPOLLET后是边沿触发
    //设置为ET+EPOLLIN(先是可读事件) flag = 1表示ADD
    set_event(clientfd, EPOLLIN | EPOLLET, 1);
    if((clientfd % 1000) == 0){
        struct timeval current;
        gettimeofday(&current, NULL);
        int time_used = TIME_SUB_MS(current, begin);
        memcpy(&begin, &current, sizeof(struct timeval));

        //printf("accept finished: %d, timeused: %d\n",clientfd, time_used);

    }
    return 0;
}
  • recv_cb:读事件回调 处理客户端数据(请求数据)

读数据完成后,主动切换事件类型为 EPOLLOUT,让 Reactor 主线程监控 “可写事件”,准备发响应。

int recv_cb(int fd){
    //1.清空读缓冲区,准备接收客户端数据
    conn_list[fd].rlen = 0;
    memset(conn_list[fd].rbuffer, 0, BUFFER_LEN); 
    int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LEN, 0);                
    //2.异常&断开连接则移除fd           
    if(count == 0){
        //printf("disconnect: %d\n", fd);
        close(fd);
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
        return 0;
    }else if(count < 0){
        close(fd);
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd ,NULL);
        return 0;
    }
    //3.记录读缓冲区数据长度
    conn_list[fd].rlen = count;

    //4.处理请求
    //这里先模拟一下,直接打印出来
    memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, count);
    conn_list[fd].wlen = count;
    printf("recv: %s\n", conn_list[fd].rbuffer);  

 
    //5.切换事件为EPOLLOUT(准备发响应) 
    //受到客户端的请求,接下来准备发送响应了 此时切换事件类型
    //flag=0表示MOD
    set_event(fd, EPOLLOUT, 0);
    return count;
}
  • send_cb:写事件回调 发送响应(发给客户端)

根据连接状态控制 “发送逻辑”,发送完成后切换回 EPOLLIN 事件,等待客户端下一次请求,实现监控fd、收发数据的事件循环。

int send_cb(int fd){
    //1.处理响应
    //2.根据连接状态发送数据
    int count = 0;
    if(conn_list[fd].status == 1){
        //状态1:响应数据发送中
        //数据可能一次发送不完 继续监控EPOLLOUT
        count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlen, 0);                                                                                                     
        //继续监控写事件 又会调用send_cb回到这个函数
        set_event(fd, EPOLLOUT, 0);
    }
    //状态2:等待发送(暂时不发) 继续监控写事件
    else if(conn_list[fd].status == 2){
        set_event(fd, EPOLLOUT, 0);
    }
    //状态0:响应发送完成 或者 空闲 
    else if(conn_list[fd].status == 0){
        if(conn_list[fd].wlen != 0){
            count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlen, 0);                                                                
            //printf("send: %d\n", count);                                    
        }
        //发送完剩余事件后切换回读事件继续监控客户端fd
        set_event(fd, EPOLLIN, 0);
    }
    return count;
}
  • set_event:epoll 事件管理封装

封装 epoll_ctl 的复杂调用,对外提供简单的 “添加 / 修改事件” 接口,减少代码冗余

int set_event(int fd, int event, int flag){
    if(flag){//add
        //flag = 1 添加事件 EPOLL_CTL_ADD
        struct epoll_event ev;
        ev.events = event;
        ev.data.fd = fd;
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    }else{//mod
        //flag = 0 修改事件 EPOLL_CTL_MOD
        struct epoll_event ev;
        ev.events = event;
        ev.data.fd = fd;
        epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
    }
}

Reactor 模式实现的核心优势 对比基础 epoll

解耦事件监控和业务处理,实现高并发、高扩展、高性能的网络服务器,主要的价值体现在解耦,新增协议 / 功能只需加回调,无需改核心循环。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐