这个目录包含了 VoidBus 的使用示例。
交互式的 client 和 server,适合手动测试多 channel 分布和编解码。
# 终端 1
cd example/server
go run main.go
# 终端 2
cd example/client
go run main.go自动传输 ~10MB 文件,显示完整日志和性能数据。
# 终端 1
cd example/noninteractive-server
go run main.go
# 终端 2
cd example/noninteractive-client
go run main.go详见 noninteractive-server/README.md。
- 多 Channel 同时使用:TCP/WS/UDP 同时连接,分片随机分布到不同 channel
- 多 Codec 随机组合:最大深度限制为 3
- Session-based 关联:Client 通过 SessionID 将所有 channel 关联到同一连接
- 健康权重选择:ChannelPool 根据健康度加权随机选择 channel
- 优雅退出:Ctrl+C 后正确清理所有 goroutine 和资源
- Debug 模式:显示消息实际使用的 channel 和 codec 链
cd example/server
go run main.goServer 将监听:
- TCP: :19000
- WebSocket: :19001
- UDP: :19002
cd example/client
go run main.goClient 将连接到:
- TCP: 127.0.0.1:19000
- WebSocket: ws://127.0.0.1:19001
- UDP: 127.0.0.1:19002
- Server 和 Client 启动后,在 Client 输入消息
- Server 会显示收到的消息并回复
- 观察日志可以看到消息通过不同的 channel 发送(TCP/WS/UDP)
在 Server 或 Client 进程中按 Ctrl+C:
- 立即停止 Accept/Read 循环
- 关闭所有 channel 连接
- 等待所有 goroutine 退出
- 打印 "Server/Client stopped"
// 1. 创建 Bus 并设置密钥
bus := voidbus.New(nil)
bus.SetKey(key)
// 2. 注册多种 codec
bus.RegisterCodec(base64.New())
bus.RegisterCodec(xor.New())
bus.RegisterCodec(aes.NewAES256Codec())
bus.RegisterCodec(chacha20.New())
// 3. 注册所有 server channel
bus.AddChannel(tcp.NewServerChannel(...))
bus.AddChannel(ws.NewServerChannel(...))
bus.AddChannel(udp.NewServerChannel(...))
// 4. Listen - 聚合所有 channel
listener := bus.Listen()
// 5. Accept 循环
for {
conn := listener.Accept() // 每个 conn 已经关联了所有 channel
go handleClient(conn)
}// 1. 创建 Bus 并设置密钥
bus := voidbus.New(nil)
bus.SetKey(key)
// 2. 注册多种 codec
bus.RegisterCodec(base64.New())
bus.RegisterCodec(xor.New())
bus.RegisterCodec(aes.NewAES256Codec())
bus.RegisterCodec(chacha20.New())
// 3. 注册所有 client channel
bus.AddChannel(tcp.NewClientChannel(...))
bus.AddChannel(ws.NewClientChannel(...))
bus.AddChannel(udp.NewClientChannel(...))
// 4. Dial - 使用所有 channel
conn := bus.Dial() // 自动协商,所有 channel 关联到同一 Session
// 5. 发送/接收
conn.Write([]byte("Hello"))
buf := make([]byte, 4096)
n := conn.Read(buf)- 首次 Negotiation:通过第一个 channel(TCP)发送 NegotiateRequest(SessionID=空)
- 获取 SessionID:收到 NegotiateResponse 后获取 SessionID
- 后续 Negotiation:异步通过 WS/UDP 发送 NegotiateRequest(带上 SessionID)
- Channel 关联:所有 channel 的 receiveLoop 都把数据汇总到同一个 recvQueue
- 聚合 Listener:voidBusListener 同时监听 TCP/WS/UDP
- 首个连接:第一个 channel 连接时立即 Accept 返回
- 后续关联:收到带 SessionID 的 NegotiateRequest,动态添加到已有 Session
- Channel 池:每个 accepted conn 都有独立的 ChannelPool,包含所有已关联 channel
每条消息发送时:
- 切片:FragmentManager.AdaptiveSplit() 根据最小 MTU 切片
- 选择 Channel:每个分片独立调用 ChannelPool.SelectChannel()
- 健康权重:初始 HealthScore=1.0,理论均匀分布
- 故障切换:连续失败时 HealthScore 下降,自动切换到其他 channel
启用 DebugMode 可以看到详细的 channel 选择日志:
bus.SetDebugMode(true)日志示例:
[DEBUG] sendInternal: sending fragment 0 on channel udp-6285cdda
[DEBUG] sendInternal: sending fragment 1 on channel ws-fc4c2bba
[DEBUG] sendInternal: sending fragment 2 on channel tcp-e6ec75c8
- 密钥一致性:Server 和 Client 必须使用相同的 32-byte 密钥
- Codec 注册:Server 和 Client 必须注册相同的 codec
- UDP 限制:每个 Session 只能有一个 UDP channel(Server 会拒绝重复 UDP 连接)
- MTU 计算:UDP 实际可用 MTU = 1395 bytes(扣除 VoidBus Header + UDP Header)
检查端口是否被占用:
lsof -i :19000 -i :19001 -i :19002启用 DebugMode 查看 channel 健康度:
bus.SetDebugMode(true)检查 goroutine 是否全部退出:
# 发送 SIGQUIT 查看 goroutine stack
kill -QUIT <pid>