C语言如何实现WebSocket通信?

99ANYc3cd6
预计阅读时长 32 分钟
位置: 首页 C语言 正文

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它使得客户端和服务器之间可以实时、高效地交换数据,在 C 语言中实现 WebSocket,主要有两种途径:

websocket c语言
(图片来源网络,侵删)
  1. 使用成熟的库:这是最推荐、最简单的方法,我们不需要从头实现复杂的 WebSocket 协议细节(如握手、帧解析、掩码处理等)。
  2. 从头实现:仅用于学习目的,不推荐用于生产环境。

使用成熟的 C 语言 WebSocket 库

对于 C/C++ 开发者,有几个非常优秀的 WebSocket 库可供选择,这里我们重点介绍最流行和功能最强大的两个:libwebsocketsAutobahn-WebSocket-C

libwebsockets (LWS)

libwebsockets 是一个功能极其丰富的、轻量级的、可扩展的 C 库,它既可以作为客户端,也可以作为服务器,并且支持 HTTP/S、WebSocket/S 等多种协议,它被广泛应用于嵌入式设备和服务器端。

特点:

  • 功能全面,性能优异。
  • 支持客户端和服务器模式。
  • 支持 SSL/TLS 加密。
  • 事件驱动的架构。
  • 跨平台(Linux, Windows, macOS 等)。

示例:创建一个简单的 WebSocket 服务器

第一步:安装 libwebsockets

  • 在 Ubuntu/Debian 上:
    sudo apt-get update
    sudo apt-get install libwebsockets-dev
  • 在 macOS 上 (使用 Homebrew):
    brew install libwebsockets
  • 从源码编译:
    git clone https://github.com/warmcat/libwebsockets
    cd libwebsockets
    mkdir build
    cd build
    cmake ..
    make
    sudo make install

第二步:编写 C 代码

websocket c语言
(图片来源网络,侵删)

下面是一个简单的 WebSocket 服务器,它会回显客户端发送的所有消息。

lws-server.c

#include <libwebsockets.h>
#include <stdio.h>
#include <string.h>
// 一个结构体,用于保存每个连接的上下文
struct session_data {
    int count;
};
// 回调函数,处理 WebSocket 事件
static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
    return 0; // 对于 HTTP 服务,我们通常不需要处理
}
// 处理 WebSocket 事件的回调函数
static int callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
    struct session_data *pdata = (struct session_data *)user;
    char buf[LWS_PRE + 512];
    int n;
    switch (reason) {
        case LWS_CALLBACK_ESTABLISHED:
            // 客户端连接成功
            printf("Client connected!\n");
            pdata->count = 0;
            break;
        case LWS_CALLBACK_RECEIVE:
            // 接收到客户端数据
            printf("Received %d bytes: %s\n", (int)len, (char *)in);
            // 将接收到的数据回显给客户端
            n = snprintf(buf + LWS_PRE, sizeof(buf) - LWS_PRE, "Server received: %s", (char *)in);
            // 发送数据给客户端
            lws_write(wsi, buf + LWS_PRE, n, LWS_WRITE_TEXT);
            break;
        case LWS_CALLBACK_CLOSED:
            // 客户端连接关闭
            printf("Client disconnected.\n");
            break;
        default:
            break;
    }
    return 0;
}
// 协议列表,定义了服务器支持的协议和对应的回调函数
static const struct lws_protocols protocols[] = {
    // "dumb-increment-protocol" 是我们自定义的协议名
    // callback_dumb_increment 是对应的回调函数
    // sizeof(struct session_data) 是为每个连接分配的用户数据大小
    { "dumb-increment-protocol", callback_dumb_increment, sizeof(struct session_data) },
    { NULL, NULL, 0 } // 结束标记
};
int main(void) {
    struct lws_context *context;
    struct lws_context_creation_info info = {0};
    // 初始化上下文信息
    info.port = 8080; // 监听端口
    info.interfaces = NULL; // 监听所有接口
    info.protocols = protocols;
    info.options = LWS_SERVER_OPTION_DO_SSL_AUTHORITY_ON_ACCEPT; // 如果需要SSL,可以添加
    // 创建 WebSocket 上下文
    context = lws_create_context(&info);
    if (!context) {
        fprintf(stderr, "Failed to create context\n");
        return 1;
    }
    printf("WebSocket server started on port 8080...\n");
    // 主循环,处理事件
    while (1) {
        lws_service(context, 50); // 50ms 超时
    }
    // 清理资源
    lws_context_destroy(context);
    return 0;
}

第三步:编译和运行

你需要链接 libwebsockets 库。

websocket c语言
(图片来源网络,侵删)
gcc lws-server.c -o lws-server -lwebsockets

运行服务器:

./lws-server

你的 WebSocket 服务器就在 8080 端口上运行了,等待客户端连接。


Autobahn-WebSocket-C (官方测试客户端)

这个库是 WebSocket 协议标准(RFC 6455)的官方参考实现之一,它主要用于客户端,并且可以用来测试你的 WebSocket 服务器是否符合标准,它也支持服务器模式,但通常 libwebsockets 在服务器端更常用。

特点:

  • 协议权威:由 WebSocket 标准的主要贡献者编写。
  • 强大的测试工具:内置了大量的测试用例,可以验证服务器实现的合规性。
  • 纯 C 语言实现,非常轻量。

