一、前言
网络层最重要的协议是IP协议。

真实的网络传输,并不是直接把数据包跨网络传输到对端主机的,而是会经过一跳又一跳的路由器。此时就会有一个问题,下一跳能选择的路由器那么多,该选择哪一个呢?这其实就是路径选择问题,这就是IP协议要解决的问题。

IP协议解决什么问题?
IP协议能提供一种能力:将数据包跨网络从主机B送到主机C。有这种能力,并不代表能100%做到,而是具有较大的概率。TCP协议会提供可靠性策略来保证IP协议的能力。
讲个故事,张三具有考满分的能力,但是这次考试,张三没有考到满分,于是他的校长父亲就重新进行考试,如果还没考满分,那就继续重考。每次考试都是张三自己跑去考试的,他的父亲给它提供了当他没考到满分时无数次重考的机会。所以对于张三来讲,他的父亲给他提供了考试的可靠性机制。
父亲就相当于提供了策略(TCP),张三就相当于提供了能力(IP)。所以TCP与IP相互配合,就能提供将数据包跨网络可靠地从主机A送到主机B的能力。
再讲个故事,今天我和朋友想要从辽宁去北京玩,一定是先坐飞机到北京,然后在去具体的景点玩。

我和朋友这次旅游的目标地址=所在城市+具体景点。
IP地址也是同理,IP地址=目标网络+目标主机。
任何主机,都一定处在一个子网中,路由器至少横跨了两个子网。我们的数据包再传输过程中其实是不断地从一个子网通过路由器进入另一个子网,到了最终的目标网络后,再在目标网络中寻找目标主机。
我们在学习网络的时候,要意识到:
- 网络不是凭空产生的,而是有人专门为我们建设的。
- 网络世界是被精心设计过的。
二、IP报文

如何分离报头与有效载荷?
4位首部长度,范围是[0,15],单位是4字节,所以报头的长度为[0,60]字节。IP报头的标准长度是20字节。首部长度-标准报头长度=选项长度。16位总长度=报头长度(选项+标准报头)+有效载荷长度。
如何进行向上交付?
8位协议,由发送方填充,接收方识别。决定了向上交给TCP还是UDP。
4位版本,基本填的都是IPv4,IPv4用4个字节表示IP地址。还有一种是IPv6,用16个字节表示IP地址,但是IPv6在全球使用得不多。
8位服务类型,分为3位优先权字段(已经弃用),4位TOS字段和1位保留字段(必须置为0)。TOS分别表示:最小延时、最大吞吐量、最高可靠性、最小成本,这四者相互冲突,只能选择一个。
8位生存时间(TTL):数据报到达目的地的最大报文跳数,一般是64。每次经过一个路由,TTL-= 1,一直减到0还没到达,那么就丢弃了,这个字段主要是用来防止出现路由循环。
OS会将大量的报文缓存起来,为了管理大量的报文,一定会生成一个结构代表报文,并将这些结构体管理起来。

三、网段划分
IP地址分别为两个部分:网络号与主机号。
- 网络号:保证相互连接的两个网段具有不同的标识。
- 主机号:同一网段内,主机之间具有相同的网络号,但是必须有不同的主机号。
IP地址/24这里的24就是子网掩码,表示前24位代表的是主机号。(后面会讲子网掩码)

路由器有一个非常重要的功能就是:构建子网,它会对局域网内的IP地址进行管理。管理任务肯定不能由人管理,得由程序管理。所以应用层就有一个DHCP协议,专门进行IP地址的分配与回收。
DHCP:
- 能够自动给子网内新增主机节点分配IP地址。
- 一般路由器都带有DHCP功能,因此路由器可以看做一个DHCP服务器。
路由器主要工作在网络层,但不仅仅工作网络层,它还能为我们提供应用层服务,它甚至还支持了http协议。
四、IP地址分类
IP地址是有限的,所以IP地址也是一种资源, 为了更好地让大家根据需求申请不同范围内的IP地址,于是就有了把IP地址分类的方案。

- A类 0.0.0.0到127.255.255.255
- B类 128.0.0.0到191.255.255.255
- C类 192.0.0.0到223.255.255.255
- D类 224.0.0.0到239.255.255.255
- E类 240.0.0.0到247.255.255.255
随着Internet的飞速发展,这种划分方案的局限性很快显现出来,大多数组织都申请B类网络,导致B类网络很快就分配完了,而A类却浪费了大量地址。
- 例如,申请了一个B类网络,理论上一个子网内能允许6万5千多个主机, A类网络的子网内的主机数更多。
- 然而实际网络架设中,不会存在一个子网内有这么多的情况,因此大量的IP地址都被浪费掉了。
针对这种情况提出了新的划分方案,称为CIDR。
- 引入一个额外的子网掩码(subnet mask)来区分网络号和主机号。
- 子网掩码也是一个32位的正整数,通常用一串 "0" 来结尾。
- 将IP地址和子网掩码进行 "按位与" 操作,得到的结果就是网络号。
- 网络号和主机号的划分与这个IP地址是A类、B类还是C类无关。

