当前位置: 首页 > news >正文

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是面向连接的&#xff0c;面向流的可靠性传输。TCP会将多个间隔较小且数据量小的数据&#xff0c;合并成一个大的数据块&#xff0c;然后进行封包发送&#xff0c;这样一个数据包里就可能含有多个消息的数据&#…...

最长快乐前缀——力扣1392

文章目录 题目描述KMP题目描述 KMP class Solution {public:string longestPrefix(string s) {int n = s...

使用java.util.List的containsAll()方法可能导致的问题

今天在偶然之间发现了一个bug&#xff0c;原因居然是使用了containsAll()方法&#xff0c;这个问题很简单&#xff0c;看以下代码就能发现很大的问题。 package collection;import java.util.ArrayList; import java.util.List;/*** author heyunlin* version 1.0*/ public cl…...

在线文本转语音播放 (TTS)

具体请前往&#xff1a;在线文本转语音播放(TTS)...

OPTEE之ARM安全扩展

目录 一、BTI(Branch Target Identification) 二、如何使能OP-TEE core的BTI 三、如何使能TA的BTI 一、BTI(Branch Target Identification) 分支目标识别(BTI)是ARMv8.5(及Armv9.0)扩展&#xff0c;它为间接分支及其目标提供了控制流完整性(CFI)保护&#xff0c;从而有助于限…...

Vue [Day4]

组件的三大组成部分 组件的样式冲突 scoped <style scoped></style>data 是一个函数 components/BaseButton.vue <template><div class"BaseButton"><button click"count--">-</button><span>{{ count }}</…...

google chrome 官方下载

官方渠道&#xff1a; 1、链接直接打开就可以下载&#xff0c;最新版实时更新。 32位&#xff08;x86&#xff09;&#xff1a;https://dl.google.com/tag/s/installdataindex/update2/installers/ChromeStandaloneSetup.exe 64位&#xff08;x64&#xff09;&#xff1a;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个整数&#xff0c;用空格分隔。 输出 输出排序后的结果&#xff0c;用空格分隔。 输入样例 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重建二叉树背英语单词&#xff0c;看了二十页 06从尾到头打印链表 从尾到头遍历链表 方法一就是用栈来辅助&#xff0c;栈的结构是先进后出的&#xff0c;将链表中的元素加入到栈中去&#xff0…...

K8S系列文章 之 容器存储基础 Volume

Volume Volume是容器数据卷。我们经常创建删除一些容器&#xff0c;但有时候需要保留容器中的一些数据&#xff0c;这时候就用到了Volume。它也是容器之间数据共享的技术&#xff0c;可以将容器中产生的数据同步到本地。实际就是把容器中的目录挂载到运行着容器的服务器或个人…...

【CHI】架构介绍

Learn the architecture - Introducing AMBA CHI AMBA CHI协议导论--言身寸 1. AMBA CHI简介 一致性集线器接口&#xff08;CHI&#xff09;是AXI一致性扩展&#xff08;ACE&#xff09;协议的演进。它是Arm提供的高级微控制器总线架构&#xff08;AMBA&#xff09;的一部分。…...

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是一款连续电感电流导通模式的降压恒流源&#xff0c;用于驱动一颗或多颗串联LED输入电压范围从 5 V 到 60V&#xff0c;输出电流 最大可达 2.0A 。根据不同的输入电压和外部器件&#xff0c; 可以驱动高达数十瓦的 LED。内置功率开关&#xff0c;采用高端电流…...

vue3+vite项目配置ESlint、pritter插件

配置ESlint、pritter插件 在 Vue 3 Vite 项目中&#xff0c;你可以通过以下步骤配置 ESLint 和 Prettier 插件&#xff1a; 安装插件&#xff1a; 在项目根目录下&#xff0c;打开终端并执行以下命令安装 ESLint 和 Prettier 插件&#xff1a; npm install eslint prettier e…...

K8S kubeadm搭建

kubeadm搭建整体步骤 1&#xff09;所有节点进行初始化&#xff0c;安装docker引擎和kubeadm kubelet kubectl 2&#xff09;生成集群初始化配置文件并进行修改 3&#xff09;使用kubeadm init根据初始化配置文件生成K8S的master控制管理节点 4&#xff09;安装CNI网络插件&am…...

Squeeze-and-Excitation Networks阅读笔记一

文章目录 Abstract1 INTRODUCTION Abstract 卷积算子&#xff08;convolution operator&#xff09;是卷积神经网络&#xff08;cnn&#xff09;的核心组成部分&#xff0c;它使网络能够通过融合每层局部接受域内的空间和通道信息来构建信息特征。广泛的先前研究已经调查了这种…...

LabVIEW开发3D颈动脉图像边缘检测

LabVIEW开发3D颈动脉图像边缘检测 近年来&#xff0c;超声图像在医学领域对疾病诊断具有重要意义。边缘检测是图像处理技术的重要组成部分。边缘包含图像信息。边缘检测的主要目的是根据强度和纹理等属性识别图像中均匀区域的边界。超声&#xff08;US&#xff09;图像存在视觉…...

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 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...