资讯动态

libwebsockets编译实战:从源码下载到嵌入式交叉编译全流程

发布时间:2026/10/2 5:32:40 来源:尧图企业网站定制
做嵌入式或者物联网相关的开发只要牵扯到设备端和服务器端实时通信WebSocket基本是绕不开的一个协议。之前我在一个网关项目里需要把设备状态实时推送到前端页面最开始用的是HTTP轮询设备一多、频率一高服务器压力立刻上来了后来换成了WebSocket方案整个链路清爽了很多。当时调研了一圈开源库最终选了libwebsockets而这一选就用到了现在。libwebsockets是一个用C语言实现的WebSocket协议库官方定位就是轻量级、嵌入式友好同时也支持完整的服务端和客户端能力。它对资源的占用控制得比较好在树莓派这类性能不强的板子上也能跑得很稳而且License是MIT商用基本没有太多限制。这篇东西不是讲协议原理的重点放在最实际的三件事上怎么把源码下载下来、怎么编译出自己想要的库、以及怎么确认编译出来的东西真的能用。文章面向的是那些刚接触这个库的开发者或者说已经在集成路上被编译问题卡住的人。我会把整个流程拆开来讲包括我在实际编译中踩过的坑和一些取舍逻辑尽量让你少走弯路。1. 为什么选libwebsockets轻量、干净、工艺成熟大多数人在选WebSocket库的时候常常纠结于libwebsockets和websocketpp、uWebSockets这几个热门的库。用下来之后我的体会是libwebsockets最大的优势在于它的克制的设计它不像websocketpp那样重度依赖Boost也不像uWebSockets那样为了极致性能引入了比较复杂的异步模型。C语言实现的库在交叉编译、嵌入式部署上天然有优势不扯什么运行时依赖编出来是什么就是什么。1.1 这个库解决了什么问题WebSocket协议本身并不复杂但实现起来坑很多。握手阶段的HTTP Upgrade流程、帧的解析与掩码处理、分片消息重组、Ping/Pong心跳机制、关闭握手的时序这些细节开发者自己撸一遍少说也得一两周还未必覆盖到所有的边界情况。libwebsockets把这些全部封装好了你只需要关心业务逻辑连接建立之后往缓冲区里写数据或者从回调里读数据。它的典型使用场景包括嵌入式设备状态上报设备通过WebSocket连接服务器实时上报传感器数据。前端实时看板后端通过WebSocket向浏览器推送告警、日志、指标。多设备消息转发在局域网内部署一个轻量WebSocket服务端供多个客户端订阅消息。IoT网关一边接入传感器协议一边通过WebSocket把数据上传到云平台。这个库还自带了一个事件循环机制event loop默认基于poll()实现也可以换epoll在Linux上性能表现相当不错。编译的时候很多组件都可以裁剪对应不同的业务需求这一点后面会详细说。1.2 和其他方案的一笔对比账为了说清楚为什么是它我把自己实际用过的几个库放在一起做个对比特性libwebsocketswebsocketppuWebSockets语言CCC依赖可选OpenSSL/mbedTLSBoost较重无但需要较新编译器嵌入式友好度高低中交叉编译方便依赖多麻烦一般构建系统CMakeCMake/Boost.BuildMakefile/CMake事件模型自带事件循环配合ASIO自带libuv二次开发难度中中中高文档/样例较全一般较少如果你的项目是纯C/C混编、以后要往ARM板子上移植libwebsockets几乎是阻力最小的选择。如果你本身就在用Boost且项目全是C那websocketpp的无缝集成也有它的道理。组件的选择不分绝对好坏只有适不适合手头的项目但从本篇文章的编译环境出发我默认你已经选定libwebsockets。1.3 它的源码结构一览从GitHub把仓库拉下来之后你会看到几个关键目录以v4.x版本为例/lib核心代码协议解析、事件循环、TLS封装都在这里。/bin部分工具的入口代码比如测试服务器。/test-apps官方自带的测试程序源码编译后会在build目录里生成可执行文件。/include公共头文件编译安装后拷贝到系统include目录。/cmakeCMake模块处理依赖查找和平台检测。了解目录结构有助于后面找文件和排查问题特别是当你想确认某个API的原型时直接去include/libwebsockets.h里查最快。2. 编译前的准备环境检查与依赖梳理很多人编译开源库翻车往往不是库本身的问题而是环境里缺了某个依赖或者依赖版本不对。所以在下载源码之前先把环境捋清楚能省下后面很多麻烦。2.1 操作系统与编译器要求官方对平台的适配做得比较好Linux、macOS、WindowsMSVC/MinGW都能编。我自己的主力环境是Ubuntu 20.04/22.04下面的操作都以这个环境为例。如果你用的是CentOS或者树莓派OS命令上把包管理器换成yum/apt对应的就好。编译器方面gcc版本建议4.8以上因为libwebsockets的CMake构建会用到一些C11特性。用gcc --version确认一下太老的编译器建议先升级。另外cmake版本不能太老3.10以上比较稳妥。2.2 关键依赖的可选与必选Libwebsockets在编译时有两个方向的依赖TLS库默认会去查找OpenSSL。如果你的场景是纯内网、没有wss加密可以不装SSL编译时用LWS_WITH_SSLOFF关掉。但我的建议是即使暂时用不上也把OpenSSL装好因为一旦后续要上wss重新编译库的代价要比提前装好大得多。libuv这是一个可选依赖用LWS_WITH_LIBUVON启用。libuv的事件循环在某些高并发场景下有优势但多数嵌入式场景用不到可以不开。Ubuntu下基础依赖安装命令sudo apt update sudo apt install -y build-essential cmake git libssl-dev如果你的系统里没有OpenSSL头文件CMake配置阶段会直接报错提示找不到openssl这也是很多新手遇到的第一道坎。装好libssl-dev之后这个报错通常就消失了。2.3 源码下载的两种方式与版本选择下载源码有两种常用方式# 方式一git clone 最新仓库 git clone https://github.com/warmcat/libwebsockets.git # 方式二下载指定版本的release压缩包 wget https://github.com/warmcat/libwebsockets/archive/refs/tags/v4.3.3.tar.gz tar -xzvf v4.3.3.tar.gz版本选择上我个人建议优先选release tag而不是main分支。main分支是开发分支API可能会变动今天我基于某个版本写好的代码过一两个月可能就编译不过了。在GitHub的Tags页面能看到所有正式版本选最新的稳定版即可。以我写这篇文章的时间点v4.3.x是一个比较稳的版本。下载到本地后解压进入源码目录接下来就可以开始配置编译参数了。3. 用cmake-gui配出一份适合自己项目的编译配置一说到CMake很多习惯写Makefile的老哥第一反应就是命令行敲两行就编完搞什么GUI。但我为什么推荐先用cmake-gui是因为libwebsockets的编译开关非常多命令行一条条写下来不但容易漏还不直观。图形界面的好处在于能实时看到所有可配置项随手勾选反复调整编译参数时效率会高很多。3.1 配置工具的选择逻辑直接跑cmake ..其实也能编但默认配置会把很多用不到的功能也编进去导致编译时间成倍增加。而且libwebsockets的钩子选项多达几十个命令行方式稍不留神就会配置出看似成功实则带病的构建比如漏掉某个关键的宏定义。考虑到第一遍编译多半会遇到各种依赖问题用cmake-gui能在界面里直接看到红色告警或缺失项这对新手极其友好。所以我建议第一次配置用图形界面跑通之后把最终配置固定下来后续自动化构建再改回命令行。安装cmake-guisudo apt install -y cmake-curses-gui注意这里装的是cmake-curses-gui提供的工具叫ccmake是一个终端里运行的交互式配置界面。如果你在桌面环境也可以装cmake-qt-gui就是带图形界面的cmake-gui。两者功能等价下面操作以ccmake为例。3.2 完整的配置流程在源码根目录下创建并进入build目录然后启动配置工具cd libwebsockets mkdir build cd build ccmake ..首次打开ccmake会先自动跑一遍CMake配置然后显示当前所有的编译选项。按下c键重新配置配置过程中若有红色高亮项说明该配置有问题或者依赖缺失需要处理。配置完成后按g键生成Makefile并自动退出。我习惯调的几个关键开关选项名默认值我的建议说明LWS_WITH_SSLON视需求用wss就开纯内网可关LWS_WITH_MINIMAL_EXAMPLESONOFF关了能省大量编译时间LWS_WITHOUT_TESTAPPSOFFON纯使用场景不需要官方测试程序就开LWS_WITHOUT_TEST_SERVEROFFON需要测试就保持OFF关掉测试服务器LWS_WITH_LIBUVOFFOFF不用libuv就别开CMAKE_BUILD_TYPE空Release生产用ReleaseCMAKE_INSTALL_PREFIX/usr/local按需修改安装路径如果你只想快速看到一个能跑的实验环境最简单的配置方式是LWS_WITH_SSL保持ON确保环境里装了libssl-devLWS_WITH_MINIMAL_EXAMPLES保持ON方便查看官方示例代码LWS_WITHOUT_TESTAPPS设为OFF这样能编出测试服务器这样配置出来的库功能齐全适合第一次跑通缺点就是编译时间会长一点。有一说一我现在已经在用机器的环境比较好四核八线程开满默认配置大概一分钟左右能编完。但在树莓派或者老旧的笔记本上默认配置编译一次可能要七八分钟这也解释了网上不少人说libwebsockets编译很慢的原因其实就是把用不上的组件全编了一遍。3.3 理解开关背后的裁剪思路这里要展开说一下为什么要花这么多精力去理解编译开关。库的裁剪不只是为了编译速度更重要的是降低运行时的资源占用和攻击面。嵌入式设备上的Flash和RAM都很有限如果只用到WebSocket客户端功能却把服务端、SSEServer-Sent Events、HTTP目录服务、cgi等等全部编进去最终镜像体积会大不少。仔细计算下来一个去掉所有不需要功能的libwebsockets静态库体积能比默认配置缩减一半还要多。所以在配置的时候不妨先问自己几个问题我这个设备是做客户端还是服务端传输层是明文还是要TLS和业务层之间的连接是只用WebSocket还要不要HTTP/2等其他协议底层IO复用用默认poll就够还是要epoll事件循环把这些想清楚再动手配置编译出来的库会非常贴合你的实际场景运行稳定性和编译效率都是最优的。这也是一个好的嵌入式工程师和复制粘贴编译派的本质区别。3.4 最小客户端库配置实战这里我以在ARM板上做一个纯WebSocket客户端使用TLS连接云服务器为假想需求给出一份精简配置的具体操作。进入ccmake界面后做以下修改LWS_WITH_SSL ON需要TLSLWS_WITH_MINIMAL_EXAMPLES OFFLWS_WITHOUT_TESTAPPS ONLWS_WITHOUT_TEST_SERVER ONLWS_WITH_LIBUV OFFLWS_WITH_HTTP2 OFF纯WebSocket用不到HTTP/2LWS_WITH_HTTP_STREAM_COMPRESSION OFFLWS_WITH_FILE_OPS OFF如果不需要访问本地文件配置完成后按g生成。然后在build目录下执行make -j$(nproc)-j$(nproc)表示使用本机所有CPU核心并行编译能大幅缩短编译时间。如果你在嵌入式设备的交叉编译环境里还可以手动指定核心数避免OOM比如make -j4。编译完成后你会在build目录下看到以libwebsockets.so结尾的共享库或libwebsockets.a静态库以及一个libwebsockets.h的头文件路径整个编译阶段就结束了。4. 从make到make install:产物确认与项目衔接编译成功只是第一步。你需要的不是build目录里的中间产物而是能装到系统路径、能被其他工程引用的干净库文件。4.1 安装命令与路径选择如果你用的是默认prefix/usr/local那么只需要sudo make install安装完成后确认这几个路径下是否出现了相应文件/usr/local/include/libwebsockets.h——头文件写代码时#include libwebsockets.h会用到。/usr/local/lib/libwebsockets.so——共享库。/usr/local/lib/libwebsockets.a——如果没有关闭静态库生成还会有这个。如果你用的是自定义prefix比如/opt/lws那么记得把头文件路径和库路径都加到编译环境中。我在项目里常用的做法是export LWS_ROOT/opt/lws export C_INCLUDE_PATH$LWS_ROOT/include:$C_INCLUDE_PATH export LIBRARY_PATH$LWS_ROOT/lib:$LIBRARY_PATH export LD_LIBRARY_PATH$LWS_ROOT/lib:$LD_LIBRARY_PATH这些环境变量可以写进~/.bashrc但也建议在项目的CMakeLists.txt里显式指定因为环境变量跨项目共享容易污染其他工程的构建。4.2 验证库文件信息安装完毕后不要急着写业务代码先用几个Linux下的基础命令确认库文件是正常的# 查看共享库的链接依赖 ldd /usr/local/lib/libwebsockets.so # 查看导出的符号表确认核心API在里面 nm -D /usr/local/lib/libwebsockets.so | grep lws_client_connect如果ldd输出里出现not found大概率是某个依赖没装或者路径不对比如libssl.so缺失。遇到这种情况回去检查OpenSSL的安装和编译参数。nm如果能搜到lws_client_connect相关符号说明客户端API已经正确导出了。4.3 在自己的CMake工程里链接这个库在CMakeLists.txt里链接libwebsockets很简单cmake_minimum_required(VERSION 3.10) project(MyLwsTest C) set(CMAKE_C_STANDARD 11) find_package(PkgConfig REQUIRED) pkg_check_modules(LIBWEBSOCKETS REQUIRED libwebsockets) include_directories(${LIBWEBSOCKETS_INCLUDE_DIRS}) link_libraries(${LIBWEBSOCKETS_LIBRARIES}) add_executable(my_lws_test main.c)如果你用的是命令行gcc直接编译对应的命令是gcc -o my_lws_test main.c -lwebsockets -lssl -lcrypto这里有一个细节链接顺序很重要。-lwebsockets要放在源文件之后因为GNU ld是单遍扫描的被依赖的库要放在依赖者后面。很多人第一次编译自己的工程时遇到的undefined reference问题往往是库的链接顺序反了。5. 测试验证启动官方服务器、手测连接与压测尝试库编好、装好之后最需要确认的就是它真的能建立WebSocket连接、收发消息。libwebsockets在源码里自带了几套测试程序这一步比你自己写一个demo再去调试要方便得多。5.1 用官方测试服务器做回环验证如果你在配置时保留了测试服务器的构建LWS_WITHOUT_TEST_SERVER为OFF那么在build/bin目录下编译后的可运行文件里会有一个libwebsockets-test-server不同版本名字可能略有差异有的是websockets-test-server。启动方式cd build/bin ./libwebsockets-test-server -p 7681-p参数指定端口我这里用的是7681。正常情况下命令行会输出监听日志大概类似Listening on port 7681说明服务端已经跑起来了。需要注意一点如果你编译时开了TLS但没给测试服务器配证书它启动时可能会因为找不到证书而报错退出。这种情况下有两处理方式要么把LWS_WITH_SSL关掉重编一个纯明文测试版本要么在启动参数里指定证书路径。对于最基础的验证我建议先用纯明文版本跑通再考虑TLS。5.2 用浏览器验证整个链路打开Chrome或者Firefox在开发者工具的控制台里执行var ws new WebSocket(ws://127.0.0.1:7681, dumb-increment-protocol); ws.onopen function() { console.log([open] connected); ws.send(hello libwebsockets); }; ws.onmessage function(e) { console.log([message], e.data); };192.168.x.x等局域网地址同理把127.0.0.1换成服务器IP即可。如果你在控制台看到[open] connected并且能收到服务端的回包说明这个库的WebSocket服务端数据处理流程基本没有问题。5.3 用命令行工具做非浏览器验证有时候你需要模拟大量连接或者在一些无浏览器的环境中验证。可以使用websocat这样的命令行WebSocket客户端# 安装websocat sudo apt install -y websocat # 连接测试服务器 websocat ws://127.0.0.1:7681连接成功后可以直接输入文本向服务端发送消息收到的回包会打印在终端里。有条件的也可以写一段Python脚本用websockets库并发建立几十条连接观察服务端日志是否稳定。5.4 客户端模式测试验证库作为客户端的能力前面的操作都是从浏览器/命令行作为客户端连到libwebsockets服务端。但实际项目中你可能更常需要把libwebsockets作为嵌入式设备上的客户端去连接外部的WebSocket服务器。验证客户端模式也很简单在build/bin目录下官方也编出了一个测试客户端程序通常叫libwebsockets-test-client。先在一个终端启动网络上的任意WebSocket服务端比如用Python跑一个pip install websockets python3 -m websockets ws://127.0.0.1:9002然后在另一个终端执行./libwebsockets-test-client ws://127.0.0.1:9002观察两边日志如果服务端收到了来自客户端的连接握手客户端没有异常退出说明libwebsockets作为客户端时的连接逻辑也正常。这一步容易被人忽略但恰恰是嵌入式联网设备里最常用的工作模式。6. 嵌入式交叉编译给ARM板定制一个库前面所有流程都在x86 Linux上操作对于部署在树莓派、全志、RK等ARM平台上的项目还需要考虑交叉编译。这部分的思路和原生编译完全一样只是需要额外提供一个工具链文件并关掉那些在板子上用不到的组件。6.1 什么是工具链文件为什么需要它交叉编译的意思是在PC上编译生成目标板比如ARM上运行的二进制。CMake默认会使用本机的gcc它编译出来的程序只能在x86上跑。这时就需要通过一个toolchain.cmake文件告诉CMake编译器换成交叉编译器目标系统改成ARM。一个典型的toolchain文件以树莓派32位系统为例SET(CMAKE_SYSTEM_NAME Linux) SET(CMAKE_SYSTEM_PROCESSOR arm) SET(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) SET(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g) SET(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf) SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)我这里用的是Ubuntu自带的arm-linux-gnueabihf交叉编译工具链如果没有先安装sudo apt install -y gcc-arm-linux-gnueabihf g-arm-linux-gnueabihf如果你用的是aarch6464位ARM则把工具链前缀改成aarch64-linux-gnu-。6.2 交叉编译的配置命令在源码根目录下重新建一个build-arm目录避免和原来x86的build目录混淆cd libwebsockets mkdir build-arm cd build-arm cmake .. -DCMAKE_TOOLCHAIN_FILE../toolchain.cmake \ -DLWS_WITH_SSLOFF \ -DLWS_WITH_MINIMAL_EXAMPLESOFF \ -DLWS_WITHOUT_TESTAPPSON \ -DCMAKE_BUILD_TYPERelease然后编译make -j4注意这里不要使用-j$(nproc)因为交叉编译时单机核心太多会导致内存被瞬间打满除非你确定内存足够。6.3 交叉编译时的常见坑交叉编译的坑主要来自依赖库。如果目标板上需要TLS你在PC上装的OpenSSL是x86版本不能直接链接到ARM程序里。要么关掉SSL要么从源码交叉编译一份ARM版的OpenSSL。我在最初尝试时就是在这里差点放弃。一开始想着反正编译的是Linux库应该会自动适配吧结果连接时一堆undefined reference to SSL_CTX_new这才反应过来OpenSSL也需要为ARM平台单独编译。所以嵌入式场景有个原则用不到的能关就关能省掉一个依赖就省掉一个依赖。这也是为什么我在工具链文件示例里默认关闭SSL。6.4 验证交叉编译产物交叉编译完成后通过file命令检查产物架构file lib/libwebsockets.so如果输出中含有ARM字样的架构信息说明交叉编译成功如果显示x86-64说明编译器还是本机的回到toolchain文件检查路径和名字是否写对。把这个libwebsockets.so和头文件拷贝到目标板再在板子上用ldconfig刷新动态库缓存就可以正常连接使用。在板子上做基础连通性测试时可以直接用板子上的curl或者其他工具做一次握手包的探测echo -e GET / HTTP/1.1\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw\r\nSec-WebSocket-Version: 13\r\nHost: example.com\r\n\r\n | nc 目标服务器IP 端口如果服务器返回101状态码说明网络链路和协议栈是通的问题基本锁定在应用层代码上。7. 我在编译测试中踩过的坑以及对应的排查思路这一部分我非常想写因为翻看网上帖子很多人编译失败后习惯性地把错误信息直接丢进搜索引擎找到什么答案就贴什么回头问题并没有真正解决。我在这里把几个高频的、有共性的问题整理出来顺手说说我的定位方法。7.1 报错找不到ssl/openssl头文件这个报错基本上集中在CMake配置阶段错误信息里往往会出现Could NOT find OpenSSL。原因很简单OpenSSL的开发头文件没有安装。我多次强调装libssl-dev就是为了这个。如果确实装了还找不到确认一下安装路径是否在CMake的搜索范围内。Ubuntu下默认路径是/usr/include/openssl如果没问题再查一下环境变量OPENSSL_ROOT_DIR有没有被错误地设置。排查这类问题不要急着在CMakeLists里写死路径先手动找一下openssl/ssl.h在哪个目录再决定是装包还是加路径效率高得多。7.2 编译很慢多半是编了太多用不上的组件libwebsockets编译很慢是热搜里经常出现的关键词我也深有体会。有一次我在一台老笔记本上默认配置编译去泡了杯咖啡回来还没编完。后来我发现问题不在于编译器而在于默认配置把minimal examples、test server、test apps、各种插件全都编译了一遍。解决方案就是前文讲过的裁剪逻辑。如果是做开发调试可以用make -j4并在配置阶段把LWS_WITH_MINIMAL_EXAMPLES关闭只保留自己需要的部分。裁剪之后编译时间能从十几分钟降级到一两分钟体感差别非常显著。7.3 链接的时候出现undefined reference这个报错一般不是libwebsockets本身的问题而是调用方工程的问题。最常见的原因有三个链接库顺序不对-lwebsockets必须放在源文件或者依赖它的库之后。少了TLS依赖libwebsockets如果编译时开了SSL链接时就要跟着加-lssl -lcrypto。头文件和库版本不匹配比如头文件是新的库文件是旧的API对不上。解决方法是编译时加上-v参数看编译器实际执行的链接命令把所有-l参数顺序捋一遍基本能定位。7.4 编译出来体积偏大静态库和动态库取舍有些场景下你希望把libwebsockets静态编进应用里这样运行时不需要额外动态库。在CMake配置里可以设置LWS_WITH_STATICON或者同时生成两种库。不过静态链接有个注意点如果libwebsockets内部依赖了OpenSSL静态链接时通常需要添加-ldl -lpthread否则会出现符号找不到的错误。体积方面如果做极致裁剪可以尝试开启LWS_WITH_MINIMAL_EXAMPLESOFF加上-Os优化选项最终静态库体积能小到几百KB级别对于Flash紧张的板子来说相当友好。7.5 测试连接失败但服务端看起来启动正常如果是服务端模式启动日志正常但客户端连不上优先检查端口占用和防火墙。用ss -lntp查看端口有没有被监听用curl -v做一次HTTP探测看握手阶段是否响应。如果是客户端模式优先检查服务端地址、端口和协议名是否匹配比如测试服务器要求子协议为dumb-increment-protocol客户端没指定这个子协议时会被直接拒绝。这一类问题的排查思路是从链路最底层往上逐步排除。先确认TCP能通再确认HTTP 101返回再去看WebSocket协议层最后才是业务逻辑。我在实际项目里摸索出的心得其实就一句话把一个库用好不在于把文档从头翻到尾而在于把它在你的具体工程里跑出符合预期的行为。libwebsockets的代码质量和文档在开源项目里都算上乘编译安装这块熟悉以后后面写业务代码会顺畅许多。如果将来你有时间建议把minimal-examples目录下的官方示例逐个跑一遍那些示例覆盖了从客户端、服务端、异步收发到原始HTTP处理的全部常见玩法每跑通一个你就对这个库的理解深一层。这套编译测试流程现在花半个下午走一遍以后能给整个项目省下好几个下午。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