建立连接
这页讲的是一条完整的设备连接闭环:设备端启动并等待接入,你的业务服务端签发连接 token,客户端拿 token 连接目标设备,设备端最终拿到这条连接对应的 hconn。
先准备这几项
| 项目 | 用途 |
|---|---|
device_id | 标识是哪一台设备 |
device_secret_key | 设备级密钥;设备端启动和服务端签发时会用到 |
access_id | 应用级凭证标识 |
secret_key | 应用级密钥;服务端签发时使用 |
remote_id | 客户端这次要连接的目标标识 |
常见场景里,客户端传入的 remote_id 就直接使用目标设备的 device_id。
先看整体流程
- 设备端用
device_id和device_secret_key启动 SDK。 - 你的业务服务端先判断当前主体是否允许访问目标设备,再按公开的自定义签名算法签发连接
token。 - 客户端拿到
token后,用目标remote_id发起连接。 - 设备端在
on_conn_accepted(hconn)里拿到这条连接,后续收发都围绕这条hconn展开。
第一步:设备端启动并等待客户端接入
设备端 C SDK 的主线调用顺序是:TiRtcInit() -> TiRtcStart()。
- 先通过
TIRTC_OPT_SECRET_KEY设置device_secret_key。 TiRtcStart()只传device_id。
c
#include <string.h>
#include "tiRTC.h"
static tirtc_conn_t current_hconn;
static void on_event(int event, const void *data, int len) {
(void)data;
(void)len;
}
static void on_conn_accepted(tirtc_conn_t hconn) {
current_hconn = hconn;
}
static TIRTCCALLBACKS cbs = {
.on_event = on_event,
.on_conn_accepted = on_conn_accepted,
};
int start_device(void) {
const char *device_secret_key = "your-device-secret-key";
int code = TiRtcInit();
if (code != 0) {
return code;
}
code = TiRtcSetOption(
TIRTC_OPT_SECRET_KEY,
device_secret_key,
(uint32_t)strlen(device_secret_key)
);
if (code != 0) {
return code;
}
return TiRtcStart("your-device-id", &cbs);
}TiRtcStart() 返回 0 只表示参数通过初步检查。真正启动成功以 TiEVENT_SYS_STARTED 为准。等客户端真正连上来之后,设备端会在 on_conn_accepted(hconn) 里拿到这条连接的 hconn。
第二步:业务服务端签发 token
服务端签发前,先做你自己的授权判断:当前主体能不能访问这个目标设备。如果允许,再签发这次连接所需的短时 token。
remote_id表示这次连接的目标标识。- 常见设备连接场景里,
remote_id直接使用目标设备的device_id。 - 服务端签发时写入
scope的目标,也应当和客户端随后传入的remote_id保持一致。
token 里至少要表达这些信息
| 字段 | 说明 |
|---|---|
sub | 这次请求的主体标识,例如你的 user_id 或其他稳定主键 |
scope | 这次授权的目标范围;常见写法是 connect:<remote_id> |
iss | 当前应用的 access_id |
iat | 签发时间 |
exp | 过期时间 |
nonce | 每次签发都不同的随机值 |
公开的自定义签名算法
最终下发给客户端的字符串格式是:
text
token = "v1.<payload_b64>.<app_sig>"计算过程如下:
text
payload_json = {
"sub": <subject>,
"scope": "connect:" + <remote_id>,
"iss": <access_id>,
"iat": <issued_at>,
"exp": <expires_at>,
"nonce": <random_nonce>
}
payload_b64 = base64url(payload_json)
device_sig = HMAC_SHA256(device_secret_key, payload_b64)
app_sig = HMAC_SHA256(secret_key, payload_b64 + "." + device_sig)
token = "v1." + payload_b64 + "." + app_sig这里有两个边界需要保持清楚:
secret_key和device_secret_key只留在受控服务端或设备端环境里,不下发给客户端。- 客户端只消费最终的
token,不参与签名。
Go 示例
go
package tirtc
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"time"
)
type ConnectTokenClaims struct {
Subject string `json:"sub"`
Scope string `json:"scope"`
Issuer string `json:"iss"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
Nonce string `json:"nonce"`
}
func hmacB64(key, content string) string {
h := hmac.New(sha256.New, []byte(key))
_, _ = h.Write([]byte(content))
return base64.RawURLEncoding.EncodeToString(h.Sum(nil))
}
func randomNonce() (string, error) {
buf := make([]byte, 16)
if _, err := rand.Read(buf); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(buf), nil
}
func GenerateConnectToken(accessID, secretKey, remoteID, deviceSecretKey, subject string, ttlSeconds int64) (string, error) {
now := time.Now().Unix()
nonce, err := randomNonce()
if err != nil {
return "", err
}
claims := ConnectTokenClaims{
Subject: subject,
Scope: "connect:" + remoteID,
Issuer: accessID,
IssuedAt: now,
ExpiresAt: now + ttlSeconds,
Nonce: nonce,
}
payloadJSON, err := json.Marshal(claims)
if err != nil {
return "", err
}
payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON)
deviceSig := hmacB64(deviceSecretKey, payloadB64)
appSig := hmacB64(secretKey, payloadB64+"."+deviceSig)
return "v1." + payloadB64 + "." + appSig, nil
}第三步:客户端拿 token 发起连接
这里的 remote_id 要和服务端签发时绑定的目标一致。常见场景里,这个值就是目标设备的 device_id。
kotlin
val conn = TiRtcConn()
val code = conn.connect(
remoteId = remoteId,
token = token,
)
// `connect(...)` 返回 0 只表示建连请求已提交,真正结果以后续状态回调为准。
if (code != 0) {
Log.e("TiRTC", "connect request failed code=$code")
}swift
let conn = TiRtcConn(delegate: nil)
let code = conn.connect(to: remoteId, token: token)
// `connect(...)` 返回 0 只表示建连请求已提交,真正结果以后续状态回调为准。
if code != 0 {
print("connect request failed: \(code)")
}dart
final TiRtcConn conn = TiRtcConn();
final int code = conn.connect(remoteId: remoteId, token: token);
// `connect(...)` 返回 0 只表示建连请求已提交,真正结果以后续状态回调为准。
if (code != 0) {
debugPrint('connect request failed: $code');
}后续的播放、命令收发和流消息,都是基于这条连接继续展开。
第四步:设备端拿到这条连接的 hconn
客户端这次连接建立后,设备端会进入 on_conn_accepted(hconn) 回调。这里拿到的 hconn,就是后续命令收发、流消息和音视频收发要继续使用的那条连接句柄。
如果你的设备端一直没有收到 on_conn_accepted(hconn),优先回头检查三件事:
- 设备端是否已经收到
TiEVENT_SYS_STARTED。 - 客户端建连时传入的
remote_id,是否和服务端签发时绑定的是同一个目标。 - 客户端使用的
token是否仍然有效。
常见对齐点
- 设备端以
TiEVENT_SYS_STARTED作为启动完成信号。 - 服务端签发时绑定的目标标识,和客户端建连时传入的
remote_id,必须指向同一个目标。 - 客户端把
token当成 opaque string 使用,不要在客户端自行拼装、解析或改写它。