Build a Linux TCP Network Calculator in C++: Socket Abstractions, Custom Protocols, and JSON Serialization

This project is a Linux Socket and C++-based TCP network calculator. Its core capability is a complete request-compute-response loop between client and server, using JSON serialization and a “length header + delimiter” protocol to solve TCP sticky packet and partial packet issues. Keywords: TCP network programming, socket abstraction, custom protocol.

The technical specification snapshot captures the project at a glance

Parameter Description
Language C++17
Transport Protocol TCP
Serialization Format JSON (jsoncpp)
Concurrency Model fork-based multi-process
Core Dependencies POSIX Socket, jsoncpp, pthread
Key Challenges Sticky packets, partial packets, protocol framing, business logic decoupling
Project Structure TCP server + TCP client + calculator business module
Star Count Not provided in the original article

This project completes the TCP calculator workflow through a layered design

This project is not a simple example of socket calls. It is a full application-layer protocol implementation. It separates network communication, protocol parsing, and business computation into independent modules: Socket, Protocol, and Calculator.

This separation directly addresses two common pain points. First, scattered system calls make code difficult to maintain. Second, coupling business logic with network details makes it hard to extend the system to future use cases such as chat, translation, or file transfer.

The module responsibilities can be summarized in one sentence each

  • Socket.hpp: Encapsulates the TCP socket lifecycle
  • Protocol.hpp: Defines request/response messages and framing/parsing rules
  • Calculator.hpp: Handles arithmetic operations and error codes
  • TcpServer.hpp: Manages listening, accepting, send/receive, and concurrent processing
  • NetCalServer.cc / NetCalClient.cc: Wires all modules together
// Main server composition flow: network layer -> protocol layer -> business layer
std::unique_ptr
<Calculator> cal = std::make_unique<Calculator>();
std::unique_ptr
<Protocol> protocol = std::make_unique<Protocol>(
    [&cal](Request &req) -> Response {
        return cal->Execute(req); // Core business callback: compute the request and return the result
    }
);

This code injects calculator logic into the protocol layer through a callback, which cleanly decouples the protocol from the business logic.

Socket abstraction unifies low-level TCP details behind a clean interface

The project wraps system calls such as socket, bind, listen, accept, recv, send, and connect inside an abstract base class, Socket, and its implementation class, TcpSocket. The core value is a unified interface rather than exposing raw file descriptors.

BuildTcpSocketMethod() is a classic Template Method pattern. It chains socket creation, binding, and listening in a fixed order, preventing upper-layer code from calling the initialization flow incorrectly.

Socket class design diagram AI Visual Insight: The diagram shows the inheritance relationship between the abstract base class and the concrete TCP subclass. The base class exposes a unified interface for create, bind, listen, accept, send, receive, and connect operations, while the subclass implements the concrete system calls. This structure combines the Template Method pattern with polymorphism: the base class defines the fixed workflow, and the subclass handles protocol-specific behavior.

Returning smart pointers from Accepter is a key design choice

