← Cloudflare Realtime / realtime / sfu
DataChannels
Используйте DataChannels Realtime SFU, чтобы передавать данные приложения с низкой задержкой через WebRTC. К типичной полезной нагрузке относятся сообщения чата, состояние игры, обновления датчиков и управляющие события.
Для передачи аудио и видео используйте медиатреки Realtime SFU, а не DataChannels.
graph LR
A[Publisher] -->|Application data| B[Cloudflare Realtime SFU]
B -->|Application data| C@{ shape: procs, label: "Subscribers"}
Каждый издатель может отправлять именованный DataChannel нескольким подписчикам. По умолчанию сообщения передаются от издателя к подписчикам.
Настройка DataChannel
- Создайте сессию Realtime для издателя и по одной для каждого подписчика.
- В каждой сессии создавайте транспорт DataChannel с помощью
POST /apps/{appId}/sessions/{sessionId}/datachannels/establish. Перед созданием каналов завершите весь необходимый обмен Session Description Protocol (SDP). - В сессии издателя создайте именованный DataChannel с помощью
POST /apps/{appId}/sessions/{sessionId}/datachannels/newи задайтеlocationк"local". - В каждой сессии подписчика вызывайте тот же эндпойнт с
locationимеет значение"remote". ЗадайтеsessionIdк ID сессии издателя и используйте тот жеdataChannelName. - В каждом клиенте вызовите
createDataChannel()сnegotiated: trueи идентификатор, возвращённый API. - После открытия DataChannel отправляйте сообщения от издателя.
Настройка доставки сообщений
DataChannels по умолчанию используют надежную доставку с сохранением порядка. Выбирайте частичную надежность или доставку без сохранения порядка, если актуальность данных важнее их гарантированной доставки, например при передаче состояния игры или показаний датчиков в реальном времени.
Задайте эти необязательные поля при создании DataChannel с помощью HTTPS API:
ordered(boolean, по умолчаниюtrue): установите значениеfalseчтобы разрешить доставку сообщений не по порядку. Задержанное сообщение не будет блокировать последующие сообщения.maxRetransmits(integer): ограничивает количество попыток повторной отправки после первой передачи. Установите значение0для отключения повторных передач, либо не указывайте, чтобы снять ограничение на их количество.maxPacketLifeTime(integer): ограничивает, сколько миллисекунд транспорт пытается доставить сообщение. Не указывайте значение, если ограничение по времени жизни не требуется.
maxRetransmits и maxPacketLifeTime являются взаимоисключающими. Не задавайте оба параметра для одного и того же канала.
Поведение упорядочивания и повторных попыток не связано друг с другом. Для надёжной доставки без сохранения порядка задайте ordered: false и опустить оба maxRetransmits и maxPacketLifeTime. Сообщения могут приходить не по порядку, но транспорт продолжает повторять неудавшиеся попытки доставки.
Используйте те же значения на стороне издателя (location: "local"), каждый подписчик (location: "remote"), и у каждого клиента createDataChannel() вызов. Realtime DataChannels используют согласованные идентификаторы, поэтому браузер не получает эти настройки от удалённого пира.
Создайте ненадёжный неупорядоченный канал издателя:
{
"dataChannels": [
{
"location": "local",
"dataChannelName": "player-state",
"ordered": false,
"maxRetransmits": 0
}
]
}Затем создайте соответствующий удалённый канал на подписчике с теми же полями надёжности:
{
"dataChannels": [
{
"location": "remote",
"sessionId": "<PUBLISHER_SESSION_ID>",
"dataChannelName": "player-state",
"ordered": false,
"maxRetransmits": 0
}
]
}Создайте соответствующий DataChannel в браузере с такими же настройками. В этом примере pc является активным RTCPeerConnection, а также resp представляет собой ответ API для канала.
const dc = pc.createDataChannel("player-state", {
negotiated: true,
id: resp.dataChannels[0].id,
ordered: false,
maxRetransmits: 0,
});Для частичной надёжности выберите ограничение на количество повторных передач или время жизни пакета в зависимости от того, как долго полезная нагрузка сохраняет актуальность.
Дождитесь готовности подписчика (waitForAck)
Задайте waitForAck: true на удаленном DataChannel, чтобы задержать доставку, пока подписчик не сообщит о своей готовности.
waitForAckприменяется только кlocation: "remote"DataChannels и по умолчанию равноfalse.- Пока шлюз закрыт, SFU задерживает доставку сообщений этому подписчику.
- После открытия DataChannel подписчик отправляет любое сообщение, например
"ack". SFU обрабатывает это первое сообщение, открывает шлюз и начинает пересылать сообщения издателя. - Подтверждение должно поступить на SFU в течение 30 секунд после создания удалённого DataChannel. В противном случае SFU закроет такой канал. Чтобы повторить попытку, создайте удалённый DataChannel заново.
Без canReply, более поздние сообщения подписчика не пересылаются издателю.
Создайте удалённый DataChannel с включённым gate, вызвав POST /apps/{appId}/sessions/{sessionId}/datachannels/new для сессии подписчика:
{
"dataChannels": [
{
"location": "remote",
"sessionId": "<PUBLISHER_SESSION_ID>",
"dataChannelName": "my-channel",
"waitForAck": true
}
]
}Затем на подписчике отправьте подтверждение после того, как DataChannel откроется. В этом примере предполагается, что вы уже инициализировали API_BASE, headers, а также pc, и определили waitForOpen() вспомогательную функцию.
const response = await fetch(
`${API_BASE}/sessions/${subscriberId}/datachannels/new`,
{
method: "POST",
headers,
body: JSON.stringify({
dataChannels: [
{
location: "remote",
sessionId: publisherId,
dataChannelName: "my-channel",
waitForAck: true,
},
],
}),
},
);
if (!response.ok) {
throw new Error(`Failed to create DataChannel: ${response.status}`);
}
const resp = await response.json();
const channelId = resp.dataChannels?.[0]?.id;
if (channelId === undefined) {
throw new Error("DataChannel response did not include an id");
}
const dc = pc.createDataChannel("my-channel-subscribed", {
negotiated: true,
id: channelId,
});
await waitForOpen(dc);
dc.send("ack"); // The first message opens the gate.Возврат к издателю (canReply)
По умолчанию сообщения передаются от издателя к подписчикам. Задайте canReply: true когда одному подписчику нужно ответить в том же канале, например когда оператор отвечает устройству, публикующему телеметрию.
graph LR
P[Publisher] -->|Publisher messages| SFU[Cloudflare Realtime SFU]
SFU -->|Publisher messages| S1[Subscriber with canReply]
SFU -->|Publisher messages| S2[Other subscribers]
S1 -->|Reply| SFU
SFU -->|Reply| P
canReply управляет доступом к ответам следующим образом:
canReplyприменяется только кlocation: "remote"DataChannels и по умолчанию равноfalse.- Не более одного подписчика может иметь доступ для ответа к каждому DataChannel издателя. Предоставление доступа другому подписчику заменяет предыдущего подписчика.
- SFU пересылает ответы только от подписчика, у которого есть доступ.
- Издатель получает ответы. Остальные подписчики их не получают.
Разрешите ответы при подписке
Создайте удалённый DataChannel в сессии подписчика с canReply: true:
{
"dataChannels": [
{
"location": "remote",
"sessionId": "<PUBLISHER_SESSION_ID>",
"dataChannelName": "my-channel",
"canReply": true
}
]
}Пример потока:
- У издателя создайте локальный DataChannel с именем
my-channel. - У подписчика получите DataChannel с помощью
canReply: trueи открыть согласованный канал в браузере. - От издателя отправьте сообщение подписчику.
- От подписчика отправьте ответ в том же канале. Издатель получит этот ответ.
Изменить доступ к ответам
Чтобы изменить доступ для ответа без пересоздания удалённого DataChannel, вызовите PUT /apps/{appId}/sessions/{subscriberSessionId}/datachannels/update:
{
"dataChannels": [
{
"location": "remote",
"sessionId": "<PUBLISHER_SESSION_ID>",
"dataChannelName": "my-channel",
"canReply": true
}
]
}Используйте то же тело запроса с "canReply": false для отзыва. В следующей таблице перечислены распространенные шаблоны:
| Цель | Действие |
|---|---|
| Разрешите ответы после подписки | Создайте удалённый DataChannel без canReply, затем обновите его с помощью canReply: true. |
| Передача доступа другому подписчику | У нового подписчика обновите DataChannel с помощью canReply: true. Предыдущий подписчик теряет доступ для ответа. |
| Остановить ответы | У подписчика с доступом на ответ обновите DataChannel с помощью canReply: false. |
// The subscriber already pulled "my-channel" without canReply.
// Allow replies later.
const response = await fetch(
`${API_BASE}/sessions/${subscriberId}/datachannels/update`,
{
method: "PUT",
headers,
body: JSON.stringify({
dataChannels: [
{
location: "remote",
sessionId: publisherId,
dataChannelName: "my-channel",
canReply: true,
},
],
}),
},
);
if (!response.ok) {
throw new Error(`Failed to update DataChannel: ${response.status}`);
}
// The same negotiated DataChannel can now send replies to the publisher.
dc.send(JSON.stringify({ type: "reply", body: "pong" }));Сочетание подтверждения и ответов
Вы можете задать оба canReply и waitForAck на том же удаленном DataChannel. Первое сообщение подписчика открывает шлюз подтверждения и не пересылается. Последующие сообщения подписчика пересылаются издателю, пока у этого подписчика есть доступ для ответа.
Пример
Просмотрите Пример echo для DataChannel ↗ для полной настройки транспорта, публикации и подписки.
В примере токен приложения размещён в коде браузера для локального тестирования. В production храните токен на своём backend-сервере.