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

Muduo网络库解析--网络模块(2)

前文

重写Muduo库实现核心模块的Git仓库

注:本文将重点剖析 Muduo 网络库的核心框架,深入探讨作者精妙的代码设计思路,并针对核心代码部分进行重写,将原本依赖 boost 的实现替换为原生的 C++11 语法。需要说明的是,本文并不打算对整个 Muduo 库进行完整的重写。Muduo库源码链接

在上文中,我们对Muduo网络库的核心网络模块中的SocketInetAddress以及Acceptor进行了解析。这节我们将对剩余的核心网络模块中的TcpConnection以及TcpServer进行解析。

TcpConnection

Muduo 网络库 中,TcpConnection 是一个非常重要的类,主要用于表示并管理一个 TCP 连接。它抽象了应用层和网络层之间的交互,负责处理一个具体的 TCP 连接的生命周期以及数据的发送和接收。

TcpConnection 的主要作用

  1. 抽象 TCP 连接

    • TcpConnection 表示一个具体的 TCP 连接,隐藏了底层的 socket 描述符和 epoll 等细节,使得用户只需要关注逻辑层面。
    • 每一个客户端连接都会对应一个 TcpConnection 实例。
  2. 管理 TCP 连接的生命周期

    • 包括连接的建立、数据的收发、连接的关闭等。
    • 在连接的不同阶段会触发对应的回调(callback),如连接建立回调、消息回调和关闭回调。
  3. 提供高效的异步 I/O

    • 通过事件驱动模型,结合 ChannelEventLoop,实现异步非阻塞的 I/O 操作。
  4. 数据缓冲

    • 提供输入缓冲区和输出缓冲区(Buffer),用于存储接收和发送的数据。
  5. 支持用户自定义回调

    • 用户可以设置各种回调函数,比如连接建立的回调(ConnectionCallback)、消息到来的回调(MessageCallback)、写完成回调等。
  6. 线程安全

    • TcpConnection 的大部分操作是线程安全的,支持跨线程调用,比如关闭连接时可以跨线程调用 shutdown

TcpConnection 的核心功能

  1. 连接管理
    • 提供方法来开启和关闭连接(connectEstablished()connectDestroyed())。
    • 判断连接状态(isConnected() 等)。
  2. 数据传输
    • 接收数据:通过 MessageCallback 回调函数处理接收到的数据。
    • 发送数据:提供 send() 方法,用于发送字符串或二进制数据。发送过程是非阻塞的,数据会先存入输出缓冲区。
  3. 回调设置
    • 支持用户设置各种回调函数,如:
      • ConnectionCallback:连接状态变化时的回调。
      • MessageCallback:收到数据时的回调。
      • WriteCompleteCallback:数据发送完毕时的回调。
      • CloseCallback:连接关闭时的回调。
  4. 与事件循环集成
    • 每个 TcpConnection 实例绑定一个 EventLoop,并通过 Channel 监听和处理其对应 socket 的事件(如可读、可写等)。

类图如下:

image-20241214123752689

类的关键成员变量和方法

  1. 主要成员变量
    • EventLoop* loop_:所属的事件循环
    • StateE state_:表示连接的状态(如连接中、已连接、正在关闭、未连接)
    • unqiue_ptr<Socket> socket_:表示该TCP连接的socket
    • unique_ptr<Channel> channel_:表示该套接字描述的channel
    • InetAddress localAddr_:本地的IP和端口
    • InetAddress peerAddr_:对端的IP和端口
    • 输入缓冲区和输出缓冲区
    • 一系列回调函数:connectionCallback_messageCallback_
  2. 主要方法
    • void send(const std::string& message):发送数据。
    • void shutdown():关闭连接的写端。
    • void connectEstablished():在连接建立后被调用,初始化连接。
    • void connectDestroyed():在连接关闭后被调用,清理资源。
    • 回调设置方法:setConnectionCallback()setMessageCallback() 等。

TcpConnection.h

