【高并发网络通信架构】4.高效事件驱动模型:Reactor 模型

目录

一,往期文章

二,基本概念

1.前言

2.基本框架

3.核心特征

4.工作流程

5.用“网络通信”来理解 Reactor 模型

三,代码实现

1.使用 epoll 进行多路复用实现 Reactor 模式的操作流程

2.Reactor 模式实现代码(参考)


一,往期文章

【高并发网络通信架构】1.Linux下实现单客户连接的tcp服务端

【高并发网络通信架构】2.引入多线程实现多客户端连接的tcp服务端

【高并发网络通信架构】3.引入IO多路复用(select,poll,epoll)实现高并发tcp服务端

二,基本概念

1.前言

  • 在计算机科学中,Reactor(释义“反应堆”) 是一种软件设计模式,一种常用的事件驱动的编程模型,常用于构建高性能的并发应用程序。它描述了一种处理并发事件的设计模式,通过事件循环和回调机制来实现,广泛应用于网络编程、服务器开发等领域。
  • Reactor 模型本身是一种模式或思想,而不是具体的代码框架或库。不同的编程语言和平台都可以使用 Reactor 模型来实现事件驱动的应用程序,如 Node.js、Twisted、Netty 等,都基于 Reactor 模型来构建高性能的网络应用程序。
  • 在 Reactor 模型中,事件驱动是主要的编程范式,以监听事件并相应地调用相应的回调函数来处理事件。

2.基本框架

  • 事件源(Event Source):事件源就是产生事件的对象或组件。它可以是网络套接字、文件描述符、用户输入设备等。事件源负责监听和检测事件的发生,并在事件发生时通知事件分发器。
  • 事件循环(Event Loop):事件循环是 Reactor 模型的核心组件。它是一个循环结构,负责监视事件的发生并调度对应的事件处理器来处理事件。事件循环不断地等待事件的发生,一旦有事件发生,它会将事件交给事件分发器,然后调用相应的事件处理器进行处理。事件循环负责管理事件的顺序和执行,并确保处理器不会被阻塞。
  • 事件分发器(Event Dispatcher):事件分发器负责将事件源发出的事件分发给合适的事件处理器。它接收事件,并根据事件的类型和其他规则,选择最适合处理该类型事件的事件处理器。事件分发器起到了事件路由的功能,确保将事件正确地传递给相应的处理器。
  • 事件处理器(Event Handler):事件处理器是对特定类型事件的处理逻辑进行封装的对象。每个事件处理器都定义了如何处理某种类型的事件。它们封装了如何处理事件和执行相应的操作,例如读取网络数据、写入文件等。
  • 多路复用器(Multiplexer):多路复用器用于管理和监听多个事件源,并在事件发生时通知事件循环。它可以基于操作系统提供的底层机制(如 select、poll、epoll)实现高效的事件分发。
  • 反应器(Reactor):反应器是事件循环的组成部分,负责协调事件的注册和反注册、分发和取消分发。它可以管理多个事件源和事件处理器,提供统一的接口和调度机制。
  • 事件队列(Event Queue):事件队列用于存储和管理待处理的事件。它可以是一个先进先出(FIFO)队列或其他数据结构,用于确保事件按照顺序进入事件循环进行处理。

总结

  • 这些组件共同协作,构成了 Reactor 模型的基本框架。它们将事件的产生和处理解耦,通过异步非阻塞的方式实现高效的事件驱动处理,提高了应用程序的性能和并发性能。
  • 需要注意的是,虽然 Reactor 模式是一种基本的并发编程模式,但根据具体的实现和平台,可能会有不同的变体和改进,如多线程 Reactor、异步 Reactor 等。不同的变体可以根据应用程序的需求和系统的特性进行选择和优化。

