C语言WebSocket API实例如何实现?

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

在 C 语言生态中,直接从头实现 WebSocket 协议(处理帧解析、掩码处理等)是非常复杂和繁琐的。强烈推荐使用成熟的第三方库来简化开发,目前最流行、功能最强大的 C 语言 WebSocket 库是 libwebsockets

c语言websocketapi实例
(图片来源网络,侵删)

本教程将分为以下几个部分:

  1. 核心概念简介:WebSocket 是什么,为什么在 C 中使用它。
  2. 环境准备:安装 libwebsockets 库。
  3. 完整实例:创建一个简单的 C WebSocket 服务器。
  4. 代码详解:逐行解释服务器的关键代码。
  5. 如何测试:使用浏览器和客户端工具进行测试。
  6. 进阶方向:客户端实现、多线程等。

核心概念简介

  • WebSocket: 一种在单个 TCP 连接上进行全双工通信的协议,它允许服务器主动向客户端推送数据,实现了真正的实时通信,非常适合聊天室、实时数据推送、在线游戏等场景。
  • 为什么在 C 中使用?: C 语言以其高性能、低开销和资源占用少而闻名,对于需要处理大量并发连接、对性能要求极高的后端服务(如物联网网关、高频交易系统、游戏服务器),C 语言 + WebSocket 是一个非常强大的组合。

环境准备

我们将使用 libwebsockets (LWS) 库,在 Linux/macOS 上,最简单的安装方式是通过包管理器。

安装 libwebsockets

在 Ubuntu/Debian 上:

sudo apt-get update
sudo apt-get install libwebsockets-dev libssl-dev

libssl-dev 是必需的,因为 WebSocket 的 wss:// (安全 WebSocket) 协议依赖 OpenSSL。

c语言websocketapi实例
(图片来源网络,侵删)

在 macOS 上 (使用 Homebrew):

brew install libwebsockets

从源码编译安装 (可选,获取最新版):

如果需要最新特性或修复,可以从 GitHub 克隆并编译。

git clone https://github.com/warmcat/libwebsockets.git
cd libwebsockets
mkdir build
cd build
cmake ..
make
sudo make install

完整实例:一个简单的回显服务器

我们将创建一个 WebSocket 服务器,它会接收客户端发来的任何消息,并将相同的消息(回显)发送回去。

c语言websocketapi实例
(图片来源网络,侵删)

项目结构

websocket_server/
├── CMakeLists.txt
└── main.c

CMakeLists.txt

为了方便编译,我们使用 CMake。

cmake_minimum_required(VERSION 3.10)
project(websocket_server C)
# 查找 libwebsockets 包
find_package(libwebsockets REQUIRED)
# 添加可执行文件
add_executable(server main.c)
# 链接 libwebsockets 库
target_link_libraries(server lws)

main.c

