手写muduo网络库(十):TcpServer

引言

TcpServer 类在 muduo 网络库中扮演着对外提供服务的重要角色,它封装了 TCP 服务器的基本功能,包括监听连接、处理新连接、管理连接生命周期等。本文将详细剖析 TcpServer 类的代码实现,深入探讨其功能和实现逻辑,并结合 C++ 语言特性进行讲解。

代码文件概述

TcpServer.h 头文件分析

#pragma once

#include "EventLoop.h"
#include "Acceptor.h"
#include "InetAddress.h"
#include "NonCopyable.h"
#include "EventLoopThreadPool.h"
#include "Callbacks.h"
#include "TcpConnection.h"
#include "Buffer.h"

#include 
#include 
#include 
#include 
#include 

// 对外的服务器编程使用的类
class TcpServer:NonCopyable
{
public:
    using ThreadInitCallback = std::function;

    enum Option
    {
        kNoReusePort,
        kReusePort,
    };

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

    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; }

    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;

    EventLoop *loop_;

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

    std::unique_ptr acceptor_;

    std::shared_ptr threadPool_;

    ConnectionCallback connectionCallback_;
    MessageCallback messageCallback_;
    WriteCompleteCallback writeCompleteCallback_;

    ThreadInitCallback threadInitCallback_;
    int numThreads_;
    std::atomic_int started_;
    int nextConnId_;
    ConnectionMap connections_;
};
1. 头文件包含

头文件包含了一系列必要的头文件,这些头文件定义了 TcpServer 类所依赖的其他类和工具,如 EventLoopAcceptorInetAddress 等。

2. 类型别名和枚举
  • using ThreadInitCallback = std::function;:定义了一个类型别名 ThreadInitCallback,它是一个函数对象,接受一个 EventLoop* 类型的参数,用于线程初始化回调。
  • enum Option:定义了一个枚举类型 Option,包含两个值 kNoReusePort 和 kReusePort,用于指定是否重用端口。
3. 构造函数和析构函数
  • TcpServer(EventLoop *loop, const InetAddress &listenAddr, const std::string &nameArg, Option option = kNoReusePort);:构造函数,接受一个 EventLoop 指针、一个 InetAddress 对象、一个服务器名称和一个端口重用选项。
  • ~TcpServer();:析构函数,负责清理资源。
4. 回调函数设置

提供了一系列的回调函数设置方法,如 setThreadInitCallbacksetConnectionCallbacksetMessageCallback 和 setWriteCompleteCallback,用于设置线程初始化回调、连接建立回调、消息接收回调和写完成回调。

5. 成员函数
  • setThreadNum(int numThreads);:设置线程池中的线程数量。
  • start();:启动服务器。
6. 私有成员函数
  • newConnection(int sockfd, const InetAddress &peerAddr);:处理新连接。
  • removeConnection(const TcpConnectionPtr &conn);:移除连接。
  • removeConnectionInLoop(const TcpConnectionPtr &conn);:在事件循环中移除连接。
7. 私有成员变量
  • EventLoop *loop_;:主事件循环指针。
  • const std::string ipPort_;:服务器监听的 IP 地址和端口。
  • const std::string name_;:服务器名称。
  • std::unique_ptr acceptor_;:用于接受新连接的 Acceptor 对象。
  • std::shared_ptr threadPool_;:事件循环线程池。
  • ConnectionCallback connectionCallback_;MessageCallback messageCallback_;WriteCompleteCallback writeCompleteCallback_;ThreadInitCallback threadInitCallback_;:各种回调函数。
  • int numThreads_;:线程池中的线程数量。
  • std::atomic_int started_;:原子变量,用于标记服务器是否已经启动。
  • int nextConnId_;:下一个连接的 ID。
  • ConnectionMap connections_;:存储所有连接的映射表。

TcpServer.cpp 实现文件分析

#include "TcpServer.h"
#include "LogStream.h"
#include "TcpConnection.h"

#include 
#include 

static EventLoop *CheckLoopNotNull(EventLoop *loop)
{
    if (loop == nullptr)
    {
        LOG_ERROR << "mainLoop is null!!!";
        exit(-1);
    }
    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))
    , threadPool_(new EventLoopThreadPool(loop, name_))
    , connectionCallback_()
    , messageCallback_()
    , nextConnId_(1)
    , started_(0)
{
    acceptor_->setNewConnectionCallback(
        std::bind(&TcpServer::newConnection, this, std::placeholders::_1, std::placeholders::_2));
}

