新增功能: - ws_registry_add/remove: 连接注册/注销,自动管理活跃连接链表 - ws_broadcast(): 向所有活跃连接广播文本消息 - ws_broadcast_to_path(): 按握手路径(频道)定向广播 - ws_connection_count(): 获取当前 WebSocket 连接数 - ws_handle_connection() 新增 path 参数,用于频道标识 实现细节: - 全局链表 + pthread_mutex 保证多线程安全 - 原子计数器统计连接数 - 连接建立时自动注册,关闭/异常时自动注销 - 默认行为保持 echo 不变 编译零警告,61 项集成测试 + 127 个单元测试全部通过。
156 lines
3.8 KiB
C
156 lines
3.8 KiB
C
#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 */
|