拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SpacetimeDB 客户端连接实战指南:从 `DbConnection` 建立到生命周期管理

SpacetimeDB 客户端连接实战指南从DbConnection建立到生命周期管理【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB本篇技术指南围绕 SpacetimeDB 1.12.0 客户端的核心入口DbConnection展开讲解如何在生成模块绑定bindings之后通过 TypeScript、C#、Rust、Unreal 四种 SDK 建立与数据库的 WebSocket 长连接涵盖连接参数构建、MainCloud 与 Token 认证、连接推进advance机制、生命周期回调以及 Identity / ConnectionId 的身份模型。读完本文你将掌握四种语言下完整的连接接入方案并理解连接在底层是如何工作的从而在实际项目中正确接入并避免连上了但收不到数据这类经典坑。连接前置条件在调用DbConnection之前需要确认三件事已经就绪已为你的模块生成客户端绑定通过spacetime generate生成类型安全接口具体方法见 生成客户端绑定。绑定会为模块中的表生成类型定义与访问器、为 reducer 生成可调用函数、为订阅提供查询接口并支持注册数据库变更回调保证客户端与服务端在编译期就具备类型安全。一个已发布并正在运行的数据库可以是本地 host也可以是部署在 MainCloud 上的数据库。数据库的 URI 以及数据库的名称或 IdentityURI 指向 SpacetimeDB host名称或 Identity 用于标识目标数据库。关于数据库与模块的关系可以参考 核心架构 中的说明host 是承载数据库的服务器数据库是运行在 host 上的应用导出表tables与 reducer而模块则是一份用 C#、Rust 或 TypeScript 编写、定义了数据库 schema 与业务逻辑的软件。在连接前理解这条链路有助于准确填写连接参数。基本连接DbConnection构建器模式四种 SDK 都遵循同一种构建器builder模式先通过一系列withXxx方法设置连接参数再调用build()或new完成连接。最基本的连接只需要两个参数host 的 URI 与数据库的名称或 Identity。import { DbConnection } from ./module_bindings; const conn new DbConnection.builder() .withUri(https://maincloud.spacetimedb.com) .withModuleName(my_database);using SpacetimeDB; var conn DbConnection.Builder() .WithUri(new Uri(https://maincloud.spacetimedb.com)) .WithModuleName(my_database) .Build();use module_bindings::DbConnection; let conn DbConnection::builder() .with_uri(https://maincloud.spacetimedb.com) .with_module_name(my_database) .build();#include ModuleBindings/DbConnection.h UDbConnection* Conn UDbConnection::Builder() -WithUri(TEXT(https://maincloud.spacetimedb.com)) -WithModuleName(TEXT(my_database)) -Build();使用时应将https://maincloud.spacetimedb.com替换为实际的 SpacetimeDB host URI将my_database替换为数据库的名称或 Identity。构建器参数的底层约束从 Rust SDK 的源码实现sdks/rust/src/db_connection.rs可以看到with_uri与with_database_name的具体语义with_uri第 1060 行URI 必须不带 scheme 或使用http、https、ws、wss中的一种SDK 会将其解析为http::Uri连接时实际建立的是 WebSocket。with_database_name第 1067 行接受数据库的名称或 Identity在build时与 URI 一起传给WsConnection::connect用于在 host 上定位目标数据库第 980-986 行。build()标注了#[must_use]第 936 行编译期即提醒开发者连接建立后必须显式推进连接frame_tick、run_threaded、run_background_task、run_async或advance_one_message之一否则连接永远不会前进。在 TypeScript SDK 中sdks/typescript/src/sdk/db_connection_builder.tsbuild()会在缺少uri或nameOrAddress时直接抛错第 264-272 行并调用ensureMinimumVersionOrThrow校验模块绑定的 CLI 版本与运行时兼容性。也就是说URI 数据库标识是连接的最低要求二者缺一不可。更多可配置项进阶构建器还提供了一批可选的连接参数这些在原文档基础上可从源码确认构建器方法作用源码依据with_compression/withCompression设置消息压缩算法TypeScript 支持gzip | brotli | none默认 gziphost 端对超过 1KiB 阈值的消息启用压缩Rust 第 1093 行、TS 第 96 行with_confirmed_reads/withConfirmedReads启用已确认读服务端仅在事务被确认为持久化后才下发查询结果会增大 reducer 调用与订阅更新到达客户端之间的延迟Rust 第 1113 行、TS 第 143 行with_debug_to_file将 SDK 内部日志追加写入指定文件用于排查 SDK 问题会产生大量日志并影响性能不应在生产环境使用且多连接并行时建议各自使用独立文件Rust 第 1130 行连接 MainCloudMainCloud 是 SpacetimeDB 的托管服务连接方式与本地完全一致只需将 URI 指向https://maincloud.spacetimedb.com并填上部署在该云上的数据库名称或 Identity。四语言写法与基本连接相同此处不再重复代码直接替换 URI 与模块名即可TypeScriptnew DbConnection.builder().withUri(https://maincloud.spacetimedb.com).withModuleName(my_database);C#DbConnection.Builder().WithUri(new Uri(https://maincloud.spacetimedb.com)).WithModuleName(my_database).Build();RustDbConnection::builder().with_uri(https://maincloud.spacetimedb.com).with_module_name(my_database).build();UnrealUDbConnection::Builder()-WithUri(TEXT(https://maincloud.spacetimedb.com))-WithModuleName(TEXT(my_database))-Build();关于 MainCloud 的数据库发布与部署流程详见 MainCloud 部署文档。使用 Token 认证SpacetimeDB 的认证基于 OpenID Connect身份由 JWTJSON Web Token中的 issuer 与 subject 字段哈希派生而来具体算法见 核心架构文档中的 Identity 一节。你可以通过 SpacetimeAuth 或任何符合 OIDC 规范的提供商获取 JWT然后在构建连接时通过withToken传入const conn new DbConnection.builder() .withUri(https://maincloud.spacetimedb.com) .withModuleName(my_database) .withToken(your_auth_token_here);var conn DbConnection.Builder() .WithUri(new Uri(https://maincloud.spacetimedb.com)) .WithModuleName(my_database) .WithToken(your_auth_token_here) .Build();let conn DbConnection::builder() .with_uri(https://maincloud.spacetimedb.com) .with_module_name(my_database) .with_token(your_auth_token_here) .build();UDbConnection* Conn UDbConnection::Builder() -WithUri(TEXT(https://maincloud.spacetimedb.com)) -WithModuleName(TEXT(my_database)) -WithToken(TEXT(your_auth_token_here)) -Build();Token 会在连接握手时发送给服务器用于验证你的身份。关于如何获取和管理 Token参考 SpacetimeAuth 文档。匿名连接与会话令牌从 Rust SDK 源码的with_token注释sdks/rust/src/db_connection.rs可以确认两条重要行为Token 是可选的。如果不调用with_token或显式传入Nonehost 会为这次连接生成一个新的匿名 Identity。也就是说不传 Token 也能连上只是每次都是新用户。Token 拒绝的两种时机如果握手建立前 Token 就被拒绝build()会直接返回错误如果 WebSocket 已建立、但在收到初始连接消息之前被拒绝则会触发on_connect_error回调。另外注意on_connect回调收到的第三个参数就是私有访问令牌它是服务端签发给当前 Identity 的凭证应保存下来供下次连接复用TypeScript 的onConnect文档同样强调这一点见 db_connection_builder.ts。Token 的完整获取与签发流程可阅读 SpacetimeAuth 的 项目创建指南。推进连接AdvanceC# 与 Unreal 的关键一步CriticalC#、Unity 与 Unreal 用户必读在 C#包括 Unity与 Unreal Engine 中你必须手动推进连接来消费入站消息——连接不会自动处理消息如果你使用 C#含 Unity或 Unreal必须在游戏循环或更新方法中调用DbConnection.FrameTick()// In Unity, call this in your Update() method void Update() { conn.FrameTick(); } // Or in a console application, call this in your main loop while (running) { conn.FrameTick(); // Your application logic... }// In your Actors Tick() method void AMyActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); if (Conn) { Conn-FrameTick(); } }如果不推进连接客户端将收不到任何来自服务器的更新——包括订阅数据、reducer 回调以及连接事件。这是新手最容易踩的坑连接看起来建好了但数据一动不动。相比之下Rust 与 TypeScript 不需要手动轮询TypeScript 通过浏览器的事件循环或 Node.js 的事件循环自动处理消息Rust 则依赖 Tokio 异步运行时。底层机制为什么 Rust/TS 不用手动推进从 Rust SDK 源码sdks/rust/src/db_connection.rs可以看出frame_tick的实现本质是循环调用advance_one_message直到没有待处理消息为止第 656-659 行pub fn frame_tick(self) - crate::Result() { while self.advance_one_message()? {} Ok(()) }整个连接由三部分组成第 5-17 行、第 992-999 行一个后台 Tokio workerWsConnection负责收发原始 WebSocket 消息parse_loop将原始消息解析为领域类型ParsedMessage当用户调用advance_one_message/frame_tick时已解析的消息才会被应用更新客户端缓存、触发回调。Rust SDK 还提供了另外三种自动推进的方式第 661-705 行方法适用场景run_threaded()非浏览器环境启动一个独立线程循环推进正常断连时优雅退出run_async()/advance_one_message_async()异步环境async fn 中awaitrun_background_task()浏览器wasm环境通过wasm_bindgen_futures::spawn_local在本地任务队列中循环推进有趣的是Rust/TS 自动处理与C#/Unreal 手动推进的差异本质上是谁在驱动事件循环的问题浏览器与 Node.js 的事件循环天然持续运转Tokio runtime 也能在后台调度任务而 C#/Unreal 这类以帧为驱动模型的宿主把推进权明确交给了开发者让你在Update()/Tick()中自行控制消息处理的时机与粒度。连接生命周期连接回调注册回调可以观察连接状态的变化。连接建立成功、连接失败、断开连接三种事件分别对应on_connect/on_connect_error/on_disconnectconst conn DbConnection.builder() .withUri(https://maincloud.spacetimedb.com) .withModuleName(my_database) .onConnect((conn, identity, token) { console.log(Connected! Identity: ${identity.toHexString()}); // Save token for reconnection localStorage.setItem(auth_token, token); }) .onConnectError((_ctx, error) { console.error(Connection failed:, error); }) .onDisconnect(() { console.log(Disconnected from SpacetimeDB); });var conn DbConnection.Builder() .WithUri(new Uri(https://maincloud.spacetimedb.com)) .WithModuleName(my_database) .OnConnect((conn, identity, token) { Console.WriteLine($Connected! Identity: {identity}); // Save token for reconnection }) .OnConnectError((error) { Console.WriteLine($Connection failed: {error}); }) .OnDisconnect((conn, error) { if (error ! null) { Console.WriteLine($Disconnected with error: {error}); } else { Console.WriteLine(Disconnected normally); } }) .Build();let conn DbConnection::builder() .with_uri(https://maincloud.spacetimedb.com) .with_module_name(my_database) .on_connect(|_ctx, _identity, token| { println!(Connected! Saving token...); // Save token for reconnection }) .on_connect_error(|_ctx, error| { eprintln!(Connection failed: {}, error); }) .on_disconnect(|_ctx, error| { if let Some(err) error { eprintln!(Disconnected with error: {}, err); } else { println!(Disconnected normally); } }) .build() .expect(Failed to connect);// Create delegates FOnConnectDelegate ConnectDelegate; ConnectDelegate.BindDynamic(this, AMyActor::OnConnected); FOnConnectErrorDelegate ErrorDelegate; ErrorDelegate.BindDynamic(this, AMyActor::OnConnectError); FOnDisconnectDelegate DisconnectDelegate; DisconnectDelegate.BindDynamic(this, AMyActor::OnDisconnected); // Build connection with callbacks UDbConnection* Conn UDbConnection::Builder() -WithUri(TEXT(https://maincloud.spacetimedb.com)) -WithModuleName(TEXT(my_database)) -OnConnect(ConnectDelegate) -OnConnectError(ErrorDelegate) -OnDisconnect(DisconnectDelegate) -Build(); // Callback functions (must be UFUNCTION) UFUNCTION() void OnConnected(UDbConnection* Connection, FSpacetimeDBIdentity Identity, const FString Token) { UE_LOG(LogTemp, Log, TEXT(Connected! Identity: %s), *Identity.ToHexString()); // Save token for reconnection } UFUNCTION() void OnConnectError(const FString Error) { UE_LOG(LogTemp, Error, TEXT(Connection failed: %s), *Error); } UFUNCTION() void OnDisconnected() { UE_LOG(LogTemp, Warning, TEXT(Disconnected from SpacetimeDB)); }回调触发时机的精确语义结合 Rust SDK 源码sdks/rust/src/db_connection.rs与 TypeScript 实现db_connection_builder.ts三个回调有明确的边界on_connect在收到 host 的初始连接消息InitialConnection后触发。回调携带三个参数已连接的DbConnection、本次连接的Identity、以及可用于今后以同一身份重新认证的私有访问令牌若你通过with_token传入了 Token它就是那个 Token。这正是持久化 Token 实现记住我的最佳时机。on_connect_error仅在收到初始连接消息之前的异步连接失败时触发在build()阶段直接抛出的错误不会走这个回调。例如 host 在 WebSocket 建立后、初始消息前拒绝了 Token就会触发它。on_disconnect仅在连接成功建立之后被关闭时触发无论是主动disconnect()还是出错错误参数null/None表示正常断开。如果build()失败或触发的是on_connect_error则不会走到这里。TypeScript 的onDisconnect还约束同一个DbConnectionBuilder上onDisconnect只能注册一次重复调用会抛错第 220-221 行Rust 同样通过panic!限制每种回调只能注册一个并建议注册单个回调来执行多项操作第 1144-1151 行。Unreal 的onConnect回调所接收的Identity与Token参数还必须在UFUNCTION中声明否则不会被引擎执行。主动断开连接当不再需要连接时应显式关闭conn.disconnect();conn.Disconnect();conn.disconnect();Conn-Disconnect();Rust 的disconnect()第 713 行实现值得注意它并非立即关闭 socket而是向pending_mutations队列投递一条Disconnect消息由下一次推进连接时真正执行——这种先入队、后应用的设计queue_mutation第 723-730 行是为了避免在回调执行过程中直接持有内部锁导致死锁。因此断开操作同样依赖于连接推进在 C#/Unreal 中调用了Disconnect()后仍需在循环中调用FrameTick()让断连真正生效。重连行为当前限制与建议注意当前限制自动重连在各 SDK 中的实现并不一致。如果连接被中断你可能需要重新创建一个DbConnection来恢复连接。如果你的应用对连接可靠性有硬性要求建议在应用层自行实现重连逻辑。这是一条重要的工程实践提示不要假设 SDK 会替你处理断线重连。推荐的模式是把build()封装成一个可重复调用的函数配合on_connect_error与on_disconnect回调触发退避重试backoff并结合on_connect中保存的 Token 以同一身份重连。连接身份Identity 与 ConnectionId每次连接都会从服务器获得一个唯一的 Identity通过on_connect回调即可访问.onConnect((conn, identity, token) { console.log(Identity: ${identity.toHexString()}, ConnectionId: ${conn.connectionId}); }).OnConnect((conn, identity, token) { var connectionId conn.ConnectionId; Console.WriteLine($Identity: {identity}, ConnectionId: {connectionId}); }).on_connect(|ctx, identity, token| { let connection_id ctx.connection_id(); println!(Identity: {:?}, ConnectionId: {:?}, identity, connection_id); })UFUNCTION() void OnConnected(UDbConnection* Connection, FSpacetimeDBIdentity Identity, const FString Token) { FSpacetimeDBConnectionId ConnectionId Connection-GetConnectionId(); UE_LOG(LogTemp, Log, TEXT(Identity: %s, ConnectionId: %s), *Identity.ToHexString(), *ConnectionId.ToHexString()); }两者的区别是理解整个鉴权模型的关键核心架构文档Identity长期有效、全局有效的公开标识符跨连接保持不变始终指向同一个终端用户。用户的 Identity 会附加在其发起的每次 reducer 调用上可用于权限判定。模块自身也有 Identity——spacetime publish时会被自动签发一个客户端连接 host 时需要提供它。ConnectionId唯一标识一次连接会话。同一个用户可以在你的数据库上打开多条连接每条都会获得不同的ConnectionId。在 Rust SDK 中Identity 与 ConnectionId 在收到InitialConnection消息后才填充identity/connection_id字段初始为None见 db_connection.rs并分别通过try_identity()与try_connection_id()提供可选访问、connection_id()提供直接访问第 764-778 行。在on_connect回调中读取它们可以保证这两个值一定已经就绪。连接建立之后下一步做什么连接就绪后你可以通过 SDK API 与表交互、调用 reducer、订阅数据注册回调以观察数据库变更表的插入、更新、删除调用服务端的 reducer 与 procedure。各语言专属的详细参考见Rust SDK 参考C# SDK 参考TypeScript SDK 参考Unreal SDK 参考常见问题速查连上了但收不到任何数据检查你是否在 C#/Unity/Unreal 中调用了FrameTick()——这是这些宿主上消息处理的唯一入口Rust 端则确认build()返回的DbConnection没有被编译器警告#[must_use]忽略务必调用frame_tick/run_*之一。Token 被拒区分两种时机build()直接报错握手前拒绝与on_connect_error回调触发WebSocket 建立后、初始消息前拒绝。Token 应为 OIDC 兼容的 JWT获取方式见 SpacetimeAuth 文档。连接中断后怎么办当前 SDK 的自动重连实现并不一致请自行封装重连逻辑并结合on_connect回调保存的 Token 恢复同一身份的会话。如何在多端保持同一用户身份在on_connect中持久化第三个参数Token下次连接时通过with_token传入即可Identity 跨连接不变而每次连接的 ConnectionId 均不同。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门