应用层自定义协议与序列化实战:从 TCP 粘包问题到 JSON 通信

在 TCP 网络编程中,“发送字符串” 只能满足简单需求,而实际业务(如网络计算器、用户信息传输)需要传递结构化数据。这就需要我们自定义应用层协议,解决 “数据边界识别” 和 “结构化数据传输” 两大核心问题。本文将以 “网络计算器” 为案例,从协议设计、序列化 / 反序列化到代码实现,完整讲解应用层协议的落地过程,同时深入解析 TCP 粘包问题的解决方案。

一、为什么需要自定义应用层协议?

TCP 是 “面向字节流” 的协议,它会将数据无边界地存入发送 / 接收缓冲区,这就导致两个关键问题:

  1. 粘包问题:多次发送的数据可能被合并接收(如 “1+1” 和 “2+3” 被拼成 “1+12+3”),或单次发送的数据被拆分接收(如 “12345” 被拆成 “12” 和 “345”),接收方无法识别数据边界。
  2. 结构化数据传输:业务中需要传递的 “操作数 + 运算符”“结果 + 状态码” 等结构化信息,无法直接通过字符串传递(如 “1+1” 需要解析出1+1三个字段,解析逻辑脆弱且易出错)。

应用层协议的本质是双方约定的结构化数据格式,它需要同时解决:

        如何识别数据边界(解粘包);

        如何将结构化数据转换为可传输的字节流(序列化);

        如何将字节流还原为结构化数据(反序列化)。

二、核心概念:序列化与反序列化

在设计协议前,需先理解 “序列化” 和 “反序列化” 的作用 —— 它们是连接 “内存中的结构化数据” 和 “网络传输的字节流” 的桥梁。

2.1 基本定义

        序列化:将内存中的结构化数据(如 C++ 的struct/class)转换为连续的字节流(如字符串、JSON),便于网络传输或存储。

        反序列化:将接收到的字节流还原为原始的结构化数据,供业务逻辑使用。

2.2 常见序列化方案对比

本文选择JSON(通过 Jsoncpp 库)作为序列化方案,其优势与其他方案对比如下:

方案优点缺点适用场景
自定义字符串格式(如 “x op y”)实现简单,无需第三方库扩展性差(新增字段需修改解析逻辑),不支持复杂结构(如嵌套对象)极简单场景(如仅传输两个整数 + 运算符)
JSON(Jsoncpp)人类可读,支持复杂结构(对象、数组),跨语言兼容序列化后体积略大,性能低于二进制方案大多数业务场景(如 API 接口、中小型数据传输)
二进制协议(如 Protobuf)体积小、性能高可读性差,需定义.proto 文件,学习成本高高并发、大数据量场景(如游戏、物联网)

三、应用层协议设计:解决粘包与结构化传输

以 “网络计算器” 为例,我们设计一套完整的应用层协议,需包含 “数据边界标识” 和 “结构化数据字段” 两部分。

3.1 协议核心设计思路

为解决粘包问题,我们采用 “长度前缀法”:在每个数据包前添加 “有效数据长度” 字段,接收方先读取长度,再根据长度读取完整的有效数据。同时,有效数据部分使用 JSON 格式存储结构化信息,便于序列化 / 反序列化。

3.2 协议格式定义

最终约定的数据包格式如下(\r\n为分隔符,不属于有效数据):

[有效数据长度]\r\n[JSON格式的有效数据]\r\n

        有效数据长度:表示后续 JSON 数据的字节数(如 JSON 数据 “{"datax":1,"datay":1,"oper":"+"}” 长度为 32,则前缀为 “32”);

        JSON 有效数据:存储结构化业务数据(如请求中的 “操作数 + 运算符”,响应中的 “结果 + 状态码”);

        分隔符\r\n:用于区分 “长度字段” 和 “有效数据”,避免长度字段与有效数据混淆(如长度 “123” 和有效数据 “456” 不会被误判为 “123456”)。

3.3 业务结构体定义

针对 “网络计算器” 场景,定义两个核心结构体:

  1. 请求结构体(Request):客户端向服务器发送的 “计算请求”,包含两个操作数和一个运算符;
  2. 响应结构体(Response):服务器向客户端返回的 “计算结果”,包含结果值和状态码(如 0 表示成功,1 表示除零错误)。

结构体字段定义如下:

