突破编程_C++_网络编程(一种高性能处理 TCP 粘包问题的方法)

1 前言

在“突破编程_C++_网络编程(Windows 套接字(处理 TCP 粘包问题))”一文中,已经讲解了 TCP 粘包问题,并且给出了样例代码。但是该样例代码的核心是使用队列(std::queue)做报文的处理。

std::queue 是 C++ 标准模板库(STL)中的一个容器适配器,它提供了一种先进先出(FIFO)的数据结构。在 STL 中,std::queue 并不直接存储元素,而是依赖于一个底层容器来管理元素的存储,这个底层容器通常是 std::deque(双端队列)或者 std::list(双向链表),具体取决于实现。

默认情况下,std::queue 会使用 std::deque 作为底层容器:这是最常见的实现方式。std::deque 提供了从两端快速插入和删除元素的能力,这与 std::queue 的操作特性非常吻合。在 std::deque 上,std::queue 的 push() 操作对应于 std::deque 的 push_back(),而 pop() 对应于 std::deque 的 pop_front()。

如果使用 std::queue 作为处理 TCP 粘包问题,所有接收到的 TCP 数据会先存入队列中,然后再一个一个拿出来处理。一般情况下,这种处理模式是可行的,但是当报文的字节数很大时,这种模式的效率会比较低,所以本文会实现一种高性能的处理方式。

2 回顾一下 TCP 粘包问题

TCP(传输控制协议)是一种面向流的协议,它不保留数据包边界。在TCP连接中,数据被看作是一连串无结构的字节流。TCP 粘包问题指的是接收方在接收数据时,由于发送方发送的数据包较小,或者接收方处理较慢等原因,导致多个数据包粘在一起,形成一个大的数据块,从而使得接收方无法辨认出各个数据包的边界。

2.1 粘包产生的原因

(1)发送方原因:

  • 发送数据包较小,尤其是小于TCP协议的MSS(最大报文段长度)。
  • 发送数据包的时间间隔太短,导致接收方来不及处理。

(2)接收方原因:

  • 接收处理速度慢,导致多个数据包到达后才开始处理。
  • 接收缓冲区大小设置不当,可能过大或过小。

(3)网络原因:

  • 网络延迟或拥塞,导致数据包传输时间不一致。

2.2 粘包问题的影响

TCP 粘包问题对网络通信的影响是多方面的,它可能会导致数据的不完整、错误解析、性能下降甚至安全问题。以下是 TCP 粘包问题可能带来的一些具体影响:

  • 数据完整性受损:由于接收方无法准确识别数据包边界,可能会导致数据包被错误地合并或拆分,从而造成数据丢失或重复。

  • 错误解析:粘包可能导致接收方错误地解析数据,比如将两个数据包的内容错误地解释为一个数据包,或者将一个数据包的内容错误地解释为两个数据包。

  • 应用逻辑错误:在某些应用中,数据包的内容和顺序是非常重要的。粘包问题可能会导致应用逻辑处理错误,比如在聊天应用中,消息的顺序可能会被打乱。

  • 性能下降:为了处理粘包问题,接收方可能需要额外的缓冲区来暂存数据,并进行边界检测,这会增加CPU和内存的使用,从而降低系统的整体性能。

  • 延迟增加:在某些情况下,为了等待更多的数据以确定数据包的边界,接收方可能会延迟处理已经接收到的数据,这会增加处理延迟。

  • 资源浪费:由于粘包问题,接收方可能需要分配更大的缓冲区来暂存数据,这可能会导致内存资源的浪费。

  • 安全问题:粘包问题可能会被恶意利用,比如通过发送特制的数据包来破坏接收方的缓冲区,从而导致缓冲区溢出等安全问题。

  • 协议复杂性增加:为了解决粘包问题,可能需要在应用层定义额外的协议来标识数据包的边界,这会增加协议的复杂性。

  • 兼容性问题:不同的系统和应用可能采用不同的方法来处理粘包问题,这可能会导致兼容性问题。

  • 调试困难:粘包问题可能会使得网络通信的调试变得更加困难,因为错误可能不容易被发现和定位。

