Greetings everyone, here comes another Rust lang series episode. Today we will try to create simple TCP server in Rust that will listen at given port and return simple text greeting when connected. Each connection will be handled in special thread.
TCP server simplified implementation
We will use only API from standard library, API related to TCP protocol is part of networking std::net module. Let's take a look at code at first before we break it down.
use std::io::Write;
use std::net::TcpListener;
use std::thread;
fn main() {
let ip_and_port = "127.0.0.1:1234";
let listener = TcpListener::bind(ip_and_port).unwrap();
println!("listening on port 1234 on http://localhost:1234");
for stream in listener.incoming() {
thread::spawn(|| {
let mut stream = stream.unwrap();
stream.write(b"Hi to all Steemiters!\r\n").unwrap();
});
}
}
Output
When you open your browser and open http://localhost:1234 address you'll see this message
Hi to all Steemiters!
You can also check connection from command-line with tools curl or whatever is provided on your OS.
curl localhost:1234
# output
Hi to all Steemiters!
curl: (56) Recv failure: Connection reset by peer
Breaking down
Let's analyze the source code a bit:
First we set ip address and port, we set it to localhost (127.0.0.1) and port to 1234.
std::io::Write provides write method for TcpStream
std::net::TcpListener is basic building component for TCP socket server
bind() method creates TcpListener and binds it to given port
incomming() iterator over received connections
thread::spawn() new thread is created and executes code each TCP stream (connection)
write() sends data to TCPStream
Version with proper error handling
Of course previous version doesn't solve issue handling that can occur on multiple places and just terminate main thread when something bad occurs. Let's try to implement something the a better care for potential errors. Possible implementation could look like this for example.
use std::io::Write;
use std::net::TcpListener;
use std::net::TcpStream;
use std::thread;
fn create_server() -> Option<TcpListener> {
let ip_and_port = "127.0.0.1:1234";
let res = TcpListener::bind(ip_and_port);
match res {
Ok(listener) => return Some(listener),
Err(err) => {
println!("Cannot create server: - {}", err.to_string());
None
}
}
}
fn handle_incoming_stream(mut stream: TcpStream) {
thread::spawn(move || {
let write_res = stream.write(b"Hi to all Steemiters!\r\n");
if let Err(err) = write_res {
println!("Cannot send data - {}", err.to_string());
} else {
println!("Response sent OK");
}
});
}
fn main() {
let server_result = create_server();
if let Some(listener) = server_result {
println!("listening on port 1234 on http://localhost:1234");
for stream_res in listener.incoming() {
if let Ok(mut stream) = stream_res {
handle_incoming_stream(stream);
} else if let Err(err) = stream_res {
println!("Stream error - {}", err.to_string());
}
}
}
}
Output
When you see this application multiple times you can see for example this error properly handled.
Cannot create server: - Address already in use (os error 98)
Application finished!
Breaking down
Code is just split to mutliple functions to separate concerns and handling related errors. TcpListener and TcpStreams operations returns std::io::Result that we handled on proper places. But in general there is nothing new that should be explained.
Postfix
That's all for now, thank you for your appreciations, feel free to comment and point out possible mistakes (first 24 hours works the best but any time is fine). May Jesus bless your programming skills, use them wisely and see you next time.
Meanwhile you can also check the official documentation for additional related information: