前言 muduo库是陈硕个人开发基于reactor模式的tcp网络编程库。本人之前有学习过boost.asio网络库,故学习一下Muduo网络库,并分析它们之间的优缺点

前言

muduo库是陈硕个人开发基于reactor模式的tcp网络编程库。本人之前有学习过boost.asio网络库,故学习一下Muduo网络库,并分析它们之间的优缺点。

本系列将重点放在以下几件事情:

梳理Muduo的核心架构设计以及各个模块的职责

理解Muduo的事件驱动机制

理解Muduo的多线程模型

剖析作者精妙的代码设计思路并且重写其核心代码,将原来依赖boost库的地方都替换成C++11语法

下面列出主要讲解的模块:

网络相关模块

Socket

InetAddress

TcpConnection

Acceptor

TcpServer

事件循环相关模块

EventLoop

Channel

Poller

EpollPoller

线程相关模块

Thread

EventLoopThread

EventLoopThreadPool

基础模块

Buffer

Timestamp

Logger

概述篇

一、Muduo网络库简介

Muduo网络库是一个基于非阻塞IO和事件驱动的C++高并发TCP网络库。它基于Reactor的事件处理模式,并且采用了one loop per thread的线程模型,即每个线程只运行了一个事件循环。这种模型使得Muduo能够充分利用多核CPU的性能,实现高效的网路通信。

Reactor事件处理模式

Reactor是这样的一种模式,它要求主线程只负责监听文件描述符上是否有事件发生,如果有的话立即通知工作线程。除此之外,主线程不做任何其他实质性工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。

二、基于muduo实现简易聊天服务器

实现

在使用muduo网络库的一个巨大优势在于:

业务逻辑与网络层的解耦。muduo是一个网络层框架,已经把网络层面的接受新的连接、收发数据等操作都封装好了,开发者可以只去关注业务逻辑而不必花费大量时间在底层网络通信的细节上。

这里介绍下面的代码会用到的两个模块:

接受新连接:Muduo 的 TcpServer 模块自动处理新客户端的连接请求。

连接的生命周期管理:通过 TcpConnection 管理每个连接的状态(建立、关闭)。

聊天服务器功能:

支持多个客户端同时连接。

广播消息:当某个客户端发送消息时,服务器将消息广播给所有其他客户端

管理客户端连接,支持连接建立和断开通知

类ChatServer用来抽象这一过程,下面是ChatServer的声明

#include

#include

#include

#include

#include

class ChatServer {

public:

// ChatServer的构造函数

ChatServer(muduo::net::EventLoop* loop,

const muduo::net::InetAddress& listenAddr);

void start();

private:

// 连接新的客户端的回调函数

void onConnection(const muduo::net::TcpConnectionPtr& conn);

// 接受消息的回调函数

void onMessage(const muduo::net::TcpConnectionPtr& conn,

muduo::net::Buffer* buf,

muduo::Timestamp time);

// 管理Server的组件

muduo::net::TcpServer server_;

// // 保存连接的客户端

std::set connections_; // 保存连接的客户端

};

下面是ChatServer的定义

#include "ChatServer.h"

using namespace muduo;

using namespace muduo::net;

ChatServer::ChatServer(EventLoop* loop, const InetAddress& listenAddr)