TcpServer::~TcpServer()
{
    for(auto &item : connections_)
    {
        TcpConnectionPtr conn(item.second);
        item.second.reset();
        conn->getLoop()->runInLoop(
            std::bind(&TcpConnection::connectDestroyed, conn));
    }
}

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

void TcpServer::start()
{
    if (started_.fetch_add(1) == 0)
    {
        threadPool_->start(threadInitCallback_);
        loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get()));
    }
}

void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{
    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_.c_str() << "] - new connection [" << connName.c_str() << "] from" << peerAddr.toIpPort().c_str();
    sockaddr_in local;
    ::memset(&local, 0, sizeof(local));
    socklen_t addrlen = sizeof(local);
    if(::getsockname(sockfd, (sockaddr *)&local, &addrlen) < 0)
    {
        LOG_ERROR << "sockets::getLocalAddr";
    }

    InetAddress localAddr(local);
    TcpConnectionPtr conn(new TcpConnection(ioLoop,
                                            connName,
                                            sockfd,
                                            localAddr,
                                            peerAddr));
    connections_[connName] = conn;

    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);

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

    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_.c_str() << "] - connection " << conn->name().c_str();
    connections_.erase(conn->name());
    EventLoop *ioLoop = conn->getLoop();
    ioLoop->queueInLoop(
        std::bind(&TcpConnection::connectDestroyed, conn));
}
1. 辅助函数 CheckLoopNotNull
static EventLoop *CheckLoopNotNull(EventLoop *loop)
{
    if (loop == nullptr)
    {
        LOG_ERROR << "mainLoop is null!!!";
        exit(-1);
    }
    return loop;
}

该函数用于检查传入的 EventLoop 指针是否为空,如果为空则输出错误日志并退出程序,确保主事件循环指针不为空。

2. 构造函数 TcpServer::TcpServer
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))
    , threadPool_(new EventLoopThreadPool(loop, name_))
    , connectionCallback_()
    , messageCallback_()
    , nextConnId_(1)
    , started_(0)
{
    acceptor_->setNewConnectionCallback(
        std::bind(&TcpServer::newConnection, this, std::placeholders::_1, std::placeholders::_2));
}
  • 初始化成员变量,包括主事件循环指针、服务器监听的 IP 地址和端口、服务器名称等。
  • 创建 Acceptor 对象和 EventLoopThreadPool 对象。
  • 使用 std::bind 绑定 TcpServer::newConnection 函数到 Acceptor 的新连接回调函数上,当有新连接到来时,Acceptor 会调用 TcpServer::newConnection 函数进行处理。
3. 析构函数 TcpServer::~TcpServer
TcpServer::~TcpServer()
{
    for(auto &item : connections_)
    {
        TcpConnectionPtr conn(item.second);
        item.second.reset();
        conn->getLoop()->runInLoop(
            std::bind(&TcpConnection::connectDestroyed, conn));
    }
}

析构函数遍历所有的连接,将连接从 connections_ 映射表中移除,并在相应的事件循环中调用 TcpConnection::connectDestroyed 函数销毁连接。

4. setThreadNum 函数
void TcpServer::setThreadNum(int numThreads)
{
    int numThreads_=numThreads;
    threadPool_->setThreadNum(numThreads_);
}

该函数用于设置线程池中的线程数量,调用 EventLoopThreadPool 的 setThreadNum 函数进行设置。

5. start 函数
void TcpServer::start()
{
    if (started_.fetch_add(1) == 0)
    {
        threadPool_->start(threadInitCallback_);
        loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get()));
    }
}
  • 使用 std::atomic_int 类型的 started_ 变量确保服务器只启动一次。
  • 启动事件循环线程池,并调用 Acceptor 的 listen 函数开始监听连接。
