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

(图片来源网络,侵删)
本教程将分为以下几个部分:
- 核心概念简介:WebSocket 是什么,为什么在 C 中使用它。
- 环境准备:安装 libwebsockets 库。
- 完整实例:创建一个简单的 C WebSocket 服务器。
- 代码详解:逐行解释服务器的关键代码。
- 如何测试:使用浏览器和客户端工具进行测试。
- 进阶方向:客户端实现、多线程等。
核心概念简介
- 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。

(图片来源网络,侵删)
在 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 服务器,它会接收客户端发来的任何消息,并将相同的消息(回显)发送回去。

(图片来源网络,侵删)
项目结构
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;
}
代码详解
-
protocols数组:- 这是
libwebsockets的核心,它定义了服务器支持的所有协议(包括 HTTP 和 WebSocket)。 - 每个协议都有一个名称(如
"my-protocol")和一个对应的回调函数(callback_websocket)。 - 当客户端连接并指定协议(
ws://localhost:9001/my-protocol)时,LWS 会调用相应的回调函数。
- 这是
-
callback_websocket函数:- 这个函数是事件驱动的。
reason参数指明了当前发生的事件类型。 LWS_CALLBACK_ESTABLISHED: 客户端成功连接后触发,通常在这里做一些初始化工作。LWS_CALLBACK_RECEIVE: 收到客户端数据时触发。in参数指向接收到的数据,len是数据长度,我们在这里调用了lws_write将数据原样返回。LWS_CALLBACK_CLOSED: 客户端断开连接时触发,通常在这里进行清理工作。
- 这个函数是事件驱动的。
-
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 文件。
- 打开浏览器开发者工具的 "Console" (控制台) 和 "Network" (网络) 标签页。
- 在页面的输入框中输入任何文本,"Hello C Server!"。
- 点击 "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_filepath和ssl_private_key_filepath,并将 URL 从ws://改为wss://即可轻松启用安全连接。 - JSON 数据: 在实际应用中,通常使用 JSON 格式传输结构化数据,你需要一个 C 语言的 JSON 库(如
cJSON)来解析和构建 JSON 消息。
