当前位置: 首页 > article >正文

muduo库的模拟实现——TcpServer部分

文章目录

  • 一、Connection模块
    • 1.成员变量
    • 2.构造函数以及Channel的五个事件回调函数
    • 3.建立连接接口
    • 4.发送接口
    • 5.关闭连接接口
  • 二、TcpServer模块

一、Connection模块

Connection类可以说是muduo库里最复杂也是最核心的类的,在我看来这个类有承上启下的作用,承上是通过TcpServer模块与上层应用层协议进行交互,启下关联的是EventLoop等一系列Reactor部分的模块。所以本篇文章会着重介绍Connection模块的实现。

1.成员变量

我们先来看一下Connection类的成员变量:

    private:
        uint64_t _connectionID;    // 连接的唯一ID,便于连接的管理和查找
        uint64_t _timerID;         // 定时器ID,必须是唯一的,这块为了简化操作,使用_connectionID作为定时器ID
        int _sockfd;               // 连接关联的文件描述符
        bool _inactiveReleaseFlag; // 连接是否启动非活跃销毁的判断标志,默认为false表示关闭
        EventLoop *_eventLoop;     // 连接所关联的一个EventLoop
        ConnectionStatus _status;  // 连接的状态
        Socket _socket;            // 套接字操作管理
        Channel _channel;          // 连接的事件管理
        Buffer _inBUffer;          // 输入缓冲区---存放从socket中读取到的数据
        Buffer _outBuffer;         // 输出缓冲区---存放要发送给对端的数据
        ConnectedCallBack _connectedCallBack;
        MessageCallBack _messageCallBack;
        CloseCallBack _closeCallBack;
        EventCallBack _eventCallBack;

        // 这是组件内的连接关闭回调函数,是组件内设置的,因为服务器组件内会把所有的连接管理起来
        // 一旦某个连接要关闭了,就应该从管理的地方移除掉自己的信息
        CloseCallBack _serverCloseCallBack;
        Any _context; // 请求的接收处理上下文

其中有一个EventLoop对象_eventLoop,这是每个Connection都要关联的一个EventLoop对象,将来构造Connection对象的时候需要通过EventLoop对象来构造。什么时候会构造Connection对象呢?当主线程接收到新连接的时候。那由谁来构造Connection对象呢?当新连接到来的时候,TcpServer服务器会从线程池中选择一个子线程,由于子线程与EventLoop对象是一一对应的,所以可以通过子线程的EventLoop对象来构造Connection对象,这样就能将新连接通过Connection对象管理起来了。

Connection类有了EventLoop对象以后,还需要有Channel对象,用EventLoop对象构造一个Channel对象进行连接的事件管理。因为新连接会有IO数据通信,所以需要Channel对象来监控这些事件。连接到来以后,Connection模块通过Channel对象启动可读事件监控,就可以等待对方发送数据过来。当服务端要响应回去的时候,再通过Channel对象启动可写事件监控,就可以等待数据的发送。

既然涉及到数据的发送,那就必须要有缓冲区了,所以Connection类有接收缓冲区_inbuffer,和发送缓冲区_outbuffer。因为我们通过Channel对象启动读事件监控以后,当对方发送数据过来以后,比如发送的是HTTP请求报文,这时候就会调用可读事件的回调函数,去read读取这个请求报文,但读取上来不可能是Connection模块处理,只有HTTP模块能处理这些请求报文,所以Connection模块只负责数据的接收和保存,保存就保存在它的接收缓冲区中,等待上层HTTP模块来提取并解析。并且,通过缓冲区也能解决数据读取不完整或者出现粘包问题,因为上层一旦解析到报文不完整,就不会设置解析状态为finish完成解析,会继续等待Connection接收缓冲区的下一轮数据,不过这都是上层HTTP协议层要关心的事情,Connection模块并不关心,这就是缓冲区在这里的意义。

除此之外,Connection类还需要提供回调函数供上层使用者去设置,这些回调函数会在下面详细介绍。最后,Connection类还有一个ConnectionStatus状态机,就是用来表示当前连接的状态,分别有连接关闭状态、连接刚建立状态、连接完成状态和连接待关闭状态。

    typedef enum
    {
        DISCONNECTED, // 连接关闭状态
        CONNECTING,   // 连接刚建立的状态
        CONNECTED,    // 连接完成状态,可以进行各种通讯数据操作了
        DISCONNECTING // 连接待关闭状态
    } ConnectionStatus;

2.构造函数以及Channel的五个事件回调函数