结构体字段类型说明
Request_data_xint第一个操作数(如 1)
Request_data_yint第二个操作数(如 1)
Request_operchar运算符(如 +、-、*、/、%)
Response_resultint计算结果(如 2)
Response_codeint状态码(0 = 成功,1 = 除零错误,2 = 非法运算符)

四、基础封装:Socket 与协议工具类

        为简化代码,先封装通用的 Socket 类(TCP)和协议工具类(编码 / 解码、序列化 / 反序列化)。

4.1 Socket 封装(Socket.hpp)

        基于 TCP 协议封装 Socket 基类和实现类,提供 “创建、绑定、监听、接受连接、收发数据” 等核心接口,采用 “模板方法模式” 统一流程:

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

// 地址结构转换宏(struct sockaddr_in* → struct sockaddr*)
#define Convert(addrptr) ((struct sockaddr *)addrptr)

namespace Net_Work {
// 错误码定义
enum ErrorCode {
    SocketError = 1,
    BindError,
    ListenError
};
const static int defaultsockfd = -1; // 默认无效socket描述符
const static int backlog = 5;        // 默认最大等待连接数

// Socket基类(接口类)
class Socket {
public:
    virtual ~Socket() {}
    // 创建socket(失败则退出)
    virtual void CreateSocketOrDie() = 0;
    // 绑定端口(失败则退出)
    virtual void BindSocketOrDie(uint16_t port) = 0;
    // 监听socket(失败则退出)
    virtual void ListenSocketOrDie(int backlog) = 0;
    // 接受连接(返回新的通信socket,传出客户端IP和端口)
    virtual Socket* AcceptConnection(std::string* peerip, uint16_t* peerport) = 0;
    // 连接服务器(返回是否成功)
    virtual bool ConnectServer(std::string& serverip, uint16_t serverport) = 0;
    // 获取socket描述符
    virtual int GetSockFd() = 0;
    // 设置socket描述符(用于接受连接后创建新socket)
    virtual void SetSockFd(int sockfd) = 0;
    // 关闭socket
    virtual void CloseSocket() = 0;
    // 接收数据(指定缓冲区大小,返回是否成功)
    virtual bool Recv(std::string* buffer, int size) = 0;
    // 发送数据
    virtual void Send(std::string& send_str) = 0;

    // 模板方法:创建监听socket(统一流程)
    void BuildListenSocketMethod(uint16_t port, int backlog = Net_Work::backlog) {
        CreateSocketOrDie();
        BindSocketOrDie(port);
        ListenSocketOrDie(backlog);
    }

    // 模板方法:创建客户端连接socket(统一流程)
    bool BuildConnectSocketMethod(std::string& serverip, uint16_t serverport) {
        CreateSocketOrDie();
        return ConnectServer(serverip, serverport);
    }

    // 模板方法:通过已有的sockfd创建socket(用于accept后)
    void BuildNormalSocketMethod(int sockfd) {
        SetSockFd(sockfd);
    }
};

// TCP Socket实现类
class TcpSocket : public Socket {
public:
    TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd) {}
    ~TcpSocket() { CloseSocket(); }

    // 创建TCP socket(SOCK_STREAM表示TCP)
    void CreateSocketOrDie() override {
        if (_sockfd < 0) {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0) {
                perror("socket create failed");
                exit(SocketError);
            }
        }
    }

    // 绑定端口(INADDR_ANY表示绑定所有网卡IP)
    void BindSocketOrDie(uint16_t port) override {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(port); // 主机字节序转网络字节序

        if (::bind(_sockfd, Convert(&local), sizeof(local)) < 0) {
            perror("bind failed");
            exit(BindError);
        }
    }

    // 设为监听状态
    void ListenSocketOrDie(int backlog) override {
        if (::listen(_sockfd, backlog) < 0) {
            perror("listen failed");
            exit(ListenError);
        }
    }

    // 接受客户端连接(返回新的TcpSocket用于通信)
    Socket* AcceptConnection(std::string* peerip, uint16_t* peerport) override {
        struct sockaddr_in peer;
        socklen_t peer_len = sizeof(peer);
        int new_sockfd = ::accept(_sockfd, Convert(&peer), &peer_len);
        if (new_sockfd < 0) {
            perror("accept failed");
            return nullptr;
        }

        // 传出客户端IP和端口
        *peerip = inet_ntoa(peer.sin_addr);
        *peerport = ntohs(peer.sin_port);
        return new TcpSocket(new_sockfd);
    }

    // 连接服务器
    bool ConnectServer(std::string& serverip, uint16_t serverport) override {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 字符串IP转网络字节序
        server.sin_port = htons(serverport);

        if (::connect(_sockfd, Convert(&server), sizeof(server)) < 0) {
            perror("connect failed");
            return false;
        }
        return true;
    }

    // 接收数据(将数据追加到buffer,返回是否成功)
    bool Recv(std::string* buffer, int size) override {
        char in_buf[size];
        ssize_t n = ::recv(_sockfd, in_buf, size - 1, 0); // 留1字节存'\0'
        if (n > 0) {
            in_buf[n] = '\0';
            *buffer += in_buf; // 追加接收,避免拆分数据丢失
            return true;
        } else if (n == 0) {
            // 对端关闭连接
            return false;
        } else {
            // 接收错误
            perror("recv failed");
            return false;
        }
    }

    // 发送数据
    void Send(std::string& send_str) override {
        ::send(_sockfd, send_str.c_str(), send_str.size(), 0);
    }

    // 获取/设置socket描述符、关闭socket(实现略)
    int GetSockFd() override { return _sockfd; }
    void SetSockFd(int sockfd) override { _sockfd = sockfd; }
    void CloseSocket() override {
        if (_sockfd > defaultsockfd) {
            ::close(_sockfd);
            _sockfd = defaultsockfd;
        }
    }

