TCP作为传输层控制协议,其保证的是数据传输的可靠性和传输效率,但TCP提供的仅仅是数据传输的策略,而真正负责数据在网络中传输的则传输层之下的网络层和数据链路层。
双方在进行网络通信的过程中,并不是直接在双方传输层之间进行数据传输的,而是发送方通过传输层向下进行交付,在网络层和数据链路层进行封装以后发送给对端主机,而对端主机收到数据后同样也在网络层和数据链路层进行解包,传输层才能拿到数据,最后在向上交付。
我们是IP协议作为网络层协议,解决的就是提供一种能力,将数据可靠的从主机A发送到主机B的能力。
注意:
也就是说,在网络层有能力将数据送到对方主机的情况下,虽然网络层不能保证每次都能将数据成功送到对方主机,但在TCP提供的可靠性策略的保证下,最终网络层就一定能够将数据可靠的发送到对方主机。
路径选择
数据进行的网络传输一般都是跨网络的,而路由器就是连接多个网络的硬件设备,因此数据在进行跨网络传输时一定需要经过多个路由器。
数据在知道自己的目的主机以后,就会寻找最短路径到达该目的地。
确定数据路由的目的地后,数据就可以在网络中进行路由了,但数据在路由时无法自行进行路径选择,因为这个数据本身是“不认识路”的,因此数据在路由的过程中需要不断“找路人问路”,而这里所谓的“路人”就是网络当中的一台台路由器。
网络当中的路由器是“认识路的”,它们将自己的“认路经验”都记录到路由表当中,因此路由器可以通过查路由表找到去特定点的最短路径。因此数据在路由时,会不断通过路由器来进行路径选择,以此来一步步靠近目标网络或目标主机。
如何分离?
IP分离报头与有效载荷的方法与TCP是一模一样的,当IP从底层获取到一个报文后,虽然IP不知道报头的具体长度,但IP报文的前20个字节是IP的基本报头,并且这20字节当中涵盖4位首部长度。
-当IP从底层获取到一个报文以后,首先读取报文的前20个字节,并且提取4位首部长度,此时就获取了IP报头大小size;
需要注意的是这里的4位首部长度跟TCP的4位首部长度没什么区别:
4位二进制的取值范围是0000 ~ 1111,因此IP报头的最大长度为15×4=60字节,因为基本报头的长度是20字节,所以IP报头中选项字段的长度最多是40字节。如果IP报头当中不携带选项字段,那么IP报头的长度就是20字节,此时报头当中的4位首部长度字段所填的值就是20÷4=5,即0101。
如何交付?
基于IP协议的上层传输层协议不止一种,因此当IP从底层获取到一个报文并对其进行解包后,IP需要知道应该将分离后得到的有效载荷交付给上层的哪一个协议。
在IP报头当中有一个字段叫做8位协议,该字段表示的就是上层协议的类型,IP就是根据该字段判定应该将分离出来的有效载荷交付给上层的哪一个协议的。该字段是发送方的IP层从上层传输层获取到数据后填充的,比如是上层TCP交给IP层的数据,那么该数据在封装IP报头时的8位协议填充的就是TCP对应的编号。
32为源IP地址与目的IP地址
在IP协议中,源IP地址代表发送端IP地址,目的IP地址地址代表接收方IP地址。
数据在网络中尽显传输时,并不是一步到位的,而是通过一个个路由器进行转发,最终数据慢慢趋近于目标主机,路由器在进行路由转发的过程中,就会提取出该IP报文中的目的IP地址,以此为进行下一步操作;
对于发送方来说,我们需要将数据发送到对端主机,就需要知道目的IP地址,只有知道了目的IP地址以后,我们才能将数据顺利的发送到目的地。对于接收端来说,我们可能会接收到数据以后再向发送端发送数据,即使不发送数据,我们也要给发送方形成一个响应报文才可以,所以既需要源IP地址也需要目的IP地址。
理解socket编程
8位生存时间
报文在网络传输过程中,可能因为某些原因导致报文无法到达目标主机,比如报文在路由时出现了环路路由的情况,或者目标主机已经异常离线了,此时这个报文就成了一个废弃的游离报文。
为了避免网络当中出现大量的游离报文,于是在IP的报头当中就出现了一个字段,叫做8位生存时间(Time To Live,TTL)。8位生存时间代表的是报文到达目的地的最大报文跳数,每当报文经过一次路由,这里的生存时间就会减一,当生存时间减为0时该报文就会被自动丢弃,此时这个报文就会在网络中消散。
数据链路层
数据在网络中进行传输,是通过一个个的路由器进行路由转发,比如我们的数据需要从主机A发送到主机C,可能就需要从路由器A转发到路由器B,在转发到路由器C… …经过一系列转发操作以后到达主机B,而在数据从一个结点转发到下一个节点的就是通过数据链路层来实现的。
为什么要进行分片?
每一种物理网络都会规定链路层数据帧的最大长度,称为链路层MTU(Maximum Transmission Unit).IP协议在传输数据包时,若IP数据报加上数据帧头部后长度大于MTU,则将数据报文分为若干分片进行传输,并在目标系统中进行重组。比如说,在以太网环境中可传输最大IP报文大小(MTU)为1500字节。如果要传输的数据帧大小超过1500字节,即IP数据报长度大于1472(1500-20-8=1472,普通数据报)字节,则需要分片之后进行传输。
什么是分片?
分片就是将一个比较大的报文,拆成多个小的,满足条件的报文,分片的行为是网络层做的,同样,组装的行为也是对端网络层做的,对于TCP/UDP,将数据传输给下层以后,他其实并不关心下层是怎么将数据发送的(我将一个完整的数据发送给你,就必须传输回来的数据也是完整的,至于怎么实现,就是网络层的事情)。
如何实现分片?
分片的实现是由我们IP报头中的16位标识,3位标志,13位片偏移来决定的:
是唯一标识主机发送的报文,如果数据在IP层进行了分片,那么每一个分片报文的16位标识是相同的,也就是说,相同报文的分片,标识是相同的,不同报文,标识是不同的。
第一位保留,表示暂时没有规定该字段的意义。第二位表示禁止分片,表示如果报文长度超过MTU,IP模块就会丢弃该报文。第三位表示“更多分片”,如果报文没有进行分片,则该字段设置为0,如果报文进行了分片,则除了最后一个分片报文设置为0以外,其余分片报文均设置为1。
分片相对于原始数据开始处的偏移,表示当前分片在原数据中的偏移位置,实际偏移的字节数是这个值×8得到的。因此除了最后一个报文之外,其他报文的长度必须是8的整数倍,否则报文就不连续了。
我们假设IP层需要发送3000个字节的数据,此时就需要我们进行分片,假设IP报头的长度就为20个字节,需要注意的是,对于每一个分片我们都需要封装一个20字节的报头,所以3000个字节数据来说我们就需要进行3次分片。
对于这三个分片来说,他们的16位标识是一样的,假设16位标识位1234,则这3个分片报文对应的16位标识,3位标志中的“更多分片”和13位片偏移分别如下:
如何识别分片还是未分片?
我们可以根据3位标志中的更多分片来识别,如果更多分片标志位为1,我们就可以确定他被分片了,如果更多标志位为0并且13位偏移量也为0,就证明没有被分片。
双方进行网络间通信,不仅仅是需要将数据发送出去的,还需要将数据向上交付,我们知道TCP/UDP发送给网络层是一个完整的数据,那么我们在进行数据向上交付的时候,也需要一个完整的数据,这就需要我们对分片的报文进行组装了。
如何识别分片的开始,中间,结尾?
我们想要组装一个完整的报文,就必须识别出分片报文的开始,中间和结尾,通过三位标志中的更多分片和13位偏移我们就能识别出来:
虽然中间报文存在多个,但是我们依然可以根据偏移量最终将一个完整的报文组装起来,结合(偏移量 + 自身大小 = 下一个偏移量),我们就可以进行升序排序,一旦我们扫描结果不匹配,必定是中间报文出现丢失了,如果成功计算到结尾,就一定是收取完整了。
在我们的网络层中可能存在大量分片以后的报文,我们在进行组装的过程中需要在大量分片报文中识别出一个完整报文的开头,中间和结尾,又是如何做到的呢?
这也就是我们在进行分片过程中为什么每一个分片报文必须有一个IP报头的原因,IP报头中保存了16为标识和32位源IP地址,一个完整报文分片以后他的每一份分片报文的16位标识是一样的,因此IP可以通过IP报头当中的32位源IP地址和16位标识,将经过分片的数据各自聚合在一起,聚合在一起后就可以开始进行组装了。
为什么不建议进行分片?
虽然我们的传输层并不关心网络层分片的问题,但是分片确是会对传输层产生影响的:
只要分片报文当中的某一个出现了丢包,此时传输层都需要将数据整体进行重传,因为传输层并不知道底层IP对数据进行了分片,当传输层发送出去的数据得不到应答时传输层就只能将数据整体进行重传,因此数据在发送时不建议进行分片。
如何尽可能避免分片?
实际数据分片的根本原因在于传输层一次向下交付的数据太多了,导致IP无法直接将数据向下交给MAC帧,如果传输层控制好一次交给IP的数据量不要太大,那么数据在IP层自然也就不需要进行分片。
MAC帧的有效载荷最大为MTU,TCP的有效载荷最大为MSS,由于TCP和IP常规情况下报头的长度都是20字节,因此一般情况下 MSS = MTU - 20 - 20,而MTU的值一般是1500字节,因此MSS的值一般就是1460字节。
所以一般建议TCP将发送的数据控制在1460字节以内,此时就能够降低数据分片的可能性。之所以说是降低数据分片的可能性,是因为每个网络的链路层对应的MTU可能是不同的,如果数据在传输过程中进入到了一个MTU较小的网络,那么该数据仍然可能需要在路由器中进行分片。
IP地址分为两个部分,网络号和主机号:
下图中路由器连接了两个网段。对于网络标识来讲,同一网段内主机的网络标识是相同的,不同网段内主机的网络标识是不同的。而对于主机标识来讲,同一网段内主机的主机标识是不同的,不同网段内主机的主机标识是可以相同的。
DHCP协议
通过合理设置主机号和网络号,就可以保证在相互连接的网络中,每台主机的IP地址都不相同。但是手动管理P地址是一个非常麻烦的事情,当子网中新增主机时需要给其分配一个IP地址,当子网当中有主机断开网络时又需要将其IP地址进行回收,便于分配给后续新增的主机使用。
因此对于IP地址的分配和回收一般不会手动进行,而是采用DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)技术。
当我们连接WiFi时需要输入密码,本质就是因为路由器需要验证你的账号和密码,如果验证通过,那么路由器就会给你动态分配了一个IP地址,然后你就可以基于这个IP地址进行各种上网动作了。
数据在网络中如何传输?
当IP要将数据通过网络从一台主机发送到另一台主机时,并不是直接进行发送的,而是先将数据发送到目标主机所在的网络,然后再将数据发送到目标主机。
发送数据之所以不是直接发送到目标主机是因为效率太低了:
因此,为了提高数据路由的效率,我们对网络进行了网段划分。
网段划分
过去曾经提出一种划分网络号和主机号的方案,把所有IP 地址分为五类,如下图所示:
当要判断一个IP地址是属于哪一类时,只需要遍历IP地址的前五个比特位,第几个比特位最先出现0值,那么这个IP地址对应就属于A、B、C、D、E类地址。
子网划分
随着Internet的飞速发展,这种划分方案的局限性很快显现出来:
针对这种情况提出了新的划分方案,称为CIDR(Classless Interdomain Routing):
此时一个网络就被更细粒度的划分成了一个个更小的子网,通过不断的子网划分,子网中IP地址对应的主机号就越来越短,因此子网当中可用IP地址的个数也就越来越少,这也就避免了IP地址被大量浪费的情况。
可见,IP地址与子网掩码做与运算可以得到网络号,主机号从全0到全1就是子网的地址范围;
IP地址和子网掩码还有一种更简洁的表示方法,例如140.252.20.68/24,表示IP地址为140.252.20.68,子网掩码的高24位是1,也就是255.255.255.0。
子网划分不是只能进行一次,我们可以在划分出来的子网的基础上继续进行子网划分,因此一个数据在路由的时候,随着数据不断路由进入更小的子网,其网络号的位数是在不断变化的,准确来说其网络号的位数是在不断增加的,这也就意味着IP地址当中的主机号的位数在不断减少。最终当数据路由到达目标主机所在的网络时,就可以在该网络当中找到对应的目标主机并将数据交给该主机,此时该数据的路由也就结束了。
并不是所有的IP地址都能够作为主机的IP地址,有些IP地址本身就是具有特殊用途的。
也就是说,IP地址中主机号为全0的代表的是当前局域网的网络号,IP地址中主机号为全1的代表的是广播地址,这两个IP地址都是不能作为主机的IP地址的。因此在某个局域网中最多能存在的主机个数是 2^主机号位数 - 2。
我们知道,IP地址(IPv4)是一个4字节32位的正整数。那么一共只有2的32次方个IP地址,大概是43亿左右。而TCP/IP协议规定,每个主机都需要有一个IP地址。
这意味着,一共只有43亿台主机能接入网络么?
实际上,由于一些特殊的IP地址的存在,数量远不足43亿;另外IP地址并非是按照主机台数来配置的,而是每一个网卡都需要配置一个或多个IP地址。
-CIDR虽然在一定程度上缓解了IP地址不够用的问题,因为CIDR提高了IP地址的利用率,减少了浪费,但IP地址的绝对上限并没有增加。仍然不是很够用,这时候有三种方式来解决:
私网IP地址的种类
如果一个组织内部组建局域网,IP地址只用于局域网内的通信,而不直接连到Internet上,理论上使用任意的IP地址都可以,但是RFC 1918规定了用于组建局域网的私有IP地址。
包含在这个范围中的,都称为私网IP,其余的则称为公网IP(或全局IP)。
我们可以通过ifconfig命令来查看我们这台机器的私网IP,其中网络接口lo(loop)代表的是本地环回,而eth0代表的就是我这台机器的网络接口,可以看到我的私网IP地址是10.0.28.16。
打开Windows当中的cmd窗口,通过ipconfig命令可以看到大量以192.168开头的私网IP。
数据在服务器之间的传输过程
我们的路由器对内面对的是自己构建的子网,对外自己本身也是别人构建的子网的一个主机,路由器是连接两个或多个网络的硬件设备,在路由器上有两种网络接口,分别是LAN口和WAN口:
大致关系就如下:
所以数据要从一个局域网发送到另一个局域网,如果不经过公网是基本上不可能的。我们在和别人聊天的时候,也不是直接将数据从一个局域网直接发送到了另一个局域网,而是先将数据经过公网发送到了服务器,然后再由服务器将数据经过公网转发到了另一个局域网。
为什么私网IP不能出现在公网当中?
数据在路由的过程中,实际就是一跳一跳(Hop by Hop)“问路”的过程。所谓“一跳”就是数据链路层中的一个区间,具体在以太网中指从源MAC地址到目的MAC地址之间的帧传输区间。
IP数据包的传输过程中会遇到很多路由器,这些路由器会帮助数据包进行路由转发,每当数据包遇到一个路由器后,对应路由器都会查看该数据的目的IP地址,并告知该数据下一跳应该往哪跳。
路由表可以通过route
命令进行查看:
当IP数据包到达路由器时,路由器就会用该数据的目的IP地址,依次与路由表中的子网掩码 Genmask
进行“按位与”操作,然后将结果与子网掩码对应的目的网络地址Destination
进行比对,如果匹配则说明该数据包下一跳就应该跳去这个子网,此时就会将该数据包通过对应的发送接口Iface
发出。
如果将该数据包的目的IP地址与子网掩码进行“按位与”后,没有找到匹配的目的网络地址,此时路由器就会将这个数据包发送到默认路由,也就是路由表中目标网络地址中的default
。可以看到默认路由对应的Flags
是UG,实际就是将该数据转给了另一台路由器,让该数据在另一台路由器继续进行路由。
数据包不断经过路由器路由后,最终就能到达目标主机所在的目标网络,此时就不再根据该数据包目的IP地址当中的网络号进行路由了,而是根据目的IP地址当中的主机号进行路由,最终根据该数据包对应的主机号就能将数据发送给目标主机了。