linux下socket编程处理TCP粘包

2023-10-31

一、 数据接收时会出现以下几种情况

  • 一次接收到了客户端发送过来的一个完整的数据包
  • 一次接收到了客户端发送过来的 N 个数据包,由于每个包的长度不定,无法将各个数据包拆开
  • 一次接收到了一个或者 N 个数据包 + 下一个数据包的一部分,还是很悲剧,无法将数据包拆开
  • 一次收到了半个数据包,下一次接收数据的时候收到了剩下的一部分 + 下个数据包的一部分,更悲剧,头大了
  • 另外,还有一些不可抗拒的因素:比如客户端和服务器端的网速不一样,发送和接收的数据量也会不一致

二、几种解决方案

  • 使用标准的应用层协议(比如:http、https)来封装要传输的不定长的数据包
  • 在每条数据的尾部添加特殊字符,如果遇到特殊字符,代表当条数据接收完毕了
    有缺陷:效率低,需要一个字节一个字节接收,接收一个字节判断一次,判断是不是那个特殊字符串
  • 在发送数据块之前,在数据块最前边添加一个固定大小的数据头,这时候数据由两部分组成:数据头 + 数据块
    数据头:存储当前数据包的总字节数,接收端先接收数据头,然后在根据数据头接收对应大小的字节
    数据块:当前数据包的内容

三、数据头 + 数据块解决方案

如果使用 TCP 进行套接字通信,如果发送的数据包粘连到一起导致接收端无法解析,我们通常使用添加包头的方式轻松地解决掉这个问题。关于数据包的包头大小可以根据自己的实际需求进行设定,这里没有啥特殊需求,因此规定包头的固定大小为4个字节,用于存储当前数据块的总字节数。
在这里插入图片描述

3.1发送端

对于发送端来说,数据的发送分为 4 步:
1、根据待发送的数据长度 N 动态申请一块固定大小的内存:N+4(4 是包头占用的字节数)
2、将待发送数据的总长度写入申请的内存的前四个字节中,此处需要将其转换为网络字节序(大端)
3、将待发送的数据拷贝到包头后边的地址空间中,将完整的数据包发送出去(字符串没有字节序问题)
4、释放申请的堆内存。

由于发送端每次都需要将这个数据包完整的发送出去,因此可以设计一个发送函数,如果当前数据包中的数据没有发送完就让它一直发送,处理代码如下:

/*
函数描述: 发送指定的字节数
函数参数:
    - fd: 通信的文件描述符(套接字)
    - msg: 待发送的原始数据
    - size: 待发送的原始数据的总字节数
函数返回值: 函数调用成功返回发送的字节数, 发送失败返回-1
*/
int writen(int fd, const char* msg, int size)
{
    const char* buf = msg;
    int count = size;
    while (count > 0)
    {
        int len = send(fd, buf, count, 0);
        if (len == -1)
        {
            close(fd);
            return -1;
        }
        else if (len == 0)
        {
            continue;
        }
        buf += len;
        count -= len;
    }
    return size;
}

有了这个功能函数之后就可以发送带有包头的数据块了,具体处理动作如下:

/*
函数描述: 发送带有数据头的数据包
函数参数:
    - cfd: 通信的文件描述符(套接字)
    - msg: 待发送的原始数据
    - len: 待发送的原始数据的总字节数
函数返回值: 函数调用成功返回发送的字节数, 发送失败返回-1
*/
int sendMsg(int cfd, char* msg, int len)
{
   if(msg == NULL || len <= 0 || cfd <=0)
   {
       return -1;
   }
   // 申请内存空间: 数据长度 + 包头4字节(存储数据长度)
   char* data = (char*)malloc(len+4);
   int bigLen = htonl(len); 
   memcpy(data, &bigLen, 4);  //将数据长度放入data段的前4个字节,这代表后面将要跟随的数据长度
   memcpy(data+4, msg, len);  //将数据放入4字节长度的后面
   // 发送数据
   int ret = writen(cfd, data, len+4);  //将数据发送出去
   // 释放内存
   free(data);
   return ret;
}

3.2 接收端

了解了套接字的发送端如何发送数据,接收端的处理步骤也就清晰了,具体过程如下:

