# peer **Repository Path**: wxdqing/peer ## Basic Information - **Project Name**: peer - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-23 - **Last Updated**: 2026-10-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Peer `peer` 是服务器运行时的通信基础库,模块路径为 `gitee.com/wxdqing/peer`,当前使用 Go `1.26.5`。 它提供: - TCP、WebSocket、KCP 和 gRPC stream 传输适配; - client -> gateway -> game 的双向消息链路; - Hello 握手、会话身份和 game stream 控制协议; - 节点注册、上游目标解析、限流、熔断、指标与异步写队列; - 面向 game 端的 stream handler、actor、inbound handler 和 dispatcher 接入方式。 它不负责玩家业务状态、Actor 业务实现、房间/场景路由或持久化。 ## 核心抽象 根包只定义小而稳定的通信能力,不提供单体 `RuntimePeer` 实现: ```go type Sender interface { Send(Packet) error } type Lifecycle interface { Close(reason string) error State() State } type Observable interface { ID() int32 Metadata() Metadata Stats() Stats } ``` `connection.Link` 组合以上三个接口,是 gateway session hook 等上层代码使用的链路句柄。 ```text Packet 消息字节抽象 Sender 发送能力 Lifecycle 关闭与状态 Observable ID、元数据和统计 connection.Link 上述能力的稳定组合 ``` `Metadata` 只描述链路,例如类型、协议、远端地址和标签;不要把玩家背包、场景或战斗状态放入其中。 ## 日志 根包提供最小的 `Logger` 接口,以及基于标准库的 `StdLogger` 和静默的 `NopLogger`。未配置 logger 时,各组件默认使用 `peer.DefaultLogger()`;`client.Config.Silent` 会覆盖 logger 并保持完全静默。 ```go type Logger interface { Debugf(format string, args ...any) Warnf(format string, args ...any) Errorf(format string, args ...any) } ``` 可在 `client.Config.Logger`、`remote.Config.Logger`、`fusedgateway.Options.Logger` 中配置,也可通过 `transport.WithLogger` 和 `noderegistry.WithLogger` 注入。融合网关会把自身 logger 自动传递给面向客户端的 transport 和 remote 配置。 库只记录无法继续返回给调用方的异步错误。同步 API 返回的错误仍由调用方决定如何记录;正常取消、EOF 和主动关闭不会作为错误输出。详细约定见 [日志契约](docs/contracts/logging.md)。 ## 当前架构 标准链路是 **一条客户端连接对应一条 gateway → game gRPC 双向流**(Fused,1:1): ```text Client │ TCP / WebSocket / KCP │ msgcodec frames ▼ Gateway fusedSession │ uplink: client Read → gRPC Send │ downlink: gRPC Recv → client Write │ Hello 本地校验,身份通过 gRPC metadata 传递 ▼ Game │ remote.PeerStreamService(每 client 一条 Receive stream) │ StreamHandler / StreamActor / InboundHandler / Dispatcher ▼ Application / Actor runtime ``` 客户端 transport 与当前活跃 gRPC stream **同生共死**。不要把多条 client 复用到同一条 game stream,也不要把 TCP 会话与 stream 会话拆成两套生命周期。 | 场景 | Client 连接 | gRPC stream | 说明 | |------|-------------|-------------|------| | 登录 / 重连 / 顶号(应用层) | 新 | 新 | 新连接整套新建;旧对由 game 关 session 级联拆除 | | `StreamController` rebind | 保持 | 换新 | placement / stream 故障;**不是**顶号 | `peer` 不实现登录顶号;应用在 game 侧 `BindSession` 等逻辑关闭旧 session 即可,gateway 随旧 stream 结束关掉旧 client 连接。 ### Client `client.Connect` 负责主动连接 gateway、完成 Hello/HelloAck 握手并启动独立读写协程。 支持的网络: - `runtime.NetworkTCP` - `runtime.NetworkWebSocket` - `runtime.NetworkKCP` 入站消息可以通过 `Config.OnApp` 在读协程处理,或通过 `Config.InboundBuffer` 创建的 channel 消费。`RoundTrip` 使用 `msgcodec.Head.RequestID` 关联响应。 ### Gateway `runtime/fusedgateway.Gateway` 是当前 client -> game 的标准桥接实现: 1. 接收 client transport; 2. 在 gateway 本地处理 Hello; 3. 将认证结果转换为 `handshake.StreamIdentity`,不向 game 转发原始 token; 4. 通过 placement/registry/custom resolver/fallback address 选择 game 节点; 5. 为该客户端建立独立 gRPC stream(与 client 连接 1:1); 6. 在 uplink/downlink 之间转发 `peer.Envelope.Messages`。 `StreamController` 支持授权过期、placement 事件和 stream 故障后的重新绑定(**同 client 连接、换 game stream**)。重绑定期间,请求消息会收到 `GatewayServiceUnavailable`,无 request ID 的单向消息会被丢弃并可通过 hook 观测。这与应用层顶号(新 client 连接 + 新 stream)无关。 ### Game `runtime.NewGameServerRuntime` 封装 `remote.NewServer`,启动 gRPC `PeerStreamService` 并注册节点地址。 每条入站 stream 按以下优先级选择处理模式: 1. `StreamHandlerFactory`:handler 自行管理 stream 并发模型; 2. `ActorFactory`:一个读协程加一个 actor 协程; 3. `InboundHandler`:顺序处理入站 envelope; 4. `Dispatcher`:请求进入 dispatcher,可返回响应 envelope。 应用代码统一 import `peer/remote`;公开契约与非公开实现位于同一包,避免转发层。 ## 协议与编码 ### Client leg `codec/msgcodec` 使用**方向不对称**固定头;字节序由 `msgcodec.Codec.Order` / `DefaultCodec` 配置,默认 **LittleEndian**(Unity): ```text uplink (client→game, 16B): body_len uint32 # 上限 MaxBodyLen = 1 MiB message_id uint32 request_id uint64 downlink (game→client, 24B): body_len uint32 message_id uint32 request_id uint64 packet_seq uint64 # 服务端下行序号;上行无此字段 ``` 进程级可配置(gateway/game `[peer.msgcodec] byte_order = "little"|"big"`)。Hello 用上行头,HelloAck / App 响应 / Unavailable 用下行头。 当前内置消息 ID: ```text 1 Hello 2 HelloAck 3 GameStreamInit 4 GameStreamReady 5 GameStreamRejected 6 GatewayServiceUnavailable 100 App ``` ### Transport wire `codec/wire` 提供流式长度帧、可选 gzip、XXTEA 和池化 packet。client-facing transport 使用方向化格式: - client -> gateway:长度 + 原始 msgcodec bytes; - gateway -> client:长度 + compression flag + msgcodec bytes; - legacy/internal:flags + payload,可启用 compression/encryption。 `MaxPacketSize` 同时限制编码包和解压后的数据大小。WebSocket adapter 会把该限制下推到 Gorilla `SetReadLimit`,避免在 codec 校验前分配超大消息。 ### Gateway -> game gRPC 使用 `proto/peer/peer.proto` 中的双向 `PeerStreamService.Receive`。client-leg wire bytes 放在 `Envelope.Messages` 中,不在 gateway 重编码业务 payload。 Hello 解析出的可信身份使用 metadata 传递: ```text x-peer-gateway-id x-peer-server-id x-peer-account-id x-peer-player-id x-peer-target-node-name x-peer-placement-version x-peer-node-identity x-peer-owner-node-session-id ``` ## 包结构 ```text peer/ cmd/ 示例可执行入口 client/ 主动连接 gateway 的会话客户端 codec/ msgcodec/ client-leg 固定头与 protobuf payload wire/ 压缩、加密、长度帧和池化 packet connection/ Link 与 Registrar 能力接口 errors/ 公共通信错误 gen/peer/ protobuf/gRPC 生成代码 internal/ wirebuf/ wire 池化 buffer builder noderegistry/ 节点目录接口、配置和默认 memory backend redis/ Redis backend protocol/ handshake/ Hello、HelloAck、StreamIdentity、gRPC metadata gamestream/ GameStreamInit/Ready/Rejected 校验 message/ 通用消息构造 compat/ 协议版本策略 remote/ game-side gRPC stream 实现与公开 API runtime/ transport server 与 game/gateway runtime 组装 fusedgateway/ client transport <-> game gRPC 标准链路 transport/ tcp/ TCP acceptor、connector、transport websocket/ WebSocket acceptor、connector、transport kcp/ KCP adapter kcp/kcpgo/ kcp-go 默认实现 internal/testkit/ transport 测试夹具 example/ 三节点示例、压测和 pprof 工具 proto/peer/peer.proto wire schema docs/ 当前架构、契约、操作说明与历史记录 ``` ## 快速开始 ### 运行三节点示例 在三个终端中执行: ```powershell cd D:\projects\server\libs\peer # terminal 1: game gRPC server go run ./cmd/peer-game # terminal 2: fused gateway go run ./cmd/peer-gateway -game 127.0.0.1:19001 # terminal 3: TCP client go run ./cmd/peer-client -gateway 127.0.0.1:17400 -account user-1 -msg ping ``` 完整示例说明见 [example/README.md](example/README.md)。 ### 连接 gateway ```go session, err := client.Connect(ctx, client.Config{ Network: runtime.NetworkTCP, Address: "127.0.0.1:17400", PlainWire: true, Handshaker: client.DevAuthHandshaker{AccountID: "user-1"}, }) if err != nil { return err } defer session.Close() reply, rtt, err := session.RoundTrip(ctx, []byte("ping")) ``` 生产 gateway 使用 Redis session key 时,改用 `client.SessionKeyHandshaker`,并在 gateway 的 Hello handler 中完成 session 校验和身份派生。 ## Transport 能力 所有 byte transport 共享 `transport.Option`: | 能力 | 配置 | |------|------| | 最大包/解压大小 | `WithMaxPacketSize` | | gzip | `WithCompressionThreshold` | | XXTEA | `WithEncryptionKey` | | 读写超时 | `WithReadTimeout` / `WithWriteTimeout` | | 生命周期回调 | `WithLifecycleHooks` | | 收发限流 | `WithRateLimit` | | 发送熔断 | `WithCircuitBreaker` | | 指标 | `WithMetrics` | | 异步写队列 | `WithWriteQueueSize` | 启用异步写队列后,`Send` 只负责入队;队列满返回 `peer/errors.ErrBackpressure`,关闭后返回 `peer/errors.ErrPeerClosed`。底层写错误会成为队列终止错误,并关闭所属 transport,避免留下只读不写的连接。 TCP、WebSocket 和 KCP connector 的 context 只控制建连过程;连接成功后由调用方持有并关闭 transport。`client.Session` 会额外把其连接生命周期绑定到 `client.Connect` 的 context。 ## 节点目录 `noderegistry.New` 默认创建 memory backend。Redis backend 通过 `noderegistry/redis` 的 option 注入。 game server 在 `Start` 时注册 `NodeRecord`,在 `Close` 时移除;keepalive 周期刷新 TTL。gateway 可按 `StreamIdentity.TargetNodeName` 查询 registry,查询不到时再使用自定义 resolver 或 fallback address。gateway 通过 `noderegistry.Lookup` 保留后端错误;Redis 等后端不可用时直接返回错误,不把故障当成节点不存在后继续 fallback。 `NodeRecord.Metadata` 用于描述节点能力,不承载连接级 session 数据。 ## 生命周期与并发约定 - `client.Session` 每个连接拥有一个读协程和一个写协程;`Close` 幂等。 - `fusedgateway.Gateway.Close` 会取消所有存量 client session,再关闭 client-facing listener。 - `connection.Link` 的 `State`、`ID` 和 `Stats` 描述真实 fused client 链路,可在 session hook 中读取。 - `Session.SendApp` / `RoundTrip` 可以和 `Close` 并发;关闭后返回明确错误,内部发送队列不会由 `Close` 直接关闭。 - `Session.Inbound()` 返回的 channel 会在 session 关闭时关闭。 - `Config.OnApp` 在读协程执行,回调不应长期阻塞。 - `ServerRuntime.Start` 幂等;启动失败不会提交 started 状态,可以修正环境后重试。 - runtime/remote server 关闭后不能重新启动。 - `wire.Packet`、`peer.ReleasedPacket` 等池化对象必须按各自契约调用 `Return` 或 `Release`。 - `transport.SetReceiver` 应在启动 `ReadLoop` 前完成。 ## 选择入口 | 场景 | 入口 | |------|------| | 主动连接 gateway | `client.Connect` | | 启动 client-facing transport server | `runtime.NewServerRuntime` | | 启动 game gRPC server | `runtime.NewGameServerRuntime` | | 组装标准 gateway | `runtime/fusedgateway.New` | | 直接启动 remote gRPC server | `remote.NewServer` | | 建立原始 gateway -> game stream | `remote.DialFusedStream` | | 注册/查询 game 节点 | `noderegistry.New` | ## 验证 ```powershell cd D:\projects\server\libs\peer go test -count=1 ./... go vet ./... go test ./codec/wire -run '^$' -bench . -benchmem go test ./example -count=1 -v ``` ## 进一步阅读 - [架构概览](docs/architecture/overview.md) - [Client 设计](docs/contracts/client-design.md) - [Fused gateway 握手](docs/contracts/fusedgateway-handshake.md) - [Gateway session hooks](docs/contracts/gateway-session-hooks.md) - [Transport packet pipeline](docs/contracts/transport-packet-pipeline.md) - [Remote stream 生命周期](docs/contracts/remote-stream-lifecycle.md) - [节点目录](docs/contracts/node-registry.md) - [日志契约](docs/contracts/logging.md) - [验证命令](docs/operations/verification.md) - [三节点示例](example/README.md) `docs/history/` 保存历史计划与评审,`docs/reports/` 保存阶段性分析和性能报告;当前行为以代码、README 和 `docs/contracts/` 为准。