首页 > 系统相关 >【Linux网络编程】Reactor模式与Proactor模式

【Linux网络编程】Reactor模式与Proactor模式

时间:2024-08-28 20:53:03浏览次数:9  
标签:socket 内核 int listenFd 模式 线程 事件 Linux Reactor

【Linux网络编程】Reactor模式与Proactor模式

Reactor模式

Reactor 模式是指主线程即 IO 处理单元只负责监听文件描述符上是否有事件发生,有则立刻将该事件通知给工作线程即逻辑单元,除此之外,主线程不做任何其它实质性的动作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。

同步 IO 模型实现 Reactor 模式的工作流程:

  • 主线程往 epoll 内核事件表中注册 socket 上的读就绪事件。
  • 主线程调用 epoll_wait 等待 socket 上有数据可读。
  • 当 socket 上有数据可读时,epoll_wait 通知主线程,主线程则将 socket 可读事件放入请求队列。
  • 睡眠在请求队列上的工作线程被唤醒,它从 socket 上读取数据,并处理客户请求,然后往 epoll 内核事件表中注册写就绪事件。
  • 主线程调用 epoll_wait 等待 socket 可写。
  • 当 socket 可写时,epoll_wait 通知主线程,主线程将 socket 可写事件放入请求队列中。
  • 睡眠在请求队列上的某个工作线程被唤醒,它往 socket 上写入服务器处理客户请求的结果。

如下为 Reactor 模式的工作流程图:

image

工作线程从请求队列中取出事件后,将根据事件的类型来决定如何处理它:对于可读事件则执行读数据和处理请求的操作;对于可写事件则执行写数据的操作。因此并没有所谓的“读工作线程”和“写工作线程”。

Proactor 模式

与 Reactor 模式不同,Proactor 模式将所有的 IO 操作交由主线程与内核去处理,工作线程仅仅负责业务逻辑。

异步 IO 模型(以 aio_read 与 aio_write 为例)实现 Proactor 模式的工作流程:

  • 主线程调用 aio_read 函数向内核注册 socket 读完成事件,并告诉内核用户的读缓冲区的位置,以及读操作完成时如何通知应用程序。
  • 主线程继续处理其它逻辑。
  • 当 socket 上的数据被读入用户缓冲区后,内核将向应用程序发送一个信号,以通知应用程序数据已经可用。
  • 应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求后,调用 aio_write 函数向内核注册 socket 上的写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序。
  • 主线程继续处理其它逻辑。
  • 当用户缓冲区中的数据写入 socket 后,内核向应用程序发送一个信号,以通知应用程序数据已经发送完毕。
  • 应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭 socket。

如下为 Proactor 模式的工作流程图:

image

连接 socket 上的读写事件是通过 aio_read / aio_write 向内核注册的,因此内核将通过信号来向应用程序报告连接 socket 上的读写事件。所以,主线程中的 epoll_wait 调用仅用来检测监听 socket 上的连接请求事件,而不能用来检测连接 socket 上的读写事件。

Reactor 模型代码

#include "webserver.h"

WebServer::WebServer(int port, int trigMode, int timeoutMs) : port_(port), timeoutMs_(timeoutMs), 
                    isClose_(false), epoller_(new Epoller()) {
    InitEventMode_(trigMode);
    if(!InitSocket_()) { isClose_ = true };
}

WebServer::~WebServer() {
    close(listenFd_);
    isClose_ = true;
}

void WebServer::Start() {
    while (!isClose_) {
        int eventCnt = epoller_->Wait(timeoutMs_);
        for (int i = 0; i < eventCnt; ++i) {
            int fd = epoller_->GetEventFd(i);  // 获取事件对应的fd
            uint32_t events = epoller_->GetEvents(i);  // 获取事件的类型
            
            if (fd == listenFd_) {
                DealListen_();
            } else if (events & EPOLLIN) {
                DealRead_();  // 子线程中执行
            } else if (events & EPOLLOUT) {
                DealWrite_(); // 子线程中执行
            } else if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
                CloseConn_();
            } else {
                perror("Unexpected event");
            }
        }
    }
}

void WebServer::DealListen_() {
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    do {
        int cfd = accept(listenFd_, (struct sockaddr*)&cliaddr, &len);
        if (cfd < 0) { return ; }
        // 获取客户端信息
        char cliIp[16];
        inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));
        unsigned short cliPort = ntohs(cliaddr.sin_port);
        // 输出客户端的信息
        printf("client's ip is %s, and port is %d\n", cliIp, cliPort );
        SetFdNonblock(cfd);
        epoller_->AddFd(cfd, connEvent_ | EPOLLIN);
    } while (listenEvent_ & EPOLLET);
}

void WebServer::InitEventMode_(int trigMode) {
    listenEvent_ = EPOLLRDHUP;
    connEvent_ = EPOLLRDHUP | EPOLLONESHOT;  // 注册EPOLLONESHOT,防止多线程同时操作一个socket的情况
    switch ((trigMode))
    {
    case 0:
        break;
    case 1:
        connEvent_ |= EPOLLET;
        break;
    case 2:
        listenEvent_ |= EPOLLET;
        break;
    case 3:
        connEvent_ |= EPOLLET;
        listenEvent_ |= EPOLLET;
        break;
    default:
        connEvent_ |= EPOLLET;
        listenEvent_ |= EPOLLET;
        break;
    }
}

