# zzserver **Repository Path**: douyaye/zzserver ## Basic Information - **Project Name**: zzserver - **Description**: 一个websocket的库 - **Primary Language**: Go - **License**: 0BSD - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-11-05 - **Last Updated**: 2026-06-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # zzServer 基于 Go 的轻量 WebSocket 服务端框架,采用 hub 单协程管理连接,通过 `IRouter` 接口处理业务逻辑,支持与 Gin 集成。 ## 特性 - WebSocket 长连接,内置 Ping/Pong 心跳与读超时检测 - 同一连接可同时接收 `TextMessage` 与 `BinaryMessage` - 默认发送类型可配置(Text / Binary),也支持按消息指定发送类型 - 与 Gin 集成:可在同一端口同时提供 HTTP API 与 WebSocket - 广播、按连接 ID 查找、遍历在线客户端 - 优雅关闭:停止监听 → 回调 → 断开所有客户端 → 停止 hub ## 安装 ```bash go get gitee.com/douyaye/zzserver ``` 依赖:Go 1.24+、Gin、Gorilla WebSocket。 ## 快速开始 ```go package main import ( "log" "net/http" "gitee.com/douyaye/zzserver" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) func main() { srv := zzserver.NewZZServer() g := gin.Default() g.GET("/hello", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"code": 0}) }) srv.SetGinEngine(g) // 不设置则自动生成默认 Gin srv.SetRouter(&MyRouter{}) srv.SetWebsocketPort(9999) srv.SetWsPath("/") // 可选,默认 "/" if err := srv.Start(); err != nil { log.Fatal(err) } srv.WaitCloseSignal(nil, nil) } type MyRouter struct { zzserver.BaseRouter } func (r *MyRouter) OnMessage(c *zzserver.Client, msgType int, message []byte) { switch msgType { case websocket.TextMessage: c.SendText("收到: " + string(message)) case websocket.BinaryMessage: c.Send(websocket.BinaryMessage, message) } } func (r *MyRouter) OnConnected(c *zzserver.Client) { log.Printf("客户端 %d 已连接, IP=%s", c.ConnectionIndex, c.GetIP()) } func (r *MyRouter) OnDisconnect(c *zzserver.Client) { log.Printf("客户端 %d 已断开", c.ConnectionIndex) } ``` 连接地址: ``` ws://127.0.0.1:9999/ WebSocket(默认 Text 发送) http://127.0.0.1:9999/hello HTTP 接口 ``` 更多示例见 `example/json`(JSON 文本)和 `example/protobuf`(Protobuf 二进制)。 ## 路由接口 IRouter 实现 `IRouter`,或嵌入 `BaseRouter` 只重写需要的方法: | 方法 | 执行协程 | 说明 | |------|----------|------| | `OnConnected(c)` | hub | 客户端已加入在线列表;此回调完成前不会处理该连接的消息 | | `OnMessage(c, msgType, message)` | 读协程 | 收到业务消息;**应尽快返回**,耗时逻辑请放到业务 goroutine | | `OnDisconnect(c)` | hub | 客户端已从在线列表移除 | | `OnServerClose()` | 调用 `Close()` 的协程 | 服务器关闭时执行一次,发生在所有客户端断开**之前** | ```go type IRouter interface { OnMessage(c *Client, msgType int, message []byte) OnDisconnect(c *Client) OnServerClose() OnConnected(c *Client) } ``` ## Server API ### 配置 | 方法 | 说明 | |------|------| | `NewZZServer()` | 创建服务并启动 hub 协程 | | `SetRouter(r IRouter)` | 设置路由(`Start` 前必须调用) | | `SetWebsocketPort(port int)` | 监听端口(`Start` 前必须调用) | | `SetGinEngine(g *gin.Engine)` | 设置 Gin 引擎;不设置则自动生成 | | `SetWsPath(path string)` | WebSocket 路径,默认 `"/"` | | `SetMessageType(t int)` | 新连接的默认**发送**类型:`websocket.TextMessage` 或 `websocket.BinaryMessage` | | `SetCheckOrigin(fn)` | 跨域校验,默认允许所有来源 | ### 生命周期 | 方法 | 说明 | |------|------| | `Start() error` | 同步完成端口监听后返回;监听成功后即可接受连接 | | `Close()` | 优雅关闭 | | `WaitCloseSignal(before, after)` | 阻塞等待 SIGINT / SIGTERM 后调用 `Close()` | `Start()` 可能返回的错误: - `ErrRouterNotSet` — 未设置 Router - `ErrPortNotSet` — 未设置端口 - `ErrAlreadyStarted` — 重复调用 `Start()` ### 客户端管理 | 方法 | 说明 | |------|------| | `Online() (int, bool)` | 在线人数;第二个值为 `false` 表示 hub 操作超时 | | `GetClient(id int) (*Client, bool)` | 按 `ConnectionIndex` 查找;未找到或超时返回 `nil, false` | | `Range(f func(c *Client) bool) bool` | 在 hub 协程中遍历客户端;`f` 应尽快返回;超时返回 `false` | | `SendToAll(message []byte)` | 广播消息给所有在线客户端 | ## Client API ### 字段 | 字段 | 说明 | |------|------| | `ConnectionIndex` | 连接唯一序号(自增),可作会话 ID | | `User` | 业务用户对象,登录后由业务方赋值,如 `c.User = myUser` | | `Server` | 所属 `Server`,可调用 `c.Server.SendToAll(...)` | ### 发送消息 | 方法 | 说明 | |------|------| | `Send(msgType int, data []byte)` | 按指定类型发送;`data` 会被拷贝 | | `SendByte(data []byte)` | 以默认类型 `WsMessageType` 发送 | | `SendText(msg string)` | 发送文本 | | `SendJson(obj interface{}) error` | 序列化 JSON 后发送 | 发送队列满时会自动断开连接。 ### 连接信息 | 方法 | 说明 | |------|------| | `GetIP()` | 解析 `X-Forwarded-For` / `X-Real-IP` 后的登录 IP | | `GetRemoteAddr()` | 底层 TCP 地址 | | `LastMsgTime()` | 最后收到消息或 Pong 的时间 | | `Close()` | 断开连接;写协程会发出 WebSocket Close 帧 | ## 使用注意 **发送与接收类型** - `SetMessageType` 只影响该连接的默认**发送**类型(`SendByte` / `SendText` / `SendJson`) - **接收**类型由每条消息的 `msgType` 参数决定,同一连接可混用 Text 与 Binary **OnMessage 不要阻塞** `OnMessage` 在读协程中同步执行,阻塞会卡住该连接后续消息的读取。解析、落库等耗时操作应 `go func() { ... }()` 异步处理。 **空闲踢人** 框架不内置空闲超时,可自行定时 `Range` 检查 `LastMsgTime()`: ```go go func() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for range ticker.C { deadline := time.Now().Add(-30 * time.Second) srv.Range(func(c *zzserver.Client) bool { if c.LastMsgTime().Before(deadline) { c.Close() } return true }) } }() ``` 心跳 Pong 会刷新 `LastMsgTime()`,正常挂机的客户端不会被误踢。 **关闭顺序** ``` Close() ├─ 停止 HTTP 监听 ├─ OnServerClose() ├─ 断开所有客户端 └─ 停止 hub ``` ## 架构简述 ``` ┌─────────────┐ WebSocket 连接 ──►│ 读协程 │──► OnMessage(业务) └─────────────┘ │ connected / disconnected ▼ ┌─────────────┐ │ hub 协程 │──► OnConnected / OnDisconnect │ (clients map)│──► Range / Online / GetClient / 广播 └─────────────┘ │ ┌─────────────┐ │ 写协程 │◄── Send / bufSend │ + Ping 心跳 │ └─────────────┘ ``` ## 示例项目 | 目录 | 说明 | |------|------| | `example/json` | Gin + 文本消息 + 空闲踢人 | | `example/protobuf` | Protobuf 二进制协议 | | `example/protobuf/client` | 测试客户端 | --- by. douya