private:
    int _sockfd; // socket描述符
};
} // namespace Net_Work

4.2 协议工具类(Protocol.hpp)

封装 “编码(添加长度前缀)”“解码(解粘包)”“序列化(结构体转 JSON)”“反序列化(JSON 转结构体)” 四大核心功能,同时提供 “请求 / 响应结构体” 和 “工厂类”(简化对象创建)。

4.2.1 依赖库:Jsoncpp 安装与使用

本文使用 Jsoncpp 实现 JSON 序列化,需先安装:

        Ubuntu:sudo apt-get install libjsoncpp-dev

        CentOS:sudo yum install jsoncpp-devel

4.2.2 完整协议工具类代码
#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <jsoncpp/json/json.h>

namespace Protocol {
// 协议分隔符(\r\n用于区分长度和有效数据)
const std::string LineBreakSep = "\r\n";

// 1. 编码:给有效数据(如JSON字符串)添加长度前缀,生成完整数据包
std::string Encode(const std::string& message) {
    std::string len_str = std::to_string(message.size()); // 有效数据长度
    // 格式:[长度]\r\n[有效数据]\r\n
    return len_str + LineBreakSep + message + LineBreakSep;
}

// 2. 解码:从接收缓冲区中提取完整数据包(解粘包)
// package:接收缓冲区(需持续累加数据),message:输出的完整有效数据
bool Decode(std::string& package, std::string* message) {
    // 步骤1:查找第一个\r\n,确定长度字段的结束位置
    size_t len_sep_pos = package.find(LineBreakSep);
    if (len_sep_pos == std::string::npos) {
        // 未找到分隔符,数据不完整
        return false;
    }

    // 步骤2:解析长度字段(字符串转整数)
    std::string len_str = package.substr(0, len_sep_pos);
    int message_len = std::stoi(len_str); // 有效数据长度

    // 步骤3:计算完整数据包的总长度(长度字段+2个\r\n+有效数据)
    int total_len = len_str.size() + LineBreakSep.size() + message_len + LineBreakSep.size();
    if (package.size() < total_len) {
        // 接收缓冲区数据不足,等待后续数据
        return false;
    }

    // 步骤4:提取有效数据,并从缓冲区中删除已处理的数据包
    *message = package.substr(len_sep_pos + LineBreakSep.size(), message_len);
    package.erase(0, total_len); // 清除已处理数据,避免重复解析
    return true;
}

// 3. 请求结构体(客户端→服务器:操作数+运算符)
class Request {
public:
    Request() : _data_x(0), _data_y(0), _oper(0) {}
    Request(int x, int y, char op) : _data_x(x), _data_y(y), _oper(op) {}

    // 序列化:Request→JSON字符串
    bool Serialize(std::string* out) {
        Json::Value root;
        root["datax"] = _data_x;   // 第一个操作数
        root["datay"] = _data_y;   // 第二个操作数
        root["oper"] = _oper;      // 运算符(JSON中char以int存储)
        Json::FastWriter writer;   // 无格式JSON(体积小,适合传输)
        *out = writer.write(root);
        return true;
    }