介绍了成员变量以后,接下来再看看Connection类的构造函数,构造函数主要是对成员变量进行一些初始化赋值,并且设置channel对象的五个事件回调函数,这五个事件回调函数是Connection类提供的。

        Connection(EventLoop *eventLoop, uint64_t connectionID, int sockfd)
        :_connectionID(connectionID), _sockfd(sockfd), _inactiveReleaseFlag(false), _eventLoop(eventLoop), 
        _status(CONNECTING), _socket(_sockfd), _channel(eventLoop, _sockfd)
        {
            _channel.setCloseCallBack(std::bind(&Connection::handleClose, this));
            _channel.setEventCallBack(std::bind(&Connection::handleEvent, this));
            _channel.setReadAbleCallBack(std::bind(&Connection::handleRead, this));
            _channel.setWriteAbleCallBack(std::bind(&Connection::handleWrite, this));
            _channel.setErrorCallBack(std::bind(&Connection::handleError, this));
        }

下面再来介绍五个事件回调函数的实现,这五个事件回调函数就是连接关联的从属Reactor监控到事件IO事件触发以后,执行的回调函数,比如客户端向连接发送HTTP请求报文,那么epoll模型就会监控到可读事件触发,通过Channel对象去调用可读事件触发的回调函数,而这个函数绑定的就是Connection类实现的handleRead函数,这个函数的逻辑就是通过read接口或者recv接口将数据从连接的套接字中读取上来,至于如何处理那就是上层HTTP协议层需要关心的事情了。

handleClose函数:
首先看一下handleClose函数的实现,这是关闭连接事件触发的回调函数,一旦连接关闭了,套接字就不能做其它操作了,我们就没必要保存记录这个连接的信息了,所以需要关闭连接。但是关闭连接并不能立即关闭,因为有可能接收缓冲区里还有数据没有被处理,所以必须将接收缓冲区的数据通过_messageCallBack回调函数交给上层的协议层去处理,处理完毕以后,再调用releaseInLoop函数执行关闭连接的操作。releaseInLoop函数也是Connection类实现的成员函数,这是真正关闭释放连接的函数,下面会介绍。

        // 描述符触发挂断事件
        void handleClose()
        {
            // 一旦连接挂断了,套接字就什么都干不了了,因此有数据待处理就处理一下,处理完毕以后就关闭连接
            if(_inBUffer.getReadableSize() > 0)
            {
                _messageCallBack(shared_from_this(), &_inBUffer);
            }
            return releaseInLoop();
        }

handleEvent函数:
我们之前实现Channel类的时候,可读事件触发就调用可读事件绑定的回调函数,可写事件触发就调用可写事件绑定的回调函数,但无论是什么事件触发,最后都会调用handleEvent回调函数,这是任意事件触发都会调用的回调函数,所以Connection也要提供一个任意事件触发的回调函数。这个函数接口是用来实现其它四个事件回调函数都要执行的操作,为了不让代码重复冗余,就把这些操作单独拿出来执行,比如任意事件触发都要刷新定时器任务。

        // 描述符触发任意事件
        void handleEvent()
        {
            // 1.刷新连接的活跃度---延迟定时销毁任务
            if(_inactiveReleaseFlag == true)
            {
                _eventLoop->refreshTimer(_connectionID);
            }
            // 2.调用组件使用者的任意事件回调
            if(_eventCallBack)
            {
                _eventCallBack(shared_from_this());
            }
        }

handleRead函数:
handleRead函数就是可读事件触发的回调函数,当连接的文件描述符有数据到来的时候,可读事件就会触发,这个函数就会被调用。该函数会将文件描述符的数据都读取上来,保存在inBuffer接收缓冲区中,然后将接收缓冲区的数据通过_messageCallBack函数传递给上层,让上层去处理这些数据。_messageCallBack函数是外部设置的回调函数,一般绑定的就是应用层协议进行报文接收并解析的函数,也就是Connection负责将数据读取上来,应用层的_messageCallBack函数负责接收和解析报文。

        // 描述符可读事件触发后调用的函数,接收socket数据放到接收缓冲区中,然后调用messageCallBack
        void handleRead()
        {
            // 1.接收socket的数据放到缓冲区
            char buffer[65536];
            ssize_t recvRes = _socket.nonBlockRecv(buffer, 65535);
            if (recvRes < 0)
            {
                // 出错了,但也不能直接关闭连接,需要先看一下有没有数据还没有处理
                // 得要看一下发送缓冲区还有没有数据没有发送
                // 接收缓冲区还有没有数据没有处理
                return shutdownInLoop();
            }
            // 将数据放入接收缓冲区,并且将写偏移向后移动
            _inBUffer.writeAndPush(buffer, recvRes);
            // 2.调用messageCallBack进行业务处理
            if (_inBUffer.getReadableSize() > 0)
            {
                // shared_from_this---从当前对象自身获取自身的shared_ptr
                return _messageCallBack(shared_from_this(), &_inBUffer);
            }
        }