class TcpConnection : noncopyable, public std::enable_shared_from_this<TcpConnection>
{
public:TcpConnection(EventLoop* loop, const std::string& name,int sockfd,const InetAddress& localAddr,const InetAddress& peerAddr);~TcpConnection();EventLoop* getLoop() const { return loop_; }const std::string& name() const { return name_; }const InetAddress& localAddress() const { return localAddr_; }const InetAddress& peerAddress() const { return peerAddr_; }bool conncted() const { return state_ == StateE::kConnected; }// 发送数据void send(const std::string& buf);// 关闭连接void shutdown();void setConnectionCallback(const ConnectionCallback& cb){ connectionCallback_ = cb; }void setMessageCallback(const MessageCallback& cb){ messageCallback_ = cb; }void setWriteCompleteCallback(const WriteCompleteCallback& cb){ writeCompleteCallback_ = cb; }void setHighWaterMarkCallback(const HighWaterMarkCallback& cb, size_t highWaterMark){ highWaterMarkCallback_ = cb; highWaterMark_ = highWaterMark; }void setCloseCallback(const CloseCallback& cb){ closeCallback_ = cb; }// 建立连接void connectEstablished();// 销毁连接 void connectDestroyed();private:enum StateE{kDisconnected,	// 已关闭连接kConnecting,	// 正在连接kConnected,		// 已连接kDisconnecting	// 正在关闭连接};void handleRead(Timestamp receiveTime);void handleWrite();void handleClose();void handleError();void sendInLoop(const char* message, size_t len);void shutdownInLoop();void setState(StateE state) { state_ = state; }EventLoop* loop_;const std::string name_;std::atomic_int state_;bool reading_;std::unique_ptr<Socket> socket_;std::unique_ptr<Channel> channel_;const InetAddress localAddr_;const InetAddress peerAddr_; ConnectionCallback connectionCallback_; // 有新连接的回调MessageCallback messageCallback_;       // 有读写消息的回调WriteCompleteCallback writeCompleteCallback_;    // 消息发送完成以后的回调CloseCallback closeCallback_;HighWaterMarkCallback highWaterMarkCallback_;   // 高水位回调// 高水位的值size_t highWaterMark_;Buffer inputBuffer_;Buffer outputBuffer_;
};

TcpConnection.cc

构造函数

TcpConnection 表示并管理一个 TCP 连接,在事件就绪时,会自动调用用户注册的回调函数。与此相对应,channel_ 中负责注册和管理这些用户定义的回调函数。

static EventLoop* CheckLoopNotNull(EventLoop* loop)
{if(loop == nullptr){LOG_FATAL("%s:%s:%d TcpConnection is null!", __FILE__, __FUNCTION__, __LINE__);}return loop;
}TcpConnection::TcpConnection(EventLoop *loop, std::string const &name,int sockfd, InetAddress const &localAddr,InetAddress const &peerAddr) : loop_(CheckLoopNotNull(loop)),name_(name),state_(StateE::kConnecting),reading_(true),socket_(new Socket(sockfd)),channel_(new Channel(loop_, sockfd)),localAddr_(localAddr),peerAddr_(peerAddr),highWaterMark_(64*1024*1024)
{channel_->setReadCallback(std::bind(&TcpConnection::handleRead, this, std::placeholders::_1));channel_->setWriteCallback(std::bind(&TcpConnection::handleWrite, this));channel_->setCloseCallback(std::bind(&TcpConnection::handleClose, this));channel_->setErrorCallback(std::bind(&TcpConnection::handleError, this));LOG_INFO("TcpConnection::ctor[%s] at fd=%d", name_.c_str(), sockfd);socket_->setKeepAlive(true);
}
新连接

在接受到新的客户端连接后,构造了一个新的TcpConnection,紧接着会执行一些后续操作(设置状态等),就是在MainLoop中执行TcpConnection::connectEstablished。在构造函数中,状态设置为正在连接,在connectEstablish设置为已连接,后续可以注册读写事件了。

// 建立连接
void TcpConnection::connectEstablished()
{setState(kConnected);channel_->tie(shared_from_this()); // 将TcpConnection绑定到Channel上channel_->enableReading();      // 向Poller注册EPOLLIN事件// 新连接建立,执行回调connectionCallback_(shared_from_this());
}

connectionCallback_为用户注册的回调函数。

读事件

Poller 在监听到读事件就绪后,会将活跃的 Channel 集合返回给 EventLoop,即 activeChannels。随后,EventLoop 遍历 activeChannels 中的每一个 Channel,并调用其对应的就绪事件回调函数。理解了前面这段话,我们可以思考谁把各种回调函数注册到了Channel

