多路转接-epoll/Reactor(2)

news2024/11/18 3:11:12

epoll

上次说到了poll,它存在效率问题,因此出现了改进的poll----epoll。

目前epoll是公认的效率最高的多路转接的方案。

快速了解epoll接口

 epoll_create:

这个参数其实已经被废弃了。 这个值只要大于0就可以了。

 这是用来创建一个epoll模型的。

创建成功了就返回一个文件描述符。失败了返回-1

epoll_wait:

第一个参数就是epoll_create的返回值。后面俩参数是用户定义的缓冲区,用来返回就绪的文件描述符和事件。这里的timeout的单位是毫秒,跟poll那里一样。

这个接口的返回值是:已经就绪的fd的个数。

epoll_event结构体

 这里依旧是以位图的形式传递标记位。

epoll_ctl:

第一个参数依旧是epfd,op表示我们要进行哪些操作

 看名字也能大概看出来,分别是新增,修改和删除。

epoll原理

在Linux内核中,专门为epoll设计了一套独立的模型

 首先在内核中设计了一颗红黑树,它以fd为键值,其结点的值一般是一个结构体,里面包含了要关心的fd,以及这个fd关心的事件,还有一些链接字段等,然后还会有一个等待队列,如果有fd就绪了,就会将结点链入到这个就绪队列中,等待上层来处理,注意:这个结点既可以在红黑树中,也可以同时在就绪队列中。我们用epoll的时候,OS会将一个回调函数注册到底层,底层一旦就绪,就会自动调用这个回调函数,比如将数据向上交付,交给tcp的接受队列,查找红黑树中的fd,然后构建就绪结点插入到就绪队列中。

  这样,用户只需要从就绪队列中获取就绪结点就可以了。因此我们把这三个组合起来就是epoll模型。

这样回想起来之间了解的接口,比如epoll_create,它创建的就是一套epoll模型和注册底层的回调机制。但是有没有可能创建了多个epoll模型呢?那么多个模型OS也要管理起来,(比如可以记录红黑树的头结点指针和队列的头结点指针,就可以将这两个结构放在一起。)所以内核会创建一个struct file,把它也当作一个文件,struct file中也存在一个指针指向这个模型,OS再将这个文件添加到进程的文件描述符表里,通过fd来找到,这就解释了为什么epoll_create返回的也是一个文件描述符,因为epoll模型也被接入到了struct file中了。

如下

到这里,我们发现,select&&poll,他俩跟epoll完全不一样。 

所以现在对着图来看epoll的接口,就会发现简单的很多了,epoll_create就是为了创建epoll模型,epoll_ctl就是拿着epfd,进行的操作其实就是在修改红黑树。 

epoll_wait也是拿着epfd,看就绪队列是否有fd就绪。

另外这里的红黑树的性质,像不像我们在使用select/poll时用户维护的数组?性质是一样的,不过这次是由内核维护的。所以epoll是单独设计的一点,还体现在用户不需要再使用额外的数据结构来管理文件描述符和要关心的事件了。

epoll的优势:

1.检测就绪的时间复杂度是O(1)。因为只要看就绪队列是否为空就可以了。

获取就绪fd的时间复杂度是O(n)。

2.fd和event没有上限。

3.epoll的返回值 n,表示有几个fd就绪了,并且在内核中,会把这些结点一个一个弹出。就绪事件是连续的。这样就不需要用户再进行遍历排除非法的fd了,也就是需要浪费用户额外的时间了。

在进入代码前,可以先了解一下cmake

CMake

cmake是一个自动生成makefile的工具。

在CentOS下可以用

sudo yum install -y cmake

 来安装Cmake,如果是Ubuntu的话把yum改成apt就好了。

cmake --version

这个命令来查看cmake的版本。 

关于使用

首先要创建一个文件:CMakeLists.txt

因为我的CentOS只能安装到2.8.12.2这样的版本,所以在这里我选择最低的版本为2.8这样的,写好后,我们执行

cmake .

然后就会生成一大堆文件,包括makefile,

 vim一下这个Makefile

发现已经帮我们写好了

我们可以直接make

