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

- 使用成熟的库:这是最推荐、最简单的方法,我们不需要从头实现复杂的 WebSocket 协议细节(如握手、帧解析、掩码处理等)。
- 从头实现:仅用于学习目的,不推荐用于生产环境。
使用成熟的 C 语言 WebSocket 库
对于 C/C++ 开发者,有几个非常优秀的 WebSocket 库可供选择,这里我们重点介绍最流行和功能最强大的两个:libwebsockets 和 Autobahn-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 服务器,它会回显客户端发送的所有消息。
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 库。

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 的工作原理,可以尝试自己实现一个简单的版本,这主要涉及两个部分:
- 握手:客户端发送一个 HTTP 请求,带有
Upgrade: websocket和Connection: Upgrade头,服务器响应一个101 Switching Protocols状态码。 - 数据帧:后续的数据不再是 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 的客户端工具是完美的选择,除非你的目标是深入理解协议本身,否则不要尝试自己从头实现。
