0voice-2.1.2-事件驱动reactor的原理与实现

什么是 reactor

它只是一种设计模式,用来组织和管理事件驱动的网络程序。

底层还是用操作系统提供的 IO 多路复用技术(select / poll / epoll)。

Reactor 做的事就是:在这些系统调用之上,提供一个统一的“事件分发机制”。

  • io 管理转变为了对 io 事件管理

  • 回调函数就是事先绑定好每个 fd 对应的处理函数

  • 不同的 io 事件,对应不同回调

    • register
      • io --> event --> callback
      • listenfd --> EPOLLIN --> accept_cb
      • clientfd --> EPOLLIN --> recv_cb
      • clientfd --> EPOLLOUT --> send_cb

conn 结构体的作用

struct conn conn_list[CONNECTION_SIZE] = {0};
struct conn {

    int fd;

    char rbuffer[BUFFER_LENTH];
    int rlength;

    char wbuffer[BUFFER_LENTH];
    int wlength;

    RCALLBACK send_callback;

    union { //二选一,或者的关系
        RCALLBACK recv_callback;
        RCALLBACK accept_callback;
    } r_action;

};

  • 直接分析最核心的问题,epollreactor 实现差异在哪里?

  • 看主循环

    //epllo
    if (connfd == sockfd) {
        int clientfd = accept(sockfd , (struct sockaddr*)&clientaddr , &len);
        printf("accept finished: %d\n",clientfd);
        ev.events = EPOLLIN;
        ev.data.fd = clientfd;
        epoll_ctl(epfd , EPOLL_CTL_ADD , clientfd , &ev);
    } else if (events[i].events & EPOLLIN) {
        char buffer[1024] = {0}; // 临时的读写缓冲区
        int count = recv(connfd, buffer , 1024 , 0);
        printf("RECV %s\n", buffer);
    
        if (count == 0) { // 客户端断开连接
            printf("client disconnect: %d\n",connfd);
            close(i); // 这里应该是 close(connfd); 你的代码这里有个小bug
            epoll_ctl(epfd , EPOLL_CTL_DEL , connfd , &ev); // 从epoll中删除
            continue;
        }
        
        count = send(connfd , buffer , count , 0); // 直接发送数据
        printf("SEND: %d\n", count);
    }
    
    //reactor
    
    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. epllo 要先去判断是不是 sockfd , 再判断是否是可读事件。

        • 这是因为 sockfdclientfd 后面执行的操作不一样,而这个操作就是 reactor 的回调函数,它本身就在事件创立的时候就已经确定了,所以就不用多判断了,直接看事件是可读还是可写就行了。
      2. epllo 则需要自己每次 临时 创建读写缓存区 ,而reactor 的每个事件都有独立的读写
        缓冲区

        • 注意 reactor 的缓存区信息都是存下来记录的(数组的内容,长度),可以记录肯定比临时存储的强很多,不然存在以下问题。

        • 无法处理半包/粘包: 如果一个客户端发送的数据量大于 1024 字节,或者分多次发送一个逻辑上的完整包,这个临时缓冲区无法完整保存数据,也无法累积接收。它只能处理一次 recv 接收到的数据。

        • 无法处理发送缓冲区满: 如果 send(connfd , buffer , count , 0) 时,内核的发送缓冲区已满,send 函数可能会阻塞(如果套接字是阻塞模式)或者只发送一部分数据(如果是非阻塞模式),并且返回已发送的字节数。你的代码没有机制来记录剩余未发送的数据,导致数据丢失或逻辑错误。

        • 状态信息缺失: 每个连接除了读写数据,还可能有其他状态,例如认证状态、协议解析进度、超时时间等。这份代码没有一个地方来集中存储这些状态,使得管理非常困难。


posted @ 2025-09-20 16:35  xqy2003  阅读(3)  评论(0)    收藏  举报