C++解决TCP粘包
目录
- TCP粘包问题
- TCP客户端
- TCP服务端
- 源码测试
TCP粘包问题
TCP是面向连接的,面向流的可靠性传输。TCP会将多个间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包发送,这样一个数据包里就可能含有多个消息的数据,面向流的通信是无消息保护边界的,也就是TCP粘包。接收端需要自己完成数据的拆包和组包,解决粘包问题。
要解决TCP粘包问题,就要给TCP定义公共包头,包头一般包括消息类型和消息大小,用包头来分割每个数据包,做数据包的边界。
下面分别用C++实现TCP客户端和TCP服务端,使用qt测试。
TCP客户端
TCP客户端主动连接到TCP服务端,并接收TCP服务端发送的数据,对接收的数据按照定义的公共包头进行分割组包,每当组成一个完整数据包时,打印相关信息。
TcpClient.h
#ifndef TCPCLIENT_H
#define TCPCLIENT_H#include <string.h>
#include <stdint.h>
#include <stdint.h>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <new>#define MAX_PKT_SIZE (256<<20) //网络包最大长度
//业务包头
struct CommMsgHdr
{uint16_t uMsgType;uint32_t uTotalLen;
};typedef struct _TcpHandle_{int32_t fd;uint32_t uRcvLen; //已接收数据大小uint32_t uAllLen; //消息总长度struct sockaddr_in local_addr;struct sockaddr_in remote_addr;_TcpHandle_(){uRcvLen = 0;uAllLen = 0;}
}TcpHandle;class TcpClient
{
public:TcpClient();int32_t create_tcpClient(char *serverIp, int32_t serverPort);int32_t SendData(char *data, int32_t len);bool m_runing;int epoll_fd;TcpHandle* pTcpHandle;
private:pthread_t threadId;
};#endif // TCPCLIENT_H
TcpClient.cpp
#include "TcpClient.h"int32_t TcpRcv(const int32_t& fd, void* buff, const uint32_t& len)
{int32_t iCurrRecv = recv(fd, buff, len, MSG_NOSIGNAL);if (0 < iCurrRecv) {return iCurrRecv;} else if (iCurrRecv < 0) {if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {return 0;} else return -1;} else return -1;
}void* DealTcpThread(void* obj)
{TcpClient* pTcpClient = (TcpClient*)obj;TcpHandle* pTcpHandle = pTcpClient->pTcpHandle;const int kEpollDefaultWait = 1;//超时时长,单位msstruct epoll_event alive_events[256];uint32_t recv_buffer_max = 1024 * 1024;uint8_t *recv_buffer = nullptr;recv_buffer = new uint8_t[recv_buffer_max];uint32_t head_len = (uint32_t)sizeof(CommMsgHdr);while (pTcpClient->m_runing){int num = epoll_wait(pTcpClient->epoll_fd, alive_events, 256, kEpollDefaultWait);for (int i = 0; i < num; ++i){int fd = alive_events[i].data.fd;int events = alive_events[i].events;if ( events & EPOLLIN ){//1.开始接收头部if(pTcpHandle->uRcvLen < head_len){int32_t iRecvLen = TcpRcv(fd, recv_buffer + pTcpHandle->uRcvLen, head_len - pTcpHandle->uRcvLen);if (0 == iRecvLen) continue;else if (0 > iRecvLen) {printf("Recv head data, return [%d] and err[%s],fd=[%d].", iRecvLen, strerror(errno),fd);close(fd);//关闭socketcontinue;}pTcpHandle->uRcvLen += iRecvLen;//如果已经接收完整头部if(pTcpHandle->uRcvLen >= head_len){CommMsgHdr* pHdr = (CommMsgHdr *)recv_buffer;pTcpHandle->uAllLen = pHdr->uTotalLen;//如果报文头里的uTotalLen太小或太大,异常处理if ( pHdr->uTotalLen < head_len || pHdr->uTotalLen > MAX_PKT_SIZE ){printf("uTotalLen invalid,uTotalLen=%u,fd=[%d]",pHdr->uTotalLen,fd);close(fd);//关闭socketcontinue;}//如果uTotalLen大于已分配的缓存,重新分配if (((CommMsgHdr *)recv_buffer)->uTotalLen > recv_buffer_max){uint8_t *new_recv_buffer = new uint8_t[((CommMsgHdr *)recv_buffer)->uTotalLen];memcpy(new_recv_buffer, recv_buffer,head_len);delete [] recv_buffer;// 释放原有空间recv_buffer = new_recv_buffer;// 重新指向新开辟的空间recv_buffer_max = ((CommMsgHdr *)recv_buffer)->uTotalLen;// 重新赋值最大buffer长度}}}//2.开始接收数据体else{int32_t iRecvLen = TcpRcv(fd, recv_buffer + pTcpHandle->uRcvLen, pTcpHandle->uAllLen - pTcpHandle->uRcvLen);if (0 == iRecvLen) continue;else if (0 > iRecvLen) {printf("Recv body data, return [%d] and err[%s],fd=[%d].", iRecvLen, strerror(errno),fd);close(fd);//关闭socketcontinue;}pTcpHandle->uRcvLen += iRecvLen;//完成接收if(pTcpHandle->uRcvLen == pTcpHandle->uAllLen){CommMsgHdr* pHdr = (CommMsgHdr*)recv_buffer;printf("Rcv completed,msgType=%d,uTotalLen=%u\n",pHdr->uMsgType,pHdr->uTotalLen);pTcpHandle->uRcvLen = 0;pTcpHandle->uAllLen = 0;}}}}}delete [] recv_buffer;recv_buffer = nullptr;return nullptr;
}TcpClient::TcpClient()
{pTcpHandle = new TcpHandle;epoll_fd = epoll_create(1);
}int32_t TcpClient::create_tcpClient(char *serverIp, int32_t serverPort)
{if (pTcpHandle == NULL) return -1;pTcpHandle->fd = -1;if((pTcpHandle->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0){printf("socket err=%s\n",strerror(errno));return -2;}pTcpHandle->remote_addr.sin_family = AF_INET;pTcpHandle->remote_addr.sin_port = htons(serverPort);pTcpHandle->remote_addr.sin_addr.s_addr = inet_addr(serverIp);if(connect(pTcpHandle->fd, (struct sockaddr *)&pTcpHandle->remote_addr, sizeof(pTcpHandle->remote_addr)) < 0){printf("connect err=%s\n",strerror(errno));return -3;}struct epoll_event evt;evt.events = EPOLLIN;fcntl(pTcpHandle->fd, F_SETFL, O_NONBLOCK);//设置非阻塞evt.data.fd = pTcpHandle->fd;epoll_ctl(epoll_fd,EPOLL_CTL_ADD,pTcpHandle->fd,&evt);m_runing = true;pthread_create(&threadId,NULL,DealTcpThread,this);return 0;
}int32_t TcpClient::SendData(char *data, int32_t len)
{int32_t ret = send(pTcpHandle->fd, data, len, MSG_NOSIGNAL);return ret;
}
TCP服务端
服务端启动监听,当有客户端接入时,向客户端循环发送大小不相等的数据包。
TcpServer.h
#ifndef TCPSERVER_H
#define TCPSERVER_H#include <string.h>
#include <stdint.h>
#include <stdint.h>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <new>#define MAX_PKT_SIZE (256<<20) //网络包最大长度
//业务包头
struct CommMsgHdr
{uint16_t uMsgType;uint32_t uTotalLen;
};typedef struct _TcpHandle_{int32_t fd;uint32_t uRcvLen; //已接收数据大小uint32_t uAllLen; //消息总长度struct sockaddr_in local_addr;struct sockaddr_in remote_addr;_TcpHandle_(){uRcvLen = 0;uAllLen = 0;}
}TcpHandle;class TcpServer
{
public:TcpServer();int32_t create_tcpServer(int32_t listenPort);bool m_runing;int epoll_fd;TcpHandle* pTcpSerHandle;
private:pthread_t threadId;
};#endif // TCPSERVER_H
TcpServer.cpp
#include "TcpServer.h"int SendLoop(int32_t fd, uint8_t * buff, uint32_t len) {uint64_t total_send_bytes = 0;int64_t curr_send_len = 0;uint64_t left_bytes = len;while(total_send_bytes < len) {curr_send_len = send(fd, buff + total_send_bytes, left_bytes, MSG_NOSIGNAL);if(curr_send_len < 0) {if( errno == EINTR || errno == EAGAIN)continue;return -1;} else {total_send_bytes += curr_send_len;left_bytes -= curr_send_len;}}return 0;
}void* DealTcpThread(void* obj)
{TcpServer* pTcpServer = (TcpServer*)obj;TcpHandle* pTcpSerHandle = (TcpHandle*)pTcpServer->pTcpSerHandle;socklen_t src_len = sizeof(struct sockaddr_in);while (pTcpServer->m_runing){struct sockaddr_in src;memset(&src, 0, src_len);int connfd = accept(pTcpSerHandle->fd, (struct sockaddr*) &src, &src_len);if(connfd > -1){//开始发送for(int index=0;index<100;index++){uint32_t dataLength = 1024*1024*16 + index*10;void *sendbuff = new char[dataLength];CommMsgHdr* pHead = (CommMsgHdr*)sendbuff;pHead->uMsgType = 1001;pHead->uTotalLen = dataLength;SendLoop(connfd,(uint8_t * )sendbuff,dataLength);}}}return nullptr;
}TcpServer::TcpServer()
{pTcpSerHandle = new TcpHandle;
}int32_t TcpServer::create_tcpServer(int32_t listenPort)
{pTcpSerHandle->fd = -1;pTcpSerHandle->local_addr.sin_family = AF_INET;pTcpSerHandle->local_addr.sin_port = htons(listenPort);pTcpSerHandle->local_addr.sin_addr.s_addr = INADDR_ANY;pTcpSerHandle->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);int opt = 1;setsockopt(pTcpSerHandle->fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//复用端口if (bind(pTcpSerHandle->fd, (struct sockaddr*) &pTcpSerHandle->local_addr,sizeof(struct sockaddr_in)) < 0){printf("http server bind error(%s)",strerror(errno));return -1;}listen(pTcpSerHandle->fd, 32);m_runing = true;pthread_create(&threadId,NULL,DealTcpThread,this);return 0;
}
源码测试
先启动服务端
TcpServer *pTcpServer;pTcpServer = new TcpServer;pTcpServer->create_tcpServer(9090);
再启动客户端
TcpClient* pTcpClient;pTcpClient = new TcpClient;pTcpClient->create_tcpClient("127.0.0.1",9090);
客户端打印
Rcv completed,msgType=1001,uTotalLen=16777216
Rcv completed,msgType=1001,uTotalLen=16777226
Rcv completed,msgType=1001,uTotalLen=16777236
Rcv completed,msgType=1001,uTotalLen=16777246
Rcv completed,msgType=1001,uTotalLen=16777256
Rcv completed,msgType=1001,uTotalLen=16777266
Rcv completed,msgType=1001,uTotalLen=16777276
Rcv completed,msgType=1001,uTotalLen=16777286
...
...
...
相关文章:
C++解决TCP粘包
目录 TCP粘包问题TCP客户端TCP服务端源码测试 TCP粘包问题 TCP是面向连接的,面向流的可靠性传输。TCP会将多个间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包发送,这样一个数据包里就可能含有多个消息的数据&#…...
最长快乐前缀——力扣1392
文章目录 题目描述KMP题目描述 KMP class Solution {public:string longestPrefix(string s) {int n = s...
使用java.util.List的containsAll()方法可能导致的问题
今天在偶然之间发现了一个bug,原因居然是使用了containsAll()方法,这个问题很简单,看以下代码就能发现很大的问题。 package collection;import java.util.ArrayList; import java.util.List;/*** author heyunlin* version 1.0*/ public cl…...
在线文本转语音播放 (TTS)
具体请前往:在线文本转语音播放(TTS)...
OPTEE之ARM安全扩展
目录 一、BTI(Branch Target Identification) 二、如何使能OP-TEE core的BTI 三、如何使能TA的BTI 一、BTI(Branch Target Identification) 分支目标识别(BTI)是ARMv8.5(及Armv9.0)扩展,它为间接分支及其目标提供了控制流完整性(CFI)保护,从而有助于限…...
Vue [Day4]
组件的三大组成部分 组件的样式冲突 scoped <style scoped></style>data 是一个函数 components/BaseButton.vue <template><div class"BaseButton"><button click"count--">-</button><span>{{ count }}</…...
google chrome 官方下载
官方渠道: 1、链接直接打开就可以下载,最新版实时更新。 32位(x86):https://dl.google.com/tag/s/installdataindex/update2/installers/ChromeStandaloneSetup.exe 64位(x64):htt…...
Misc取证学习
文章目录 Misc取证学习磁盘取证工具veracryto挂载fat文件DiskGenius 磁盘取证例题[RCTF2019]disk 磁盘[](https://ciphersaw.me/ctf-wiki/misc/disk-memory/introduction/#_2)内存取证工具volatility 内存取证例题数字取证赛题0x01.从内存中获取到用户admin的密码并且破解密码 …...
vue打包到jar资源访问被shiro拦截
1.shiro放发对静态资源的拦截// filterMap.put("/202307171550/**", "anon"); // filterMap.put("/config/**", "anon"); // filterMap.put("/index.html", "anon"); 2.装载资源访问 priva…...
选择排序(指针法)
描述 用选择法对10个整数排序。 输入 输入包含10个整数,用空格分隔。 输出 输出排序后的结果,用空格分隔。 输入样例 1 3 1 4 1 5 9 2 6 5 3 输出样例 1 1 1 2 3 3 4 5 5 6 9 输入样例 2 2 4 6 8 10 12 14 16 18 20 输出样例 2 2 4 6 8 1…...
8.6 day07 休息+剑指offer
文章目录 06从尾到头打印链表03数组中重复的数字04二维数组中的查找05 替换空格06重建二叉树背英语单词,看了二十页 06从尾到头打印链表 从尾到头遍历链表 方法一就是用栈来辅助,栈的结构是先进后出的,将链表中的元素加入到栈中去࿰…...
K8S系列文章 之 容器存储基础 Volume
Volume Volume是容器数据卷。我们经常创建删除一些容器,但有时候需要保留容器中的一些数据,这时候就用到了Volume。它也是容器之间数据共享的技术,可以将容器中产生的数据同步到本地。实际就是把容器中的目录挂载到运行着容器的服务器或个人…...
【CHI】架构介绍
Learn the architecture - Introducing AMBA CHI AMBA CHI协议导论--言身寸 1. AMBA CHI简介 一致性集线器接口(CHI)是AXI一致性扩展(ACE)协议的演进。它是Arm提供的高级微控制器总线架构(AMBA)的一部分。…...
hcip的ospf综合实验
题目 拓扑图 IP地址分配及环回 R1 < Huawei>sy Enter system view, return user view with CtrlZ. [Huawei]sysname r1 [r1]int g0/0/0 [r1-GigabitEthernet0/0/0]ip add 172.16.0.1 27 Aug 6 2023 19:03:33-08:00 r1 %%01IFNET/4/LINK_STATE(l)[0]:The line protocol I…...
AP5179 高端电流采样降压恒流驱动IC SOP8 LED车灯电源驱动
产品描述 AP5179是一款连续电感电流导通模式的降压恒流源,用于驱动一颗或多颗串联LED输入电压范围从 5 V 到 60V,输出电流 最大可达 2.0A 。根据不同的输入电压和外部器件, 可以驱动高达数十瓦的 LED。内置功率开关,采用高端电流…...
vue3+vite项目配置ESlint、pritter插件
配置ESlint、pritter插件 在 Vue 3 Vite 项目中,你可以通过以下步骤配置 ESLint 和 Prettier 插件: 安装插件: 在项目根目录下,打开终端并执行以下命令安装 ESLint 和 Prettier 插件: npm install eslint prettier e…...
K8S kubeadm搭建
kubeadm搭建整体步骤 1)所有节点进行初始化,安装docker引擎和kubeadm kubelet kubectl 2)生成集群初始化配置文件并进行修改 3)使用kubeadm init根据初始化配置文件生成K8S的master控制管理节点 4)安装CNI网络插件&am…...
Squeeze-and-Excitation Networks阅读笔记一
文章目录 Abstract1 INTRODUCTION Abstract 卷积算子(convolution operator)是卷积神经网络(cnn)的核心组成部分,它使网络能够通过融合每层局部接受域内的空间和通道信息来构建信息特征。广泛的先前研究已经调查了这种…...
LabVIEW开发3D颈动脉图像边缘检测
LabVIEW开发3D颈动脉图像边缘检测 近年来,超声图像在医学领域对疾病诊断具有重要意义。边缘检测是图像处理技术的重要组成部分。边缘包含图像信息。边缘检测的主要目的是根据强度和纹理等属性识别图像中均匀区域的边界。超声(US)图像存在视觉…...
python10.4.3
10.4.3重构 错误实例 import jsondef laoyonghu(): #获取老用户名字filenameusername.jsonwith open(filename) as f:usernamejson.load(f)return usernamedef xinyonghu(): #获取新用户名字filenameusername.jsonusernameinput("whats your name:")with ope…...
【Java低代码组件调试黄金法则】:20年架构师亲授5大高频故障定位技巧,90%开发者从未听说
第一章:Java低代码组件调试的本质与认知跃迁Java低代码平台并非屏蔽复杂性,而是将复杂性重新封装、可视化与可追溯化。调试低代码组件的本质,是穿透表层拖拽逻辑,定位其背后生成的Java字节码、Spring Bean生命周期行为、以及运行时…...
第二桌面 + 小龙虾:让企业AI智能体安全落地、全员可用
本文发布于2026年4月1日。引言:从“养虾”到“用虾”,AI落地需要新底座过去几个月,OpenClaw(昵称“小龙虾”)在开发者圈子里火得一塌糊涂。这个开源AI智能体网关,能听懂人话,还能替你操作电脑、…...
【限时解禁】Cuvil编译器v0.9.3内部架构设计图(含Python动态类型静态化映射表),仅开放72小时
第一章:Cuvil 编译器在 Python AI 推理中的应用Cuvil 是一款面向 AI 工作负载的轻量级领域专用编译器,专为优化 Python 生态中基于 PyTorch 和 ONNX 的模型推理而设计。它通过静态图重写、算子融合与硬件感知调度,在不修改用户代码的前提下&a…...
Cocos Creator 屏幕适配实战:从设计分辨率到完美适配的完整指南
1. 理解屏幕适配的核心概念 第一次用Cocos Creator做横屏游戏时,我盯着iPad和手机上完全变形的UI界面愣了半天。这才明白为什么老司机们总说:"屏幕适配不做,上线火葬场"。屏幕适配的本质是解决设计分辨率(美术产出资源时…...
Qwen3-14B开源模型实战:跨境电商多平台产品文案批量生成
Qwen3-14B开源模型实战:跨境电商多平台产品文案批量生成 1. 跨境电商文案生成的痛点与解决方案 跨境电商运营面临的最大挑战之一,就是需要为同一款产品在不同平台(亚马逊、eBay、速卖通等)生成符合各自规范的优质文案。传统人工…...
Pixel Epic智识终端效果展示:跨领域研报生成一致性与专业性验证
Pixel Epic智识终端效果展示:跨领域研报生成一致性与专业性验证 1. 产品概览与核心价值 Pixel Epic智识终端是一款基于AgentCPM-Report大模型构建的专业研究报告生成工具。与传统AI工具不同,它创新性地采用了像素RPG游戏的美学设计,将枯燥的…...
基于RK3506与LVGUI的CyberGear电机交互式控制台开发实践
1. 从零搭建CyberGear电机控制环境 第一次拿到RK3506开发板和小米CyberGear电机时,我花了整整两天时间才把基础环境搭好。这里分享几个关键步骤,帮你避开我踩过的坑。 硬件连接部分要注意XT30PB插头的防呆设计,插反了会烧毁接口。建议先用万用…...
嵌入式开发必备:三大代码对比工具深度评测
1. 代码对比工具概述作为一名嵌入式开发工程师,我每天都要处理大量的代码修改和版本对比工作。在多年的开发实践中,我发现选择合适的代码对比工具能极大提升工作效率。虽然Beyond Compare是业内公认的标杆产品,但实际工作中我们还有更多选择&…...
管道应力理论(应用)
本文仅对管道应力涉及的理论知识(偏向于应用)进行简单介绍。管道应力:对管道应力校核是为了防止管壁内应力过大对管道造成破坏,不同的荷载引起不同类型的应力,在实际工程应用中,一般分为三种:一…...
10分钟掌握Deep-Live-Cam:从零搭建实时AI换脸系统的完整指南
10分钟掌握Deep-Live-Cam:从零搭建实时AI换脸系统的完整指南 【免费下载链接】Deep-Live-Cam real time face swap and one-click video deepfake with only a single image 项目地址: https://gitcode.com/GitHub_Trending/de/Deep-Live-Cam Deep-Live-Cam是…...
