Send messages through the RDT channel

Updated

Send and receive reliable control and device messages using RDT.

Starting with v1.9.2, IoT SDK adds Reliable Data Transmission (RDT), an end-to-end reliable messaging channel built on top of the RTC connection that runs independently from your audio and video streams. RDT delivers messages reliably and in order, so use it for content that must arrive complete and intact, such as device commands, status updates, or file transfers and downloads, rather than the best-effort delivery used for media data.

Understand the tech

Within a single RTC connection, you can simultaneously send audio data, video data, and RDT messages.

The following figure shows the RDT connection lifecycle, and how the RDT channel state determines which messages you can send:

RDT supports two message types with different priority and size limits:

TypePriorityLimits
RDT_STREAM_CMDHigh. Not subject to congestion control.Up to 100 packets per second; 256 bytes maximum packet size.
RDT_STREAM_DATALower. Subject to congestion control.Up to 4 Mbps per second; 128 KB maximum packet size. No packet-count limit.

Prerequisites

In order to follow this procedure you must have:

Enable RDT

RDT connections are established per remote user, based on the on_user_joined and on_user_offline callbacks. Only users with the broadcaster role trigger these callbacks. IoT SDK sets every user as a broadcaster by default, so this usually needs no extra configuration, but if you use IoT SDK alongside another SDK, such as RTC SDK, make sure the user's role is also set to broadcaster in that other SDK.

IoT SDK establishes RDT connections automatically, so you only need to set enable_rdt to true in rtc_channel_options_t before you call agora_rtc_join_channel to use RDT. Set enable_rdt to false if you don't want to use RDT at all.

int rval;
connection_id_t conn_id = 0;

rval = agora_rtc_create_connection(&conn_id);
if (rval < 0) {
    printf("Failed to create connection, reason: %s\n", agora_rtc_err_2_str(rval));
    return -1;
}

rtc_channel_options_t channel_opt = { 0 };
channel_opt.enable_rdt = true;

rval = agora_rtc_join_channel(conn_id, "channel-xxx", uid, token, &channel_opt);
if (rval < 0) {
    printf("Failed to join channel, reason: %s\n", agora_rtc_err_2_str(rval));
    return -1;
}

Control how RDT connections are established

Use auto_connect_rdt in rtc_channel_options_t to control whether IoT SDK proactively establishes an RDT connection with each remote user:

  • auto_connect_rdt = true: IoT SDK automatically establishes an RDT connection with a remote user when it receives on_user_joined for that user, and automatically disconnects it when it receives on_user_offline.
  • auto_connect_rdt = false: IoT SDK doesn't proactively establish RDT connections, but still accepts incoming RDT connection requests from remote users.
rtc_channel_options_t channel_opt = { 0 };
channel_opt.enable_rdt = true;
// Configure whether to proactively establish RDT connections.
// true: establish; false: don't establish (IoT SDK still accepts incoming requests)
channel_opt.auto_connect_rdt = true;

Set auto_connect_rdt based on your use case. For example:

  • In IoT scenarios such as smart cameras and doorbells, you typically want every device to exchange RDT messages with every client, but you don't want clients to exchange RDT messages with each other. Set auto_connect_rdt = true when a device joins the channel, so it establishes RDT connections with each client, and set auto_connect_rdt = false when a client joins the channel, so clients don't establish RDT connections with each other.
  • In office scenarios such as video conferencing and real-time collaboration, you typically want every participant to exchange data with every other participant. Set auto_connect_rdt = true for all users, so they establish RDT connections with each other.

Check RDT channel state

Handle the on_rdt_state callback to track changes in RDT channel availability for a peer:

rdt_state_e g_rdt_state = RDT_STATE_CLOSED;

static void __on_rdt_state(connection_id_t conn_id, uint32_t uid, rdt_state_e state)
{
    g_rdt_state = state;
}

The callback reports one of the following states:

