Distributed Systems Server Demo © — Project Specification
1. Objectives
Build a 3-tier toy RPC system in C:
- Tier 1: Client
- Tier 2: Server (multiple implementations; focus of demo)
- Tier 3: Database (toy math service)
The goal is to demonstrate and compare:
- Single-threaded blocking server
- Thread-per-connection server
- Single-threaded
select()state machine - Event-library-driven server (e.g., libevent)
All versions:
- Use TCP
- Use a fresh TCP connection per request (client→server and server→db)
- Use identical wire protocol
- Share common RPC/message code
The client and database remain constant across experiments. Only the server implementation changes.
2. Repository Structure
Top-level:
Makefilecommon/client/db/server_sync/server_threaded/server_select/server_event/
2.1 common/
Contains:
- Wire encoding/decoding
- Message definitions
- Framing logic
- Socket utilities
- Shared configuration constants
Builds into a static library (e.g., libcommon.a).
2.2 One Binary per Directory
Each subdirectory builds exactly one executable:
client/clientdb/dbserver_sync/serverserver_threaded/serverserver_select/serverserver_event/server
All link against libcommon.a.
3. Build Requirements
- Compiler:
clang - Language standard: C11
- Strict warnings enabled and treated as errors
- No GNU-only extensions unless justified
- Optional sanitizer build via make variable
Default target builds all binaries.
4. Transport and Framing
All communication uses TCP stream sockets.
No assumption is permitted that:
- A single
recv()returns a full message. - A single
send()writes all bytes.
All messages use length-prefixed framing:
int32 total_len_beint32 msg_type_be- fields…
total_len_be is the number of bytes after itself.
All integers:
- 32-bit signed
- Network byte order
Strings:
int32 len_be- Followed by raw bytes (not NUL-terminated)
A maximum message size must be enforced (configurable constant).
5. Message Model
5.1 Common Types
i32— signed 32-bit integermsg_type_t— enum of message kindsop_t— arithmetic operation enum (ADD, SUB, MUL, DIV)
5.2 Client → Server Request
Fields after msg_type:
i32 request_idi32 opi32 ai32 bstring key(may be empty)
request_id is echoed in the response.
5.3 Server → Client Response
Fields:
i32 request_idstring error(empty means success)i32 result(valid only if no error)
5.4 Server → DB Request
Fields:
i32 request_idi32 opi32 ai32 b
5.5 DB → Server Response
Fields:
i32 request_idstring errori32 result
Division by zero must produce a non-empty error string.
6. Common Library Responsibilities
6.1 Wire Layer
Responsibilities:
- Encode/decode 32-bit integers
- Encode/decode strings
- Maintain offset within buffers
- Bounds checking
6.2 Message Layer
Responsibilities:
- Pack typed structs into framed buffers
- Unpack buffers into typed structs
- Validate lengths and field counts
6.3 Framing Layer
Responsibilities:
- Incremental read state machine
- Maintain:
- read buffer
- expected frame length
- current offset
- Detect complete frame
Conceptual connection state includes:
- File descriptor
- Read buffer and offsets
- Write buffer and offsets
- Parsed message object (when available)
- Logical phase/state
6.4 Socket Utilities
Responsibilities:
- Create listening socket
- Accept connections
- Connect to remote host
- Set non-blocking mode
7. Client Specification
7.1 Behavior
For each request:
- Open TCP connection to server
- Serialize request
- Optionally send in two parts with delay
- Receive response
- Print timing metrics
- Close connection
7.2 Options
--n Nnumber of requests--fork Nspawn N processes--slow-sendsplit request into chunks with random sleep--annotateprint decoded wire format
7.3 Timing Output
Per request, print:
- Time spent sending (including artificial delay)
- Time waiting for response
8. Database Server Specification
8.1 Behavior
Loop:
- Accept connection
- Read exactly one request
- Optionally sleep (random delay)
- Compute result
- Optionally split response write with delay
- Close connection
8.2 Options
--delay-ms MIN MAX--split-send--annotate
The DB server may remain single-threaded for simplicity.
9. Server Variants (Tier 2)
All server versions implement identical external semantics.
Common logical flow per client request:
- Accept client connection
- Read framed client request
- Open connection to DB
- Send DB request
- Read DB response
- Construct client response
- Send response
- Close both connections
Optional mode: two DB calls per request.
9.1 Version 1: Single-Threaded Blocking
Model:
- One accept loop
- Blocking
accept,recv,send,connect
Properties:
- Fully serialized handling
- Idle while waiting on DB or slow client
- No shared-state concurrency issues
Demonstrates head-of-line blocking.
9.2 Version 2: Thread-per-Connection
Model:
- Main thread accepts
- Each accepted socket handled by new thread
Shared server state (if any) must be protected by mutex.
Demonstrates:
- Concurrency
- Synchronization costs
- Thread overhead
9.3 Version 3: select()-Driven State Machine
Model:
- Single-threaded
- All sockets non-blocking
- One
select()loop
Each connection maintains explicit state:
States include:
- Reading client frame
- Connecting to DB
- Writing DB request
- Reading DB response
- Writing client response
- Closing
No mutexes required.
Demonstrates:
- Readiness-based multiplexing
- Explicit state machines
- Complexity of manual event management
9.4 Version 4: Event Library (libevent/libev)
Model:
- Event base
- Read/write callbacks
- Timer callbacks
Connection state stored in per-connection context struct.
Semantics identical to select version.
Demonstrates:
- Cleaner event abstraction
- Reduced boilerplate
- Similar architectural model
10. Shared State (Optional Demonstration)
To justify mutexes in threaded version:
Optional server-side cache:
- Map from string key → last result
- Accessed by all threads
Threaded server must guard map with mutex.
select/event versions require no locking.
11. Configuration Constants
Defined in common/config.h:
- Default ports (client-server, server-db)
- Max message size
- Default backlog
- Default timeouts
12. Pedagogical Demonstrations
- Partial reads/writes break naive assumptions.
- Blocking servers waste time waiting on I/O.
- Thread-per-connection removes serialization but adds synchronization.
- select() avoids threads but requires explicit state machines.
- Event libraries abstract readiness but preserve same core model.
- Network-bound workloads expose scalability limits.
13. Non-Goals
- Production-grade RPC
- Persistent connections
- TLS
- Binary compatibility guarantees
- High-performance memory allocators
The system exists purely to expose concurrency and I/O model tradeoffs in C.
Last Updated 02/12/2026