Native accept() returns only an integer file descriptor, but this project wraps each new connection back into a TcpSocket object and manages it with `std::shared_ptr

`. As a result, upper layers operate on objects instead of raw file descriptors. This also improves resource safety. When a connection exits and the smart pointer lifetime ends, `Close()` can release resources automatically, reducing the risk of file descriptor leaks. “`cpp std::shared_ptr Accepter(InetAddr &clientaddr) override { int sockfd = accept(_sockfd, CONV(&addr), &len); if (sockfd (sockfd); // Wrap the communication fd into an object } “` This code upgrades a raw connection handle into a manageable, reusable, and extensible communication object. ## A custom protocol is the core technique for solving TCP sticky packet issues TCP guarantees reliable byte-stream delivery, but it does not preserve message boundaries. If multiple JSON payloads are sent continuously, the server may receive them all at once or split across multiple reads. That is the sticky packet and partial packet problem. The protocol format in this project is: `length header + \r\n + JSON body + \r\n`. It does not prevent sticky packets from happening, but it allows the receiver to split messages accurately after they occur. ![Packet format diagram](https://i-blog.csdnimg.cn/direct/ad642a812f724c40a6419251b243ee29.png) **AI Visual Insight:** The diagram presents the full application-layer message structure: a decimal length header at the front, `\r\n` boundary markers in the middle and at the end, and the JSON payload in between. This means the receiver can parse the length first, then extract the body accordingly, reconstructing independent messages from a continuous byte stream. ### `Packet` and `Unpack` form the forward and reverse sides of the protocol `Packet()` adds the protocol envelope around JSON, while `Unpack()` strips complete messages one by one from the receive buffer. If the buffer does not yet contain a full message, parsing pauses and preserves the partial packet. “`cpp std::string Packet(const std::string &json_string) { return std::to_string(json_string.size()) + “\r\n” + json_string + “\r\n”; // Frame the payload according to the protocol } int Unpack(std::string &packet, std::string *json_string) { auto pos = packet.find(“\r\n”); // Find the end of the length header if (pos == std::string::npos) return 0; int len = std::stoi(packet.substr(0, pos)); int total = pos + 2 + len + 2; // length header + delimiter + body + ending delimiter if ((int)packet.size() ParseRequest -> Calculator::Execute -> Packet -> Send`. As a result, `TcpServer` behaves like a generic TCP container, while both the protocol and the business logic remain replaceable. ![Server main loop diagram](https://i-blog.csdnimg.cn/direct/31c1837e1da14df0bca24cfd213d40e2.png) **AI Visual Insight:** The diagram illustrates a service model in which the listening socket accepts connections, spawns child processes, and continuously sends and receives data on communication sockets. The technical focus is the separation of connection management from per-connection business processing, allowing the main loop to focus on access control while child flows handle session work. ## The client actively validates sticky packet handling by sending batched requests The client does not send just one request at a time. Instead, it constructs three framed messages continuously and appends them into `outbuffer`. This intentionally creates a sticky packet scenario to verify whether the server can unpack and process messages one by one. After receiving responses, the client follows the same unpacking and deserialization path. This shows that the protocol is symmetric: requests and responses reuse the same boundary rules. ### The minimum run steps are straightforward “`bash g++ -o netcal_server NetCalServer.cc -std=c++17 -ljsoncpp g++ -o netcal_client NetCalClient.cc -std=c++17 -ljsoncpp ./netcal_server 8080 ./netcal_client 127.0.0.1 8080 “` These commands compile and launch the server and client to verify the TCP network calculator locally. ## This project is essentially a user-space mapping of the upper three OSI layers From a design perspective, `Calculator` corresponds to the application layer because it defines the concrete “calculator” business logic. `Protocol` corresponds to the presentation layer because it handles serialization, deserialization, packet framing, and unpacking. `TcpServer` corresponds to the session layer because it manages connection establishment, maintenance, and termination. Meanwhile, TCP, IP, the data link layer, and the physical layer are all handled by the Linux kernel protocol stack. The code only uses system calls to ask the kernel to establish connections, send data, and receive data. ## FAQ ### Why define an application-layer protocol when TCP is already in use? Because TCP provides only a reliable byte stream, not message boundaries. Without a custom protocol, the receiver cannot determine which part of a continuous byte stream belongs to one complete request. ### Why implement business computation on the server as a callback? Because callbacks turn `TcpServer` and `Protocol` into a reusable framework. If you later change the application to chat, translation, or file services, you only need to replace the business handler instead of rewriting the network layer. ### What areas of this implementation can still be optimized? You can add a thread pool or `epoll` to handle large-scale connections, implement complete read/write loops for `send` and `recv`, and add protocol version checks, malformed packet protection, and unit tests to improve production readiness. ## Core summary This article reconstructs a TCP network calculator project built on Linux Sockets. It breaks down socket abstraction, custom application-layer protocol design, JSON serialization, concurrent server processing, and the end-to-end client request workflow, with a strong focus on using a length header plus delimiter to solve TCP sticky packet issues.