接下来就跟我们熟悉的一样了。

所以其实makefile已经有取代方案了,今后我们面对复杂的项目,使用CMake会简单很多。 

epoll的工作模式 

  epoll有两种工作模式:

LT(Level Triggered): 水平触发

一般epoll的默认工作模式就是LT模式。

当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分.
如上面的例子, 由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait
仍然会立刻返回并通知socket读事件就绪.
直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回.
支持阻塞读写和非阻塞读写

也就是说,在LT模式下,如果上层不及时处理数据,LT会一直通知,直到数据全部被处理。 

ET(Edge Triggered):边缘触发

当epoll检测到socket上事件就绪时, 必须立刻处理.
如上面的例子, 虽然只读了1K的数据, 缓冲区还剩1K的数据, 在第二次调用 epoll_wait 的时候,
epoll_wait 不会再返回了.
也就是说, ET模式下, 文件描述符上的事件就绪后, 只有一次处理机会.
ET的性能比LT性能更高( epoll_wait 返回的次数少了很多). Nginx默认采用ET模式使用epoll.
只支持非阻塞的读写
也就是说只有因为新增而引起的变化时,ET才会通知。
一般普遍的认为ET的工作效率要比LT要高一些。
ET的效率高主要是它的 通知效率高。
因为ET模式下,只会通知一遍,因此在ET模式下,每次通知,必须把数据全部读走,一般是用循环读取,直到读取的字节数少于预期读取的字节数,或者读取出错才停止。另外因为fd是默认阻塞的,所以在ET模式下,要讲fd设置成非阻塞的,避免服务器因为读取被挂起。(这里有可能会成为面试题的)
ET的效率更高,不仅仅是通知效率更高(因为通知一次就要全部读走),它的IO效率也要更高一些,但是IO效率更高要如何理解呢?
我们以TCP协议为例,如果我们能一次将数据全部读走,那么我们就能给对方通知一个更大的窗口,从概率上让对方能一次发送更多的数据,因此IO效率也可能就更高。
但是话说回来,LT的效率一定比ET要低吗?如果在LT模式下,我们也是循环读取,在通知第一次的时候就将数据全部读走的话,不就和ET一样了吗?所以这个时候ET就不一定比LT高效了。

epollecho服务简单实现 

首先对epoll进行简单的封装

Epoller.hpp

#pragma once

#include "nocopy.hpp"
#include "Log.hpp"
#include <cerrno>
#include <cstring>
#include <sys/epoll.h>

class Epoller : nocopy
{
    static const int size = 128;
public:
    Epoller()
    {
        _epfd = epoll_create(size);
        if(_epfd == -1)
        {
            lg(Error,"epoll_create error: %s",strerror(errno));
        }
        else
        {
            lg(Info,"epoll_create success: %d",_epfd);
        }
    }

    int Epollwait(struct epoll_event revents[],int num)
    {
        int n = epoll_wait(_epfd,revents,num,-1); // -1表示阻塞等待
        return n;
    }

    int EpollUpdata(int oper,int sock,uint32_t event)
    {
        int n = 0;
        if(event == EPOLL_CTL_DEL) // 这里对于删除操作单独处理了
        {
            n = epoll_ctl(_epfd,oper,sock,nullptr);
            if(n != 0)
            {
                lg(Error,"epoll_ctl delete error");
            }
        }
        else 
        {
            // EPOLL_CTL_ADD || EPOLL_CTL_MOD
            struct epoll_event ev;
            ev.events = event;
            ev.data.fd = sock; // 为了方便后期它就绪时,我们知道是哪个fd。
            n = epoll_ctl(_epfd,oper,sock,&ev); // 本质上是对红黑树进行插入或修改结点
            if(n != 0)
            {
                lg(Error,"epoll_ctl error");
            }
        }

        return n;
    }

    ~Epoller()
    {
        if(_epfd >= 0)
            close(_epfd);
    }
private:
    int _epfd;
    int _timeout;
};

 EpollServer.hpp

#pragma once

#include <iostream>
#include <memory>
#include <sys/epoll.h>
#include "Socket.hpp"
#include "Log.hpp"
#include "Epoller.hpp"
#include "nocopy.hpp"

