INTEGRITY Документация

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

  1. Создайте сессию Realtime для издателя и по одной для каждого подписчика.
  2. В каждой сессии создавайте транспорт DataChannel с помощью POST /apps/{appId}/sessions/{sessionId}/datachannels/establish. Перед созданием каналов завершите весь необходимый обмен Session Description Protocol (SDP).
  3. В сессии издателя создайте именованный DataChannel с помощью POST /apps/{appId}/sessions/{sessionId}/datachannels/new и задайте location к "local".
  4. В каждой сессии подписчика вызывайте тот же эндпойнт с location имеет значение "remote". Задайте sessionId к ID сессии издателя и используйте тот же dataChannelName.
  5. В каждом клиенте вызовите createDataChannel() с negotiated: true и идентификатор, возвращённый API.
  6. После открытия DataChannel отправляйте сообщения от издателя.

Настройка доставки сообщений

DataChannels по умолчанию используют надежную доставку с сохранением порядка. Выбирайте частичную надежность или доставку без сохранения порядка, если актуальность данных важнее их гарантированной доставки, например при передаче состояния игры или показаний датчиков в реальном времени.

Задайте эти необязательные поля при создании DataChannel с помощью HTTPS API:

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, чтобы задержать доставку, пока подписчик не сообщит о своей готовности.

Без 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 управляет доступом к ответам следующим образом:

Разрешите ответы при подписке

Создайте удалённый DataChannel в сессии подписчика с canReply: true:

{
	"dataChannels": [
		{
			"location": "remote",
			"sessionId": "<PUBLISHER_SESSION_ID>",
			"dataChannelName": "my-channel",
			"canReply": true
		}
	]
}

Пример потока:

  1. У издателя создайте локальный DataChannel с именем my-channel.
  2. У подписчика получите DataChannel с помощью canReply: true и открыть согласованный канал в браузере.
  3. От издателя отправьте сообщение подписчику.
  4. От подписчика отправьте ответ в том же канале. Издатель получит этот ответ.

Изменить доступ к ответам

Чтобы изменить доступ для ответа без пересоздания удалённого 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-сервере.