当前位置:首页 > 单片机 > C语言与CPP编程
[导读]1.背锅侠TCP在前面介绍套接字通信的时候说到了TCP是传输层协议,它是一个面向连接的、安全的、流式传输协议。因为数据的传输是基于流的所以发送端和接收端每次处理的数据的量,处理数据的频率可以不是对等的,可以按照自身需求来进行决策。TCP协议是优势非常明显,但是有时也会给我们造成困...

1. 背锅侠 TCP

在前面介绍套接字通信的时候说到了 TCP 是传输层协议,它是一个面向连接的、安全的、流式传输协议。因为数据的传输是基于流的所以发送端和接收端每次处理的数据的量,处理数据的频率可以不是对等的,可以按照自身需求来进行决策。

TCP 协议是优势非常明显,但是有时也会给我们造成困扰,正所谓:成也萧何败萧何。假设我们有如下需求:

客户端和服务器之间要进行基于 TCP 的套接字通信

  • 通信过程中客户端会每次会不定期给服务器发送一个不定长度的有特定含义的字符串。
  • 通信的服务器端每次都需要接收到客户端这个不定长度的字符串,并对其进行解析。
根据上面的描述,服务器在接收数据的时候有如下几种情况:

  • 一次接收到了客户端发送过来的一个完整的数据包
  • 一次接收到了客户端发送过来的 N 个数据包,由于每个包的长度不定,无法将各个数据包拆开
  • 一次接收到了一个或者 N 个数据包 下一个数据包的一部分,还是很悲剧,无法将数据包拆开
  • 一次收到了半个数据包,下一次接收数据的时候收到了剩下的一部分 下个数据包的一部分,更悲剧,头大了
  • 另外,还有一些不可抗拒的因素:比如客户端和服务器端的网速不一样,发送和接收的数据量也会不一致
对于以上描述的现象很多时候我们将其称之为 TCP的粘包问题,但是这种叫法不太对的,本身 TCP 就是面向连接的流式传输协议,特性如此,我们却说是 TCP 这个协议出了问题,这只能说是使用者的无知。多个数据包粘连到一起无法拆分是我们的需求过于复杂造成的,是程序猿的问题而不是协议的问题,TCP 协议表示这锅它不想背。

现在问题来了,服务器端如果想保证每次都能接收到客户端发送过来的这个不定长度的数据包,程序猿应该如何解决这个问题呢?下面给大家提供几种解决方案:

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

2. 解决方案

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

2.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, 
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