uint32_t EVENT_IN = (EPOLLIN);
uint32_t EVENT_OUT = (EPOLLOUT);

class EpollServer : nocopy
{
    const int num = 64;
public:
    EpollServer(uint16_t port = 8080)
        :_port(port)
        ,_listensocket_ptr(new Sock())
        ,_epoller_ptr(new Epoller())
        {}
    
    void Init()
    {
        _listensocket_ptr->Socket();
        _listensocket_ptr->Bind(_port);
        _listensocket_ptr->Listen();

        lg(Info,"create listen socket success :%d\n",_listensocket_ptr->Fd());
    }

    void Start()
    {
        // 首先将listensock添加到epoll中(还有其关心的事件),也就是添加到内核的红黑树中
        _epoller_ptr->EpollUpdata(EPOLL_CTL_ADD,_listensocket_ptr->Fd(),EVENT_IN);
        struct epoll_event revs[num]; // num是我们自己定的,不受接口限制
        while(true)
        {
            int n = _epoller_ptr->Epollwait(revs,num);
            if(n > 0)
            {
                // 有事件就绪了
                lg(Debug,"event happend, fd is : %d",revs[0].data.fd);
                Dispatcher(revs,n);
            }
            else if(n == 0)
            {
                lg(Info,"time out ....\n");
            }
            else 
            {
                lg(Error,"epoll_wait eroor\n");
            }
        }
    }

    void Dispatcher(struct epoll_event* revs,int num)
    {
        for(int i = 0; i < num; ++i)
        {
            uint32_t events = revs[i].events;
            int fd = revs[i].data.fd;
            if(events & EVENT_IN)
            {
                if(fd == _listensocket_ptr->Fd())
                {
                    //新链接
                    Accepter();
                }
                else 
                {
                    // 普通读取
                    Recver(fd);
                }
            }
            else // 这里只考虑读取事件了
            {}
        }
    }

    void Accepter()
    {
        std::string clientip;
        uint16_t clientport;
        int sock = _listensocket_ptr->Accept(&clientip,&clientport);
        if(sock < 0)
        {
            lg(Error,"Accept error, fd is : %d",sock);
        }

        // 获取到链接后,不能直接read,而是将fd添加进epoll
        _epoller_ptr->EpollUpdata(EPOLL_CTL_ADD,sock,EVENT_IN);
        lg(Info,"get a new link, clientip : %s, clientport: %d",clientip.c_str(),clientport);
    }
    
    void Recver(int fd)
    {
        // 这里依旧只是一个demo,并没有处理到位
        char buffer[1024];
        ssize_t n = read(fd,buffer,sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "get a massge: " << buffer << std::endl;
            //然后回显给对方
            std::string echo_str = "server echo $ ";
            echo_str += buffer;
            write(fd,echo_str.c_str(),echo_str.size());
        }
        else if(n == 0)
        {
            lg(Info,"client quit, me too,close fd is: %d",fd);
            // 这里关闭时有一个细节,要先从epoll中移除
            // 再close,避免先close导致fd非法再从epoll中移除而报错
            _epoller_ptr->EpollUpdata(EPOLL_CTL_DEL,fd,0);
            close(fd);
        }
        else 
        {
            lg(Warning,"recver error,close ..");
            _epoller_ptr->EpollUpdata(EPOLL_CTL_DEL,fd,0);
            close(fd);
        }
    }

    ~EpollServer()
    {
        _listensocket_ptr->Close();
    }

private:
    std::unique_ptr<Sock> _listensocket_ptr;
    std::unique_ptr<Epoller> _epoller_ptr;
    uint16_t _port;
};

在读取这里,以TCP为例,因为TCP是面向字节流的,我们并不能保证每次读取都是能读到完整的数据, 如果当前缓冲区里只有半个请求,我们读吗?读了之后放哪呢?在这里我们并没有处理,因此会在Ractor中解决。

我们可以通过单独设计一个类来进行防拷贝

nocopy.hpp

#pragma once