IP地址和子网掩码还有一种更简洁的表示方法,例如140.252.20.68/24,表示IP地址为140.252.20.68,子网掩码的高24位是1,也就是255.255.255.0。
子网掩码的出现也就意味着能对原本的IP地址分类再次进行细分,


所以,子网掩码能提高分类之后网络的利用率。
五、特殊的IP地址
将IP地址中的主机地址全部设为0,就成为了网络号,代表这个局域网。
将IP地址中的主机地址全部设为1,就成为了广播地址,用于给同一个链路中相互连接的所有主机发送数据包。
127.*的IP地址用于本机环回(loop back)测试,通常是127.0.0.1。
六、私有IP地址与公网IP地址
如果一个组织内部组建局域网,IP地址只用于局域网内的通信,而不直接连到Internet上,理论上使用任意的IP地址都可以,但是RFC 1918规定了用于组建局域网(私网)的私有IP地址:
- 10.*,前8位是网络号,共16,777,216个地址。
- 172.16.*到172.31.*,前12位是网络号,共1,048,576个地址。
- 192.168.*,前16位是网络号,共65,536个地址。
- 包含在这个范围中的,都称为私有IP(内网IP),其余的则称为全局IP(或公网IP)。

查询我电脑上的IP地址,就是私有IP。

七、讲故事
一个学校里根据专业的不同划分出了许多的学院,今天每一个院都有对应的一个院学生会主席,院学生会主席会把本院的每一个学生都拉进自己院的群里。同时,所有院学生会主席也会被校学生会主席拉进一个群里。每个学生都有自己的学号,规定,学号一共有八个比特位位,前三位代表的学院,后五位是个人编号。
今天,数信院有一个叫张三的学生,他捡到了一个钱包,钱包里有学生证,但是学生证上只有学号看的清,学号是01000100,张三虽然知道前三位是院号,但他不知道具体代表的是哪个院,于是他在全校一个人一个人地问学号来找失主。查找的本质就是排除,一个一个找,其实就相当于一次排除掉一个不是失主的学生。
但是一个一个找效率太低了,于是张三开始动脑子了,张三作为一个普通学生,不知道这个学号是谁的,但是知道前三位是院号,而且失主的前三位院号和自己不一样,所以一定不是自己院的。并且,虽然知己不知道前三位代表的是哪个院,但是我们数信院学生会主席知道啊。于是张三把学号拍了照发到数信院群里,并@数信院学生会主席说:“主席,我捡到了一个钱包,虽然不知道是哪个院的,但一定不是我们院的”。数信院主席知道后,拿走了钱包,并把学号转发到学生会主席群里,此时理学院主席看到了就说:“这是我们院的,我们院前三位是010,这是我们院李四的学号”。数信院主席就把钱包给了理学院主席,然理学院主席就在理学院群里告诉李四他的钱包丢了,被数信院的张三捡到了,把钱包给了李四。
第二种查找效率明显更高,因为张三找数信院主席时就排除掉了数信院所有的学生,数信院主席找理学院主席时就把经管院、化材院、马克思学院的学生都排除了。理学院主席找张三时就把理学院其他学生排除了。

把学号划分为院号与个人编号,能够便于定位任意一名学生。所以把IP划分为网络号与主机号也是同理,能够提高定位和查找主机的效率。这就是为什么要进行子网划分的原因。
八、浅谈运营商
我们的家用路由器会直接连接运营商建设的基础设施。我们进行网络传输所需要的基础设施都是由运营商来建设的。

我们发送/接收的所有报文都需要经过运营商。
我们的家用路由器中有我们的账户信息,以后每次发送/接受的报文与都会携带我们的账户信息,所以运营商就能知道用我们了多少流量,如果流量用完了不充值,运行商就可以直接把我们发送/接收的报文丢弃。
下面模拟报文在网络中传输的情况:

现在报文到达目的服务器了,目的服务器知道了报文来自192.168.1.201/24,但是当服务器想要返回响应时,发现找不到回去的路了。192.168.1.201是内网IP,内网IP是不能出现在公网中的。因为内网IP不能出现在公网中,只会在自己对应的私网中使用。内网IP出现重复是很正常的,所以服务器才不知道怎么找到会回去的路。
允许内网IP重复,这样能大大缓解IP不足的问题。
既然我们上面模拟的情况有误,修改:
路由器要横跨两个子网,所以一定要有两个IP,一个是子网IP、一个是WAN口IP。路由器转发报文时,会将源IP改为路由器的WAN口IP,这种技术叫做NAT技术。这样就保证了如何把一个报文从我的主机传输到服务器,但是服务器如何把响应返回我的主机呢?这个后面再讲。

