データグラムとストリームの配送
MoQT の Object(MoQT がデータを運ぶ単位)は、保証の異なる 2 種類のワイヤー形式で運べます。 このページは、1 つの Object を QUIC DATAGRAM として送り、もう少し大きな Object をストリームとして送ります。 データグラムにはロスがあり、再送はなく、コネクション自体のデータグラムサイズに制限されます。 ストリームには信頼性があり、Object 1 つあたり 16 KiB の上限で分割されます。
コード
#define WIRED_MAIN#include "wired.h"
/* Bigger than MOQDATA_BLOB_CHUNK (16384), so the stream delivery below * demonstrates the per-Object cap by arriving as two Objects, not one. */#define BLOB_LEN 20000static u8 g_blob[BLOB_LEN];static u8 g_wire[MOQDATA_BLOB_WIRE_CAP(BLOB_LEN)];
/* Any datagram from the client is just a "go ahead" trigger (content * unchecked): send one lossy OBJECT_DATAGRAM (draft-ietf-moq-transport-22 * 11.2.1), then one reliable Object stream (11.3.1) whose blob exceeds the * 16 KiB per-Object cap and so arrives chunked into two Objects. */static void on_datagram(void* ctx, wired_wt_session* s, wired_span data) { (void)ctx; (void)data;
u8 dg[32]; usz off = 0; moqdg_obj o = {0}; o.track_alias = 2; o.payload = wired_span_cstr("ping-obj"); if (moqdg_put(wired_mspan_of(dg, sizeof dg), &off, &o) == MOQDATA_OK) wired_server_wt_send_datagram_to(s, wired_span_of(dg, off));
usz n = moqdata_blob_build( wired_mspan_of(g_wire, sizeof g_wire), 1, wired_span_of(g_blob, BLOB_LEN)); if (n) wired_moqraw_io().send_uni(s, wired_span_of(g_wire, n));}
int wired_main(int argc, char** argv) { /* The same fixed demo identity as the HTTP/3 hello page. */ static wired_srvboot_demo_keys keys; wired_srvboot_id id; wired_srvboot_demo(&id, &keys, 0x50, "guide-md"); id.max_datagram_frame_size = 65535; /* WebTransport requires DATAGRAM */
for (usz i = 0; i < BLOB_LEN; i++) g_blob[i] = (u8)('a' + i % 26);
/* No hub here, so no SETUP or requests: the "moqt-22" subprotocol only * says the session speaks draft-22's data plane. */ wired_srvrun_opt opt = {0}; opt.incoming_cpu = -1; opt.wt_protocols = "moqt-22"; opt.wt_on_datagram = on_datagram;
u16 port = (u16)wired_cliargs_int(argc, argv, "--port", 4433); wired_srvrun_handler h = {0}; wired_srvrun_obs obs = {0}; return wired_server_run_opt(port, &id, h, obs, &opt) ? 0 : 1;}クライアント
package main
import ( "bufio" "bytes" "context" "fmt" "io" "os" "time"
"wired-guide/moqtclient")
func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // A "moqt-22" WebTransport session; this server runs no hub, so no // SETUP or request follows -- only the data plane. s, conn, status := moqtclient.DialWT(ctx, os.Args[1]+"/deliver") fmt.Println("status", status)
// A "go" datagram tells the server to send one of each delivery mode. moqtclient.Check(s.SendDatagram([]byte("go")))
// Lossy: one OBJECT_DATAGRAM, bounded by the connection's own QUIC // datagram size, dropped (never retried) if the network loses it. dg, err := s.ReceiveDatagram(ctx) moqtclient.Check(err) dr := bufio.NewReader(bytes.NewReader(dg)) moqtclient.Varint(dr) // Type 0x00: Object ID and priority present, no properties, payload alias := moqtclient.Varint(dr) group := moqtclient.Varint(dr) objID := moqtclient.Varint(dr) _, err = dr.ReadByte() // raw Publisher Priority byte, not a varint moqtclient.Check(err) payload, err := io.ReadAll(dr) moqtclient.Check(err) fmt.Printf("datagram alias=%d group=%d id=%d payload=%q\n", alias, group, objID, payload)
// Reliable: one Object stream, its 20000-byte blob split at the 16 KiB // per-Object cap into two Objects that arrive complete, in order. uni, err := s.AcceptUniStream(ctx) moqtclient.Check(err) ur := bufio.NewReader(uni) salias, sgroup, obj1 := moqtclient.ReadObject(ur) moqtclient.Varint(ur) // second Object's ID delta obj2 := make([]byte, moqtclient.Varint(ur)) _, err = io.ReadFull(ur, obj2) moqtclient.Check(err) fmt.Printf("stream alias=%d group=%d object_1=%d bytes object_2=%d bytes total=%d bytes\n", salias, sgroup, len(obj1), len(obj2), len(obj1)+len(obj2))
conn.CloseWithError(0, "")}実行結果
$ ./moqt-delivery --port 4433conn closed by peer (CONNECTION_CLOSE)$ go run . https://127.0.0.1:4433status 200datagram alias=2 group=0 id=0 payload="ping-obj"stream alias=1 group=0 object_1=16384 bytes object_2=3616 bytes total=20000 bytesmoqdg_putは、OBJECT_DATAGRAM(draft-ietf-moq-transport-22 11.2.1)を直接エンコードします。wired_server_wt_send_datagram_toは、それを QUIC DATAGRAM としてキューに積むだけで、ストリームも再送も関与しません。moqdata_blob_buildは、blob を 1 つの SUBGROUP_HEADER と、それに続く最大MOQDATA_BLOB_CHUNK(16384)バイトずつの Object へフレーム化します(draft-ietf-moq-transport-22 11.3.1)。 このページの blob は 20000 バイトなので、16384 バイトと残り 3616 バイトの、2 つの Object になります。wired_server_wt_open_uniは、フレーム化したストリーム全体を FIN まで含めて、信頼性のある形で送ります。- クライアントの
go runの出力には、両方が現れます。 データグラムは 1 つのペイロードとしてそのまま届き、ストリームはobject_1とobject_2として届き、両者の長さを足すと元の 20000 バイトに戻ります。 - このページは、
moqdata/moqdgを使って Object を直接組み立てており、wired_moqt_hubは経由しません。 PUBLISH / SUBSCRIBE という制御メッセージのやり取り(コントロールプレーン)はなく、ハブ自身も土台にしている 2 つの WebTransport 配送プリミティブだけを使っています。 ハブ自身のより高レベルな API については、トラックの公開 を参照してください。