2.3 解决粘包问题的方法

(1)固定长度消息:

每个消息都发送固定长度的数据,接收方按照固定长度进行读取和处理。

**(2)消息头和消息体:

消息分为消息头和消息体,消息头中包含消息体的长度信息,接收方根据消息头中的长度来确定消息体的边界。

**(3)使用特殊字符或字节序列:

在消息体中使用特殊的字符或字节序列作为消息边界的标识。

**(4)应用层协议:

定义应用层协议,明确消息的开始和结束,例如使用 HTTP 协议中的 Content-Length 字段。

**(5)使用缓冲区处理:

接收方使用缓冲区暂存接收到的数据,当满足一个完整消息的条件时再进行处理。

**(6)使用 TCP 的紧急数据或带外数据:

通过发送紧急指针或带外数据来标记消息的结束。

3 一种高性能的 TCP 粘包问题的处理方式

本方式采用使用消息头(Header)+消息体长度(Length)+消息体(Body)的方法来做处理:

  1. 消息头设计:设计一个固定长度的消息头结构,通常包含消息体的长度信息。消息头的长度是固定的,以便于接收方能够快速地识别出消息头并从中读取消息体的长度。

  2. 消息体长度:在消息头中,最关键的信息是消息体的长度,这个长度值告诉接收方接下来需要读取多少字节的数据来构成一个完整的消息体。

  3. 消息体发送:发送方在发送消息体之前,先发送包含消息体长度的消息头。这样,接收方在接收到消息头后,就能够知道接下来需要读取多少字节的数据。

  4. 接收处理

    • 接收方首先读取消息头,解析出消息体的长度。
    • 根据消息头中的长度信息,接收方分配一个足够大的缓冲区来存储整个消息体。
    • 接收方接着从TCP流中读取指定长度的消息体数据,直到读取完整个消息体。
  5. 连续读取:在读取完一个完整消息后,接收方再次读取消息头,以确定下一个消息的边界,继续处理后续的消息。

  6. 缓冲区管理:为了处理可能的粘包情况,接收方可能需要使用一个缓冲区来暂存接收到的数据。当缓冲区中的数据足以构成一个完整的消息时,就从缓冲区中提取出消息并处理,然后将剩余的数据留在缓冲区中,等待构成下一个消息。

  7. 错误处理:在设计协议时,还应考虑错误处理机制,比如如果接收到的消息头中的长度信息不合理(如长度为负数或过大),则需要采取相应的错误处理措施。

通过这种方式,即使在TCP流中数据包被粘在一起,接收方也能够准确地识别出每个消息的边界,从而有效地解决了粘包问题。这种方法简单、高效,且易于实现,因此在处理TCP粘包问题时被广泛采用。

(1)定义数据缓冲区

#include <iostream>  
#include <string>  
#include <sstream>
#include <vector>  
#include <deque>  
#include <tuple>  
#include <memory>  using namespace std;std::deque<tuple<unique_ptr<char[]>, uint64_t>> remainingDatas;

(2)定义工具函数,用来显示报文

