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

284 lines
7.3 KiB
Markdown

# 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:
```sh
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
```orchid
import std::socket::tcp::(bind, accept, connect, read, write_all, close)
```
Or import all functions:
```orchid
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
```sh
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)
```sh
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`
```orchid
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:
```sh
rustup run nightly cargo orcx -- exec --proj ./examples/tcp-echo-server "src::main::main"
```
### TCP Client
Location: `examples/tcp-client/src/main.orc`
```orchid
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):
```sh
rustup run nightly cargo orcx -- exec --proj ./examples/tcp-client "src::main::main"
```
### HTTP Server
Location: `examples/http-server/src/main.orc`
```orchid
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:
```sh
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:
```sh
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:
```orchid
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:
```orchid
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:
```sh
rustup run nightly cargo build
```