【Muduo】TcpServer类

TcpServer统领之前所有的类,是用户直接使用的类。它通过ThreadPool管理所有的loopthread,保存所有的TcpConnection,保存用户提供的各种回调函数并向TcpConnection的Channel中注册回调。它负责监听指定的端口,并接受来自客户端的连接请求,为每个连接创建一个TcpConnection对象进行管理。它的业务逻辑较先前的三大组件来说,还是比较简明的。

主要成员变量

  • EventLoop* loop_:指向事件循环的指针,这是由用户创建并传入的,是mainLoop。事件循环是Muduo网络库的核心组件之一,负责监听文件描述符上的事件(如可读、可写、错误等),并调度相应的事件处理函数。
  • InetAddress listenAddr_:表示服务器监听的地址和端口。
  • Acceptor acceptor_Acceptor对象用于监听指定的端口,并接受来自客户端的连接请求。当有新的连接请求到达时,Acceptor会调用相应的回调函数进行处理。
  • 回调函数TcpServer类允许用户注册多个回调函数,以便在特定事件发生时执行自定义逻辑。这些回调函数包括新连接到来时的回调、连接关闭时的回调等。

主要功能

  1. 监听端口TcpServer类通过Acceptor对象监听指定的端口,等待客户端的连接请求。当有新的连接请求到达时,Acceptor会触发相应的事件,通知TcpServer进行处理。
  2. 接受连接:当Acceptor接收到客户端的连接请求时上报,TcpServer会创建一个新的TcpConnection对象来表示这个连接,并分配subLoop将其与客户端进行关联。同时,TcpServer会将新创建的TcpConnection对象添加到内部的管理列表中,以便后续进行管理和操作。
  3. 管理连接TcpServer类负责管理与其关联的所有TcpConnection对象。这包括连接的建立、保持和关闭等操作。当客户端断开连接时,TcpServer会从管理列表中移除相应的TcpConnection对象,并释放相关资源。
  4. 事件处理:当与TcpServer关联的事件发生时(如新连接到来、连接关闭等),TcpServer会调用相应的回调函数进行处理。这些回调函数可以是Muduo网络库预定义的,也可以是用户自定义的。通过回调函数,用户可以编写自己的业务逻辑来处理各种事件。
  5. 线程安全:由于Muduo网络库是多线程的,因此TcpServer类需要是线程安全的。这意味着在多线程环境下,多个线程可以同时访问同一个TcpServer对象,而不会导致数据竞争或其他并发问题。Muduo网络库通过EventLoopThreadPool来管理所有EventLoopThread,使用互斥锁和信号量等同步机制来保证TcpServer类的线程安全性。

使用方式

用户通常只需要创建一个TcpServer对象,并设置相应的监听地址和端口,然后调用其start()方法来启动服务器。在服务器运行过程中,用户可以通过注册回调函数来处理各种事件。当有新的连接请求到达时,Muduo网络库会自动为每个连接创建一个TcpConnection对象,并将其与TcpServer进行关联。用户可以通过访问与TcpServer关联的TcpConnection对象来与客户端进行通信和交互。当客户端断开连接时,Muduo网络库会自动从管理列表中移除相应的TcpConnection对象,并释放相关资源。

源码

TcpServer.h

#pragma once

#include "noncopyable.h"
#include "LogStream.h"
#include "EventLoop.h"
#include "EventLoopThreadPool.h"
#include "Acceptor.h"
#include "Callbacks.h"
#include "InetAddress.h"
#include "Buffer.h"
#include "Timestamp.h"
#include "TcpConnection.h"

#include <functional>
#include <string>
#include <memory>
#include <unordered_map>
#include <atomic>

// 对外的服务器编程使用的类
class TcpServer : noncopyable
{
public:
    using ThreadInitCallback = std::function<void(EventLoop *)>;
    enum Option
    {
        kNoReusePort,
        kReusePort,
    };

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

    const std::string &ipPort() const { return ipPort_; }
    const std::string &name() const { return name_; }
    EventLoop *getLoop() const { return loop_; }

    void setThreadNum(int numThreads);

    //
    std::shared_ptr<EventLoopThreadPool> threadPool()
    {
        return threadPool_;
    }

    /// Starts the server if it's not listening.
    void start();

    void setThreadInitCallback(const ThreadInitCallback &cb)
    {
        threadInitCallback_ = cb;
    }

    void setConnectionCallback(const ConnectionCallback &cb)
    {
        connectionCallback_ = cb;
    }

    void setMessageCallback(const MessageCallback &cb)
    {
        messageCallback_ = cb;
    }

    void setWriteCompleteCallback(const WriteCompleteCallback &cb)
    {
        writeCompleteCallback_ = cb;
    }

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 *loop_; // the acceptor loop, 用户定义的loop

    const std::string ipPort_;
    const std::string name_;

    std::unique_ptr<Acceptor> acceptor_;              // 运行在mainLoop,监听新连接事件
    std::shared_ptr<EventLoopThreadPool> threadPool_; // one loop pre thread