string binaryStringToHex(string& binaryStr, string strPre, string strSplit)
{string ret;static const char *hex = "0123456789ABCDEF";uint64_t offset = 0;for (auto c : binaryStr){ret.append(strPre);ret.push_back(hex[(c >> 4) & 0xf]); //取二进制高四位ret.push_back(hex[c & 0xf]);        //取二进制低四位if (offset < binaryStr.length() - 1){ret.append(strSplit);}offset++;}return ret;
}string binaryCharToHex(const char* data, uint64_t len, string strPre, string strSplit)
{string strBinaryVal = string(data, len);return binaryStringToHex(strBinaryVal, strPre, strSplit);
}

(3)TCP 粘包问题处理函数

void revMsg(const char *data, uint64_t len)
{if (0 == len) {return;}unique_ptr<char[]> dataTmp = make_unique<char[]>(len);memcpy(dataTmp.get(), data, len);remainingDatas.push_back(tuple<unique_ptr<char[]>, uint64_t>(move(dataTmp), len));while (remainingDatas.size() > 0) {auto remainingDataPtr = get<0>(remainingDatas[0]).get();if (0x20 != remainingDataPtr[0]) {remainingDatas.clear();return;}if (1 == get<1>(remainingDatas[0]) && remainingDatas.size() <= 1) {return;}uint8_t msgLen = 0;uint64_t totalLen = 0;for (auto& remainingData : remainingDatas) {totalLen += get<1>(remainingData);}if (1 == get<1>(remainingDatas[0])) {remainingDataPtr = get<0>(remainingDatas[1]).get();msgLen = (uint8_t)remainingDataPtr[0];}else {msgLen = (uint8_t)remainingDataPtr[1];}if (msgLen + 2 > totalLen) {return;}unique_ptr<char[]> totalData = make_unique<char[]>(totalLen);uint64_t totalDataOffset = 0;for (auto& remainingData : remainingDatas) {uint64_t lenTmp = get<1>(remainingData);memcpy(&(totalData.get())[totalDataOffset], get<0>(remainingData).get(), lenTmp);totalDataOffset += lenTmp;}remainingDatas.clear();uint64_t offset = 2;uint64_t lastMsgOffset = 0;			// 最后一帧完整报文的最后一个字节偏移while (offset + msgLen <= totalLen) {uint64_t revMsgLen = msgLen + 2;unique_ptr<char[]> revMsg = make_unique<char[]>(revMsgLen);memcpy(revMsg.get(), &(totalData.get())[offset - 2], revMsgLen);// 打印处理粘包后的一帧完整报文auto strMsg = binaryCharToHex(revMsg.get(), revMsgLen, "", " ");printf("[recive] %s\n", strMsg.c_str());offset += msgLen;lastMsgOffset = offset;if (offset + 2 < totalLen) {if (0x20 != totalData[offset]) {return;}offset++;msgLen = (uint8_t)totalData[offset];offset++;}}if (lastMsgOffset < totalLen) {uint64_t lastRemainLen = totalLen - lastMsgOffset;unique_ptr<char[]> lastRemainData = make_unique<char[]>(lastRemainLen);memcpy(lastRemainData.get(), &(totalData.get())[lastMsgOffset], lastRemainLen);remainingDatas.push_back(tuple<unique_ptr<char[]>, uint64_t>(move(lastRemainData), lastRemainLen));}}
}

(4)使用模拟的字节流数据做测试

std::vector<std::string> split(const std::string& str, const char delimiter)
{std::vector<std::string> tokens;std::istringstream tokenStream(str);std::string token;while (std::getline(tokenStream, token, delimiter)){tokens.push_back(token);}return tokens;
}tuple<unique_ptr<char[]>, size_t> getBytesFromStr(string str)
{vector<string> strs = split(str, ' ');if (strs.size() > 0){unique_ptr<char[]> bytes(new char[strs.size()]);for (size_t i = 0; i < strs.size(); i++){int val = strtol(strs[i].c_str(), nullptr, 16);bytes[i] = (char)val;}return tuple<unique_ptr<char[]>, size_t>(move(bytes), strs.size());}else{return tuple<unique_ptr<char[]>, size_t>(unique_ptr<char[]>(nullptr), 0);}
}int main() {// 模拟TCP接收数据  string str = "20";auto res = getBytesFromStr(str);revMsg((get<0>(res)).get(), get<1>(res));str = "04 02 00 00 01 20 04 02 00 00 02 20 04 02 00";res = getBytesFromStr(str);revMsg((get<0>(res)).get(), get<1>(res));str = "00 03 20 04 02 00 00 04 20 04 02 00";res = getBytesFromStr(str);revMsg((get<0>(res)).get(), get<1>(res));return 0;
}

上面代码的输出为:

[recive] 20 04 02 00 00 01
[recive] 20 04 02 00 00 02
[recive] 20 04 02 00 00 03
[recive] 20 04 02 00 00 04

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/2378.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode热题100】【多维动态规划】编辑距离

题目链接&#xff1a;72. 编辑距离 - 力扣&#xff08;LeetCode&#xff09; 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 你可以插入、删除、替换字符 定义dp[i][j]是将word1[0:i-1]转换成word2[0:j-1]所使用的最少操作数 如…

Json三方库介绍

目录 Json是干什么的Json序列化代码Json反序列化代码 Json是干什么的 Json是一种轻量级的数据交换格式&#xff0c;也叫做数据序列化方式。Json完全独立于编程语言的文本格式来存储和表述数据。易于人阅读和编写&#xff0c;同时也易于机器解析和生成&#xff0c;并有效地提升…

AcWing 797. 差分——算法基础课题解

AcWing 797. 差分 题目描述 输入一个长度为 n 的整数序列。 接下来输入 m 个操作&#xff0c;每个操作包含三个整数 l,r,c&#xff0c;表示将序列中 [l,r]之间的每个数加上 c。 请你输出进行完所有操作后的序列。 输入格式 第一行包含两个整数 n 和 m。 第二行包含 n 个…

【前后端】django与vue的结合使用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、前后端分离的简介二、django与vue的结合使用三、总结 前言 随着开发语言及人工智能工具的普及&#xff0c;使得越来越多的人会主动学习使用一些开发工具&a…

笔记:Python 列表与元组编程题(练习题)

文章目录 前言一、Python 列表与元组是什么&#xff1f;二、编程题总结 前言 欢迎阅读本编程练习题集&#xff0c;旨在帮助您加深对 Python 中列表&#xff08;List&#xff09;与元组&#xff08;Tuple&#xff09;的理解和应用。列表与元组是 Python 中常用的数据结构&#…

企业级DDoS防护与内网文件安全防护:全方位策略与技术实践

在数字化转型的浪潮中&#xff0c;企业面临着日益严峻的网络安全威胁&#xff0c;其中DDoS&#xff08;分布式拒绝服务&#xff09;攻击与内网文件防护是两个至关重要的议题。本文将深入探讨企业如何通过综合运用多种技术和策略&#xff0c;构建起强大的DDoS防护体系与内网文件…

YOLOv9训练结果分析->mAP、Precision、Recall、FPS、Confienc、混淆矩阵分析

简介 这篇博客&#xff0c;主要给大家讲解我们在训练yolov9时生成的结果文件中各个图片及其中指标的含义&#xff0c;帮助大家更深入的理解&#xff0c;以及我们在评估模型时和发表论文时主要关注的参数有那些。本文通过举例训练过程中的某一时间的结果来帮助大家理解&#xf…

vue项目启动npm install和npm run serve时出现错误Failed to resolve loader:node-sass

1.常见问题 问题1&#xff1a;当执行npm run serve时&#xff0c;出现Failed to resolve loader: node-sass&#xff0c;You may need to install it 解决方法&#xff1a; npm install node-sass4.14.1问题2&#xff1a;当执行npm run serve时&#xff0c;出现以下错误 Fa…

复杂算子onnx导出(2): 将graph写入到onnx

前文通过自定义导出实现了trace, 然后将输入输出节点连接起来就形成了graph, 接下来利用得到的graph写入到onnx文件中。通过onnx.helper就可以构建出一个onnx文件,并且保存下来。 1. onnx.helper 示例 onnx结构中,包括了nodes,initializer ,inputs,outputs,graph。然后指定…

水牛社靠谱吗,水牛社可以当做副业来做吗?

水牛社这个平台是否靠谱&#xff0c;能否作为副业的选择&#xff0c;一直是网友们热议的话题。实际上&#xff0c;水牛社是一个集合了众多网上赚钱活动任务和提供资源项目教程的综合性平台&#xff0c;它并非只局限于某一特定的项目&#xff0c;而是展现出多样化的特点。随着网…

SpringCloud系列(11)--将微服务注册进Eureka集群

前言&#xff1a;在上一章节中我们介绍并成功搭建了Eureka集群&#xff0c;本章节则介绍如何把微服务注册进Eureka集群&#xff0c;使服务达到高可用的目的 Eureka架构原理图 1、分别修改consumer-order80模块和provider-payment8001模块的application.yml文件&#xff0c;使这…

spring boot 基础案例【1】在页面上打印hellow world

1.在页面上打印hellow world 要在 Spring Boot 中打印 “Hello World” 并理解其代码&#xff0c;你可以创建一个简单的 Spring Boot 应用。这里&#xff0c;我将展示一个基本的示例并逐行解释代码的功能。 1.1 创建 Spring Boot 应用 首先&#xff0c;确保你已经安装了 Jav…

[vite] ts写配置根目录别名

参考:配置 Vite | Vite 写对象的形式吧 import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path// https://vitejs.dev/config/ export default defineConfig({plugins: [vue()],resolve: {alias: {"": path.resolve(__dirname…

MySQL的root用户无法远程连接

默认root用户只允许本地连接&#xff0c;所以需要修改mysql库中user表中名为root的用户的host为“%” select Host,User from user;UPDATE mysql.user SET host % WHERE user root; FLUSH PRIVILEGES;

JAVA:Kettle 强大的开源ETL工具

请关注微信公众号&#xff1a;拾荒的小海螺 1、简述 Kettle&#xff08;Pentaho Data Integration&#xff09;&#xff1a;强大的开源ETL工具Kettle&#xff0c;又称作Pentaho Data Integration&#xff0c;是一款流行的开源ETL&#xff08;Extract, Transform, Load&#x…

字符长、看不懂、费率飙升|Runes协议上线后发生了什么?

作者&#xff1a;比特里里 X/推&#xff1a;lilyanna_btc 1、字符数长了&#xff0c;单词都完整了&#xff0c;反而看不懂了 由于 Runes 协议的字符长度限制&#xff0c;大部分的票都在 13 个字符及以上&#xff0c;人名、域名、slogan&#xff0c;各类玩法都出来了。很多人适…

情感识别——情感计算的模型和数据集调查

概述 情感计算指的是识别人类情感、情绪和感觉的工作&#xff0c;已经成为语言学、社会学、心理学、计算机科学和生理学等领域大量研究的主题。 本文将概述情感计算的重要性&#xff0c;涵盖思想、概念和方法。 情感计算是皮卡德于 1997 年提出的一个想法&#xff0c;此后出…

线性模型算法

简介 本文章介绍机器学习中的线性模型有关内容&#xff0c;我将尽可能做到详细得介绍线性模型的所有相关内容 前置 什么是回归 回归的就是整合&#xff0b;预测 回归处理的问题可以预测&#xff1a; 预测房价 销售额的预测 设定贷款额度 可以根据事物的相关特征预测出对…

什么是关键字驱动测试?关键字驱动测试是如何实现的?

什么是关键字驱动测试&#xff1f; 关键字驱动测试 &#xff08;KDT&#xff09; 是测试自动化中的一种脚本技术&#xff0c;其中测试用例指令与实际测试脚本逻辑分开。它利用一组预定义的关键字来表示要在被测应用程序 &#xff08;AUT&#xff09; 上执行的操作。这些关键字…

spring boot的项目+nginx,怎么预防XSS攻击

在一个结合了Spring Boot和Nginx的项目架构中&#xff0c;防御跨站脚本攻击&#xff08;XSS&#xff09;需要在两个层面上进行综合防护&#xff1a;应用层&#xff08;Spring Boot应用&#xff09;和服务器层&#xff08;Nginx&#xff09;。这里是一些具体的策略和步骤&#x…