跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

手写 C++ TCP 服务器:自定义协议与粘包处理实战

TCP 基于字节流传输,天然存在粘包风险。通过手写 C++ TCP 计算器服务器,演示如何设计应用层协议(长度头 + 内容)来解决粘包问题。核心在于自定义 Encode/Decode 函数,结合缓冲区管理确保数据完整性解析。

开源信徒发布于 2026/3/22更新于 2026/6/1216 浏览
手写 C++ TCP 服务器:自定义协议与粘包处理实战

手写一个 C++ TCP 服务器实现自定义协议(顺便解决粘包问题)

在之前的网络编程实践中,我们直观地感受了 UDP 和 TCP 套接字的使用,成功完成了客户端与服务端的通信。但这里有一个关键细节常被忽略:UDP 基于数据报传输,发送即完整;而 TCP 基于字节流传输,如何保证读取上来的数据是一个完整的报文?

TCP 的字节流特性与粘包风险

TCP 是面向连接的协议,主要负责控制何时发送、发送多少以及出错如何处理。我们在应用层调用 read/write 系统调用时,数据只是从用户空间拷贝到了内核空间的 TCP 缓冲区。至于何时真正发送给对方,完全由 TCP 协议栈决定。

这就导致接收端面临不确定性。比如发送方发了一个长报文,接收方可能只收到一部分就返回给用户;或者一次 read() 读到了多个报文。这就像你通过微信道歉,本想发'我不后悔我爱你',结果对方只收到了'我不后悔',误会由此产生。

为了避免这种差异,我们必须制定应用层协议,确保数据的边界清晰。

自定义应用层协议设计

为什么需要序列化?

想象一个聊天群,我发送'哈哈哈',实际传输的不止这三个字,还有昵称、头像、时间戳等元数据。看似简单的字符串,背后其实是结构化数据的序列化和反序列化过程。

为了演示这一点,我们构建一个简单的网络版计算器。

协议格式

由于 TCP 没有消息边界,我们需要自定义协议头来标识长度。采用如下格式:

len\n
content\n

例如发送 10 + 20,编码后变为:

7\n10 + 20\n

这种 长度 + 换行 + 内容 + 换行 的结构,让接收方能准确知道要读多少个字节。

核心代码实现

协议封装:Encode 与 Decode

这是解决粘包的关键。我们需要将业务数据打包成符合协议的字符串,并在接收端解析。

编码函数 Encode:

std::string Encode(std::string &content) {
    std::string s;
    size_t len = content.size();
    s += std::to_string(len);
    s += "\n";
    s += content;
    s += "\n";
    return s;
}

解码函数 Decode:

这个函数负责从缓冲区中提取完整报文。它做了三件事:判断头部是否完整、判断数据量是否足够、解析并移除已处理部分。

bool Decode(std::string &s, std::string *content) {
    size_t left_pos = s.find("\n");
    if (left_pos == std::string::npos)  ;
    
    std::string content_len = s.(, left_pos);
     len = std::(content_len);
    
    
     (s.() < content_len.() + len + )  ;
    
    *content = s.(left_pos + , len);
    s.(, content_len.() + len + );
     ;
}
return
false
substr
0
int
stoi
// 检查缓冲区是否包含完整的数据(长度位 + 换行 + 内容 + 换行)
if
size
size
2
return
false
substr
1
erase
0
size
2
return
true

注意:Decode 会修改传入的字符串引用,将已解析的部分擦除,这样下一次循环就能继续处理剩余数据。

请求与响应结构

请求 Request:

class request {
public:
    int x_;
    int y_;
    char op_;
    // ... 序列化与反序列化逻辑 ...
};

响应 Response:

class response {
public:
    int result_;
    int code_; // 0:成功,1:除0,2:取模0,3:非法操作
    // ... 序列化与反序列化逻辑 ...
};

服务器核心逻辑

