C++解决TCP粘包

2023-11-11

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;//超时时长,单位ms
    struct 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);//关闭socket
                        continue;
                    }

                    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);//关闭socket
                            continue;
                        }

                        //如果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);//关闭socket
                        continue;
                    }

                    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
...
...
...
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++解决TCP粘包 的相关文章

随机推荐

  • “百钱买百鸡”编程详解。

    1 题目描述 百钱买百鸡 是我国古代的著名数学题 3 文钱可以买 1 只公鸡 2 文钱可以买一只母鸡 1 文钱可以买 3 只小鸡 用 100 文钱买100 只鸡 那么各有公鸡 母鸡 小鸡多少只 2 分析 计算机无法通过直接的计算得出具体的数
  • QTimer与事件循环和多线程

    定时器的源码分析 startTimer返回定时器的ID 在定时时间到了后 收到一个QTimerEvent 并覆盖虚函数timerEvent进行处理 该QTimerEvent包括了定时器ID 看QTimer的源码就明白了 QObject st
  • java创建自定义类的数组

    java创建自定义类的数组 错题笔记 学习动态规划做例题hdu 2602遇到的问题 创建自定义类后 新建一个自定义类的数组 向数组赋值时报如下错误 java lang NullPointerException Cannot assign f
  • 【Qt】一篇全面的信号和槽函数机制总结

    信号和槽函数机制 文章目录 信号和槽函数机制 一 信号和槽机制简介 二 信号 2 1 信号的发出 2 2 信号的处理 三 槽函数 3 1 带有默认参数的信号和槽函数 3 2 使用 QObject connect 将信号连接到槽函数的三种方法
  • integer operation result is out of range

    程序中 有如下定义 define UART1 EN 1 lt lt 31 编译后编译器报错 integer operation result is out of range 经查资料是由于溢出所致 宏定义默认常量是有符号型 当左移31位时
  • kerberos认证过程

    KDC Key Distribution Center 密钥分发中心 里面包含两个服务 AS和TGS AS Authentication Server 身份认证服务 TGS Ticket Granting Server 票据授予服务 TGT
  • [python] 使用scikit-learn工具计算文本TF-IDF值

    在文本聚类 文本分类或者比较两个文档相似程度过程中 可能会涉及到TF IDF值的计算 这里主要讲述基于Python的机器学习模块和开源工具 scikit learn 希望文章对你有所帮助 相关文章如下 python爬虫 Selenium获取
  • 铨顺宏RFID:试卷管理中RFID技术智能系统发挥着什么样的作用

    1 项目背景 在我国的教育招生考试中 试卷的管理一直是比较棘手的问题 它涉及试卷的组卷 印刷 封包 运输 发放 回收 入库 阅卷以及历史保存等一系列复杂的流程 且数量巨大 到目前为止 我国的试卷管理主要还是采取的人工管理方式 势必存在安全形
  • 随机抽奖小程序

    本实例使用随机数字生成5位抽奖号码 并显示在窗体的5个文本框中 当用户单击 开始 按钮时 将启动一个线程对象为5个文本框生成随机数字 单击 抽奖 按钮时 线程对象停止运行 并且将准确的中奖号码显示在信息文本框中 开发一个抽奖小工具的实例 1
  • 基于PaddleClas的PP-LCNet实现车辆颜色及车型属性识别

    目录 源码 yolov5源码 1 环境准备 2 数据准备 3 车辆检测
  • 目标检测评价指标合集

    目标检测评价指标 混淆矩阵 confusion matrix 可谓是贯穿了整个目标检测评价体系 衍生了一系列的目标检测评价指标 如精确率 precision 准确率 accuracy 召回率 recall F1 score ROC AUC指
  • ag-grid 学习笔记四:ag-grid方法(重设行数据、增删改、反选、新增列、插入新行、合计行接口、遍历行对象、获取置顶行数量、获取底部合计行对象、获取行对象、刷新、单元格焦点)

    一 setRowData重新设置表格行数据 重新设置表格数据很简单 只需要调用 gridOptions api setRowData 数据集 接口传入数据即可 以下函数为调用方式 function resetGrid 新的数据项 var N
  • Hackinglab(鹰眼)——基础关

    目录 1 key在哪里 2 再加密一次你就得到key啦 3 猜猜这是经过了多少次加密 4 据说MD5加密很安全 真的是么 5 种族歧视 6 HAHA浏览器 7 key究竟在哪里呢 8 key又找不到了 9 冒充登陆用户 10 比较数字大小
  • Lucene使用IK中文分词

    Lucene使用IK中文分词 环境 Lucene 6 x IKAnalyzer2012 u6 也可以通过Maven或Gradle构建工程测试和验证 对于Lucene的最新版本 需要找到IK Analyzer对应的兼容版 传送门 Lucene
  • 最新gcc下载和linux环境变量设置

    最新gcc下载和linux环境变量设置 一 gcc下载 提供最新gcc下载路径 点这里跳转 http ftp gnu org gnu gcc 安装 gcc package configure prefix usr local gcc nam
  • 前端使用代理跨域后后端无法接收Session

    将一个 MVC 项目重构为一个前后端分离项目 前端使用了 react axios vite 在前后端分离项目中 通常都会使用代理来解决跨域问题 vite 需要在 vite config js 文件中配置代理 export default d
  • hive的分组和组内排序

    背景 hive的分组和组内排序 语法 语法 row number over partition by 字段a order by 计算项b desc rank rank是排序的别名 partition by 用于给结果集分组 如果没有指定那么
  • ES内存持续增长问题分析

    环境介绍 es版本 5 6 4 Xms31g Xmx31g XX MaxDirectMemorySize 10g 问题说明 用top命令观察ES使用的物理内存不断增加到54 6G 已知堆内存31G 堆外内存MaxDirectMemorySi
  • one-class(单分类) kNN(K-Nearest Neighbor)算法Matlab实现

    one class 单分类 kNN K Nearest Neighbor 算法Matlab实现 本文的核心是给出了一个基于kNN的单分类 one class 分类器实现代码 并给出了数据以及运行实例 让读者能更好地理解并使用 代码基于MAT
  • C++解决TCP粘包

    目录 TCP粘包问题 TCP客户端 TCP服务端 源码测试 TCP粘包问题 TCP是面向连接的 面向流的可靠性传输 TCP会将多个间隔较小且数据量小的数据 合并成一个大的数据块 然后进行封包发送 这样一个数据包里就可能含有多个消息的数据 面