根据 TcpConnection 的构造函数,我们可以得出结论:TcpConnection 负责管理一个 TCP 连接的完整生命周期,而 Channel 则负责管理已连接后 socket 的各种回调函数、感兴趣的事件以及就绪事件等。因此,为了实现这一管理职责,TcpConnection 需要负责 Channel 的生命周期管理;

并在构造函数中,调用Channel的公有接口注册回调函数。

注册用于处理读事件的成员函数是 TcpConnection::handleRead,这意味着当读事件就绪时,会自动调用该函数。第一步先读数据,若数据已成功读出则执行messageCallback_()messageCallback_()其实就是我们作为用户利用TcpServer::setMessageCallback注册的回调函数,在此函数就负责业务逻辑处理,将读数据和业务处理解耦。

void TcpConnection::handleRead(Timestamp receiveTime)
{int saveErrno = 0;ssize_t n = inputBuffer_.readFd(channel_->fd(), &saveErrno);if(n > 0){// 已建立连接的用户,有可读事件发生messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);}else if(n == 0){// 客户端断开连接handleClose();}else{errno = saveErrno;LOG_ERROR("TcpConnection::handleRead");handleError();}
}
写事件

作为用户,我们希望只调用简单的接口就可以实现将数据发送出去,而不用关心其内部细节。在Muduo中提供了这样简单的接口:void TcpConnection::send(std::string const &msg)

其内部帮我们实现了线程安全:

  • 若调用send的线程与loop_所属的线程相同,则直接调用sendInLoop
  • 否则调用EventLoop::runInLoop,其实也等同于EventLoop::queueInLoop

void TcpConnection::send(std::string const &msg)
{if(state_ == kConnected){if(loop_->isInLoopThread()){// 在一个线程sendInLoop(msg.c_str(), msg.size());    }else{loop_->runInLoop(std::bind(&TcpConnection::sendInLoop, this, msg.c_str(), msg.size()));}}
}

Send只是向用户提供了一个向对端写数据的简易接口,真正做出向对端socketwirte操作的是TcpConnection::sendInLoop

此函数考虑了从应用缓冲区向内核缓冲区拷贝与网卡从内核缓冲区发送数据之间的速度,设置了一个水位标志,只有应用缓冲区的数据量越过水位标志才会发送数据。

调用 sendInLoop 会通过系统调用 write 将数据发送到对端。如果数据全部发送成功,则会触发回调函数 writeCompleteCallback_;如果未能全部发送完,则会将剩余数据存入写缓冲区,并为 Channel 设置 EPOLLOUT 事件。待内核缓冲区就绪时,Channel 会触发写事件,进而调用已注册的写回调函数(即 TcpConnection::handleWrite)来继续发送数据。

/*发送数据,应用程序写的快,内核发送慢,需要把带发送数据写入缓冲区,而且设置了水位回调
*/
void TcpConnection::sendInLoop(const char *message, size_t len)
{ssize_t nwrote = 0;         // 本次已写字节size_t remaining = len;     // 剩余字节bool faultError = false;    // 是否发生错误if(state_ == kDisconnected){LOG_ERROR("disconnected, give up writing!");return;}// 表示channel第一次写数据=> fd未注册写事件&&发送缓冲区可读字节为0if(!channel_->isWriting() && outputBuffer_.readableBytes() == 0){nwrote = ::write(channel_->fd(), message, len); // 向内核缓冲区写数据if(nwrote >= 0){remaining = len - nwrote;if(remaining == 0 && writeCompleteCallback_)   {// 表示数据已全部发送,调用发送完毕回调loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));}}else // 出错{nwrote = 0;if(errno != EWOULDBLOCK){LOG_ERROR("TcpConnection::sendInLoop");if(errno == EPIPE || errno == ECONNRESET){faultError = true;}}}}/*下面此判断逻辑说明:1.当前这一次write并没有全部发送完毕,需要将剩余的数据保存到缓冲区outputBuffer_中2.给Channel注册EPOLLOUT事件,poller发现tcp的发送缓冲区有空间,会通知sock - channel,调用writeCallback_回调3.就是调用TcpConnection::handleWrite方法,把发送缓冲区中的数据全部发送完为止*/if(!faultError && remaining > 0)    // 这次write系统调用无错误 && 还有剩余数据待发送{/*如果在某次调用sendInLoop并未一次性地把数据全部发送完,会把数据存到缓冲区;待下一次调用sendInLoop会取到上次未读完的数据*/size_t oldLen = outputBuffer_.readableBytes();  if(oldLen + remaining >= highWaterMark_&& oldLen < highWaterMark_&& highWaterMarkCallback_)  // 旧数据 + 此次未写数据 >= 高水位标志 && 旧数据 < 高水位标志 // => 意味着 此次未写数据要使写缓冲区待发送数据(缓冲区待发送数据 = 旧数据 + 此次未写数据)>=高水位标志{loop_->queueInLoop(std::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));}// 将此次未写数据添加到 缓冲区outputBuffer_.append(static_cast<const char*>(message) + nwrote, remaining);if(!channel_->isWriting()){channel_->enableWriting();  // 设置EPOLLOUT事件}}
}

待内核缓冲区就绪时,Channel 会触发写事件,进而调用已注册的写回调函数(即 TcpConnection::handleWrite)来继续发送数据。

为什么在 handleWrite 中判断 state_ == kDisconnecting

写事件触发的时机:当内核发送缓冲区有空间时,写事件会触发。这时,handleWrite 会尝试继续发送缓冲区中的数据。

判断是否完成发送:如果此时缓冲区中的数据已全部发送完成,并且连接状态是 kDisconnecting,说明可以安全地关闭连接。

void TcpConnection::handleWrite()
{if(channel_->isWriting()){int saveErrno = 0;ssize_t n = outputBuffer_.writeFd(channel_->fd(), &saveErrno);if(n > 0){outputBuffer_.retrieve(n);if(outputBuffer_.readableBytes() == 0) // 表示已发送完 {channel_->disableWriting();// 消息发送完之后的回调函数if(writeCompleteCallback_){loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));}/*为什么要判断连接状态?1.保证在断开连接前,所有待发送的数据都已发送完毕。2.实现优雅关闭(半关闭)*/if(state_ == kDisconnecting){shutdownInLoop();}}}else{LOG_ERROR("TcpConnection::handleWrite");}}else{LOG_ERROR("TcpConnection fd=%d is down, no more writing", channel_->fd());}
}
关闭

作为用户,可以在任意线程内(并非是TcpConnection所属的EventLoop线程内)调用 TcpConnection::shutdown() 来优雅地关闭与已连接客户端的连接。该方法会确保所有未发送的数据被完整发送后,再关闭连接的写端,从而实现对客户端的安全关闭操作。

// 关闭连接
void TcpConnection::shutdown() 
{   if(state_ == kConnected){setState(kDisconnecting);loop_->runInLoop(std::bind(&TcpConnection::shutdownInLoop, this));}
}

TcpConnection::shutdownInLoop 会确保在 EventLoop 所属的线程内执行,并首先检查数据是否已全部发送:

  • 若数据已全部发送:直接关闭写端,触发 EPOLLHUP 事件,通知对端连接已关闭。
  • 若数据未发送完:跳过关闭写端的逻辑,同时为 Channel 注册 EPOLLOUT 事件。随后,在 EventLoop 所属线程中,当内核发送缓冲区可用时触发写事件,执行 TcpConnection::handleWrite

handleWrite 中:

  • 如果缓冲区中的数据被完全发送,则会再次调用 TcpConnection::shutdownInLoop,完成闭环。
  • 最终,当所有数据发送完毕,关闭写端并触发 EPOLLOUT 事件,执行 Channel 注册的关闭事件回调,完成优雅关闭流程。
// 此方法会确保在EventLoop所属的线程内执行
void TcpConnection::shutdownInLoop()
{if(!channel_->isWriting()) // 表示写缓冲区内的数据全部发送完{socket_->shutdownWrite();// 关闭写端,触发EPOLLHUP;// =》channel::closeCallback_->TcpConnection::handleClose}
}

TcpConnection构造函数可知,Channel的关闭事件回调也就是TcpConnection::handleClose()

void TcpConnection::handleClose() 
{LOG_INFO("TcpConnection::handleClose fd=%d state=%d", channel_->fd(), (int)state_);state_ = kDisconnected;channel_->disableAll();TcpConnectionPtr connPtr(shared_from_this());connectionCallback_(connPtr);   // 执行连接关闭的回调closeCallback_(connPtr);        // 执行关闭连接的回调
}

connectionCallback_为用户注册的回调函数,closeCallaback_TcpServer注册的回调函数,最终会调用TcpConnection::connectDestroyed

// 连接销毁
void TcpConnection::connectDestroyed()
{if(state_ == kConnected){setState(kDisconnected);channel_->disableAll(); // 把channel的所以有感兴趣的事件从Poller中deleteconnectionCallback_(shared_from_this());}channel_->remove();// 在Poller中移除remove
}
错误

发生错误时,即Channel触发了EPOLLERR事件,会调用Channel中注册的错误回调函数,此函数是在TcpConnection构造函数中设置的。

void TcpConnection::handleError()
{int optval;socklen_t optlen = sizeof optval;int err;if(::getsockopt(channel_->fd(), SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0){err = errno;}else{err = optval;}LOG_ERROR("TcpConnection::handleError name:%s - SO_ERROR:%d", name_.c_str(), err);
}

至此,TcpConnection 的核心部分以及与其他组件的关联已经完整串联和解析。

TcpServer

TcpServerMuduo网络库的最顶层模块,它抽象了服务端的网络通信流程,包括监听端口、接收客户端连接、创建 TcpConnection 实例,以及管理多个连接的生命周期和事件回调。

具体如何使用它来构建一个建议聊天服务器,大家可以看看我的这篇文章Muduo架构设计剖析

TcpServer的组成

  1. Acceptor

    • 负责监听指定的地址和端口,并接收新连接。
    • 为每个连接分配一个文件描述符(fd),并将其交给主线程或线程池中的事件循环(EventLoop)处理。
  2. EventLoop

    • MainLoop,管理 TcpServer 和所有连接的事件驱动逻辑。
  3. EventLoopThreadPool

    • 可以将新连接分配给不同的线程,提高并发处理能力。
  4. 用户回调函数

    • 用户可以通过 TcpServer 注册各种回调函数(如连接回调、消息回调、写完成回调等),用于处理应用层逻辑。

类图如下:

image-20241214170151812

Tcpconnection.h

class TcpServer : noncopyable
{
public:using ThreadInitCallback = std::function<void(EventLoop*)>;enum Option{KnoReusePort,kReusePort};TcpServer(EventLoop* loop,const InetAddress& listenAddr,const std::string& argName,Option option = KnoReusePort);~TcpServer();void setThreadInitCallback(const ThreadInitCallback& cb) { threadInitCallback_ = std::move(cb); }void setConnectionCallback(const ConnectionCallback& cb) { connectionCallback_ = std::move(cb); }void setMessageCallback(const MessageCallback& cb) { messageCallback_ = std::move(cb); }void setWriteCallback(const WriteCompleteCallback& cb) { writeCompleteCallback_ = std::move(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);private:using ConnectionMap = std::unordered_map<std::string, TcpConnectionPtr>;EventLoop* loop_;   //用户定义的mainloopconst std::string ipPort_;const std::string name_;std::unique_ptr<Acceptor> accpetor_;std::shared_ptr<EventLoopThreadPool> threadPool_;   // one loop per threadConnectionCallback connectionCallback_; // 有新连接的回调MessageCallback messageCallback_;       // 有读写消息的回调WriteCompleteCallback writeCompleteCallback_;    // 消息发送完成以后的回调ThreadInitCallback threadInitCallback_; // loop线程初始化回调std::atomic_int  started_;int nextConnId_;ConnectionMap connections_;
};

TcpConnection.cc

构造函数
static EventLoop* CheckLoopNotNull(EventLoop* loop)
{if(loop == nullptr){LOG_FATAL("%s:%s:%d mainloop is null!", __FILE__, __FUNCTION__, __LINE__);}return loop;
}TcpServer::TcpServer(EventLoop *loop, InetAddress const &listenAddr, const std::string& argName, Option option) :loop_(loop),ipPort_(listenAddr.toIpPort()),name_(argName),accpetor_(new Acceptor(loop, listenAddr, option==kReusePort)),threadPool_(new EventLoopThreadPool(loop, name_)),connectionCallback_(),messageCallback_(),started_(0),nextConnId_(1)
{// 当有新用户连接时,此函数作为回调函数accpetor_->setNewConnectionCallback(std::bind(&TcpServer::NewConnection, this, std::placeholders::_1, std::placeholders::_2));
}
新用户连接回调函数
// 在构造TcpServer时,创建了accpetor_,并把TcpServer::NewConnection绑定到Acceptor
// 当有新连接时->在MainLoop中的Acceptor::handleRead()->TcpServer::NewConnection
void TcpServer::NewConnection(int sockfd, InetAddress const &peerAddr)
{// 根据轮询算法选择一个subloop来管理对应的channelEventLoop* ioloop = threadPool_->getNextLoop();char buf[64] = {0};snprintf(buf, sizeof(buf), "-%s#%d", ipPort_.c_str(), nextConnId_);++nextConnId_;// TcpConnection的名字std::string connName = name_ + buf; LOG_INFO("TcpServer::newConnecton [%s] - new connection[%s] from %s\n", name_.c_str(), connName.c_str(), peerAddr.toIpPort().c_str());// 通过sofkfd,获取其绑定的本地的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));connections_[connName] = conn;// 以下回调都是用户设置给 TcpServer// TcpServer -> Channel -> poller => notify channel 调用回调conn->setConnectionCallback(connectionCallback_);conn->setMessageCallback(messageCallback_);conn->setWriteCompleteCallback(writeCompleteCallback_);conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, std::placeholders::_1));// 执行此语句时是在mainLoop,将其入队到ioloop的任务队列,调用TcpConnection::connectionEstablishioloop->runInLoop(std::bind(&TcpConnection::connectEstablished,conn));
}

构造完TcpConnection并设置好回调函数后,会在MainLoop中执行TcpConnection::connectEstablished表示此TCP连接建立成功。

移除连接

TcpServer为每个TcpConnction设置了关闭回调conn->setCloseCallback(),为其绑定的函数是TcpServer::removeConnection,也就意味着当socket关闭时,就会触发EPOLLEHUP事件,Channel会调用其关闭回调函数,这个关闭回调函数就是TcpServer::removeConnection

此函数会在其他线程调用(即不是loop_绑定的线程),通过runInLoop函数进而一定会在MainLoop中执行TcpServer::removeConnectionInLoop

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

在MainLoop运行的移除TCP连接的操作

此函数会在MainLoop上运行,就是移除connections_中的映射关系。但其中的TcpConnection::connectDestroyed会在TcpConnection对应的SubLoop中运行。

void TcpServer::removeConnectionInLoop(TcpConnectionPtr const &conn)
{LOG_INFO("TcpServer::removeConnection [%s] - connection %s",name_.c_str(), conn->name().c_str());connections_.erase(conn->name());EventLoop* ioLoop = conn->getLoop();ioLoop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
}
设置SubLoop数量
void TcpServer::setThreadNum(int numThreads)
{threadPool_->setThreadNum(numThreads);
}
开始监听客户端连接
void TcpServer::start()
{if(started_++ == 0)// 防止一个TCPServer对象被start多次{// 启动底层的线程池threadPool_->start(threadInitCallback_);loop_->runInLoop(std::bind(&Acceptor::listen, accpetor_.get()));}
}
设置SubLoop数量
void TcpServer::setThreadNum(int numThreads)
{threadPool_->setThreadNum(numThreads);
}
开始监听客户端连接
void TcpServer::start()
{if(started_++ == 0)// 防止一个TCPServer对象被start多次{// 启动底层的线程池threadPool_->start(threadInitCallback_);loop_->runInLoop(std::bind(&Acceptor::listen, accpetor_.get()));}
}

多线程模型

  • 主线程
    • 负责管理 Acceptor 和接受新连接。
    • 新连接被分配给线程池中的工作线程。
  • 工作线程
    • 每个线程运行一个独立的 EventLoop,处理分配到的 TcpConnection 的读写事件和回调逻辑。

这种设计实现了主线程的轻量化,同时利用线程池处理大量并发连接。

总结

TcpServer 是 Muduo 的核心组件之一,为用户提供了简单易用的接口来实现高效的 TCP 服务器。它将底层复杂的网络操作(如 socket、epoll 等)封装为事件驱动的编程模型,并支持多线程,从而使用户能够专注于实现业务逻辑。

相关文章:

Muduo网络库解析--网络模块(2)

前文 重写Muduo库实现核心模块的Git仓库 注&#xff1a;本文将重点剖析 Muduo 网络库的核心框架&#xff0c;深入探讨作者精妙的代码设计思路&#xff0c;并针对核心代码部分进行重写&#xff0c;将原本依赖 boost 的实现替换为原生的 C11 语法。需要说明的是&#xff0c;本文…...

【读书笔记】《论语别裁》语文的变与不变

1.内容摘要 在《论语别裁》第01章“学而”中&#xff0c;作者探讨了语言和文字的变与不变&#xff0c;通过中西文化的对比&#xff0c;分析了文字作为思想表达工具的独立性和持久性。作者指出&#xff0c;虽然外语&#xff08;如英语和法语&#xff09;在每三十年有明显变化&a…...

elasticsearch 使用预处理将JSON类型转换成Object类型

文章目录 使用 Ingest Pipeline 和 json 处理器示例&#xff1a;使用Ingest Pipeline 写入数据使用该Pipeline 在Elasticsearch&#xff08;ES&#xff09;中&#xff0c;您可以使用“预处理”&#xff08;通常是通过处理器、Ingest Pipeline等方式&#xff09;将JSON类型的数据…...

华为HarmonyOS NEXT 原生应用开发: 数据持久化存储(用户首选项)的使用 token令牌存储鉴权!

Preferences 数据持久化存储 用户首选项&#xff08;Preferences&#xff09; 1. 封装 仓库工具类 ● 这里可以选择将 数据字段 key 抽取为一个静态方法&#xff0c;这里选择让用户传参&#xff0c;看起来较容易理解&#xff01; /*** 首选项 preferences - 实现数据持久化…...

每天40分玩转Django:Django视图和URL

Django视图和URL 一、课程概述 学习项目具体内容预计用时视图基础函数视图、类视图、视图装饰器90分钟URL配置URL模式、路由系统、命名URL60分钟请求处理请求对象、响应对象、中间件90分钟 二、视图基础 2.1 函数视图 # blog/views.py from django.shortcuts import render…...

Kioptirx level4

具体步骤 通过nmap扫描我们所在的网段探测一下存活的主机&#xff0c;发现目标主机开放了80端口可以去访问一下 在访问的同时通过dirb去爆破一下目录&#xff0c;这边发现有一个john的目录不知道是什么可以去 这边在用dirsearch去扫一下防止有漏掉的页面我们没有访问到&#x…...

JPG 转 PDF:免费好用的在线图片转 PDF 工具

JPG 转 PDF&#xff1a;免费好用的在线图片转 PDF 工具 在日常工作和生活中&#xff0c;我们经常需要将图片转换为 PDF 格式。无论是制作电子文档、准备演示材料&#xff0c;还是整理照片集&#xff0c;将图片转换为 PDF 都是一个常见的需求。今天为大家介绍一款完全免费、无需…...

《Django 5 By Example》阅读笔记:p543-p550

《Django 5 By Example》学习第 19 天&#xff0c;p543-p550 总结&#xff0c;总计 8 页。 一、技术总结 1.fixtures (1)定义 A fixture is a collection of files that contain the serialized contents of the database. (2)作用 1)数据导入 一般来说&#xff0c;我们是…...

精品基于Python实现的微信小程序校园导航系统-微信小程序

[含文档PPT源码等] [包运行成功永久免费答疑辅导] 《django微信小程序校园导航系统》该项目采用技术Python的django框架、mysql数据库 &#xff0c;项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、核心代码介绍视频等 软件开发环境及开发工具&#xf…...

【数字花园】个人知识库网站搭建:①netlify免费搭建数字花园

目录 [[数字花园]]的构建原理包括三个步骤&#xff1a;五个部署方案教程相关教程使用的平台 步骤信息管理 这里记录的自己搭建数字花园&#xff08;在线个人知识库&#xff09;的经历&#xff0c;首先尝试的是网上普遍使用的方法&#xff0c;也就是本篇文章介绍的。 后面会继续…...

数据仓库工具箱—读书笔记01(数据仓库、商业智能及维度建模初步)

数据仓库、商业智能及维度建模初步 记录一下读《数据仓库工具箱》时的思考&#xff0c;摘录一些书中关于维度建模比较重要的思想与大家分享&#x1f923;&#x1f923;&#x1f923; 博主在这里先把这本书"变薄"~有时间的小伙伴可以亲自再读一读&#xff0c;感受一下…...

分布式 窗口算法 总结

前言 相关系列 《分布式 & 目录》《分布式 & 窗口算法 & 总结》《分布式 & 窗口算法 & 问题》 参考文献 《【算法】令牌桶算法》 固定窗口算法 简介 固定窗口算法是最简单的流量控制算法。固定窗口算法的核心原理是将系统的生命周期划分为一个个…...

docker容器内部启动jupyter notebook但是宿主机无法访问的解决方法

目录 1.问题2.解决方法 1.问题 在docker容器内启动了jupyter notebook&#xff0c;在宿主机内用如下的url无法访问 http://localhost:8888 http://127.0.0.1:8888 启动方法&#xff1a; jupyter notebook 2.解决方法 启动方法加上选项[ --ip‘*’]或者[–ip‘0.0.0.0’] 即启…...

2.2 数据库设计方法

数据库设计流程&#xff1a; 1.需求分析&#xff1a;准确了解分析用户需求&#xff08;包括数据与处理&#xff09;。需求分析是整个设计过程的基础&#xff0c;需求分析决定了构建数据库大厦的速度和质量 2.概念结构设计&#xff1a;概设结构设计是整个数据库设计的关键&…...

ALOHA 协议详解

注&#xff1a;本文为 “ALOHA 协议” 相关文章合辑。 未去重整理。 动态分配信道&#xff08;ALOHA 协议、CSMA 协议&#xff09; QuantumYou 于 2021-07-27 09:32:04 发布 ALOHA 协议 纯 ALOHA 协议 -纯 ALOHA 协议思想&#xff1a;不监听信道&#xff0c;不按时间槽发送…...

Quant connect的优势和不足,学习曲线难

Quant connect的优势和不足 Quant connect作为一个成熟的算法交易平台&#xff0c;具有许多优势&#xff0c;包括&#xff1a; 强大的回测功能&#xff1a;Quant connect提供了丰富的数据源和回测功能&#xff0c;可以对各种交易策略进行全面的回测和分析。 容易上手&#xf…...

分布式 漏桶算法 总结

前言 相关系列 《分布式 & 目录》《分布式 & 漏桶算法 & 总结》《分布式 & 漏桶算法 & 问题》 概述 简介 LBA Leaky Bucket Algorithm 漏桶算法是一种流行于网络通信领域的流量控制/频率限制算法。漏桶算法的核心原理是通过一个概念上的“漏桶”来…...

2450.学习周刊-2024年50周

封面 人生五个球 ✍优秀博文 面对老板安排的工作&#xff0c;事事有回应&#xff0c;有必要吗&#xff1f; 职场精英进阶手册&#xff1a;工作推进五原则&#xff0c;让你合理高效地利用时间 上个班而已&#xff0c;千万别畏手畏脚 理解了雷军说的SU7要守正出奇&#xff0…...

前端性能优化实战:从加载到渲染的全链路提升

"这个页面怎么这么慢啊&#xff1f;" 产品经理小李站在我的工位旁,指着屏幕上的数据大屏抱怨道。我打开 Chrome DevTools 看了一眼,首屏加载时间确实有点吓人 - 足足用了 8 秒。作为一个追求极致体验的前端开发者,这个数字让我坐不住了。 回想起上周的性能检测会议,…...

pdf merge

在 Ubuntu 22.04 上&#xff0c;你可以使用以下命令行工具来合并多个 PDF 文件&#xff1a; 1. pdftk pdftk 是一个强大的 PDF 工具&#xff0c;支持合并、拆分和其他操作。安装和使用方法如下&#xff1a; sudo apt install pdftk pdftk file1.pdf file2.pdf cat output me…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...