handleWrite函数:
handleWrite函数和handleRead函数是同理的,当连接的可写事件触发的时候,就代表服务器可以向对方发送数据了,于是就将outBuffer发送缓冲区里的数据发送出去。发送完之后,如果发送缓冲区中已经没有数据了,就可以关闭写事件监控了。

        // 描述符可写事件触发后调用的函数,将发送缓冲区的数据进行发送
        void handleWrite()
        {
            // outBuffer中保存的数据就是要发送的数据
            ssize_t writeRes = _socket.nonBlockSend(_outBuffer.getReadStartPosition(), _outBuffer.getReadableSize());
            if (writeRes < 0)
            {
                // 发送错误就该关闭连接了,但是可能缓冲区中还有数据需要被处理
                if (_inBUffer.getReadableSize() > 0)
                {
                    _messageCallBack(shared_from_this(), &_inBUffer);
                    // 这时候就是实际的关闭释放操作了
                    return releaseInLoop();
                }
            }
            // 还需要将读偏移向后移动
            _outBuffer.moveReadOffset(writeRes);

            // 如果发送缓冲区没有数据可写,就要关闭写事件监控
            if (_outBuffer.getReadableSize() == 0)
            {
                _channel.closeWriteAbleEvent();
                // 如果当前是连接待关闭状态,则有数据就发送完数据释放连接,没有数据就直接释放连接
                if (_status == DISCONNECTING)
                {
                    return releaseInLoop();
                }
            }
        }

handleError函数:
handleError函数实现的比较简单粗暴,只要是出错了,那就调用handleClose函数关闭连接即可。

        // 描述符触发出错事件
        void handleError()
        {
            handleClose();
        }

3.建立连接接口

当新连接到来的时候,TcpServer模块为新连接创建好Connection对象并且设置好一些列回调函数以后,就要调用Connection对象的建立连接接口,这个有点像Connection对象的启动接口吧,将连接状态设置为已建立连接状态,然后启动Channel对象的可读事件监控,最后调用应用层协议的建立连接函数_connectedCallBack,有可能应用层协议也需要对新连接进行一些处理。

        // 连接获取之后所处的状态下要进行的各种设置
        // 启动读监控、调用回调函数
        // 该函数执行完毕以后,则连接进入已完成连接状态
        void establishedInLoop()
        {
            // 1.修改连接状态
            // 首先当前状态必须是上层的半连接状态
            assert(_status == CONNECTING);
            _status = CONNECTED;
            // 一旦启动读事件监控,就有可能会立即触发读事件,如果这时候启动了非活跃连接销毁
            // 2.启动读事件监控
            _channel.startReadAbleEvent();
            // 3.调用回调函数
            if(_connectedCallBack)
            {
                _connectedCallBack(shared_from_this());
            }
            
        }

4.发送接口

我们知道当上层协议构建好响应报文之后,就要交给Connection模块进行发送,所以Connection需要一个发送接口,供给外部协议使用。外部只需要传递Buffer缓冲区,该函数就会把形参缓冲区的数据拷贝到自己的发送缓冲区中。注意,这个函数并不是实际的发送数据的函数,因为我们不确定现在是否可以发送数据,如果不可发送而盲目发送,就有可能会导致要发送的数据出现丢失。所以我们必须让epoll模型帮我们监控可写事件,当可写事件触发的时候才代表可以发送数据,然后通过handleWrite函数将数据发送出去。

        // 这个接口并不是实际的发送数据接口,而是把数据放到了发送缓冲区,启动了可写事件监控
        void sendInLoop(Buffer &buffer)
        {
            if(_status == DISCONNECTED)
            {
                return;
            }
            _outBuffer.writeBufferAndPush(buffer);
            if(_channel.isWriteAble() == false)
            {
                _channel.startWriteAbleEvent();
            }
        }

5.关闭连接接口