服务器接收到数据后,不会直接处理,而是先放入缓冲区,然后循环尝试 Decode。如果返回空字符串,说明报文不完整,继续等待;一旦解析成功,再交由计算逻辑处理。

while (true) {
    int sockfd = listenfd_.Accept(&client_port, &client_ip);
    if (sockfd < 0) continue;
    
    if (fork() == 0) { // 子进程处理
        listenfd_.Close();
        std::string inbuffer_stream;
        while (1) {
            char buffer[1280];
            ssize_t s = read(sockfd, buffer, sizeof buffer - 1);
            if (s > 0) {
                buffer[s] = 0;
                inbuffer_stream += buffer; // 累加到缓冲区
                
                // 循环尝试解析,直到无法解析或无数据
                while (true) {
                    std::string info = callback_(inbuffer_stream);
                    if (info.empty()) break;
                    write(sockfd, info.c_str(), info.size());
                }
            } else if (s == 0) {
                break;
            } else {
                break;
            }
        }
        close(sockfd);
        exit(0);
    }
}

可以看到,只要 callback_(即我们的 Calculator 函数)返回空字符串,主循环就会暂停写入,继续等待更多数据进入 inbuffer_stream。这样就完美解决了粘包问题——只有凑齐了一个完整协议,才会被处理。

完整项目结构

主要模块包括 Socket 封装、TcpServer 框架、Protocol 协议封装以及 CalculatorServer 计算逻辑。

模块作用
Socket封装 socket API
TcpServerTCP 服务器框架
Protocol协议封装
CalculatorServer计算逻辑

总结

到这里,我们已经完整实现了一个基于 C++ 的 TCP 计算器服务器。通过这个案例,我们应该建立这样的认知:TCP 编程的本质不是收发数据,而是如何正确解析数据的边界。

核心思想很简单:TCP 没有消息边界,所以我们必须设计应用层协议。通过长度头 + 内容的方式,配合缓冲区的累积与解析,即可稳定可靠地处理任意长度的数据流。

目录

  1. 手写一个 C++ TCP 服务器实现自定义协议(顺便解决粘包问题)
  2. TCP 的字节流特性与粘包风险
  3. 自定义应用层协议设计
  4. 为什么需要序列化?
  5. 协议格式
  6. 核心代码实现
  7. 协议封装:Encode 与 Decode
  8. 请求与响应结构
  9. 服务器核心逻辑
  10. 完整项目结构
  11. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ 实战:构建基于对话框的搜索引擎
  • 设计支持万人并发的秒杀系统架构方案
  • Java 统计子字符串在父字符串中的出现次数
  • GitHub Copilot 性能优化实战:提升 AI 代码建议响应速度
  • CANN 技术栈解析:Python、C++ 及算子开发选型指南
  • HarukaBot 搭建与使用指南:B 站直播动态 QQ 推送
  • PaddleOCR C++部署避坑指南:VS2019/2022+CPU环境完整配置流程
  • 2026 年 3 月全球 AI 前沿动态:从模型突破到智能体跃迁
  • 宇树 G1 机器人 VR 遥操与模仿学习开发:xr_teleoperate 集成 unitree_IL_lerobot
  • Whisper v0.2 本地语音转文字工具安装与使用指南
  • 通义万相 2.1 文生图技术特性与部署实践
  • ChatGPT 降低低质内容生成指令实战:精准控制输出质量
  • 基于 LLaMA-Factory 的 LLM DPO 训练实战
  • faster-whisper 快速部署与性能优化指南
  • DeepSeek 中冷启动数据与多阶段训练的作用
  • OmniSteward:基于 LLM Agent 的智能家居与电脑控制方案
  • 2026 RAG 技术演进:DeepSeek 结合 Neo4j 构建企业智能体系
  • TWIST2:基于 VR 的人形机器人全身遥操与视觉自主策略
  • Qwen-Image-2512 免费本地部署实战
  • 医疗 NLP 实战:从电子病历分析到智能问答模型落地

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online