尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

不依赖Boost的Asio独立安装指南:从源码到CMake集成全流程

不依赖Boost的Asio独立安装指南:从源码到CMake集成全流程 说实话标题里“不依赖boost的asio安装”这个需求我太理解了。C写网络程序Asio几乎是绕不开的名字。但一到安装环节很多人就被boost劝退下载boos全套源码、跑bootstrap、跑b2、再等它编译完少说半小时多则一小时。关键是项目里往往只用到Asio那一小块其他boost组件根本用不上纯粹是拖着个大包袱。其实Asio从很早开始就提供了独立的standalone版本完全可以不装boost把asio当作一个普通的头文件库直接用。这篇就专门围绕这个场景把下载、引入、编译、接入CMake的完整流程走一遍同时把我在实际部署中踩过的坑、排查的思路一起写出来。内容面向刚接触Asio的C开发者也适合交叉编译、嵌入式这类讲究依赖干净的环境。整个安装流程并不复杂核心就三件事下载独立源码、把include目录交给编译器、定义ASIO_STANDALONE宏。但这里面的版本选型、OpenSSL依赖、线程库链接还有和boost旧环境打架的问题如果不提前搞清楚很容易进坑。我会按照一个实际项目的推进顺序来讲从背景拆解一直写到问题排查。1. 为什么asio可以不依赖boost1.1 boost.asio与standalone版本的关系很多人以为Asio必须是boost的一部分这是一个历史误会。Asio的作者Christopher Kohlhoff在开发初期确实依托于boost库去开发但很快就把核心实现抽离出来了形成了两个发布渠道一个是boost.asio跟随boost的发布节奏另一个就是独立的standalone asio单独打包成tar.gz或zip不依赖boost直接给出头文件。这两个版本在API设计上是同一套代码风格、命名空间、文档结构几乎一致所以平时教怎么用Asio的教程、代码示例在standalone版本上基本能直接跑通。区别主要在底层实现standalone版本大量使用C11及以后的标准库组件替代了boost里那些基础设施比如用std::error_code替代boost::system::error_codestd::shared_ptr替代boost::shared_ptrstd::thread替代boost::thread。对于现代C项目来说这反而是个巨大的优点标准库自己就可以支撑Asio运行。一个常见的疑惑是既然有两个版本那我直接用boost.asio不就行了从功能上讲没问题但抛开编译成本不看boost发行版里的asio版本更新非常迟缓。拿实际经历来说boost 1.74里的asio还停留在某种旧版状态而standalone版本早就迭代了好几轮C20协程支持、各种新API、较新的bug修复都集中在standalone仓库里。追求新特性和少依赖的话独立版本明显更合适。从我个人的迁移经验看从boost.asio切到standalone asio代码改动量其实很小主要就是去掉对boost命名空间的依赖把某些boost::的引用换成std::或直接使用asio自带的类型别名。比如boost::asio::ip::tcp在standalone里还是asio::ip::tcp这种形式名字清晰很多。1.2 选择独立asio的好处与适用场景为什么一定要强调“不依赖boost安装”单独一个asio头文件库明明可以用包管理器顺手解决但现实里的痛点很具体。首先是依赖树膨胀的问题。boost不是一个库而是几十个库的集合。引入boost.asio时即便只想要异步网络实际依赖也可能牵扯出一串组件。项目编译时间上升、二进制体积变大、团队其他成员需要额外学习boost的使用习惯和管理方式。对于追求干净工程的人来说这种隐形成本非常高。其次是交叉编译和嵌入式环境。嵌入式设备的交叉编译工具链通常没有现成的boost即使有版本匹配也是个麻烦事。很多ARM Linux平台的SDK会自带老版本boost但Asio要求的boost组件又可能不完整最后只能自己交叉编译boost过程相当痛苦。standalone asio是纯头文件不涉及动态库、静态库的链接只要把include目录拷贝到目标根文件系统里交叉编译器带上-I参数就能用大大简化了嵌入式项目的依赖管理。还有一类常见场景是CI流水线。自动化构建环境里安装完整boost对网络和磁盘都是压力。用standalone asio则只需要在CI脚本里下载源码包、解压、指定include路径几秒钟就能完成构建速度明显提升。所以我的结论是除非你的项目本来就大量使用boost否则新项目完全没必要为了asio去引入boost。独立版本无论是上手门槛、构建速度、还是长期维护成本都更贴合现代C工程的诉求。2. 安装前的准备版本、环境与关键依赖2.1 下载前先确认版本号与来源下载asio standalone源码包时有两个渠道是官方认可的一个是作者维护的官网think-async.com那里能看到Asio用户指南、常见问题也提供各版本源码包下载另一个是GitHub上的chriskohlhoff/asio仓库除了源码本身还有issue跟踪和提交历史。从GitHub直接git clone也可以但如果只是给项目引入下载某个release快照的tar.gz会更干净仓库里那些测试代码、文档、历史提交不会一起塞进vendor目录。版本选择上我建议以官网列出的最新稳定版为准不要为了稳妥去下载两三年前的版本。以我写这篇时的常见版本来看1.30.x、1.28.x这类版本号都很常见具体数字以你实际看到的为准。越新的版本对C20协程、国家对一些新标准库特性的利用就越充分bug修复也更完整。要注意的是部分老版本的standalone asio对编译器要求偏高如果你用的是比较旧的gcc新版本可能会因为语法特性不能编译。这时候再去下载一个次新版本不要死磕最新的。有一个常见误解是asio没用到就别下载装它。实际上standalone asio只有头文件不需要编译生成任何库文件所以它在系统里的形态就是一个目录。你完全可以把它放进项目的third_party/asio目录随项目一起走这种vendor方式在团队协作中特别方便也避免了每个人环境里的版本漂移问题。2.2 确认Core之外的功能依赖OpenSSL选装standalone asio的核心网络功能是header-only的不需要任何动态链接库。但一旦你用到SSL/TLS相关的接口比如asio::ssl::stream就会依赖OpenSSL。这不是Asio自身的问题而是SSL功能本来就是封装OpenSSL来实现的。换句话说如果你的项目只需要TCP、UDP、串口、定时器等基础IO能力那根本不用安装任何额外的库下载源码包后直接引入头文件即可。如果你后续要用到SSL那需要提前把OpenSSL的开发包装上。Linux下常见的命令是sudo apt install libssl-dev这也是我为数不多推荐提前装的依赖避免代码写到一半才发现缺了头文件。不需要SSL功能的时候还可以通过定义ASIO_NO_SSL来禁用所有SSL相关的头文件包含这样编译器不会因为找不到openssl/ssl.h之类的东西而报错对编译环境的依赖更小。这个宏在嵌入式等精简环境下非常有用我后面排查章节会专门讲。2.3 环境检查编译器与CMake因为standalone asio依赖C11及以上特性编译器版本不能太老。实际开发现象是gcc 7或clang 5基本上就能编译C11的代码但如果想体验C20协程支持编译器必须支持Coroutines这需要gcc 10以上或者clang 12以上而且还需要一些移植性配置。在Linux上验证编译器很简单g --version cmake --version如果g低于某个能接受的水平建议先用包管理器升级编译器。Windows上我用得比较多的是MSVCVisual Studio 2019及以后版本都能很好地编译standalone asio要点是项目属性里设置C语言标准为C17或C20。macOS上的clang也一直维护得不错基本开箱即用。CMake版本建议至少3.10以上因为现在很多项目采用CMake的target-based方式管理依赖用find_package(Threads)这类模块需要较新的CMake支持。如果你的工程还在用Makefile也没问题编译命令里手动加-I参数和-D宏就可以具体命令我在下一步讲。3. 实操安装从源码包到能跑的工程3.1 解压结构与include目录说明假设你已经把asio源码包下载到了~/tools/asio-1.28.0.tar.gz解压后看一下目录结构会发现里面核心部分是include目录tar -zxvf asio-1.28.0.tar.gz -C ~/tools cd ~/tools/asio-1.28.0 ls include/正常情况下include/asio.hpp和include/asio目录就在那里。asio.hpp是整个库的主入口所有公开API几乎都能从这个头文件引入。也就是说你的编译器include路径只需要指向这个include目录源码本身不需要任何编译阶段。这里要说清楚一个细节standalone asio没有传统意义上的“链接库”概念。Linux下不需要libasio.a或libasio.soWindows下也不存在asio.lib。它就是纯header-only的库存在形式等同于nlohmann/json这类现代头文件库。所以安装动作本质就是“把源码目录放到一个合适的位置 让编译器能看到”术语可以叫“vendor”或“include-only”。我习惯把独立版asio放进项目的third_party/asio目录这样别人clone代码之后自带依赖不用每台机器都跑一遍下载步骤。如果公司内部有镜像或制品库也可以把tar.gz上传上去安装时统一从镜像拉取避免团队访问外网不畅导致构建失败。3.2 写一段最小测试代码并编译安装是否成功最简单的方式是写一个能编译、能运行的测试程序。我一般会写一个最基础的TCP daytime客户端逻辑简单但覆盖了核心头文件、核心API和线程库依赖。#include asio.hpp #include iostream int main() { asio::io_context io; asio::ip::tcp::resolver resolver(io); asio::ip::tcp::socket socket(io); try { auto endpoints resolver.resolve(time.nist.gov, 13); asio::connect(socket, endpoints); for (;;) { char data[128]; std::error_code ec; size_t len socket.read_some(asio::buffer(data), ec); if (ec asio::error::eof) { break; } if (ec) { throw std::system_error(ec); } std::cout.write(data, len); } } catch (std::exception e) { std::cerr error: e.what() std::endl; } return 0; }这段代码依赖asio::io_context、resolver、socket、buffer、error_code这些基本类型正好能测试standalone版本的完整可用性。编译命令如下Linux gg -stdc17 -DASIO_STANDALONE -I~/tools/asio-1.28.0/include test.cpp -lpthread -o test最关键的两个参数-DASIO_STANDALONE显式告诉asio进入standalone模式不要试图找boost.asio头文件。-lpthreadAsio内部用到线程、互斥量等在Linux上必须链接pthread库否则编译能过但链接会报undefined reference。编译成功后运行./test正常情况下会连上时间服务器并打印一串时间报文。如果网络环境访问不了time.nist.gov可以换成一个局域网内已知TCP服务或者干脆只写一个定时器来验证Asio基本功能我认为这一步能跑通就说明环境没问题了。Windows下的编译命令稍有不同MSVC的命令行或者Visual Studio工程里只需要设置预处理宏ASIO_STANDALONE和include路径不用特意链接pthread。具体步骤我写在后面问题排查表格里。3.3 用CMake集成到项目实际工程里很少手动敲g命令CMake集成是更普遍的方式。把standalone asio接入CMake有两种常见做法直接include路径配合宏定义或者用FetchContent自动下载。直接include路径的方式适合团队已经规整了vendor目录的情况CMakeLists.txt写起来非常简单cmake_minimum_required(VERSION 3.10) project(asio_demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 路径按你的vendor目录调整 set(ASIO_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/third_party/asio/include) add_executable(test_app main.cpp) target_include_directories(test_app PRIVATE ${ASIO_INCLUDE_DIR}) target_compile_definitions(test_app PRIVATE ASIO_STANDALONE) # Linux上需要Threads find_package(Threads REQUIRED) target_link_libraries(test_app PRIVATE Threads::Threads)找Threads这个CMake模块再链接Threads::Threads是Linux环境的标准做法比直接写-lpthread更不容易踩平台切换的坑。因为Threads::Threads这个target在Linux上解析为pthread在Windows上则是一个no-op可以很好地跨平台。如果想要全自动可以用FetchContent从GitHub拉取指定版本的asio源码include(FetchContent) FetchContent_Declare( asio URL https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-28-0.tar.gz ) FetchContent_MakeAvailable(asio) # 之后 target_include_directories 指向 asio_SOURCE_DIR/include但FetchContent方式有一个和网络相关的风险一旦下载源不可达整个configure阶段就会失败。对CI环境来说我喜欢把tar.gz先缓存到制品库再通过file(URL)方式引用内网地址或者干脆走vendor目录的方案综合来看更稳。这里没有绝对正确答案按团队基础设施情况来选就行。3.4 C20协程快速体验可选如果你是新项目且编译器支持C20那独立asio的coroutine支持值得体验一下。传统异步回调式的代码容易陷入回调地狱用了协程后异步代码看起来几乎像同步代码。下面是一个简单的异步定时器使用asio::co_spawn和awaitable#include asio.hpp #include asio/experimental/awaitable_operators.hpp #include iostream using asio::awaitable; using asio::co_spawn; using asio::detached; using asio::use_awaitable; awaitablevoid timer_demo(asio::io_context io) { asio::steady_timer timer(io, std::chrono::seconds(1)); co_await timer.async_wait(use_awaitable); std::cout timer fired after 1s std::endl; } int main() { asio::io_context io; co_spawn(io, timer_demo(io), detached); io.run(); return 0; }编译时需要开启C20并且通常要定义ASIO_HAS_CO_AWAIT或明确指定ASIO_HAS_CO_AWAIT宏具体看版本文档。这个体验例子只是为了证明独立asio在最新标准下体验不错不是安装环节的必需项但对老代码迁移到协程的评估很有意义。4. 常见问题与排查实录4.1 编译阶段找不到asio.hpp或误入boost.asio第一个最典型的报错是fatal error: asio.hpp: No such file or directory这种情况几乎都是include路径问题。检查编译命令或CMake里target_include_directories的路径是否真的指向了include目录尤其注意不要把路径写成asio的上级目录。比如你解压在/home/user/asio-1.28.0正确的include参数是-I/home/user/asio-1.28.0/include而不是-I/home/user/asio-1.28.0。另一种隐蔽的报错是明明没有安装boost但编译时出现boost相关头文件找不到的提示比如fatal error: boost/system/error_code.hpp: No such file or directory出现这个的原因多半是你没有定义ASIO_STANDALONE。在这种情况下standalone asio默认会回退到boost模式尝试找boost.asio的头文件。解决办法很简单把宏加上优先在CMake里target_compile_definitions加或者在源文件第一行写#define ASIO_STANDALONE但考虑到多个源文件都会包含asio.hpp宏分散容易遗漏我还是推荐编译选项统一加。还有一类情况更隐蔽系统里同时存在boost.asio和standalone asio头文件。编译器寻找头文件时有搜索路径顺序如果把boost的include目录和asio的include目录都交给了编译器某些旧版本头文件可能会抢占路径导致明明写了standalone版本却被封装成boost版本编译。这种情况下优先保证asm的include目录在boost include目录之前并且显式定义ASIO_STANDALONE。4.2 链接阶段pthread相关undefined reference编译通过但链接报错的场景非常经典错误信息类似undefined reference to pthread_create undefined reference to pthread_join这是因为Asio内部启动线程、创建互斥量时需要链接到pthread库。如果你使用g手动编译在命令末尾增加-lpthread即可。注意这个参数要放在源文件之后链接器的符号解析顺序有时候会把参数顺序搞出问题虽然现代gcc版本大多自动处理但手动命令还是养成库参数放后面的习惯。如果用了CMake最佳实践是find_package(Threads REQUIRED) target_link_libraries(your_target PRIVATE Threads::Threads)而不是手写-lpthread因为Threads模块在Windows上会直接变成空操作Windows不需要额外线程链接。还有一个高级问题如果代码里使用了asio::experimental或者协程相关特性某些老版本可能需要额外链接libstdcfs之类的东西。我在gcc 9环境下遇到过一次原因是std::filesystem的依赖不完整但大部分asio项目不会踩这个点只有在同时使用协程和文件系统时才会触发。遇到这个报错时考虑升级编译器是最有效的做法。4.3 Windows环境MSVC与MinGW的差异Windows上使用standalone asioMSVC用户通常不需要特意链接winsock库因为现代SDK已经把Ws2_32等系统库默认包含在工程设置里了。至少Visual Studio 2019的默认Windows SDK配置就能跑通。但如果你使用的是MinGW-w64配合CMake有时候需要显式链接ws2_32和mswsocktarget_link_libraries(your_target PRIVATE ws2_32 mswsock)否则可能出现类似undefined reference to WSAStartup8这种链接错误其实是winsock基础函数没有找到原因就是MinGW环境不会自动排入系统库。Windows上还容易出现另一个问题没有设置编译标准。Visual Studio默认语言标准可能停留在C14但新版asio可能要求C17。建议项目属性里把C语言标准明确设置为ISO C17或更高再进行编译。4.4 交叉编译与嵌入式环境的坑交叉编译场景下standalone asio的header-only特性完胜boost但不代表完全没有坑。最常见的坑是pthread库的交叉链接。如果你的工具链是arm-linux-gnueabihf-g通常需要在CMake里用对应的toolchain file并确保find_package(Threads)能找到目标平台的pthread库而不是宿主机器的。一个写死在根文件系统里的libpthread.so路径容易造成链接错平台库的乌龙。我的建议是始终通过工具链配置文件指定CMAKE_FIND_ROOT_PATH让CMake仅搜索目标系统根目录里的库。另一个坑是OpenSSL的交叉编译。很多交叉编译环境没有目标平台的libssl.a和头文件一旦你的代码引入了asio/ssl.hpp整个编译马上报错。这时要么自己交叉编译OpenSSL要么像前面说的明确不需要SSL就定义ASIO_NO_SSL从源码层面把SSL依赖屏蔽掉。嵌入式项目里我碰到过多次因为引用了一个SSL头文件导致整条构建链挂掉的我现在都会在非SSL需求的工程里提前加上这个宏。4.5 常见问题速查表现象大概率原因解决办法fatal error: asio.hpp: No such fileinclude路径配置错误检查-I或CMake的include目录确保指向源码包下的include文件夹boost相关头文件找不到忘了定义ASIO_STANDALONE宏编译选项或CMake定义中加上-DASIO_STANDALONEundefined reference to pthread_*Linux下没有链接线程库手动编译加-lpthreadCMake用Threads::Threads找不到openssl/ssl.h使用了SSL功能但没有安装OpenSSL开发包安装libssl-dev或定义ASIO_NO_SSL禁用SSL头文件交叉编译链接到宿主pthreadCMake搜索路径指向了宿主机配置CMAKE_FIND_ROOT_PATH指向目标平台根文件系统WSAStartup等winsock链接错误MinGW环境未链接WinSock库添加ws2_32、mswsock到链接列表协程功能编译不通过编译器不支持C20协程或未开启标准使用gcc 10并设置CMAKE_CXX_STANDARD 20旧boost.asio头文件抢占路径编译器同时搜索了boost和asio头文件调整include搜索顺序确保asio在前并加ASIO_STANDALONE四级排查这些问题的思路其实核心就是一条先确认宏再看include路径最后查链接库。顺序对的话绝大多数安装问题能很快定位。4.6 尽量少用包管理器安装asio我见过很多朋友习惯用Homebrew、apt或vcpkg一把梭安装asio虽然快速但我不太推荐把这个作为主要安装方式。原因有两个一是包管理器里的asio版本往往落后于官方发布有些发行版仓库里的asio还被打包成boost依赖形态装了之后反而跟你项目里已有的boost版本产生混乱二是包管理器安装的头文件位置分散在系统目录移植到别的机器时依赖环境不透明反而不如把stdio源码vendor进项目里来得可控。如果实在要用包管理器比如Deiban/Ubuntu下apt install libasio-dev建议装完后检查一下头文件版本再写个测试编译确认它是否真正独立了boost。很多环境下你会发现它确实独立了毕竟这么多年社区推动下来主流发行版已经默认打包standalone形态但版本号依然可能比较旧。大概率够用只是别追求最新特性。个人使用心得与收尾我自己从boost.asio迁移到standalone asio算起来也有几年时间了。最大的感受是asio这个库的设计本来就不应该和boost绑定得那么死。它真正需要的不过是标准库、一个线程库和一个异步事件循环这些在现代C里都已经非常成熟。只要你把ASIO_STANDALONE这个宏记住把include目录指对整个安装过程就会变得异常清爽。如果后续要继续扩展我建议你维护一个vendor/asio目录把它作为项目的一部分提交到代码库。这样新同事、CI、自动化测试拉取代码后不需要任何额外安装步骤直接configure和build。对这个目录的更新不要随意每次锁定一个版本号用release tag命名目录方便回滚。顺便可以说一句要是哪天项目真要上协程独立asio对C20的支持会让你觉得当初这个选择特别值。
返回列表