class nocopy
{
public: 
    nocopy(){}
    nocopy(const nocopy &) = delete;
    const nocopy& operator=(const nocopy &) = delete;
};

Reactor

 Reactor模式是一种事件驱动的并发编程模型,它解决了在高并发环境下处理大量客户端请求的问题。

  Reactor其实是一种半同步半异步的模型,同步是因为调用epoll,要自己等,异步体现在它可以进行回调处理。

代码:

首先我们先对设置非阻塞的函数进行一个封装

Comm.hpp

#pragma once

#include <unistd.h>
#include <fcntl.h>

void SetNonBlockOrDie(int sock)
{
    int fl = fcntl(sock,F_GETFL);
    if(fl < 0)
        exit(4);
    fcntl(sock,F_SETFL,fl | O_NONBLOCK);
}

TcpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <memory>
#include <cerrno>
#include <unordered_map>
#include <functional>
#include "Comm.hpp"
#include "Log.hpp"
#include "nocopy.hpp"
#include "Epoller.hpp"
#include "Socket.hpp"

class Connection;
class TcpServer;

uint32_t EVENT_IN = (EPOLLIN | EPOLLET);
uint32_t EVENT_OUT = (EPOLLOUT | EPOLLET);

const static int g_buffer_size = 128;

using func_t = std::function<void(std::weak_ptr<Connection>)>;
using except_func = std::function<void(std::weak_ptr<Connection>)>;

// 对每个链接进行管理
class Connection
{
public:
    Connection(int sock)
        : _sock(sock)
    {
    }

    void SetHandler(func_t recv_cb, func_t send_cb, except_func except_cb)
    {
        _recv_cb = recv_cb;
        _send_cb = send_cb;
        _except_cb = except_cb;
    }

    int SockFd()
    {
        return _sock;
    }

    void AppendInBuffer(const std::string &info)
    {
        _inbuffer += info;
    }

    void AppendOutBuffer(const std::string &info)
    {
        _outbuffer += info;
    }

    std::string &InBuffer()
    {
        return _inbuffer;
    }

    std::string &OutBuffer()
    {
        return _outbuffer;
    }

    void SetWeakPtr(std::weak_ptr<TcpServer> tcp_server_ptr)
    {
        _tcp_server_ptr = tcp_server_ptr;
    }

    ~Connection()
    {
    }

private:
    int _sock;
    std::string _inbuffer; // 输入缓冲区,但是无法接收二进制流
    std::string _outbuffer;

public:
    // 回调方法
    func_t _recv_cb;
    func_t _send_cb;
    func_t _except_cb;

    // 回指指针
    std::weak_ptr<TcpServer> _tcp_server_ptr;

    std::string _ip;
    uint16_t _port;
};

class TcpServer : public std::enable_shared_from_this<TcpServer>, public nocopy
{
    static const int num = 64;

public:
    TcpServer(uint16_t port, func_t OnMessage)
        : _port(port),
          _OnMessage(OnMessage),
          _quit(true),
          _epoller_ptr(new Epoller()),
          _listensock_ptr(new Sock())
    {
    }

    void Init()
    {
        _listensock_ptr->Socket();
        SetNonBlockOrDie(_listensock_ptr->Fd());
        _listensock_ptr->Bind(_port);
        _listensock_ptr->Listen();
        lg(Info, "create listen socket success: %d", _listensock_ptr->Fd());
        Addconnection(_listensock_ptr->Fd(), EVENT_IN, std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);
    }

    void Addconnection(int sock, uint32_t event, func_t recv_cb, func_t send_cb, except_func except_cb,
                       const std::string &ip = "0.0.0.0", uint16_t port = 0)
    {
        // 1.给sock建立一个Connection对象,并插入到map中
        std::shared_ptr<Connection> new_connection(new Connection(sock));
        new_connection->SetWeakPtr(shared_from_this()); // shared_from_this()是返回当前对象的shared_ptr,目的是设置回指
        new_connection->SetHandler(recv_cb, send_cb, except_cb);
        new_connection->_ip = ip;
        new_connection->_port = port;

        // 2.添加到unordered_map中
        _connections.insert(std::make_pair(sock, new_connection));

        // 3.添加对应的fd和事件到epoll中
        _epoller_ptr->EpollUpdata(EPOLL_CTL_ADD, sock, event);
    }

