# Presence (/zh-CN/api-reference/rtm/cpp/presence)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

Presence 提供监控用户上线、下线及用户历史状态变更通知的能力。通过 Presence 功能，你可以实时获取以下信息：

- 用户加入或离开指定频道的实时事件通知
- 自定义临时用户状态及其变更实时事件通知
- 查询指定用户加入或订阅了哪些频道
- 查询指定频道有哪些用户加入及其用户临时状态数据

:::info[信息]
**Presence** 能力适用于 Message Channel 和 Stream Channel。
:::

## whoNow [#whonow]

### 接口描述 [#接口描述]

调用 `whoNow` 方法，你可以实时查询指定频道的在线用户数量、在线用户列表及在线用户的临时状态等信息。

### 接口方法 [#接口方法]

你可以通过以下方式调用 `whoNow` 方法：

```cpp
virtual void whoNow(const char* channelName, RTM_CHANNEL_TYPE channelType, const PresenceOptions& options, uint64_t& requestId) = 0;
```

| 参数 | 类型 | 是否必填 | 默认值 | 描述 |
| --- | --- | --- | --- | --- |
| `channelName` | const char* | 必填 | - | 频道名称。 |
| `channelType` | `RTM_CHANNEL_TYPE` | 必填 | - | 频道类型。详见 [`RTM_CHANNEL_TYPE`](/zh-CN/api-reference/rtm/cpp/enumv#channeltype)。 |
| `options` | `const PresenceOptions&` | 必填 | - | 查询附加选项。 |
| `requestId` | uint64_t& | 必填 | - | （输出参数）请求标识符，后续用于识别和处理相应的请求。 |

`PresenceOptions` 数据类型包含以下属性：

|       属性       |  类型   | 是否必填 | 默认值  | 描述  |
| ------------- | ---- | ----- | ---- | --------------- |
| `includeUserId` | bool |   选填   | `true`  | 返回结果中是否包含在线成员的用户 ID。              |
| `includeState`  | bool |   选填   | `false` | 返回结果中是否包含在线用户的临时状态数据。         |
|      `page`      | const char*  |   选填   |    `NULL`    | 页码书签。如果不填写，则 SDK 默认返回第一页结果。你可以在返回值中查看是否还有下一页。 |

### 异步回调 [#异步回调]

调用该方法后，SDK 会触发 `onWhoNowResult` 回调并通过以下参数返回 API 调用结果：

| 参数 | 类型 | 描述 |
| --- | --- | --- |
| `requestId` | const uint64_t | 请求标识符。 |
| `userStateList` | `const UserState*` | 频道在线用户及其临时状态信息列表。 |
| `count` | const size_t | `UserState` 列表长度，当 `whoNow` 属性中 `includeUserId`、`includeState` 属性都设置为 `false` 时，此值表示频道当前总在线人数。 |
| `nextPage` | const char* | [presence-cpp-html-1-4-2 details](#table-slot-presence-cpp-html-1-4-2) |
| `errorCode` | `RTM_ERROR_CODE` | 错误码，详见[错误排查](/zh-CN/realtime-media/rtm/build/troubleshooting)。 |

<a id="table-slot-presence-cpp-html-1-4-2"></a>

**presence-cpp-html-1-4-2 details**

下一页书签。通过该属性可以确认是否有下一页：

- 值为空表示没有下一页。
- 值不为空表示有下一页。你可以将该值填写在 `whoNow` 的 `page` 属性中，从而查询下一页结果。

`UserState` 数据类型包含以下属性：

|  属性  |  类型  | 描述      |
| ---- | ----- | -------- |
| `userId`         |  const char*  |  用户 ID。     |
| `states`         |    `const StateItem*`      | 频道在线用户及其临时状态信息列表。        |
| `statesCount`         |    size_t      | 用户状态的数量。        |

`StateItem` 数据类型包含以下属性：

|  属性  |  类型  | 描述      |
| ---- | ------ | --------- |
| `key`         |         const char*        | 用户状态的键。如果指定的键已经存在，则该键的值会被新值覆盖；如果指定的键不存在，则 SDK 会新增该键/值对。     |
| `value` | const char* | 用户状态的值。 |

### 基本用法 [#基本用法]

```cpp tab="方法调用"
// 方法调用
PresenceOptions options;
options.includeState = true;
options.includeUserId =true;
options.page = "yourBookMark";

uint64_t requestId;
rtmClient->getPresence()->whoNow("channelName", RTM_CHANNEL_TYPE_MESSAGE, options, requestId);
```

```cpp tab="异步回调"
// 异步回调
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) {
            printf("who now failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
        } else {
            printf("who now success\n");
            for (int i = 0 ;i < count; i++) {
                printf("user: %s\n", userStateList[i].userId);
                for (int j = 0 ; j < userStateList[i].statesCount; j++) {
                    printf("key: %s value: %s\n", userStateList[i].states[j].key, userStateList[i].states[j].value);
                }
            }
        }
    }
};
```

## whereNow [#wherenow]

### 接口描述 [#接口描述-1]

在数据统计、App 调试等场景中，你可能需要了解指定用户订阅或加入的所有频道。调用 `whereNow` 方法，你可以实时获取指定用户所在频道的列表。

### 接口方法 [#接口方法-1]

你可以通过以下方式调用 `whereNow` 方法：

```cpp
virtual void whereNow(const char* userId, uint64_t& requestId) = 0;
```

| 参数 | 类型 | 是否必填 | 默认值 | 描述 |
| --- | --- | --- | --- | --- |
| `userId` | const char* | 必填 | - | 用户 ID。 |
| `requestId` | uint64_t& | 必填 | - | （输出参数）请求标识符，后续用于识别和处理相应的请求。 |

### 异步回调 [#异步回调-1]

调用该方法后，SDK 会触发 `onWhereNowResult` 回调并通过以下参数返回 API 调用结果：

| 参数 | 类型 | 描述 |
| --- | --- | --- |
| `requestId` | const uint64_t | 请求标识符。 |
| `channels` | `const ChannelInfo*` | 频道信息。 |
| `count` | const size_t | 频道数量。 |
| `errorCode` | `RTM_ERROR_CODE` | 错误码，详见[错误排查](/zh-CN/realtime-media/rtm/build/troubleshooting)。 |

`ChannelInfo` 数据类型包含以下属性：

|  属性  |  类型  | 描述      |
| ---- | ----- | --------- |
| `channelName`     | const char* |  频道名称。         |
| `channelType` | `RTM_CHANNEL_TYPE` | 频道类型。详见 [`RTM_CHANNEL_TYPE`](/zh-CN/api-reference/rtm/cpp/enumv#channeltype)。 |

### 基本用法 [#基本用法-1]

```cpp tab="方法调用"
// 方法调用
uint64_t requestId;
rtmClient->getPresence()->whereNow("tony", requestId);
```

```cpp tab="异步回调"
// 异步回调
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) {
            printf("where now failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
        } else {
            printf("where now success\n");
            for (int i = 0; i < count; i++) {
                printf("channel: %s channel type: %d\n", channels[i].channelName, channels[i].channelType);
            }
        }
    }
};
```

## setState [#setstate]

### 接口描述 [#接口描述-2]

为满足不同业务场景对用户状态的设置需求，RTM 提供 `setState` 方法自定义临时用户状态。用户可以为自己添加分数、游戏状态、位置、心情、连麦状态等自定义状态。

设置成功后，只要用户保持订阅频道并一直在线，自定义状态就会在频道中持续存在。`setState` 方法设置的是临时用户状态，当用户离开频道或断开与 RTM 的连接时，状态会消失。如果需要在重新加入频道或者重新连接时恢复用户状态，你需要实时在本地缓存该数据。如果你希望永久保存用户状态数据，声网推荐改用 Storage 功能的 `setUserMetadata` 方法。

如果用户修改了临时用户状态，RTM 会实时触发 `RTM_PRESENCE_EVENT_TYPE_REMOTE_STATE_CHANGED` 类型的 `onPresenceEvent` 事件通知。你可以通过订阅频道并配置对应属性来接收该事件。

### 接口方法 [#接口方法-2]

你可以通过以下方式调用 `setState` 方法：

```cpp
virtual void setState(const char* channelName, RTM_CHANNEL_TYPE channelType, const StateItem* items, size_t count, uint64_t& requestId) = 0;
```

| 参数 | 类型 | 是否必填 | 默认值 | 描述 |
| --- | --- | --- | --- | --- |
| `channelName` | const char* | 必填 | - | 频道名称 |
| `channelType` | `RTM_CHANNEL_TYPE` | 必填 | - | 频道类型。详见 [`RTM_CHANNEL_TYPE`](/zh-CN/api-reference/rtm/cpp/enumv#channeltype)。 |
| `items` | `const StateItem*` | 必填 | - | 用户状态。 |
| `count` | size_t | 必填 | - | 用户状态的数量。 |
| `requestId` | uint64_t& | 必填 | - | （输出参数）请求标识符，后续用于识别和处理相应的请求。 |

`StateItem` 数据类型包含以下属性：

|  属性  |  类型  | 描述      |
| ---- | ------ | --------- |
| `key`         |         const char*        | 用户状态的键。如果指定的键已经存在，则该键的值会被新值覆盖；如果指定的键不存在，则 SDK 会新增该键/值对。     |
| `value` | const char* | 用户状态的值。 |

### 异步回调 [#异步回调-2]

调用该方法后，SDK 会触发 `onPresenceSetStateResult` 回调并通过以下参数返回 API 调用结果：

| 参数 | 类型 | 描述 |
| --- | --- | --- |
| `requestId` | const uint64_t | 请求标识符。 |
| `errorCode` | `RTM_ERROR_CODE` | 错误码，详见[错误排查](/zh-CN/realtime-media/rtm/build/troubleshooting)。 |

### 基本用法 [#基本用法-2]

```cpp tab="方法调用"
// 方法调用
std::vector<StateItem> items;
StateItem item;
item.key = "mood";
item.value = "pumped";
items.push_back(item);

uint64_t requestId;
rtmClient->getPresence()->setState("channelName", RTM_CHANNEL_TYPE_MESSAGE, items.data(), items.size(), requestId);
```

```cpp tab="异步回调"
// 异步回调
class RtmEventHandler : public IRtmEventHandler {
    void onPresenceSetStateResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
        if (errorCode != RTM_ERROR_OK) {
            printf("SetState failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
        } else {
            printf("SetState success\n");
        }
    }
};
```

## getState [#getstate]

### 接口描述 [#接口描述-3]

如需获取指定频道中指定用户的临时用户状态，你可以调用 `getState` 方法。

### 接口方法 [#接口方法-3]

你可以通过以下方式调用 `getState` 方法：

```cpp
virtual void getState(const char* channelName, RTM_CHANNEL_TYPE channelType, const char* userId, uint64_t& requestId) = 0;
```

| 参数 | 类型 | 是否必填 | 默认值 | 描述 |
| --- | --- | --- | --- | --- |
| `channelName` | const char* | 必填 | - | 频道名称。 |
| `channelType` | `RTM_CHANNEL_TYPE` | 必填 | - | 频道类型。详见 [`RTM_CHANNEL_TYPE`](/zh-CN/api-reference/rtm/cpp/enumv#channeltype)。 |
| `userId` | const char* | 必填 | - | 用户 ID。 |
| `requestId` | uint64_t& | 必填 | - | （输出参数）请求标识符，后续用于识别和处理相应的请求。 |

### 异步回调 [#异步回调-3]

调用该方法后，SDK 会触发 `onPresenceGetStateResult` 回调并通过以下参数返回 API 调用结果：

| 参数 | 类型 | 描述 |
| --- | --- | --- |
| `requestId` | const uint64_t | 请求标识符。 |
| `state` | `const UserState&` | 频道在线用户及其临时状态信息列表。 |
| `errorCode` | `RTM_ERROR_CODE` | 错误码，详见[错误排查](/zh-CN/realtime-media/rtm/build/troubleshooting)。 |

`UserState` 数据类型包含以下属性：

|  属性  |  类型  | 描述      |
| ---- | ----- | -------- |
| `userId`         |  const char*  |  用户 ID。     |
| `states`         |    `const StateItem*`      | 频道在线用户及其临时状态信息列表。        |
| `statesCount`         |    size_t      | 用户状态的数量。        |

`StateItem` 数据类型包含以下属性：

|  属性  |  类型  | 描述      |
| ---- | ------ | --------- |
| `key`         |         const char*        | 用户状态的键。如果指定的键已经存在，则该键的值会被新值覆盖；如果指定的键不存在，则 SDK 会新增该键/值对。     |
| `value` | const char* | 用户状态的值。 |

### 基本用法 [#基本用法-3]

```cpp tab="方法调用"
// 方法调用
uint64_t requestId;
rtmClient->getPresence()->getState("channelName", RTM_CHANNEL_TYPE_MESSAGE, "tony", requestId);
```

```cpp tab="异步回调"
// 异步回调
class RtmEventHandler : public IRtmEventHandler {
    void onPresenceGetStateResult(const uint64_t requestId, const UserState &state, RTM_ERROR_CODE errorCode) override {
        if (errorCode != RTM_ERROR_OK) {
            printf("GetState failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
        } else {
            printf("GetState user id: %s success\n", state.userId);
            for (int i = 0; i < state.statesCount; i++) {
                printf("key: %s, value: %s\n", state.states[i].key, state.states[i].value);
            }
        }
    }
};
```

## removeState [#removestate]

### 接口描述 [#接口描述-4]

当不再需要某个临时用户状态时，你可以调用 `removeState` 方法删除自己的一个或多个临时状态。

成功删除用户状态后，订阅该频道且开启 Presence 事件监听的用户会收到 `RTM_PRESENCE_EVENT_TYPE_REMOTE_STATE_CHANGED` 类型的 `onPresenceEvent` 事件通知，详见[事件监听](/zh-CN/api-reference/rtm/cpp/configuration#事件监听)。

### 接口方法 [#接口方法-4]

你可以通过以下方式调用 `removeState` 方法：

```cpp
virtual void removeState(const char* channelName, RTM_CHANNEL_TYPE channelType, const char** keys, size_t count, uint64_t& requestId) = 0;
```

| 参数 | 类型 | 是否必填 | 默认值 | 描述 |
| --- | --- | --- | --- | --- |
| `channelName` | const char* | 必填 | - | 频道名称 |
| `channelType` | `RTM_CHANNEL_TYPE` | 必填 | - | 频道类型。详见 [`RTM_CHANNEL_TYPE`](/zh-CN/api-reference/rtm/cpp/enumv#channeltype)。 |
| `keys` | const char** | 必填 | - | 需要删除状态的 `key` 列表。如果不填写，则删除全部状态。 |
| `count` | size_t | 必填 | - | `key` 的数量。 |
| `requestId` | uint64_t& | 必填 | - | （输出参数）请求标识符，后续用于识别和处理相应的请求。 |

### 异步回调 [#异步回调-4]

调用该方法后，SDK 会触发 `onPresenceRemoveStateResult` 回调并通过以下参数返回 API 调用结果：

| 参数 | 类型 | 描述 |
| --- | --- | --- |
| `requestId` | const uint64_t | 请求标识符。 |
| `errorCode` | `RTM_ERROR_CODE` | 错误码，详见[错误排查](/zh-CN/realtime-media/rtm/build/troubleshooting)。 |

### 基本用法 [#基本用法-4]

```cpp tab="方法调用"
// 方法调用
std::vector<const char*> keys;
keys.push_back("key0");
keys.push_back("key1");
keys.push_back("key2");

uint64_t requestId;
rtmClient->getPresence()->removeState("channelName", RTM_CHANNEL_TYPE_MESSAGE, keys.data(), keys.size(), requestId);
```

```cpp tab="异步回调"
// 异步回调
class RtmEventHandler : public IRtmEventHandler {
    void onPresenceRemoveStateResult(const uint64_t requestId, RTM_ERROR_CODE errorCode) override {
        if (errorCode != RTM_ERROR_OK) {
            printf("RemoveState failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
        } else {
            printf("RemoveState success\n");
        }
    }
};
```