关闭连接接口是shutdownInLoop,这个函数接口也不是直接关闭连接,因为可能接收缓冲区和发送缓冲区有数据需要处理,所以在关闭之前需要检查并处理这些数据。如果接收缓冲区还有数据要处理,就调用_messageCallBack函数让上层去处理。如果发送缓冲区还有数据要处理,就启动Channel对象的可写事件监控,将发送缓冲区的数据发送出去。

		// 实际的释放接口
        void releaseInLoop()
        {
            // 1.修改连接状态,将其置为DISCONNECTED
            _status = DISCONNECTED;
            // 2.移除连接的事件监控
            _channel.removeEvent();
            // 3.关闭描述符
            _socket.closeSocket();
            // 4.如果当前定时器队列中还有定时销毁任务,则取消任务
            if(_eventLoop->hasTimer(_connectionID))
            {
                cancelInactiveReleaseInLoop();
            }
            // 5.调用关闭回调函数
            // 避免先移除服务器管理的连接信息导致Connection被释放再去处理,可能会出错
            // 所以要先调用用户的关闭事件
            if(_closeCallBack)
            {
                _closeCallBack(shared_from_this());
            }
            // 移除服务器内部管理的连接信息
            if(_serverCloseCallBack)
            {
                _serverCloseCallBack(shared_from_this());
            }
        }

        // 这个关闭操作并非实际的连接释放操作,需要判断还有没有数据待处理,待发送
        void shutdownInLoop()
        {
            // 设置连接为半关闭状态
            _status = DISCONNECTING;
            if(_inBUffer.getReadableSize() > 0)
            {
                if(_messageCallBack)
                {
                    _messageCallBack(shared_from_this(), &_inBUffer);
                }
            }
            // 要么就是写入数据的时候出错关闭,要么就是没有待发送数据,直接关闭
            if(_outBuffer.getReadableSize() > 0)
            {
                if(_channel.isWriteAble() == false)
                {
                    _channel.startWriteAbleEvent();
                }
            }
            if(_outBuffer.getReadableSize() == 0)
            {
                releaseInLoop();
            }
        }

至此Connection模块的大部分功能都已经实现了,接下来的一些函数接口都比较简单,或者是这些函数的封装与复用,这里就不再过多介绍了,可以直接看完整代码:

namespace ns_connection
{
    typedef enum
    {
        DISCONNECTED, // 连接关闭状态
        CONNECTING,   // 连接刚建立的状态
        CONNECTED,    // 连接完成状态,可以进行各种通讯数据操作了
        DISCONNECTING // 连接关闭状态
    } ConnectionStatus;

    class Connection;
    // 这四个回调函数,是让组件使用者来设置的
    using ConnectionSharedPtr = std::shared_ptr<Connection>;
    using ConnectedCallBack = std::function<void(const ConnectionSharedPtr &)>;
    using MessageCallBack = std::function<void(const ConnectionSharedPtr &, Buffer *)>;
    using CloseCallBack = std::function<void(const ConnectionSharedPtr &)>;
    using EventCallBack = std::function<void(const ConnectionSharedPtr &)>;

    class Connection : public std::enable_shared_from_this<Connection>
    {
    public:
        Connection(EventLoop *eventLoop, uint64_t connectionID, int sockfd)
        :_connectionID(connectionID), _sockfd(sockfd), _inactiveReleaseFlag(false), _eventLoop(eventLoop), 
        _status(CONNECTING), _socket(_sockfd), _channel(eventLoop, _sockfd)
        {
            _channel.setCloseCallBack(std::bind(&Connection::handleClose, this));
            _channel.setEventCallBack(std::bind(&Connection::handleEvent, this));
            _channel.setReadAbleCallBack(std::bind(&Connection::handleRead, this));
            _channel.setWriteAbleCallBack(std::bind(&Connection::handleWrite, this));
            _channel.setErrorCallBack(std::bind(&Connection::handleError, this));
        }

        ~Connection()
        {
            LOG("RELEASE CONNECTION:%p", this);
        }

        // 发送数据,将数据放到发送缓冲区,启动写事件监控
        void send(const char *data, size_t len)
        {
            // 注意:外界传入的data,可能是个临时的空间,我们现在只是把发送操作压入了任务池,有可能并没有被立即执行
            // 因此有可能执行的时候,data指向的空间已经被释放了。
            Buffer buffer;
            buffer.writeAndPush(data, len);
            _eventLoop->runInLoop(std::bind(&Connection::sendInLoop, this, std::move(buffer)));
        }

        // 提供给组件使用者的关闭接口--并不实际关闭,需要判断有没有数据待处理
        void shutdown()
        {
            _eventLoop->runInLoop(std::bind(&Connection::shutdownInLoop, this));
        }

        // 启动非活跃销毁,并定义多长时间无通信就是非活跃,添加定时任务
        void startInactiveRelease(int sec)
        {
            _eventLoop->runInLoop(std::bind(&Connection::startInactiveReleaseInLoop, this, sec));
        }

        // 取消非活跃销毁
        void cancelInactiveRelease(int sec)
        {
            _eventLoop->runInLoop(std::bind(&Connection::cancelInactiveReleaseInLoop, this));
        }

        // 切换协议---重置上下文以及阶段性处理函数 -- 这个接口必须在eventLoop线程中立即执行
        // 防备新的事件触发后,处理的时候,切换任务还没有被执行---会导致数据使用原协议处理了
        void upGrade(const Any &context, const ConnectedCallBack &connectedCallBack, const MessageCallBack &messageCallBack,
                     const CloseCallBack &closeCallBack, const EventCallBack &eventCallBack)
        {
            // 所以这里需要断言一下,判断当前线程只能是eventLoop线程
            _eventLoop->assertInLoop();
            _eventLoop->runInLoop(std::bind(&Connection::upGradeInLoop, this, context, connectedCallBack, 
                                messageCallBack, closeCallBack, eventCallBack));
        }