6. newConnection 函数
void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{
    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_.c_str() << "] - new connection [" << connName.c_str() << "] from" << peerAddr.toIpPort().c_str();
    sockaddr_in local;
    ::memset(&local, 0, sizeof(local));
    socklen_t addrlen = sizeof(local);
    if(::getsockname(sockfd, (sockaddr *)&local, &addrlen) < 0)
    {
        LOG_ERROR << "sockets::getLocalAddr";
    }

    InetAddress localAddr(local);
    TcpConnectionPtr conn(new TcpConnection(ioLoop,
                                            connName,
                                            sockfd,
                                            localAddr,
                                            peerAddr));
    connections_[connName] = conn;

    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);

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

    ioLoop->runInLoop(
        std::bind(&TcpConnection::connectEstablished, conn));
}
  • 从线程池中获取一个事件循环用于处理新连接。
  • 生成连接名称,记录新连接的日志信息。
  • 获取本地地址信息。
  • 创建 TcpConnection 对象,并将其添加到 connections_ 映射表中。
  • 设置 TcpConnection 的各种回调函数,包括连接建立回调、消息接收回调、写完成回调和关闭回调。
  • 在事件循环中调用 TcpConnection::connectEstablished 函数,标志连接建立完成。
7. removeConnection 函数
void TcpServer::removeConnection(const TcpConnectionPtr &conn)
{
    loop_->runInLoop(
        std::bind(&TcpServer::removeConnectionInLoop, this, conn));
}

该函数将 removeConnectionInLoop 函数封装到主事件循环中执行,确保线程安全。

8. removeConnectionInLoop 函数
void TcpServer::removeConnectionInLoop(const TcpConnectionPtr &conn)
{
    LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_.c_str() << "] - connection " << conn->name().c_str();
    connections_.erase(conn->name());
    EventLoop *ioLoop = conn->getLoop();
    ioLoop->queueInLoop(
        std::bind(&TcpConnection::connectDestroyed, conn));
}
  • 从 connections_ 映射表中移除指定的连接。
  • 在相应的事件循环中调用 TcpConnection::connectDestroyed 函数销毁连接。

设计思路

1. 事件驱动模型

TcpServer 采用事件驱动模型,通过 EventLoop 和 Acceptor 监听新连接事件,当有新连接到来时,触发相应的回调函数进行处理。这种模型使得服务器能够高效地处理大量并发连接。

2. 线程池设计

使用 EventLoopThreadPool 管理多个事件循环线程,将新连接分配到不同的线程中处理,提高服务器的并发处理能力。通过设置线程池的线程数量,可以根据实际需求调整服务器的性能。

3. 回调函数机制

TcpServer 提供了一系列的回调函数,如连接建立回调、消息接收回调、写完成回调等,用户可以通过设置这些回调函数来实现自己的业务逻辑,提高了代码的灵活性和可扩展性。

C++ 语言特性细节

1. std::function 和 std::bind

std::function 是一个通用的多态函数包装器,它可以存储、复制和调用任何可调用对象。std::bind 用于将一个可调用对象和其参数进行绑定,生成一个新的可调用对象。在 TcpServer 中,使用 std::function 定义回调函数类型,使用 std::bind 绑定回调函数,使得代码更加灵活和易于管理。

2. 智能指针

使用 std::unique_ptr 和 std::shared_ptr 管理动态分配的对象,避免了手动内存管理带来的内存泄漏问题。std::unique_ptr 用于管理 Acceptor 对象,确保其所有权的唯一性;std::shared_ptr 用于管理 EventLoopThreadPool 对象,方便在多个地方共享该对象。

3. 原子操作

使用 std::atomic_int 类型的 started_ 变量确保服务器只启动一次,避免了多线程环境下的竞态条件。std::atomic_int 提供了原子操作,保证了变量的读写操作是线程安全的。

总结

TcpServer 类是 muduo 网络库中一个重要的组件,它封装了 TCP 服务器的基本功能,通过事件驱动模型、线程池设计和回调函数机制,实现了高效、灵活和可扩展的服务器编程。同时,代码中充分运用了 C++ 语言的特性,如 std::functionstd::bind、智能指针和原子操作等,提高了代码的安全性和可维护性。通过深入学习 TcpServer 类的实现,我们可以更好地理解网络编程的原理和 C++ 语言的高级特性。

你可能感兴趣的:(linux网络编程与服务器开发,网络,开发语言,c++,linux,服务器)