    // 链接管理器
    void Accepter(std::weak_ptr<Connection> conn)
    {
        auto connection = conn.lock();
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sock = ::accept(connection->SockFd(), (struct sockaddr *)&peer, &len); // :: 表示使用系统原生的接口
            if (sock > 0)
            {
                uint16_t peerport = ntohs(peer.sin_port);
                char ipbuf[128];
                inet_ntop(AF_INET, &peer.sin_addr.s_addr, ipbuf, sizeof(ipbuf));
                lg(Debug, "get a new client,get info[%s : %d],sockfd : %d", ipbuf, peerport, sock);

                SetNonBlockOrDie(sock);
                // listensock只要设置recv_cb,而其他sock读写异常都要关心设置
                Addconnection(sock, EVENT_IN,
                              std::bind(&TcpServer::Recver, this, std::placeholders::_1),
                              std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                              std::bind(&TcpServer::Excepter, this, std::placeholders::_1),
                              ipbuf, peerport);
            }
            else
            {
                if (errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    continue;
                else
                    break; // 说明是真出错了
            }
        }
    }

    // 事件管理器
    // 这里不需要关心数据的格式,服务器只要IO数据即可,有没有读完,报文的细节交给上层
    void Recver(std::weak_ptr<Connection> conn)
    {
        if (conn.expired())
            return;
        auto connection = conn.lock();
        int sock = connection->SockFd();
        while (true) // 循环读取数据,因为是ET模式
        {
            char buffer[g_buffer_size];
            memset(buffer, 0, sizeof(buffer));
            int n = recv(sock, buffer, sizeof(buffer) - 1, 0); // 虽然这里flags是0,但是sock已经设置成非阻塞了
            if (n > 0)
            {
                connection->AppendInBuffer(buffer);
            }
            else if (n == 0)
            {
                lg(Info, "sockfd : %d,client info %s:%d,quit...", connection->SockFd(), connection->_ip.c_str(), connection->_port);
                connection->_except_cb(connection);
                return;
            }
            else
            {
                if (errno = EWOULDBLOCK)
                    break;
                if (errno == EINTR)
                    continue;
                else
                {
                    lg(Warning, "sockfd: %d,client info %s:%d recv error...", connection->SockFd(), connection->_ip.c_str(), connection->_port);
                    connection->_except_cb(connection);
                    return;
                }
            }
        }

        // 数据读上来了,但是不一定完整,交给上层处理
        _OnMessage(connection);
    }

    // 发送这里会麻烦一些
    void Sender(std::weak_ptr<Connection> conn)
    {
        if (conn.expired())
            return;
        auto connection = conn.lock();
        auto &outbuffer = connection->OutBuffer();
        while (true)
        {
            ssize_t n = send(connection->SockFd(), outbuffer.c_str(), sizeof(outbuffer), 0); // 同理,不会再阻塞了
            if (n > 0)
            {
                outbuffer.erase(0, n);
                if (outbuffer.empty())
                    break;
            }
            else if (n == 0)
            {
                return;
            }
            else
            {
                if (errno = EWOULDBLOCK)
                    break;
                if (errno == EINTR)
                    continue;
                else
                {
                    lg(Warning, "sockfd: %d,client info %s:%d send error...", connection->SockFd(), connection->_ip.c_str(), connection->_port);
                    connection->_except_cb(connection);
                    return;
                }
            }
        }

        // 如果发送缓冲区被打满了,可能还没发完就退出循环了
        // 此时才来判断是否要关心写事件就绪
        if(!outbuffer.empty())
        {
            // 开启对写事件的关心
            EnableEvent(connection->SockFd(),true,true);
        }
        else 
        {
            // 关闭对写事件的关心
            EnableEvent(connection->SockFd(),true,false);
        }
    }