        // 获取管理的文件描述符
        int getSocketfd()
        {
            return _sockfd;
        }

        // 获取连接ID
        int getConnectionID()
        {
            return _connectionID;
        }

        // 返回是否处于连接状态
        bool isConnected()
        {
            return (_status == CONNECTED);
        }

        // 设置上下文
        void setContext(const Any &context)
        {
            _context = context;
        }

        // 获取上下文
        Any *getContext()
        {
            return &_context;
        }

        void setConnectedCallBack(const ConnectedCallBack &callBack)
        {
            _connectedCallBack = callBack;
        }

        void setMessageCallBack(const MessageCallBack &callBack)
        {
            _messageCallBack = callBack;
        }

        void setCloseCallBack(const CloseCallBack &callBack)
        {
            _closeCallBack = callBack;
        }

        void setEventCallBack(const EventCallBack &callBack)
        {
            _eventCallBack = callBack;
        }

        void setServerClosedCallBack(const CloseCallBack &callBack)
        {
            _serverCloseCallBack = callBack;
        }

        // 连接建立就绪后,进行channel回调设置以及启动读监控
        void established()
        {
            _eventLoop->runInLoop(std::bind(&Connection::establishedInLoop, this));
        }

    private:
        // 连接获取之后所处的状态下要进行的各种设置
        // 启动读监控、调用回调函数
        // 该函数执行完毕以后,则连接进入已完成连接状态
        void establishedInLoop()
        {
            // 1.修改连接状态
            // 首先当前状态必须是上层的半连接状态
            assert(_status == CONNECTING);
            _status = CONNECTED;
            // 一旦启动读事件监控,就有可能会立即触发读事件,如果这时候启动了非活跃连接销毁
            // 2.启动读事件监控
            _channel.startReadAbleEvent();
            // 3.调用回调函数
            if(_connectedCallBack)
            {
                _connectedCallBack(shared_from_this());
            }
            
        }

        // 这个接口并不是实际的发送数据接口,而是把数据放到了发送缓冲区,启动了可写事件监控
        void sendInLoop(Buffer &buffer)
        {
            if(_status == DISCONNECTED)
            {
                return;
            }
            _outBuffer.writeBufferAndPush(buffer);
            if(_channel.isWriteAble() == false)
            {
                _channel.startWriteAbleEvent();
            }
        }

        // 实际的释放接口
        void releaseInLoop()
        {
            // 1.修改连接状态,将其置为DISCONNECTED
            _status = DISCONNECTED;
            // 2.移除连接的事件监控
            _channel.removeEvent();
            // 3.关闭描述符
            _socket.closeSocket();
            // 4.如果当前定时器队列中还有定时销毁任务,则取消任务
            if(_eventLoop->hasTimer(_connectionID))
            {
                cancelInactiveReleaseInLoop();
            }
            // 5.调用关闭回调函数
            // 避免先移除服务器管理的连接信息导致Connection被释放再去处理,可能会出错
            // 所以要先调用用户的关闭事件
            if(_closeCallBack)
            {
                _closeCallBack(shared_from_this());
            }
            // 移除服务器内部管理的连接信息
            if(_serverCloseCallBack)
            {
                _serverCloseCallBack(shared_from_this());
            }
        }

        // 这个关闭操作并非实际的连接释放操作,需要判断还有没有数据待处理,待发送
        void shutdownInLoop()
        {
            // 设置连接为半关闭状态
            _status = DISCONNECTING;
            if(_inBUffer.getReadableSize() > 0)
            {
                if(_messageCallBack)
                {
                    _messageCallBack(shared_from_this(), &_inBUffer);
                }
            }
            // 要么就是写入数据的时候出错关闭,要么就是没有待发送数据,直接关闭
            if(_outBuffer.getReadableSize() > 0)
            {
                if(_channel.isWriteAble() == false)
                {
                    _channel.startWriteAbleEvent();
                }
            }
            if(_outBuffer.getReadableSize() == 0)
            {
                releaseInLoop();
            }
        }

        // 启动非活跃连接超时释放规则
        void startInactiveReleaseInLoop(int sec)
        {
            // 1.将判断标志_inactiveReleaseFlag置为true
            _inactiveReleaseFlag = true;
            // 2.如果当前定时销毁任务已经存在,那就刷新延迟一下即可
            if(_eventLoop->hasTimer(_connectionID) == true)
            {
                return _eventLoop->refreshTimer(_connectionID);
            }
            // 3.如果不存在定时销毁任务,则新增
            _eventLoop->addTimer(_connectionID, sec, std::bind(&Connection::releaseInLoop, this));
        }