1、首先接收 4 字节数据,并将其从网络字节序转换为主机字节序,这样就得到了即将要接收的数据的总长度
2、根据得到的长度申请固定大小的堆内存,用于存储待接收的数据
3、根据得到的数据块长度接收固定数目的数据保存到申请的堆内存中
4、处理接收的数据
5、释放存储数据的堆内存

从数据包头解析出要接收的数据长度之后,还需要将这个数据块完整的接收到本地才能进行后续的数据处理,因此需要编写一个接收数据的功能函数,保证能够得到一个完整的数据包数据,处理函数实现如下:

/*
函数描述: 接收指定的字节数
函数参数:
    - fd: 通信的文件描述符(套接字)
    - buf: 存储待接收数据的内存的起始地址
    - size: 指定要接收的字节数
函数返回值: 函数调用成功返回发送的字节数, 发送失败返回-1
*/
int readn(int fd, char* buf, int size)
{
    char* pt = buf;
    int count = size;
    while (count > 0)
    {
        int len = recv(fd, pt, count, 0);
        if (len == -1)
        {
            return -1;
        }
        else if (len == 0)
        {
            return size - count;
        }
        pt += len;
        count -= len;
    }
    return size;
}

这个函数搞定之后,就可以轻松地接收带包头的数据块了,接收函数实现如下:

/*
函数描述: 接收带数据头的数据包
函数参数:
    - cfd: 通信的文件描述符(套接字)
    - msg: 一级指针的地址,函数内部会给这个指针分配内存,用于存储待接收的数据,这块内存需要使用者释放
函数返回值: 函数调用成功返回接收的字节数, 发送失败返回-1
*/
int recvMsg(int cfd, char** msg)
{
    // 接收数据
    // 1. 读数据头
    int len = 0;
    readn(cfd, (char*)&len, 4);  //先读出代表数据长度的前4个字节
    len = ntohl(len);
    printf("数据块大小: %d\n", len);

    // 根据读出的长度分配内存,+1 -> 这个字节存储\0
    char *buf = (char*)malloc(len+1);
    int ret = readn(cfd, buf, len);  //再读出真正长度的数据
    if(ret != len)
    {
        close(cfd);
        free(buf);
        return -1;
    }
    buf[len] = '\0';
    *msg = buf;

    return ret;
}

这样,在进行套接字通信的时候通过调用封装的 sendMsg() 和 recvMsg() 就可以发送和接收带数据头的数据包了,而且完美地解决了粘包的问题。

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

linux下socket编程处理TCP粘包 的相关文章

