从零开始认识IP协议
1 路由过程
路由就是在复杂的网络结构中,找出一条通往终点的路线。
IP协议提供了保证主机A可以跨网络发送数据到主机B。跨网络过程中,需要经过不同的路由器的一次一次的转发才能到达主机B。IP协议解决的就是在这个传输过程中为什么要去这个路由器子网而不是另一个!
IP 数据包的传输过程也和问路一样:
- 当 IP 数据包,到达路由器时,路由器会先查看目的 IP;
- 路由器通过检索,如果这个数据包是能直接发送给目标主机,叫做内网转发
- 如果需要发送给下一个路由器进行路由,发送给下一个路由器的过程叫做缺省路由。
- 这样最终一定可以到达目标 IP 地址。
我们可以查看自己主机的路由表:
Destination
表示这台主机可以找到的网络;Gateway
表示下一次路由一个将报文交给哪台主机;Genmask
表示对应的子网掩码;face
是发送接口;Flags
中的 U 标志表示此条目有效( 可以禁用某些条目 ),G标志表示此条目的下一跳地址是某个路由器的地址,没有G 标志的条目表示目的网络地址是与本机接口直接相连的网络,不必经路由器转发。
如果在路由器中没有查到对应的网络,就会执行default
的过程,找到缺省的路由器。举个例子:
2 IP分片与组装
对于IP协议的报头我们到目前为止讲解了大部分的字段:
- 4位版本号:表示是IPv4还是IPv6
- 4位首部长度:标明报头长度,以32位(4字节)为单位计算。因此,TCP头部的最大长度为
15 * 4 = 60字节
。 - 16位总长度(Total Length):是表示整个IP数据包的长度(包括头部和数据),以字节为单位。
- 8位协议(Protocol):指示数据部分使用的协议(例如TCP、UDP、ICMP等)。
- 32位源IP:表示发送报文的主机IP地址
- 32位目的IP:表示目的主机的IP地址
那么就只剩下16位标识,3位标志,13位片偏移没有涉及过。而IP分片与组装就会使用了!
2.1 什么是IP分片与组装
在网络协议栈中,从上到下依次是应用层,传输层,网络层,数据链路层。现在我们正在学习的是网络层,当网络层将数据传给数据链路层时,数据链路层也会加上对应的报头结构。
- 数据链路层时最终数据发送的场所,其对于报文的长度有要求,称之为
MTU
(比如最大1500字节)那么网络层传下来的数据只能小于MTU - 数据链路层报头
。 - 而网络层并不决定这个数据的长度,因为其只是接收了传输层的数据,加上了IP协议的报头结构
- 报文的整体长度是由传输层决定的,假如传输层十分强硬,直接给了网络层2000字节的数据。网络层针对上下两层,就要进行处理:进行分片转发。
网络层的分片转发会对传输层的数据进行分割,传输层不在意是否分片,只要求报文可以成功发送并接收到应答。数据链路层也不在意是否分片,你给我数据链路层什么数据我就发送什么数据。
那么最终,传输层的一个报文就会分成多片进行发送,接收方如何知道每次的报文是一个整体,也就是网络层进行组装!
我们可以给出几个结论:
- 在网络世界中,只有IP报文!其他都是在网络协议栈中的!
- 1个报文 vs 10报文:按照分片规则,有可能会被拆分成多个分片报文。这样丢包的概率肯定就增大了!肯定是要接受到完整的10个报文才可以。所以这种分片策略不是网络转发的主流!
对于分片策略,只有网络层可以处理,而分片造成的丢包问题最终是要由传输层进行解决的! 所以传输层就意识到最好不要进行分片!那么传输层也做出相应改变,根据MTU,调整报文大小最大为MTU - 数据链路层报头 - 网络层报头
,也就是TCP传输层缓冲区每次刷新策略!
回看滑动窗口内部为什么还要划分成一个一个的空间?就是保证网络层收到数据后不会进行分片处理!!!尽可能保证不会发生丢包!
至此我们解决了:
- 为什么会进行分片?
- 为什么不想进行分片?
- 如何做到不分片?
2.2 如何分片与组装
上面谈及了网络协议栈中会避免进行分片,但如果出现分片是如何进行的呢?我们一起来看看:
对于服务端来讲,每时每刻都会有大量的报文传输过来,有些是进行分片了的,有些事没有进行分片。那么怎样辨别与组装呢?并保证收集全了?我们来看看组装的这个过程就理解了。
首先我们来看三个字段的含义:
- 16 位标识(id): 唯一的标识主机发送的报文。如果 IP 报文在数据链路层被分片了, 那么每一个片里面的这个 id 都是相同的。
- 3 位标志字段: 第一位保留(保留的意思是现在不用, 但是还没想好说不定以后要用到)。第二位置为 1 表示禁止分片,这时候如果报文长度超过 MTU,IP 模块就会丢弃报文。第三位表示"更多分片",如果分片了的话,最后一个分片置为 1,其他是0。类似于一个结束标记。
- 13 位分片偏移(framegament offset): 是分片相对于原始 IP 报文(不包含报头)开始处的偏移。其实就是在表示当前分片在原报文中处在哪个位置。实际偏移的字节数是这个值除以 8 得到的。 因此, 除了最后一个报文之外(之前如果都是 8 的整数倍,最后一片的偏移量也一定是 8 的整数倍),其他报文的长度必须是 8 的整数倍(否则报文就不连续了)。
假如现在MTU是1500字节,目前要发送的报文长度是3000字节(20字节数据链路层报头)。
- 首先,分片不能对报文直接进行切割,因为这样只能保证一个片带有报头,其余的都没有。这是绝对不可以的!
- 那么数据链路层的报头是20字节,第一片是
1460字节数据 + 20字节网络层报头 + 20字节数据链路层报头
,第二片是1480字节数据 + 20字节数据链路层报头
,第三片是20字节数据 + 20字节数据链路层报头
- 上面三片的片偏移量是
0
,1480
,2960
。根据片偏移规则<<3
对应片偏移字段为0
,185
,370
。 - 上面三片的标志分别为:
001
,001
,000
那么数据链路层是如何知道分片的呢?我们按照第一片 , 中间部分,最后一片来分析,如果今天分片了:
- 对于第一片来说,片偏移为0,更多分片标志位为1!那么根据更多分片就能知道这一片是分片过的!
- 对于中间片来说,片偏移非0,更多分片标志位为1!可以知道其是被分片过的!
- 对于最后一片,片偏移非0,更多分片标志位为0,可以判断出其是分片的最后一片!
- 如果没分片,那么更多分片标志位一定为0,片偏移一定为0!
收到很多分片之后,如何保证收集齐了呢?
- 根据id确定是同一个报文,然后将所有的片按照片偏移进行升序排序,遍历一遍,根据
自身长度+片偏移量 == 下一篇偏移量
就可以判断出来是否收集全了!
那么收集全了,如何进行组装呢?
- 我们可以根据2的过程,检查一个就尾插一个,这样判断结束就有了完整报文!