1. 写在前面:为什么我坚持让你自己编译一遍muduo
做C++后端开发的,几乎没有人没听过muduo这个名字。陈硕大佬开源的这套基于Reactor模式的高性能C++网络库,可以说是国内C++网络编程领域绕不开的经典作品。无论是看《Linux多线程服务端编程》这本书,还是想搞懂epoll、事件循环、线程池这些底层机制,muduo都是极好的学习素材。而要把muduo用起来、读进去,第一步就是把它在你的Linux环境下成功编译安装。
很多人会问:既然apt/yum里可能有现成的包,为什么非要自己从源码编译一遍?我个人的观点很直接:学习型项目必须源码编译。一方面,源码编译能让你清楚地看到muduo依赖什么、由哪些模块组成、头文件和库文件分别装到哪里;另一方面,muduo的代码风格非常值得精读,你编译完之后顺手打开~/muduo目录翻一翻源码,比任何教程都管用。更现实的原因是,muduo的master分支一直在迭代,系统仓库里的版本往往偏老,自己编译才能拿到最新的特性和修复。
这篇分享我会完整走一遍从环境准备、源码下载、CMake配置、编译安装到验证可用的全过程,并且把我在多台Linux服务器上编译muduo时踩过的坑一并整理出来。内容适合刚接触muduo的C++学习者,也适合要给项目引入muduo但卡在编译环节的工程师。跟着操作一遍,你有很大概率能顺利用起来。
2. 环境准备:编译前先把这些底子打好
2.1 系统与编译器版本要求
muduo虽然年代久远,但代码维护得很勤快,对现代Linux发行版的兼容性相当不错。我实测过的环境包括Ubuntu 18.04/20.04/22.04、CentOS 7/8以及Debian 11,都能顺利编译。不过有一点要提醒:CentOS 7自带的编译器版本过低,需要优先升级gcc,否则会在编译muduo时遇到C++11标准支持不完整的问题。
具体来看版本要求:
- 操作系统:任何主流的64位Linux发行版,内核版本其实无所谓,只要glibc版本别太老。32位系统我建议直接放弃,muduo的原子操作和内存模型优化都是为64位环境设计的。
- 编译器:GCC 4.8以上即可,但强烈建议GCC 7及以上。muduo源码大量使用C++11特性,老编译器虽然能过,但编译速度慢、警告多。我用GCC 9.4编译时全程零警告,体验非常好。
- CMake:必须3.0以上,否则无法正确解析muduo的CMakeLists.txt。Ubuntu 18.04自带的CMake 3.10就能用,但如果你的系统太老,建议去CMake官网下载预编译二进制,别用源码折腾。
- 构建工具:make是标配,一般系统都有。如果没有,记得先装上build-essential(Ubuntu系)或Development Tools(CentOS系)。
在动手之前,先花一分钟检查一下环境:
uname -a gcc --version g++ --version cmake --version make --version看到输出都正常,再继续往下走。这一步能帮你提前发现很多问题,不至于编译途中突然报错然后手忙脚乱。
2.2 依赖库:Boost是唯一硬性要求
muduo的依赖非常克制,核心依赖只有Boost库,而且主要是Boost的boost::function、boost::bind这些组件,不是Boost.Asio那种重量级网络组件。另一个可选的依赖是Google Protobuf,只有当你需要用到muduo自带的protobuf编解码器时才需要安装。
先说Boost的安装。这里有一个非常经典的坑:muduo仓库的master分支需要Boost 1.52以上版本,而某些老教程会告诉你任意版本都行,结果你用系统自带的libboost-dev装出来的老版本,编译时报一堆找不到头文件的错误。我在Ubuntu 18.04上就踩过这个雷,系统源里的Boost是1.65,已经能用了,但如果你用CentOS 7,系统源里的Boost只有1.53,勉强够用,编译时可能会有偶发问题。
推荐直接用apt或yum安装:
# Ubuntu / Debian sudo apt update sudo apt install -y libboost-dev libboost-system-dev libboost-filesystem-dev # CentOS / RHEL sudo yum install -y boost-devel如果你非要最新版Boost,自己去boost.org下载源码编译也行,但说实话没必要。muduo对Boost版本的依赖其实很宽松,系统源里的版本足够稳定,省下的时间不如多看两页源码。
Protobuf这边,我的建议是先不要装,跟着本文走完编译安装,等确实需要protobuf编解码功能时再回来补装。因为muduo的CMake会自动检测系统里有没有protobuf,有就开启相关示例的编译,没有就跳过,并不会导致整个编译失败。先把门槛降到最低,把一个最小可用的muduo跑起来,后面再逐步加功能。
3. 源码获取:从GitHub拉取最新代码
3.1 分支选择:master还是cpp11
muduo的GitHub仓库地址是https://github.com/chenshuo/muduo,当然,如果你访问GitHub不方便,也可以从国内的一些代码托管平台的镜像拉取,搜索“muduo mirror”就能找到。
这里要讲一个重要的背景。muduo历史上经历过一次重大分支调整:早期版本同时维护了master和cpp11两个分支,cpp11分支是用C++11重写过的版本,去掉了对Boost的依赖。后来陈硕把cpp11分支合并成了新的master,所以现在你从仓库拉下来的master分支,实际上就是C++11版本,不再依赖Boost。这一点很关键——很多教程还在教你先装Boost再编译,那是因为它们的年代太久远,还在用老分支。
所以现在的推荐做法是:
cd ~ git clone https://github.com/chenshuo/muduo.git拉下来之后,master分支就是最新代码。如果你想看看老版本长什么样,可以切到v1.0或v2.0这样的tag去考古,但对于学习和使用,直接master就好。仓库体积不大,几十MB,几秒钟就能拉完。
如果你不想用git,也可以直接去GitHub仓库页面点“Code”按钮下载zip包,效果一样。只不过我个人更喜欢git clone的方式,因为后续你想更新代码、切分支、查看提交历史都非常方便——学习muduo源码时,git log和git blame是很好的工具。
3.2 仓库结构速览:分清楚每个目录是干什么的
拉完代码后,先别急着编译,花几分钟把目录结构看清楚。muduo的仓库布局非常清晰,一看就懂:
muduo/ ├── CMakeLists.txt # 顶层CMake配置 ├── build.sh # 一键编译脚本 ├── examples/ # 丰富的示例程序 ├── muduo/ │ ├── base/ # 基础库:日志、线程、时间戳等 │ ├── net/ # 网络库核心:EventLoop、TcpServer等 │ └── ... ├── tests/ # 单元测试 ├── VERSION # 版本号 └── ...muduo/base是底层基础库,包含线程封装、日志、时间戳、原子操作等组件,这部分和网络无关,但可以被任何C++项目复用。muduo/net才是核心,里面有EventLoop(事件循环)、Channel(IO通道)、Poller(epoll封装)、TcpServer(TCP服务器)、TcpConnection(TCP连接)、Buffer(缓冲区)、InetAddress(网络地址)等都是网络编程中耳熟能详的组件。
examples目录我个人非常推荐多翻翻,里面有几个经典示例:echo(回显服务器)、chat(聊天室)、pingpong(性能测试)、http(HTTP服务器)等。这些示例代码量不大,但浓缩了muduo的核心用法,是你从编译安装走向实际编程最好的跳板。
4. 正式编译:一步步把muduo装进你的系统
4.1 使用官方构建脚本一键编译
muduo仓库根目录提供了一个build.sh脚本,这是最简单的编译方式。在仓库根目录执行:
cd ~/muduo chmod +x build.sh ./build.sh这个脚本的逻辑是:用CMake生成Makefile,然后make编译全部代码,默认情况下编译结果会放在build/release目录下(如果开启Debug模式则在build/debug目录下)。脚本里还包含了解析编译参数的能力,支持传入debug表示编译Debug版本。
整个编译过程需要几分钟,取决于你的机器性能。结束后,你会看到类似下面的输出提示,说明编译成功了:
[100%] Built target muduo_base [100%] Built target muduo_net如果看到的是Error字样,那就说明编译过程中出了问题。别慌,第5节我会专门整理常见问题和解决办法。
4.2 手动CMake编译:为了把每个选项握在手里
如果你是那种喜欢把每一步都搞清楚、不想被脚本隐藏细节的人,或者你需要在编译时定制一些选项(比如指定安装路径),那手动执行CMake流程会更适合你。完整流程我写在这里,同时会解释每条命令在干什么:
cd ~/muduo mkdir -p build/release cd build/release cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local ../.. make -j$(nproc)分步拆解:
mkdir -p build/release && cd build/release:创建并进入构建目录。强烈建议不要直接在源码根目录下编译,因为编译过程中会产生大量中间文件,污染源码目录,让你之后想git status查看源码改动时满屏都是未跟踪文件,烦不胜烦。CMake的out-of-source构建就是为了解决这个问题。cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local ../..:配置构建系统。CMAKE_BUILD_TYPE决定是Release还是Debug版本。Debug版包含调试信息,更适合用GDB跟踪源码学习;Release版做了优化,性能好但没有调试信息。如果你是学习阶段,我建议先编Debug版,后面阅读源码、断点调试都方便。CMAKE_INSTALL_PREFIX决定最终安装路径,默认是/usr/local,我觉得一般不需要改,除非你没有root权限,那可以指定到自己的目录,比如-DCMAKE_INSTALL_PREFIX=$HOME/muduo-install。make -j$(nproc):启动并行编译,$(nproc)会自动获取CPU核心数作为并行任务数。这个优化很重要,默认的make是单线程编译,在8核机器上可能要等好几分钟,而make -j8能把时间缩短到一分钟左右。不过要注意,如果在编译CentOS 7上跑老版本muduo,并行编译偶尔会触发依赖顺序问题,遇到报错就退回make单线程编译,不影响使用。
编译完成之后,输出目录里会出现两个核心库文件:libmuduo_base.a和libmuduo_net.a。这是静态库,后缀.a。muduo默认只生成静态库,这在C++网络库中很常见——静态库部署简单,不需要考虑动态库的运行时依赖问题。
4.3 安装到系统目录
编译完成并不意味着安装完成。CMake世界里的安装,指的是把头文件、库文件复制到系统约定的位置,让其他项目能自动找到它们。这一步同样需要执行:
sudo make install如果你在CMake配置时指定的安装路径是/usr/local,那么执行完这条命令后,muduo的头文件会安装到/usr/local/include/muduo,库文件会安装到/usr/local/lib。如果CMake配置时用了自定义路径,那安装位置就跟着自定义路径走,但后续编译器可能找不到,需要在编译你自己项目时手动加上-I和-L参数。
这里有个小细节:如果你编译时改了CMAKE_INSTALL_PREFIX,而后续其他项目要引用muduo,需要在环境变量或者CMake配置里显式指定路径。为了省心,我强烈建议使用默认的/usr/local安装路径,作为普通用户虽然没有写/usr/local/include的权限,但加上sudo就解决了,这也是绝大多数Linux软件的标准安装方式。
4.4 验证安装是否成功
安装完成后,不能高兴得太早,要验证一下muduo是否真的可用。最简单的验证方式,是写一个muduo版的Hello World——一个最小化的Echo服务器。这里给出一段极简代码:
#include <muduo/net/TcpServer.h> #include <muduo/net/EventLoop.h> #include <muduo/base/Logging.h> #include <iostream> using namespace muduo; using namespace muduo::net; class EchoServer { public: EchoServer(EventLoop* loop, const InetAddress& listenAddr) : server_(loop, listenAddr, "EchoServer") { server_.setConnectionCallback( std::bind(&EchoServer::onConnection, this, std::placeholders::_1)); server_.setMessageCallback( std::bind(&EchoServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } void start() { server_.start(); } private: void onConnection(const TcpConnectionPtr& conn) { if (conn->connected()) { LOG_INFO << "New connection established"; } else { LOG_INFO << "Connection closed"; } } void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time) { std::string msg(buf->retrieveAllAsString()); LOG_INFO << "Received " << msg.size() << " bytes"; conn->send(msg); } TcpServer server_; }; int main(int argc, char* argv[]) { EventLoop loop; InetAddress listenAddr(8888); EchoServer server(&loop, listenAddr); server.start(); loop.loop(); return 0; }将代码保存为echo.cc,然后编译:
g++ -std=c++11 -o echo echo.cc -lmuduo_net -lmuduo_base -lpthread注意几个链接参数:
-lmuduo_net -lmuduo_base:链接muduo的两个静态库-lpthread:muduo依赖POSIX线程,必须链接pthread- 不需要额外加
-lboost_*,因为新版master分支已经去掉了Boost依赖
如果编译通过,运行./echo,再用telnet 127.0.0.1 8888连上去,输入什么就回显什么,那muduo就彻底装好了、能用了。
5. 编译踩坑实录:把我这几台机器上踩过的坑都告诉你
5.1 常见编译错误与解决办法
不管你是用build.sh还是手动CMake编译,都会大概率遇到下面几个问题。我把它们整理成表格,另外附上我当时的排查思路:
| 报错信息 | 原因分析 | 解决方案 |
|---|---|---|
fatal error: boost/...: No such file or directory | 系统没装Boost或版本过老 | 新master无需Boost,若确认是老版本代码,安装libboost-dev |
CMake Error: CMake 3.x is required | CMake版本过低 | 下载CMake预编译包并配置PATH,或用cmake3命令 |
error: 'shared_ptr' was not declared in this scope | 编译器标准未设为C++11 | 在CMakeLists.txt或编译命令中显式加-std=c++11 |
undefined reference to 'pthread_atfork' | 链接时缺少pthread库 | 编译命令末尾加-lpthread |
collect2: error: ld returned 1 exit status | 缺少某个依赖或库路径不对 | 检查-L路径是否正确,用ldd查看动态库依赖 |
上面表格里第三个问题值得展开说一下。如果你的编译器默认标准是C++98,那即使代码里写了#include <memory>,shared_ptr等C++11类型依然不可见。新版muduo的CMakeLists.txt里已经设置了C++11标准,但如果你是自己写测试代码去引用muduo头文件,记得手动加-std=c++11。我在CentOS 7上用老版本gcc时踩过这个坑,当时编译一直报错,加了这个参数瞬间就好了。
还有一个很隐蔽的问题是LOG_INFO在旧版muduo中的用法。新版muduo采用流式日志语法,即LOG_INFO << "message",而老版本的日志API可能完全不一样。如果你参考的是网上很久以前的教程,编译时可能会报日志相关错误。建议以源码examples目录为准,那才是最权威的用法参考。
5.2 关于静态库和动态库的选择
muduo编译产出是静态库,静态链接进你的可执行程序后,运行时不再依赖muduo的库文件,部署非常方便。但静态链接也有个缺点:如果你的服务器上跑好几个用muduo写成的服务,每个可执行文件都会内嵌一份muduo代码,磁盘占用会稍稍增加。对于现代服务器来说,这个增加完全可以忽略不计。
如果你希望用动态库的方式,需要自己修改CMake配置,把add_library的STATIC改成SHARED。但我不建议新手去动这个,因为muduo官方只保证静态库的编译和测试覆盖,动态库可能遇到隐藏的链接问题,没必要给自己找麻烦。
我个人的习惯是,学习阶段用Debug版静态库,编译出来的库包含完整符号信息,用GDB调试时可以追溯到库内部的每一行代码——这是学习网络库源码的利器。等以后真正上线项目了,再切到Release版,追求最好的运行性能。
5.3 不同Linux发行版的差异提醒
虽然muduo的跨平台性做得很好,但不同Linux发行版上编译仍有些细微差异,我统一整理一下:
Ubuntu/Debian系整体是最顺利的,apt源里依赖齐全,gcc版本也新,基本上下载源码直接编译就能过。唯一要留意的是,新版Ubuntu默认gcc已经是11或12了,某些老版本muduo代码(比如你checkout到老tag)在高版本gcc下会报告一些额外警告,但基本不影响编译通过。
CentOS/RHEL系的系统要稍微关注:
- CentOS 7的默认gcc是4.8.5,尽管能用C++11,但对新标准的支持比较拙计,建议
yum install -y devtoolset-9-gcc-c++进行升级。装完后需要执行scl enable devtoolset-9 bash才能在当前shell里使用新gcc。切到新gcc后我实测编译很流畅。 - CentOS 8的gcc是8.x,已经可以流畅编译muduo,不用额外折腾。
- CentOS系里如果找不到libboost-dev这样的包名,也别慌,那说明发行版用的是libboost-devel这样的命名风格,
yum search boost搜一下就行。
Arch Linux/Manjaro系:
Arch的软件包版本都很新,一般不会遇到版本过老的问题。直接装boost和protobuf就行。如果你使用Arch系的ARM版本(比如树莓派上的Arch Linux ARM),编译可能耗时更长,但成功率和x86_64版本是一样的。
5.4 编译通过但运行时报错的排查方向
编译通过只是第一步,运行时问题更隐蔽。我最常遇到的两类运行时报错及排查思路:
报错1:找不到动态库。如果你未来把muduo改成了动态库,或你的程序链接了动态muduo库,运行时会报error while loading shared libraries: libmuduo_net.so: cannot open shared object file。原因是动态库安装位置不在系统默认搜索路径。解决办法是把/usr/local/lib加入动态库搜索路径:
echo "/usr/local/lib" | sudo tee /etc/ld.so.conf.d/muduo.conf sudo ldconfig执行完后再运行程序,就能找到动态库了。
报错2:端口被占用。运行示例程序时报bind: Address already in use或Address already available,说明端口没释放。muduo默认设置SO_REUSEADDR,所以连续重启服务一般没问题,但如果你之前有个进程还挂着,被占用的端口自然绑不上。排查方式很传统:
lsof -i:8888 kill -9 <PID>然后再启动你的程序就好。
5.5 一个最容易被忽略的问题:磁盘空间
muduo源码编译产生的中间文件不少,完整编译Release和Debug两套版本,build目录加起来可能占用几个GB的空间。对于云服务器或树莓派这种磁盘紧张的环境,这个问题不可忽视。我自己的习惯是:
- 只编译一个版本,要么Release要么Debug,不要两个都要
- 编译完成后,如果需要清理空间,可以删除
build目录,安装好的库和头文件不受影响 - 定期
git pull拉取最新代码的同时,旧build目录可以一并清掉重新编译
另外,编译muduo对内存要求不算高,1GB内存的机器完全能扛住,但如果你的机器内存特别小(比如512MB),可以不使用make -j并行编译,而是顺序编译,降低内存峰值占用。
6. 编译安装之后:我给新手的三点深挖建议
装好muduo只代表迈过了一道小坎,真正的考验在后面。根据我自己从“装好muduo”到“能用muduo写项目”的过渡经验,我建议你按下面的顺序继续深挖。
第一,先跑通再改源码。运行一下examples目录下的echo、chat、pingpong这些示例,感受一下muduo的事件循环和非阻塞IO到底是什么体验。然后试着改动其中几行代码,比如修改日志输出、改变消息处理逻辑,观察运行结果的变化。这个“动手改代码”的循环是最快理解框架的方式。
第二,带着问题读源码。muduo的源码写得很克制,读起来并不难,但前提是你得知道自己在找什么。比如你可以带着“EventLoop是如何实现事件循环的”“Channel是怎么分发事件的”“Buffer为什么能高效处理粘包”这样的问题去源码里找答案。源码路径分别是muduo/net/EventLoop.cc、muduo/net/Channel.cc、muduo/net/Buffer.cc,对照《Linux多线程服务端编程》这本书来看,效果最好。
第三,用muduo写一个自己的小项目。回显服务器只是入门,我建议你尝试写一个简单的HTTP服务器、一个聊天室或者一个简易RPC服务。只有真正开始写业务代码,你才会发现muduo的线程模型、连接管理、定时器这些API设计的精妙之处。这种发现的乐趣,是看任何教程都体会不到的。
我在实际学习过程中最大的体会是:muduo这套代码是越读越有味道的。一开始看可能觉得有点绕,但当你理解了Reactor模式的本质、明白了“one loop per thread”的线程模型为什么会成为高并发服务器的经典范式,再看其他网络库时就会有一种豁然开朗的感觉。而这一切的起点,就是今天这篇编译安装教程。希望你能亲手完成整个流程,把这个强大的工具真正装进自己的武器库。