    void Excepter(std::weak_ptr<Connection> conn)
    {
        if(conn.expired());
        auto connection = conn.lock();

        int fd = connection->SockFd();
        lg(Warning,"Excepter hander sockfd: %d,client info %s : %d, excepter hander",
            fd,connection->_ip.c_str(),connection->_port);

        // 1.移除对该fd的关心
        _epoller_ptr->EpollUpdata(EPOLL_CTL_DEL,fd,0);

        // 2.再关闭fd
        close(fd);
        lg(Debug,"close fd done %d  ...",fd);

        // 3.再从unordered_map中移除
        _connections.erase(fd);
    }

    void EnableEvent(int sock,bool readable,bool writeable)
    {
        uint32_t events = 0;
        events = ((readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET);
        _epoller_ptr->EpollUpdata(EPOLL_CTL_MOD,sock,events);
    }

    bool IsConnectionSafe(int fd)
    {
        auto iter = _connections.find(fd);
        if(iter == _connections.end())
            return false;
        else 
            return true;
    }

    void Dispatcher(int timeout)
    {
        int n = _epoller_ptr->Epollwait(revs,num,timeout);
        for(int i = 0; i < n; ++i)
        {
            uint32_t event = revs[i].events;
            int sock = revs[i].data.fd;

            //这里只处理读写事件
            if((event & EPOLLIN) && IsConnectionSafe(sock))
            {
                if(_connections[sock]->_recv_cb)
                    _connections[sock]->_recv_cb(_connections[sock]);
            }
            if((event & EPOLLOUT) && IsConnectionSafe(sock))
            {
                if(_connections[sock]->_send_cb)
                    _connections[sock]->_send_cb(_connections[sock]);
            }
        }
    }

    void Loop()
    {
        _quit = false;

        while(!_quit)
        {
            Dispatcher(-1); // 阻塞式

        }

        _quit = true;
    }

    void PrintConnection()
    {
        std::cout << "_connections fd list: ";
        for (auto &connection : _connections)
        {
            std::cout << connection.second->SockFd() << ", ";
            std::cout << "inbuffer: " << connection.second->InBuffer().c_str();
        }
        std::cout << std::endl;
    }

    ~TcpServer()
    {}

private:
    std::shared_ptr<Epoller> _epoller_ptr;                             // 内核
    std::shared_ptr<Sock> _listensock_ptr;                             // 监听socket
    std::unordered_map<int, std::shared_ptr<Connection>> _connections; // 哈希表管理链接
    struct epoll_event revs[num];
    uint16_t _port;
    bool _quit;
    // 处理上层信息(跟上层的应用场景有关)
    func_t _OnMessage;
};

其中在写,也就是Sender那里要说明:

select/poll/epoll因为发送缓冲区经常有空间,因此写事件是经常就绪的,如果我们对它进行EPOLLOUT设置关心,EPOLLOUT几乎每次都就绪,server经常返回,浪费CPU资源,

因此:对于读事件,我们设置常关心,对于写事件,按需关心 -> 也就是直接写入,如果写入完成,那么就结束了,不需要关心,如果因为一些原因(比如发送缓冲区满了)而没写完,也就是outbuffer里面还有数据,此时再设置关心,等下次写完了,再去掉关心。 

Main.cc

#include <iostream>
#include <functional>
#include <memory>
#include "Log.hpp"
#include "TcpServer.hpp"  // 只处理IO
// 这里可以把之前的网络计算器搬过来,用来当作上层应用


// for debug
void DefaultOnmessage(std::weak_ptr<Connection> conn)
{
    if(conn.expired()) return;
    auto connection_ptr = conn.lock();

    std::cout << "上层得到了数据: " << connection_ptr->InBuffer() << std::endl;

    // 在这里可以加入多线程(线程池),那么主线程只负责读取数据,在这里交给其他线程处理数据

    //std::string response_str = ?
    // if(response_str.empty()) return;
    // lg(Debug, "%s", response_str.c_str());
    // response_str 发送出去
    // connection_ptr->AppendOutBuffer(response_str);
    // 正确的理解发送?
    // connection_ptr->_send_cb(connection_ptr);
    
    auto tcpserver = connection_ptr->_tcp_server_ptr.lock();
    tcpserver->Sender(connection_ptr);
}

int main()
{
    std::shared_ptr<TcpServer> epoll_svr(new TcpServer(8080,DefaultOnmessage));
    epoll_svr->Init();
    epoll_svr->Loop();

    return 0;
}

总之我们的服务仅仅只是处理数据,将读取上来,而并没有做处理,我们也可以加入上层应用,比如之前的网络版本计算器,现在没有加入,导致读取上来的数据是堆积在一起的。

在大型服务器中,我们还可以加入多线程,主线程专门只做连接,和读取数据,其他线程处理数据。

还可以加入链接管理机制,比如用一个小堆,里面放入每个链接的链接时长,对于长时间没有相应的链接,我们可以判定超时而将它关闭,如果在超时前发送了消息,那么就重置它的超时时间。 

总之Reactor就是一个反应堆,它是一个半同步半异步模型,说是同步,体现在它也要进行等待,它参与了这个过程,异步体现在它可以将数据交给其他线程处理,然后只将结果返回。

就像打地鼠游戏一样,玩家监视着很多地洞,哪个洞冒出了地鼠,我们玩家就要去处理。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1573338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用友U9 存在PatchFile.asmx接口任意文件上传漏洞

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 用友U9是由中国用友软件股份有限公司开发的一款企业…

FJSP:狐猴优化算法(Lemurs Optimizer,LO)求解柔性作业车间调度问题(FJSP),提供MATLAB代码

一、柔性作业车间调度问题 柔性作业车间调度问题&#xff08;Flexible Job Shop Scheduling Problem&#xff0c;FJSP&#xff09;&#xff0c;是一种经典的组合优化问题。在FJSP问题中&#xff0c;有多个作业需要在多个机器上进行加工&#xff0c;每个作业由一系列工序组成&a…

Linux(Ubuntu)中创建【samba】服务,用于和Windows系统之间共享文件

目录 1.先介绍一下什么是Samba 2.安装&#xff0c;配置服务 安装 配置&#xff08;smb.conf&#xff09; 配置用户 3.出现的问题&#xff08;Failed to add entry for user XXXX&#xff09; 4.创建文件夹 5.windows访问 1.先介绍一下什么是Samba Samba是一个开源的软…

HTML5.Canvas简介

1. Canvas.getContext getContext(“2d”)是Canvas元素的方法&#xff0c;用于获取一个用于绘制2D图形的绘图上下文对象。在给定的代码中&#xff0c;首先通过getElementById方法获取id为"myCanvas"的Canvas元素&#xff0c;然后使用getContext(“2d”)方法获取该Ca…

剑指Offer题目笔记26(动态规划的基础知识)

面试题88&#xff1a; 问题&#xff1a; ​ 一个数组cost的所有数字都是正数&#xff0c;它的第i个数字表示在一个楼梯的第i级台阶往上爬的成本&#xff0c;在支付了成本cost[i]之后可以从第i级台阶往上爬1级或2级。请计算爬上该楼梯的最少成本。 解决方案一&#xff1a;&…

【简单讲解下epoll】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

Day:004(1) | Python爬虫:高效数据抓取的编程技术(数据解析)

数据解析-正则表达式 在前面我们已经搞定了怎样获取页面的内容&#xff0c;不过还差一步&#xff0c;这么多杂乱的代码夹杂文字我们怎样 把它提取出来整理呢&#xff1f;下面就开始介绍一个十分强大的工具&#xff0c;正则表达式&#xff01; 正则表达式是对字符串操作的一种…

力扣Lc28---- 557. 反转字符串中的单词 III(java版)-2024年4月06日

1.题目描述 2.知识点 1)用StringBuilder的方法 实现可变字符串结果 最后返回的时候用.toString的方法 2)在Java中使用StringBuilder的toString()方法时&#xff0c;它会返回StringBuilder对象当前包含的所有字符序列的字符串表示。 在我们的例子中&#xff0c;sb是一个Stri…

