Skip to content

net

Event-driven TCP and UDP client sockets. Each socket is an i32 handle; inbound data and lifecycle transitions arrive as events, so the VM never blocks on the network — the same non-blocking model as ws and mqtt.

Version v0.1
Platform PC, ESP32
Type Native (C)

Clients only

This version covers the TCP client and UDP client. Server sockets (tcpListen/tcpAccept) are deferred to a later release.

star.mod

require dev-libs/net v0.1

Usage

import "net"

Functions

Open / Close

Function Signature Description
net.tcpConnect(host, port) (string, i32) -> i32 Start an async TCP connection, returns handle (-1 on error)
net.udpOpen(port) (i32) -> i32 Open a UDP socket (0 = ephemeral local port), returns handle
net.close(sock) (i32) -> void Close the socket and free the handle
var sock: i32 = net.tcpConnect("192.168.1.10", 8080)
# ... use connection ...
net.close(sock)

net.tcpConnect() is non-blocking — the call returns the handle immediately and the connection proceeds asynchronously. The connected event fires when the TCP connection is established. On ESP32 the IP stack does not exist until WiFi has an address, so opening before then returns -1 rather than crashing; open from the WiFi gotip handler (see wifi).

Send

Function Signature Description
net.send(sock, data) (i32, string) -> i32 Queue bytes on a TCP socket, returns the byte count (-1 if the outbound buffer is full)
net.udpSendTo(sock, host, port, data) (i32, string, i32, string) -> i32 Send a UDP datagram to host:port (host is an IP), returns bytes sent (-1 on error/backpressure)
net.send(sock, "GET / HTTP/1.1\r\n\r\n")
net.udpSendTo(usock, "192.168.1.255", 5000, "temp=23.5")

net.send() never blocks and never writes a partial frame. The bytes are appended to a per-connection outbound buffer and flushed to the socket as it drains; if the buffer is full (the network cannot keep up with the send rate), the call returns -1 and nothing is queued — this is backpressure, not a fatal error, so retry on a later event-loop turn. The connection stays open.

Events

Function Signature Description
net.on(sock, event, EventType) (i32, string, i32) -> i32 Bind a lifecycle/data event to a declared event type (0=success, -1=error)

Supported events: "connected", "disconnected" (TCP), "data" (TCP), "datagram" (UDP). The library emits these struct shapes, so the program must declare the event types with exactly these names and fields:

event NetConnected {
    sock: i32
}

event NetDisconnected {
    sock: i32
}

event NetData {
    data: string
}

event NetDatagram {
    data: string
    addr: string
    port: i32
}

TCP delivers a data event per readable chunk — TCP is a stream, so a chunk is whatever bytes arrived, not a message; the program does its own framing. UDP delivers one datagram event per packet, carrying the sender's addr/port.

var sock: i32 = net.tcpConnect("192.168.1.10", 8080)
net.on(sock, "connected", NetConnected)
net.on(sock, "data", NetData)

on NetConnected fn(e: NetConnected): void {
    net.send(e.sock, "ping")
}

on NetData fn(e: NetData): void {
    console.log("recv: " + e.data)
}

runtime.keepAlive()

Auto-Reconnect (TCP)

Function Signature Description
net.setAutoReconnect(sock, enabled) (i32, bool) -> void Enable/disable automatic reconnection on unexpected disconnect
net.setReconnectDelays(sock, delays) (i32, array) -> void Set backoff delay schedule in seconds (max 8 entries)
net.setAutoReconnect(sock, true)
net.setReconnectDelays(sock, [1, 2, 5, 10, 30])

When auto-reconnect is enabled and the connection drops unexpectedly, the disconnected event fires and the library reconnects after each delay in turn, the last value repeating until it succeeds; connected fires again on success. Calling net.close() disables auto-reconnect. The retry index resets on success.

Address

Function Signature Description
net.remoteAddr(sock) (i32) -> string Remote address as "ip:port"
net.localAddr(sock) (i32) -> string Local address as "ip:port"

Pattern: UDP echo round-trip

package main

import "net"

event NetDatagram {
    data: string
    addr: string
    port: i32
}

fn main(): void {
    var sock: i32 = net.udpOpen(0)
    net.on(sock, "datagram", NetDatagram)

    on NetDatagram fn(e: NetDatagram): void {
        console.log("echo: " + e.data)
        net.close(sock)
        runtime.exit()
    }

    net.udpSendTo(sock, "127.0.0.1", 5000, "hello")
    runtime.keepAlive()
}

Notes

  • TCP client and UDP client only — server sockets are deferred to a later release
  • Up to 32 sockets on host, 4 on ESP32 (smaller send queues on-device)
  • Non-blocking from open through close: connect and reads advance from a state machine on the event loop, so the VM never blocks on the network
  • Outbound TCP bytes are queued per connection and flushed as the socket drains; a chunk is queued whole or rejected whole (net.send returns -1 under backpressure), so a slow network never tears framing on the wire
  • TCP uses TCP_NODELAY (Nagle disabled) for low latency
  • tcpConnect uses DNS resolution via getaddrinfo — hostnames work; udpSendTo takes a literal IP
  • Auto-reconnect (TCP) with configurable backoff delays (max 8 entries, last repeats)
  • On ESP32, uses lwIP socket layer; refuses to open until WiFi has an IP
  • Event field order is verified at runtime via signature hash — see Events ```