        void cancelInactiveReleaseInLoop()
        {
            _inactiveReleaseFlag = false;
            if(_eventLoop->hasTimer(_connectionID) == true)
            {
                _eventLoop->cancelTimer(_connectionID);
            }
        }

        void upGradeInLoop(const Any &context,
                           const ConnectedCallBack &connectedCallBack,
                           const MessageCallBack &messageCallBack,
                           const CloseCallBack &closeCallBack,
                           const EventCallBack &eventCallBack)
        {
            _context = context;
            _connectedCallBack = connectedCallBack;
            _messageCallBack = messageCallBack;
            _closeCallBack = closeCallBack;
            _eventCallBack = eventCallBack;
        }

        // 五个channel事件的回调函数

        // 描述符可读事件触发后调用的函数,接收socket数据放到接收缓冲区中,然后调用messageCallBack
        void handleRead()
        {
            // 1.接收socket的数据放到缓冲区
            char buffer[65536];
            ssize_t recvRes = _socket.nonBlockRecv(buffer, 65535);
            if (recvRes < 0)
            {
                // 出错了,但也不能直接关闭连接,需要先看一下有没有数据还没有处理
                // 得要看一下发送缓冲区还有没有数据没有发送
                // 接收缓冲区还有没有数据没有处理
                return shutdownInLoop();
            }
            // 将数据放入接收缓冲区,并且将写偏移向后移动
            _inBUffer.writeAndPush(buffer, recvRes);
            // 2.调用messageCallBack进行业务处理
            if (_inBUffer.getReadableSize() > 0)
            {
                // shared_from_this---从当前对象自身获取自身的shared_ptr
                return _messageCallBack(shared_from_this(), &_inBUffer);
            }
        }

        // 描述符可写事件触发后调用的函数,将发送缓冲区的数据进行发送
        void handleWrite()
        {
            // outBuffer中保存的数据就是要发送的数据
            ssize_t writeRes = _socket.nonBlockSend(_outBuffer.getReadStartPosition(), _outBuffer.getReadableSize());
            if (writeRes < 0)
            {
                // 发送错误就该关闭连接了,但是可能缓冲区中还有数据需要被处理
                if (_inBUffer.getReadableSize() > 0)
                {
                    _messageCallBack(shared_from_this(), &_inBUffer);
                    // 这时候就是实际的关闭释放操作了
                    return releaseInLoop();
                }
            }
            // 还需要将读偏移向后移动
            _outBuffer.moveReadOffset(writeRes);

            // 如果发送缓冲区没有数据可写,就要关闭写事件监控
            if (_outBuffer.getReadableSize() == 0)
            {
                _channel.closeWriteAbleEvent();
                // 如果当前是连接待关闭状态,则有数据就发送完数据释放连接,没有数据就直接释放连接
                if (_status == DISCONNECTING)
                {
                    return releaseInLoop();
                }
            }
        }

        // 描述符触发挂断事件
        void handleClose()
        {
            // 一旦连接挂断了,套接字就什么都干不了了,因此有数据待处理就处理一下,处理完毕以后就关闭连接
            if(_inBUffer.getReadableSize() > 0)
            {
                _messageCallBack(shared_from_this(), &_inBUffer);
            }
            return releaseInLoop();
        }

        // 描述符触发出错事件
        void handleError()
        {
            handleClose();
        }

        // 描述符触发任意事件
        void handleEvent()
        {
            // 1.刷新连接的活跃度---延迟定时销毁任务
            if(_inactiveReleaseFlag == true)
            {
                _eventLoop->refreshTimer(_connectionID);
            }
            // 2.调用组件使用者的任意事件回调
            if(_eventCallBack)
            {
                _eventCallBack(shared_from_this());
            }
        }

    private:
        uint64_t _connectionID;    // 连接的唯一ID,便于连接的管理和查找
        uint64_t _timerID;         // 定时器ID,必须是唯一的,这块为了简化操作,使用_connectionID作为定时器ID
        int _sockfd;               // 连接关联的文件描述符
        bool _inactiveReleaseFlag; // 连接是否启动非活跃销毁的判断标志,默认为false表示关闭
        EventLoop *_eventLoop;     // 连接所关联的一个EventLoop
        ConnectionStatus _status;  // 连接的状态
        Socket _socket;            // 套接字操作管理
        Channel _channel;          // 连接的事件管理
        Buffer _inBUffer;          // 输入缓冲区---存放从socket中读取到的数据
        Buffer _outBuffer;         // 输出缓冲区---存放要发送给对端的数据
        ConnectedCallBack _connectedCallBack;
        MessageCallBack _messageCallBack;
        CloseCallBack _closeCallBack;
        EventCallBack _eventCallBack;