3.核心特征

  1. 事件驱动:Reactor 模式是一种事件驱动的设计模式,它通过监听事件的发生来驱动应用程序的行为。当一个事件发生时,Reactor 会根据事件类型将事件分发给对应的处理器进行处理。这种事件驱动的机制使得应用程序能够响应外部事件,并根据事件类型进行适当的操作。
  2. 非阻塞:Reactor 模式使用非阻塞的方式处理事件。事件的产生和处理是并发进行的,事件循环以及事件处理器都是以非阻塞的方式执行。这意味着当一个事件正在处理时,其他事件的处理不会被阻塞,从而提高了应用程序的并发性能。
  3. 多路复用:Reactor 模式利用操作系统提供的多路复用机制(select,poll,epoll等),允许一个事件循环同时监听多个事件源。通过这种机制,事件循环可以同时处理多个事件,并且只会在有事件发生时才会唤醒相应的事件处理器进行处理,避免了轮询的低效率问题。
  4. 可伸缩性:Reactor 模式的设计使得应用程序具有良好的可伸缩性。对于不同类型的事件,可以创建对应的事件处理器,并将其注册到事件分发器中。这样可以根据应用程序的需求,动态地增加或减少事件处理器的数量,从而实现更好地适应不断变化的工作负载。
  5. 灵活性:Reactor 模式的设计使得应用程序更加灵活和可扩展。事件的产生和处理被解耦,新增一种类型的事件只需要创建对应的事件处理器并进行注册,而不需要对事件循环进行修改。这种灵活性使得应用程序能够轻松地适应新的需求和变化。
  6. 高性能:由于 Reactor 模式的非阻塞和并发处理特性,它能够实现高性能的事件处理。应用程序不会因为某个事件的处理而阻塞其他事件的处理,从而提高了整体的性能和吞吐量。

总结

  • Reactor 模式具有事件驱动、非阻塞、多路复用、可伸缩性、灵活性和高性能等特点。它能够提供高效的事件处理机制,使得应用程序能够并发地处理多个事件,并具有良好的可维护性和扩展性。

4.工作流程

  1. 初始化:创建一个事件循环(Event Loop)和一个事件分发器(Event Dispatcher)。事件循环是一个主循环,负责等待事件的到来并调度相应的事件处理器来处理事件。事件分发器负责将事件从事件循环传递给适当的事件处理器。
  2. 注册事件源和事件处理器:将事件源(Event Source)和对应的事件处理器(Event Handler)注册到事件分发器中,建立事件源与事件处理器的关联。注册过程中,为每个事件源和事件处理器分配唯一的标识符用于后续事件分发。
  3. 启动事件循环:启动事件循环,进入事件监听状态。事件循环开始等待事件的发生,并根据事件源所监听的事件类型,注册对应的事件处理器。
  4. 等待事件发生:事件循环通过事件分发器等待事件的发生。这可以通过事件源的阻塞方法、非阻塞方法、轮询等方式实现。事件循环会等待直到至少一个事件源发出通知。
  5. 事件通知:当一个或多个事件源发生事件时,它们会通知事件循环,并将事件从事件源传递给事件循环。
  6. 事件分发:事件循环收到事件通知后,将事件传递给事件分发器。
  7. 路由到事件处理器:事件分发器根据事件的类型和其他规则,选择合适的事件处理器来处理该事件。它会根据事件的标识符查找事件源和处理器之间的映射关系,并将事件传递给匹配的事件处理器。
  8. 执行事件处理器:事件处理器执行相应的操作来处理事件。这可以包括读取事件数据、进行计算、更新状态等。事件处理器的执行通常是非阻塞的,以便允许事件循环继续监听其他事件。
  9. 返回等待状态:当事件处理器完成对事件的处理后,它返回到事件循环,继续等待下一个事件的发生。
  10. 重复执行:事件循环不断重复上述步骤,监听和处理事件,直到满足停止条件。停止条件可以是等待时间超时、特定事件发生、特定状态满足或应用程序定义的其他条件。

5.用“网络通信”来理解 Reactor 模型

假设你正在开发一个聊天应用程序,用户可以通过该应用程序与其他用户进行实时通信。为了处理并发的网络连接,你可以使用Reactor模式。

  1. 首先,你创建一个事件循环(Event Loop)作为主循环,并初始化一个网络套接字,用于监听传入的连接请求。
  2. 当有新的客户端连接请求时,网络套接字会触发一个事件,将连接请求通知给你的事件循环。
  3. 接下来,你创建一个连接处理器(Connection Handler),它负责处理每个客户端的连接。连接处理器封装了处理连接的逻辑,比如身份验证、消息的接收和发送等。
  4. 你注册连接处理器到事件分发器(Event Dispatcher),建立套接字与连接处理器之间的映射关系。这样,当有新的连接请求时,事件分发器就能将连接事件传递给相应的连接处理器。
  5. 在事件循环启动后,它开始等待连接请求的发生。当有客户端连接时,套接字触发连接事件,并通知事件循环。
  6. 事件循环收到连接事件后,将连接事件交给事件分发器。
  7. 事件分发器根据连接事件的类型和其他规则,选择合适的连接处理器来处理该连接。它根据客户端的身份、请求类型等找到对应的连接处理器。
  8. 连接处理器执行相应的操作,比如身份验证、处理消息的接收和发送等。这些操作是非阻塞的,因此可以同时处理其他客户端的连接。
  9. 处理完成后,连接处理器返回到事件循环,继续等待下一个连接请求。
  10. 事件循环不断监听连接事件,将连接事件分发给对应的连接处理器来处理。这种解耦的设计使得你能够并发地处理多个客户端的连接和通信,提高了应用程序的响应性和扩展性。