Django之五种中间件定义类型—process_request、process_view、process_response.......

目录 1. 前言 2. 基础中间件 3. 如何自定义中间件 4. 五种自定义中间件类型 4.1 process_request 4.2 process_view 4.3 process_response 4.4 process_exception 4.5 process_template_response 5. 最后 1. 前言 哈喽&#xff0c;大家好&#xff0c;我是小K,今天咋们…

90天玩转Python-02-基础知识篇:初识Python与PyCharm

90天玩转Python系列文章目录 90天玩转Python—01—基础知识篇:C站最全Python标准库总结 90天玩转Python--02--基础知识篇:初识Python与PyCharm 90天玩转Python—03—基础知识篇:Python和PyCharm(语言特点、学习方法、工具安装) 90天玩转Python—04—基础知识篇:Pytho…

ubuntu20.04.6将虚拟机用户目录映射为磁盘Z

文章目录 linux虚拟机设置为NAT模式安装sshd服务映射目录到windows磁盘安装samba套件修改配置文件smb.conf重启smbd并设置用户名和密码 windows映射遇到的问题1、设置好之后映射不成功2、smbd下载失败3、smbd密码配置问题4、当有改动时候&#xff0c;最好重启一下smbd服务 linu…

图解大型网站多级缓存的分层架构

