Libwebsockets(LWS)是一个灵活、轻量级的纯C库,使用非阻塞事件循环,以极小的占用空间轻松实现现代网络协议。自2010年以来,它一直在不断开发,并被全球数千万台设备和数千名开发人员使用。

uWebSockets 安装

需要先看《VCPKG 跨平台C++ 库管理器》,安装VCPKG环境之后,再来进行安装,这个需要VCKPKG C++库管理工具。

通过命令安装

vcpkg install libwebsockets

安装完毕,然后,就可以直接引用,并使用了。

示例

服务端

#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>

#define MAX_PAYLOAD_SIZE 1024
static int interrupted = 0;

// 每个连接的私有数据结构(Per-Session Data)
struct per_session_data__echo {
    unsigned char buf[LWS_PRE + MAX_PAYLOAD_SIZE];
    size_t len;
};

// 信号处理
static void sigint_handler(int sig) {
    interrupted = 1;
}

// 服务器回调函数
static int callback_echo(struct lws* wsi, enum lws_callback_reasons reason,
    void* user, void* in, size_t len) {
    struct per_session_data__echo* pss = (struct per_session_data__echo*)user;

    switch (reason) {
    case LWS_CALLBACK_RECEIVE:
        if (len > MAX_PAYLOAD_SIZE) len = MAX_PAYLOAD_SIZE;
        // 复制接收到的数据,注意要偏移 LWS_PRE 长度
        memcpy(&pss->buf[LWS_PRE], in, len);
        pss->len = len;
        // 标记该连接可写,通知底层触发 WRITEABLE 回调
        lws_callback_on_writable(wsi);
        break;

    case LWS_CALLBACK_SERVER_WRITEABLE:
        if (pss->len > 0) {
            // 发送数据,指针必须指向预留了 LWS_PRE 后的位置
            int n = lws_write(wsi, &pss->buf[LWS_PRE], pss->len, LWS_WRITE_TEXT);
            if (n < 0) return -1;
            pss->len = 0; // 清空标志,防止重复发送
        }
        break;

    default:
        break;
    }
    return 0;
}

// 协议列表
static struct lws_protocols protocols[] = {
    { "echo-protocol", callback_echo, sizeof(struct per_session_data__echo), MAX_PAYLOAD_SIZE, 0, NULL, 0 },
    { NULL, NULL, 0, 0, 0, NULL, 0 } /* 结束标志 */
};

int main(void) {
    struct lws_context_creation_info info;
    struct lws_context* context;
    int n = 0;

    signal(SIGINT, sigint_handler);
    memset(&info, 0, sizeof(info));

    info.port = 8081;                    // 监听端口
    info.protocols = protocols;          // 绑定的协议
    info.gid = -1;
    info.uid = -1;

    context = lws_create_context(&info);
    if (!context) {
        lwsl_err("lws init failed\n");
        return 1;
    }

    printf("Echo 服务器已启动,正在监听 8081 端口...\n");

    // 事件循环
    while (n >= 0 && !interrupted) {
        n = lws_service(context, 0);
    }

    lws_context_destroy(context);
    printf("服务器已退出\n");
    return 0;
}

客户端

#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>

static int interrupted = 0;
static int client_finished = 0;

// 客户端回调函数
static int callback_client_echo(struct lws* wsi, enum lws_callback_reasons reason,
    void* user, void* in, size_t len) {
    switch (reason) {
    case LWS_CALLBACK_CLIENT_ESTABLISHED:
        printf("客户端:连接服务器成功!\n");
        // 连接成功后,请求写权限以发送第一条数据
        lws_callback_on_writable(wsi);
        break;

    case LWS_CALLBACK_CLIENT_WRITEABLE: {
        const char* msg = "Hello Libwebsockets Echo!";
        size_t msg_len = strlen(msg);
        unsigned char buf[LWS_PRE + 128];

        // 必须在 LWS_PRE 之后填入数据
        memcpy(&buf[LWS_PRE], msg, msg_len);
        printf("客户端:发送消息 -> \"%s\"\n", msg);

        lws_write(wsi, &buf[LWS_PRE], msg_len, LWS_WRITE_TEXT);
        break;
    }

    case LWS_CALLBACK_CLIENT_RECEIVE:
        // 打印收到的回显数据
        printf("客户端:收到回显 <- \"%.*s\"\n", (int)len, (char*)in);
        client_finished = 1; // 标记完成,准备退出循环
        interrupted = 1;
        break;

    case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
        lwsl_err("客户端:连接失败\n");
        interrupted = 1;
        break;

    default:
        break;
    }
    return 0;
}

static struct lws_protocols protocols[] = {
    { "echo-protocol", callback_client_echo, 0, 1024, 0, NULL, 0 },
    { NULL, NULL, 0, 0, 0, NULL, 0 }
};

int main(void) {
    struct lws_context_creation_info info;
    struct lws_client_connect_info i;
    struct lws_context* context;
    int n = 0;

    memset(&info, 0, sizeof(info));
    info.port = CONTEXT_PORT_NO_LISTEN; // 客户端不需要监听端口
    info.protocols = protocols;

    context = lws_create_context(&info);
    if (!context) {
        lwsl_err("lws context init failed\n");
        return 1;
    }

    // 配置连接参数
    memset(&i, 0, sizeof(i));
    i.context = context;
    i.address = "127.0.0.1";           // 服务器 IP
    i.port = 8081;                     // 服务器端口
    i.path = "/";
    i.host = i.address;
    i.origin = i.address;
    i.protocol = "echo-protocol";      // 必须与服务器的协议名一致
    i.ssl_connection = 0;              // 0 表示不使用 SSL (ws://)

    if (!lws_client_connect_via_info(&i)) {
        lwsl_err("Client connection failed\n");
        lws_context_destroy(context);
        return 1;
    }

    // 事件循环
    while (n >= 0 && !interrupted) {
        n = lws_service(context, 0);
    }

    lws_context_destroy(context);
    return 0;
}

调试结果

先启动服务端,再启动客户端

再启动客户端

可见我们已经实现了WebSocket Server和 WebSocket Client。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