muduo网络库 TcpServer

引言

muduo网络库的文章到这里就结束,本篇文章给muduo网络库收一个尾,这个TcpServer也就是我们对外给用户提供的接口。

TcpServer

TcpServer是对外提供的接口,控制着整个muduo网络库,所以这个类必须要有一个开启的入口,设置subLoop的个数,然后把其他的接口都隐藏在私有类,比如对于连接的新增,移除,还有对连接的存储通过map,并且我们的TcpServer所属的线程就是baseLoop,也就是说如果没有subLoop,那么就只有这个线程运行所有的channel。然后还有一些回调的函数,这个回调的函数并不是交给channel执行的,而是放在eventLoop的queue里面,也就是doPendingFunctors()里面执行。

cpp 复制代码
 class TcpServer : noncopyable{
    public:
        using ThreadInitCallback = std::function<void(EventLoop*)>; // 对应EventThreadLoop

        enum Option {
            KNoReusePort,
            KReusePort,
        };

        TcpServer(EventLoop* loop, const InetAddress& listenAddr, const std::string& nameArg, Option option = KNoReusePort);
        ~TcpServer();

        void setThreadInitCallback(const ThreadInitCallback& cb) { m_threadInitCallback = cb; }
        void setConnectionCallback(const ConnectionCallback& cb) { m_connectionCallback = cb; } 
        void setMessageCallback(const MessageCallback& cb) { m_messageCallback = cb; }
        void setWriteCompleteCallback(const WriteCompleteCallback& cb) { m_writeCompleteCallback = cb; }
        
        // 设置底层subLoop的个数 
        void setThreadNum(int numThreads);

        // 开启服务的监听
        void start();
    private:
        void newConnection(int sockfd, const InetAddress& peerAddr);
        void removeConnection(const TcpConnectionPtr& conn);
        void removeConnectionInLoop(const TcpConnectionPtr& conn);

        using connectionMap = std::unordered_map<std::string, TcpConnectionPtr>;

        EventLoop* m_loop; // baseLoop 用户定义的loop 
        const std::string m_ipPort; // 服务器相关的ip地址和端口号
        const std::string m_name;
        std::unique_ptr<Acceptor> m_acceptor; // 运行在mainLoop  监听新连接的事件
        std::shared_ptr<EventLoopThreadPool> m_threadPool;

        ConnectionCallback m_connectionCallback; // 有新连接时的回调
        MessageCallback m_messageCallback; // 有读写消息时的回调
        WriteCompleteCallback m_writeCompleteCallback; // 消息发送   完成之后的回调
        
        ThreadInitCallback m_threadInitCallback; // loop线程初始化的回调
        std::atomic<int> m_started;

        int m_nextConnId; 
        connectionMap m_connectionMap;
    };
}

对于TcpServer的构造函数,里面要设置baseLoop的绑定,本服务器的地址端口,acceptor的创造,线程池的创建,最后给acceptor设置了一个对于新连接的处理函数,也就是接受加分配给subLoop。我们再来回顾一下,设置了这个函数之后,其实就是给acceptor里面的那个channel的readbackcall回调函数设置了操作,其实目的就是接受到有新的fd就接触阻塞,然后执行回调。

然后对于析构函数来说,我们需要学习一个点,我们要删除指针,但是又要利用这个指针调用其回调函数,所以我们搞了一个展示的指针,正好一出这个作用域这个指针也消除了。

cpp 复制代码
    TcpServer::TcpServer(EventLoop* loop, const InetAddress& listenAddr, const std::string& nameArg, Option option)
            : m_loop(CheckLoopNotNull(loop))
            ,  m_ipPort(listenAddr.toIpPort())
            , m_name(nameArg)
            , m_acceptor(new Acceptor(loop, listenAddr, option == KReusePort))
            , m_threadPool(std::make_shared<EventLoopThreadPool>(loop, m_name))
            , m_connectionCallback()
            , m_messageCallback()
            , m_nextConnId(1)
            , m_started(0)
    {
        m_acceptor->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this, std::placeholders::_1, std::placeholders::_2));
    }

    TcpServer::~TcpServer()
    {
        for (auto& item : m_connectionMap) {
            TcpConnectionPtr conn(item.second); // 建立一个临时的指针
            item.second.reset();

            // 销毁连接
            conn->getLoop()->runInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
        }
    }

这个就是两个对外开放的接口,目的是开启线程池加mainLoop的循环,也就是开启了监听。

当然还有一些设置的接口,我们在之前的代码里面可能已经遇到了很多回调函数,我们用if判断的,这个是因为是由用户设置的,很可能如果用户没有设置,那么就没有那些回调函数。

