快速开始
4 步完成 XIM SDK 接入:
注册控制台,创建应用
在 XIM 控制台注册账号,创建应用获取 AppID 和 AppSecret
安装 XIM SDK
通过 <script> 标签或包管理器安装对应平台的 XIM SDK
初始化并连接
使用 AppID 初始化 SDK,调用 login 建立 WebSocket 连接
发送第一条消息
调用 sendText 发送文字消息,体验毫秒级送达
// 最简示例
XIM.init({ appId: 'your_app_id' });
await XIM.login('user_001', 'token');
await XIM.sendText('user_002', 'Hello!');JS SDK H5 / 微信小程序 / 支付宝小程序
引入 SDK
浏览器 / H5:
<script src="/dist/xim-sdk.js"></script>
<script>
const XIM = xSpace.XIM;
</script>模块方式(需打包工具):
import { XIM } from '@xim/sdk';初始化
在调用任何其他接口前,必须先调用 XIM.init()。
XIM.init({
appId: 'your_app_id', // 必填,应用 ID
platform: 'web', // 可选,平台标识
});| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
appId | string | 是 | 应用 ID |
server | string | 否 | WebSocket 地址,不传则使用 SDK 默认地址 |
platform | string | 否 | 平台标识,如 web、wxmp |
deviceId | string | 否 | 设备唯一标识 |
appVersion | string | 否 | 应用版本号 |
nickname | string | 否 | 登录时上报的昵称 |
avatarUrl | string | 否 | 登录时上报的头像 URL |
登录与登出
await XIM.login('user_001', 'token_string');| 参数 | 类型 | 说明 |
|---|---|---|
uid | string | 用户 ID,只允许字母、数字、下划线、连字符 |
token | string | 鉴权 token,传空字符串时服务端不做鉴权 |
登录成功后触发 connected 事件,同时自动拉取离线消息。
XIM.logout();事件监听
// 监听
XIM.on('message', (msg) => { ... });
// 取消监听
XIM.off('message', handler);| 事件名 | 回调参数 | 说明 |
|---|---|---|
connected | - | WebSocket 登录成功 |
disconnected | reason: string | 连接断开 |
kicked | reason: string | 被踢下线 |
message | msg: Message | 收到新消息 |
binary_message | msg: Message | 收到二进制透传消息(type=5) |
conversation_updated | conv: Conversation | 会话列表有更新 |
msg_recalled | convId, msgId | 消息被撤回 |
group_member_change | groupId, uid, action | 群成员变化 |
channel_data | channelId, senderId, seqNum, data, ... | 收到频道数据帧 |
channel_text | { channelId, senderId, text, ... } | 收到频道文本消息 |
channel_member_join | channelId, userId, ... | 有人加入频道 |
channel_member_leave | channelId, userId, ... | 有人离开频道 |
channel_destroyed | channelId, reason | 频道被销毁 |
消息发送
发送文本消息
const msg = await XIM.sendText('user_002', '你好', 1);| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
targetId | string | 必填 | 单聊传对方 uid,群聊传 groupId |
content | string | 必填 | 消息正文 |
convType | number | 1 | 1=单聊,2=群聊 |
发送图片消息
const msg = await XIM.sendImage('user_002', file, 1);
// 小程序传 tempFilePath 字符串SDK 内部自动完成上传、获取宽高、封装 content JSON。接收方 msg.type === 2,content 为:
{ "url": "https://...", "w": 640, "h": 480, "size": 102400 }发送文件消息
const msg = await XIM.sendFile('user_002', file, 1);接收方 msg.type === 4,content 为:
{ "url": "https://...", "name": "report.pdf", "size": 204800 }发送二进制透传消息
const payload = new Uint8Array([1, 2, 3]);
const msg = await XIM.sendData('user_002', payload, 1);消息接收
XIM.on('message', (msg) => {
if (msg.type === 1) {
console.log(msg.content); // 文本
} else if (msg.type === 2) {
const { url, w, h } = JSON.parse(msg.content); // 图片
} else if (msg.type === 4) {
const { url, name, size } = JSON.parse(msg.content); // 文件
} else if (msg.type === 5) {
console.log(msg.payload); // 二进制
}
});| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 服务端消息 ID |
convId | string | 会话 ID,单聊 uidA|uidB,群聊 g_xxx |
targetId | string | 单聊=对方 uid,群聊=groupId |
senderId | string | 发送方 uid |
type | number | 1=文本,2=图片,3=语音,4=文件,5=二进制 |
content | string | 消息内容 |
timestamp | number | 服务端时间戳(毫秒) |
seq | number | 会话内有序序号 |
payload | Uint8Array | type=5 时的原始二进制 |
历史消息
const { messages, hasMore } = await XIM.getHistory('user_002', 1, beforeTime, 20);优先读本地 IndexedDB 缓存,不足时向服务端补齐。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
targetId | string | 必填 | 对方 uid 或 groupId |
convType | number | 必填 | 1=单聊,2=群聊 |
beforeTime | number | 0 | 拉取此时间戳之前的消息(毫秒) |
count | number | 20 | 每次拉取条数 |
// 下拉加载更多
let minTime = 0;
let hasMore = true;
async function loadOlder() {
if (!hasMore) return;
const result = await XIM.getHistory('user_002', 1, minTime, 20);
if (result.messages.length > 0) minTime = result.messages[0].timestamp;
hasMore = result.hasMore;
}会话管理
const convs = XIM.getConversations(); // 纯本地,无网络请求
XIM.markRead('user_002', 1); // 标记已读
XIM.markUnread('user_002', 1); // 标记未读
await XIM.recallMessage(msgId); // 撤回消息| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 会话 ID |
type | number | 1=单聊,2=群聊 |
targetId | string | 单聊=对方uid,群聊=groupId |
name | string | 会话显示名 |
unreadCount | number | 未读数 |
lastMsg | Message | 最后一条消息 |
群组
const group = await XIM.createGroup('我的群', ['user_002', 'user_003']);
await XIM.joinGroup('g_xxxxxxxx');
await XIM.leaveGroup('g_xxxxxxxx');实时频道
频道是 fire-and-forget 的实时数据通道,不持久化,适合游戏对局等实时场景。
const info = await XIM.channelJoin('room_001');
XIM.channelSendText('room_001', '你好大家');
XIM.on('channel_text', ({ channelId, senderId, text }) => {
console.log(`${senderId}: ${text}`);
});
await XIM.channelLeave('room_001');用户资料
await XIM.setMyProfile({ nickname: '张三', avatarUrl: '...' });
const profiles = await XIM.getUserProfiles(['user_001', 'user_002']);
const cached = XIM.getCachedProfile('user_001');类型定义
interface Message {
id: string;
convId: string;
targetId: string;
senderId: string;
type: number; // 1=文本, 2=图片, 3=语音, 4=文件, 5=二进制
content: string;
timestamp: number;
status: number; // 0=发送中, 1=已发送
seq: number;
payload?: Uint8Array;
flags?: number;
extra?: string;
}
interface Conversation {
id: string;
type: number; // 1=单聊, 2=群聊
targetId: string;
name: string;
unreadCount: number;
lastMsg?: Message;
}
interface InitConfig {
appId: string;
server?: string;
platform?: string;
deviceId?: string;
appVersion?: string;
appName?: string;
nickname?: string;
avatarUrl?: string;
}Unity C# SDK
引入 SDK
通过 Unity Package Manager 导入 XIM SDK,或将 com.xim.sdk 包放入项目的 Packages/ 目录。
// 在脚本顶部引入命名空间
using xSpace;初始化
在场景加载后调用 XIM.Init() 完成初始化。SDK 会自动创建一个 DontDestroyOnLoad 的 GameObject 来驱动内部网络循环。
using XSpace;
XIM.Init("your_app_id");可选参数:
XIM.Init(
appId: "your_app_id",
server: "wss://your-server.com", // 可选,自定义服务器地址
autoDownload: true // 可选,是否自动下载语音/图片文件
);| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
appId | string | 必填 | 应用 ID,在控制台获取 |
server | string | null | 自定义 WebSocket 服务器地址,不传则使用默认地址 |
autoDownload | bool | true | 是否自动下载收到的语音和图片文件到本地缓存 |
登录与登出
Unity SDK 使用 async/await 模式,所有异步接口返回 Task<T>。
Result result = await XIM.LoginAsync("user_001", "token");
if (result.Success) {
Debug.Log("登录成功");
}| 参数 | 类型 | 说明 |
|---|---|---|
uid | string | 用户 ID,只允许字母、数字、下划线、连字符 |
token | string | 鉴权 token,传空字符串时服务端不做鉴权 |
nickname | string | 可选,登录时上报昵称 |
avatarUrl | string | 可选,登录时上报头像 URL |
登出:
await XIM.LogoutAsync();获取当前连接状态:
ConnState state = XIM.ConnectionState;
// ConnState.Connected / Connecting / Disconnected事件监听
Unity SDK 使用静态委托事件,回调始终在主线程触发,可直接操作 UI。
// 连接事件
XIM.OnConnected += () => Debug.Log("已连接");
XIM.OnDisconnected += (code, reason) => Debug.Log($"断开: {reason}");
XIM.OnKickedOffline += () => Debug.Log("被踢下线");
// 消息事件
XIM.OnNewMessage += (msg) => Debug.Log($"新消息: {msg.Content}");
XIM.OnMessageRecalled += (msgId) => Debug.Log($"撤回: {msgId}");
// 会话事件
XIM.OnConversationUpdated += (conv) => Debug.Log($"会话更新: {conv.TargetId}");
// 群组事件
XIM.OnGroupMemberJoined += (groupId, userId) => { };
XIM.OnGroupMemberLeft += (groupId, userId) => { };
// 频道事件
XIM.OnChannelText += (channelId, senderId, text, seqNum, timestamp) => { };
XIM.OnChannelData += (channelId, senderId, seqNum, data, contentType) => { };消息发送
所有发送接口返回 Task<Message>,可 await 获得完整消息对象(含服务端分配的 msgId 和时间戳)。
发送文本消息
Message msg = await XIM.SendTextAsync("user_002", "你好");发送图片消息
Message msg = await XIM.SendImageAsync("user_002", "/path/to/image.png");发送语音消息
Message msg = await XIM.SendVoiceAsync("user_002", "/path/to/voice.wav", durationSec: 5);发送二进制消息
byte[] data = Encoding.UTF8.GetBytes("custom payload");
Message msg = await XIM.SendDataAsync("user_002", data, 0, data.Length, convType: 1, flags: 0);撤回消息
Result result = await XIM.RecallAsync(msg.MsgId);| 方法 | 参数 | 说明 |
|---|---|---|
SendTextAsync | targetId, content, convType=1 | 发送文本消息 |
SendImageAsync | targetId, filePath, convType=1 | 发送图片消息,filePath 为本地绝对路径 |
SendVoiceAsync | targetId, filePath, durationSec, convType=1 | 发送语音消息 |
SendDataAsync | targetId, data, offset, len, convType=1, flags=0 | 发送二进制透传消息 |
RecallAsync | msgId | 撤回已发送消息 |
convType:1 = 单聊,2 = 群聊。
消息接收
Unity SDK 使用静态委托事件,回调始终在主线程触发,可直接操作 UI。
XIM.OnNewMessage += (Message msg) => {
Debug.Log($"收到消息: {msg.Content} 来自 {msg.SenderId}");
};
XIM.OnMessageRecalled += (string msgId) => {
Debug.Log($"消息被撤回: {msgId}");
};
XIM.OnBinaryMessage += (string msgId, string convId, string senderId, byte[] payload, uint flags) => {
Debug.Log($"收到二进制消息: {payload.Length} bytes");
};| 事件 | 回调参数 | 说明 |
|---|---|---|
OnNewMessage | Message msg | 收到新消息(文本/图片/语音) |
OnMessageRecalled | string msgId | 消息被撤回 |
OnBinaryMessage | msgId, convId, senderId, byte[] payload, uint flags | 收到二进制透传消息 |
Message 对象关键字段:
| 字段 | 类型 | 说明 |
|---|---|---|
MsgId | string | 消息唯一 ID |
ConvId | string | 会话 ID |
SenderId | string | 发送者 UID |
Content | string | 文本内容(文本消息) |
FilePath | string | 本地文件路径(图片/语音消息,autoDownload 开启时自动填充) |
FileUrl | string | 文件远程 URL |
Duration | int | 语音时长(秒) |
Timestamp | long | 服务端时间戳(毫秒) |
Type | int | 消息类型:1=文本 2=图片 3=语音 5=二进制 |
历史消息
HistoryResult history = await XIM.GetHistoryAsync(
targetId: "user_002",
convType: 1,
beforeTime: DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
count: 20
);
foreach (Message msg in history.Messages) {
Debug.Log($"{msg.SenderId}: {msg.Content}");
}| 参数 | 类型 | 说明 |
|---|---|---|
targetId | string | 单聊传对方 uid,群聊传 groupId |
convType | int | 1=单聊 2=群聊 |
beforeTime | long | 拉取此时间戳之前的消息(毫秒) |
count | int | 拉取条数,最大 50 |
会话管理
会话列表由客户端本地维护,收发消息时自动更新。
// 获取会话列表
List<Conversation> convs = await XIM.GetConversationsAsync();
// 删除会话
await XIM.DeleteConversationAsync("user_002", convType: 1);
// 标记会话已读
XIM.MarkRead("user_002", convType: 1);监听会话更新:
XIM.OnConversationUpdated += (Conversation conv) => {
Debug.Log($"会话更新: {conv.TargetId} 未读: {conv.UnreadCount}");
};| 字段 | 类型 | 说明 |
|---|---|---|
TargetId | string | 会话目标 ID |
ConvType | int | 1=单聊 2=群聊 |
LastMessage | Message | 最后一条消息 |
UnreadCount | int | 未读消息数 |
UpdatedAt | long | 最后更新时间戳 |
群组
// 创建群组
GroupInfo group = await XIM.CreateGroupAsync("我的群", new List<string> { "user_002", "user_003" });
// 加入群组
await XIM.JoinGroupAsync("group_001");
// 退出群组
await XIM.LeaveGroupAsync("group_001");群事件监听:
XIM.OnGroupMemberJoined += (string groupId, string userId) => { };
XIM.OnGroupMemberLeft += (string groupId, string userId) => { };
XIM.OnGroupDismissed += (string groupId) => { };| 方法 | 参数 | 说明 |
|---|---|---|
CreateGroupAsync | name, memberIds | 创建群组并拉入初始成员 |
JoinGroupAsync | groupId | 主动加入群组 |
LeaveGroupAsync | groupId | 退出群组 |
实时频道
频道用于低延迟实时数据广播,适用于游戏同步、直播互动等场景。
// 加入频道
ChannelInfo channel = await XIM.ChannelJoinAsync("room_001");
// 发送二进制数据
byte[] payload = ...;
XIM.ChannelSend("room_001", payload, 0, payload.Length, reliable: true, contentType: 0);
// 发送文本
XIM.ChannelSendText("room_001", "Hello everyone!");
// 离开频道
await XIM.ChannelLeaveAsync("room_001");频道事件:
XIM.OnChannelData += (string channelId, string senderId, uint seqNum, byte[] data, uint contentType) => { };
XIM.OnChannelText += (string channelId, string senderId, string text) => { };
XIM.OnChannelMemberJoined += (string channelId, string userId) => { };
XIM.OnChannelMemberLeft += (string channelId, string userId) => { };
XIM.OnChannelDestroyed += (string channelId, string reason) => { };| 方法 | 参数 | 说明 |
|---|---|---|
ChannelJoinAsync | channelId | 加入频道,返回 ChannelInfo |
ChannelLeaveAsync | channelId | 离开频道 |
ChannelSend | channelId, data, offset, len, reliable, contentType | 发送二进制帧 |
ChannelSendText | channelId, text | 发送文本消息 |
用户资料
// 设置自己的资料
UserProfile myProfile = await XIM.SetMyProfileAsync(new UserProfile {
Nickname = "小明",
AvatarUrl = "https://example.com/avatar.png"
});
// 批量获取用户资料
List<UserProfile> profiles = await XIM.GetUserProfilesAsync(
new List<string> { "user_001", "user_002" }
);
// 获取缓存的资料(不发起网络请求)
UserProfile cached = XIM.GetCachedProfile("user_001");| 方法 | 参数 | 说明 |
|---|---|---|
SetMyProfileAsync | UserProfile profile | 设置当前用户资料 |
GetUserProfilesAsync | List<string> uids | 批量获取用户资料(网络请求) |
GetCachedProfile | string uid | 从本地缓存获取用户资料 |
文件缓存
SDK 内置文件缓存系统,自动管理下载的图片、语音等文件。也可手动调用下载。
// 获取缓存根目录
string cacheRoot = XIM.CacheDir;
// 获取指定类型缓存目录
string imageDir = XIM.CacheDirForType(CacheType.Image);
// 获取已缓存文件的本地路径(未缓存返回 null)
string path = XIM.GetCachedPath("https://example.com/file.png", CacheType.Image);
// 手动下载文件
string localPath = await XIM.DownloadFileAsync("https://example.com/file.png", CacheType.Image);
// 清除所有缓存
XIM.ClearCache();
// 清除指定类型缓存
XIM.ClearCache(CacheType.Voice);| CacheType | 说明 |
|---|---|
Image | 图片文件缓存 |
Voice | 语音文件缓存 |
File | 通用文件缓存 |
类型定义
// 操作结果
public class Result {
public bool Success;
public int Code; // 错误码,0 = 成功
public string Error; // 错误信息
}
// 消息
public class Message {
public string MsgId;
public string ConvId;
public string SenderId;
public int Type; // 1=文本 2=图片 3=语音 5=二进制
public string Content; // 文本内容
public string FilePath; // 本地文件路径
public string FileUrl; // 远程文件 URL
public int Duration; // 语音时长(秒)
public long Timestamp; // 服务端时间戳(毫秒)
}
// 会话
public class Conversation {
public string TargetId;
public int ConvType; // 1=单聊 2=群聊
public Message LastMessage;
public int UnreadCount;
public long UpdatedAt;
}
// 群组信息
public class GroupInfo {
public string GroupId;
public string Name;
public string OwnerId;
public List<string> MemberIds;
}
// 用户资料
public class UserProfile {
public string Uid;
public string Nickname;
public string AvatarUrl;
}
// 频道信息
public class ChannelInfo {
public string ChannelId;
public List<string> Members;
}
// 历史消息结果
public class HistoryResult {
public List<Message> Messages;
public bool HasMore;
}
// 连接状态
public enum ConnState {
Disconnected,
Connecting,
Connected
}
// 缓存类型
public enum CacheType {
Image,
Voice,
File
}Unity SDK 还提供语音相关控制属性:
| 属性/方法 | 类型 | 说明 |
|---|---|---|
XIM.ReceiveVoice | bool | 是否接收并播放语音消息(PTT 开关) |
XIM.VoiceVolume | float | 语音播放音量,0.0 ~ 1.0 |
Cocos C++ SDK Cocos2d-x / 原生 C++
引入 SDK
在项目中引入 XIM 头文件,所有 API 均位于 xspace 命名空间下:
#include <xim/XIM.h>
using namespace xspace;将 XIM SDK 的 include/ 目录加入头文件搜索路径,链接 libxim_core 静态库。Cocos2d-x 项目在 CMakeLists.txt 中添加:
target_include_directories(your_target PRIVATE path/to/xim-sdk/include)
target_link_libraries(your_target xim_core)初始化
在调用任何其他接口前,必须先调用 XIM::init()。
xspace::Config cfg;
cfg.appId = "your_app_id"; // 必填
cfg.server = "ws://x.x.x.x:9090/ws"; // 可选,私有部署时填写
cfg.dataDir = "/path/to/data"; // 可选,本地数据库/缓存目录
cfg.logLevel = xspace::LogLevel::Info;
cfg.autoDownload = true; // 收到图片/语音自动下载
XIM::init(cfg);| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
appId | string | 是 | 应用 ID,从控制台获取 |
server | string | 否 | WebSocket 地址,不传则使用默认公有云地址 |
dataDir | string | 否 | 本地数据目录(数据库、缓存),默认使用平台缓存路径 |
logLevel | LogLevel | 否 | Debug / Info / Warn / Error,默认 Warn |
autoDownload | bool | 否 | 收到图片/语音消息时自动下载到本地缓存,默认 true |
重要:必须在游戏主循环中每帧调用 XIM::poll(),SDK 的所有回调和事件均在 poll 内触发:
// Cocos2d-x update 回调中
void GameScene::update(float dt) {
XIM::poll();
}查询当前连接状态:
ConnState state = XIM::connState();
// Disconnected / Connecting / Connected登录与登出
C++ SDK 使用异步回调模式。Callback 签名为 std::function<void(int code, const std::string& msg)>,code 为 0 表示成功。
XIM::login("user_001", "token", [](int code, const std::string& msg) {
if (code == 0) {
// 登录成功,自动拉取离线消息
} else {
// 登录失败,msg 为错误描述
}
}, "张三", "https://example.com/avatar.png");| 参数 | 类型 | 说明 |
|---|---|---|
uid | const string& | 用户 ID,只允许字母、数字、下划线、连字符 |
token | const string& | 鉴权 token,传空字符串时服务端不做鉴权 |
cb | Callback | 结果回调 |
nickname | const string& | 可选,登录时上报的昵称 |
avatarUrl | const string& | 可选,登录时上报的头像 URL |
登出:
XIM::logout([](int code, const std::string& msg) {
// 登出完成
});事件监听
C++ SDK 通过 Event 枚举 + const void* 指针传递事件数据。回调签名为 std::function<void(Event evt, const void* data)>,按 Event 类型做 static_cast 取出数据。
XIM::on(Event::NewMessages, [](Event evt, const void* data) {
auto* d = static_cast<const NewMessagesData*>(data);
for (auto& msg : d->messages) {
// 处理新消息
}
});
XIM::on(Event::Connected, [](Event evt, const void* data) {
// 连接已建立
});
XIM::on(Event::Disconnected, [](Event evt, const void* data) {
auto* d = static_cast<const DisconnectedData*>(data);
// d->code, d->reason
});
// 取消监听
XIM::off(Event::NewMessages);| Event 枚举 | 事件数据类型 | 说明 |
|---|---|---|
Connecting | - | 正在连接 |
Connected | - | 登录成功,连接已建立 |
Disconnected | DisconnectedData* | 连接断开(code + reason) |
KickedOffline | - | 被踢下线 |
NewMessages | NewMessagesData* | 收到新消息(含离线消息批量推送) |
MessageRecalled | MessageIdData* | 消息被撤回 |
MessageUpdated | MessageIdData* | 消息状态更新 |
ConversationUpdated | ConversationData* | 会话列表有更新 |
GroupMemberJoined | GroupEventData* | 群成员加入 |
GroupMemberLeft | GroupEventData* | 群成员离开 |
GroupDismissed | GroupEventData* | 群组被解散 |
BinaryMessage | BinaryMessageData* | 收到二进制透传消息 (type=5) |
ChannelData | ChannelDataEvent* | 收到频道数据帧 |
ChannelText | ChannelTextEvent* | 收到频道文本消息 |
ChannelMemberJoined | ChannelMemberEvent* | 有人加入频道 |
ChannelMemberLeft | ChannelMemberEvent* | 有人离开频道 |
ChannelDestroyed | ChannelDestroyedEvent* | 频道被销毁 |
ChannelFrameAck | ChannelFrameAckEvent* | 频道帧确认(reliable 模式) |
消息发送
发送接口使用 SendCallback,签名为 std::function<void(int code, const std::string& errMsg, const Message& msg)>,成功时 msg 包含服务端分配的 id 和时间戳。
发送文本消息
XIM::sendText("user_002", "你好", 1, [](int code, const std::string& err, const Message& msg) {
if (code == 0) {
// msg.id 为服务端分配的消息 ID
}
});发送图片消息
XIM::sendImage("user_002", "/path/to/image.png", 1,
[](int code, const std::string& err, const Message& msg) {
// SDK 自动上传图片、获取宽高、封装 content JSON
});发送语音消息
XIM::sendVoice("user_002", "/path/to/voice.aac", 5, 1,
[](int code, const std::string& err, const Message& msg) {
// durationSec=5 表示语音时长 5 秒
});发送二进制透传消息
uint8_t payload[] = {0x01, 0x02, 0x03};
XIM::sendData("user_002", payload, sizeof(payload), 1, 0,
[](int code, const std::string& err, const Message& msg) {
// flags=0 正常持久化; flags=1 不持久化(skip_persist)
});撤回消息
XIM::recall("msg_id_xxx", [](int code, const std::string& msg) {
// 撤回成功后,对方收到 Event::MessageRecalled
});标记已读
XIM::markRead("user_002", 1); // convType: 1=单聊 2=群聊| 方法 | 参数 | 说明 |
|---|---|---|
sendText | targetId, content, convType=1, SendCallback | 发送文本消息 |
sendImage | targetId, filePath, convType=1, SendCallback | 发送图片(自动上传) |
sendVoice | targetId, filePath, durationSec, convType=1, SendCallback | 发送语音消息 |
sendData | targetId, data, len, convType=1, flags=0, SendCallback | 发送二进制透传消息 |
recall | msgId, Callback | 撤回已发送消息 |
markRead | targetId, convType=1 | 标记会话已读 |
消息接收
消息通过事件驱动接收,所有事件回调在 poll() 中触发。必须每帧调用 poll(),否则不会收到任何回调。
XIM::on(Event::NewMessages, [](Event evt, const void* data) {
auto* d = static_cast<const NewMessagesData*>(data);
for (auto& msg : d->messages) {
if (msg.type == 1) {
// 文本消息: msg.content
} else if (msg.type == 2) {
// 图片消息: msg.content 为 JSON {"url":"...","w":640,"h":480}
} else if (msg.type == 3) {
// 语音消息: msg.content 为 JSON {"url":"...","duration":5}
}
}
});
// 二进制消息单独事件
XIM::on(Event::BinaryMessage, [](Event evt, const void* data) {
auto* d = static_cast<const BinaryMessageData*>(data);
// d->payload, d->payloadLen, d->senderId, d->convId
});
// 消息撤回事件
XIM::on(Event::MessageRecalled, [](Event evt, const void* data) {
auto* d = static_cast<const MessageIdData*>(data);
// d->msgId
});poll() 在 Cocos2d-x 中的典型用法:
bool AppDelegate::applicationDidFinishLaunching() {
auto scene = Scene::create();
scene->scheduleUpdate(); // 开启 update
// ...
}
void MyScene::update(float dt) {
XIM::poll(); // 每帧调用,驱动所有回调和事件
}| Message 字段 | 类型 | 说明 |
|---|---|---|
id | string | 服务端消息 ID |
convId | string | 会话 ID |
targetId | string | 单聊=对方 uid,群聊=groupId |
senderId | string | 发送方 uid |
type | int | 1=文本,2=图片,3=语音,4=自定义,5=二进制 |
content | string | 消息内容(文本或 JSON) |
timestamp | int64_t | 服务端时间戳(毫秒) |
status | int | 0=发送中,1=成功,2=失败,3=已读 |
seq | int64_t | 消息序列号(服务端分配,用于排序和 gap 检测) |
payload | vector<uint8_t> | type=5 时的原始二进制数据 |
flags | uint32_t | bit0: skip_persist (1=不持久化) |
extra | string | 开发者自定义扩展 JSON,SDK 透传不解析 |
历史消息
XIM::getHistory("user_002", 1, 0, 20, [](int code, HistoryResult result) {
if (code == 0) {
for (auto& msg : result.messages) {
// 处理历史消息
}
if (result.hasMore) {
// 还有更多历史消息,可继续拉取
}
}
});| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
targetId | const string& | 必填 | 对方 uid 或 groupId |
convType | int | 必填 | 1=单聊,2=群聊 |
beforeTime | int64_t | 0 | 拉取此时间戳之前的消息(毫秒),0 表示从最新开始 |
count | int | 20 | 每次拉取条数 |
下拉加载更多:
int64_t minTime = 0;
bool hasMore = true;
void loadOlder() {
if (!hasMore) return;
XIM::getHistory("user_002", 1, minTime, 20, [&](int code, HistoryResult result) {
if (code == 0 && !result.messages.empty()) {
minTime = result.messages.front().timestamp;
}
hasMore = result.hasMore;
});
}会话管理
// 获取会话列表
XIM::getConversations([](int code, std::vector<Conversation> convs) {
for (auto& conv : convs) {
// conv.targetId, conv.name, conv.unreadCount, conv.lastMsg
}
});
// 删除会话
XIM::deleteConversation("user_002", 1, [](int code, const std::string& msg) {
// 删除完成
});
// 标记已读
XIM::markRead("user_002", 1);监听会话更新:
XIM::on(Event::ConversationUpdated, [](Event evt, const void* data) {
auto* d = static_cast<const ConversationData*>(data);
// d->conversation.targetId, d->conversation.unreadCount
});| Conversation 字段 | 类型 | 说明 |
|---|---|---|
id | string | 会话 ID |
type | int | 1=单聊,2=群聊,3=聊天室 |
targetId | string | 单聊=对方 uid,群聊=groupId |
name | string | 会话显示名 |
unreadCount | int | 未读消息数 |
lastMsg | Message | 最后一条消息 |
群组
// 创建群组
XIM::createGroup("我的群", {"user_002", "user_003"}, [](int code, GroupInfo info) {
if (code == 0) {
// info.id, info.name, info.memberCount
}
});
// 加入群组
XIM::joinGroup("g_xxxxxxxx", [](int code, const std::string& msg) {});
// 离开群组
XIM::leaveGroup("g_xxxxxxxx", [](int code, const std::string& msg) {});群事件监听:
XIM::on(Event::GroupMemberJoined, [](Event evt, const void* data) {
auto* d = static_cast<const GroupEventData*>(data);
// d->groupId, d->userId
});
XIM::on(Event::GroupMemberLeft, [](Event evt, const void* data) { ... });
XIM::on(Event::GroupDismissed, [](Event evt, const void* data) { ... });| 方法 | 参数 | 说明 |
|---|---|---|
createGroup | name, memberIds, callback | 创建群组并拉入初始成员 |
joinGroup | groupId, Callback | 加入群组 |
leaveGroup | groupId, Callback | 退出群组 |
实时频道
频道是 fire-and-forget 的实时数据通道,不持久化,适合游戏对局、实时同步等场景。
// 加入频道
XIM::channelJoin("room_001", [](int code, ChannelInfo info) {
// info.channelId, info.memberUids, info.members, info.recentMessages
});
// 发送文本消息(自动 UTF-8 编码,reliable=true)
XIM::channelSendText("room_001", "你好大家");
// 发送二进制数据
uint8_t data[] = {0x01, 0x02};
XIM::channelSend("room_001", data, sizeof(data), false, 0);
// reliable=false 不保证到达;contentType 自定义
// 离开频道
XIM::channelLeave("room_001");频道事件:
XIM::on(Event::ChannelText, [](Event evt, const void* data) {
auto* d = static_cast<const ChannelTextEvent*>(data);
// d->channelId, d->senderId, d->text, d->seqNum, d->timestamp
});
XIM::on(Event::ChannelData, [](Event evt, const void* data) {
auto* d = static_cast<const ChannelDataEvent*>(data);
// d->channelId, d->senderId, d->data, d->dataLen, d->contentType
});
XIM::on(Event::ChannelMemberJoined, [](Event evt, const void* data) {
auto* d = static_cast<const ChannelMemberEvent*>(data);
// d->channelId, d->userId, d->nickname
});
XIM::on(Event::ChannelMemberLeft, [](Event evt, const void* data) { ... });
XIM::on(Event::ChannelDestroyed, [](Event evt, const void* data) { ... });| 方法 | 参数 | 说明 |
|---|---|---|
channelJoin | channelId, callback | 加入频道,返回 ChannelInfo |
channelLeave | channelId, Callback | 离开频道 |
channelSend | channelId, data, len, reliable=false, contentType=0 | 发送二进制帧 |
channelSendText | channelId, text | 发送文本消息(reliable=true) |
用户资料
// 设置自己的资料
UserProfile profile;
profile.nickname = "张三";
profile.avatarUrl = "https://example.com/avatar.png";
profile.gender = 1; // 0=未知 1=男 2=女
profile.signature = "这是签名";
XIM::setMyProfile(profile, [](int code, const std::string& msg) {});
// 批量获取用户资料
XIM::getUserProfiles({"user_001", "user_002"},
[](int code, std::vector<UserProfile> profiles) {
for (auto& p : profiles) {
// p.uid, p.nickname, p.avatarUrl, p.gender, p.signature, p.moreInfo
}
});
// 获取缓存的用户资料(纯本地,无网络请求)
UserProfile cached = XIM::getCachedProfile("user_001");| 方法 | 参数 | 说明 |
|---|---|---|
setMyProfile | UserProfile, Callback | 设置当前用户资料 |
getUserProfiles | vector<string>, callback | 批量获取用户资料(网络请求) |
getCachedProfile | uid | 从本地缓存获取(同步,不发起网络请求) |
文件缓存
SDK 内置文件缓存管理,按类型(图片/语音/文件)分目录存储。autoDownload=true 时收到图片和语音会自动下载。
// 获取缓存根目录
std::string dir = XIM::cacheDir();
// 获取指定类型的缓存目录
std::string imgDir = XIM::cacheDirForType(CacheType::Image);
// 获取某个 URL 的本地缓存路径(未下载时为空字符串)
std::string localPath = XIM::cachedPath("https://oss.example.com/a.jpg", CacheType::Image);
// 手动下载文件到缓存
XIM::downloadFile("https://oss.example.com/a.jpg", CacheType::Image,
[](int code, const std::string& localPath) {
if (code == 0) {
// localPath 为本地文件路径
}
});
// 清除所有缓存
XIM::clearCache();
// 清除指定类型缓存
XIM::clearCache(CacheType::Voice);
// 删除单个缓存文件
bool ok = XIM::removeCache("https://oss.example.com/a.jpg", CacheType::Image);| CacheType | 说明 |
|---|---|
Image | 图片缓存 |
Voice | 语音缓存 |
File | 文件缓存 |
类型定义
namespace xspace {
// ─── 回调类型 ───
using Callback = std::function<void(int code, const std::string& msg)>;
using SendCallback = std::function<void(int code, const std::string& errMsg,
const Message& msg)>;
using EventHandler = std::function<void(Event evt, const void* data)>;
// ─── 枚举 ───
enum class LogLevel { Debug, Info, Warn, Error };
enum class ConnState { Disconnected, Connecting, Connected };
enum class CacheType { Image = 0, Voice = 1, File = 2 };
enum class Event {
Connecting, Connected, Disconnected, KickedOffline,
NewMessages, MessageRecalled, MessageUpdated,
ConversationUpdated,
GroupMemberJoined, GroupMemberLeft, GroupDismissed,
OnRemoteTalkStart, OnRemoteTalkStop, VoiceData,
BinaryMessage,
ChannelData, ChannelMemberJoined, ChannelMemberLeft,
ChannelDestroyed, ChannelFrameAck, ChannelText,
};
// ─── 配置 ───
struct Config {
std::string appId;
std::string server;
std::string dataDir;
LogLevel logLevel = LogLevel::Warn;
bool autoDownload = true;
};
// ─── 数据模型 ───
struct Message {
std::string id;
std::string convId;
std::string targetId;
std::string senderId;
int type = 0; // 1=文本 2=图片 3=语音 4=自定义 5=二进制
std::string content;
int64_t timestamp = 0;
int status = 0; // 0=发送中 1=成功 2=失败 3=已读
int64_t seq = 0;
std::vector<uint8_t> payload;
uint32_t flags = 0; // bit0: skip_persist
std::string extra; // 开发者自定义扩展 JSON
};
struct Conversation {
std::string id;
std::string targetId;
int type = 0; // 1=单聊 2=群聊 3=聊天室
std::string name;
Message lastMsg;
int unreadCount = 0;
};
struct GroupInfo {
std::string id;
std::string name;
std::string ownerId;
int memberCount = 0;
};
struct UserProfile {
std::string uid;
std::string nickname;
std::string avatarUrl;
int gender = 0; // 0=未知 1=男 2=女
std::string signature;
std::string moreInfo;
};
struct ChannelInfo {
std::string channelId;
std::vector<std::string> memberUids;
std::vector<ChannelMember> members;
std::string metadata;
std::vector<ChannelMessage> recentMessages;
};
struct HistoryResult {
std::vector<Message> messages;
bool hasMore = false;
};
// ─── 事件数据结构 ───
struct DisconnectedData { int code; std::string reason; };
struct NewMessagesData { std::vector<Message> messages; };
struct MessageIdData { std::string msgId; };
struct ConversationData { Conversation conversation; };
struct GroupEventData { std::string groupId; std::string userId; };
struct BinaryMessageData {
std::string msgId, convId, senderId;
const uint8_t* payload; size_t payloadLen; uint32_t flags;
};
struct ChannelDataEvent {
std::string channelId, senderId;
uint32_t seqNum;
const uint8_t* data; size_t dataLen;
bool reliable; int64_t timestamp; uint32_t contentType;
};
struct ChannelTextEvent {
std::string channelId, senderId, text;
uint32_t seqNum; int64_t timestamp;
};
struct ChannelMemberEvent {
std::string channelId, userId, nickname, avatarUrl;
};
struct ChannelDestroyedEvent { std::string channelId, reason; };
struct ChannelFrameAckEvent { std::string channelId; uint32_t seqNum; };
} // namespace xspace