1.IP协议
• IP协议全称为“网际互连协议(Internet Protocol)”,IP协议是TCP/IP体系中的网络层协议。
• 在主机通信的过程中,上层应用程序解决从应用层代码中获取数据并处理数据的问题,应用层解决的是读取完整报文、序列化、协议处理问题,传输层解决的是类似数据传输可靠性的传输策略问题,而网络层解决的是将数据从一台主机送到另一台主机,也就是数据的路由问题,下面进行详细讲解。
1.1.IP协议基本概念
网络层解决的问题:
TCP作为传输层控制协议,其保证的是数据传输的可靠性和传输效率,但TCP提供的仅仅是数据传输的策略,而真正负责数据在网络中传输的则传输层之下的网络层和链路层。
• 双方在进行网络通信时,发送的数据并不是直接从一方的传输层直接发送到了另一方的传输层,而是需要传输层将数据继续向下进行交付,在网络层和链路层经过数据封装后再通过网络发送到对方主机,对方主机收到数据后也同样需要在链路层和网络层进行数据解包,此时对方的传输层才拿到了发送过来的数据,然后再继续将该数据向上进行交付。
网络通信的过程,就像两个人在送互相送数据,这两个人分别在两栋楼的四楼,如果一个人要将数据交给对方,那么这个人就必须先从四楼走到一楼,然后再在路上经过路径选择到达对方楼下,最后再上到四楼将数据交给对方。其中,送数据的这个人从四楼下来的过程就是数据封装的过程,这个人在路上经过路径选择到达对方楼下的过程就是数据路由的过程,而这个人再上到四楼将数据交给对方的过程就是数据解包的过程。
• 而网络层要解决的问题就是,将数据从一台主机送到另一台主机,也就是数据的路由。
保证数据可靠的从一台主机送到另一台主机的前提:
当双方在进行基于TCP的网络通信时,要保证将数据可靠的从一台主机送到另一台主机,前提是发送方要有将数据送到对方主机的能力,要是发送方连将数据发送给对方的能力都没有,那就更不用谈可靠的将数据送给对方主机了。
• 需要注意的是,发送方有将数据送到对方主机的能力,并不意味着发送方每次发送的数据都能够成功的发送到对方,但如果发送方连将数据发送给对方的能力都没有,那发送方基本就不可能将数据发送给对方。
• 一旦发送方有了将数据发送给对方的能力,就算发送方某次发送的数据没有成功到达对方,此时上层TCP由于没有收到对应数据的应答,此时上层TCP会要求进行数据重发,直到数据成功发送到对方主机为止。
也就是说,在网络层有能力将数据送到对方主机的情况下,虽然网络层不能保证每次都能将数据成功送到对方主机,但在TCP提供的可靠性策略的保证下,最终网络层就一定能够将数据可靠的发送到对方主机。注:
1.网络层解决的问题是,将数据从一台主机送到另一台主机,因此网络层解决的是主机到主机的问题。
2.发送方传输层从上方进程拿到数据后,该数据贯穿网络协议栈进行封装和解包,最终到达接收方传输层,此时接收方传输层也会将数据向上交给对应的进程,因此传输层解决的是进程到进程的问题。
3.网络层提供的是将数据从主机A跨网络发送给主机B的能力,但不能保证数据百分百发送成功,即不能保证数据发送的可靠性,而传输层是用来保证发送数据可靠性的。因此传输层的TCP协议+网络层的IP协议可以可靠的将数据从主机A跨网络发送给主机B。
路径选择:
数据进行的网络传输一般都是跨网络的,而路由器就是连接多个网络的硬件设备,因此数据在进行跨网络传输时一定需要经过多个路由器。
数据路由就像我们旅游一样,当确定了要到达的目标主机后,就需要寻找最短的路径到达该目的地。
• 目的地的确定是非常重要的,因为目的地直接决定了数据路由时的路径选择,这也是跨网络找到目标主机的根本。
• 只有数据经过了较为正确的路径选择,最终才可能慢慢趋近于目标网络或目标主机。
确定数据路由的目的地后,数据就可以在网络中进行路由了,但数据在路由时无法自行进行路径选择,因为这个数据本身是“不认识路”的,因此数据在路由的过程中需要不断“找路人问路”,而这里所谓的“路人”就是网络当中的一台台路由器。网络当中的路由器是“认识路的”,它们将自己的“认路经验”都记录到路由表当中,因此路由器可以通过查路由表找到去特定点的最短路径。因此数据在路由时,会不断通过路由器来进行路径选择,以此来一步步靠近目标网络或目标主机。
主机和路由器:
• 主机:配有IP地址,但是不进行路由控制的设备。但实际现在几乎不存在不进行路由控制的设备了,就连你的笔记本也会进行路由控制。
• 路由器:既配有IP地址,又能进行路由控制。实际现在主流的路由器已经不仅仅具有路由的功能了,它甚至具备某些应用层的功能。
• 节点:主机和路由器的统称。
1.2.IP协议格式
• 4位版本号(version):指定IP协议的版本(IPv4/IPv6),对于IPv4来说,就是4。
• 4位首部长度(header length):表示IP报头的长度,以4字节为单位(与TCP相同)。
• 8位服务类型(Type Of Service):3位优先权字段(已经弃用),4位TOS字段,和1位保留字段(必须置为0)。4位TOS分别表示:最小延时,最大吞吐量,最高可靠性,最小成本。这四者相互冲突,只能选择一个。比如对于ssh/telnet这样的应用程序,最小延时比较重要,而对于ftp这样的程序,最大吞吐量比较重要。
• 16位总长度(total length):IP报文(IP报头+有效载荷)的总长度,用于将各个IP报文进行分离。
• 16位标识(id):唯一的标识主机发送的报文,如果数据在IP层进行了分片,那么每一个分片对应的id都是相同的。
• 3位标志字段:第一位保留,表示暂时没有规定该字段的意义。第二位表示禁止分片,表示如果报文长度超过MTU,IP模块就会丢弃该报文。第三位表示“更多分片”,如果报文没有进行分片,则该字段设置为0,如果报文进行了分片,则除了最后一个分片报文设置为0以外,其余分片报文均设置为1。
• 13位片偏移(framegament offset):分片相对于原始数据开始处的偏移,表示当前分片在原数据中的偏移位置,实际偏移的字节数是这个值 × 8 \times 8 ×8得到的。因此除了最后一个报文之外,其他报文的长度必须是8的整数倍,否则报文就不连续了。
• 8位生存时间(Time To Live,TTL):数据报到达目的地的最大报文跳数,一般是64,每经过一个路由,TTL -= 1,一直减到0还没到达,那么就丢弃了,这个字段主要是用来防止出现路由循环。
• 8位协议:表示上层协议的类型。
• 16位首部检验和:使用CRC进行校验,来鉴别数据报的首部是否损坏,但不检验数据部分。
• 32位源IP地址和32位目的IP地址:表示发送端和接收端所对应的IP地址。
• 选项字段:不定长,最多40字节。
IP报头在内核当中本质就是一个位段类型,给数据封装IP报头时,实际上就是用该位段类型定义一个变量,然后填充IP报头当中的各个属性字段,最后将这个IP报头拷贝到数据的首部,至此便完成了IP报头的封装。注:与TCP报头类似,IP报头也分为两部分,分别是前20字节的标准报头和选项字段。
问题1:IP如何将报头与有效载荷进行分离?
答:IP分离报头与有效载荷的方法与TCP是一模一样的,当IP从底层获取到一个报文后,虽然IP不知道报头的具体长度,但IP报文的前20个字节是IP的基本报头,并且这20字节当中涵盖4位首部长度。
因此IP是这样分离报头与有效载荷的:
• 当IP从底层获取到一个报文后,首先读取报文的前20个字节,并从中提取出4位的首部长度,此时便获得了IP报头的大小size
• 如果size的值大于20字节,则需要继续从报文当中读取size-20字节的数据,这部分数据就是IP报头当中的选项字段。
• 读取完IP的基本报头和选项字段后,剩下的就是有效载荷了。
IP就是通过这种“定长报头+自描述字段”的方式进行报头和有效载荷的分离的。但需要注意的是,IP报头当中的4位首部长度描述的基本单位与TCP报头当中的4位首部长度一样,都是以4字节为单位进行描述的,这也恰好是报文的宽度。4位二进制的取值范围是0000 ~ 1111,因此IP报头的最大长度为15*4=60字节,因为基本报头的长度是20字节,所以IP报头中选项字段的长度最多是40字节。如果IP报头当中不携带选项字段,那么IP报头的长度就是20字节,此时报头当中的4位首部长度字段所填的值就是20/4 = 5,即0101。
问题2:IP如何决定将有效载荷交付给上层的哪一个协议?答:基于IP协议的传输层协议不止一种,因此当IP从底层获取到一个报文并对其进行解包后,IP需要知道应该将分离后得到的有效载荷交付给上层的哪一个协议。
在IP报头当中有一个字段叫做8位协议,该字段表示的就是上层协议的类型,IP就是根据该字段判定应该将分离出来的有效载荷交付给上层的哪一个协议的。该字段是发送方的IP层从上层传输层获取到数据后填充的,比如是上层TCP交给IP层的数据,那么该数据在封装IP报头时的8位协议填充的就是TCP对应的编号。
32位源IP地址和32位目的IP地址:
IP报头当中的32位源IP地址和32位目的IP地址,分别代表的就是该报文的发送端和接收端对应的IP地址。
数据在网络传输过程中会遇到一个个的路由器,这些路由器会帮助网络当中的数据进行路由转发,使得网络中的数据慢慢趋近于目标主机。路由器在帮助数据进行路由转发时,会提取出该数据的IP报头当中的目的IP地址,并以此作为数据路由转发的重要依据。
当接收端收到了发送端发来的数据后,接收端可能也想要给发送端发送数据,因此发送端在发送数据时除了需要指明该数据的目的IP地址,还需要指明该数据的源IP地址,也就是发送端的IP地址。即便接收端收到数据后没有数据想要发送给发送端,但至少接收端需要向发送端发送一个响应报文,表明发送端发送的数据已经被接收端可靠的收到了,因此发送出去的数据除了需要指明该数据的目的IP地址,还需要指明该数据的源IP地址。
理解socket编程:
• 在进行socket编程的时候,当一端想要发送数据给另一端时,必须要指明对端的IP地址和端口号,也就是发送数据的目的IP地址和目的端口号。
• 其中这里的IP地址就是给网络层的IP用的,用于数据在网络传输过程中的路由转发,而这里的端口号就是给传输层的TCP或UDP用的,用于指明该数据应该交给上层的哪一个进程。
• 发送数据时我们不需要指明发送数据的源IP地址和源端口号,因为传输层和网络层都是在操作系统内核当中实现的,数据在进行封装时操作系统会自行填充上对应的源IP地址和源端口号。
8位生存时间:
报文在网络传输过程中,可能因为某些原因导致报文无法到达目标主机,比如报文在路由时出现了环路路由的情况,或者目标主机已经异常离线了,此时这个报文就成了一个废弃的游离报文。
为了避免网络当中出现大量的游离报文,于是在IP的报头当中就出现了一个字段,叫做8位生存时间(Time To Live,TTL)。8位生存时间代表的是报文到达目的地的最大报文跳数,每当报文经过一次路由,这里的生存时间就会减一,当生存时间减为0时该报文就会被自动丢弃,此时这个报文就会在网络中消散。
1.3.分片与组装
要想解释清楚分片与组装,就要从网络层之下的数据链路层入手。
数据链路层解决的问题:
IP能够将数据跨网络从一台主机送到另一台主机,而数据在进行跨网络传送时,需要经过一个个的路由器进行路由转发,最终才能到达目标主机。
比如要将数据从主机B跨网络传送到主机C,那么主机B需要先将数据交给路由器F,路由器F再将数据交给路由器G,…,最终由路由器D将数据交给主机C。
因此IP进行数据跨网络传送的前提是,需要先将数据从一个节点传送到和自己相连的下一个节点,这个问题实际就是由IP之下的数据链路层解决的,其中数据链路层最典型的代表协议就是MAC帧。
而两个节点直接相连也就意味着这两个节点是在同一个局域网当中的,因此要讨论两个相邻节点的数据传送时,实际讨论的就是局域网通信的问题。
最大传输单元MTU:
MAC帧作为数据链路层的协议,它会将IP传下来的数据封装成数据帧,然后发送到网络当中。但MAC帧携带的有效载荷的最大长度是有限制的,也就是说IP交给MAC帧的报文不能超过某个值,这个值就叫做最大传输单元(Maximum Transmission Unit,MTU),这个值的大小一般是1500字节。
由于MAC帧无法发送大于1500字节的数据,因此IP层向下交付的数据的长度不能超过1500字节,这里所说的数据包括IP的报头和IP的有效载荷。
分片与组装:
如果IP层要传送的数据超过了1500字节,那么就需要先在IP层对该数据进行分片,然后再将分片后的数据交给下层MAC帧进行发送。
如果发送数据时在IP层进行了分片,那么当这些分片数据到达对端主机的IP层后就需要先进行组装,然后再将组装好的数据交付给上层传输层。
注意:
• 数据的分片不是经常需要做的,实际在网络通信过程中不分片才是常态,因为数据分片会存在一些潜在的问题,比如分片可能会增加丢包的概率。
• 数据的分片和组装发生在IP层,不仅源端主机可能会对数据进行分片,数据在路由过程中的路由器也可能对数据进行分片。因为不同网络的MTU是不一样的,如果传输路径上的某个网络的MTU比源端网络的MTU小,那么路由器就可能对IP数据报再次进行分片。
• 分片数据的组装只会发生在目的端的IP层。
• 在分片的数据中,每一个分片在IP层都会被添加上对应的IP报头,而传输层添加的报头只会出现在第一个分片中,因此网络中传输的数据包可能没有传输层的报头。
数据的分片和组装都是由IP层完成的:
数据的分片和组装都是在IP层完成的,上层的传输层和下层的链路层并不关心。
传输层只负责为数据传送提供可靠性保证,比如当数据传送失败后,传输层的TCP协议可以组织进行数据重传。
• 当TCP将待发送的数据交给IP后,TCP并不关心该数据是否会在IP层进行分片,即TCP并不关心数据具体的发送过程。
• 当TCP从IP获取到数据后,TCP也不关心该数据是否在IP层经过了组装。
而链路层的MAC帧只负责,将数据从一个节点传送到和自己相连的下一个节点。• 当IP将待发送的数据交给MAC帧后,MAC帧并不知道该数据是IP经过分片后的某个分片数据,还是一个没有经过分片的数据,MAC帧只知道它一次最多只能发送MTU大小的数据,如果IP交给MAC帧大于MTU字节的数据,那MAC帧就无法进行发送。
• 当MAC帧从网络中获取到数据后,MAC帧也不关心这个数据是否需要进行组装,MAC帧只需要将该数据的MAC帧报头去掉后直接上交给上层IP就行了,而至于该数据的组装问题则是IP需要解决的。
因此,数据的分片和组装完全是由IP协议自己完成的,传输层和链路层不必关心也不需要关心。
分片的过程:
假设IP层要发送4500字节的数据,由于该数据超过了MAC帧规定的MTU,因此IP需要先将该数据进行分片,然后再将一个个的分片交给MAC帧进行发送。
IP报头如果不携带选项字段,那么其大小就是20字节,假设IP层添加的IP报头的长度就是20字节,并按下列方式将数据分片后形成了四个分片报文:
需要注意的是,分片后的每一个分片数据都需要封装上对应的IP报头,因此4500字节的数据至少需要分为四个分片报文进行发送。
分片报文到达对方的IP层后需要被重新组装起来,因此IP层在对数据进行分片时需要记录分片的信息,而IP报头当中的16位标识、3位标志和13位片偏移实际就是与数据分片相关的字段。
• 16位标识:唯一标识主机发送的报文,如果数据在IP层进行了分片,那么每一个分片报文的16位标识是相同的。
• 3位标志:第一位保留,表示暂时没有规定该字段的意义。第二位表示禁止分片,表示如果报文长度超过MTU,IP模块就会丢弃该报文。第三位表示“更多分片”,如果报文没有进行分片,则该字段设置为0,如果报文进行了分片,则除了最后一个分片报文设置为0以外,其余分片报文均设置为1。
• 13位片偏移:分片相对于原始数据开始处的偏移,表示当前分片在原数据中的偏移位置,实际偏移的字节数是这个值 ×8得到的。因此除了最后一个报文之外,其他报文的长度必须是8的整数倍,否则报文就不连续了。
因此上述四个分片报文对应的16位标识都是一样的,假设四个分片报文的16位标识都是123,则这四个报文对应的16位标识、3位标志中的“更多分片”和13位片偏移分别如下:
组装的过程:
MAC帧交给IP层的数据可能来自世界各地,这些数据可能是经过分片后发送的,也可能是没有经过分片直接发送的,因此IP必须要通过某种方式来区分收到的各个数据。
• IP报头当中有32位源IP地址,源IP地址记录了发送端所对应的IP地址,因此通过IP报头当中的32位源IP地址就可以区分来自不同主机的数据。
• IP报头当中有16位标识,未分片的数据各自的16位标识都是不同的,而由同一个数据分片得到的各个分片报文所对应的16位标识都是相同的,因此通过IP报头当中16位标识就可以判断哪些报文是没有经过分片的独立报文,哪些报文是经过分片后的分片报文。
因此IP可以通过IP报头当中的32位源IP地址和16位标识,将经过分片的数据各自聚合在一起,聚合在一起后就可以开始进行组装了。对于各个分片报文来说:
• 第一个分片报文中的13位片偏移的值一定为0。
• 最后一个分片报文中的“更多分片”标志位一定为0。
• 对于每一个分片报文来说,当前报文的13位片偏移加上当前报文的数据字节数 ÷ 8所得到的值,就是下一个分片报文的所对应的13位片偏移。
根据分片报文的这三个特点就能够将分片报文合理的组装起来。• 先找到分片报文中13位片偏移为0的分片报文,然后提取出其IP报头当中的16位总长度字段,通过计算即可得出下一个分片报文所对应的13位片偏移,按照此方式依次将各个分片报文拼接起来。
• 直到拼接到一个“更多分片”标志位为0的分片报文,此时表明分片报文组装完毕。
分片报文丢包的问题:
分片后的报文在网络传输过程中也可能会出现丢包问题,但接收端有能力判断是否收到了全部分片报文,比如假设某组分片报文对应的16位标识值为x:
• 如果分片报文中的第一个分片报文丢包了,那么接收端收到的分片报文中就找不到对应16位标识为x,并且13位片偏移为0的分片报文。
• 如果分片报文中的最后一个分片报文丢包了,那么接收端收到的分片报文中就找不到对应16为标识为x,并且“更多分片”标志位为0的分片报文。
• 如果分片报文中的其它分片报文丢包了,那么接收端在进行分片报文的组装时就会找不到对应13位片偏移为特定值的分片报文。
需要注意的是,未分片报文的“更多分片”标志位为0,最后一个分片报文的“更多分片”标志位也为0,但当接收端只收到分片报文中的最后一个分片报文时,接收端不会将其识别成一个未分片的报文,因为未分片的报文所对应的13位片偏移的值也应该是0,而最后一个分片报文所对应的13位片偏移的值不为0。因此只有当一个报文的13位片偏移为0,并且该报文的“更多分片”标志位也为0时,该报文才会被识别成一个没有被分片的独立报文,否则该报文就会被识别成一个分片报文。
问题1:为什么不建议进行分片?
答:虽然传输层并不关心IP层的分片问题,但分片对传输层也是有影响的。
• 如果一个数据在网络传输过程中没有经过分片,那么只要接收端收到了这一个报文,我们就可以认为该数据被对方可靠的收到了。
• 而如果一个数据在网络传输过程中进行了分片,那么只有当接收端收到了全部的分片报文并将其成功组装起来,这时我们才认为该数据被对方可靠的收到了。但如果众多的分片报文当中有一个报文出现了丢包,就会导致接收端就无法将报文成功组装起来,这时接收端会将收到的分片报文全部丢弃,此时传输层TCP会因为收不到对方应答而进行超时重传。
• 假设在网络传输时丢包的概率是万分之一,如果将数据拆分为一百份进行发送,那么此时丢包的概率就上升到了百分之一。因为只要有一个分片报文丢包了也就等同于这个报文整体丢失了,因此分片会增加传输层重传数据的概率。
需要注意的是,只要分片报文当中的某一个出现了丢包,此时传输层都需要将数据整体进行重传,因为传输层并不知道底层IP对数据进行了分片,当传输层发送出去的数据得不到应答时传输层就只能将数据整体进行重传,因此数据在发送时不建议进行分片。问题2:如何尽可能避免分片?
答:实际数据分片的根本原因在于传输层一次向下交付的数据太多了,导致IP无法直接将数据向下交给MAC帧,如果传输层控制好一次交给IP的数据量不要太大,那么数据在IP层自然也就不需要进行分片。
• 因此TCP作为传输控制协议,它需要控制一次向下交付数据不能超过某一阈值,这个阈值就叫做MSS(Maximum Segment Size,最大报文段长度)。
• 通信双方在建立TCP连接时,除了需要协商自身窗口大小等概念之外,还会协商后续通信时每一个报文段所能承载的最大报文段长度MSS。
MAC帧的有效载荷最大为MTU,TCP的有效载荷最大为MSS,由于TCP和IP常规情况下报头的长度都是20字节,因此一般情况下 MSS = MTU - 20 - 20,而MTU的值一般是1500字节,因此MSS的值一般就是1460字节。所以一般建议TCP将发送的数据控制在1460字节以内,此时就能够降低数据分片的可能性。之所以说是降低数据分片的可能性,是因为每个网络的链路层对应的MTU可能是不同的,如果数据在传输过程中进入到了一个MTU较小的网络,那么该数据仍然可能需要在路由器中进行分片。