Fanning a datagram out to every session
srvrun.h has three ways to fan a datagram out from inside a callback: a single-slot broadcast (last writer wins if two land in the same step), a bounded ring that queues several without overwriting, and sending to one session directly. This page picks between them by a leading tag byte.
Code
#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;}Client
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)}Output
$ ./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"How it works
Section titled “How it works”- Tag
1callswired_server_broadcast_datagram: queues one payload per connection with an active WT session, overwritten by a second call in the same step. - Tag
2callswired_server_broadcast_datagram_ring: the same fan-out, but into the bounded ring also backingwired_server_wt_send_datagram_to– several calls in one step are all delivered, none overwritten. - Tag
3callswired_server_wt_send_datagram_to(s, ...): addressed atsonly, not a broadcast – the client’s second session never sees it. - Neither primitive excludes the sender: a broadcast reaches every active session, including the one it came from, so the client reads its own copy back too.