net/udp
Sending and receiving UDP packets, with broadcast and multicast.
import net.udp
UDP sends separate packets, with no connection and no guarantee that they arrive or arrive in order; in exchange it is fast and simple. It is what DNS, games, live streaming and discovering devices on a local network use. Up to about 1500 bytes per packet is safe.
A UDP socket is not tied to one peer. The same socket can send to many addresses and receive from anyone, and every Packet carries from_host and from_port so you know where to answer. Each Send is one packet and each Receive returns one whole packet, so unlike TCP there is no need to find where a message ends.
Choose UDP when a late answer is worth nothing (game state, sensor readings, voice) or when one small question gets one small answer. When every byte must arrive, in order, use net/tcp. To look up names, net/dns already speaks the DNS protocol over UDP for you.
What trips people up
- A timeout looks like an empty packet.
Receivereturns emptydataboth when the timeout passed and when a peer really sent zero bytes, so never use empty packets as messages. - Too small a buffer cuts the packet. The rest of a packet larger than
maxis thrown away, not kept for the next call. Pass the largest size your protocol allows (0 means 65536 forReceive). - Text stops at a zero byte.
Receivegives astr; binary packets needReceiveBytes. - IPv4 only. Sockets, addresses and multicast groups are all IPv4.
- Nothing is guaranteed. Packets can be lost, repeated or reordered; the retry guide shows the usual answer.
Answering a request
A UDP server binds a port and answers each packet to the address it came from. The client uses Open, which picks a free port for it, sends a packet and waits for the reply on the same socket. Set a timeout on both sides, because a lost packet would otherwise leave Receive waiting forever.
import net.udp
import str
mut server := udp.Socket {}
func pong():
udp.SetTimeout(server, 2000)
mut answered := 0
until answered < 3:
p := udp.Receive(server, 1500)
if p.data == "":
break
end
answered += 1
_ := udp.Send(server, p.from_host, p.from_port, "pong " + str.FromInt(answered) + " for " + p.data)
end
end
func main:
server = udp.BindOn("127.0.0.1", 0)
t := spawn(pong)
client := udp.Open()
udp.SetTimeout(client, 2000)
each name in ["alpha", "beta", "gamma"]:
_ := udp.Send(client, "127.0.0.1", udp.Port(server), name)
reply := udp.Receive(client, 1500)
println reply.data
end
join(t)
udp.Close(client)
udp.Close(server)
endBinding to 127.0.0.1 with BindOn keeps the server private to this machine. Bind listens on every address, which is what a service on the network needs.
Timeouts and retries
UDP never tells you that a packet was lost, so a client that expects an answer has to wait a short time and send again. With SetTimeout, Receive returns a packet with empty data when nothing arrived in time. The server in this example ignores the first two requests, the way a busy network might drop them, and the client tries until it gets an answer or runs out of attempts.
import net.udp
mut server := udp.Socket {}
func flaky():
udp.SetTimeout(server, 3000)
mut seen := 0
until seen < 3:
p := udp.Receive(server, 1500)
if p.data == "":
break
end
seen += 1
if seen == 3:
_ := udp.Send(server, p.from_host, p.from_port, "got " + p.data)
end
end
end
func ask(sock: udp.Socket, port: int, question: str, tries: int): str:
repeat tries in i:
_ := udp.Send(sock, "127.0.0.1", port, question)
reply := udp.Receive(sock, 1500)
if reply.data != "":
ret reply.data
end
println "attempt", i + 1, "timed out"
end
ret ""
end
func main:
server = udp.BindOn("127.0.0.1", 0)
t := spawn(flaky)
client := udp.Open()
udp.SetTimeout(client, 200)
answer := ask(client, udp.Port(server), "status?", 5)
if answer == "":
println "server unreachable"
else:
println "answer:", answer
end
join(t)
udp.Close(client)
udp.Close(server)
endBecause a request can arrive twice (the first copy was only slow, not lost), a request that changes something should carry an id, so the server can recognise a repeat and answer it without doing the work again. DNS works exactly like this.
Binary packets
Compact protocols put numbers in bytes instead of text. SendBytes sends a block of memory as one packet and ReceiveBytes fills a buffer and reports the sender through its last two arguments. The packet here has one byte for the kind of message, one for a sensor number and two for a value, high byte first, which is the usual network order.
import net.udp
import mem
func main:
receiver := udp.BindOn("127.0.0.1", 0)
sender := udp.Open()
udp.SetTimeout(receiver, 2000)
out := mem.AllocateZeroed(8 as u64)
o := out as u8*
o[0] = 1 as u8
o[1] = 42 as u8
o[2] = 1 as u8
o[3] = 44 as u8
sent := udp.SendBytes(sender, "127.0.0.1", udp.Port(receiver), out, 4)
println "sent", sent, "bytes"
buf := mem.AllocateZeroed(1500 as u64)
mut host := ""
mut port := 0
n := udp.ReceiveBytes(receiver, buf, 1500, host, port)
b := buf as u8*
kind := b[0] as int
sensor := b[1] as int
value := (b[2] as int) * 256 + (b[3] as int)
println "received", n, "bytes from", host
println "kind", kind, "sensor", sensor, "value", value
mem.Free(out)
mem.Free(buf)
udp.Close(sender)
udp.Close(receiver)
endKeep packets below about 1500 bytes. Larger ones are split by the network and lost completely if one part goes missing. Use ReceiveBytes rather than Receive whenever the data may contain a zero byte, since a str ends at the first one.
Finding devices on the local network
To find a printer, a game host or another copy of your program on the same network without knowing its address, send to a group instead of to one machine. With JoinMulticast a socket receives what anyone sends to the group address; SetBroadcast instead allows sending to the broadcast address of the whole network, such as 255.255.255.255. This example depends on how the network is set up, so it is only compiled here. On most machines it prints heard: printer at 192.168.1.50, because the sender also receives its own multicast packets.
import net.udp
func main:
group := "239.255.42.99"
port := 9999
listener := udp.Bind(port)
if not udp.JoinMulticast(listener, group):
println "could not join", group
ret 1
end
udp.SetTimeout(listener, 1000)
announcer := udp.Open()
_ := udp.Send(announcer, group, port, "printer at 192.168.1.50")
p := udp.Receive(listener, 1500)
if p.data == "":
println "nobody announced anything"
else:
println "heard:", p.data
end
_ := udp.LeaveMulticast(listener, group)
udp.Close(announcer)
udp.Close(listener)
ret 0
endMulticast addresses from 239.0.0.0 to 239.255.255.255 are meant for private use inside an organisation. Every program that wants the announcements binds the same port and joins the same group.
Types
Socket
struct Socket| Name | Type | Description |
|---|---|---|
fd | i64 = -1 | The socket descriptor. |
import net.udp
import mem
func main:
receiver := udp.BindOn("127.0.0.1", 0)
sender := udp.Open()
println udp.Ok(receiver), udp.Ok(sender), udp.Port(receiver) > 0
udp.SetTimeout(receiver, 2000)
println udp.Send(sender, "127.0.0.1", udp.Port(receiver), "hello")
p := udp.Receive(receiver, 1500)
println p.data, p.from_host, p.from_port > 0
_ := udp.SendTo(sender, "127.0.0.1", udp.Port(receiver), "two")
text := "three"
_ := udp.SendBytes(sender, "127.0.0.1", udp.Port(receiver), text as void*, text.len())
println udp.Receive(receiver, 1500).data
buffer := mem.AllocateZeroed(64 as u64)
mut host := ""
mut port := 0
println udp.ReceiveBytes(receiver, buffer, 64, host, port), host
udp.SetTimeout(receiver, 100)
println "[" + udp.Receive(receiver, 1500).data + "]"
udp.SetBroadcast(sender, true)
println udp.JoinMulticast(receiver, "239.1.2.3") or true, udp.LeaveMulticast(receiver, "239.1.2.3") or true
other := udp.Bind(0)
println udp.Ok(other)
udp.Close(other)
udp.Close(receiver)
udp.Close(sender)
mem.Free(buffer)
endPacket
struct Packet| Name | Type | Description |
|---|---|---|
data | str = "" | The contents; empty when nothing arrived within the timeout. |
from_host | str = "" | The address of the sender. |
from_port | int = 0 | The port of the sender. |
The example for this item is shown with the ones above: net-udp-Echo.salam
Functions
Bind
func Bind(port: int): SocketThe example for this item is shown with the ones above: net-udp-Echo.salam
BindOn
func BindOn(host: str, port: int): SocketBind, but on the given address only.The example for this item is shown with the ones above: net-udp-Echo.salam
Open
func Open(): SocketThe example for this item is shown with the ones above: net-udp-Echo.salam
Port
func Port(s: Socket): intThe example for this item is shown with the ones above: net-udp-Echo.salam
Ok
func Ok(s: Socket): boolThe example for this item is shown with the ones above: net-udp-Echo.salam
Close
func Close(s: Socket)The example for this item is shown with the ones above: net-udp-Echo.salam
Send
func Send(s: Socket, host: str, port: int, data: str): intThe example for this item is shown with the ones above: net-udp-Echo.salam
SendTo
func SendTo(s: Socket, host: str, port: int, data: str): intSend.The example for this item is shown with the ones above: net-udp-Echo.salam
SendBytes
func SendBytes(s: Socket, host: str, port: int, ptr: void*, n: int): intn bytes from memory in one packet.The example for this item is shown with the ones above: net-udp-Echo.salam
Receive
func Receive(s: Socket, max: int): Packetmax bytes (65536 when max is 0); a longer packet is cut. When a timeout is set and nothing arrives, the packet comes back with empty data.The example for this item is shown with the ones above: net-udp-Echo.salam
ReceiveBytes
func ReceiveBytes(s: Socket, buf: void*, max: int, out_host &: str, out_port &: int): intThe example for this item is shown with the ones above: net-udp-Echo.salam
SetBroadcast
func SetBroadcast(s: Socket, enable: bool)The example for this item is shown with the ones above: net-udp-Echo.salam
SetTimeout
func SetTimeout(s: Socket, millis: int)The example for this item is shown with the ones above: net-udp-Echo.salam
JoinMulticast
func JoinMulticast(s: Socket, group_ip: str): boolThe example for this item is shown with the ones above: net-udp-Echo.salam
LeaveMulticast
func LeaveMulticast(s: Socket, group_ip: str): boolThe example for this item is shown with the ones above: net-udp-Echo.salam