libwebsockets 详解:介绍、交叉编译与使用指南

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

四、常见问题与解决方案

  1. 编译错误:找不到 OpenSSL

    • 确保已安装 OpenSSL 开发库
    • 交叉编译时通过 -DOPENSSL_ROOT_DIR 指定路径
    • 若不需要 SSL,可禁用:-DLWS_WITH_SSL=OFF
  2. 连接问题

    • 检查防火墙设置,确保端口已开放
    • 验证服务器地址和端口是否正确
    • 对于 WSS/HTTPS,检查证书是否有效
  3. 性能优化

    • 调整事件循环超时时间
    • 合理设置接收缓冲区大小
    • 对于高并发场景,考虑使用多线程
  4. 内存管理

    • 确保为发送数据分配足够的内存(包括 LWS_PRE 空间)
    • 及时释放不再使用的缓冲区
    • 监控长时间运行的连接,防止内存泄漏

五、学习资源

libwebsockets 凭借其轻量级设计和强大功能,成为嵌入式设备和高性能服务器实现 WebSocket 和 HTTP 通信的理想选择。通过交叉编译,它可以轻松部署到各种平台,满足不同场景下的实时通信需求。

相关推荐
Mr YiRan2 小时前
JNI技术之手写JNIEnv与静态缓存与native异常
java·c++
飞翔的SA2 小时前
全程 Python:无需离开 Python 即可实现光速级 CUDA 加速,无需c++支持
开发语言·c++·python·nvidia·cuda
SccTsAxR2 小时前
算法进阶:贪心策略证明全攻略与二进制倍增思想深度解析
c++·经验分享·笔记·算法
CoderMeijun3 小时前
CMake 入门笔记
c++·笔记·编译·cmake·构建工具
楼田莉子3 小时前
设计模式:创建型设计模式简介
服务器·开发语言·c++·设计模式
啦啦啦!3 小时前
c++AI大模型接入SDK项目
开发语言·数据结构·c++·人工智能·算法
cmpxr_3 小时前
【C】隐式类型转换
c语言·c++·算法
大江东去浪淘尽千古风流人物3 小时前
【Basalt】nfr_mapper 中的“小 SfM/BA 后端”
c++·人工智能·计算机视觉·oracle·augmented reality
Magic--3 小时前
C++ STL中vector与list的核心区别
c++·windows·list