    // 反序列化:JSON字符串→Request
    bool Deserialize(std::string& in) {
        Json::Value root;
        Json::Reader reader;
        if (!reader.parse(in, root)) {
            // JSON解析失败
            std::cerr << "Request deserialize failed: " << reader.getFormattedErrorMessages() << std::endl;
            return false;
        }

        // 提取字段(需判断字段是否存在,避免崩溃)
        if (root.isMember("datax") && root["datax"].isInt() &&
            root.isMember("datay") && root["datay"].isInt() &&
            root.isMember("oper") && root["oper"].isInt()) {
            _data_x = root["datax"].asInt();
            _data_y = root["datay"].asInt();
            _oper = static_cast<char>(root["oper"].asInt());
            return true;
        }
        return false;
    }

    // 获取字段(实现略)
    int GetX() const { return _data_x; }
    int GetY() const { return _data_y; }
    char GetOper() const { return _oper; }

private:
    int _data_x;  // 第一个操作数
    int _data_y;  // 第二个操作数
    char _oper;   // 运算符(+、-、*、/、%)
};

// 4. 响应结构体(服务器→客户端:结果+状态码)
class Response {
public:
    Response() : _result(0), _code(0) {}
    Response(int result, int code) : _result(result), _code(code) {}

    // 序列化:Response→JSON字符串
    bool Serialize(std::string* out) {
        Json::Value root;
        root["result"] = _result; // 计算结果
        root["code"] = _code;     // 状态码(0=成功,1=除零错误,2=非法运算符)
        Json::FastWriter writer;
        *out = writer.write(root);
        return true;
    }

    // 反序列化:JSON字符串→Response
    bool Deserialize(std::string& in) {
        Json::Value root;
        Json::Reader reader;
        if (!reader.parse(in, root)) {
            std::cerr << "Response deserialize failed: " << reader.getFormattedErrorMessages() << std::endl;
            return false;
        }

        if (root.isMember("result") && root["result"].isInt() &&
            root.isMember("code") && root["code"].isInt()) {
            _result = root["result"].asInt();
            _code = root["code"].asInt();
            return true;
        }
        return false;
    }

    // 设置/获取字段(实现略)
    void SetResult(int result) { _result = result; }
    void SetCode(int code) { _code = code; }
    int GetResult() const { return _result; }
    int GetCode() const { return _code; }

private:
    int _result; // 计算结果
    int _code;   // 状态码
};

// 5. 工厂类(简化Request/Response对象创建,避免重复new/delete)
class Factory {
public:
    // 创建空Request
    std::shared_ptr<Request> BuildRequest() {
        return std::make_shared<Request>();
    }

    // 创建带参数的Request
    std::shared_ptr<Request> BuildRequest(int x, int y, char op) {
        return std::make_shared<Request>(x, y, op);
    }

    // 创建空Response
    std::shared_ptr<Response> BuildResponse() {
        return std::make_shared<Response>();
    }

    // 创建带参数的Response
    std::shared_ptr<Response> BuildResponse(int result, int code) {
        return std::make_shared<Response>(result, code);
    }
};
} // namespace Protocol

五、业务实现:网络计算器(服务器与客户端)

基于上述封装,实现 “网络计算器” 的服务器和客户端,完整流程为:

  1. 客户端:构造 “操作数 + 运算符” 请求 → 序列化→编码→发送;
  2. 服务器:接收数据→解码→反序列化→计算→构造响应→序列化→编码→发送;
  3. 客户端:接收响应→解码→反序列化→显示结果。

5.1 服务器实现(TcpServerMain.cc)

服务器采用 “多线程” 模型(复用之前的线程池逻辑),每个客户端连接由独立线程处理,核心是 “协议解析” 和 “计算逻辑”。

#include <iostream>
#include <memory>
#include <pthread.h>
#include "Socket.hpp"
#include "Protocol.hpp"

// 线程数据:传递通信socket和客户端信息
struct ThreadData {
    Net_Work::Socket* sock;
    std::string peerip;
    uint16_t peerport;
};