总结

  • 综上所述,Reactor 模式具有事件驱动、非阻塞、多路复用、可伸缩性、灵活性和高性能等特点。它能够提供高效的事件处理机制,使得应用程序能够并发地处理多个事件,并具有良好的可维护性和扩展性。

三,代码实现

1.使用 epoll 进行多路复用实现 Reactor 模式的操作流程

  1. 初始化服务端:调用init_server函数,创建服务端套接字,在接口执行以下操作:
    • 创建监听套接字:使用socket函数创建一个监听套接字,指定协议族、套接字类型和协议号。例如,socket(AF_INET, SOCK_STREAM, 0)会创建一个TCP套接字。
    • 绑定地址和端口:使用bind函数将套接字绑定到指定的IP地址和端口号。需要创建一个sockaddr_in结构体,并设置相应的地址类型、IP地址和端口号。通过调用bind函数,并将套接字描述符和sockaddr_in结构体的指针作为参数,将套接字与所需的地址绑定。
    • 监听连接:使用listen函数开始监听套接字。需要传入套接字描述符和最大等待连接数。这将设置套接字进入等待连接的状态,并且可以接受新的客户端连接。
  2. 初始化事件循环机制:创建一个epoll实例,使用epoll_create函数创建一个 epoll 实例。返回的文件描述符可以用于后续的 epoll 操作。
  3. 创建事件处理器并添加到事件循环中:创建一个EventHandler结构体,包含需要监听的文件描述符和对应的事件处理函数。将该结构体作为参数,调用reactor_add_handler函数将事件处理器添加到事件循环中。
  4. 启动事件循环:调用reactor_run函数,即开始事件循环。在事件循环中,会执行以下操作:
    • 使用epoll_wait函数阻塞等待就绪事件的发生。此函数接收epoll文件描述符、用于获取事件的数组和数组长度。
    • 一旦有就绪事件,遍历事件数组,并找到对应的事件处理器。
    • 调用事件处理器的处理函数,处理该事件。
  5. 处理连接事件:当监听套接字有新的连接时,会触发连接事件。在连接事件处理函数中,使用accept函数接受客户端连接,创建一个新的套接字用于与客户端进行通信。然后,创建一个新的事件处理器,并将该套接字添加到事件循环中。
  6. 处理读事件:当有数据可读时,触发读事件。在读事件处理函数中,使用read函数读取数据并进行处理。如果读取到数据,则可以对数据进行相应的操作。如果读取返回0,表示连接已关闭,需要从事件循环中移除该套接字。
  7. 从事件循环中移除事件处理器:当连接关闭或出现错误时,需要将事件处理器从事件循环中移除。通过调用reactor_remove_handler函数,将套接字从epoll实例中移除,并在数组中删除相应的事件处理器。
  8. 清理操作:在事件循环结束后,关闭监听套接字和epoll实例,并释放任何其他资源。

2.Reactor 模式实现代码(参考)

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

#define MAX_EVENTS 10
#define BUFFER_SIZE 1024

// 定义事件处理器的结构体
typedef struct {
    int fd;                          // 文件描述符
    void (*handler)(int);            // 事件处理函数的指针
} EventHandler;

// 定义事件循环机制以及事件处理器的存储
int epoll_fd;                        // epoll 实例的文件描述符
EventHandler event_handlers[MAX_EVENTS];    // 事件处理器数组
int num_handlers = 0;                // 事件处理器数组中的处理器数量

// 初始化服务端套接字
int init_server(int port);

// 处理连接事件
void handle_accept(int listen_fd);

// 处理读事件
void handle_read(int client_fd);

// 初始化事件循环机制
void reactor_init();

// 添加事件处理器到事件循环中
void reactor_add_handler(int fd, EventHandler event_handler);

// 从事件循环中移除事件处理器
void reactor_remove_handler(int fd);

// 启动事件循环
void reactor_run();

