TCP Sockets

Nostos provides low-level TCP socket support for building network applications. The Tcp module allows you to create both TCP clients and servers, giving you full control over network communication.

Overview

The TCP module provides six core functions for socket operations:

  • Tcp.connect(host, port) - Connect to a TCP server
  • Tcp.listen(port) - Listen for incoming connections
  • Tcp.accept(listener) - Accept an incoming connection
  • Tcp.read(socket, maxBytes) - Read data from a socket
  • Tcp.write(socket, data) - Write data to a socket
  • Tcp.close(handle) - Close a socket or listener

TCP Client

Creating a TCP client is straightforward. Connect to a server, send data, receive a response, and close the connection.

# Simple TCP client
main() = {
    println("Connecting to server...")
    sock = Tcp.connect("localhost", 9999)
    println("Connected!")

    # Send a message
    message = "Hello from Nostos!"
    bytesWritten = Tcp.write(sock, message)
    println("Sent " ++ show(bytesWritten) ++ " bytes")

    # Read response (up to 1024 bytes)
    response = Tcp.read(sock, 1024)
    println("Received: " ++ response)

    # Clean up
    Tcp.close(sock)
    println("Connection closed")
}

TCP Server

A TCP server listens on a port and accepts incoming connections. Each connection can be handled in its own process for concurrency.

# Simple TCP echo server
handleClient(sock) = {
    println("Client connected")

    # Read data from client
    data = Tcp.read(sock, 1024)
    println("Received: " ++ data)

    # Echo it back with prefix
    Tcp.write(sock, "Echo: " ++ data)

    # Close client connection
    Tcp.close(sock)
    println("Client disconnected")
}

main() = {
    println("Starting server on port 9999...")
    listener = Tcp.listen(9999)
    println("Listening...")

    # Accept loop - handle one client at a time
    loop {
        client = Tcp.accept(listener)
        handleClient(client)
    }
}

Concurrent Server

For handling multiple clients simultaneously, spawn a new process for each connection using Nostos's lightweight processes.

# Concurrent TCP server - handles multiple clients
handleClient(sock) = {
    try {
        data = Tcp.read(sock, 4096)
        if data != "" then {
            Tcp.write(sock, "Echo: " ++ data)
        } else ()
    } catch { e ->
        println("Client error: " ++ e)
    }
    Tcp.close(sock)
}

main() = {
    listener = Tcp.listen(8080)
    println("Server listening on port 8080")

    # Accept loop - spawn process for each client
    loop {
        client = Tcp.accept(listener)
        spawn { handleClient(client) }
    }
}

Error Handling

TCP operations can fail (connection refused, timeout, etc.). Use try/catch to handle errors gracefully.

main() = {
    try {
        sock = Tcp.connect("localhost", 9999)
        Tcp.write(sock, "Hello")
        response = Tcp.read(sock, 1024)
        Tcp.close(sock)
        println("Response: " ++ response)
    } catch { e ->
        println("Connection failed: " ++ e)
    }
}

Example: Simple Chat Server

Here's a more complete example showing a multi-client chat server using MVars to share state between client handlers.

# Simple chat server with shared client list
mvar clients: List[Int] = []

broadcast(message, senderSock) = {
    clientList = clients
    clientList.each(sock => {
        if sock != senderSock then {
            try { Tcp.write(sock, message) } catch { _ -> () }
        } else ()
    })
}

handleClient(sock) = {
    # Add to client list
    clients = clients ++ [sock]
    println("Client joined. Total clients: " ++ show(length(clients)))

    try {
        loop {
            data = Tcp.read(sock, 1024)
            if data == "" then throw("Client disconnected")
            else {
                println("Broadcasting: " ++ data)
                broadcast(data, sock)
            }
        }
    } catch { _ ->
        # Remove from client list
        clients = clients.filter(s => s != sock)
        Tcp.close(sock)
        println("Client left. Total clients: " ++ show(length(clients)))
    }
}

main() = {
    listener = Tcp.listen(5000)
    println("Chat server running on port 5000")

    loop {
        client = Tcp.accept(listener)
        spawn { handleClient(client) }
    }
}

API Reference

Tcp Module Functions

Function Signature Description
Tcp.connect String -> Int -> Int Connect to host:port, returns socket handle
Tcp.listen Int -> Int Listen on port, returns listener handle
Tcp.accept Int -> Int Accept connection, returns socket handle
Tcp.read Int -> Int -> String Read up to N bytes from socket
Tcp.write Int -> String -> Int Write string to socket, returns bytes written
Tcp.close Int -> () Close socket or listener

Tips

  • Always close sockets: Use try/catch with Tcp.close in the catch block to ensure cleanup.
  • Handle empty reads: Tcp.read returns an empty string when the connection is closed by the peer.
  • Spawn per client: For scalable servers, spawn a lightweight process for each connection.
  • Use MVars for shared state: When multiple client handlers need to share data, use MVars for thread-safe access.
  • Read in a loop: TCP is a stream protocol - data may arrive in chunks. Read in a loop until you have a complete message.