TCP

Luma provides TCP networking through the tcp module.
A TCP connection is an active valuetcp.connect() establishes a real network connection immediately.
Reading and writing adapt automatically based on your types.

Connecting

Basic connection

sock: tcp = tcp.connect("localhost", 3306)

Connects to the given host and port over TCP.
Panics if the connection fails (host unreachable, port closed, DNS failure).

Safe connection (optional)

sock: ?tcp = tcp.connect("localhost", 3306)

Returns nil if the connection fails instead of panicking.

sock: ?tcp = tcp.connect("db.example.com", 3306)
if sock == nil {
    print("Cannot reach database server")
    return
}

Connection options

The third argument is an options map with any combination of:

sock: tcp = tcp.connect("localhost", 3306, { timeout: 5 })
sock: tcp = tcp.connect("localhost", 3306, { timeout: 5, read_timeout: 10 })
sock: tcp = tcp.connect("db.example.com", 3307, { tls: true })
sock: tcp = tcp.connect("db.example.com", 3307, { tls: true, timeout: 5, read_timeout: 10 })
Option Type Default Description
timeout int system default Connection timeout in seconds
read_timeout int no timeout Read timeout in seconds, applies to all reads
tls bool false Perform TLS handshake immediately after connecting

Options work with optional connections too:

sock: ?tcp = tcp.connect("slow-host.example.com", 3306, { timeout: 3 })

TLS connections

When a service expects TLS from the start (HTTPS, SMTPS, MySQL with require_secure_transport), use the tls option:

sock: tcp = tcp.connect("smtp.gmail.com", 465, { tls: true })

For protocols that start unencrypted and upgrade mid-conversation (MySQL, SMTP, LDAP), see start_tls() below.

Writing data

sock.write(data)

The type of the argument determines what happens:

  • [byte] — sends the raw bytes
  • str — converts to bytes and sends
// Binary data — Luma sees [byte], sends bytes
header: [byte] = buf.to_bytes()
sock.write(header)

// Text command — Luma sees str, sends the string
sock.write("QUIT\r\n")

Writing to a closed or broken connection panics.

Reading data

Read exact bytes

data: [byte] = sock.read(4)

Blocks until exactly N bytes have been received.
This is the primary mode for protocol work where packet sizes are known.

The declared type controls the result format:

data: [byte] = sock.read(4)       // 4 bytes as byte list
text: str = sock.read(1024)       // 1024 bytes as string

Use optional types for safe reads:

data: ?[byte] = sock.read(4)      // nil on error instead of panic
text: ?str = sock.read(1024)      // nil on error instead of panic

Read a line

line: str = sock.read_line()

Reads until \n is encountered.
Used for text-based protocols (SMTP, HTTP headers, Redis).

line: str = sock.read_line()      // panics if connection drops
line: ?str = sock.read_line()     // returns nil if connection drops

Read timeout

The read timeout is set once at connection time and applies to all reads:

sock: tcp = tcp.connect("host", 3306, { read_timeout: 5 })
data: [byte] = sock.read(4)      // panics if not received within 5s
data: ?[byte] = sock.read(4)     // returns nil on timeout

Closing

sock.close()

Closes the TCP connection.
Safe to call multiple times (second call is a no-op).
After closing, any read or write will panic.

TLS upgrade

Some protocols start unencrypted and upgrade to TLS mid-conversation:

sock: tcp = tcp.connect("localhost", 3306)
// ... unencrypted handshake ...
sock.start_tls()
// ... now encrypted ...

Panics if the TLS handshake fails.
After this call, all reads and writes go through the encrypted channel.

This is different from { tls: true } in connect options — start_tls() upgrades an existing connection, while tls: true encrypts from the start.

Error handling

Consistent with Luma’s file I/O conventions:

Operation Non-optional Optional
tcp.connect(h, p) Panics on failure ?tcp returns nil
sock.read(n) Panics on error/timeout ?[byte] or ?str returns nil
sock.read_line() Panics on error ?str returns nil
sock.write(data) Panics on error No optional variant
sock.close() No-op if already closed N/A
sock.start_tls() Panics on failure N/A

Examples

Read a MySQL server greeting

sock: tcp = tcp.connect("localhost", 3306)

// MySQL sends a greeting packet immediately after connection
// Packet format: [3 bytes length][1 byte seq][payload]
header: [byte] = sock.read(4)

length: int = header[0] + header[1] * 256 + header[2] * 65536
seq: int = header[3]

payload: [byte] = sock.read(length)
print("Protocol version: ${payload[0]}")

sock.close()

Simple HTTP client

sock: tcp = tcp.connect("example.com", 80)

sock.write("GET / HTTP/1.1\r\n")
sock.write("Host: example.com\r\n")
sock.write("Connection: close\r\n")
sock.write("\r\n")

status: str = sock.read_line()
print("Status: ${status}")

sock.close()

Safe connection with fallback

sock: ?tcp = tcp.connect("primary.example.com", 5432, { timeout: 3 })

if sock == nil {
    print("Primary unreachable, trying backup...")
    sock = tcp.connect("backup.example.com", 5432)
}

data: [byte] = sock.read(1024)
sock.close()

TLS connection

sock: tcp = tcp.connect("smtp.gmail.com", 465, { tls: true })

greeting: str = sock.read_line()
print("Server says: ${greeting}")

sock.write("EHLO localhost\r\n")
response: str = sock.read_line()
print("EHLO response: ${response}")

sock.close()

Binary protocol with timeouts

sock: tcp = tcp.connect("gameserver.local", 9999, { read_timeout: 10 })

// Send a ping packet
sock.write([0x01, 0x00, 0x00, 0x04])

// Read response — nil if server doesn't respond within 10s
response: ?[byte] = sock.read(4)
if response == nil {
    print("Server timed out")
    sock.close()
    return
}

print("Got response: ${response}")
sock.close()

Method summary

Method Arguments Returns Description
tcp.connect() host: str, port: int tcp or ?tcp Connect to a server
tcp.connect() host: str, port: int, options tcp or ?tcp Connect with options
sock.write() data: [byte] or data: str Send data
sock.read() n: int [byte], str, ?[byte], or ?str Read exact N bytes
sock.read_line() str or ?str Read until newline
sock.close() Close connection
sock.start_tls() Upgrade to TLS

Design notes

  • TCP connections are active valuestcp.connect() connects immediately, unlike file() which is lazy
  • write() dispatches by argument type — pass bytes or a string, Luma figures it out
  • read() dispatches by return type — declare what you want, Luma gives it to you
  • Options (timeout, read_timeout, tls) are set once at connect time, keeping protocol code clean
  • start_tls() exists separately because mid-conversation TLS upgrade is a genuinely different operation from connecting with TLS enabled
Last updated on