# TCP and UDP
`net/*` provides event-driven TCP/UDP servers and outbound client
connections. All I/O is non-blocking and runs on the VM's epoll event
loop — handlers fire via callbacks when data arrives.
Servers and connections are `native_resource` values: GC-tracked,
finaliser-closed, dynamically startable and stoppable from the REPL.
## `net/*` Stdlib functions
| Function | Returns | Purpose |
|---|---|---|
| `[net/tcp $port $on-connect]` | server resource | Start a TCP server |
| `[net/udp $port $on-data]` | server resource | Start a UDP server |
| `[net/stop $server]` | `:ok` | Stop accepting (keep resource) |
| `[net/start $server]` | `:ok` | Restart a stopped server |
| `[net/close $server]` | `:ok` | Permanently close + drop connections |
| `[net/list]` | list | All registered servers |
| `[net/info $server]` | map | Server name/port/protocol/state/connections |
| `[net/connections $server]` | list | Active connections |
| `[net/connect $host $port]` | conn resource | Outbound TCP client |
| `[net/connect-tls $host $port]` | conn resource | Outbound TLS client (mbedTLS) |
| `[net/send $conn $data]` | bytes written or `%Err` | Send |
| `[net/recv $conn]` | string or nil | Read available data |
| `[net/on-data $conn $fn]` | nil | Register raw-data callback |
| `[net/on-line $conn $fn]` | nil | Register line-mode callback |
| `[net/on-close $conn $fn]` | nil | Register close callback |
| `[net/close-conn $conn]` | nil | Close a connection |
| `[net/udp-send $server $addr $port $data]` | bytes or `%Err` | Send a UDP datagram |
## TCP Server
def on-connect {|conn| @doc "Called with a fresh connection resource on each accept." echo "new connection from" $conn net/on-line $conn { |line| echo "received: $line" net/send $conn "echo: $line\n" } } def srv [net/tcp 8080 $on-connect] # Server runs on the event loop. Stop or close to clean up: # net/stop $srv # stop accepting, keep resource # net/close $srv # permanent close, drops all connections
The connection handler receives a `native_resource` representing the
connection. Register `on-line`, `on-data`, and `on-close` callbacks
on it to drive the protocol.
## UDP Server
def on-data {|datagram| @doc "Called with each UDP datagram arrival." echo "got:" $datagram~data "from" $datagram~addr } def srv [net/udp 9090 $on-data] # To send back: use net/udp-send with the server, addr, and port # net/udp-send $srv "127.0.0.1" $client-port "reply\n"
UDP is connectionless — there's no per-connection resource, just
datagrams arriving at the server.
## Client Connections
def conn [net/connect "example.com" 80] net/send $conn "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n" set received "" net/on-data $conn { |data| set received ($received + $data) if [ends-with? $received "</html>"] { echo "got full response" net/close-conn $conn } } # For TLS: def tls-conn [net/connect-tls "api.github.com" 443]
## Callbacks
`net/on-data`, `net/on-line`, and `net/on-close` register closures
on a connection. Each can be replaced by calling again.
net/on-data $conn { |data| echo "raw bytes: $data(len)" } net/on-line $conn { |line| echo "line: $line" } net/on-close $conn { echo "connection closed" }
`on-line` splits incoming data on newlines and fires once per line.
`on-data` fires for each raw read.
## Server Lifecycle
def srv [net/tcp 8080 $on-connect] [net/info $srv] # => %{ :name "tcp:8080" :port 8080 :protocol :tcp :state :running :connections @{} } [net/connections $srv] # => @{ %{ :addr "127.0.0.1" :port 54321 } ... } net/stop $srv # stop accepting, keeps connections net/start $srv # start accepting again net/close $srv # permanent: stop + drop connections + free resource
## Introspecting Servers
[net/list]
# => @{ %{ :name "tcp:8080" :port 8080 :protocol :tcp :state :running } ... }
## TLS
Outbound TLS uses mbedTLS. The connection drives a small state
machine (connect → handshake → ready) through the same per-fd epoll
callback as plain TCP; once ready, `net/send` and `net/on-line`
transparently use `mbedtls_ssl_write` and `mbedtls_ssl_read`.
def c [net/connect-tls "api.github.com" 443] net/send $c "GET / HTTP/1.1\r\nHost: api.github.com\r\nConnection: close\r\n\r\n" net/on-line $c { |line| echo $line }
Server-side TLS is not currently supported. Use a reverse proxy
(nginx, caddy) for TLS termination if you need it.
## Event Loop
The `deft` scripting binary drives a single event loop. To process
incoming connections and callbacks, your script must keep running —
either via an explicit `vwait` (in a coroutine context) or by
reaching the end of the script after setting up servers and
callbacks.
def srv [net/tcp 8080 $on-connect] echo "listening on :8080" # script blocks here, event loop dispatches callbacks sleep 60000 # crude: keep alive 60s
In a TUI app, the workspace's app-thread loop drives the event loop
automatically.
## Examples
### Echo server
def on-connect {|conn| net/on-line $conn { |line| net/send $conn "echo: $line\n" } } def srv [net/tcp 8080 $on-connect] echo "echo server on :8080" sleep 600000
### HTTP request
def c [net/connect "example.com" 80] net/send $c "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n" set body "" net/on-data $c { |data| set body ($body + $data) } net/on-close $c { echo "got $($body(len)) bytes" }
## Quick Reference
| Form | Purpose |
|---|---|
| `[net/tcp $port $on-connect]` | Start TCP server |
| `[net/udp $port $on-data]` | Start UDP server |
| `[net/connect $host $port]` | Outbound TCP |
| `[net/connect-tls $host $port]` | Outbound TLS |
| `[net/send $conn $data]` | Send |
| `[net/on-line $conn $fn]` | Per-line callback |
| `[net/on-data $conn $fn]` | Per-read callback |
| `[net/on-close $conn $fn]` | Close callback |
| `[net/stop $srv]` / `[net/start $srv]` | Pause/resume |
| `[net/close $srv]` | Permanent close |
| `[net/list]` / `[net/info $srv]` | Introspect |