Files
orchid/NETWORKING.md
2025-11-21 20:17:00 +01:00

7.3 KiB

Networking in Orchid

This document explains how to use TCP networking in the Orchid programming language.

Prerequisites

Build the project with the nightly Rust toolchain:

rustup run nightly cargo build

Socket Module Overview

The socket module is available at std::socket::tcp and provides functions for TCP client/server communication.

Importing

import std::socket::tcp::(bind, accept, connect, read, write_all, close)

Or import all functions:

import std::socket::tcp

API Reference

Server Functions

Function Signature Description
bind String -> TcpListener Create a TCP listener bound to an address
accept TcpListener -> TcpStream Accept an incoming connection
listener_addr TcpListener -> String Get the address the listener is bound to

Client Functions

Function Signature Description
connect String -> TcpStream Connect to a TCP server

Stream Functions

Function Signature Description
read TcpStream -> Int -> String Read up to N bytes as UTF-8 string
read_bytes TcpStream -> Int -> String Read up to N bytes as raw byte string
read_exact TcpStream -> Int -> String Read exactly N bytes
write TcpStream -> String -> Int Write data, returns bytes written
write_all TcpStream -> String -> Int Write all data
flush TcpStream -> Int Flush the stream buffer
close TcpStream -> Int Close the connection
peer_addr TcpStream -> String Get the remote peer's address
local_addr TcpStream -> String Get the local address

Quick Start Examples

Testing Socket Bind

rustup run nightly cargo orcx -- exec "std::socket::tcp::bind \"127.0.0.1:8080\""

Output:

<TcpListener 127.0.0.1:8080>

Testing Connection (expect error if no server running)

rustup run nightly cargo orcx -- exec "std::socket::tcp::connect \"127.0.0.1:8080\""

Output (if no server):

error: IO error: Connection refused (os error 111):  @

Example Scripts

TCP Echo Server

Location: examples/tcp-echo-server/src/main.orc

import std::socket::tcp::(bind, accept, read, write_all, close)

const handle_client := \client. do cps {
  cps data = read client 1024;
  cps _ = write_all client data;
  cps _ = close client;
  cps pass 0;
}

const accept_loop := \server. do cps {
  cps client = accept server;
  cps _ = handle_client client;
  cps pass $ accept_loop server;
}

const main := do cps {
  cps server = bind "127.0.0.1:8080";
  cps pass $ accept_loop server;
}

Run the echo server:

rustup run nightly cargo orcx -- exec --proj ./examples/tcp-echo-server "src::main::main"

TCP Client

Location: examples/tcp-client/src/main.orc

import std::socket::tcp::(connect, read, write_all, close, peer_addr)
import system::io::println

const main := do cps {
  cps conn = connect "127.0.0.1:8080";
  cps addr = peer_addr conn;
  cps println $ "Connected to ${addr}";
  cps _ = write_all conn "Hello from Orchid!\n";
  cps response = read conn 1024;
  cps println $ "Server response: ${response}";
  cps _ = close conn;
  cps pass 0;
}

Run the client (requires a server running on port 8080):

rustup run nightly cargo orcx -- exec --proj ./examples/tcp-client "src::main::main"

HTTP Server

Location: examples/http-server/src/main.orc

import std::socket::tcp::(bind, accept, read, write_all, close, peer_addr, listener_addr)
import system::io::println

const http_response := "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 44\r\nConnection: close\r\n\r\n<html><body><h1>Hello Orchid!</h1></body></html>"

const handle_client := \client. do cps {
  cps addr = peer_addr client;
  cps println $ "Client connected: ${addr}";
  cps request = read client 4096;
  cps println $ "Received request from ${addr}";
  cps _ = write_all client http_response;
  cps _ = close client;
  cps println $ "Connection closed: ${addr}";
  cps pass 0;
}

const accept_loop := \server. do cps {
  cps client = accept server;
  cps _ = handle_client client;
  cps pass $ accept_loop server;
}

const main := do cps {
  cps server = bind "127.0.0.1:8080";
  cps addr = listener_addr server;
  cps println $ "HTTP Server listening on ${addr}";
  cps println "Press Ctrl+C to stop";
  cps pass $ accept_loop server;
}

Run the HTTP server:

rustup run nightly cargo orcx -- exec --proj ./examples/http-server "src::main::main"

Then open http://127.0.0.1:8080 in your browser or test with curl:

curl http://127.0.0.1:8080

Understanding the CPS Syntax

Orchid uses Continuation-Passing Style (CPS) for side effects. The do cps { ... } block is used for sequential operations:

  • cps variable = expression; - Bind the result of an expression to a variable
  • cps expression; - Execute an expression for its side effects
  • cps pass value; - Return a value from the block

Example breakdown:

const main := do cps {
  cps server = bind "127.0.0.1:8080";  -- Bind socket, store in 'server'
  cps client = accept server;          -- Accept connection, store in 'client'
  cps data = read client 1024;         -- Read data from client
  cps _ = write_all client data;       -- Write data back (ignore result)
  cps _ = close client;                -- Close connection
  cps pass 0;                          -- Return 0
}

Error Handling

Socket operations return errors when they fail. Errors are displayed with the IO error message:

error: IO error: Connection refused (os error 111):  @

Common errors:

  • Connection refused - No server listening on the target address
  • Address already in use - Port is already bound by another process
  • Connection reset by peer - Remote end closed the connection unexpectedly

Building a Simple Protocol

Here's an example of a simple request/response protocol:

import std::socket::tcp::(bind, accept, read, write_all, close)

const handle_request := \request.
  if request == "PING\n" then "PONG\n"
  else if request == "TIME\n" then "2024-01-01 00:00:00\n"
  else "UNKNOWN\n"

const handle_client := \client. do cps {
  cps request = read client 1024;
  cps response = pass $ handle_request request;
  cps _ = write_all client response;
  cps _ = close client;
  cps pass 0;
}

const server_loop := \server. do cps {
  cps client = accept server;
  cps _ = handle_client client;
  cps pass $ server_loop server;
}

const main := do cps {
  cps server = bind "127.0.0.1:9000";
  cps pass $ server_loop server;
}

Limitations

  • The current implementation handles one client at a time (sequential accept loop)
  • Sockets are non-serializable (cannot be persisted or transferred across boundaries)
  • No UDP support (TCP only)
  • No TLS/SSL support

Troubleshooting

Port Already in Use

If you get "Address already in use", either:

  1. Wait for the previous process to release the port
  2. Use a different port
  3. Kill the process using the port: lsof -i :8080 then kill <pid>

Connection Refused

Ensure the server is running before starting the client.

Build Errors

Make sure to use the nightly toolchain:

rustup run nightly cargo build