int main(int argc,char *argv[]){

    if(argc < 2)return -1;

    int port = atoi(argv[1]);

    int listen_fd = init_server(port);
    if(listen_fd == -1)return -1;

    // 初始化事件循环机制
    reactor_init();

    // 创建一个事件处理器并添加到事件循环中
    EventHandler event_handler;
    event_handler.fd = listen_fd;
    event_handler.handler = handle_accept;
    reactor_add_handler(listen_fd, event_handler);

    // 启动事件循环
    reactor_run();

    close(listen_fd);

    return 0;
}

// 初始化服务端套接字
int init_server(int port){
    //获取服务端fd,通常为3,前面0,1,2用于指定输入,输出,错误值
    int listen_fd = socket(AF_INET,SOCK_STREAM,0);

    if(-1 == listen_fd){
        printf("Socket error code: %d codeInfo: %s\n", errno, strerror(errno));
        return -1;
    }

    //设置服务端套接字为非阻塞模式
    // int flags = fcntl(sfd,F_GETFL,0);
    // fcntl(sfd,F_SETFL,flags | O_NONBLOCK);

    struct sockaddr_in server_addr;
    memset(&server_addr,0,sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;  //ipv4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   //0.0.0.0
    server_addr.sin_port = htons(port);
    
    //绑定IP和端口号
    if(-1 == bind(listen_fd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in)))
    {
        printf("Bind error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //监听该套接字上的连接
    if(-1 == listen(listen_fd,SOMAXCONN))
    {
        printf("Listen error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    printf("Socket init successed: server fd = %d\n",listen_fd);
    return listen_fd;
}

// 处理连接事件
void handle_accept(int listen_fd) {
    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(struct sockaddr_in);
    int client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addr_len);
    printf("Accepted new connection: client fd = %d\n",client_fd);

    // 添加读事件到事件循环中
    EventHandler event_handler;
    event_handler.fd = client_fd;
    event_handler.handler = handle_read;
    reactor_add_handler(client_fd, event_handler);
}

// 处理读事件
void handle_read(int client_fd) {
    char buffer[BUFFER_SIZE] = {0};
    ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE);
    if (bytes_read > 0) {
        printf("Received client fd=%d DataLen: %d Data: %s\n", client_fd, (int)bytes_read, buffer);
    } else if (bytes_read == 0) {
        printf("Connection closed: client fd = %d\n",client_fd);
        // 关闭连接并从事件循环中移除
        reactor_remove_handler(client_fd);
        close(client_fd);
    } else {
        perror("Read error");
        // 关闭连接并从事件循环中移除
        reactor_remove_handler(client_fd);
        close(client_fd);
    }
}

// 初始化事件循环机制
void reactor_init() {
    epoll_fd = epoll_create(1);
    if (epoll_fd < 0) {
        perror("Epoll creation failed");
        exit(1);
    }
    printf("Create epoll successed: epoll fd = %d\n",epoll_fd);
}

// 添加事件处理器到事件循环中
void reactor_add_handler(int fd, EventHandler event_handler) {
    struct epoll_event event;
    event.events = EPOLLIN;          // 只监听读事件
    event.data.fd = fd;
    // 将文件描述符添加到 epoll 实例中进行事件监听
    int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
    if (ret < 0) {
        perror("Epoll control failed");
        exit(1);
    }
    // 将事件处理器添加到数组中
    event_handlers[num_handlers++] = event_handler;
}

// 从事件循环中移除事件处理器
void reactor_remove_handler(int fd) {
    // 从 epoll 实例中移除文件描述符
    int ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
    if (ret < 0) {
        perror("Epoll control failed");
        exit(1);
    }
    // 从数组中移除事件处理器
    for (int i = 0; i < num_handlers; i++) {
        if (event_handlers[i].fd == fd) {
            event_handlers[i] = event_handlers[--num_handlers];
            break;
        }
    }
}

// 启动事件循环
void reactor_run() {
    struct epoll_event events[MAX_EVENTS];
    while (1) {
        // 等待事件发生
        int num_ready = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (num_ready < 0) {
            perror("Epoll wait failed");
            break;
        }

        // 遍历就绪事件,并调用相应的事件处理函数
        for (int i = 0; i < num_ready; i++) {
            int fd = events[i].data.fd;
            // 查找对应的事件处理器并调用其处理函数
            for (int j = 0; j < num_handlers; j++) {
                if (event_handlers[j].fd == fd) {
                    event_handlers[j].handler(fd);
                    break;
                }
            }
        }
    }
    close(epoll_fd);    //清理操作
}

猜你喜欢

转载自blog.csdn.net/weixin_43729127/article/details/131741179