WebSocket 使用指南
概述
UVHTTP 提供了完整的 WebSocket 支持,允许你轻松实现实时双向通信。WebSocket 协议建立在 HTTP 协议之上,通过一个握手阶段从 HTTP 升级到 WebSocket。
WebSocket 工作原理
握手过程
客户端发起 HTTP 请求,包含特殊的头部:
Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: <随机字符串> Sec-WebSocket-Version: 13服务器响应升级请求:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: <计算后的字符串>连接建立后,双方可以双向发送消息
基本使用
创建 WebSocket 服务器
c
#include "uvhttp.h"
// WebSocket 连接建立回调
int on_connect(uvhttp_ws_connection_t* ws_conn) {
printf("WebSocket 连接建立\n");
return 0;
}
// WebSocket 消息接收回调
int on_message(uvhttp_ws_connection_t* ws_conn,
const char* data,
size_t len,
int opcode) {
(void)ws_conn;
printf("收到消息: %.*s\n", (int)len, data);
// 回显消息
uvhttp_server_ws_send(ws_conn, data, len);
return 0;
}
// WebSocket 连接关闭回调
int on_close(uvhttp_ws_connection_t* ws_conn) {
(void)ws_conn;
printf("WebSocket 连接关闭\n");
return 0;
}
// WebSocket 错误回调
int on_error(uvhttp_ws_connection_t* ws_conn,
int error_code,
const char* error_msg) {
(void)ws_conn;
printf("WebSocket 错误: %d - %s\n", error_code, error_msg);
return 0;
}
int main() {
uv_loop_t* loop = uv_default_loop();
uvhttp_server_t* server = NULL;
uvhttp_server_new(loop, &server);
uvhttp_router_t* router = NULL;
uvhttp_router_new(&router);
uvhttp_server_set_router(server, router);
// 注册 WebSocket 处理器
uvhttp_ws_handler_t ws_handler = {
.on_connect = on_connect,
.on_message = on_message,
.on_close = on_close,
.on_error = on_error,
.user_data = NULL
};
uvhttp_server_register_ws_handler(server, "/ws", &ws_handler);
// 启动服务器
uvhttp_server_listen(server, "0.0.0.0", 8080);
printf("WebSocket 服务器运行在 http://localhost:8080/ws\n");
uv_run(loop, UV_RUN_DEFAULT);
// 清理资源
uvhttp_server_free(server);
return 0;
}WebSocket 消息类型
WebSocket 支持多种消息类型(opcode):
0x0: Continuation Frame(连续帧)0x1: Text Frame(文本帧)0x2: Binary Frame(二进制帧)0x8: Close Frame(关闭帧)0x9: Ping Frame(心跳帧)0xA: Pong Frame(心跳响应帧)
发送不同类型的消息
c
// 发送文本消息
const char* text = "Hello WebSocket";
uvhttp_server_ws_send(ws_conn, text, strlen(text));
// 发送二进制消息
const char* binary_data = "\x01\x02\x03\x04";
uvhttp_server_ws_send(ws_conn, binary_data, 4);
// 发送 Ping
uvhttp_ws_send_ping(NULL, ws_conn, (const uint8_t*)"ping", 4);
// 发送 Close
uvhttp_server_ws_close(ws_conn, 1000, "正常关闭");应用层认证
由于认证功能应该在应用层实现,你可以在 on_connect 回调中接受或拒绝连接。注意 on_connect 只接收连接对象——原始 HTTP 请求头(如 Authorization)在此不可访问。基于请求头的认证必须在 WebSocket 升级完成前、于 HTTP 层进行:
c
int on_connect(uvhttp_ws_connection_t* ws_conn) {
// on_connect 只接收 WebSocket 连接对象;原始 HTTP 请求头
// (如 Authorization)在此不可访问。请在升级前的 HTTP 处理器/
// 中间件中校验请求头,然后在此接受或拒绝连接。
printf("WebSocket 连接建立\n");
return 0; // 接受连接
}最佳实践
1. 连接管理
c
// 维护活跃连接列表
static uvhttp_ws_connection_t* g_connections[MAX_CONNECTIONS];
static int g_connection_count = 0;
int on_connect(uvhttp_ws_connection_t* ws_conn) {
if (g_connection_count < MAX_CONNECTIONS) {
g_connections[g_connection_count++] = ws_conn;
printf("连接 %d 已建立\n", g_connection_count);
} else {
printf("连接数已达上限\n");
return -1;
}
return 0;
}
int on_close(uvhttp_ws_connection_t* ws_conn) {
// 从连接列表中移除
for (int i = 0; i < g_connection_count; i++) {
if (g_connections[i] == ws_conn) {
// 移动最后一个元素到当前位置
g_connections[i] = g_connections[--g_connection_count];
break;
}
}
printf("连接关闭,剩余 %d 个连接\n", g_connection_count);
return 0;
}
// 广播消息到所有连接
void broadcast_message(const char* message, size_t len) {
for (int i = 0; i < g_connection_count; i++) {
uvhttp_server_ws_send(g_connections[i], message, len);
}
}2. 心跳检测
c
// 定期发送 Ping
void heartbeat_timer_callback(uv_timer_t* handle) {
const char* ping_msg = "ping";
for (int i = 0; i < g_connection_count; i++) {
uvhttp_ws_send_ping(NULL, g_connections[i], (const uint8_t*)ping_msg,
strlen(ping_msg));
}
// 重新设置定时器
uv_timer_start(handle, heartbeat_timer_callback, 30000);
}
int main() {
// ... 服务器初始化代码 ...
// 创建心跳定时器
uv_timer_t heartbeat_timer;
uv_timer_init(loop, &heartbeat_timer);
uv_timer_start(&heartbeat_timer, heartbeat_timer_callback, 30000); // 30秒
// ... 启动服务器 ...
}3. 消息大小限制
c
#define MAX_MESSAGE_SIZE (1024 * 1024) // 1MB
int on_message(uvhttp_ws_connection_t* ws_conn,
const char* data,
size_t len,
int opcode) {
(void)ws_conn;
(void)opcode;
if (len > MAX_MESSAGE_SIZE) {
printf("消息过大: %zu 字节\n", len);
uvhttp_server_ws_close(ws_conn, 1009, "消息过大");
return -1;
}
// 处理消息
process_message(data, len);
return 0;
}4. 错误处理
c
int on_error(uvhttp_ws_connection_t* ws_conn,
int error_code,
const char* error_msg) {
(void)ws_conn;
printf("WebSocket 错误: %d - %s\n", error_code, error_msg);
// 根据错误类型处理
switch (error_code) {
case 1000: // 正常关闭
printf("客户端正常关闭\n");
break;
case 1002: // 协议错误
printf("协议错误,关闭连接\n");
break;
case 1003: // 不支持的数据类型
printf("不支持的数据类型\n");
break;
default:
printf("未知错误\n");
}
return 0;
}客户端示例
JavaScript 客户端
javascript
const ws = new WebSocket('ws://localhost:8080/ws');
ws.onopen = function() {
console.log('WebSocket 连接已建立');
ws.send('Hello Server');
};
ws.onmessage = function(event) {
console.log('收到消息:', event.data);
};
ws.onerror = function(error) {
console.error('WebSocket 错误:', error);
};
ws.onclose = function(event) {
console.log('WebSocket 连接已关闭:', event.code, event.reason);
};Python 客户端
python
import asyncio
import websockets
async def websocket_client():
uri = "ws://localhost:8080/ws"
async with websockets.connect(uri) as websocket:
print("WebSocket 连接已建立")
# 发送消息
await websocket.send("Hello Server")
# 接收消息
async for message in websocket:
print(f"收到消息: {message}")
asyncio.run(websocket_client())curl 测试
bash
# 使用 websocat 测试 WebSocket 连接
websocat ws://localhost:8080/ws
# 发送消息
Hello Server
# 接收回显
Hello Server常见问题
Q: 如何限制连接数?
A: 在 on_connect 回调中维护一个连接列表,当达到上限时返回 -1 拒绝连接。
Q: 如何实现房间/频道功能?
A: 在连接时让客户端发送加入房间的消息,服务器维护房间到连接的映射关系。
Q: 如何处理大消息?
A: 在 on_message 回调中检查消息大小,超过限制时关闭连接。
Q: 如何实现消息压缩?
A: 在发送前使用压缩算法(如 zlib)压缩数据,客户端接收后解压。
Q: 如何处理连接断开重连?
A: 客户端实现自动重连逻辑,服务器端维护连接状态。