smikic.com
evergreentended May 31, 2025

Building a Distributed Key-Value Store in C++ (Part 3)

Key takeaways
  • Wrapping the KVStore in a TCP server is mostly boilerplate — socket, bind, listen, accept, thread-per-connection.
  • A text-based newline-delimited protocol is good enough for a first pass and easy to debug with netcat.
  • Networking code is best validated with manual integration tests before introducing a test harness for socket I/O.

Parts 1 and 2 gave us a persistent, tested in-process store. Part 3 wraps it in a TCP server so any client — a shell script, another process, or the CLI client below — can talk to it.

The KVServer class

cpp
// server.hpp
class KVServer {
public:
  KVServer(int port, const std::string& store_file = "logs/store.log");
  void run();
private:
  int port;
  KVStore store;
  void handle_client(int client_socket);
};

run() opens a listening socket and spawns a detached thread for every incoming connection. This is a naive thread-per-connection model — fine for a learning project, not suitable for production.

Main server loop

cpp
void KVServer::run() {
  int server_fd = socket(AF_INET, SOCK_STREAM, 0);
  // ... bind, listen
  while (true) {
    int client_socket = accept(server_fd, ...);
    std::thread(&KVServer::handle_client, this, client_socket).detach();
  }
}

Command handling

cpp
void KVServer::handle_client(int client_socket) {
  char buffer[1024] = {0};
  while (true) {
    int valread = read(client_socket, buffer, sizeof(buffer) - 1);
    if (valread <= 0) break;
    std::istringstream iss(buffer);
    std::string cmd, key, value;
    iss >> cmd >> key;
    std::ostringstream response;
    if (cmd == "PUT") {
      iss >> value;
      store.put(key, value);
      response << "OK\n";
    } else if (cmd == "GET") {
      auto val = store.get(key);
      response << (val ? *val : "NOT_FOUND") << "\n";
    } else if (cmd == "DEL") {
      response << (store.del(key) ? "OK\n" : "NOT_FOUND\n");
    } else if (cmd == "QUIT") {
      break;
    } else {
      response << "ERROR UNKNOWN COMMAND\n";
    }
    send(client_socket, response.str().c_str(), response.str().size(), 0);
  }
  close(client_socket);
}
NOTE

All socket I/O in this implementation is integration-tested manually. Automating socket tests requires a test harness that spins up and tears down the server in-process, which is left for a future refactor.

The CLI client

The client connects to an IP and port, then enters a read-send-print loop:

cpp
while (std::cout << "> ", std::getline(std::cin, input)) {
  send(sock, input.c_str(), input.length(), 0);
  char buffer[1024] = {0};
  int valread = read(sock, buffer, sizeof(buffer)-1);
  if (valread > 0) std::cout << buffer;
  if (input == "QUIT") break;
}

An interactive session

bash
$ ./kvstore_server
KVServer listening port 12345...
 
$ ./kvstore_client 127.0.0.1 12345
> PUT hello world
OK
> GET hello
world
> DEL hello
OK
> GET hello
NOT_FOUND
> QUIT

CMake: two executables from one library

cmake
add_executable(kvstore_server src/server.cpp src/server.hpp src/server_main.cpp)
target_link_libraries(kvstore_server PRIVATE kvstore_lib)
add_executable(kvstore_client src/client.cpp)
target_link_libraries(kvstore_client PRIVATE kvstore_lib)

Splitting the store logic into kvstore_lib means both executables share the same compiled translation units and tests link against the same target.

Roadmap

  • Phase 1 — Local store — done
  • Phase 2 — Persistence & testing — done
  • Phase 3 — Networking — done (this post)
  • Phase 4 — Multi-node architecture — next
  • Phase 5 — Consensus / leader election — planned
  • Phase 6 — Testing & resilience — planned

What's next

Part 4 runs multiple server instances and replicates writes between them — the first real step into distributed territory.

3 linked references
Building a Distributed Key-Value Store in C++ (Final)

…and the overall roadmap. Parts [2](/notes/building-distributed-kv-store-pt2), [3](/notes/building-distributed-kv-store-pt3), and [4](/notes/building-distributed-kv-store-pt4) added persistence, TCP netwo…

Building a Distributed Key-Value Store in C++ (Part 4)

…le mess as the codebase grows. </KeyTakeaways> With a working TCP server from [Part 3](/notes/building-distributed-kv-store-pt3), the next step is running multiple instances and keeping them in sync. This par…

Building a Distributed Key-Value Store in C++ (Part 2)

…ion** — planned - **Phase 6 — Testing & resilience** — planned ## What's next [Part 3](/notes/building-distributed-kv-store-pt3) puts the store on the network. A `KVServer` will accept TCP connections and han…

Stefan Mikic
Stefan Mikicdata eng

Data is my veggies — healthy, versatile, and sometimes hard to digest, but in the end, it always brings value.