17 KiB
17 KiB
teamspeak-go
纯 Go 实现的 TeamSpeak 3 客户端协议库,零 CGO 依赖,支持 gomobile 编译为 Android .aar。
- GitHub: https://github.com/honeybbq/teamspeak-go
- 本项目使用本地补丁版本(
go/_patches/),通过go.mod的replace指令生效
Architecture
┌──────────────────────────────────────────────┐
│ Application Layer │
│ (bridge.go / 用户代码) │
│ OnConnected, OnTextMessage, SendVoice ... │
├──────────────────────────────────────────────┤
│ Client (client.go) │
│ 连接管理、事件分发、命令追踪 │
│ events.go — 事件注册 & dispatchEvent │
│ commands.go — 命令发送 & return_code 匹配 │
│ notifications.go — notify* 事件解析 │
│ api.go — 高层 API (ListChannels, SendText...)│
│ handshake.go — 加密握手 & clientinit │
│ transfer.go — 文件传输 │
│ throttle.go — 命令限速 (token bucket) │
├──────────────────────────────────────────────┤
│ Transport (transport/) │
│ PacketHandler — UDP 收发、包分片 │
│ packet.go — 包类型定义 │
│ quicklz.go — QuickLZ 压缩解压 │
├──────────────────────────────────────────────┤
│ Crypto (crypto/) │
│ Identity 生成、密钥交换、EAX 加密 │
├──────────────────────────────────────────────┤
│ Handshake (handshake/) │
│ crypt_init2.go — 二次加密协商 │
│ license.go — 许可证验证 │
├──────────────────────────────────────────────┤
│ Discovery (discovery/) │
│ SRV / TSDNS / 直连 地址解析 │
└──────────────────────────────────────────────┘
Key Files
| 文件 | 职责 |
|---|---|
client.go |
Client 结构体、NewClient、Connect、Disconnect、事件循环 (startEventLoop)、notifyEvent 入队 |
events.go |
事件注册 API (OnConnected, OnTextMessage 等)、dispatchEvent 分发到所有 handler |
notifications.go |
handleNotification — 解析 notify* 命令,转换为结构体事件 |
commands.go |
handlePacket — 包类型路由、ExecCommand / ExecCommandWithResponse — 带 return_code 的异步命令 |
api.go |
高层 API:ListChannels、ListClients、GetClientInfo、SendTextMessage、ClientMove、Poke、SendVoice、WaitConnected |
handshake.go |
handleHandshakeInitIV、handleHandshakeExpand2、handleInitServer、sendClientInit |
types.go |
所有事件/数据结构体定义 |
transfer.go |
文件传输:FileTransferInitUpload、FileTransferInitDownload、FileTransferDeleteFile |
throttle.go |
Token-bucket 命令限速器(4 tokens/s,上限 8) |
Event Loop
所有事件在单个 goroutine 中串行处理,保证按到达顺序分发:
notifyEvent(evt) ← 任何 goroutine 调用
→ 入队 (evtQueueMu)
→ signal (evtCond)
↓
startEventLoop goroutine ← 唯一消费者
→ 取出全部待处理事件
→ dispatchEvent(evt) ← 逐个分发到注册的 handler
Event Lifecycle
事件分为两类:服务器通知事件(notify 前缀,服务器主动推送)和内部事件(SDK 状态变化触发)。
Phase 1: Connection (连接阶段)
客户端从发起到建立连接的握手过程,涉及加密协商和身份初始化。
| 事件名 | 类型 | 作用 | 描述 |
|---|---|---|---|
PacketTypeInit1 |
握手包 | 初始化加密通道 | 客户端发送 UDP 包后服务器返回 init1 响应,完成第一阶段密钥交换。SDK 内部处理,应用层无感 |
clientinitiv |
服务器响应 | 加密参数协商 | 服务器返回 alpha、beta、omega 加密参数,客户端调用 InitCrypto 初始化加密上下文 |
initivexpand2 |
服务器响应 | 二次加密扩展 | 服务器返回许可证和扩展密钥,客户端生成临时密钥对(clientek)完成最终加密握手 |
clientinit |
客户端命令 | 发送身份信息 | 加密握手完成后,客户端发送昵称、版本、HWID、默认频道等信息,请求加入服务器 |
initserver |
服务器响应 | 连接建立确认 | 服务器分配客户端 ID(clid),连接正式建立。触发 OnConnected 回调 |
OnConnected |
SDK 回调 | 连接成功通知 | 应用层注册的连接成功回调。SDK 自动发送 clientupdate 解除静音。此时可调用 ListChannels、ListClients 获取快照 |
Phase 2: Runtime — User Events (用户事件)
服务器上用户的上下线、移动、消息等实时事件。
| 事件名 | 类型 | 作用 | 描述 |
|---|---|---|---|
notifycliententerview → OnClientEnter |
服务器通知 | 用户进入视野 | 有新客户端连接到服务器或进入可见范围。数据结构 ClientInfo:ID(clid)、Nickname、UID、ChannelID、Type、ServerGroups |
notifyclientleftview → OnClientLeave |
服务器通知 | 用户离开视野 | 客户端断开连接或离开可见范围。数据结构 ClientLeftViewEvent:ID、ReasonID(0=正常、4=频道踢出、5=服务器踢出)、ReasonMsg |
notifyclientmoved → OnClientMoved |
服务器通知 | 用户切换频道 | 客户端被移动到另一个频道。数据结构 ClientMovedEvent:ID、TargetChannelID、ReasonID、InvokerID、InvokerName、InvokerUID。如果是自己被移动,这是频道切换的确认点 |
notifytextmessage → OnTextMessage |
服务器通知 | 收到文字消息 | 收到私聊/频道/服务器范围的文字消息。数据结构 TextMessage:TargetMode(1=私聊、2=频道、3=服务器)、TargetID、InvokerID、InvokerName、InvokerUID、Message、InvokerGroups |
notifyclientpoke → OnPoked |
服务器通知 | 被其他用户戳 | 有用户发送 poke 消息。数据结构 PokeEvent:InvokerID、InvokerName、InvokerUID、Message |
notifyclientneededpermissions |
服务器通知 | 权限不足 | 操作因权限不足被拒绝。携带 permid、permvalue。SDK 仅记录日志,不触发应用层回调 |
Phase 3: Runtime — Voice (语音事件)
实时语音数据的收发,通过 UDP 二进制包传输。
| 事件名 | 类型 | 作用 | 描述 |
|---|---|---|---|
VoiceDataEvent → OnVoiceData |
二进制包 | 收到语音数据 | 其他客户端发送的 Opus 语音帧。数据结构 VoiceDataEvent:ClientID(发送者)、Data(Opus 数据)、Codec(4=Opus Voice、5=Opus Music) |
SendVoice(data, codec) |
客户端命令 | 发送语音数据 | 发送 Opus 编码的语音帧到服务器,服务器转发给同频道其他用户。通过 handler.SendVoicePacket 直接写入 UDP |
语音包二进制格式:
Offset Size Field
0 2 packetID (big-endian)
2 2 clientID (little-endian)
4 1 codec (4=Opus Voice, 5=Opus Music)
5 N Opus encoded data
Phase 4: Runtime — File Transfer (文件传输事件)
服务器文件的上传、下载和管理。文件数据通过独立的 TCP 连接传输。
| 事件名 | 类型 | 作用 | 描述 |
|---|---|---|---|
ftinitupload / notifystartupload |
命令+通知 | 初始化文件上传 | 客户端请求上传文件,服务器返回 FileUploadInfo:Port(TCP 端口)、FileTransferKey(传输密钥)、SeekPosition(断点)、ClientFileTransferID、ServerFileTransferID |
ftinitdownload / notifystartdownload |
命令+通知 | 初始化文件下载 | 客户端请求下载文件,服务器返回 FileDownloadInfo:Port、FileTransferKey、Size、ClientFileTransferID、ServerFileTransferID |
notifystatusfiletransfer |
服务器通知 | 文件传输状态 | 传输完成或失败的状态通知。数据结构 FileTransferStatusInfo:Status(0=成功)、Message(错误信息)、ClientFileTransferID |
ftdeletefile |
客户端命令 | 删除服务器文件 | 删除指定频道目录下的文件,支持 | 分隔的批量删除 |
Phase 5: Runtime — Commands & Errors (命令与错误)
客户端命令的异步响应机制和错误处理。
| 事件名 | 类型 | 作用 | 描述 |
|---|---|---|---|
return_code |
服务器响应 | 命令执行结果 | 每个命令附带 return_code 参数,服务器执行完成后返回对应的 return_code,SDK 通过 commandTracker 匹配异步响应。id=0 表示成功,非 0 为错误码 |
error |
服务器响应 | 服务器错误 | 服务器返回的错误信息。id(错误码)、msg(错误描述)。错误码 3329 为致命错误(如被 ban),SDK 自动断开连接 |
clientupdate |
客户端命令 | 更新客户端状态 | 连接成功后 SDK 自动发送,设置 client_input_muted=0、client_output_muted=0 |
命令限速: SDK 内置 token-bucket 限速器(throttle.go),速率 4 tokens/s,上限 8。所有 ExecCommand 调用自动排队等待。
Phase 6: Disconnect (断开阶段)
连接关闭和清理。
| 事件名 | 类型 | 作用 | 描述 |
|---|---|---|---|
clientdisconnect |
客户端命令 | 主动断开 | 客户端发送 clientdisconnect reasonmsg=Shutdown,然后关闭 UDP 连接 |
OnDisconnected |
SDK 回调 | 断开连接通知 | 连接断开时触发。参数 error:nil = 正常断开,非 nil = 异常断开原因 |
OnClosed (handler) |
传输层回调 | 底层连接关闭 | UDP 连接关闭时触发,SDK 内部据此调用 OnDisconnected。应用层不直接使用 |
Lifecycle Overview (生命周期总览)
NewClient(identity, addr, nickname, opts...)
│
├─ 启动事件循环协程 (startEventLoop)
├─ 初始化 PacketHandler、Crypto、Throttle
│
Connect()
│
├─ 地址解析 (SRV / TSDNS / 直连)
├─ UDP 连接
├─ PacketTypeInit1 握手
├─ ← clientinitiv (加密参数)
├─ → InitCrypto
├─ ← initivexpand2 (二次加密)
├─ → clientek (临时密钥)
├─ → clientinit (身份信息)
├─ ← initserver → 连接建立
│
├─ → OnConnected() 回调
├─ → clientupdate (解除静音)
│
│ ┌─ API 调用 ─────────────────────────────┐
│ │ ListChannels() → channellist │
│ │ ListClients() → clientlist │
│ │ GetClientInfo() → clientinfo │
│ │ SendTextMessage() → sendtextmessage │
│ │ ClientMove() → clientmove │
│ │ SendVoice() → UDP 二进制包 │
│ └─────────────────────────────────────────┘
│
│ ┌─ 服务器事件 ────────────────────────────┐
│ │ notifycliententerview → OnClientEnter │
│ │ notifyclientleftview → OnClientLeave │
│ │ notifyclientmoved → OnClientMoved │
│ │ notifytextmessage → OnTextMessage │
│ │ notifyclientpoke → OnPoked │
│ │ VoiceDataEvent → OnVoiceData │
│ │ error (id=3329) → 自动断开 │
│ └─────────────────────────────────────────┘
│
Disconnect()
│
├─ → clientdisconnect reasonmsg=Shutdown
├─ → 关闭 UDP 连接 (handler.Close)
└─ → OnDisconnected(nil) 回调
Data Types
| 结构体 | 用途 | 关键字段 |
|---|---|---|
ClientInfo |
客户端信息 | ID (uint16), Nickname, UID, ChannelID (uint64), Type, ServerGroups ([]string) |
ChannelInfo |
频道信息 | ID (uint64), Name, ParentID (uint64), Description |
TextMessage |
文字消息 | TargetMode (int), TargetID (uint64), InvokerID (uint16), InvokerName, InvokerUID, Message, InvokerGroups |
ClientMovedEvent |
频道移动 | ID (uint16), TargetChannelID (uint64), ReasonID (int), InvokerID (uint16), InvokerName, InvokerUID |
ClientLeftViewEvent |
用户离开 | ID (uint16), ReasonID (int), ReasonMsg, TargetID (uint16) |
PokeEvent |
Poke 消息 | InvokerID (uint16), InvokerName, InvokerUID, Message |
VoiceDataEvent |
语音数据 | ClientID (uint16), Data ([]byte), Codec (byte) |
FileUploadInfo |
上传信息 | Port, FileTransferKey, SeekPosition, ClientFileTransferID, ServerFileTransferID |
FileDownloadInfo |
下载信息 | Port, FileTransferKey, Size, ClientFileTransferID, ServerFileTransferID |
FileTransferStatusInfo |
传输状态 | Status (int), Message, ClientFileTransferID |
API Reference
Client Construction
identity, _ := crypto.GenerateIdentity(8)
client := teamspeak.NewClient(identity, "host:9987", "Nickname",
teamspeak.WithServerPassword("pass"),
teamspeak.WithDefaultChannel("/General"),
teamspeak.WithDefaultChannelPassword("cpw"),
teamspeak.WithLogger(logger),
teamspeak.WithResolver(customResolver),
)
Connection
client.Connect() // 启动连接(非阻塞)
client.WaitConnected(context.Background()) // 阻塞等待握手完成
client.Disconnect() // 优雅断开
client.ClientID() // 获取服务器分配的客户端 ID
Data Queries
channels, err := client.ListChannels() // 获取所有频道
clients, err := client.ListClients() // 获取所有在线客户端
info, err := client.GetClientInfo(clid) // 获取单个客户端详情
Messaging
client.SendTextMessage(targetMode, targetID, message) // 发送文字消息
client.ClientMove(clid, channelID, password) // 移动频道
client.Poke(clid, message) // Poke 用户
Voice
client.SendVoice(opusData, 4) // 发送 Opus Voice 帧 (codec=4)
client.SendVoice(opusData, 5) // 发送 Opus Music 帧 (codec=5)
File Transfer
info, err := client.FileTransferInitUpload(channelID, "/path", "", size, false)
teamspeak.UploadFileData(host, info, reader)
info, err := client.FileTransferInitDownload(channelID, "/path", "")
teamspeak.DownloadFileData(host, info, writer)
client.FileTransferDeleteFile(channelID, []string{"/file1", "/file2"})
Event Registration
client.OnConnected(func() { ... })
client.OnDisconnected(func(err error) { ... })
client.OnClientEnter(func(info teamspeak.ClientInfo) { ... })
client.OnClientLeave(func(evt teamspeak.ClientLeftViewEvent) { ... })
client.OnClientMoved(func(evt teamspeak.ClientMovedEvent) { ... })
client.OnTextMessage(func(msg teamspeak.TextMessage) { ... })
client.OnPoked(func(evt teamspeak.PokeEvent) { ... })
client.OnKicked(func(reason string) { ... })
client.OnVoiceData(func(evt teamspeak.VoiceDataEvent) { ... })
Middleware
// 命令中间件:拦截/修改出站命令
client.UseCommandMiddleware(func(next func(string) error) func(string) error {
return func(cmd string) error {
log.Println("CMD:", cmd)
return next(cmd)
}
})
// 事件中间件:拦截/修改入站事件
client.UseEventMiddleware(func(next func(any)) func(any) {
return func(evt any) {
log.Println("EVT:", evt)
next(evt)
}
})
Local Patches
本项目通过 go.mod 的 replace 指令使用本地补丁版本:
replace github.com/honeybbq/teamspeak-go => ./_patches/github.com/honeybbq/teamspeak-go
补丁修复了上游库的以下问题:
- 32 位目标平台的整数溢出(
math.MaxUint32 overflows int) - 其他 gomobile 兼容性修复