实现收发消息
本文将指导你如何利用 RTM SDK 构建简单的应用程序,内容涵盖了集成和开发的基础知识:开通声网账号、获取 SDK、发送消息、接收消息等。
前提条件
开始前,请检查你的开发环境和运行环境是否满足如下要求:
-
Unity Hub 任意版本
-
Unity 2018.4.0 或以上版本
-
操作系统与编译器要求:
开发平台 操作系统版本 编译器版本 Android Android 4.1 或以上 Android Studio 3.0 或以上 iOS iOS 9.0 或以上 Xcode 9.0 或以上 macOS macOS 10.10 或以上 Xcode 9.0 或以上 Windows Windows 7 或以上 Microsoft Visual Studio 2017 或以上
开通服务
开始构建项目前,你需要先完成一些准备工作。检查你的浏览器是否满足平台支持中的最低版本要求,然后依次完成以下步骤。
1. 注册账号
使用声网服务的第一步是注册声网账号,点击注册账号前往控制台注册。如果你已经拥有声网账号,那么你可以直接登录控制台。
你可以在控制台中查看项目配置、用量分析、账单报表等所有项目相关的信息,请务必保管好你的声网账号和密码。
2. 创建项目
成功登录控制台后,按照以下步骤创建一个声网项目:
-
点击左侧导航栏的项目管理按钮进入项目管理页面。
-
在项目管理页面,点击创建项目按钮。
-
在弹出的对话框内输入项目名称,选择产品为云信令(原实时消息)、鉴权机制为调试模式:APP ID。

- 点击创建项目,新建的项目就会显示在项目管理页中。
3. 获取 App ID
每个项目会被分配一个 App ID 作为项目唯一标识。在项目管理页面找到你的项目,点击 App ID 一栏右侧的复制按钮进行复制,后续的步骤中我们将会使用到它。

