目录
一,往期文章
二,基本概念
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.核心特征
- 事件驱动:Reactor 模式是一种事件驱动的设计模式,它通过监听事件的发生来驱动应用程序的行为。当一个事件发生时,Reactor 会根据事件类型将事件分发给对应的处理器进行处理。这种事件驱动的机制使得应用程序能够响应外部事件,并根据事件类型进行适当的操作。
- 非阻塞:Reactor 模式使用非阻塞的方式处理事件。事件的产生和处理是并发进行的,事件循环以及事件处理器都是以非阻塞的方式执行。这意味着当一个事件正在处理时,其他事件的处理不会被阻塞,从而提高了应用程序的并发性能。
- 多路复用:Reactor 模式利用操作系统提供的多路复用机制(select,poll,epoll等),允许一个事件循环同时监听多个事件源。通过这种机制,事件循环可以同时处理多个事件,并且只会在有事件发生时才会唤醒相应的事件处理器进行处理,避免了轮询的低效率问题。
- 可伸缩性:Reactor 模式的设计使得应用程序具有良好的可伸缩性。对于不同类型的事件,可以创建对应的事件处理器,并将其注册到事件分发器中。这样可以根据应用程序的需求,动态地增加或减少事件处理器的数量,从而实现更好地适应不断变化的工作负载。
- 灵活性:Reactor 模式的设计使得应用程序更加灵活和可扩展。事件的产生和处理被解耦,新增一种类型的事件只需要创建对应的事件处理器并进行注册,而不需要对事件循环进行修改。这种灵活性使得应用程序能够轻松地适应新的需求和变化。
- 高性能:由于 Reactor 模式的非阻塞和并发处理特性,它能够实现高性能的事件处理。应用程序不会因为某个事件的处理而阻塞其他事件的处理,从而提高了整体的性能和吞吐量。
总结
- Reactor 模式具有事件驱动、非阻塞、多路复用、可伸缩性、灵活性和高性能等特点。它能够提供高效的事件处理机制,使得应用程序能够并发地处理多个事件,并具有良好的可维护性和扩展性。
4.工作流程
- 初始化:创建一个事件循环(Event Loop)和一个事件分发器(Event Dispatcher)。事件循环是一个主循环,负责等待事件的到来并调度相应的事件处理器来处理事件。事件分发器负责将事件从事件循环传递给适当的事件处理器。
- 注册事件源和事件处理器:将事件源(Event Source)和对应的事件处理器(Event Handler)注册到事件分发器中,建立事件源与事件处理器的关联。注册过程中,为每个事件源和事件处理器分配唯一的标识符用于后续事件分发。
- 启动事件循环:启动事件循环,进入事件监听状态。事件循环开始等待事件的发生,并根据事件源所监听的事件类型,注册对应的事件处理器。
- 等待事件发生:事件循环通过事件分发器等待事件的发生。这可以通过事件源的阻塞方法、非阻塞方法、轮询等方式实现。事件循环会等待直到至少一个事件源发出通知。
- 事件通知:当一个或多个事件源发生事件时,它们会通知事件循环,并将事件从事件源传递给事件循环。
- 事件分发:事件循环收到事件通知后,将事件传递给事件分发器。
- 路由到事件处理器:事件分发器根据事件的类型和其他规则,选择合适的事件处理器来处理该事件。它会根据事件的标识符查找事件源和处理器之间的映射关系,并将事件传递给匹配的事件处理器。
- 执行事件处理器:事件处理器执行相应的操作来处理事件。这可以包括读取事件数据、进行计算、更新状态等。事件处理器的执行通常是非阻塞的,以便允许事件循环继续监听其他事件。
- 返回等待状态:当事件处理器完成对事件的处理后,它返回到事件循环,继续等待下一个事件的发生。
- 重复执行:事件循环不断重复上述步骤,监听和处理事件,直到满足停止条件。停止条件可以是等待时间超时、特定事件发生、特定状态满足或应用程序定义的其他条件。
5.用“网络通信”来理解 Reactor 模型
假设你正在开发一个聊天应用程序,用户可以通过该应用程序与其他用户进行实时通信。为了处理并发的网络连接,你可以使用Reactor模式。
- 首先,你创建一个事件循环(Event Loop)作为主循环,并初始化一个网络套接字,用于监听传入的连接请求。
- 当有新的客户端连接请求时,网络套接字会触发一个事件,将连接请求通知给你的事件循环。
- 接下来,你创建一个连接处理器(Connection Handler),它负责处理每个客户端的连接。连接处理器封装了处理连接的逻辑,比如身份验证、消息的接收和发送等。
- 你注册连接处理器到事件分发器(Event Dispatcher),建立套接字与连接处理器之间的映射关系。这样,当有新的连接请求时,事件分发器就能将连接事件传递给相应的连接处理器。
- 在事件循环启动后,它开始等待连接请求的发生。当有客户端连接时,套接字触发连接事件,并通知事件循环。
- 事件循环收到连接事件后,将连接事件交给事件分发器。
- 事件分发器根据连接事件的类型和其他规则,选择合适的连接处理器来处理该连接。它根据客户端的身份、请求类型等找到对应的连接处理器。
- 连接处理器执行相应的操作,比如身份验证、处理消息的接收和发送等。这些操作是非阻塞的,因此可以同时处理其他客户端的连接。
- 处理完成后,连接处理器返回到事件循环,继续等待下一个连接请求。
- 事件循环不断监听连接事件,将连接事件分发给对应的连接处理器来处理。这种解耦的设计使得你能够并发地处理多个客户端的连接和通信,提高了应用程序的响应性和扩展性。
总结
- 综上所述,Reactor 模式具有事件驱动、非阻塞、多路复用、可伸缩性、灵活性和高性能等特点。它能够提供高效的事件处理机制,使得应用程序能够并发地处理多个事件,并具有良好的可维护性和扩展性。
1.使用
epoll
进行多路复用实现 Reactor 模式的操作流程
- 初始化服务端:调用init_server函数,创建服务端套接字,在接口执行以下操作:
- 创建监听套接字:使用
socket
函数创建一个监听套接字,指定协议族、套接字类型和协议号。例如,socket(AF_INET, SOCK_STREAM, 0)
会创建一个TCP套接字。- 绑定地址和端口:使用
bind
函数将套接字绑定到指定的IP地址和端口号。需要创建一个sockaddr_in
结构体,并设置相应的地址类型、IP地址和端口号。通过调用bind
函数,并将套接字描述符和sockaddr_in
结构体的指针作为参数,将套接字与所需的地址绑定。- 监听连接:使用
listen
函数开始监听套接字。需要传入套接字描述符和最大等待连接数。这将设置套接字进入等待连接的状态,并且可以接受新的客户端连接。- 初始化事件循环机制:创建一个
epoll
实例,使用epoll_create
函数创建一个epoll
实例。返回的文件描述符可以用于后续的epoll
操作。- 创建事件处理器并添加到事件循环中:创建一个
EventHandler
结构体,包含需要监听的文件描述符和对应的事件处理函数。将该结构体作为参数,调用reactor_add_handler
函数将事件处理器添加到事件循环中。- 启动事件循环:调用
reactor_run
函数,即开始事件循环。在事件循环中,会执行以下操作:
- 使用
epoll_wait
函数阻塞等待就绪事件的发生。此函数接收epoll
文件描述符、用于获取事件的数组和数组长度。- 一旦有就绪事件,遍历事件数组,并找到对应的事件处理器。
- 调用事件处理器的处理函数,处理该事件。
- 处理连接事件:当监听套接字有新的连接时,会触发连接事件。在连接事件处理函数中,使用
accept
函数接受客户端连接,创建一个新的套接字用于与客户端进行通信。然后,创建一个新的事件处理器,并将该套接字添加到事件循环中。- 处理读事件:当有数据可读时,触发读事件。在读事件处理函数中,使用
read
函数读取数据并进行处理。如果读取到数据,则可以对数据进行相应的操作。如果读取返回0,表示连接已关闭,需要从事件循环中移除该套接字。- 从事件循环中移除事件处理器:当连接关闭或出现错误时,需要将事件处理器从事件循环中移除。通过调用
reactor_remove_handler
函数,将套接字从epoll
实例中移除,并在数组中删除相应的事件处理器。- 清理操作:在事件循环结束后,关闭监听套接字和
epoll
实例,并释放任何其他资源。
2.Reactor 模式实现代码(参考)
#include
#include #include #include #include #include #include #include #include #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); //清理操作 }