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

  • 一次接收到了客户端发送过来的一个完整的数据包
  • 一次接收到了客户端发送过来的 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 -> 这个字节存储\0char *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() 就可以发送和接收带数据头的数据包了,而且完美地解决了粘包的问题。

linux下socket编程处理TCP粘包相关推荐

  1. Linux下socket编程:TCP连接

    目录 一.流程简述     服务器端     客户端 二.概念及函数     1.套接字描述符     2.socket函数     3.bind函数     4.connect函数     5.li ...

  2. 一文了解linux下socket编程

    一文了解linux下socket编程 文章目录 一文了解linux下socket编程 1 网络编程的相关简述 1.1 引言 1.2 Tcp和Udp简介 1.3 TCP三次握手和四次挥手 1.4 网络编 ...

  3. LINUX下Socket编程 函数格式详解

    你需要了解的一些系统调用: socket() bind() connect() listen() accept() send() recv() sendto() recvfrom() close() ...

  4. Linux下Socket编程

    Linux下Socket编程    网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符.Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的S ...

  5. Linux下Socket编程之TCP应用

    现在,我们用前面所构建的socket类,重新设计<Linux下Socket编程之TCP Server端>中echo的服务器,然后设计客户端程序. echo服务器的工作原理很简单: 1.接收 ...

  6. linux下socket编程读写函数

    linux下socket编程,实现服务器与客户端的通信之后,在同一个虚拟机上,打开两个shell,一个运行服务器程序,一个运行客户端程序,课相互发送数据. 如果使用的是recv接收函数,当关闭客户端或 ...

  7. [zz]Linux 下 socket 编程示例

    本示例为 Client/Server 结构,通过代码演示 Client 如何建立连接,并向远程端发送数据:Server 端如何侦听系统连接请求,接收请求并建立连接,进而获取客户端发来的数据.代码虽短, ...

  8. linux下socket编程中setsockopt的作用

    如题所示,在linux进行socket编程的时候,一般而言,socket,bind,listen三步曲之后,就开始接收客户端请求,然后实现收发数据. 如下所示的代码,是没有setsockopt的情况: ...

  9. Linux下Socket编程之TCP Server端

    一.建模 绝大部分关于socket编程的教程总是从socket的概念开始讲起的.要知道,socket的初衷是个庞大的体系,TCP/IP只是这个庞大体系下一个很小的子集,而我们真正能用上的更是这个子集中 ...

最新文章

  1. jquery之stop()的用法
  2. 进击的docker 二 : docker 快速入门
  3. bootstrap学习(四)输入框、导航
  4. 玩转ECS第7讲 | ECS数据保护-数据备份新特性与最佳实践
  5. Python几种加密算法
  6. express利用nodemailer发送邮件(163邮箱)
  7. 项目入口_新进展!石家庄地铁项目长安公园站出入口全部封顶
  8. 数据结构c java_Java - 数据结构
  9. 【转】SpringMVC Controller 介绍
  10. 基于OpenSSL的HTTPS通信C++实现
  11. 计算机修改桌面图标大小,windows更改桌面图标大小设置
  12. Centos7下安装MPlay和VLC
  13. 文件包含漏洞+php伪协议
  14. 如何将MathType公式编辑器内嵌到WPS工具栏中
  15. CUE 文件及其格式说明
  16. 逐鹿战神--ios技术支持
  17. 问题:MongoDB C# driver异常:Truncation resulted in data loss
  18. 企业征信报告的查询内容有哪些?
  19. Win11-GTX3060-配置Pytorch GPU
  20. 【云原生】-Docker快速搭建免费的WordPress博客平台

热门文章

  1. 为什么大型网站前端使用PHP,后台逻辑却用Java?
  2. 打开电脑自带字符映射表
  3. 阿里云-RPA-2-第一个PRA程序
  4. PRA0.S08、PRV0.S08,插装式压力阀
  5. 计算机网络 谢希仁 第八版 学习笔记
  6. 徐明星系列之徐明星创办的OK资本成为RnF金融有限公司的锚定投资者
  7. 适合学生写的请假管理系统
  8. antdv table 的表格列配置描述 columns
  9. Matlab对图像进行傅里叶变换实例
  10. MarkDown: 为字体添加颜色