获取 SDK
点击此处下载最新版本的 Unity SDK,并在你的项目中导入它。
导入 SDK
-
打开 Unity Hub,点击 New Project 新建一个 Unity 3D 项目。
-
点击顶部菜单栏的 Assets 菜单,依次选择 Import Package、Custom Package 向项目中导入你刚刚获取的 RTM Unity Package。
初始化 RTM
调用 RTM SDK 的任何 API 之前,需要先初始化一个 IRtmClient
对象实例。参考如下步骤创建用户界面、初始化一个 IRtmClient
对象实例并添加项目脚本:
初始化 RTM 的时候可以通过配置某些参数改变 IRtmClient
其后续行为,例如配置事件监听函数、端侧消息加密等。详见初始配置。
-
我们为你提供了一个简单的示例项目,其中进行了简易的 UI 界面设计以便于验证功能特性。UI 界面及组建架构如下图所示:
JSONSampleScene
- Main Camera
- Canvas
- Image // 窗口背景
- Txt1 // 文本标签,文本内容为 Send string message to message channel
- InputField1 // 消息输入框
- Btn1 // 发送消息按钮
- Txt2 // 文本标签,文本内容为 Send binary message to stream channel
- Btn2 // 创建 Stream Channel 按钮
- Btn3 // 加入 Stream Channel 按钮
- Btn4 // 加入 Topic 按钮
- Btn5 // 订阅 Topic 中的消息发布者按钮
- InputField2 // 消息输入框
- Btn6 // 发送消息按钮
- Btn7 // 释放 Stream Channel 按钮
- Btn8 // 离开 Stream Channel 按钮
- Btn9 // 离开 Topic 按钮
- Btn10 // 取消订阅 Topic 中的消息发布者按钮
- Pannel // 信息输出面板
- Txt3 // 信息输出文字
- EventSystem -
在 Unity 编辑器中,选择顶部菜单栏的 Assets 菜单,依次点击 Create、C# Script 为项目新建脚本,名为 HelloWorld.cs。
-
在 HelloWorld.cs 脚本中添加以下代码作为程序模版。你会发现此程序并不完整,不用紧张,我们在后续步骤中将一步步指导你补充完善代码。
Assets/Hello_world.csC#using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Agora.Rtm;
public class HelloWorld : MonoBehaviour
{
[SerializeField]
private string appId = "your_appId";
// Get an App ID from console portal
private string userId = "your_userId";
// Change the user ID by yourself
private string msChannelName = "SeeYou";
private string stChannelName = "Greeting";
private string topicName = "Hello_world";
private IRtmClient rtmClient;
private IStreamChannel streamChannel;
public void Awake()
{
Initialize();
}
// Initialize the RTM
public async void Initialize()
{
// Initialize the RTM Client
// Add listener
if (rtmClient != null)
{
// Add message event listener
rtmClient.OnMessageEvent += OnMessageEvent;
// Add the presence event listener
rtmClient.OnPresenceEvent += OnPresenceEvent;
// Add the connection state change listener
rtmClient.OnConnectionStateChange += OnConnectionStateChanged;
// Login to the RTM server
// Subscribe to a Message Channel
}
}
private void ChangeComponentView(string[] componentsList, string viewType)
{
for (int i = 0; i < componentsList.Length; i++)
{
if (viewType == "ACTIVATE")
GameObject.Find(componentsList[i]).GetComponent<Button>().interactable = true;
else if (viewType == "INACTIVATE")
GameObject.Find(componentsList[i]).GetComponent<Button>().interactable = false;
}
}
private void ShowMessage(string msg)
{
GameObject.Find("Txt3").GetComponent<Text>().text += "\n" + msg;
}
// Implement the event listener handler
// OnClick event handler for Btn1
public async void PublishStringMessage()
{
// Publish string messages to Message Channel
}
// OnClick event handler for Btn6
public async void PublishBinaryMessage()
{
// Publish binary messages to a Stream Channel
}
// Onclick event handler for Btn2
public async void CreateStChannel()
{
string[] activeComponentsList = { "Btn3", "Btn7" };
string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" };
// Create a Stream Channel
}
// Onclick event handler for Btn3
public async void JoinStChannel()
{
string[] activeComponentsList = { "Btn4", "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn9", "Btn10","Btn7" };
// Join a Stream Channel
}
// Onclick event handler for Btn4
public async void JoinTopic()
{
string[] activeComponentsList = { "Btn6", "Btn8", "Btn9" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn4","Btn7" };
// Join a topic
}
// Onclick event handler for Btn5
public async void SubscribeTopic()
{
string[] activeComponentsList = { "Btn8", "Btn10" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn5","Btn7" };
// Subscribe a publisher from a topic
}
// Onclick event handler for Btn7
public void ReleaseStChannel()
{
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
// Release a Stream Channel
}
// Onclick event handler for Btn8
public async void LeaveStChannel()
{
string[] activeComponentsList = { "Btn3", "Btn7" };
string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" };
// Leave a Stream Channel
}
// Onclick event handler for Btn9
public async void LeaveTopic()
{
string[] activeComponentsList = { "Btn4", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn7", "Btn9" };
// Leave a topic
}
// Onclick event handler for Btn10
public async void UnsubscribeTopic()
{
string[] activeComponentsList = { "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn7", "Btn10" };
// Unsubscribe a publisher from a topic
}
private async void OnDestroy()
{
// Dispose a Stream Channel
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
if (result.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var result1 = streamChannel.Dispose();
}
if (rtmClient != null)
{
var result2 = await rtmClient.UnsubscribeAsync(msChannelName);
if (result2.Status.Error)
{
Debug.Log(string.Format("{0} is failed", result2.Status.Operation));
Debug.Log(string.Format("The error code is {0}, because of: {1}",result2.Status.ErrorCode, result2.Status.Reason));
}
else
{
Debug.Log("Unsubscribe Channel Success!");
}
var result = rtmClient.Dispose();
Debug.Log("Dispose rtmClient Success!");
rtmClient = null;
}
}
}
消息格式
收发信息是 RTM 基础的能力之一,基于 RTM 实时网络,你在全球任何地方发送的信息都可以在 100 毫秒内安全可靠的送到到接收端。用户可以给一个人发送消息或一次性给成千上万人广播消息。
RTM 一条消息包的大小限制在 1 KB 以内,消息负载支持字符串格式或者二进制格式:
字符串类型消息格式:
string message = "This is a string message";
二进制类型消息格式:
byte[] message = System.Text.Encoding.Default.GetBytes("This message will be converted to binary");
根据你的应用场景需要,你可以选择字符串类型消息格式或者二进制类型消息格式,收到消息后通过 MessageEvent
中 messageType
字段加以区分。
你也可以使用 JSON
或 Protobuf
等方式来构建你的负载结构以便于简化业务逻辑,此部分内容可以参考消息负载序列化。但请记住在把消息负载转交给 RTM 发送前,需要对消息负载进行序列化。
初始化 RTM
在 HelloWorld.cs 中调用 CreateAgoraRtmClient
方法初始化 IRtmClient
。你需要提供如下信息:
- 从控制台获取的 App ID(
appId
)。 - 你需要定义用户 ID(
userId
),并保证其全局唯一性。
将以下代码添加到程序中,并将程序模版中的 "your_appId"
、"your_userId"
替换成你的 appId
和 userId
。
// Paste the following code snippet below "Initialize RTM Client" comment
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(appId))
{
ShowMessage("We need a userId and appId to initialize!");
return;
}
LogConfig logConfig = new LogConfig();
// Set the log file path.
logConfig.filePath = "myFilePath";
// Set the file size of the agora.log file.
logConfig.fileSizeInKB = 512;
// Set the log report level
logConfig.level = LOG_LEVEL.LOG_LEVEL_INFO;
RtmConfig config = new RtmConfig();
config.appId = appId;
config.userId = userId;
config.logConfig = logConfig;
// Create the RTM Client
try
{
rtmClient = RtmClient.CreateAgoraRtmClient(config);
ShowMessage("RTM Client Initialize Sucessfull");
// Inactive unnecessary components
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
catch (RTMException e)
{
ShowMessage(string.Format("{0} is failed", e.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", e.Status.ErrorCode, e.Status.Reason));
}
添加事件监听
事件监听程序帮助你实现频道中消息、事件到达后的处理逻辑,添加以下代码到你的程序中以显示收到的消息或事件通知:
// Paste the following code snippet below "Implement the event listener handler" comment
// Implement the connection state change event handler
private void OnConnectionStateChanged(string channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason)
{
ShowMessage(string.Format("Channel:{0} connection state have changed to:{1} because of {2}", channelName, state, reason));
}
// Implement the presence event handler
private void OnPresenceEvent(PresenceEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var eventType = eve.type;
var publisher = eve.publisher;
var stateItems = eve.stateItems;
var interval = eve.interval;
var snapshot = eve.snapshot;
switch (eventType)
{
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_SNAPSHOT:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("You have sub or join channel:{0} channe type is:{1}", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_INTERVAL:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("The channel:{0} channe type is:{1} is now in interval mode!", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_REMOTE_JOIN_CHANNEL:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("User:{0} sub or join channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_REMOTE_LEAVE_CHANNEL:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("User:{0} unsub or leave channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_REMOTE_CONNECTION_TIMEOUT:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("User:{0} timeout from channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_REMOTE_STATE_CHANGED:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("User:{0} state change in channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
}
}
// Implement the message event handler
private void OnMessageEvent(MessageEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var topic = eve.channelTopic;
var publisher = eve.publisher;
var messageType = eve.messageType;
var message = eve.message;
if (messageType == RTM_MESSAGE_TYPE.RTM_MESSAGE_TYPE_STRING)
{
var stMessage = message.GetData<string>();
ShowMessage(string.Format("You have recieved a string type message: {0} from: {1} in channel:{2}", stmessage, publisher, channelName));
ShowMessage(string.Format("The channel type is {0}"),channelType);
}
else
{
var biMessage = message.GetData<byte[]>();
ShowMessage(string.Format("You have recieved a binary type message: \"{0}\" from: {1} in channel:{2}", System.BitConverter.ToString(bimessage), publisher, channelName));
ShowMessage(string.Format("The channel type is {0}"),channelType);
}
}
登录服务
我们需要执行登录操作以建立与 RTM 服务器的连接,然后才能调用 SDK 的其他 API。调用 LoginAsync
方法并传入 token
参数以验证身份。
当前项目在创建的时候选择的是调试模式以便于快速验证功能,所以可以用 appId
代替 Token 执行登录操作。
将以下代码添加到 HelloWorld.cs 中:
// Paste the following code snippet below "Login to the RTM server" comment
var result = await rtmClient.LoginAsync(appId);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage("Login Successfully");
}
Message Channel 中收发消息
调用 PublishAsync
方法向 Message Channel 发送消息后,RTM 会把该消息分发给该频道的所有订阅者,以下代码演示如何发送 String 类型的消息,将此代码片段添加到程序中:
// Paste the following code snippet below "Publish string messages to a message channel" comment
var message = GameObject.Find("InputField1").GetComponent<InputField>().text;
if (rtmClient != null)
{
PublishOptions publishOptions = new PublishOptions()
{
type = RTM_MESSAGE_TYPE.RTM_MESSAGE_TYPE_BINARY
};
var result = await rtmClient.PublishAsync(msChannelName, message, publishOptions);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed, The error code is {1}", result.Status.Operation, result.Status.ErrorCode));
}
else
{
ShowMessage("Publish Message Success!");
}
}
调用 SubscribeAsync
方法订阅此频道以接收此频道中的消息。将以下代码添加到程序中:
// Paste the following code snippet below "Subscribe to a Message Channel" comment
SubscribeOptions subscribeOptions = new SubscribeOptions()
{
withMessage = true,
withPresence = true
};
var result2 = await rtmClient.SubscribeAsync(msChannelName, subscribeOptions);
if (result2.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result2.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result2.Status.ErrorCode, result2.Status.Reason));
}
else
{
ShowMessage(string.Format("Subscribe channel success! at Channel:{0}", result2.Response.ChannelName));
}
成功订阅后,你可以通过之前监听的 OnMessageEvent
事件通知接收消息。
Stream Channel 中收发消息
在 Stream Channel 中的消息需要依赖 Topic 功能,你需要依次完成以下步骤:
创建 Stream Channel
调用 Stream Channel 的所有方法前,你需要创建频道并获得 IStreamChannel
对象实例。将以下代码添加到程序中:
// Paste the following code snippet below "Create a Stream Channel" comment
if (rtmClient != null)
{
streamChannel = rtmClient.CreateStreamChannel(stChannelName);
ShowMessage("Create Stream Channel:" + stChannelName + "Success !");
// Disable unnecessary components
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
加入 Stream Channel
加入 Stream Channel 才可以在后续进行发布和订阅消息的操作。调用 JoinAsync
方法并在 options
中传入 token
参数以验证身份。
当前项目在创建的时候选择的是调试模式以便于快速验证功能,所以可以用 appId
代替 token
执行加入频道操作。
在 HelloWorld.cs 中添加如下代码:
// Paste the following code snippet below "Join a Stream Channel" comment
if (streamChannel != null)
{
JoinChannelOptions options = new JoinChannelOptions();
options.withPresence = true;
options.token = appId;
var result = await streamChannel.JoinAsync(options);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join stream channel success! at Channel:{1}", result.Response.UserId, result.Response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
加入 Topic
调用 JoinTopicAsync
方法会将你注册成为 Topic 的消息发布者之一,以便于你后续向该 Topic 发送消息。将以下代码添加到频道中:
// Paste the following code snippet below "Join a topic" comment
if (streamChannel != null)
{
JoinTopicOptions joinTopicOptions = new JoinTopicOptions();
joinTopicOptions.qos = RTM_MESSAGE_QOS.RTM_MESSAGE_QOS_ORDERED;
joinTopicOptions.syncWithMedia = false;
var result = await streamChannel.JoinTopicAsync(topicName, joinTopicOptions);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join Topic:{1} success! at Channel:{2}", result.Response.UserId, result.Response.Topic, result.Response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
在 Topic 中发布消息
成功注册为 Topic 的发布者后,你就可以调用 PublishTopicMessageAsync
方法在 Topic 中发送消息了,RTM 会把此消息分发给该 Topic 的所有订阅者。以下示例向你展示如何发送 Binary 类型消息:
// Paste the following code snippet below "Publish binary messages to a Stream Channel" comment
var message = GameObject.Find("InputField2").GetComponent<InputField>().text;
byte[] byteMsg = System.Text.Encoding.Default.GetBytes(message);
if (streamChannel != null)
{
PublishOptions publishOptions = new PublishOptions();
publishOptions.type = RTM_MESSAGE_TYPE.RTM_MESSAGE_TYPE_BINARY;
publishOptions.sendTs = 0;
var result = await streamChannel.PublishTopicMessageAsync(topicName, byteMsg, publishOptions);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage("Publish Binary Message Success!");
}
}
订阅 Topic 中的发布者
你可以通过调用 SubscribeTopicAsync
方法订阅某个 Topic 中的一位或多位消息发布者以便于接收他们发送的消息。单个客户端在单个 Topic 中最多只能订阅 64 位消息发布者,详见 API 使用限制。
将以下代码添加到程序中:
// Paste the following code snippet below "Subscribe a publisher from a topic" comment
List<string> userList = new List<string>();
userList.Add("Tony");
TopicOptions topicOptions = new TopicOptions();
topicOptions.users = userList.ToArray();
var result = await streamChannel.SubscribeTopicAsync(topicName, topicOptions);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Subscribe Topic:{1} success! at Channel:{2}", result.Response.UserId, result.Response.Topic, result.Response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
成功订阅后,你可以通过之前监听的 OnMessageEvent
事件通知接收消息。
取消订阅 Topic 中发布者
如果你不再需要接收某 Topic 中某一位发布者的消息,调用 UnsubscribeTopicAsync
方法取消对他的订阅。如需取消订阅整个 Topic,在 topicOptions
中不填 users
参数即可。
将以下代码添加到程序中:
// Paste the following code snippet below "Unsubscribe a publisher from a topic" comment
List<string> userList = new List<string>();
userList.Add("Tony");
TopicOptions topicOptions = new TopicOptions();
topicOptions.users = userList.ToArray();
var result = await streamChannel.UnsubscribeTopicAsync(topicName, topicOptions);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("Unsubscribe Topic Success!"));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
离开 Topic
如果你不再需要在某 Topic 中发布消息,调用 LeaveTopicAsync
方法取消成为该 Topic 的消息发布者。
离开 Topic 不会影响你的订阅操作,你依旧可以从你订阅的 Topic 中接收消息。
将以下代码添加到程序中:
// Paste the following code snippet below "Leave a topic" comment
if (streamChannel != null)
{
var result = await streamChannel.LeaveTopicAsync(topicName);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Leave Topic:{1} success! at Channel:{2}", result.Response.UserId, result.Response.Topic, result.Response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
退出频道
当你不再关心此频道中的动态,即不再发送和接收消息,可以调用 LeaveTopicAsync
方法离开该频道。
将以下代码添加到程序中:
// Paste the following code snippet below "Release a Stream Channel" comment
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
if (result.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var result1 = streamChannel.Dispose();
}
销毁频道
如果你不再需要某个 Stream Channel,你可以调用 Dispose
方法销毁该频道以释放资源。
在 HelloWorld.cs 中添加如下代码:
// Paste the following code snippet below "Dispose a Stream Channel" comment
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
if (result.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var result1 = streamChannel.Dispose();
}
组合到一起
经过上述步骤,你的 HelloWorld.cs 文件中的代码应该如下所示:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Agora.Rtm;
public class HelloWorld : MonoBehaviour
{
[SerializeField]
private string appId = "your_appId";
// Get an App ID from console portal
private string userId = "your_userId";
// Change the user ID by yourself
private string msChannelName = "SeeYou";
private string stChannelName = "Greeting";
private string topicName = "Hello_world";
private IRtmClient rtmClient;
private IStreamChannel streamChannel;
public void Awake()
{
Initialize();
}
// Initialize RTM
public async void Initialize()
{
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(appId))
{
ShowMessage("We need a userId and appId to initialize!");
return;
}
LogConfig logConfig = new LogConfig();
// Set log file path
logConfig.filePath = "myFilePath";
// Set the file size of the agore.log file
logConfig.fileSizeInKB = 512;
// Set the log report level
logConfig.level = LOG_LEVEL.LOG_LEVEL_INFO;
RtmConfig config = new RtmConfig();
config.appId = appId;
config.userId = userId;
config.logConfig = logConfig;
// Create the RTM Client
try
{
rtmClient = RtmClient.CreateAgoraRtmClient(config);
ShowMessage("RTM Client Initialize Sucessfull");
// Inactive unnecessary components
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
catch (RTMException e)
{
ShowMessage(string.Format("{0} is failed", e.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", e.Status.ErrorCode, e.Status.Reason));
}
// Add listener
if (rtmClient != null)
{
// Add the message event listener
rtmClient.OnMessageEvent += OnMessageEvent;
// Add the presence event listener
rtmClient.OnPresenceEvent += OnPresenceEvent;
// Add the connection state change listener
rtmClient.OnConnectionStateChange += OnConnectionStateChanged;
// Login to the RTM server
var result = await rtmClient.LoginAsync(appId);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}", result.Status.ErrorCode, result.Status.Reason));
Debug.Log(string.Format("Login failed"));
}
else
{
ShowMessage("Login Successfully");
}
//Subscribe to a Message Channel
SubscribeOptions subscribeOptions = new SubscribeOptions()
{
withMessage = true,
withPresence = true
};
var result2 = await rtmClient.SubscribeAsync(msChannelName, subscribeOptions);
if (result2.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result2.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result2.Status.ErrorCode, result2.Status.Reason));
}
else
{
ShowMessage(string.Format("Subscribe channel success! at Channel:{0}", result2.Response.ChannelName));
}
}
}
private void ChangeComponentView(string[] componentsList, string viewType)
{
for (int i = 0; i < componentsList.Length; i++)
{
if (viewType == "ACTIVATE")
GameObject.Find(componentsList[i]).GetComponent<Button>().interactable = true;
else if (viewType == "INACTIVATE")
GameObject.Find(componentsList[i]).GetComponent<Button>().interactable = false;
}
}
private void ShowMessage(string msg)
{
GameObject.Find("Txt3").GetComponent<Text>().text += "\n" + msg;
}
// Implement the event listener handler
// Implement the connection state change event handler
private void OnConnectionStateChanged(string channelName, RTM_CONNECTION_STATE state, RTM_CONNECTION_CHANGE_REASON reason)
{
ShowMessage(string.Format("Channel:{0} connection state have changed to:{1} because of {2}", channelName, state, reason));
}
// Implement the presence event handler
private void OnPresenceEvent(PresenceEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var eventType = eve.type;
var publisher = eve.publisher;
var stateItems = eve.stateItems;
var interval = eve.interval;
var snapshot = eve.snapshot;
switch (eventType)
{
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_SNAPSHOT:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("You have sub or join channel:{0} channe type is:{1}", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_INTERVAL:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("The channel:{0} channe type is:{1} is now in interval mode!", channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_REMOTE_JOIN_CHANNEL:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("User:{0} sub or join channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_REMOTE_LEAVE_CHANNEL:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("User:{0} unsub or leave channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_REMOTE_CONNECTION_TIMEOUT:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("User:{0} timeout from channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
case RTM_PRESENCE_EVENT_TYPE.RTM_PRESENCE_EVENT_TYPE_REMOTE_STATE_CHANGED:
Debug.Log(string.Format("The Event : {0} Happend",eventType));
ShowMessage(string.Format("User:{0} state change in channel:{1} channe type is:{2}", publisher, channelName, channelType));
break;
}
}
// Implement the message event handler
private void OnMessageEvent(MessageEvent eve)
{
var channelName = eve.channelName;
var channelType = eve.channelType;
var topic = eve.channelTopic;
var publisher = eve.publisher;
var messageType = eve.messageType;
var message = eve.message;
if (messageType == RTM_MESSAGE_TYPE.RTM_MESSAGE_TYPE_STRING)
{
var stMessage = message.GetData<string>();
ShowMessage(string.Format("You have recieved a string type message: {0} from: {1} in channel:{2}", stmessage, publisher, channelName));
ShowMessage(string.Format("The channel type is {0}"),channelType);
}
else
{
var biMessage = message.GetData<byte[]>();
ShowMessage(string.Format("You have recieved a binary type message: \"{0}\" from: {1} in channel:{2}", System.BitConverter.ToString(bimessage), publisher, channelName));
ShowMessage(string.Format("The channel type is {0}"),channelType);
}
}
// OnClick Event Handler for Btn1
public async void PublishStringMessage()
{
// Publish string messages to a message channel
var message = GameObject.Find("InputField1").GetComponent<InputField>().text;
if (rtmClient != null)
{
PublishOptions publishOptions = new PublishOptions()
{
type = RTM_MESSAGE_TYPE.RTM_MESSAGE_TYPE_BINARY
};
var result = await rtmClient.PublishAsync(msChannelName, message, publishOptions);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed, The error code is {1}", result.Status.Operation, result.Status.ErrorCode));
}
else
{
ShowMessage("Publish Message Success!");
}
}
}
// OnClick Event Handler for Btn6
public async void PublishBinaryMessage()
{
// Publish Binary Messahe to Stream Channel
var message = GameObject.Find("InputField2").GetComponent<InputField>().text;
byte[] byteMsg = System.Text.Encoding.Default.GetBytes(message);
if (streamChannel != null)
{
PublishOptions publishOptions = new PublishOptions();
publishOptions.type = RTM_MESSAGE_TYPE.RTM_MESSAGE_TYPE_BINARY;
publishOptions.sendTs = 0;
var result = await streamChannel.PublishTopicMessageAsync(topicName, byteMsg, publishOptions);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage("Publish Binary Message Success!");
}
}
}
// Onclick event handler for Btn2
public async void CreateStChannel()
{
string[] activeComponentsList = { "Btn3", "Btn7" };
string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" };
// Create a Stream Channel
if (rtmClient != null)
{
streamChannel = rtmClient.CreateStreamChannel(stChannelName);
ShowMessage("Create Stream Channel:" + stChannelName + "Success !");
// Disable unnecessary components
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
// Onclick event handler for Btn3
public async void JoinStChannel()
{
string[] activeComponentsList = { "Btn4", "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn9", "Btn10","Btn7" };
// Join a Stream Channel
if (streamChannel != null)
{
JoinChannelOptions options = new JoinChannelOptions();
options.withPresence = true;
options.token = appId;
var result = await streamChannel.JoinAsync(options);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join stream channel success! at Channel:{1}", result.Response.UserId, result.Response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn4
public async void JoinTopic()
{
string[] activeComponentsList = { "Btn6", "Btn8", "Btn9" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn4","Btn7" };
// Join a topic
if (streamChannel != null)
{
JoinTopicOptions joinTopicOptions = new JoinTopicOptions();
joinTopicOptions.qos = RTM_MESSAGE_QOS.RTM_MESSAGE_QOS_ORDERED;
joinTopicOptions.syncWithMedia = false;
var result = await streamChannel.JoinTopicAsync(topicName, joinTopicOptions);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Join Topic:{1} success! at Channel:{2}", result.Response.UserId, result.Response.Topic, result.Response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn5
public async void SubscribeTopic()
{
string[] activeComponentsList = { "Btn8", "Btn10" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn5","Btn7" };
// Subscribe a publisher from a topic
List<string> userList = new List<string>();
userList.Add("Tony");
TopicOptions topicOptions = new TopicOptions();
topicOptions.users = userList.ToArray();
var result = await streamChannel.SubscribeTopicAsync(topicName, topicOptions);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Subscribe Topic:{1} success! at Channel:{2}", result.Response.UserId, result.Response.Topic, result.Response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
// Onclick event handler for Btn7
public void ReleaseStChannel()
{
string[] activeComponentsList = { "Btn2" };
string[] inactiveComponentList = { "Btn3", "Btn4", "Btn5", "Btn6", "Btn7", "Btn8", "Btn9", "Btn10" };
// Release a Stream Channel
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
if (result.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var result1 = streamChannel.Dispose();
}
}
// Onclick event handler for Btn8
public async void LeaveStChannel()
{
string[] activeComponentsList = { "Btn3", "Btn7" };
string[] inactiveComponentList = { "Btn2", "Btn4", "Btn5", "Btn6", "Btn8", "Btn9", "Btn10" };
// Leave a Stream Channel
if (streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Leave stream channel success! at Channel:{1}", result.Response.UserId, result.Response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn9
public async void LeaveTopic()
{
string[] activeComponentsList = { "Btn4", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn6", "Btn7", "Btn9" };
// Leave a topic
if (streamChannel != null)
{
var result = await streamChannel.LeaveTopicAsync(topicName);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("User:{0} Leave Topic:{1} success! at Channel:{2}", result.Response.UserId, result.Response.Topic, result.Response.ChannelName));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
}
// Onclick event handler for Btn10
public async void UnsubscribeTopic()
{
string[] activeComponentsList = { "Btn5", "Btn8" };
string[] inactiveComponentList = { "Btn2", "Btn3", "Btn7", "Btn10" };
// Unsubscribe a publisher from a topic
List<string> userList = new List<string>();
userList.Add("Tony");
TopicOptions topicOptions = new TopicOptions();
topicOptions.users = userList.ToArray();
var result = await streamChannel.UnsubscribeTopicAsync(topicName, topicOptions);
if (result.Status.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage(string.Format("Unsubscribe Topic Success!"));
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
}
private async void OnDestroy()
{
//dispose a Stream Channel
if (rtmClient != null && streamChannel != null)
{
var result = await streamChannel.LeaveAsync();
if (result.Error)
{
ShowMessage(string.Format("{0} is failed", result.Status.Operation));
ShowMessage(string.Format("The error code is {0}, due to: {1}",result.Status.ErrorCode, result.Status.Reason));
}
else
{
ShowMessage("Dispose Channel Success!");
ChangeComponentView(activeComponentsList, "ACTIVATE");
ChangeComponentView(inactiveComponentList, "INACTIVATE");
}
var result1 = streamChannel.Dispose();
}
if (rtmClient != null)
{
var result2 = await rtmClient.UnsubscribeAsync(msChannelName);
if (result2.Status.Error)
{
Debug.Log(string.Format("{0} is failed", result2.Status.Operation));
Debug.Log(string.Format("The error code is {0}, because of: {1}",result2.Status.ErrorCode, result2.Status.Reason));
}
else
{
Debug.Log("Unsubscribe Channel Success!");
}
var result = rtmClient.Dispose();
Debug.Log("Dispose rtmClient Success!");
rtmClient = null;
}
}
}
现在,你可以开始运行你的程序:
- 保存项目。
- 在 Unity Editor 中将代码中定义的事件处理函数分别挂载到不同的 Button 组件上。
- 复制项目,以相同的
appId
、不同的userId
再运行一个实例,验证功能。 - 同时运行上面的两个项目。
- 在其中的一个项目中的 Send string messasge to message channel 下方的输入框中输入消息,然后点击 Send Message,在另外一个项目中观察是否收到消息。
- 在其中的一个项目中的 Send binary messasge to stream channel 下方依次点击 Create Stream Channel、Join Stream Channel、Join Topic、Sub Topic Publisher 按钮,你将激活发送文本的输入框和按钮。在输入框中输入消息,并点击 Send Message 按钮,在另外一个项目中观察是否收到消息。
至此,你已经成功集成并正确使用了 RTM 服务。
贯穿始终
相比于介绍如何写出代码,声网更愿意帮助你掌握上述程序编写的过程和逻辑。完成以上程序经历了以下几个过程:
- 设置并初始化 RTM 对象。
- 添加
OnMessageEvent
、OnPresenceEvent
和OnConnectionStateChange
事件监听函数。 - 对于 Message Channel:
- 订阅了一个频道。
- 往频道中发送了字符串消息。
- 对于 Stream Channel:
- 创建了一个名为
Greeting
的频道。 - 加入了该频道。
- 加入了一个名叫
Hello_world
的 Topic。 - 订阅了该 Topic 中名为
Tony
的消息发布者(我自己)。 - 向该 Topic 发送了一条二进制消息。
- 取消订阅了该 Topic 中名为
Tony
的消息发布者(我自己)。 - 离开了该 Topic。
- 退出了该频道。
- 销毁了该频道。
- 创建了一个名为
下一步
现在,你已经学会了如何使用 RTM SDK 来实现 Message Channel 和 Stream Channel 的收发消息功能。下一步,你可以通过 SDK 的 API 参考了解更多功能的使用方法。