bool WebServer::InitSocket_() {
    // 创建socket
    listenFd_= socket(AF_INET, SOCK_STREAM, 0);
    if (listenFd_ == -1) {
        perror("socket");
        return false;
    }

    // 设置端口复用
    int optval = 1;
    setsockopt(listenFd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port = htons(port_);
    // 绑定
    int ret = bind(listenFd_, (struct sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1) {
        perror("bind");
        return false;
    }

    // 监听
    ret = listen(listenFd_, 128);
    if (ret == -1) {
        perror("listen");
        return false;
    }

    // 将listenFd_注册至epoll事件表中
    ret = epoller_->AddFd(listenFd_, listenEvent_ | EPOLLIN);
    if (ret == 0) {
        perror("Add listen error");
        return false;
    }

    // 设置listenFd_非阻塞
    SetFdNonblock(listenFd_);
    return true;
}

int WebServer::SetFdNonblock(int fd) {
    assert(fd > 0);
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
}

标签:socket,内核,int,listenFd,模式,线程,事件,Linux,Reactor
From: https://www.cnblogs.com/yangxuanzhi/p/18385524

相关文章

  • Linux监控&性能调优分析-perf(中)监控应用程序性能及剖析内存访问
    5用perf调查繁忙的CPU在调查系统性能问题时,可以使用perf工具来识别和监控最繁忙的CPU,以便集中精力。5.1用perfstat显示哪些CPU事件被计数通过禁用CPU计数聚合,您可以使用perfstat显示哪些CPU事件被计数。要使用此功能,必须使用-a标志在全系统模式下统计事件。#p......
  • 【Linux网络编程】基于 EPOLL 的 SOCKET 通信
    【Linux网络编程】基于EPOLL的SOCKET通信epoller.h#ifndefEPOLLER_H#defineEPOLLER_H#include<sys/epoll.h>#include<fcntl.h>#include<unistd.h>#include<assert.h>#include<errno.h>#include<vector>classEpoller{publ......
  • 重头开始嵌入式第二十九天(Linux系统编程 网络通信 tcp)
    目录1.常见网络模型1.bs2.p2p3.cs2.网络编程之TCP(传输控制协议)1.TCP模型2.服务器端:1.socket();2、bind();3、listen();4、accept();5、接受函数:/发送函数:6、close()  ===>关闭指定的套接字id;3.客户端:1.connect();2、send()3、客户端信息获取4、客户端的信息bin......
  • 单例模式 lock 多线程 双重检查锁定机制
    单例模式单例模式publicclassSingleton{//定义一个静态变量来保存类的实例privatestaticSingletonuniqueInstance;//定义一个标识确保线程同步privatestaticreadonlyobjectlocker=newobject();//定义私有构造函数,使外界不能创建该类......
  • Java设计模式之工厂模式详细讲解和案例示范
    在Java的设计模式中,工厂模式(FactoryPattern)是最常见和最有用的一种创建型模式。工厂模式的核心思想是将对象的创建与使用分离,从而提供了一种灵活的方式来创建不同类型的对象。这种模式尤其适用于复杂对象的创建过程,并且可以很好地应对对象类型的变化。本文将详细讲解工厂模......
  • 设计模式反模式:UML图示常见误用案例分析
    设计模式反模式:UML图示常见误用案例分析在软件开发中,设计模式是应对常见设计问题的最佳实践,但如果使用不当,设计模式也可能变成反模式,导致系统架构的复杂性增加,甚至引发各种问题。在这篇文章中,我们将探讨在UML图示中常见的设计模式误用案例,尤其是在电商交易系统中的实际应用......
  • 突破编程 C++ 设计模式(组合模式)详尽攻略
    在软件开发中,设计模式为程序员提供了解决特定问题的最佳实践。设计模式不仅提高了代码的可复用性和可维护性,还能帮助团队更好地进行协作。在这篇文章中,我们将深入探讨组合模式——一种结构型设计模式。组合模式允许你将对象组合成树形结构来表示“部分-整体”的层次关系。组合......
  • Cloudflare Workers 每日免费限制 超出流量自动关闭 - 失败模式 改为 失败时自动关闭
    cloudflareworkers每日免费限制超出流量自动关闭-失败模式改为失败时自动关闭(阻止)位置在Workers和Pages-相应的workers-设置-函数-更改失败模式改为失败时自动关闭(阻止)这个设置,网上竟然没有人说,这么重要的事情,应该要设置,必须要设置!!注意:设置后记得从新部署......
  • 软件设计师全套备考系列文章13 -- 数据库:概念、三级模式两级映像、设计过程、数据模型
    软考--软件设计师(13)--数据库:概念、三级模式两级映像、设计过程、数据模型文章目录软考--软件设计师(13)--数据库:概念、三级模式两级映像、设计过程、数据模型前言一、章节考点二、基本概念三、三级模式、两级映像四、设计过程五、数据模型前言考试时间:每年5月、......
  • 【Linux网络编程】I/O 多路复用技术
    【Linux网络编程】I/O多路复用技术什么是I/O多路复用?为什么需要I/O多路复用最简单的socket网络模型,就是单线程模型,一个同时进行监听、处理,然而,单线程模型同时只能服务一个客户端,当线程发生阻塞的时候,其他客户端只能排队等待,甚至连接失败。为了能够同时服务更多的客户端,......