cpp 复制代码
    void TcpServer::setThreadNum(int numThreads)
    {
        m_threadPool->setThreadNum(numThreads);
    }
    
    // 开启服务器监听 loop.loop()
    void TcpServer::start()
    { 
        if (m_started++ == 0) { // 防止一个TcpServer对象被start多次
            m_threadPool->start(m_threadInitCallback);
            m_loop->runInLoop(std::bind(&Acceptor::listen, m_acceptor.get())); // 开启监听
        }
    }

对于一个新连接的到来,我们需要用轮询算法分配一个subLoop,然后通过getsockname来获得服务器的地址,之所以我们可以获得,因为我们传入的是acceptor的sockfd(也就是新创建的fd),其实就是本机的,然后客户端的地址已经被传入进去了,所以这个样子就可以形成一个新连接,有了这个连接之后先存储这个连接,然后对这个连接设置多种回调函数,注意:这个messagecallback是用户来设置的,我们一般把这个函数叫做onMessage。最后我们绑定了关闭连接的回调函数,这个我们马上来说。最后把建立这个连接的函数绑定并放入ioLoop(也就是对应的loop)里面,这个建立连接的函数就是要把channel绑定给对应的loop,并开启可读的事件。

cpp 复制代码
    void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
    {
        EventLoop* ioLoop = m_threadPool->getNextLoop();
        char buf[64] = {0};
        snprintf(buf, sizeof(buf), "-%s#%d", m_ipPort.c_str(), m_nextConnId);
        m_nextConnId++;
        std::string connName = m_name + buf;
        LOG_INFO("TcpServer::Connection [%s] - new connection [%s] from %s \n", m_name.c_str(), 
        connName.c_str(), peerAddr.toIpPort().c_str());
        
        // 通过sockfd获取其绑定的本机的ip地址和端口号信息
        sockaddr_in local;
        ::bzero(&local, sizeof(local));
        socklen_t addrlen = sizeof(local);
        if (::getsockname(sockfd, (sockaddr*)(&local), &addrlen) < 0) {
            LOG_ERROR("sockets::getLocalAddr");
        }
        InetAddress localAddr(local);

        // 根据连接成功的sockfd,创建TcpConnection连接对象
        TcpConnectionPtr conn(new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr));
        m_connectionMap[connName] = conn;
        // 下面的回调都是用户设置给TcpServer =》TcpConnection =》Channel =》Poller =》notify =》channel回调
        conn->setConnectionCallback(m_connectionCallback);
        conn->setMessageCallback(m_messageCallback);
        conn->setWriteCompleteCallback(m_writeCompleteCallback);

        // 设置了如何关闭连接的回调  conn->shutdown
        conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, std::placeholders::_1));

        // 直接调用TcpConnection::connectEstablished
        ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
    }

这个就是销毁连接的函数

cpp 复制代码
    void TcpServer::removeConnection(const TcpConnectionPtr& conn)
    {
        m_loop->runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn));
    }
    
    void TcpServer::removeConnectionInLoop(const TcpConnectionPtr& conn)
    {
        LOG_INFO("TcpServer::removeConnnectionInLoop [%s] - connection %s \n", m_name.c_str(), conn->name().c_str());

        m_connectionMap.erase(conn->name());
        EventLoop* ioloop = conn->getLoop();
        ioloop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
    }

总结

本篇文章到这里就结束了!!!muduo网络库也到这里结束了~~~我会在不久的将来重新写一遍网络库的代码,到时候会有一篇文章是关于重写网络库的心得和注意事项~~~希望可以帮助大家理解~~~

相关推荐
躺不平的理查德1 小时前
ARCH与交叉编译器的关系
服务器·arm
王维同学2 小时前
进程模块枚举、映像身份与线程启动地址关联
c++·windows·安全
hold?fish:palm2 小时前
24 回文链表
数据结构·c++·链表
举手2 小时前
Dispatcher模块剖析
linux·c++
谢慧琼3 小时前
2026零基础做企业网站,低成本搭建官方网站
服务器·前端·javascript
依然鸣3 小时前
PTA团体程序设计天梯赛L1真题讲解L1-077-080
开发语言·c++·算法·深度优先·pat考试·图论
库玛西5 小时前
现代 C++ 智能指针全景指南:从 RAII 思想到工业级实践
c语言·开发语言·c++·笔记·面试
liulilittle6 小时前
无锁并发容器的设计与实现原理
开发语言·c++·set·map·并发·无锁·lock-free
qeen876 小时前
【数据结构】自平衡二叉搜索树各种旋转算法原理解析及AVL树的C++实现
数据结构·c++·算法
世人万千丶6 小时前
去重插入与超限淘汰:ArkTS 实现鸿蒙搜索历史的 LIMIT 艺术
运维·服务器·学习·华为·harmonyos·鸿蒙