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 serverTcp.listen(port)- Listen for incoming connectionsTcp.accept(listener)- Accept an incoming connectionTcp.read(socket, maxBytes)- Read data from a socketTcp.write(socket, data)- Write data to a socketTcp.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.readreturns 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.