网络编程(三、服务器reactor模型)

上一节讲过用epoll管理fd,但是这个管理方法很粗糙,都不知道是哪个客户端的,只是遍历epoll,然后有数据的打印,没有数据就等待,这样的程序是不能用于实际的工程项目的,假如客户端A要向客户端C发送数据,中间经过服务器,服务器怎么快速的找到客户端B的fd,并进行发送,那么多个客户端,怎么管理,需要查找的时候能快速找到,所以这一节我们介绍一下服务器reactor模型。

3.1 模型介绍

对搞并发编程,网络连接上的消息处理,分为两个阶段:等待消息准备好,消息处理

当使用默认的阻塞套接字(1个线程绑定1个连接),往往把这两个阶段和二为一了,这个对套接字的代码所在的线程就要睡眠来等待消息准备好,导致了高并发下线程频繁的睡眠。唤醒、从而影响CPU使用效率。

高并发编程方法当然是把两个阶段分开处理,等待消息准备好的代码段,与处理消息的代码段是分离的。

那等待消息这个阶段是怎么实现的?
线程主动查询或者让1个线程为所有连接而等待。这就是IO多路复用。多路复用就是处理等待消息这件事的,虽然它也会睡眠,但是因为只有一个线程不要紧,并且他实现了一对多,可以监控很多个连接,如果有连接准备好了,就可以唤醒,然后就交给处理消息阶段处理了。

高性能服务器需要考虑三类事件:IO事件,定时事件及信号。

3.2 reactor模型介绍

普通函数的机制,程序调用某函数,函数执行,然后等待,等到接收到了函数之后,把结果返回给程序,

而我们的reactor模型:应用程序不是主动的调用某个API完成处理,而是恰恰相反,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reator讲主动调用应用程序注册的接口。

Reactor模型是处理并发IO比较常见的一种模式,用于同步IO,中心思想是将所有要处理IO事件注册到一个中心IO多路复用器上(这里用epoll),一旦有IO事件到来或是准备就绪,多路复用器返回并将事先注册的相应IO事件分发到对应的处理器中。

Reactor模型有三个重要组件:

  • 多路复用器:由操作系统提供,在linux上一般是select、poll、epoll
  • 事件分发器:将多路复用器中断返回就绪事件分到对应的处理函数中
  • 事件处理器:负责处理特定事件的处理函数

3.2.1 程序思路

要记得上面说的三个重要组件,在分析程序的时候,我会重点提出的:

数据结构:

//数据结构是程序的灵魂

//反应堆的数据结构
struct nty_reactor
{
    int epfd;		//epoll的fd
    struct nty_event *event;    //对socket的封装
};

//对 一个socket的封装
struct nty_event
{
    int fd;        //每个连接的fd
    int events;	  //epoll中的event事件	

    int status;   //状态,可读可写
    void *arg;    //回调函数的参数
    int (*callback)(int fd, int events, void *arg);    //回调函数
    long last_active;		//上一次活跃的时间

    char buffer[1024];		//接受和写的buff
    int len;				//数据长度
};

初始化:这个就简单了,申请reactor的内存和申请nty_event的内存

多路复用器:

//这里多路复用器用了epoll。所以多路复用器支持添加和删除
int nty_event_add(struct nty_event *event, int epfd, int events)
    {
        struct epoll_event ev;
        int op = 0;

        memset(&ev, 0, sizeof(struct epoll_event));

        ev.data.fd = event->fd;
        ev.data.ptr = event;
        ev.events = event->events = events;

        if(event->status == 0) {
            op = EPOLL_CTL_ADD;
            event->status = 1;
        } else if(event->status == 1) {
            op = EPOLL_CTL_MOD;
        }

        epoll_ctl(epfd, op, event->fd, &ev);

        return 0;
    }

    int nty_event_del(struct nty_event *event, int epfd)
    {
        if(event->status == 0) {
            return -1;
        }

        event->status = 0;
        epoll_ctl(epfd, EPOLL_CTL_DEL, event->fd, NULL);

        return 0;
    }

事件分发器:(是不是就是反应堆)

//我们需要把上面的事件都添加到反应堆中,由反应堆去检测有没有事件发生,如果有时间发生就发到时间处理器中

int nty_reactor_addlistener(struct nty_reactor *reactor, int fd, NCALLBACK listener)
    {
        if(reactor == NULL)  return -1;
        if(reactor->event == NULL)   return -1;

        nty_event_set(&reactor->event[fd], fd, listener, reactor);
        nty_event_add(&reactor->event[fd], reactor->epfd, EPOLLIN);

        return 0;
    }

//回调函数就是时间处理函数,反应堆管理fd是利用了数组的特性

//然后接着开启一个线程,等待epoll_wait有数据返回,如果有数据返回,获取到对应的回调函数,进行调用(实践处理器)

就在调用函数中处理了数据,具体实例可以在下节课说。

总结:
在这里插入图片描述

3.3 proactor模型介绍

proactor模型并不是我们的重点,这是windows系统常用的模型,但是linux系统还是常用reactor模型。这里就加入一个思维导图就可以了

在这里插入图片描述

3.4 用reactor模型模拟proactor模型

Boot。asio库采用的就是Proactor模型,在linux平台中采用epoll实现的reactor来模拟Proactor.

模拟的核心思想,就是处理客户端请求的时候,用另外一个线程去处理,这样做到了异步,主线程还是等待socket数据是否已经准备好,准备好就可以去读取,读取了数据之后,如果需要处理客户端请求,就发送到请求队列中,然后另外一个线程就会去处理,从而做到了异步。

3.5 总结

比较偷懒,就以两个图来总结把
在这里插入图片描述

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值