Learn how to write TCP servers using Rust's std::net module
TCP is a widely used network protocol. It's the underlying protocol for HTTP, SSH and many other protocols that you're probably familiar with.
In this concept we'll learn how to write a TCP server in Rust using the std::net
module.
Not familiar with TCP? Try the "TCP: An Overview" concept first.
TCP is a widely used network protocol. It's the underlying protocol for HTTP, SSH and many other protocols that you're probably familiar with.
In this concept we'll learn how to write a TCP server in Rust using the std::net
module.
Not familiar with TCP? Try the "TCP: An Overview" concept first.
std::net
moduleRust's std::net
module provides access to networking primitives.
To write TCP servers in Rust, you'll need to be familiar with the following methods:
We'll start by looking at TcpStream::connect
and TcpListener::bind
.
TcpStream::connect
is used to initiate outbound connections.
Example usage:
use std::net::TcpStream;
// Connects to a TCP server running on localhost:8080
let stream = TcpStream::connect("localhost:8080")?;
TcpListener::bind
is used to to create servers to accept inbound connections.
Example usage:
use std::net::TcpListener;
// Creates a TCP server listening on port 8080
let listener = TcpListener::bind("localhost:8080")?;
TcpListener::bind
methodThis is the interface for TcpListener::bind
:
pub fn bind<A: ToSocketAddrs>(addr: A) -> Result<TcpListener>
To create a TCP server you'd specify a string like "localhost:8080" as addr
:
// Starts a TCP server listening on localhost:8080
let listener = TcpListener::bind("localhost:8080")?;
TcpListener
structTcpListener
is the struct returned from TcpListener::bind
.
Here are some methods associated with the TcpListener
struct:
impl TcpListener {
// accept waits for and returns the next connection to the listener
pub fn accept(&self) -> Result<(TcpStream, SocketAddr)>
// incoming returns an iterator over the connections being received on this listener
pub fn incoming(&self) -> Incoming<TcpStream>
// local_addr returns the local socket address of the listener
pub fn local_addr(&self) -> Result<SocketAddr>
}
Once you've created a listener, you can use TcpListener::incoming()
to get an iterator over the incoming connections.
This method returns an iterator that yields connections as they are accepted, allowing you to handle each new connection in a loop.
for stream in listener.incoming() {
match stream {
Ok(stream) => {
// handle the connection
}
Err(e) => {
eprintln!("Failed: {}", e);
}
}
}
TcpStream
structThe iterator returned from TcpListener::incoming
yields instances of TcpStream
.
Some important methods associated with the TcpStream
struct are:
impl TcpStream {
// read reads bytes from the stream
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize>
// write writes bytes to the stream and returns the number of bytes written.
// It's often easier to use write_all instead of this method.
pub fn write(&mut self, buf: &[u8]) -> Result<usize>
// write_all writes all the bytes in buf to the stream
pub fn write_all(&mut self, buf: &[u8]) -> Result<()>
}
You can use TcpStream::read()
and TcpStream::write_all()
to read from and write to a TCP connection.
To read data from a connection, you'll need to pass in a mutable byte slice to TcpStream::read
. The data received will be stored in this byte slice. TcpStream::read
returns a Result<usize>
indicating the number of bytes read:
let mut buf = [0; 1024];
let n = stream.read(&mut buf)?;
println!("received {} bytes", n);
println!("data: {:?}", &buf[..n]);
To write data to a connection, you'll need to pass in a byte slice to TcpStream::write_all
. It returns a Result<()>
indicating whether the write was successful:
let buf = b"hello world";
stream.write_all(buf)?;
Using TcpStream::write_all
is often easier than using TcpStream::write
since it automatically handles the case where the entire buffer isn't written to the connection in one go.
Now that you're familiar with TcpListener
and TcpStream
, let's see how to put them all together to create a simple TCP server that echoes all input it receives:
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
fn main() {
// Creates a TCP server listening on localhost:8080
let listener = TcpListener::bind("localhost:8080").expect("Could not bind");
for stream in listener.incoming() {
match stream {
Ok(stream) => {
handle_client(stream);
}
Err(e) => {
eprintln!("Failed: {}", e);
}
}
}
}
fn handle_client(mut stream: TcpStream) {
let mut buf = [0; 512];
loop {
let bytes_read = stream.read(&mut buf).expect("Failed to read from client");
if bytes_read == 0 {
return;
}
stream.write_all(&buf[0..bytes_read]).expect("Failed to write to client");
}
}
There are some limitations you should know about the above example:
We won't be covering how to fix these limitations in this concept. If you're interested in learning more, check out the Rust Book.
You've now learnt about how to use functions in the std::net
module to build a TCP server.
A quick recap of the functions and structs we covered:
TcpListener::bind
: Returns a TcpListener
instanceTcpListener::incoming
: returns an iterator that yields instances of TcpStream
as new connections are acceptedTcpStream::read
: Reads data from a connectionTcpStream::write_all
: Writes data to a connectionLinks for further reading: