forked from Orchid/orchid
Initial networking module.
This commit is contained in:
283
NETWORKING.md
Normal file
283
NETWORKING.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user