    ConnectionCallback connectionCallback_;       // 有新链接时的回调
    MessageCallback messageCallback_;             // 有读写消息时的回调
    WriteCompleteCallback writeCompleteCallback_; // 消息发送完成以后的回调
    ThreadInitCallback threadInitCallback_;       // loop线程初始化的回调

    std::atomic_int started_;
    int nextConnId_;

    ConnectionMap connections_; // 保存所有的连接
};

TcpServer.cc

#include "TcpServer.h"

#include <sys/socket.h>
#include <string.h>

static EventLoop *checkLoopNotNull(EventLoop *loop)
{
    if (loop == nullptr)
    {
        LOG_FATAL << "mainLoop is null!";
    }
    return loop;
}

TcpServer::TcpServer(EventLoop *loop, const InetAddress &listenAddr, const std::string &nameArg, Option option)
    : loop_(checkLoopNotNull(loop)),
      ipPort_(listenAddr.toIpPort()),
      name_(nameArg),
      acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)), // create sokect, listen
      threadPool_(new EventLoopThreadPool(loop, name_)),
      connectionCallback_(),
      messageCallback_(),
      nextConnId_(1),
      started_(0)
{
    // 当有新用户链接时,将执行TcpServer::newConnection回调:
    // 根据轮训算法选择一个subLoop并唤醒,把当前connfd封装成channel分发给subloop
    acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this,
                                                  std::placeholders::_1, std::placeholders::_2));
}

TcpServer::~TcpServer()
{
    LOG_INFO << "TcpServer::~TcpServer [" << name_ << "] destructing";
    for (auto &item : connections_)
    {
        // 强智能指针不会再指向原来的对象,放手了,出了作用域,可以自动释放资源
        TcpConnectionPtr conn(item.second);
        item.second.reset(); 
        // 销毁连接
        conn->getLoop()->runInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
    }
}

void TcpServer::setThreadNum(int numThreads)
{
    threadPool_->setThreadNum(numThreads);
}

void TcpServer::start()
{
    LOG_DEBUG << "TcpServer::start() started_ = " << started_ << "loop_=" << loop_;
    if (started_++ == 0)// 防止一个sever对象被start多次
    { 
        threadPool_->start(threadInitCallback_); // 启动底层的线程池

        loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get())); // 注册AcceptorChannel到baseLoop
        LOG_DEBUG << "TcpServer::start success!";
    } 
}

// 有新客户端连接,运行在主线程mainLoop的acceptor会执行这个回调操作
void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{
    // 轮询选择一个subloop(子线程),正在epoll上阻塞,需要queueInLoop
    EventLoop *ioLoop = threadPool_->getNextLoop();

    char buf[64] = {0};
    snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
    ++nextConnId_;

    std::string connName = name_ + buf;
    LOG_INFO << "TcpServer::newConnection [" << name_
             << "] - new connection [" << connName
             << "] from " << peerAddr.toIpPort();

    // 通过sockfd获取其绑定的本机的ip地址和端口信息
    sockaddr_in localaddr;
    memset(&localaddr, 0, sizeof localaddr);
    // ::bzero(&localaddr, sizeof localaddr);
    socklen_t addrlen = sizeof localaddr;
    if (::getsockname(sockfd, (sockaddr *)&localaddr, &addrlen) < 0)
    {
        LOG_ERROR << "sockets::getLocalAddr error, errno=" << errno;
    }
    InetAddress localAddr(localaddr);

    // 根据连接成功的sockfd,创建TcpConnection
    TcpConnectionPtr conn(new TcpConnection(
        ioLoop,
        connName,
        sockfd,
        localAddr,
        peerAddr)
    );
    connections_[connName] = conn;
    // 下面的回调都是用户来设置给TcpServer=》TcpConnection=》Channel注册=》Poller=》notify Channel回调
    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);

    conn->setCloseCallback(
        std::bind(&TcpServer::removeConnection, this, std::placeholders::_1));

    // 直接调用TcpConnection::connectEstablished
    // 本函数运行在baseLoop,而ioloop在子线程阻塞在epoll_wait,
    // 此时在baseLoop中进行函数调用runInLoop、queueInLoop进而保存到ioloop的成员变量pendingFunctors_,
    // 进而一直在baseLoop中调用到ioloop的weakup函数,向ioloop的wakeupfd中写一个数据
    // 在子线程中的epoll_wait监听到写事件,就可以返回,才能够执行这里的回调函数
    ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

void TcpServer::removeConnection(const TcpConnectionPtr &conn)
{
    loop_->runInLoop(
        std::bind(&TcpServer::removeConnectionInLoop, this, conn));
}

void TcpServer::removeConnectionInLoop(const TcpConnectionPtr &conn)
{
    LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_
             << "] - connection " << conn->name();
    connections_.erase(conn->name());
    EventLoop *ioLoop = conn->getLoop(); // 获取conn所在的loop 
    ioLoop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值