示例:创建一个简单的 WebSocket 客户端

第一步:安装 Autobahn

# 克隆仓库
git clone https://github.com/c-ares/c-ares.git
git clone https://github.com/websockets/ws.git
git clone https://github.com/websockets/autobahn-testsuite.git
# 编译 c-ares (ws 依赖)
cd c-ares
./buildconf && ./configure && make
sudo make install
# 编译 ws
cd ../ws
./buildconf && ./configure && make
sudo make install
# Autobahn 客户端程序在 autobahn-testsuite 的 examples 目录下
cd ../autobahn-testsuite/examples

第二步:编写 C 代码

awc-client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <websockets.h>
// 回调函数,处理服务器事件
void on_event(struct ws *ws, enum ws_event event, void *payload, size_t len) {
    switch (event) {
        case WS_EVENT_OPEN:
            printf("Connection opened!\n");
            // 连接成功后,发送一条消息
            ws_send(ws, "Hello, Server!", 13, WS_TEXT_FRAME);
            break;
        case WS_EVENT_MESSAGE:
            // 接收到服务器消息
            printf("Received message: %.*s\n", (int)len, (char *)payload);
            break;
        case WS_EVENT_CLOSE:
            printf("Connection closed with code: %d\n", (int)(intptr_t)payload);
            break;
        case WS_ERROR:
            printf("An error occurred.\n");
            break;
    }
}
int main(void) {
    struct ws ws;
    // 创建 WebSocket 客户端
    // 参数:ws结构体, URL, 子协议, 用户数据, 回调函数
    ws_create(&ws, "ws://localhost:8080", NULL, NULL, on_event);
    printf("Connecting to ws://localhost:8080...\n");
    // 连接并开始运行
    ws_connect(&ws);
    // 主循环,保持连接并处理事件
    while (ws_is_alive(&ws)) {
        ws_service(&ws, 50); // 50ms 超时
    }
    // 清理
    ws_destroy(&ws);
    return 0;
}

第三步:编译和运行

你需要链接 websockets 库。

# 假设你在 autobahn-testsuite/examples 目录下
gcc awc-client.c -o awc-client -lwebsockets -lws -lc-ares

运行客户端(请确保上面的服务器正在运行):

./awc-client

你将看到客户端连接成功,发送消息,并打印出服务器回显的内容。


从头实现 WebSocket (仅用于学习)

如果你想深入理解 WebSocket 的工作原理,可以尝试自己实现一个简单的版本,这主要涉及两个部分:

  1. 握手:客户端发送一个 HTTP 请求,带有 Upgrade: websocketConnection: Upgrade 头,服务器响应一个 101 Switching Protocols 状态码。
  2. 数据帧:后续的数据不再是 HTTP 格式,而是 WebSocket 定义的帧格式,客户端发送给服务器的帧需要掩码,而服务器发送给客户端的帧则不需要。

握手过程简述

  • 客户端请求:

    GET /chat HTTP/1.1
    Host: example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Sec-WebSocket-Version: 13
  • 服务器响应: 服务器需要计算 Sec-WebSocket-Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 的 SHA-1 哈希,并进行 Base64 编码,然后放在 Sec-WebSocket-Accept 头中。

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

数据帧解析简述

一个 WebSocket 帧的结构如下:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
  • FIN: 1位,表示是否是消息的最后一帧。
  • Opcode: 4位,表示帧类型(如 0x1 文本帧, 0x2 二进制帧, 0x8 关闭帧)。
  • MASK: 1位,表示是否对数据进行掩码。客户端发送的此位必须为1
  • Payload len: 7/16/64位,数据长度。
  • Masking-key: 32位,MASK=1,则存在,客户端用它来加密数据,服务器用它来解密。

自己实现的挑战

  • 复杂性:处理边界情况(如分片消息、大消息、错误处理)非常复杂。
  • 性能:自己实现的解析器很难做到高性能。
  • 安全性:容易引入安全漏洞,特别是掩码处理。

除非你有特殊需求或是在做学术研究,否则强烈建议使用成熟的库


总结与对比

特性 libwebsockets Autobahn-WebSocket-C 自己实现
推荐度 ⭐⭐⭐⭐⭐ (生产首选) ⭐⭐⭐⭐ (测试/客户端) ⭐ (学习)
易用性 中等 极低
功能 非常全面 (服务器/客户端, SSL, HTTP) 强大 (测试套件, 客户端) 需要自己实现所有功能
性能 优秀 优秀 取决于实现
学习曲线 极高
适用场景 C/C++ 服务器、嵌入式设备、高性能应用 测试 WebSocket 服务器、简单的客户端应用 学习 WebSocket 协议 internals

对于绝大多数 C 语言 WebSocket 开发任务,直接使用 libwebsockets 是最佳选择,它稳定、功能强大、文档和社区支持都很好,如果你只是想测试你的服务器,Autobahn-WebSocket-C 的客户端工具是完美的选择,除非你的目标是深入理解协议本身,否则不要尝试自己从头实现。

-- 展开阅读全文 --
头像
dede 文章跳转网址
« 上一篇 今天
C语言中断处理如何高效实现与调试?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]