2024 02 18
Previously we took an initial look at what Redis is, and which of its features Code Crafters (CC) expects us to implement in their course on the subject.
The first thing we’re being asked to add to our Redis implementation is a simple TCP server. Because this is the first step in the project, the repository that CC starts us out with already has this simple step written for us (although commented-out). However, since understanding is the ultimate goal of this effort, I think it’s important to understand what TCP actually is, even if we aren’t writing the code for it ourselves (and we’re not writing the server from scratch, though I intend to do that eventually).
What Is TCP?
Data that is transmitted over the internet gets sent in the form of IP packets.
Now, of course this isn’t strictly accurate; those packets are first wrapped up in a lower-level data link layer protocol, which is transmitted as an electrical signal that’s only given meaning by an agreed-upon clock rate and coding scheme between networked computers. But we’re thinking in a higher layer of abstraction here, so for our purposes, that lower-level stuff doesn’t matter. Eventually, we should talk about that lower-level stuff–and yes, even implement it ourselves. But cooncerning ourselves with it now really only makes our understanding more difficult. Right now, we’re writing a Redis clone.
So what is relevant to know is that data that is transmitted over the internet gets sent in the form of IP packets, and that some messages that we might want to send are bigger than can fit in a single IP packet. That means that those larger messages would need to be chopped up and sent as a series of multiple independent IP packets, and that, of course, poses some potential challenges. What happens if one of our packets in a series gets dropped or corrupted in transit? What happens if the packets arrive at the destination out of order? How do we make sense of a jumble of packets arriving simultaneously from multiple senders?
TCP is the Transmission Control Protocol, and it exists as one of various solutions to these problems. We’ll get into how exactly it all works when we build a TCP server from scratch in the future, but for now, let’s just focus on what TCP does for us: it provides a simplified service for sending large streams of data without concerning ourselves with how the message gets broken up, or how to clean things up if something goes wrong along the way.
The TCP protocol first requires that a TCP connection is established between the machines that intend to communicate. A unique connection is identified by the pair of source and destination ports.
Why does Redis need a TCP Server?
As discussed in my last post, Redis can serve as a data store that’s accessible across a network. The TCP server is the outward facing interface for queries against that datastore. Even if we’re only using our Redis clone to provide single-process caching, or even local cross-application data sharing capability, we can route any relevant requests through TCP so that the interface is consistent across use cases.
Creating a TCP Server
Code crafters provides some boilerplate to start with, so we should familiarize ourselves with the files that are already in place when we clone the generated repository.
Fortunately, in case we want to take a purists approach to “from scratch”, the things Code Crafters does for us are not much more than what the Rust toolset would give us in a fresh project generated using cargo new
. There are Cargo.lock
and Cargo.toml
files, as well as a main.rs
file with a bit of code which has been commented out. We’ll get to that code in a minute.
Everything else already in the repo is CC-specific documentation, configuration, and scripts necessary to test your work on their servers. As we would hope, nothing else has been done for us. According to the project’s documentation, there exists a CLI that we can install to run CC’s tests locally, but we should also plan on writing our own tests during development.
The code that they have included for us (and commented out) consists of just a few simple lines to create and start our TCP server running, using the TCP implementation provided by Rust’s standard library. Because we’re writing Redis, rather than a TCP server, this is good enough. That code, after uncommenting, looks like this:
use std::net::TcpListener;
fn main() {
let listener = TcpListener::bind("127.0.0.1:6379").unwrap();
for stream in listener.incoming() {
match stream {
Ok(_stream) => {
println!("accepted new connection");
}
Err(e) => {
println!("error: {}", e);
}
}
}
}
Cool! this code sets up a local TcpListener
object, and then iterates on any incoming requests, though it doesn’t do anything with the request itself yet. Port 6379
is the default port used by Redis.
If we run this code and navigate to 127.0.0.1:6379
in our browser, we’ll see our debug output displayed a number of times:
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `target/debug/redis-starter-rust`
accepted new connection
accepted new connection
accepted new connection
accepted new connection
accepted new connection
accepted new connection
accepted new connection
accepted new connection
accepted new connection
accepted new connection
We’ll worry about all those extra requests another time. For now, let’s submit this code to the Code Crafters servers and see what they do with it.
After commiting and pushing up our changes to the repo, Code Crafters runs their tests and gives us the message: “You completed this stage today.”
Fantastic. With the first stage completed, we can move on to the following stages where we’ll actually write our own code. We’ll start by adding support for a simple PING command. See you next time!
Thanks for reading,
Alec