// 计算逻辑:根据Request计算结果,生成Response
Protocol::Response Calculate(Protocol::Request& req) {
    Protocol::Response resp;
    int x = req.GetX();
    int y = req.GetY();
    char op = req.GetOper();

    switch (op) {
        case '+':
            resp.SetResult(x + y);
            resp.SetCode(0); // 成功
            break;
        case '-':
            resp.SetResult(x - y);
            resp.SetCode(0);
            break;
        case '*':
            resp.SetResult(x * y);
            resp.SetCode(0);
            break;
        case '/':
            if (y == 0) {
                resp.SetResult(0);
                resp.SetCode(1); // 除零错误
            } else {
                resp.SetResult(x / y);
                resp.SetCode(0);
            }
            break;
        case '%':
            if (y == 0) {
                resp.SetResult(0);
                resp.SetCode(1); // 除零错误
            } else {
                resp.SetResult(x % y);
                resp.SetCode(0);
            }
            break;
        default:
            resp.SetResult(0);
            resp.SetCode(2); // 非法运算符
            break;
    }
    return resp;
}

// 线程执行函数:处理单个客户端的通信
void* HandleClient(void* args) {
    pthread_detach(pthread_self()); // 线程分离,无需主线程回收
    ThreadData* td = static_cast<ThreadData*>(args);
    if (td == nullptr || td->sock == nullptr) {
        return nullptr;
    }

    std::cout << "New client connected: " << td->peerip << ":" << td->peerport << std::endl;
    std::string recv_buf; // 接收缓冲区(累加数据,用于解粘包)
    Protocol::Factory factory;

    while (true) {
        // 1. 接收数据(追加到缓冲区)
        if (!td->sock->Recv(&recv_buf, 1024)) {
            std::cout << "Client disconnected: " << td->peerip << ":" << td->peerport << std::endl;
            break;
        }

        // 2. 解码:提取完整的有效数据(解粘包)
        std::string message;
        while (Protocol::Decode(recv_buf, &message)) {
            // 3. 反序列化:JSON→Request
            auto req = factory.BuildRequest();
            if (!req->Deserialize(message)) {
                std::cerr << "Request deserialize failed from " << td->peerip << ":" << td->peerport << std::endl;
                continue;
            }

            // 4. 计算:根据Request生成Response
            Protocol::Response resp = Calculate(*req);

            // 5. 序列化:Response→JSON
            std::string resp_json;
            resp.Serialize(&resp_json);

            // 6. 编码:添加长度前缀,发送响应
            std::string resp_package = Protocol::Encode(resp_json);
            td->sock->Send(resp_package);
            std::cout << "Handled request from " << td->peerip << ":" << td->peerport 
                      << " -> " << req->GetX() << req->GetOper() << req->GetY() 
                      << " = " << resp.GetResult() << " (code: " << resp.GetCode() << ")" << std::endl;
        }
    }

    // 释放资源
    td->sock->CloseSocket();
    delete td->sock;
    delete td;
    return nullptr;
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    uint16_t port = std::stoi(argv[1]);

    // 创建监听socket
    std::unique_ptr<Net_Work::Socket> listen_sock(new Net_Work::TcpSocket());
    listen_sock->BuildListenSocketMethod(port);
    std::cout << "Server started, listening on port " << port << std::endl;

    // 循环接受客户端连接
    while (true) {
        std::string peerip;
        uint16_t peerport;
        // 接受连接,返回新的通信socket
        Net_Work::Socket* conn_sock = listen_sock->AcceptConnection(&peerip, &peerport);
        if (conn_sock == nullptr) {
            continue;
        }

        // 创建线程数据,启动线程处理客户端
        ThreadData* td = new ThreadData{conn_sock, peerip, peerport};
        pthread_t tid;
        if (pthread_create(&tid, nullptr, HandleClient, td) != 0) {
            perror("pthread_create failed");
            delete td;
            conn_sock->CloseSocket();
            delete conn_sock;
        }
    }

    return 0;
}

5.2 客户端实现(TcpClientMain.cc)

客户端逻辑简单:读取用户输入的 “操作数 + 运算符”,构造请求并发送,接收响应后显示结果。

#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
#include "Protocol.hpp"

