Project: Simple Client-Server Application0%

Project: Simple Client-Server Application

Beginner12 min readUpdated: Jul 11, 2026
Study Materials

Project: Client-Server Application with Custom Protocol

In distributed systems, microservices frequently exchange structured commands across TCP sockets. While HTTP/REST and gRPC are popular high-level choices, constructing a custom socket protocol provides maximum speed, minimal serialization overhead, and complete control over network framing.

In this project, we will construct a production-ready Remote Command Protocol (RCP) Client-Server Application. It features a Length-Prefixed Framing Protocol that guarantees message boundary integrity, structured JSON payload routing, error recovery, and a typed client library.


1. Network Protocol Architecture

TCP streams do not preserve message boundaries. To prevent packet fragmentation and coalescing bugs, our protocol uses Length-Prefixed Framing:

Output
The 4-Byte Length-Prefixed Frame
┌────────────────────────────────────┬────────────────────────────────────┐
│ Header: 4 Bytes (Big-Endian UInt) │ Body: N Bytes (UTF-8 JSON Data) │
│ struct.pack("!I", N) │ {"command": "...", ...} │
└────────────────────────────────────┴────────────────────────────────────┘
Output
Client Application Server Daemon
│ │
client.send_request("MATH", {"a": 10, "b": 20}) │
│ │
├──► Packs 4-Byte Length Header + JSON Payload ────────────► │
│ │ Reads 4-byte header
│ │ Allocates buffer of size N
│ │ Dispatches Command Handler
│ │ Computes result: 30
│ ◄── Packs 4-Byte Length Header + Response Payload ─────────┤
│ │
Unpacks Header & Decodes JSON │
Returns 30 to caller │

2. Low-Level Protocol Framing Helpers

Visual Architecture & Process Flow

How data and code flow step-by-step

Flowchart
Step 1
def send_framed_message
sock: socket.socket, payload: Dict[str, Any]
Step 2
None:

3. The Command Server Implementation

Visual Architecture & Process Flow

How data and code flow step-by-step

Flowchart
Step 1
def __init__
self, host: str = "127.0.0.1", port: int = 55555
Step 2
None:

4. The Python Client SDK

Visual Architecture & Process Flow

How data and code flow step-by-step

Flowchart
Step 1
def __init__
self, host: str = "127.0.0.1", port: int = 55555
Step 2
None:

5. Verification & End-to-End Test

Python
import time
 
def run_test():
print("=====================================================")
print(" TESTING CUSTOM PROTOCOL CLIENT-SERVER SYSTEM ")
print("=====================================================")
 
# 1. Launch server in a background thread
server = CommandServer(host="127.0.0.1", port=55555)
server_thread = threading.Thread(target=server.start, daemon=True)
server_thread.start()
time.sleep(0.1) # Allow server to bind and listen
 
# 2. Connect client
client = CommandClient(host="127.0.0.1", port=55555)
client.connect()
 
# 3. Test RPC operations
print(f"Ping Response: {client.ping()}")
print(f"System Information: {client.get_system_info()}")
print(f"Remote Addition: 10 + 25 = {client.add_numbers(10, 25)}")
sample_text = "Advanced Python Architecture"
computed_hash = client.compute_sha256(sample_text)
print(f"SHA-256 Digest: {computed_hash}")
 
# 4. Clean shutdown
client.close()
print("=====================================================")
print(" ALL PROTOCOL TESTS COMPLETED CLEANLY! ")
print("=====================================================")
 
if __name__ == "__main__":
run_test()

6. Key Architectural Takeaways

  1. 1
    Length-Prefixed Framing Solves Fragility: By packing a fixed 4-byte header (struct.pack("!I", length)), the receiver knows precisely how many bytes to read, preventing packet boundary corruption.
  2. 2
    recv_exact_bytes Guarantee: Reads in a loop until the requested byte count is satisfied, protecting against network packet fragmentation.
  3. 3
    Layered Separation: The transport/framing layer is strictly decoupled from the application command dispatch logic.

Multiple Choice Questions

1.

Why is length-prefixed framing (struct.pack("!I", length) + payload) preferred over delimiter framing (e.g. \n) when transmitting binary or JSON payloads over TCP? A. Big-endian integers run faster on modern CPUs. B. Binary data or formatted JSON can naturally contain newline characters (\n), which would prematurely trigger delimiter detectors and corrupt the message. C. Delimiters only work on HTTP servers. D. Length prefixes compress data by 50%.

Answer: B
Explanation:If messages contain arbitrary payloads (such as formatted JSON, binary images, or serialized objects), delimiters like newlines can appear within the data, causing framing errors. Length prefixes avoid this completely.

2.

What does the format specifier "!I" represent in Python's struct module? A. A 1-byte character in ASCII format. B. A 4-byte unsigned integer stored in standard Network Byte Order (Big-Endian). C. An infinite float. D. A signed 64-bit integer.

Answer: B
Explanation:In struct, ! specifies standard network byte order (big-endian), and I represents a 4-byte (32-bit) unsigned integer.

3.

Why must the recv_exact_bytes helper function read from the socket in a while loop rather than issuing a single sock.recv(num_bytes)? A. To keep the CPU busy. B. Because TCP makes no guarantee that all requested bytes will arrive in a single packet; recv() may return fewer bytes than requested due to network fragmentation. C. Because Python limits socket reads to 1 byte at a time. D. To encrypt the incoming stream.

Answer: B
Explanation:A single call to sock.recv(N) can return anywhere from 1 to $N$ bytes depending on MTU sizing and network buffering. Reading in a loop until $N$ bytes are collected guarantees complete message assembly.

4.

What occurs in recv_exact_bytes if sock.recv() returns an empty byte string b"" before collecting all required bytes? A. It retries indefinitely. B. It raises a ConnectionError because the remote peer terminated the connection prematurely mid-message. C. It inserts spaces to pad the buffer. D. It returns None.

Answer: B
Explanation:Receiving empty bytes before the full length of a promised message arrives indicates an abnormal connection drop or disconnect, which raises a ConnectionError.

5.

What is the primary benefit of wrapping low-level socket protocol calls in a CommandClient SDK class? A. It converts Python code to C. B. It abstracts raw byte packing, socket connections, and JSON serialization away from consumers, providing a clean, typed Python API. C. It eliminates the need for network cables. D. It bypasses the operating system kernel.

Answer: B
Explanation:Providing an SDK hides socket lifecycle mechanics, protocol encoding, and framing details behind clean methods like client.ping() and client.add_numbers().

Next Lesson

Regex Patterns and Groups

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Related Lessons

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext