# ev **Repository Path**: ThinkingT/ev ## Basic Information - **Project Name**: ev - **Description**: nginx p2p - **Primary Language**: C - **License**: BSD-3-Clause-Clear - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-03-15 - **Last Updated**: 2026-04-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Nginx WebSocket长连接模块 基于Nginx的WebSocket长连接模块,支持客户端管理、消息推送和配置管理。 ## 目录 1. [功能概述](#功能概述) 2. [代码结构](#代码结构) 3. [编译安装](#编译安装) 4. [配置说明](#配置说明) 5. [协议规范](#协议规范) 6. [消息推送](#消息推送) 7. [客户端示例](#客户端示例) 8. [故障排查](#故障排查) --- ## 功能概述 ### 核心功能 | 功能 | 说明 | |------|------| | WebSocket协议 | 完整实现WebSocket协议,包括握手、帧处理和连接管理 | | 客户端管理 | 为每个客户端分配唯一ID,使用struct对象管理状态 | | 消息推送 | 通过Redis发布/订阅实现消息推送 | | 服务转发 | 支持HTTP/HTTPS/自定义协议转发 | | 心跳机制 | 自动心跳检测,超时断开连接 | ### 客户端状态管理 ```c struct ngx_ev_client_t { ngx_str_t id; // 客户端ID ngx_uint_t status; // 连接状态: 0=连接中, 1=已连接 ngx_uint_t login_status; // 登录状态: 0=未登录, 1=已登录 ngx_uint_t heartbeat_interval; // 心跳间隔(秒), 默认60 ngx_str_t token; // 认证令牌 }; ``` ### 消息类型 | 类型 | 方向 | 说明 | |------|------|------| | `auth` | 双向 | 认证/登录 | | `heartbeat` | 双向 | 心跳保活 | | `notify` | 双向 | 通知消息 | | `proxy` | 双向 | 转发消息 | | `execute` | 服务器→客户端 | 任务包 | | `result` | 客户端→服务器 | 任务结果 | | `error` | 服务器→客户端 | 错误信息 | --- ## 代码结构 ### 头文件 | 文件 | 说明 | |------|------| | `ngx_ev_struct.h` | 公共头文件,定义所有结构体 | | `ngx_ev_log_printf.h` | 日志头文件 | | `ngx_ev_mgr.h` | 客户端管理头文件 | | `ngx_ev_url.h` | URL调用头文件 | | `ngx_ev_util.h` | 工具头文件 | | `ngx_ev_heartbeat.h` | 心跳服务头文件 | | `ngx_ev_auth.h` | 认证服务头文件 | | `ngx_ev_proxy.h` | 转发服务头文件 | ### 源文件 | 文件 | 说明 | |------|------| | `ngx_ev_core_module.c` | 模块配置加载 | | `ngx_ev.c` | WebSocket处理、客户端ID管理 | | `ngx_ev_mq.c` | Redis消息队列处理 | | `ngx_ev_proxy.c` | 服务转发 | --- ## 编译安装 ### 1. 下载Nginx源码 ```bash wget http://nginx.org/download/nginx-1.20.2.tar.gz tar -zxvf nginx-1.20.2.tar.gz cd nginx-1.20.2 ``` ### 2. 编译模块 ```bash ./configure --add-module=/path/to/ev make make install ``` ### 3. 启动Nginx ```bash /usr/local/nginx/sbin/nginx ``` --- ## 配置说明 ### Nginx配置示例 ```nginx events { worker_connections 1024; } http { # WebSocket服务 server { listen 1935; location /ws { ev_websocket; # 认证服务URL ev_auth_url http://auth.example.com/auth; # 心跳服务URL ev_heartbeat_url http://heartbeat.example.com/heartbeat; # 代理转发URL ev_proxy_url http://proxy.example.com/proxy; # 长连接配置 ev_auth_keepalive_enable on; ev_proxy_keepalive_enable on; ev_heartbeat_keepalive_enable on; # Redis配置(用于消息推送) ev_push_ms_enabled on; ev_push_ms_server 127.0.0.1; ev_push_ms_port 6379; ev_push_ms_password your_password; ev_push_ms_channel ev:push; ev_push_ms_channel_index 0; } } } ``` ### 配置参数 #### 服务转发配置 | 参数 | 说明 | 默认值 | |------|------|--------| | `ev_auth_url` | 认证服务URL | - | | `ev_heartbeat_url` | 心跳服务URL | - | | `ev_proxy_url` | 代理服务URL | - | | `ev_auth_keepalive_enable` | 认证长连接 | off | | `ev_proxy_keepalive_enable` | 代理长连接 | off | | `ev_heartbeat_keepalive_enable` | 心跳长连接 | off | #### Redis消息推送配置 | 参数 | 说明 | 默认值 | |------|------|--------| | `ev_push_ms_enabled` | 启用Redis消息推送 | off | | `ev_push_ms_server` | Redis服务器地址 | 127.0.0.1 | | `ev_push_ms_port` | Redis端口 | 6379 | | `ev_push_ms_password` | Redis密码 | - | | `ev_push_ms_ssl` | 启用SSL连接 | off | | `ev_push_ms_channel` | 订阅的频道名称 | ev:push | | `ev_push_ms_channel_index` | Redis数据库索引 | 0 | --- ## 协议规范 ### 连接地址 - **HTTP**: `ws://localhost:1935/ws` - **HTTPS**: `wss://yourdomain.com/ws` ### 消息格式 所有消息使用JSON格式,通用字段: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | string | 条件 | 客户端ID,首次连接为空 | | `type` | string | 是 | 消息类型 | | `heartbeat` | int | 否 | 心跳间隔(秒) | | `data` | string | 否 | 消息内容 | | `ctx` | string | 否 | 上下文信息 | | `token` | string | 条件 | 认证令牌,登录后必填 | ### 1. 认证消息 (type: auth) **场景1:客户端无ID(首次连接)** 请求: ```json { "id": "", "heartbeat": 60, "data": "hello_world", "type": "auth", "ctx": "111111111", "token": "" } ``` 响应: ```json { "id": "123456", "heartbeat": 60, "data": "", "type": "auth", "ctx": "111111111", "token": "11111111111" } ``` **场景2:客户端有ID但未登录** 请求: ```json { "id": "123456", "heartbeat": 60, "data": "hello_world", "type": "auth", "ctx": "111111111", "token": "" } ``` 响应: ```json { "id": "123456", "heartbeat": 60, "data": "", "type": "auth", "ctx": "111111111", "token": "11111111111" } ``` **场景3:客户端已登录** 请求: ```json { "id": "123456", "heartbeat": 60, "data": "hello_world", "type": "auth", "ctx": "111111111", "token": "11111111111" } ``` ### 2. 心跳消息 (type: heartbeat) **说明**:客户端需定期发送心跳保持连接,建议间隔为配置间隔的一半。 请求: ```json { "id": "123456", "heartbeat": 60, "data": "", "type": "heartbeat", "ctx": "111111111", "token": "11111111111" } ``` 响应: ```json { "id": "123456", "heartbeat": 60, "data": "", "type": "heartbeat", "ctx": "111111111", "token": "11111111111" } ``` ### 3. 通知消息 (type: notify) **说明**:双向通知,客户端和服务器都可以发送。 客户端发送: ```json { "id": "123456", "heartbeat": 60, "data": "通知内容", "type": "notify", "ctx": "111111111", "token": "11111111111" } ``` ### 4. 转发消息 (type: proxy) **说明**:数据会转发到配置的`ev_proxy_url`。 请求: ```json { "id": "123456", "heartbeat": 60, "data": "请求数据", "type": "proxy", "ctx": "111111111", "token": "11111111111" } ``` 响应: ```json { "id": "123456", "heartbeat": 60, "data": "响应数据", "type": "proxy", "ctx": "111111111", "token": "11111111111" } ``` --- ## 消息推送 模块通过Redis发布/订阅机制实现消息推送,不再提供HTTP API接口。 ### Redis消息格式 向Redis频道(默认`ev:push`)发布消息,格式如下: ```json { "client_id": "client_1234567890_1", "opcode": "notify", "data": "消息内容" } ``` 字段说明: - `client_id` - 目标客户端ID,为空表示广播给所有客户端 - `opcode` - 消息类型:`notify`、`execute`、`error`等 - `data` - 消息内容 ### 推送示例 #### 向单个客户端推送 ```bash redis-cli PUBLISH ev:push '{"client_id":"client_1234567890_1","opcode":"notify","data":"这是一条测试消息"}' ``` #### 向所有客户端广播 ```bash redis-cli PUBLISH ev:push '{"client_id":"","opcode":"notify","data":"这是一条全局广播消息"}' ``` #### 发送执行任务 ```bash redis-cli PUBLISH ev:push '{"client_id":"client_1234567890_1","opcode":"execute","data":"task_data"}' ``` ### 使用Go发送消息 ```go package main import ( "context" "encoding/json" "github.com/redis/go-redis/v9" ) type PushMessage struct { ClientID string `json:"client_id"` Opcode string `json:"opcode"` Data string `json:"data"` } func main() { rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "your_password", DB: 0, }) msg := PushMessage{ ClientID: "client_1234567890_1", Opcode: "notify", Data: "Hello from Redis", } data, _ := json.Marshal(msg) rdb.Publish(context.Background(), "ev:push", data) } ``` --- ## 客户端示例 ### Go语言示例 ```go package main import ( "encoding/json" "log" "time" "github.com/gorilla/websocket" ) type Message struct { ID string `json:"id"` Type string `json:"type"` Heartbeat int `json:"heartbeat,omitempty"` Data string `json:"data,omitempty"` Ctx string `json:"ctx,omitempty"` Token string `json:"token,omitempty"` } func main() { // 连接WebSocket conn, _, err := websocket.DefaultDialer.Dial("ws://localhost:1935/ws", nil) if err != nil { log.Fatal(err) } defer conn.Close() var clientID, token string // 发送认证 auth := Message{Type: "auth", Heartbeat: 60, Data: "hello", Ctx: "test"} conn.WriteJSON(auth) // 接收消息 go func() { for { var msg Message if err := conn.ReadJSON(&msg); err != nil { log.Printf("读取错误: %v", err) return } switch msg.Type { case "auth": clientID = msg.ID token = msg.Token log.Printf("认证成功: %s", clientID) case "heartbeat": log.Println("收到心跳响应") case "notify": log.Printf("收到通知: %s", msg.Data) } } }() // 定时发送心跳 ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for range ticker.C { if clientID != "" { hb := Message{ ID: clientID, Type: "heartbeat", Token: token, } conn.WriteJSON(hb) } } } ``` 完整测试代码请参考 `read_test.go`。 --- ## 故障排查 ### 常见问题 | 问题 | 可能原因 | 解决方案 | |------|----------|----------| | 连接失败 | Nginx未启动 | 检查Nginx进程 | | 连接失败 | 端口错误 | 检查监听端口配置 | | 认证失败 | 认证服务异常 | 检查`ev_auth_url`配置 | | 心跳超时 | 网络不稳定 | 调整心跳间隔 | | 消息推送失败 | 客户端ID错误 | 确认客户端ID正确 | ### 日志位置 - **Nginx错误日志**: `/usr/local/nginx/logs/error.log` - **Nginx访问日志**: `/usr/local/nginx/logs/access.log` ### 调试模式 在nginx.conf中设置日志级别: ```nginx error_log /usr/local/nginx/logs/error.log debug; ``` --- ## 版本历史 - **v1.0.0** - 初始版本 - WebSocket协议支持 - 客户端管理 - HTTP API接口 - 服务转发功能 - 心跳机制 --- ## 许可证 MIT License