// 解析用户输入:如“1+1”→x=1, y=1, op='+'
bool ParseInput(const std::string& input, int* x, int* y, char* op) {
    // 查找运算符位置(仅支持单个运算符)
    size_t op_pos = input.find_first_of("+-*/%");
    if (op_pos == std::string::npos || op_pos == 0 || op_pos == input.size() - 1) {
        return false;
    }

    // 提取操作数和运算符
    *x = std::stoi(input.substr(0, op_pos));
    *y = std::stoi(input.substr(op_pos + 1));
    *op = input[op_pos];
    return true;
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }

    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);
    Protocol::Factory factory;

    // 创建客户端socket并连接服务器
    std::unique_ptr<Net_Work::Socket> client_sock(new Net_Work::TcpSocket());
    if (!client_sock->BuildConnectSocketMethod(server_ip, server_port)) {
        std::cerr << "Connect to server failed" << std::endl;
        return 1;
    }
    std::cout << "Connected to server: " << server_ip << ":" << server_port << std::endl;

    std::string recv_buf; // 接收缓冲区(用于解粘包)
    std::string input;

    while (true) {
        // 1. 读取用户输入
        std::cout << "Please enter expression (e.g. 1+1, q to quit): ";
        std::getline(std::cin, input);
        if (input == "q" || input == "Q") {
            break;
        }

        // 2. 解析输入(如“1+1”→x=1, y=1, op='+')
        int x, y;
        char op;
        if (!ParseInput(input, &x, &y, &op)) {
            std::cerr << "Invalid expression! Example: 1+1" << std::endl;
            continue;
        }

        // 3. 构造Request→序列化→编码
        auto req = factory.BuildRequest(x, y, op);
        std::string req_json;
        req->Serialize(&req_json);
        std::string req_package = Protocol::Encode(req_json);

        // 4. 发送请求
        client_sock->Send(req_package);

        // 5. 接收响应并解码
        recv_buf.clear();
        while (true) {
            if (!client_sock->Recv(&recv_buf, 1024)) {
                std::cerr << "Server disconnected" << std::endl;
                return 1;
            }

            std::string resp_json;
            if (Protocol::Decode(recv_buf, &resp_json)) {
                // 6. 反序列化→显示结果
                auto resp = factory.BuildResponse();
                if (resp->Deserialize(resp_json)) {
                    int result = resp->GetResult();
                    int code = resp->GetCode();
                    switch (code) {
                        case 0:
                            std::cout << "Result: " << result << std::endl;
                            break;
                        case 1:
                            std::cout << "Error: division by zero" << std::endl;
                            break;
                        case 2:
                            std::cout << "Error: invalid operator" << std::endl;
                            break;
                        default:
                            std::cout << "Error: unknown code " << code << std::endl;
                            break;
                    }
                }
                break;
            }
        }
    }

    // 关闭连接
    client_sock->CloseSocket();
    std::cout << "Disconnected from server" << std::endl;
    return 0;
}

六、测试与验证

6.1 编译与运行

需链接 Jsoncpp 库(编译时加-ljsoncpp),Makefile 示例:

CC = g++
CFLAGS = -std=c++11 -Wall
LDFLAGS = -ljsoncpp -lpthread

TARGETS = tcp_server tcp_client

all: $(TARGETS)

tcp_server: TcpServerMain.cc
	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

tcp_client: TcpClientMain.cc
	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

clean:
	rm -f $(TARGETS)

6.2 测试步骤

  1. 启动服务器:./tcp_server 8888
  2. 启动客户端:./tcp_client 127.0.0.1 8888
  3. 客户端输入表达式(如 “10+20”“15/3”“7%2”),查看结果;
  4. 测试异常场景(如 “5/0”“10#3”),验证错误处理。

6.3 预期结果

        正常输入:10+20 → 输出 “Result: 30”;

        除零错误:5/0 → 输出 “Error: division by zero”;

        非法运算符:10#3 → 输出 “Error: invalid operator”。

七、总结与扩展

本文通过 “网络计算器” 案例,完整讲解了应用层协议的设计与实现,核心收获包括:

  1. 协议设计:用 “长度前缀法” 解决 TCP 粘包问题,用 JSON 实现结构化数据传输;
  2. 核心工具:封装 Socket 类简化网络操作,封装协议类统一编码 / 解码、序列化 / 反序列化;
  3. 业务落地:从请求构造到响应处理,形成完整的网络通信闭环。

扩展方向

  1. 协议优化
    • 增加 “协议版本号” 字段,支持协议兼容升级;
    • 增加 “校验和” 字段,防止数据传输损坏。
  2. 性能优化
    • 替换 JSON 为 Protobuf,减少数据体积和序列化耗时;
    • 采用 IO 多路复用(epoll/select)替代多线程,支持更高并发。
  3. 功能扩展
    • 支持更多运算(如浮点数、复杂表达式);
    • 增加身份认证(如 Token),提升安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值