TCP echo server
One program starts an echo server on a free 127.0.0.1 port, has clients send lines and read back the echoes, and then shuts everything down cleanly.
Code
// A line-based TCP echo server and its clients in one program.
use rok::std::net::{TcpListener, TcpStream, SocketAddr, Shutdown};
use rok::std::io::{IoError, BufReader, Reader};
use rok::std::thread;
// Echo every line of one connection back until the client closes its side.
fn serve(conn: TcpStream) throws (IoError) -> i32 {
let lines = 0;
let r = BufReader::new(try conn.try_clone());
for item in r.lines() {
let line = try item.get();
try conn.write_str(f"echo: {line}\n");
lines += 1;
}
// Close both handles (the clone shares the socket) so the client sees EOF.
r.into_inner().close();
conn.close();
lines
}
// Send each message, wait for its echo, then close the write side.
fn client(id: i32, addr: SocketAddr, msgs: List<str>) throws (IoError) {
let conn = try TcpStream::connect(addr);
let r = BufReader::new(try conn.try_clone());
for m in msgs {
try conn.write_str(f"{m}\n");
let reply = (try r.read_line()).trim_end();
println(f"client {id}: sent {m:?}, got {reply:?}");
}
try conn.shutdown(Shutdown::Write);
// An empty read means the server closed the connection.
if (try r.read_line()).is_empty() { println(f"client {id}: closed"); }
}
fn main() throws (IoError) {
// Port 0: the OS picks a free port; local_addr tells us which.
let listener = try TcpListener::bind(try! "127.0.0.1:0".parse::<SocketAddr>());
let addr = try listener.local_addr();
let clients = 3;
// The server thread accepts `clients` connections, one handler thread each.
let server = thread::spawn(|| {
let handlers: List<thread::JoinHandle<i32>> = [];
for _ in 0..clients {
let (conn, _peer) = try! listener.accept();
handlers.push(thread::spawn(|| try serve(conn) catch e { println(f"server: {e}"); 0 }));
}
let total = 0;
for h in handlers { total += try! h.join(); }
listener.close();
total
});
try client(1, addr, ["hello", "rok"]);
try client(2, addr, ["one line"]);
try client(3, addr, ["a", "b", "c"]);
let served = try! server.join();
println(f"server echoed {served} lines and stopped");
}
client 1: sent "hello", got "echo: hello"
client 1: sent "rok", got "echo: rok"
client 1: closed
client 2: sent "one line", got "echo: one line"
client 2: closed
client 3: sent "a", got "echo: a"
client 3: sent "b", got "echo: b"
client 3: sent "c", got "echo: c"
client 3: closed
server echoed 6 lines and stopped
Walkthrough
- Binding
TcpListenerto port 0 lets the OS choose a free port, andlocal_addr()reports the actual address, so there is no clash with other programs (3.2 net,TcpListener). - The server thread starts one handler thread per connection, then
joins them all and adds up the lines served. The closures capturelistenerandconnand carry them to the other threads (Concurrency,thread::spawn). - To read line by line, the socket is
try_clone()d and wrapped in aBufReader: reads go through the clone, writes through the original (BufReader). - Shutdown order matters. After sending everything, the client calls
shutdown(Shutdown::Write)to say "nothing more", and the server'slines()ends its loop on that signal. Once the server closes both handles, the client'sread_line()returns an empty string. - The same server can be written with
accept_async/read_asyncand tasks instead of the blocking API (3.2.0 common rules).