前言 缓存技术存在于应用场景的方方面面。从浏览器请求&#xff0c;到反向代理服务器&#xff0c;从进程内缓存到分布式缓存&#xff0c;其中缓存策略算法也是层出不穷。 假设一个网站&#xff0c;需要提高性能&#xff0c;缓存可以放在浏览器&#xff0c;可以放在反向代理服…

4月6号排序算法(2)

堆排序 讲堆排序之前我们需要了解几个定义 什么叫做最大堆&#xff0c;父亲节点&#xff0c;以及孩子节点 将根节点最大的堆叫做最大堆或大根堆&#xff0c;根节点最小的堆叫做最小堆或小根堆。 每个节点都是它的子树的根节点的父亲 。 反过来每个节点都是它父亲的孩子 。 …

post请求搜索功能爬虫

<!--爬虫仅支持1.8版本的jdk--> <!-- 爬虫需要的依赖--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency>…

「 典型安全漏洞系列 」11.身份验证漏洞详解

身份验证是验证用户或客户端身份的过程。网站可能会暴露给任何连接到互联网的人。这使得健壮的身份验证机制成为有效的网络安全不可或缺的一部分。 1. 什么是身份验证 身份验证即认证&#xff0c;是验证给定用户或客户端身份的过程。身份验证漏洞使攻击者能够访问敏感数据和功…

网络安全之命令注入

漏洞原理&#xff1a; 应用系统设计需要给用户提供指定的远程命令操作的接口&#xff0c;比如&#xff1a;路由器&#xff0c;防火墙&#xff0c;入侵检测等设备的web管理界面。一般会给用户提供一个ping操作的web界面 用户从web界面输入目标IP&#xff0c;提交后台会对改IP地…

C语言 练习题

目录 1.统计二进制中1的个数 方法1 方法2 方法3 2.求两个数二进制中不同位的个数 方法1 方法2 3.打印整数二进制的奇数位和偶数位 4.用“ * ”组成的X形图案 5.根据年份和月份判断天数 6.结语 1.统计二进制中1的个数 【题目内容】 写一个函数返回参数二进制中 1 的个…

MATLAB实现数值求解高阶常微分方程组

一、高阶常微分方程组 高阶常微分方程是指包含多个高阶常微分方程的系统。这些方程通常涉及多个未知函数及其高阶导数。解决高阶常微分方程组通常比解决单个高阶常微分方程更为复杂&#xff0c;因为需要同时考虑多个方程和多个未知函数之间的关系。 一般来说&#xff0c;解决…

数据库 06-03 时间戳

01.什么是时间戳 “时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。通俗的讲, 时间戳是一份能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据。 02.用时间戳实现调度 定义 数据库给予一个事务一个时…

安全的通信协议HTTPS被攻击改采用什么防护方案

随着互联网的发展&#xff0c;保护用户在网上交换的敏感信息的安全性变得至关重要。HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff09;作为一种安全的通信协议&#xff0c;通过加密数据传输&#xff0c;保护用户的隐私和数据安全。然而&#xff0c;尽管HTTPS提…