TCP & UDP
Raw TCP connections and UDP datagrams.
On this page
valk.net has TCP servers and clients and UDP sockets, for protocols below HTTP. Reads and writes only pause the current coroutine.
A TCP server#
net.tcp_server listens on a port and accept waits for the next connection. Handle each one in its own coroutine so the server keeps accepting:
use valk.net
fn serve(con: net.TcpConnection) {
defer con.close() _
let buffer = [u8]{ 0 x 4096 }
while true {
let count = con.read(buffer) ! return // `closed` when the client leaves
if count == 0 : return
con.write(buffer.view(0, count)) ! return
}
}
fn main() {
let listener = net.tcp_server("127.0.0.1", 8000) ! {
println("Could not listen on port 8000")
return
}
while true {
let con = listener.accept() ! continue
co serve(con)
}
}
read fills the buffer you pass and returns the byte count. One read is not one message: a read can return part of what the other side wrote, or several writes at once, so your protocol needs its own framing (a length prefix or a delimiter). io.LineReader reads a connection line by line.
A TCP client#
use valk.net
fn main() {
let con = net.tcp_client("127.0.0.1", 8000) ! {
println("Could not connect")
return
}
defer con.close() _
con.write("PING") ! return
let buffer = [u8]{ 0 x 1024 }
let count = con.read(buffer) ! return
println(buffer.view(0, count).to_string()) // PING
}
write sends all of its data. write_many(.{ header, body }) sends several buffers in one go. A TcpConnection is an io.Reader, io.Writer and io.Closer.
| Member | Does |
|---|---|
read_timeout_ms, write_timeout_ms |
Per-operation timeouts (5000 by default); set_timeouts(r, w) sets both |
set_cancel(token) |
A cancelled sync.CancelToken interrupts a waiting read or write |
peer_address(), local_address() |
The two ends as net.SocketAddress |
set_no_delay(true) |
Sends small writes right away (no Nagle) |
set_receive_buffer(n), set_send_buffer(n) |
OS buffer sizes |
tcp_client(host, port, timeout_ms) takes a connect timeout. accept(timeout_ms) gives up with timeout.
UDP#
A UdpServer is bound to your own address and receives datagrams from anyone; a UdpClient talks to one server:
use valk.net
fn main() {
let server = net.udp_server("127.0.0.1", 8001) ! return
let client = net.udp_client("127.0.0.1", 8001) ! return
client.write("PING") ! return
let buffer = [u8]{ 0 x 1500 }
let count, from = server.recv_from(buffer) ! return
println("got " + buffer.view(0, count).to_string() + " from " + from.to_string())
server.send_to("PONG", from) ! return
let reply = client.read(buffer) ! return
println(buffer.view(0, reply).to_string()) // PONG
}
Each recv_from returns one datagram and its sender, so make the buffer as large as the largest datagram. Both sockets have read_timeout_ms / write_timeout_ms and set_cancel.
| Method | Does |
|---|---|
set_broadcast(true) |
Allows sending to a broadcast address |
set_ttl(hops) |
The hop limit |
join_multicast_v4(group) / join_multicast_v6(group) |
Receives a multicast group (server) |
set_multicast_ttl(hops), set_multicast_loopback(on) |
Multicast sending |
udp_server(host, port, timeout_ms, true) lets several sockets bind one port, which multicast receivers need.
Addresses and IPv6#
Hosts can be names, IPv4 addresses or IPv6 addresses ("::1", or "[::1]" as in a URL). A name with both kinds of address uses IPv4. A server bound to "::" also accepts IPv4 clients where the system allows it, and port 0 lets the system pick a free port; local_address() tells which.
use valk.net
fn main() {
let local = net.SocketAddress.parse("::1", 8080) ! return
println(local.to_string()) // [::1]:8080
println(local.ipv6) // true
let v4 = net.SocketAddress.ipv4(127, 0, 0, 1, 80)
println(v4.ip()) // 127.0.0.1
}
SocketAddress.parse only accepts numeric addresses; SocketAddress.resolve(host, port) also looks names up.