这是服务器的核心代码。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// libwebsockets 头文件
#include <libwebsockets.h>
// 定义服务名称和端口
#define SERVICE_NAME "websocket-server"
#define PORT 9001
// WebSocket 协议结构体,用于定义回调函数
static struct lws_protocols protocols[] = {
    {
        // 协议名称,客户端在连接时需要指定
        "http-only",
        // 回调函数指针,处理 WebSocket 事件
        callback_http,
        // 接收缓冲区大小
        0,
        // 用户数据大小
        0,
    },
    {
        // 这是我们的 WebSocket 协议
        "my-protocol",
        // 回调函数
        callback_websocket,
        // 接收缓冲区大小
        0,
        // 用户数据大小
        0,
    },
    { NULL, NULL, 0, 0 } /* terminator */
};
// 全局变量,指向 libwebsockets 上下文
static struct lws_context *context;
// HTTP 服务回调函数
// 对于标准的 HTTP 请求(比如升级前的握手),这个回调会被调用
static int callback_http(struct lws *wsi, enum lws_callback_reasons reason,
                         void *user, void *in, size_t len) {
    // 对于 HTTP 服务,我们通常只处理 LWS_CALLBACK_HTTP
    // 其他事件可以交给默认处理或忽略
    switch (reason) {
        case LWS_CALLBACK_HTTP:
            // 这里可以返回一个简单的 HTML 页面,用于测试
            // 返回一个包含 WebSocket 客户端脚本的 HTML
            {
                const char *html = "<html><body><h1>Hello from C WebSocket Server!</h1></body></html>";
                int len = strlen(html);
                lws_write(wsi, (unsigned char *)html, len, LWS_WRITE_HTTP);
                lws_close_reason(wsi, (enum lws_close_status)LWS_CLOSE_STATUS_NORMAL, NULL, 0);
            }
            return 1; // 表示已处理
        default:
            return 0; // 表示未处理,使用默认行为
    }
}
// WebSocket 服务回调函数
// 这是处理 WebSocket 通信的核心
static int callback_websocket(struct lws *wsi, enum lws_callback_reasons reason,
                              void *user, void *in, size_t len) {
    switch (reason) {
        case LWS_CALLBACK_ESTABLISHED:
            // 当新的 WebSocket 连接建立时触发
            printf("Client connected!\n");
            // 可以在这里发送欢迎消息
            // lws_write(wsi, ...);
            break;
        case LWS_CALLBACK_RECEIVE:
            // 当收到 WebSocket 数据帧时触发
            printf("Received message: %s\n", (char *)in);
            // 将收到的消息回显给客户端
            lws_write(wsi, (unsigned char *)in, len, LWS_WRITE_TEXT);
            break;
        case LWS_CALLBACK_CLOSED:
            // 当 WebSocket 连接关闭时触发
            printf("Client disconnected.\n");
            break;
        default:
            // 对于其他未处理的回调,返回 0
            return 0;
    }
    return 0;
}
int main(int argc, char **argv) {
    // lws 相关的参数
    struct lws_context_creation_info info;
    const char *interface = NULL; // 监听所有网络接口
    int port = PORT;
    int opts = LWS_SERVER_OPTION_DO_SSL_AUTHORITY_ON_ACCEPT;
    // 初始化上下文创建信息
    memset(&info, 0, sizeof info);
    info.port = port;
    info.iface = interface;
    info.protocols = protocols;
    info.extensions = NULL;
    info.ssl_cert_filepath = NULL; // 不使用 SSL
    info.ssl_private_key_filepath = NULL;
    info.gid = -1;
    info.uid = -1;
    options |= LWS_SERVER_OPTION_VALIDATE_UTF8;
    // 创建 libwebsockets 上下文
    // 上下文是服务器的主要句柄
    context = lws_create_context(&info);
    if (!context) {
        fprintf(stderr, "libwebsockets init failed\n");
        return 1;
    }
    printf("WebSocket server started on port %d. Waiting for connections...\n", port);
    // 主事件循环
    // lws_service 会阻塞,等待并处理 I/O 事件
    while (1) {
        lws_service(context, 0); // 0 表示无限期阻塞
    }
    // 清理资源(上面的 while(1) 永远不会退出到这里)
    lws_context_destroy(context);
    return 0;
}