        // 这是组件内的连接关闭回调函数,是组件内设置的,因为服务器组件内会把所有的连接管理起来
        // 一旦某个连接要关闭了,就应该从管理的地方移除掉自己的信息
        CloseCallBack _serverCloseCallBack;
        Any _context; // 请求的接收处理上下文
    };
}

二、TcpServer模块

TcpServer模块就是muduo库Tcp服务器的最后一个模块了,其实有了上面介绍的Connection模块的设计思路,TcpServer模块就变得非常简单了,在我看来这个模块更多的是整合和封装,最后提供接口给上层协议去调用。首先来看一下TcpServer类的成员变量:

private:
    uint64_t _nextID;                                                              // 自动增长的连接ID
    uint16_t _port;
    int _timeout;                                                                  // 非活跃连接的统计时间
    bool _inactiveReleaseFlag;                                                     // 是否启动超时销毁连接的标志
    EventLoop _baseLoop;                                                           // 主线程的EventLoop对象,负责监听事件的处理
    Acceptor _acceptor;                                                            // 监听套接字的管理对象
    LoopThreadPool _threadPool;                                                    // 从属EventLoop的线程池
    std::unordered_map<uint64_t, ns_connection::ConnectionSharedPtr> _connections; // 保存管理所有连接对应的shared_ptr对象

    ns_connection::ConnectedCallBack _connectedCallBack;
    ns_connection::MessageCallBack _messageCallBack;
    ns_connection::CloseCallBack _closeCallBack;
    ns_connection::EventCallBack _eventCallBack;

TcpServer类有一个EventLoop对象是主Reactor,这个Reactor只负责监控和接收连接的到来,其实也就是说TcpServer模块只负责接收新连接,不进行连接的数据接收和转发,连接数据接收是Connection模块完成的事情,TcpServer模块只将新连接分派给从属Reactor去管理。除此之外,TcpServer类还有四类回调函数,这四类回调函数其实是给上层协议去设置的,而且准确地说这不是TcpServer的回调函数,而是Connection的回调函数。

接下来再来看看TcpServer类的构造函数,在构造函数中,我们要通过Acceptor对象设置接收连接的回调函数,绑定的函数是TcpServer类实现的newConnection。当接收到新连接时,就会调用newConnection进行新连接的处理。设置好回调函数以后,就开始accept监控连接。

    TcpServer(uint16_t port)
        : _port(port), _nextID(0), _inactiveReleaseFlag(false), 
        _acceptor(&_baseLoop, port), _threadPool(&_baseLoop)
    {
        _acceptor.setAcceptCallBack(std::bind(&TcpServer::newConnection, this, std::placeholders::_1));
        _acceptor.startListen();
    }

接下来就是TcpServer最核心的函数newConnection了,这个函数会在TcpServer接收到新连接以后调用,函数的功能就是为连接创建Connection对象,并设置Connection的回调函数,然后启动连接的监控。这里创建新Connection对象绑定的EventLoop对象,是从线程池中选取的子线程,因为我们的从属Reactor是与子线程一一绑定的。

    // 为新连接构造一个Connection进行管理
    void newConnection(int fd)
    {
        _nextID++;
        ns_connection::ConnectionSharedPtr conn(new ns_connection::Connection(_threadPool.nextEventLoop(), _nextID, fd));
        conn->setMessageCallBack(_messageCallBack);
        conn->setCloseCallBack(_closeCallBack);
        conn->setConnectedCallBack(_connectedCallBack);
        conn->setEventCallBack(_eventCallBack);
        conn->setServerClosedCallBack(std::bind(&TcpServer::removeConnection, this, std::placeholders::_1));
        if (_inactiveReleaseFlag)
        {
            conn->startInactiveRelease(_timeout);
        }
        conn->established();
        _connections.insert(std::make_pair(_nextID, conn));
    }

至此TcpServer模块的核心部分也介绍完毕了,剩下的接口比较简单,可以直接看完整代码:

class TcpServer
{
public:
    TcpServer(uint16_t port)
        : _port(port), _nextID(0), _inactiveReleaseFlag(false), 
        _acceptor(&_baseLoop, port), _threadPool(&_baseLoop)
    {
        _acceptor.setAcceptCallBack(std::bind(&TcpServer::newConnection, this, std::placeholders::_1));
        _acceptor.startListen();
    }

    void setThreadCount(int count)
    {
        _threadPool.setThreadCount(count);
    }

    void setConnectedCallBack(const ns_connection::ConnectedCallBack &callBack)
    {
        _connectedCallBack = callBack;
    }

    void setMessageCallBack(const ns_connection::MessageCallBack &callBack)
    {
        _messageCallBack = callBack;
    }