: server_(loop, listenAddr, "ChatServer") {

server_.setConnectionCallback(

std::bind(&ChatServer::onConnection, this, std::placeholders::_1));

server_.setMessageCallback(

std::bind(&ChatServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

}

void ChatServer::start() {

server_.start();

}

void ChatServer::onConnection(const TcpConnectionPtr& conn) {

if (conn->connected()) {

LOG_INFO << "New connection from " << conn->peerAddress().toIpPort();

connections_.insert(conn);

} else {

LOG_INFO << "Connection from " << conn->peerAddress().toIpPort() << " closed";

connections_.erase(conn);

}

}

void ChatServer::onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) {

std::string msg = buf->retrieveAllAsString();

LOG_INFO << "Received message: " << msg;

// 将消息广播给所有连接的客户端

for (const auto& client : connections_) {

client->send(msg);

}

}

由上述代码可知,程序员只需要定义好接受新的连接,接受客户端发来的消息的回调函数就可以实现简易的聊天服务器。做到业务逻辑 与 网络层面 的 解耦。

接下来实现main函数

#include

#include "ChatServer.h"

int main()

{

int port = 10086;

muduo::net::InetAddress listenAddr(port); // 监听Addr

muduo::net::EventLoop loop;

ChatServer server(&loop, listenAddr);

server.start();

loop.loop();

return 0;

}

cmake_minimum_required(VERSION 3.5.0)

project(QTalk VERSION 0.1.0 LANGUAGES C CXX)

add_executable(QTalk

main.cpp

ChatServer.cpp)

set(EXECUTABLE_OUTPUT_PATH ../)

target_link_libraries(QTalk

muduo_net

muduo_base

pthread

)

测试运行

之后将程序运行起来后,利用Linux命令可以快速地进行TCP连接与收发数据:nc localhost 10086

Server会输出两条消息:

第一条消息是muduo库输出的信息,第二条是在ChatServer::onConnection即接受新的连接后的回调函数输出的消息。

发送一条消息后,客户端会接收到同样的消息,在服务端这里:

同理,第一条消息是muduo库输出的消息,第二条是ChatServer::onMessage即收到新的消息的回调函数输出的消息。

三、muduo的架构设计

muduo网络库是基于reactor事件处理模式的TCP网络库。

Reacor模式

Reactor模式的核心为:Event事件、Reactor反应堆、Demultiplex事件分发器、EventHandler事件处理器

Reactor 模式通过以上组件协作完成事件的处理,以下是其具体流程:

事件注册:

应用程序将需要监听的事件及对应的事件处理器(EventHandler)注册到Reactor中

事件循环:

Reactor启动循环,调用Demuliplex等待事件发生

事件检测:

当事件就绪时,Demultiplex将事件返回给Reactor

事件分发:

Reactor根据事件类型找到对应的EventHandler,并触发处理

事件处理:

EventHandler执行具体的业务逻辑,如读取数据、处理消息等。

muduo框架架构解析

Muduo的架构以one loop per thread即每个线程都有一个事件循环设计,每个线程都有它自己的Reactor,负责该线程的所有事件。主线程(main reactor)负责监听新的连接,并把accept后的socket封装起来交付给其他线程的 sub reactor处理,之后各个sub reactor负责与该连接的所有读写事件。

主从Reactor工作流程

主线程(main Reactor)

职责:

负责监听客户端的连接

处理accept操作,接受新的客户端连接

将接收到的客户端连接进行封装,通过负载均衡算法分配给线程池的sub Reactor

工作流程:

主线程监听套接字的可读事件(即有新连接到来)

调用 accept 接受新连接

将新连接的 socket 封装为 TcpConnection 对象

将新连接分配给某个工作线程的 EventLoop

工作线程(sub Reactor)

职责:

独立运行EventLoop,负责管理其分配的客户端连接

处理连接的读写以及超时等操作

工作流程:

主线程将新连接分配给工作线程后,工作线程接管该连接

工作线程的 EventLoop 注册该连接的事件(如可读、可写)

当连接上的事件触发时,调用对应的回调函数处理

处理完成后,根据需要更新事件监听状态

线程分配与负载均衡

Muduo使用线程池ThreadPool来管理多个工作线程,每个工作线程运行一个EventLoop。新连接分配策略采用简单的轮询算法(Round-Robin):

主线程维护一个线程池,每个线程绑定一个EventLoop

每次接收到新连接后,按照轮询算法选择一个线程,将连接分配到该线程对应的EventLoop

开发人员调用TcpServer::setThreadNum来设置工作线程的数量

四、总结

muduo的One Loop Per Thread模型通过主从Reactor的组合,合理分配了主线程和工作线程的职责,兼顾了性能与易用性。这种设计能够充分利用多核资源,同时屏蔽了复杂的底层网络细节,为开发者提供了一种简单、高效的开发体验。