代码详解

  1. protocols 数组:

    • 这是 libwebsockets 的核心,它定义了服务器支持的所有协议(包括 HTTP 和 WebSocket)。
    • 每个协议都有一个名称(如 "my-protocol")和一个对应的回调函数(callback_websocket)。
    • 当客户端连接并指定协议(ws://localhost:9001/my-protocol)时,LWS 会调用相应的回调函数。
  2. callback_websocket 函数:

    • 这个函数是事件驱动的。reason 参数指明了当前发生的事件类型。
    • LWS_CALLBACK_ESTABLISHED: 客户端成功连接后触发,通常在这里做一些初始化工作。
    • LWS_CALLBACK_RECEIVE: 收到客户端数据时触发。in 参数指向接收到的数据,len 是数据长度,我们在这里调用了 lws_write 将数据原样返回。
    • LWS_CALLBACK_CLOSED: 客户端断开连接时触发,通常在这里进行清理工作。
  3. main 函数:

    • lws_context_creation_info: 结构体,用于配置和创建 LWS 上下文。
    • lws_create_context(&info): 这是服务器的“心脏”,它根据配置初始化所有必要的资源,创建监听套接字等。
    • while(1) 循环: 这是服务器的主循环。lws_service(context, 0) 会一直阻塞,直到有网络事件(新连接、数据到达、连接关闭等)发生,当事件发生时,它会调用相应协议的回调函数来处理。
    • lws_context_destroy(context): 理论上,在服务器关闭时调用此函数来释放所有资源,但在我们的例子中,服务器是永久运行的,所以这行代码实际上不会被执行。

如何测试

步骤 1: 编译服务器

websocket_server 目录下:

mkdir build
cd build
cmake ..
make

编译成功后,会在 build 目录下生成一个名为 server 的可执行文件。

步骤 2: 运行服务器

./server

你应该会看到输出:

WebSocket server started on port 9001. Waiting for connections...

步骤 3: 编写一个简单的 HTML/JS 客户端来测试

创建一个 index.html 文件,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">C WebSocket Client Test</title>
    <style>
        body { font-family: sans-serif; }
        #log { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; margin-bottom: 10px; }
        #input { width: 80%; padding: 5px; }
        #send { width: 18%; padding: 5px; }
    </style>
</head>
<body>
    <h1>C WebSocket Client Test</h1>
    <div id="log"></div>
    <input type="text" id="input" placeholder="Type a message and press Enter">
    <button id="send">Send</button>
    <script>
        const log = document.getElementById('log');
        const input = document.getElementById('input');
        const sendButton = document.getElementById('send');
        // 连接到 WebSocket 服务器
        // 注意协议名 "my-protocol" 必须与服务器端定义的一致
        const socket = new WebSocket('ws://localhost:9001/my-protocol');
        socket.onopen = function(event) {
            log('Connected to server.');
        };
        socket.onmessage = function(event) {
            log('Received from server: ' + event.data);
        };
        socket.onclose = function(event) {
            log('Connection closed.');
        };
        socket.onerror = function(error) {
            log('WebSocket Error: ' + error);
        };
        function sendMessage() {
            const message = input.value;
            if (message) {
                log('Sending to server: ' + message);
                socket.send(message);
                input.value = '';
            }
        }
        sendButton.onclick = sendMessage;
        input.addEventListener('keyup', function(event) {
            if (event.key === 'Enter') {
                sendMessage();
            }
        });
    </script>
</body>
</html>

步骤 4: 运行客户端

直接在浏览器中打开 index.html 文件。

  1. 打开浏览器开发者工具的 "Console" (控制台) 和 "Network" (网络) 标签页。
  2. 在页面的输入框中输入任何文本,"Hello C Server!"。
  3. 点击 "Send" 按钮或按 Enter 键。

观察结果:

  • 浏览器控制台:
    • Connected to server.
    • Sending to server: Hello C Server!
    • Received from server: Hello C Server!
  • C 服务器终端:
    WebSocket server started on port 9001. Waiting for connections...
    Client connected!
    Received message: Hello C Server!
    Client disconnected.

    (当你刷新页面或关闭标签页时,会看到 Client disconnected.)


进阶方向

  • 创建 WebSocket 客户端: libwebsockets 同样可以用来创建客户端,你需要使用 lws_client_connect() 等函数来连接到服务器,并使用类似的回调机制来处理服务器的响应。
  • 多线程与并发: libwebsockets 本身是事件驱动的,单线程可以处理成千上万的并发连接,效率很高,但如果你的业务逻辑中有非常耗时的 CPU 密集型操作,为了避免阻塞主事件循环,你应该将这些操作放到一个单独的工作线程中处理。
  • SSL/TLS 支持: 只需在 lws_context_creation_info 中提供 ssl_cert_filepathssl_private_key_filepath,并将 URL 从 ws:// 改为 wss:// 即可轻松启用安全连接。
  • JSON 数据: 在实际应用中,通常使用 JSON 格式传输结构化数据,你需要一个 C 语言的 JSON 库(如 cJSON)来解析和构建 JSON 消息。
-- 展开阅读全文 --
头像
dede首页不置顶、列表页置顶,如何实现?
« 上一篇 昨天
C、C语言、E语言,哪个更值得学?
下一篇 » 昨天

相关文章

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

目录[+]