从家用路由器出去了,就到公网了吗?
不一定,大概率不是。
我们真实的网络情况是公网+私网(家用网络和运营商私网)构建的网络拓扑结构。
九、理解公网与私网
假设IP是按国家为单位划分的(不正确,只是为了方便理解),假设IP的前八位作为国家标识。

接下来把重点放在我们国家。根据上面的假设,我们国家的IP地址必须以0000 0010开头。我们国家有34个省,于是用接下来8个比特位作为省份标识再次进行子网划分。

十、IP地址的数量限制
我们知道,IP地址(IPv4)是一个4字节32位的正整数,那么一共只有2的32次方个IP地址,大概是43亿左右,而TCP/IP协议规定,个主机都需要有一个IP地址。
这意味着,一共只有43亿台主机能接入网络么?实际上,由于一些特殊的IP地址的存在,数量远不足43亿,另外IP地址并非是按照主机台数来配置的,而是每一个网卡都需要配置一个或多个IP地址。
CIDR在一定程度上缓解了IP地址不够用的问题(提高了利用率,减少了浪费,但是IP地址的绝对上限并没有增加),仍然不是很够用。这时候有三种方式来解决:
- 动态分配IP:只给接入网络的设备分配IP地址。
- NAT技术:允许私网内IP地址重复。
- IPv6:IPv6并不是IPv4的简单升级,这是互不相干的两个协议,彼此并不兼容,IPv6用16个字节即128个比特位来表示IP地址,理论上多到可以给每一粒沙子分配IP地址,但是目前还没有普及。
十一、路由
IP数据包的传输过程就和问路一样:
- 当IP数据包,到达路由器时,路由器会先查看目的IP。
- 路由器决定这个数据包是能直接发送给目标主机,还是需要发送给下一个路由器。
- 依次反复,一直到达目标IP地址。
那么如何判定当前这个数据包该发送到哪里呢?这个就依靠每个节点内部维护一个路由表。
下面就是我linux机器上的路由表:

十二、分片与组装
数据链路层规定:单个数据帧不能太大,一般不可超过mtu。如果网络层向数据链路层交付了一个超过mtu的数据,数据链路层将无法处理。所以网络层需要提供分片与组装的功能。

分片就是把一个报文拆成多片,组装就是把分成多片的报文组装起来。分片是由发送方的网络层做的,组装是由接收方的网络层做的。
分片是好事还是坏事?
分片一定是不好的,而且是(也应该是)网络通信的小概率事件。分片会提高丢包概率。如果分片中任何一片丢了,接收方的网络差就无法组装了,就不会把报文向上交付,,此时发送方的传输层也就认为是整个报文都丢包了,就重传整个报文。
如何做到尽可能不分片呢?
让TCP单次传输的数据量不要太大。TCP发送的报文,不能只按照对方的接受能力,直接发送一个大报文,必须按照自己的分片可能性,设置自己的数据段大小。这个大小一般是1460,这是TCP传送数据的最大段尺寸(MSS),不包括TCP报头与IP报头的大小。
假设现在报文已经需要分片了,接下来看看分片的过程。
先认识三个字段:
- 16位标识:唯一的标识主机发送的报文,如果IP报文被分片了,那么每一个报文里的标识字段都是相同的。
- 3位标志:第一位保留(保留的意思是现在不用,但是还没想好说不定以后要用到)。第二位置为1表示禁止分片,这时候如果报文长度超过MTU,IP模块就会丢弃报文。第三位表示"更多分片",如果分片了的话,最后一个分片置为0,其他是1,类似于一个结束标记。
- 13位片偏移: 是分片相对于原始IP报文开始处的偏移,其实就是在表示当前分片在原报文中处在哪个位置。实际偏移的字节数是这个值 * 8 得到的。因此,除了最后一个报文之外,其他报文的长度必须是8的整数倍(否则报文就不连续了)。
一个3000字节的报文应该被分为几片?

分片之后,每一个分片都必须有IP报头,因为需要IP报头中的分片属性来进行组装,所以3000字节的报文应该被分为3片。

接收方收到报文后就开始组装:
1.接收方怎么知道特定标识的报文分片了呢?
- 收到的报文,如果3位标识的最后1位为1,就证明该报文分片了。
- 如果3位标识的最后一位为0,再看片偏移量即可。如果没分片,片偏移量一定为0。
2.接收方如何保证分片收全了?
- 能确认是否收到了开头的分片,3位标志的最后1位为1,偏移量为0,就是开头的分片。
- 能确认是否收到了结尾的分片,3为标志的最后1位为1,偏移量不为0,就是结尾的分片。
- 按照片偏移对分片进行排序,然后进行计算,前一个分片的偏移量+有效载荷长度=下一个分片的偏移量。
3.如何组装呢?
保证收全了分片,按片偏移排序再组装即可。
