    void setCloseCallBack(const ns_connection::CloseCallBack &callBack)
    {
        _closeCallBack = callBack;
    }

    void setEventCallBack(const ns_connection::EventCallBack &callBack)
    {
        _eventCallBack = callBack;
    }

    void startInactiveRelease(int timeout)
    {
        _timeout = timeout;
        _inactiveReleaseFlag = true;
    }

    void start()
    {
        _threadPool.create();
        _baseLoop.start();
    }

    // 用于添加定时任务
    void runAfter(const Functor &task, int delay)
    {
        _baseLoop.runInLoop(std::bind(&TcpServer::runAfterInLoop, this, task, delay));
    }

private:
    // 为新连接构造一个Connection进行管理
    void newConnection(int fd)
    {
        _nextID++;
        ns_connection::ConnectionSharedPtr conn(new ns_connection::Connection(_threadPool.nextEventLoop(), _nextID, fd));
        conn->setMessageCallBack(_messageCallBack);
        conn->setCloseCallBack(_closeCallBack);
        conn->setConnectedCallBack(_connectedCallBack);
        conn->setEventCallBack(_eventCallBack);
        conn->setServerClosedCallBack(std::bind(&TcpServer::removeConnection, this, std::placeholders::_1));
        if (_inactiveReleaseFlag)
        {
            conn->startInactiveRelease(_timeout);
        }
        conn->established();
        _connections.insert(std::make_pair(_nextID, conn));
    }

    // 从管理Connection的_connections中移除连接信息
    void removeConnection(const ns_connection::ConnectionSharedPtr &conn)
    {
        _baseLoop.runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn));
    }

    void removeConnectionInLoop(const ns_connection::ConnectionSharedPtr &conn)
    {
        int id = conn->getConnectionID();
        auto iter = _connections.find(id);
        if (iter != _connections.end())
        {
            _connections.erase(iter);
        }
    }

    void runAfterInLoop(const Functor &task, int delay)
    {
        _nextID++;
        _baseLoop.addTimer(_nextID, delay, task);
    }

private:
    uint64_t _nextID;                                                              // 自动增长的连接ID
    uint16_t _port;
    int _timeout;                                                                  // 非活跃连接的统计时间
    bool _inactiveReleaseFlag;                                                     // 是否启动超时销毁连接的标志
    EventLoop _baseLoop;                                                           // 主线程的EventLoop对象,负责监听事件的处理
    Acceptor _acceptor;                                                            // 监听套接字的管理对象
    LoopThreadPool _threadPool;                                                    // 从属EventLoop的线程池
    std::unordered_map<uint64_t, ns_connection::ConnectionSharedPtr> _connections; // 保存管理所有连接对应的shared_ptr对象

    ns_connection::ConnectedCallBack _connectedCallBack;
    ns_connection::MessageCallBack _messageCallBack;
    ns_connection::CloseCallBack _closeCallBack;
    ns_connection::EventCallBack _eventCallBack;
};

http://www.kler.cn/a/232174.html

相关文章:

  • Redis下载历史版本
  • YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLOv11 推理的 C++ 和 Python 实现
  • ServletConfig、ServletContext、HttpServletRequest与HttpServletResponse常见API
  • web安全漏洞之ssrf入门
  • 【已解决】git push一直提示输入用户名及密码、fatal: Could not read from remote repository的问题
  • C#界面设计
  • 运维高级篇-分库分表(拆分策略详解)
  • 假期作业 7
  • 【嵌入式-传感器】从旋转编码器到学会看懂方波
  • 《动手学深度学习(PyTorch版)》笔记7.6
  • 复制和粘贴文本时剥离格式的5种方法(MacWindows)
  • c# Config 配置文件帮助类
  • 3.2 Verilog 时延
  • 一个基于 .NET 7 + Vue.js 的前后端分离的通用后台管理系统框架 - DncZeus
  • [Java][算法 哈希]Day 01---LeetCode 热题 100---01~03
  • 基于华为云欧拉操作系统(HCE OS)容器化部署传统应用(Redis+Postgresql+Git+SpringBoot+Nginx)
  • 【Network Management】AUTOSAR架构下CanNm User Data详解
  • echarts使用之地图(五)
  • 【几分钟】快速熟悉torch.save()、torch.load()、torch.nn.Module.load_state_dict()
  • ONLYOFFICE文档8.0新功能浅探
  • 软件测试学习笔记-测试用例的编写
  • 项目学习记录
  • MGIE官网体验入口 苹果多模态大语言模型AI图像编辑工具在线使用地址
  • 【Google Bard】免费生成图像——功能和使用方法详解
  • 基于Vue的移动端UI框架整理
  • OpenAI使用的海量数据集介绍