データグラムを全セッションへ配る
srvrun.h には、コールバックの内側からデータグラムを配る方法が 3 つあります。 単一スロットのブロードキャスト(同じステップ内で 2 回呼ぶと、後の呼び出しが前の内容を上書きします)、 複数件を上書きせずにためられる上限付きのリング(環状のキュー)、そして 1 つのセッションへ直接送る方法です。 このページのサーバーは、先頭の 1 バイトのタグでこの 3 つを切り替えます。
コード
#define WIRED_MAIN#include "wired.h"
/* The first byte of a received datagram picks which of srvrun.h's three * datagram-fanout primitives relays the rest: 1 = the single-slot * wired_server_broadcast_datagram (a second broadcast in the same loop * step overwrites the first); 2 = wired_server_broadcast_datagram_ring, * the bounded ring that queues several without overwriting; 3 = * wired_server_wt_send_datagram_to, addressed back at the sender only -- * not a broadcast at all. */static void on_datagram(void* ctx, wired_wt_session* s, wired_span data) { (void)ctx; if (data.n == 0) return; wired_span rest = wired_span_of(data.p + 1, data.n - 1); switch (data.p[0]) { case 1: wired_server_broadcast_datagram(rest); break; case 2: wired_server_broadcast_datagram_ring(rest); break; case 3: wired_server_wt_send_datagram_to(s, rest); break; }}
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-wt"); id.max_datagram_frame_size = 65535; /* WebTransport requires DATAGRAM */
wired_srvrun_opt opt = {0}; opt.incoming_cpu = -1; 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 ( "context" "crypto/tls" "fmt" "net/http" "os"
"github.com/quic-go/quic-go" "github.com/quic-go/webtransport-go")
func dial(ctx context.Context, addr string) (*webtransport.Session, *quic.Conn) { var conn *quic.Conn // the latest connection, kept so main can close it d := webtransport.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, QUICConfig: &quic.Config{EnableDatagrams: true, EnableStreamResetPartialDelivery: true}, DialAddr: func(ctx context.Context, addr string, t *tls.Config, q *quic.Config) (*quic.Conn, error) { c, err := quic.DialAddrEarly(ctx, addr, t, q) conn = c return c, err }, } _, s, err := d.Dial(ctx, addr+"/relay", http.Header{}) if err != nil { panic(err) } return s, conn}
func main() { ctx := context.Background() a, connA := dial(ctx, os.Args[1]) b, connB := dial(ctx, os.Args[1]) defer connA.CloseWithError(0, "") defer connB.CloseWithError(0, "")
// tag 1: the single-slot broadcast reaches every session, including A. a.SendDatagram([]byte{1, 's', 'l', 'o', 't'}) for _, who := range []struct { name string s *webtransport.Session }{{"A", a}, {"B", b}} { msg, err := who.s.ReceiveDatagram(ctx) if err != nil { panic(err) } fmt.Printf("%s recv %q\n", who.name, msg) }
// tag 2: the ring delivers all three, in order, to every session. for _, word := range []string{"ring1", "ring2", "ring3"} { a.SendDatagram(append([]byte{2}, word...)) } for i := 0; i < 3; i++ { for _, who := range []struct { name string s *webtransport.Session }{{"A", a}, {"B", b}} { msg, err := who.s.ReceiveDatagram(ctx) if err != nil { panic(err) } fmt.Printf("%s recv %q\n", who.name, msg) } }
// tag 3: send_datagram_to addresses only the sender, B never sees it. a.SendDatagram([]byte{3, 'd', 'i', 'r', 'e', 'c', 't'}) msg, err := a.ReceiveDatagram(ctx) if err != nil { panic(err) } fmt.Printf("A recv %q\n", msg)}実行結果
$ ./wt-broadcast --port 4433conn closed by peer (CONNECTION_CLOSE)conn closed by peer (CONNECTION_CLOSE)$ go run . https://127.0.0.1:4433A recv "slot"B recv "slot"A recv "ring1"B recv "ring1"A recv "ring2"B recv "ring2"A recv "ring3"B recv "ring3"A recv "direct"- タグ
1はwired_server_broadcast_datagramを呼びます。 アクティブな WT(WebTransport)セッションを持つ接続ごとに 1 件分のペイロードを積みますが、 同じステップ内で 2 回目を呼ぶと上書きされます。 - タグ
2はwired_server_broadcast_datagram_ringを呼びます。 配る先は同じですが、wired_server_wt_send_datagram_toも内部で使っている上限付きのリングに積まれます。 同じステップ内で複数回呼んでも、どれも上書きされずに届きます。 - タグ
3はwired_server_wt_send_datagram_to(s, ...)を呼びます。sだけに送られる、ブロードキャストではない呼び出しです。 クライアントの 2 つめのセッションには届きません。 - どちらのブロードキャストも送信元を除外しません。 ブロードキャストはアクティブな全セッション(送信元自身を含む)に届くので、 クライアントは自分が送った分も受信します。