StateMeaning
RDT_STATE_CLOSEDThe RDT channel is closed. You can't send or receive messages.
RDT_STATE_OPENEDThe RDT channel is open. You can send and receive both message types.
RDT_STATE_BLOCKEDThe send buffer is full. You can still send RDT_STREAM_CMD messages, but not RDT_STREAM_DATA messages.
RDT_STATE_PENDINGIoT SDK is reconnecting the RDT channel. If reconnection succeeds, the state changes to RDT_STATE_OPENED and buffered data is preserved. If reconnection fails, the state changes to RDT_STATE_CLOSED and buffered data is cleared.
RDT_STATE_BROKENThe RDT channel is broken. IoT SDK attempts to reconnect, but clears any buffered data regardless of whether reconnection succeeds.

To avoid losing data that's still in transit, call agora_rtc_get_rdt_status_info to check the send buffer length (send_queue_size) before you send a new message.

Send a message

Check the RDT channel state before you send, then call agora_rtc_send_rdt_msg with the connection ID, the peer's user ID, the message type, and the payload.

To send a high-priority control message:

if (g_rdt_state == RDT_STATE_OPENED || g_rdt_state == RDT_STATE_BLOCKED) {
    uint8_t cmd[256] = { 0 };
    int rval = agora_rtc_send_rdt_msg(conn_id, peer_uid, RDT_STREAM_CMD, cmd, sizeof(cmd));
    if (rval < 0) {
        printf("Failed to send RDT message, reason: %s\n", agora_rtc_err_2_str(rval));
        return -1;
    }
}

To send a lower-priority data message:

if (g_rdt_state == RDT_STATE_OPENED) {
    uint8_t msg[1024] = { 0 };
    int rval = agora_rtc_send_rdt_msg(conn_id, peer_uid, RDT_STREAM_DATA, msg, sizeof(msg));
    if (rval < 0) {
        printf("Failed to send RDT message, reason: %s\n", agora_rtc_err_2_str(rval));
        return -1;
    }
}

peer_uid is the user ID of the remote user you're sending to. Use the on_user_joined and on_user_offline callbacks to track the user IDs of remote users you can send RDT messages to.

If agora_rtc_send_rdt_msg fails, see Error codes for the possible reasons.

Receive messages

Handle the on_rdt_msg callback to receive incoming RDT messages:

static void __on_rdt_msg(connection_id_t conn_id, uint32_t uid, rdt_stream_type_e type, const void *msg, size_t len)
{
    printf("Received RDT message from uid=%u type=%d len=%zu\n", uid, type, len);
}

Disconnect RDT

When you leave the RTC channel, IoT SDK automatically disconnects every RDT connection created within that channel.

To avoid losing data that's still in transit, call agora_rtc_get_rdt_status_info to check that send_queue_size is 0, confirming the remote user received all buffered messages, before you leave the channel.

Sample code

The IoT SDK package includes complete RDT examples. See the full implementation in hello_rdt.c and hello_rdt_multi.c:

├── agora_rtsa_sdk  # Agora SDK libraries and header files
└── example
    └── hello_rdt
        ├── hello_rdt.c        # Send and receive RDT messages in a single channel
        └── hello_rdt_multi.c  # Send and receive RDT messages across multiple channels

Reference

This section contains content that completes the information on this page, or points you to documentation that explains other aspects of this product.

Error codes

agora_rtc_send_rdt_msg returns the following error codes when a message fails to send:

  • ERR_RDT_USER_NOT_EXIST (600): the peer user doesn't exist in the channel.
  • ERR_RDT_USER_NOT_READY (601): the RDT channel isn't established yet. Wait for RDT_STATE_OPENED.
  • ERR_RDT_DATA_BLOCKED (602): the send buffer is full. Wait for RDT_STATE_OPENED.
  • ERR_RDT_CMD_EXCEED_LIMIT (603): you've exceeded the 100 packets-per-second limit for RDT_STREAM_CMD messages.
  • ERR_RDT_DATA_EXCEED_LIMIT (604): you've exceeded the 4 Mbps per second limit for RDT_STREAM_DATA messages.

See the full error code reference for more error codes.

API reference