cocoon/websocket.h
xfy911 035e4bebb4 feat(websocket): 实现 WebSocket 广播与频道路由系统
新增功能:
- ws_registry_add/remove: 连接注册/注销,自动管理活跃连接链表
- ws_broadcast(): 向所有活跃连接广播文本消息
- ws_broadcast_to_path(): 按握手路径(频道)定向广播
- ws_connection_count(): 获取当前 WebSocket 连接数
- ws_handle_connection() 新增 path 参数,用于频道标识

实现细节:
- 全局链表 + pthread_mutex 保证多线程安全
- 原子计数器统计连接数
- 连接建立时自动注册,关闭/异常时自动注销
- 默认行为保持 echo 不变

编译零警告,61 项集成测试 + 127 个单元测试全部通过。
2026-06-05 18:12:46 +08:00

156 lines
3.8 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef WEBSOCKET_H
#define WEBSOCKET_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
/**
* @file websocket.h
* @brief WebSocket 协议实现
*
* 支持 RFC 6455 WebSocket 握手、帧解析与编码。
* 服务器端实现不发送掩码mask=0接收客户端掩码帧。
*/
/**
* @brief WebSocket 操作码
*/
typedef enum {
WS_OP_CONT = 0x0, /**< 继续帧 */
WS_OP_TEXT = 0x1, /**< 文本帧 */
WS_OP_BINARY = 0x2, /**< 二进制帧 */
WS_OP_CLOSE = 0x8, /**< 关闭帧 */
WS_OP_PING = 0x9, /**< Ping 帧 */
WS_OP_PONG = 0xA /**< Pong 帧 */
} ws_opcode_t;
/**
* @brief WebSocket 帧结构
*/
typedef struct {
uint8_t opcode; /**< 操作码 */
bool fin; /**< 是否为最后一帧 */
bool masked; /**< 是否掩码 */
uint64_t payload_len;/**< 负载长度 */
uint8_t mask_key[4]; /**< 掩码密钥(仅客户端发送时有效) */
uint8_t *payload; /**< 负载数据(已解掩码) */
} ws_frame_t;
/**
* @brief 执行 WebSocket 握手响应
*
* 根据 RFC 6455对 Sec-WebSocket-Key 计算 SHA1 + Base64 响应。
*
* @param fd 客户端 socket
* @param key 客户端发来的 Sec-WebSocket-Key
* @return 0 成功,-1 失败
*/
int ws_handshake(int fd, const char *key);
/**
* @brief 解析单个 WebSocket 帧
*
* 从数据流中解析一个完整帧。如果数据不完整,返回 -1 且不修改 frame。
*
* @param data 输入数据
* @param len 数据长度
* @param frame 输出帧结构(调用者需初始化)
* @param consumed 输出:消耗的字节数
* @return 0 成功,-1 数据不完整,-2 格式错误
*/
int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_t *frame, size_t *consumed);
/**
* @brief 释放帧占用的负载内存
*
* @param frame 帧指针
*/
void ws_frame_free(ws_frame_t *frame);
/**
* @brief 发送 WebSocket 帧
*
* @param fd 客户端 socket
* @param opcode 操作码
* @param payload 负载数据
* @param len 负载长度
* @return 0 成功,-1 失败
*/
int ws_send_frame(int fd, uint8_t opcode, const uint8_t *payload, size_t len);
/**
* @brief 发送文本帧
*
* @param fd 客户端 socket
* @param text 文本内容UTF-8
* @return 0 成功,-1 失败
*/
int ws_send_text(int fd, const char *text);
/**
* @brief 发送关闭帧
*
* @param fd 客户端 socket
* @param code 关闭码(如 1000
* @param reason 关闭原因(可为 NULL
* @return 0 成功,-1 失败
*/
int ws_send_close(int fd, uint16_t code, const char *reason);
/**
* @brief 发送 Ping 帧
*
* @param fd 客户端 socket
* @return 0 成功,-1 失败
*/
int ws_send_ping(int fd);
/**
* @brief 发送 Pong 帧
*
* @param fd 客户端 socket
* @param payload Ping 的负载(可为 NULL
* @param len 负载长度
* @return 0 成功,-1 失败
*/
int ws_send_pong(int fd, const uint8_t *payload, size_t len);
/**
* @brief 向所有活跃 WebSocket 连接广播文本消息
*
* @param text 文本内容
* @return 成功发送的连接数
*/
int ws_broadcast(const char *text);
/**
* @brief 向指定频道的所有连接发送文本消息
*
* @param path 频道路径
* @param text 文本内容
* @return 成功发送的连接数
*/
int ws_broadcast_to_path(const char *path, const char *text);
/**
* @brief 获取当前 WebSocket 连接数
*
* @return 活跃连接数
*/
int ws_connection_count(void);
/**
* @brief 处理 WebSocket 连接(主循环)
*
* 进入 WebSocket 帧循环,处理文本/二进制/ping/pong/close。
* 连接自动注册到全局广播表,退出时注销。
*
* @param fd 客户端 socket
* @param timeout_ms 超时毫秒0 表示默认)
* @param path WebSocket 握手路径(频道标识,可为 NULL
*/
void ws_handle_connection(int fd, uint32_t timeout_ms, const char *path);
#endif /* WEBSOCKET_H */