迁移指南
自 2023 年 6 月起,声网针对市场及行业需求,先后推出了 RTM v2(以下简称 v2)系列版本。此系列版本在功能、性能、体验上都有创新性提升:
-
功能覆盖:该版本通过引入
Channel
、Message
、Topic
、Presence
、Storage
和Lock
等功能模块,能覆盖更多业务场景,你可以把更多的精力集中在自己的业务创新上。 -
性能提升:我们在新版本中重构了后台架构,通过优化网络连接进一步提升性能,提供长时间低延迟、高可靠、大并发、易扩展的实时网络接入能力,让你无需为业务质量担忧。
-
体验优化:我们重新设计并简化了 API 接口;优化了包括用户指南、API 参考在内的所有文档,提供了更全面的示例程序,支持开发者低成本学习使用 SDK,快速完成集成,提高开发效率。
本文提供 RTM v1 和 v2 的主要区别及相关示例代码,从而帮助用户顺利地从 v1 迁移到 v2。
开通服务
开通 v2 和 v1 的步骤相同:
- 注册并登录声网控制台
- 创建声网项目
- 进入功能配置,开通 RTM 服务
- 获取开发所需参数,例如 App ID
如果你没有声网账号,点击此处注册账号。关于如何开通服务和如何创建项目,详见开通服务。
集成 SDK
v2 和 v1 的 SDK 包名、集成方式都不变。详见项目配置。
初始化实例
相比于 v1,v2 对初始化参数做了很大的调整,增加了很多新特性,例如端侧加密、云代理等,详见 API 参考。此外,v2 还丰富了接口调用的错误信息,你可以通过 RTM_ERROR_CODE
数据类型获取错误码,以便于你能更快地排查故障。结合错误排查文档,你也可以快速找到解决方法。
RTM v2 版本在命名字符集的支持上与 v1 版本存在差异。例如,v2 版本不支持以 "_"
开头或包含 "."
字符的频道名、用户名和 Topic 名等。在从 v1 升级到 v2 或混合使用这两个版本时,需注意字符集差异可能导致的不兼容问题。建议在迁移时统一使用 v2 版本支持的字符集。
-
v1 的示例代码如下:
C++class RtmEventHandler : public agora::rtm::IRtmServiceEventHandler {
// ...
};
// 创建 RTM 实例
agora::rtm::IRtmService* p_rs = agora::rtm::createRtmService();
rtmService.reset(p_rs, [](agora::rtm::IRtmService* p) {
p->release();
});
// 初始化 RTM 实例
if (rtmService->initialize("your_appId", new RtmEventHandler()) {
// 处理初始化报错
} -
v2 的示例代码如下:
C++class RtmEventHandler : public IRtmEventHandler {
// ...
};
// 创建并初始化 RTM 实例
RtmConfig config;
config.appId = "your_appid";
config.userId = "your_name";
config.eventHandler = new RtmEventHandler();
int errorCode = 0;
IRtmClient* rtmClient = createAgoraRtmClient(config, errorCode);
if (!rtmClient || errorCode != 0) {
// 处理初始化报错
}
登录服务
v2 登录服务的方法和 v1 有区别,如下所示:
-
v1 的示例代码如下:
C++// 方法调用
if (rtmService->login("your_token", "your_userId")) {
// 处理登录报错
}
// 异步回调
class RtmEventHandler : public agora::rtm::IRtmServiceEventHandler {
virtual void onLoginSuccess() override {
// 登录成功
}
virtual void onLoginFailure(agora::rtm::LOGIN_ERR_CODE errorCode) override {
// 登录失败
}
// ...
}; -
v2 的示例代码如下:
C++// 方法调用
uint64_t requestId = 0;
rtmClient->login("your_token", requestId);
// 异步回调
class RtmEventHandler : public IRtmEventHandler {
void onLoginResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) {
if (errorCode != RTM_ERROR_OK) {
// 登录失败
} else {
// 登录成功
}
}
// ...
};
事件通知
相比于 v1,v2 重新设计了系统事件通知方式和 API 接口,对事件通知类型进行了更详细的分类和聚合,对事件通知的负载数据结构进行了优化。
v2 的事件通知类型有 7 种,如下所示:
事件类型 | 描述 |
---|---|
onMessageEvent | 消息事件通知:接收用户所订阅的 Message Channel 及 Topic 中的所有消息事件通知。 |
onPresenceEvent | 用户出席与自定义状态变更事件通知(简称 Presence 事件通知):接收用户所订阅的 Message Channel 及加入的 Stream Channel 中所有的 Presence 事件通知。 |
onTopicEvent | Topic 变更事件通知:接收用户所加入的 Stream Channel 中所有 Topic 变更事件通知。 |
onStorageEvent | 频道属性和用户属性事件通知:接收用户所订阅的 Message Channel 及加入的 Stream Channel 中所有的 Channel Metadata 事件通知,及订阅用户的 User Metadata 事件通知。 |
onLockEvent | 锁变更事件通知:接收用户所订阅的 Message Channel 及加入的 Stream Channel 中所有的 Lock 事件通知。 |
onConnectionStateChanged | (已废弃)网络连接状态变更事件通知:接收客户端网络连接状态变更的事件通知。 |
onLinkStateEvent | 接收客户端网络连接状态变更的事件通知,包含变更前后的连接状态、服务类型、导致变更的操作类型、变更原因、频道列表等信息。 |
onTokenPrivilegeWillExpire | 接收客户端 Token 将要过期的事件通知。 |
关于事件通知的更多信息及负载数据结构见事件监听。
以监听频道消息 onMessageEvent
事件通知为例,示例代码如下:
-
v1:
C++class ChannelEventHandler: public agora::rtm::IChannelEventHandler {
public:
ChannelEventHandler(string channel) {
channel_ = channel;
}
~ChannelEventHandler() {}
virtual void onMessageReceived(const char* userId,
const agora::rtm::IMessage *msg) override {
// 处理接收到的消息
}
// ...
};
std::string channelName = "channelName";
agora::rtm::IChannel * channel = rtmService->createChannel(channelName, new ChannelEventHandler(channelName)); -
v2:
C++class RtmEventHandler: public IRtmEventHandler {
void onMessageEvent(const MessageEvent &event) {
// 处理接收到的消息
}
// ...
};
RtmConfig config;
config.eventHandler = new RtmEventHandler();
从上面的示例代码可以看出以下显著区别:
-
v1 的频道消息事件通知绑定在具体的
channel
实例上,用户需要先调用createChannel()
方法创建channel
实例,然后注册onMessageReceived
回调处理事件,SDK 会在收到消息后通过该回调通知处理程序,并且多个频道要实现绑定多次。而 v2 的消息事件通知绑定在客户端实例上,是全局设置,只需要绑定一次,就可以监听所有订阅的频道或 Topic。 -
v1 的消息事件通知负载数据结构包含的信息相对较少,而 v2 的消息事件通知负载数据结构包含了更多的信息,可以帮助你更好地实现自己的业务。
频道消息
在 1.x 中,发送频道消息的步骤如下:
-
创建一个频道实例
-
加入频道
-
发送频道消息
这种设计会有个弊端,你无法实现发送消息而不收到消息。因为发送消息和接收消息没有解耦。v2 采用了基于 Pub/Sub 的新设计,将频道消息的发送和接收解耦:你无需加入频道即可向指定频道发送消息,你只需订阅指定频道即可接收该频道中的消息,两种操作互不影响。
-
v1 的示例代码如下:
C++// 创建频道
std::string channelName = "channelName";
agora::rtm::IChannel * channel = rtmService->createChannel(channelName, new ChannelEventHandler(channelName));
// 加入频道
channel->join();
// 发送频道消息
std::string message = "Hello World!";
agora::rtm::IMessage* rtmMessage = rtmService->createMessage();
rtmMessage->setText(message.c_str());
channel->sendMessage(rtmMessage);
rtmMessage->release(); -
v2 的示例代码如下:
C++// 发送频道消息
std::string message = "hello world";
PublishOptions options;
options.messageType = RTM_MESSAGE_TYPE_STRING;
options.channelType = RTM_CHANNEL_TYPE_MESSAGE;
options.customType = "PlainText";
uint64_t requestId;
rtmClient->publish("channelName", message.c_str(), message.size(), options, requestId);
// 发送频道消息的异步回调
class RtmEventHandler : public IRtmEventHandler {
void onPublishResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
if (errorCode != RTM_ERROR_OK) {
// publish message failed
} else {
// publish message success
}
}
// ...
};
// 订阅频道
SubscribeOptions options;
options.withMessage = true;
uint64_t requestId;
rtmClient->subscribe("channelName", options, requestId);
// 订阅频道的异步回调
class RtmEventHandler : public IRtmEventHandler {
void onSubscribeResult(const uint64_t requestId, const char *channelName, RTM_ERROR_CODE errorCode) {
if (errorCode != RTM_ERROR_OK) {
// subscribe failed
} else {
// subscribe success
}
}
// ...
};
点对点消息
v1 版本中的点对点消息 API 用于向指定的用户发送消息,例如,你如果需要向用户 ID 为 Tony
的用户发送消息,你可以按照如下方式实现:
// v1
std::string message = "Hello World!";
agora::rtm::IMessage* rtmMessage = rtmService->createMessage();
rtmMessage->setText(message.c_str());
int ret = rtmService->sendMessageToPeer("Tony", rtmMessage);
rtmMessage->release();
if (ret) {
// 发送消息失败
}
自 2.2.1 版本起,我们对点对点消息进行了调整,将其定义为用户频道(User Channel):通过 publish
方法发送消息,通过 onMessageEvent
事件通知接收消息。这一新方法的实现效果与 v1 版本中点对点消息的功能完全一致你,你可以按照以下方式去实现:
// v2
// 适用于 2.2.0 及之后版本
PublishOptions options;
options.messageType = RTM_MESSAGE_TYPE_STRING;
options.channelType = RTM_CHANNEL_TYPE_USER;
options.customType = "PlainText";
std::string message = "Hello world";
uint64_t requestId;
rtmClient->publish("Tony", message.c_str(), message.size(), options, requestId);
当然,你也可以通过 Message Channel 来实现收件箱功能。只不过这种方式,发送方无法收到送达回执。
// v2
// 1. 订阅自己的收件箱
SubscribeOptions options;
options.withMessage = true;
uint64_t requestId;
rtmClient->subscribe("inbox_Tony", options, requestId);
// 2. 向 Lily 发送消息
std::string message = "This is a message";
PublishOptions options;
uint64_t requestId;
rtmClient->publish("inbox_Lily", message.c_str(), message.size(), options, requestId);
图片与文件消息
出于对用户数据和隐私保护合规性、成本优化的考虑,RTM 自 1.5.0 版本起不再直接支持传输图片和文件消息,相关的 API 也已经下架。你可以结合 RTM 和第三方对象存储服务(例如 Amazon S3 或者阿里云 OSS)来构建图片与文件消息功能,在获得极佳的实时消息传输体验的同时,还能实现更灵活的技术构建方案,例如,实现 CDN 静态资源加速、图片文本审核等业务需求。以下示例代码向你展示如何使用 v2 和 Amazon S3 对象性存储服务实现图片和文件消息的构建和发送:
// 1. 上传文件至 Amazon S3
// 2. 通知 RTM
nlohmann::json jsonObject;
// 文件类型,接收方可以根据此字段解析消息包结构
jsonObject.put("type", "file");
// 你在 Amazon S3 上的 bucket 名称,接收方需要此字段来下载文件
jsonObject.put("bucket", "uploadParams.Bucket");
// 文件在 Amazon S3 存储的 Key,接收方需要此字段来下载文件
jsonObject.put("key", "uploadParams.Key");
// 文件内容类型
jsonObject.put("contentType", "uploadParams.ContentType");
// 文件 URL 地址
jsonObject.put("url", "data.Location");
PublishOptions options;
uint64_t requestId;
rtmClient->publish("receiver", jsonObject.dump().c_str(), jsonObject.dump().size(), options, requestId);
使用 Amazon S3 实现静态文件存储时,你需要进入 Amazon S3 控制台,然后设置正确的用户权限和访问策略。详见访问控制最佳实践。
用户出席与自定义状态
在 v1 中,你可以订阅或查询多个用户的在线状态、查询频道人数或频道在线成员列表等。v2 不仅保留了这些能力,还在 v1 的基础上进行了升级和扩展,新设计了 Presence 模块。Presence 模块提供监控用户上线、下线及用户临时状态变更的能力,你可以实时获取以下信息:
- 用户加入或离开指定频道
- 自定义临时用户状态及其变更
- 查询指定用户加入或订阅了哪些频道
- 查询指定频道有哪些用户加入及其用户临时状态数据
你可以调用 whoNow
方法,实时查询指定频道的在线用户数量、在线用户列表及在线用户的临时状态等信息。
// v2
// 方法调用
PresenceOptions options;
options.includeState = true;
options.includeUserId = true;
options.page = "yourBookMark";
uint64_t requestId
rtmClient->getPresence()->whoNow("channelName", RTM_CHANNEL_TYPE_MESSAGE, options, requestId);
// 异步回调
class RtmEventHandler : public IRtmEventHandler {
void onWhoNowResult(const uint64_t requestId, const UserState *userStateList, const size_t count, const char *nextPage, RTM_ERROR_CODE errorCode) override {
if (errorCode != RTM_ERROR_OK) {
// 查询在线用户失败
} else {
// 处理 whoNow 结果
}
}
// ...
};
你可以调用 whereNow
方法,实时获取指定用户所在频道的列表。
// v2
// 方法调用
uint64_t requestId;
rtmClient->getPresence()->whereNow("tony", requestId);
// 异步回调
class RtmEventHandler : public IRtmEventHandler {
void onWhereNowResult(const uint64_t requestId, const ChannelInfo *channels, const size_t count, RTM_ERROR_CODE errorCode) override {
if (errorCode != RTM_ERROR_OK) {
// 查询用户所在频道失败
} else {
// 处理 whereNow 结果
}
}
// ...
};
为满足业务场景对用户状态的设置需求,v2 提供了设置临时用户状态的能力,你可以通过 setState
方法自定义临时用户状态。用户可以为自己添加分数、游戏状态、位置、心情、连麦状态等自定义状态。
// v2
// 方法调用
std::vector<StateItem> stateItems;
StateItem item;
item.key = "mood";
item.value = "pumped";
stateItems.push_back(item);
uint64_t requestId;
rtmClient->getPresence()->setState("channelName", RTM_CHANNEL_TYPE_MESSAGE, stateItems.data(), stateItems.size(), requestId);
// 异步回调
class RtmEventHandler : public IRtmEventHandler {
void onPresenceSetStateResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
if (errorCode != RTM_ERROR_OK) {
// 设置状态失败
} else {
// 设置状态成功
}
}
// ...
};
你也可以随时通过 getState
方法获取用户的在线状态,或者通过 removeState
方法删除用户的状态。用户临时状态变更后,RTM 服务器会触发 RTM_PRESENCE_EVENT_TYPE_REMOTE_STATE_CHANGED
类型的 onPresenceEvent
事件通知。具体用法见临时用户状态。
在 v2 中,实时监听频道中用户加入、离开、超时掉线或临时状态变更通知会更加方便,你只需实现如下步骤:
-
实现 Presence 事件监听程序
-
加入频道时,开启
withPresence
开关
// v2
// 1. 实现 Presence 事件监听程序
class RtmEventHandler : public IRtmEventHandler {
void onPresenceEvent(const PresenceEvent& event) override {
// 处理 Presence 事件
}
// ...
};
RtmConfig config;
config.eventHandler = new RtmEventHandler();
// 2. 订阅频道时,开启 withPresence 开关
SubscribeOptions options;
options.withPresence = true;
uint64_t requestId;
rtmClient->subscribe("channelName", options, requestId);
v2 对实时通知进行了全新的设计,在频道中采取两种模式将 Presence 事件通知到订阅用户:实时通知模式 (Announce) 和定时通知模式 (Interval)。你可以在控制台的项目设置中通过实时通知最大人数大小来决定两种模式相互切换的条件。其中,定时通知模式可防止频道内在线用户过多而导致的事件嘈杂,详见事件通知模式。
用户属性与频道属性
基于 v1 的用户属性和频道属性功能,v2 新增了版本控制和锁控制等能力,并优化了 API 接口形式,让功能的使用更加简单。v2中把用户属性和频道属性挂载在 Storage 模块下,以设置频道属性为例,示例代码如下:
// v2
// 方法调用
// 创建 Metadata
Metadata metadata;
// 设置 Major Revision
data.majorRevision = 174298270;
// 设置 Metadata Item
std::vector<agora::rtm::MetadataItem> items;
MetadataItem item0("Apple", "100", 174298200);
MetadataItem item1("Banana", "200", 174298100);
items.emplace_back(item0);
items.emplace_back(item1);
metadata.items = items.data();
metadata.itemCount = items.size();
// 记录设置 Metadata Item 的时间戳和用户 ID
MetadataOptions options;
options.recordTs = true;
options.recordUserId = true;
uint64_t requestId;
rtmClient->getStorage()->setChannelMetadata("channelName", RTM_CHANNEL_TYPE_MESSAGE, metadata, options, "lockName", requestId);
// 异步回调
class RtmEventHandler : public IRtmEventHandler {
void onSetChannelMetadataResult(const uint64_t requestId, const char *channelName, RTM_CHANNEL_TYPE channelType, RTM_ERROR_CODE errorCode) override {
if (errorCode != RTM_ERROR_OK) {
// 设置频道属性失败
} else {
// 设置频道属性成功
}
// ...
}
};
如何获取、更新、删除频道属性,如何使用 CAS 控制、锁控制等可以参考频道属性。用户属性的使用与频道属性是类似的,详见用户属性。
v2 通过 onStorageEvent
类型的事件通知将频道属性和用户属性分发给用户,监听 onStorageEvent
事件通知的步骤如下:
-
实现 Storage 事件监听程序
-
加入频道时,开启
withMetadata
开关
// v2
// 1. 实现 Storage 事件监听程序
class RtmEventHandler : public IRtmEventHandler {
void onStorageEvent(const StorageEvent& event) override {
// 处理 Storage 事件
}
// ...
};
RtmConfig config;
config.eventHandler = new RtmEventHandler();
// 2. 加入频道时,开启 withMetadata 开关
SubscribeOptions options;
options.withMetadata = true;
uint64_t requestId;
rtmClient->subscribe("channelName", options, requestId);
访问区域限定
RTM 支持限定访问区域功能,以适应不同国家或地区的法律法规。开启限定访问区域功能后,不论用户在哪个区域使用你的 App,SDK 都只会访问指定区域的声网服务器。v2 实现访问区域限定的代码和 v1 有区别。
-
v1 的示例代码如下:
C++// v1
agora::rtm::RtmServiceContext context;
context.areaCode = agora::rtm::AREA_CODE_GLOB;
setRtmServiceContext(context); -
v2 的示例代码如下:
C++// v2
RtmConfig config;
config.areaCode = RTM_AREA_CODE_GLOB;
其他新特性
除上述版本之间的功能差异之外,v2 还额外增加了很多被广泛验证和使用的新功能,你可以根据项目的需要选择和使用,相信革新版本的 v2 一定可以帮助你快速接入实时互动领域。v2 所具备的功能特性见特性列表。