数据包的传输过程详解及TCP沾包问题

目录

TCP沾包问题


5个基本知识点:

  1. 封装报文是从上层到下层(应用层 --> 传输层 --> 网络层 – > 数据链路层 --> 物理层),解封装报文是从下层到上层。
  2. 数据包传输的过程中,源IP和目标IP不会变,除非遇到NAT(SNAT或DNAT),源MAC和目标MAC遇到网关会变。
  3. 二层内通过MAC寻址,三层通过IP寻址。
  4. 当一个数据包的目的地址不是本机,所以需要查询路由表,当查到路由表中的网关之后,需要获取网关的MAC地址,并将数据包的MAC地址修改成网关地址,然后发送到对应的网卡。
  5. 协议数据单元在应用层、表示层和会话层被称做数据(Data),在传输层被称做分段(Segment),在网络层被称做包(Packet),在数据链路层被称做帧(Frame),在物理层被称做比特(Bit)
  6. PC1或者Server上保留的arp表是:arp和ip的映射关系。而二层交换机是arp和端口的映射关系,也就是这个arp 应该由哪个端口转发。三层交换机可以保留arp和ip的映射关系。

应用层:HTTP协议是生成针对目标WEB服务器的HTTP请求报文,该报文就是需要传递的数据

传输层:HTTP协议使用的是TCP协议,为了方便通信,将HTTP请求报文按序号分为多个报文段(segment),并对每个报文段进行封装。PC1使用本地一个大于1024以上的随机TCP源端口(这里假设是1030)建立到目的服务器TCP80号端口的连接,TCP源端口和目的端口加入到报文段中,学名叫协议数据单元(Protocol Data Unit, PDU)。因TCP是一个可靠的传输控制协议,传输层还会加入序列号、窗口大小等参数

网络层:下沉到网络层后,封装网络层的头部,主要就是添加源和目的IP地址,成为数据包。用户通常使用主机名或域名来访问服务器,这时就需要通过应用层的DNS服务来通过域名查找IP地址,或逆向从IP地址反查域名。这里的源IP地址是193.1.1.2,目的IP地址是195.1.1.2。

数据链路层:下沉到数据链路层,封帧的头部,src mac和dst mac。PC1比较去往的目标IP,发现Server IP 195.1.1.2不在本地网络中,PC1通过查找本地路由表,会有一条默认路由指向网关R1,知道数据包要先发到网关R1的Fa0/0口。PC1查找本地arp cache,如果找到193.1.1.1对应的MAC地址则进行封装; 如果在ARP cache中没有找到193.1.1.1对应的MAC地址,则用ARP协议,査询到网关对应的MAC地址 “00-11-BC-7D-25-03” 。于是,这里的源MAC地址是PC1的MAC地址“00-1B-24-7D-25-01”,目的MAC地址是网关的MAC地址 “00-11-BC-7D-25-03。

物理层:数据链路层封装后的数据帧下沉到物理层,转换成二进制形式的比特(Bit)流,从PC1的网卡发送出去

        然后在物理层,数据链路层,网络层,通过对数据包的解封再封装等操作,获得这个包的MAC和IP地址,在局域网和异构的网络中进行查找并传输,到达目的主机后在进行上层的解封,获得数据。

从这个流动过程中,可以发现数据流在中间设备上主要执行的是OSI下三层的操作。

  • 物理层的设备不改变帧的格式,广播式转发。
  • 数据链路层的设备也不改变帧的格式,但可以根据数据帧中的目的MAC地址进行转发;
  • 网络层的设备改变帧的格式,要执行帧的解封装和再封装,但不改变数据包中的源和目的IP地址

TCP沾包问题

        采用TCP协议进行网络数据传送的软件设计中,普遍存在粘包问题。网络通信采用的套接字(socket)技术,其实现实际是由系统内核提供一片连续缓存(流缓冲)来实现应用层程序与网卡接口之间的中转功能。

        多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使数据包的边界发生错位,导致读出错误的数据分包,进而曲解原始数据含义

        粘包问题的本质就是数据读取边界错误所致。

        此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

二、粘包问题的解决方案

1、定长包
2、包尾加\r\n(ftp)
3、包头加上包体长度

4、更复杂的应用层协议

设计方案一:定长发送

        在进行数据发送时采用固定长度的设计,也就是无论多大数据发送都分包为固定长度(为便于描述,此处定长为记为LEN),也就是发送端在发送数据时都以LEN为长度进行分包。这样接收方都以固定的LEN进行接收,如此一来发送和接收就能一一对应了。分包的时候不一定能完整的恰好分成多个完整的LEN的包,最后一个包一般都会小于LEN,这时候最后一个包可以在不足的部分填充空白字节。

        当然,这种方法会有缺陷。

        1.最后一个包的不足长度被填充为空白部分,也即无效字节序。那么接收方可能难以辨别这无效的部分,它本身就是为了补位的,并无实际含义。这就为接收端处理其含义带来了麻烦。当然也有解决办法,可以通过增添标志位的方法来弥补,即在每一个数据包的最前面增加一个定长的报头,然后将该数据包的末尾标记一并发送。接收方根据这个标记确认无效字节序列,从而实现数据的完整接收。

        2.在发送包长度随机分布的情况下,会造成带宽浪费。比如发送长度可能为 1,100,1000,4000字节等等,则都需要按照定长最大值即4000来发送,数据包小于4000字节的其他包也会被填充至4000,造成网络负载的无效浪费。

2.2  设计方案二:尾部标记序列

        在每个要发送的数据包的尾部设置一个特殊的字节序列,此序列带有特殊含义,跟字符串的结束符标识”\0”一样的含义,用来标示这个数据包的末尾,接收方可对接收的数据进行分析,通过尾部序列确认数据包的边界。

这种方法的缺陷较为明显:

        1.接收方需要对数据进行分析,甄别尾部序列。

        2.尾部序列的确定本身是一个问题。什么样的序列可以向”\0”一样来做一个结束符呢?这个序列必须是不具备通常任何人类或者程序可识别的带含义的数据序列,就像“\0”是一个无效字符串内容,因而可以作为字符串的结束标记。那普通的网络通信中,这个序列是什么呢?我想一时间很难找到恰当的答案。

2.3  设计方案三:头部标记分步接收

        这个方法的实现是这样的,定义一个用户报头,在报头中注明每次发送的数据包大小。接收方每次接收时先以报头的size进行数据读取,这必然只能读到一个报头的数据,从报头中得到该数据包的数据大小,然后再按照此大小进行再次读取,就能读到数据的内容了。这样一来,每个数据包发送时都封装一个报头,然后接收方分两次接收一个包,第一次接收报头,根据报头大小第二次才接收数据内容。(此处的data[0]的本质是一个指针,指向数据的正文部分,也可以是一篇连续数据区的起始位置。因此可以设计成data[user_size],这样的话。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值