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

相关推荐
测试猿实操笔记4 小时前
DNS 通俗讲解:域名 ↔ IP 翻译
网络·网络协议·tcp/ip
青禾8374 小时前
Linux 操作系统入门指南:从目录结构到常用命令
linux·运维·服务器
zhengqweasd5 小时前
流量没跑满、带宽空闲,业务依然卡顿
运维·服务器·网络
liyuanchao_blog5 小时前
OVN/OVS场景下虚拟机新网卡通过 DHCP 获取 IP 地址的完整过程
网络·网络协议·tcp/ip·云计算
今天AI了吗6 小时前
DeepSeek Harness 深度解析:从评测架构到实战落地
java·网络·数据库·人工智能·架构·java-ee
Tairitsu_H6 小时前
[C++] 深入理解红黑树:封装set与map
开发语言·c++·set·map·红黑树·模拟实现
M哥支付6 小时前
高频交易优选方案:银联快捷支付通道
服务器·网络·其他·微信·金融
爱倒腾的老唐6 小时前
11、误码率测试仪
网络
逍遥~1426 小时前
什么是工业级算力?算盘科技的核心能力解析
网络·人工智能·科技
HugoStudio_SWAN6 小时前
洛谷 B4450 / B3867 / B3923 智慧购物、储蓄与做题——从程序到生活
c++·学习·程序人生·算法·生活