libwebsockets 详解:介绍、交叉编译与使用指南
一、libwebsockets 简介
libwebsockets 是一个轻量级、跨平台的 C 语言库,专注于实现 WebSocket 协议及相关网络功能。它由 Andy Green 开发并维护,采用 MIT 许可证开源协议,广泛应用于嵌入式设备、服务器和客户端应用中。
核心特点
- 多协议支持:完整支持 WebSocket 标准(RFC6455),同时支持 HTTP/HTTPS 客户端和服务器功能
- 轻量级设计:内存占用低,代码简洁,适合资源受限的嵌入式环境
- 跨平台兼容:支持 Linux、Windows、macOS、iOS、Android 及多种嵌入式系统
- 异步非阻塞:基于事件驱动模型,高效处理并发连接
- 安全特性:集成 OpenSSL/WolfSSL 支持,可实现 WSS(WebSocket Secure)加密通信
- 扩展能力:支持自定义协议、压缩、代理等扩展功能
- 线程安全:提供线程安全的 API 设计,适合多线程环境
典型应用场景
- 物联网(IoT)设备实时通信
- 嵌入式系统的 Web 控制台
- 实时数据推送服务
- 游戏服务器与客户端通信
- 实时监控系统
- 低延迟的消息传递应用
二、libwebsockets 交叉编译
1. 准备工作
- 下载源码:
git clone https://github.com/warmcat/libwebsockets.git - 进入目录:
cd libwebsockets - 切换到稳定版本:
git checkout v4.3.2 - 准备交叉编译工具链(如 arm-linux-gnueabihf-)
2. 交叉编译配置(CMake)
libwebsockets 使用 CMake 作为构建系统,以下是针对 ARM 平台的交叉编译示例:
bash
# 创建构建目录
mkdir build && cd build
# 设置交叉编译工具链
export CC=arm-linux-gnueabihf-gcc
export CXX=arm-linux-gnueabihf-g++
# 运行 CMake 配置
cmake .. \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_SYSTEM_PROCESSOR=arm \
-DCMAKE_INSTALL_PREFIX=/path/to/install \
-DLWS_WITH_SSL=ON \
-DOPENSSL_ROOT_DIR=/path/to/arm/openssl \
-DLWS_STATIC_PIC=ON \
-DLWS_WITH_SHARED=OFF \
-DLWS_WITH_STATIC=ON \
-DLWS_WITHOUT_TESTAPPS=ON \
-DLWS_WITHOUT_EXTENSIONS=OFF \
-DLWS_WITH_HTTP2=OFF
# 编译并安装
make -j4
make install
3. 关键编译选项说明
-DLWS_WITH_SSL=ON:启用 SSL 支持(需要 OpenSSL 库)-DLWS_WITH_SHARED=ON/OFF:控制是否生成共享库-DLWS_WITH_STATIC=ON/OFF:控制是否生成静态库-DLWS_WITHOUT_TESTAPPS=ON:不编译测试程序,减小编译时间-DLWS_WITH_HTTP2=ON:启用 HTTP/2 支持(增加库体积)-DLWS_MAX_SMP=1:单线程模式(嵌入式环境常用)-DLWS_WITH_CLIENT=ON:启用客户端功能-DLWS_WITH_SERVER=ON:启用服务器功能
三、libwebsockets 使用示例
1. WebSocket 服务器示例
cpp
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
static int interrupted = 0;
// 信号处理函数,用于优雅退出
static void sigint_handler(int sig) {
interrupted = 1;
}
// 定义协议处理结构
static struct lws_protocols protocols[] = {
{
"echo-protocol", // 协议名称
callback_echo, // 回调函数
0, // 每个连接的用户数据大小
1024, // 接收缓冲区大小
},
{ NULL, NULL, 0, 0 } // 协议列表结束标记
};
// WebSocket 回调函数,处理各种事件
static int callback_echo(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len) {
switch (reason) {
// 新连接建立
case LWS_CALLBACK_ESTABLISHED:
lwsl_notice("新的客户端连接建立\n");
break;
// 接收到客户端消息
case LWS_CALLBACK_RECEIVE:
lwsl_notice("收到消息: %s (长度: %zu)\n", (char *)in, len);
// 准备响应数据(回声)
char response[1024];
snprintf(response, sizeof(response), "服务器收到: %s", (char *)in);
// 发送响应
unsigned char *buf = malloc(LWS_PRE + strlen(response) + 1);
if (buf) {
// 预留 LWS_PRE 空间给库使用
memcpy(&buf[LWS_PRE], response, strlen(response) + 1);
// 发送数据
lws_write(wsi, &buf[LWS_PRE], strlen(response), LWS_WRITE_TEXT);
free(buf);
}
break;
// 连接关闭
case LWS_CALLBACK_CLOSED:
lwsl_notice("客户端连接关闭\n");
break;
default:
break;
}
return 0;
}
// 服务器主函数
int main(int argc, const char **argv) {
struct lws_context_creation_info info;
struct lws_context *context;
const char *iface = NULL;
int port = 8080;
int opts = 0;
// 初始化日志系统
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO, NULL);
// 设置信号处理
signal(SIGINT, sigint_handler);
// 初始化上下文创建信息
memset(&info, 0, sizeof info);
info.port = port;
info.iface = iface;
info.protocols = protocols;
info.options = opts;
// 创建上下文
context = lws_create_context(&info);
if (!context) {
lwsl_err("创建上下文失败\n");
return 1;
}
lwsl_notice("WebSocket 服务器已启动,监听端口 %d\n", port);
lwsl_notice("按 Ctrl+C 退出...\n");
// 事件循环
while (!interrupted) {
// 处理网络事件,超时设置为 100 毫秒
lws_service(context, 100);
}
// 清理资源
lwsl_notice("服务器正在关闭...\n");
lws_context_destroy(context);
return 0;
}
编译命令:
bash
gcc ws_server.c -o ws_server -lwebsockets -lpthread
2. WebSocket 客户端示例
cpp
#include <libwebsockets.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
static int interrupted = 0;
static struct lws *web_socket = NULL;
static int connected = 0;
// 信号处理函数
static void sigint_handler(int sig) {
interrupted = 1;
}
// 客户端协议回调函数
static int callback_client(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len) {
switch (reason) {
// 连接建立
case LWS_CALLBACK_CLIENT_ESTABLISHED:
lwsl_notice("与服务器连接成功\n");
connected = 1;
break;
// 接收到服务器消息
case LWS_CALLBACK_CLIENT_RECEIVE:
lwsl_notice("收到服务器消息: %s (长度: %zu)\n", (char *)in, len);
break;
// 连接关闭
case LWS_CALLBACK_CLIENT_CLOSED:
lwsl_notice("与服务器的连接已关闭\n");
web_socket = NULL;
connected = 0;
break;
// 连接错误
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_err("连接错误: %s\n", (char *)in);
web_socket = NULL;
connected = 0;
break;
default:
break;
}
return 0;
}
// 定义协议
static struct lws_protocols protocols[] = {
{
"echo-protocol",
callback_client,
0,
1024,
},
{ NULL, NULL, 0, 0 }
};
// 发送消息的函数
static void send_message(struct lws *wsi, const char *msg) {
if (!wsi || !msg) return;
size_t len = strlen(msg);
unsigned char *buf = malloc(LWS_PRE + len);
if (!buf) return;
// 复制数据到缓冲区(预留 LWS_PRE 空间)
memcpy(&buf[LWS_PRE], msg, len);
// 发送文本消息
lws_write(wsi, &buf[LWS_PRE], len, LWS_WRITE_TEXT);
free(buf);
}
int main(int argc, const char **argv) {
struct lws_context_creation_info info;
struct lws_context *context;
const char *server_address = "ws://localhost:8080";
int retry = 0;
// 设置日志级别
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO, NULL);
// 设置信号处理
signal(SIGINT, sigint_handler);
// 初始化上下文信息
memset(&info, 0, sizeof info);
info.port = CONTEXT_PORT_NO_LISTEN; // 客户端不需要监听端口
info.protocols = protocols;
// 创建上下文
context = lws_create_context(&info);
if (!context) {
lwsl_err("创建上下文失败\n");
return 1;
}
lwsl_notice("WebSocket 客户端启动,连接到: %s\n", server_address);
lwsl_notice("按 Ctrl+C 退出...\n");
// 主循环
while (!interrupted) {
// 如果未连接,则尝试连接
if (!web_socket) {
struct lws_client_connect_info ccinfo = {0};
ccinfo.context = context;
ccinfo.address = "localhost";
ccinfo.port = 8080;
ccinfo.path = "/";
ccinfo.host = ccinfo.address;
ccinfo.origin = ccinfo.address;
ccinfo.protocol = protocols[0].name;
ccinfo.ssl_connection = 0; // 不使用 SSL
web_socket = lws_client_connect_via_info(&ccinfo);
if (!web_socket) {
if (retry % 5 == 0)
lwsl_warn("连接服务器失败,将重试...\n");
retry++;
// 等待一段时间再重试
usleep(1000000);
}
} else if (connected) {
// 已连接,发送测试消息
static int count = 0;
char msg[128];
snprintf(msg, sizeof(msg), "客户端消息 %d", count++);
send_message(web_socket, msg);
lwsl_notice("发送消息: %s\n", msg);
// 每 3 秒发送一次消息
sleep(3);
}
// 处理网络事件
lws_service(context, 100);
}
// 清理资源
lwsl_notice("客户端正在关闭...\n");
lws_context_destroy(context);
return 0;
}
编译命令:
bash
gcc ws_client.c -o ws_client -lwebsockets -lpthread
3. HTTPS 服务器示例(带 SSL 支持)
cpp
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
static int interrupted = 0;
// 信号处理函数
static void sigint_handler(int sig) {
interrupted = 1;
}
// HTTP 回调函数
static int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len) {
switch (reason) {
case LWS_CALLBACK_HTTP: {
// 准备响应内容
const char *html =
"<html>\n"
" <head><title>libwebsockets HTTPS 示例</title></head>\n"
" <body>\n"
" <h1>HTTPS 服务器工作正常</h1>\n"
" <p>这是一个使用 libwebsockets 构建的 HTTPS 服务器示例</p>\n"
" </body>\n"
"</html>";
// 发送 HTTP 响应头
lws_serve_http_file(wsi, "index.html", "text/html", NULL, 0);
return 0;
}
default:
break;
}
return 0;
}
// 定义协议
static struct lws_protocols protocols[] = {
{
"http-only", // 协议名称
callback_http, // 回调函数
0, // 用户数据大小
0, // 接收缓冲区大小
},
{ NULL, NULL, 0, 0 }
};
int main(int argc, const char **argv) {
struct lws_context_creation_info info;
struct lws_context *context;
int port = 443; // HTTPS 默认端口
// 设置日志级别
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO, NULL);
// 设置信号处理
signal(SIGINT, sigint_handler);
// 初始化上下文信息
memset(&info, 0, sizeof info);
info.port = port;
info.protocols = protocols;
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
// SSL 配置(需要有效的证书和密钥文件)
info.ssl_cert_filepath = "server.crt"; // 服务器证书
info.ssl_private_key_filepath = "server.key"; // 服务器私钥
// 创建上下文
context = lws_create_context(&info);
if (!context) {
lwsl_err("创建上下文失败\n");
return 1;
}
lwsl_notice("HTTPS 服务器已启动,监听端口 %d\n", port);
lwsl_notice("按 Ctrl+C 退出...\n");
// 事件循环
while (!interrupted) {
lws_service(context, 100);
}
// 清理资源
lwsl_notice("服务器正在关闭...\n");
lws_context_destroy(context);
return 0;
}
编译命令:
bash
gcc https_server.c -o https_server -lwebsockets -lpthread -lssl -lcrypto
生成自签名证书(用于测试):
bash
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes
四、常见问题与解决方案
-
编译错误:找不到 OpenSSL
- 确保已安装 OpenSSL 开发库
- 交叉编译时通过
-DOPENSSL_ROOT_DIR指定路径 - 若不需要 SSL,可禁用:
-DLWS_WITH_SSL=OFF
-
连接问题
- 检查防火墙设置,确保端口已开放
- 验证服务器地址和端口是否正确
- 对于 WSS/HTTPS,检查证书是否有效
-
性能优化
- 调整事件循环超时时间
- 合理设置接收缓冲区大小
- 对于高并发场景,考虑使用多线程
-
内存管理
- 确保为发送数据分配足够的内存(包括 LWS_PRE 空间)
- 及时释放不再使用的缓冲区
- 监控长时间运行的连接,防止内存泄漏
五、学习资源
libwebsockets 凭借其轻量级设计和强大功能,成为嵌入式设备和高性能服务器实现 WebSocket 和 HTTP 通信的理想选择。通过交叉编译,它可以轻松部署到各种平台,满足不同场景下的实时通信需求。