实现基于epoll的reactor网络模型

实现Reactor模型

reactor 将 io 管理,转变为事件管理,核心在于不同的 io 事件,对应不同的回调函数,先register(注册),后callback(回调)

ioeventcallback
listenfdEPOLLINaccept_cb
clientfdEPOLLINrecv_cb
clientfdEPOLLOUTsend_cb

一共有两类 io,分别是用于监听的 listenfd 和用于通信的 clientfd;一共有两类事件,分别是可读事件 EPOLLIN 和 可写事件EPOLLOUT,要实现 reactor,重点在于 event 与 callback 的匹配以及每一个 io 与之对应的参数

io 与之对应的参数结构体可以定义如下:

struct conn
{
    int fd; // io
    char rbuffer[BUFFER_LENGTH]; // 读缓冲区
    int rlength; // 读缓冲区长度
    char wbuffer[BUFFER_LENGTH]; // 写缓冲区
    int wlength; // 写缓冲区长度
    RCALLBACK send_callback;
    union // recv_cb和accept_cb在同一个io中不可能同时出现
    {
        RCALLBACK recv_callback;
        RCALLBACK accept_callback;
    } r_action;
};

总体代码如下所示:

#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>

#define BUFFER_LENGTH 1024
#define CONNECTION_SIZE 1024

typedef int (*RCALLBACK)(int fd);

int accept_cb(int fd);
int recv_cb(int fd);
int send_cb(int fd);

int epfd = 0;

struct conn
{
    int fd;
    char rbuffer[BUFFER_LENGTH];
    int rlength;
    char wbuffer[BUFFER_LENGTH];
    int wlength;
    RCALLBACK send_callback;
    union
    {
        RCALLBACK recv_callback;
        RCALLBACK accept_callback;
    } r_action;
};

struct conn conn_list[CONNECTION_SIZE] = {0};

int set_event(int fd, int event, int flag)
{
    if (flag)
    {
        struct epoll_event ev;
        ev.events = event;
        ev.data.fd = fd;
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    }
    else
    {
        struct epoll_event ev;
        ev.events = event;
        ev.data.fd = fd;
        epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
    }
}

int event_register(int fd, int event)
{
    if (fd < 0) return -1;
    conn_list[fd].fd = fd;
    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(fd, event, 1);
}

int accept_cb(int 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)
    {
        printf("accept errno: %d --> %s\n", errno, strerror(errno));
        return -1;
    }
    event_register(clientfd, EPOLLIN);
    return 0;
}

int recv_cb(int fd)
{
    int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);
    if (count == 0)
    {
        printf("client disconnect: %d\n", fd);
        close(fd);
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
        return 0;
    }
    conn_list[fd].rlength = count;
    printf("RECV: %s\n", conn_list[fd].rbuffer);

    conn_list[fd].wlength = conn_list[fd].rlength;
    memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, conn_list[fd].wlength);

    set_event(fd, EPOLLOUT, 0);
    return count;
}

int send_cb(int fd)
{
    int count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
    set_event(fd, EPOLLIN, 0);
    return count;
}

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

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);

    if (-1 == bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)))
    {
        printf("bind failed: %s\n", strerror(errno));
    }

    listen(sockfd, 10);
    printf("listen finshed: %d\n", sockfd);

    return sockfd;
}

int main()
{
    unsigned short port = 2000;
    epfd = epoll_create(1);
    int sockfd = init_server(port);
    conn_list[sockfd].fd = sockfd;
    conn_list[sockfd].r_action.recv_callback = accept_cb;
    set_event(sockfd, EPOLLIN, 1);

    while (1)
    {
        struct epoll_event events[1024] = {0};
        int nready = epoll_wait(epfd, events, 1024, -1);

        int i = 0;
        for (i = 0; i < nready; ++i)
        {
            int connfd = events[i].data.fd;
            if (events[i].events & EPOLLIN) conn_list[connfd].r_action.recv_callback(connfd);
            if (events[i].events & EPOLLOUT) conn_list[connfd].send_callback(connfd);
        }
    }
}

这段一共不到200行的代码,虽然看起来简陋,也确实只是玩具,但也实现了一个基于 epoll 的 reactor 网络模型

各个函数的作用如下:

(1)set_event(int fd, int event, int flag)

​ 这个函数用于设置 epoll 事件。它将文件描述符 fd 注册到 epoll 实例 epfd,注册的事件类型是 event(如 EPOLLIN, EPOLLOUT 等)。flag 参数决定是添加新的文件描述符(EPOLL_CTL_ADD),还是修改现有的文件描述符事件(EPOLL_CTL_MOD)。

(2)event_register(int fd, int event)

​ 这个函数用于初始化并注册一个新的连接到 epoll 实例。它设置文件描述符 fd 的基本属性(接收和发送缓冲区、回调函数等),并且调用 set_event() 将其添加到 epoll 监视列表中。

(3)accept_cb(int fd)

​ 这是一个回调函数,当服务器套接字准备好接受新连接时调用。它接受新的客户端连接,打印相关信息,并注册新客户端套接字以便接收数据。

(4)recv_cb(int fd)

​ 这是一个回调函数,当数据准备好被读取时调用。它从套接字 fd 读取数据到接收缓冲区,并将数据复制到发送缓冲区,准备回显给客户端。此函数还处理客户端断开连接的情况。

(5)send_cb(int fd)

​ 这是一个回调函数,用于当套接字准备好发送数据时调用。它将数据从发送缓冲区写入到客户端套接字,并将套接字的事件重新设置为 EPOLLIN 以继续接收来自客户端的数据。

(6)init_server(unsigned short port)

​ 此函数用于初始化服务器,创建套接字,绑定到指定端口,并开始监听连接请求。

(7)main()

​ 这是程序的入口点。它创建 epoll 实例,初始化服务器套接字,并进入主循环,不断等待 epoll 事件。当一个事件被触发时,根据套接字状态调用相应的回调函数处理连接或数据传输。

后续会用这份 reactor 代码实现 http server(也就是C++选手熟知的烂大街的webserver)和 websocket server,后面还会更新知名的用 reactor 模型设计的网络库,如muduo等。

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值