随机推荐

  • 每日一练 C++ 熊孩子拜访

    本题较为简单 因为只有一段子序列被倒序了 故只需要确定子序列的头和尾就完全决定了子序列 而在整段序列中 余子序列满足递增 子序列内部满足递减 尾部与正常序列不满足递减 根据以上规律 先通过递增筛查 首个不满足递增的位置就是子序列的头部 然后
  • seaborn绘制箱线图和折线图

    利用seaborn绘制箱线图和折线图 均值连线 过程出现的问题 1 问题1 参考python seaborn 共享x轴画图 数据可视化对代码进行修改 画图部分代码 fig plt figure figsize 18 6 ax1 fig ad
  • android 隐藏系统键盘

    http blog sina com cn s blog 87479ba60101akfh html 已验证 public static void closeBoard Context mcontext InputMethodManager
  • Caused by: java.io.NotSerializableException:

    详细报错信息如下 This application has no explicit mapping for error so you are seeing this as a fallback Mon Oct 18 10 44 53 CST
  • 隐私计算分类

    在大数据时代中 海量的数据的交叉计算和人工智能的发展为各行各业提供了更好的支持 但这些被使用的数据往往包含用户的隐私数据 或企业 机构的内部数据 这些数据由于数据安全和隐私的考虑 往往是不对外开发 例如政府数据由于政策保密性完全不能对外公布
  • 【H.264/AVC视频编解码技术详解】二十、H.264的去块滤波算法

    H 264 AVC视频编解码技术详解 视频教程已经在 CSDN学院 上线 视频中详述了H 264的背景 标准协议和实现 并通过一个实战工程的形式对H 264的标准进行解析和实现 欢迎观看 纸上得来终觉浅 绝知此事要躬行 只有自己按照标准文档
  • 两种办法解决 make: Warning: File “xxx“ has modification time yyy s in the future 的问题

    一 引言 最近在工作中 在本地将代码文件上传到远端服务器 在远端服务器进行 make 编译的时候 会报这样的错 make Warning File xxx has modification time yyy s in the future
  • 2021年7月19日--7月25日(调试Osgearth33+抄写osg/osgearth源码,共20小时,合计829小时,剩9171小时)

    继续按计划进行 其他随意 完成情况 除抄写osg osgearth源码最后计算外 周一 1 整合gis引擎 1小时 2 网络视频教程1小时 合计2小时 周二 18 40 19 20 osgEarth33调试一节 40分钟 19 36 20
  • 人生,天命,自己

    前提摘要 我现在刚刚毕业 正在找工作 疯狂投简历 目前还没有消息 但是我最近有一个想法 在这个想法之上正在研究一个关于文档关联的新东西 简单来说就是利用相关性算法 然后 使用py代码来实现文档关联 为什么我会公开说出我的研究 或者是我发表关
  • [C++]高效使用c++11--理解auto类型推导

    推导类型 1 理解类型推导 auto的推导方式和template是一样的 所以我们首先来介绍template是如何推导类型的 template
  • pip:python -m pip install --upgrade pip 解决办法

    在使用 pip 安装 Flask 的时候 命令行报错 You are using pip version 9 0 1 however version 21 2 4 is available You should consider upgra
  • Qt - 高级网络操作 HTTP/FTP

    欢迎转载 请注明出处 https blog csdn net qq 39453936 spm 1010 2135 3001 5343 原文链接 https blog csdn net qq 39453936 article details
  • Redis的5大类型

    Redis的5大类型 Redis是单进程 单线程 单实例并发很多的请求 如何变得很快的呢 Redis默认有16个库 redis cli raw进行当前编码的匹配 底层是按照字节存储的 二进制安全 Redis的5大类型 1 String 包含
  • 再见Xshell,这款免费开源的终端工具真香~

    作为一名后端开发 在日常工作中肯定是要和服务器打交道的 自然也就需要使用终端工具 在 Windows 系统的电脑上我一直是使用 Xshell 以前还挺好用的 后面这款工具竟然把 ftp 功能给剥离出去了 单独搞了个 Xftp 这让我使用起来
  • 4.1-支持向量机

    文章目录 一 铰链损失 Hinge loss 二 核方法 Kernel Method 2 1 径向基函数核 Radial Basis Function Kernel 2 2 Sigmoid Kernel 三 支持向量机相关方法 SVM re
  • PyTorch学习日志_20201030_ Autograd 包

    日期 2020 10 30 主题 PyTorch入门 内容 根据PyTorch官方教程文档 学习PyTorch中所有神经网络的核心 Autograd 包的基础操作 主要与张量相关 根据自己的理解和试验 为代码添加少量注解 具体代码如下 fr
  • TCP往返传输时间(RTT)的估计

    TCP往返传输时间 RTT 的估计1 TCP传输往返时间是指发送端从发送TCP包开始到接收到它的立即响应所耗费的传输时间 当接收端和发送端同时支持TCP时戳选项时 发送端记录在TCP包头选项内的时戳可以被接收端随响应反射回来 发送端就可以利
  • Windows下OMNET++的安装和各种架构调试心得

    以下所述的为windows平台下OMNET 集成在MSVC6 0环境下的使用方法 一 OMNET的安装 1 到OMNET官方网站下载windows平台下的安装程序 当前版本为omnetpp 3 2p1 win32 下载Ghostscript
  • smart检测指标详解

    一 SMART概述 要说Linux用户最不愿意看到的事情 莫过于在毫无警告的情况下发现硬盘崩溃了 诸如RAID的备份和存储技术可以在任何时候帮用户恢复数据 但为预防硬件崩溃造成数据丢失所花费的代价却是相当可观的 特别是在用户从来没有提前考虑
  • linux下socket编程处理TCP粘包

    一 数据接收时会出现以下几种情况 一次接收到了客户端发送过来的一个完整的数据包 一次接收到了客户端发送过来的 N 个数据包 由于每个包的长度不定 无法将各个数据包拆开 一次接收到了一个或者 N 个数据包 下一个数据包的一部分 还是很悲剧 无