Docs/Networking

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:

Valk
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#

Valk
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:

Valk
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.

Valk
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.