admin 管理员组

文章数量: 887017

TCP和IP之间的关系

引言

IP协议全称为“网际互连协议(Internet Protocol)”,是TCP/IP体系中的网络层协议.
我们在上篇文章中曾经提到过,TCP的全称是传输控制协议(Transmission Control Protocol"),它和我们的IP协议是什么关系呢?毕竟我们经常听到其他人把他俩摆在一起谈论,假如不知道它俩关系是啥,那就说不过去.
我们首先要思考一件事情,我们在TCP传输层提到过发送一个个报文,这里的发送是什么意思?TCP真的将实际数据发送出去了吗?
我们说并没有!TCP层并没有将实际数据发送出去,而是自顶向下交付,将我们的TCP报文交给了IP层,真正发送数据的是传输层之下的网络层和链路层
那我们将TCP报文向下交付到网络IP层后,IP层有什么用呢?有了TCP层稳定可靠的传输数据,直接发不就行了吗?
所以接下来,我们来了解IP层的定位和作用

IP层的定位和作用

我们先讲一个小故事来作为我们的开场
假如现在小明想要在这次考试中考满分,请问在不提前透题的情况下,他需要达成什么条件?

第一.他首先得有考100分的能力,假如他的能力只能考到60分,只掌握少部分知识,那就不用继续往下谈了

第二.单单有考100分的能力还不够,出的考题还必须合他的心意,也就是没有偏难怪题,刚好都在小明的能力接收范围之内
更为理想的情况下,是这次考试的主办方是他的校长父亲,能够对考试控制,为小明提供相应的考试策略(帮助),比如没有考到满分,能够进行补考;合理时间安排等等

假如达成上述的两个条件,不用说小明在这次考试中能考满分,他甚至每次考试他都有接近百分百的概率考满分
TCP层就是我们上述所讲的校长父亲的角色,它的定位是提供传输策略,就算发送方某次发送的数据没有成功到达对方,TCP层依旧会有很多策略和方法,帮助数据有效可靠的传送给对方,比如说此时上层TCP由于没有收到对应数据的ACK应答,TCP协议会进行数据重发,直到数据成功发送到对方主机为止等等
而我们的IP层就是小明自己本身的硬实力,它的定位是保证数据可靠的从一台主机送到另一台主机的前提,它提供了一种将数据从A主机跨网络发到B主机的能力
同样的,发送方有将数据送到对方主机的能力,并不意味着发送方每次发送的数据都能够成功的发送到对方,但如果发送方连将数据发送给对方的能力都没有,那发送方基本就不可能将数据发送给对方
无论是IP层还是TCP层,都是OS操作系统内部帮我们实现的,两者紧密配合,将数据从A主机可靠的,跨网络送到B主机(对端以接近百分之百的概率能够收到)

IP协议报头格式

报头与有效载荷分离

接下来,我们谈IP协议的报头格式,在学习过程中,能够明显感觉和TCP报头两者之间有很多的相似之处
PS:
和我们之前所提到的报头一样,IP报头在内核当中本质就是一个位段类型,给数据封装IP报头时,实际上就是用该位段类型定义一个变量,然后填充IP报头当中的各个属性字段,最后将这个IP报头拷贝到数据的首部,至此便完成了IP报头的封装
图来自博主2021dragon

4位首部长度/16位总长度

首先无论是学哪个协议,我们要了解的两件事,报头和有效载荷如何分离,以及有效载荷是如何向上交付(分用)的
不难看出来,IP协议报头和我们的TCP协议报头非常相像,它的首部长度字段都是20个字节,并且也同样有选项字段,剩下的就是我们的数据
并且眼尖的你可能已经发现,在20个字节的首部长度字段中,同样存在4位首部长度,所以,大胆的你可以猜想,IP报头和有效载荷如何分离是不是和TCP协议报头一样的操作
答案是肯定的,两者就是同样的操作
4位首部长度范围是【0000-1111】,假如直接表示为10进制数的话,范围就是0到15?这显然是❌的,首部长度字段都20字节
所以实际上,首部长度计算的基本单位是4字节

header_len * 4 = 最终长度

所以4位首部长度范围实际是【0000-1111】× 4 = 0到60个字节,除去20字节的首部长度字段,选项最终能有40个字节
假如我们TCP报头中没有选项呢?即标准报头,那4位首部长度应该填充几?
答案就是20/4 = 5,用四位二进制表示就是0101
并且在我们的IP报头中,存在16位的总长度字段,它告诉了我们IP数据报整体占多少个字节
整个报头和有效载荷分离的过程可以概述如下:

1.读取前20个字节(首部长度字段),并在其中找出对应的4位首部长度
2.4位首部长度*4,便可以得到我们的size = (首部长度字段+选项)长度,如果4位首部长度刚好是0101,意味着没有选项,选项最多为40个字节
3.继续读取剩余字节(16位总长度字段 - size),即读取选项字段
4.剩下的字节数,就是我们的有效载荷

8位协议

那有效载荷是如何向上交付(分用)的呢?
这个问题刚开始听起来会有点让人摸不着头脑,不是都已经解决了报头和有效载荷分离问题了吗?直接交付不就行了?
我们说没有那么简单,基于IP协议的传输层协议不止一种,不单单只有TCP协议,因此当IP从底层获取到一个报文并对其进行解包后,还需要知道应该将分离后得到的有效载荷交付给上层的哪一个协议
在IP报头中存在一个字段叫做8位协议.它的作用就在于此,它表示的就是上层协议的类型,假如传输层是TCP层,在IP层封装时,对应的8位协议字段填充的就是TCP对应的编号.

具体字段

接下来我们的任务,便是继续逐一解析IP报头里面的具体字段

32位源IP地址/目的IP地址

我们在上面提到过,IP层提供了一种将数据从A主机跨网络发到B主机的能力
这句话有几层潜在的含义
第一.不要把网络单纯只看作一个整体,在现实生活中,应该有很多个不同的网络,这样才有所谓跨网络的说法
第二.数据要从A主机发往B主机,或者B主机发往A主机,两者进行相互通信,那总要知道对方在哪里吧?
这就好比我们想要出去外面旅游,从广东出发,去北京的故宫
我们的第一步,应该是先到景点所在的城市北京
第二步,才是到达对应的景点故宫
我们发送的IP数据报文也是如此
我们先要到达对面主机所在的网络,再去寻找相应的目标主机
所以,可以很自然的得出,IP地址由两个部分组成:目标网络号+目标主机号,并且总共长度大小就是32个字节
每台主机都配置有对应的IP地址,当主机A想要向主机B发送报文时,会在IP报头里对应的源IP地址字段,填充上自己的IP地址,表示发送端
在IP报头里对应的目的IP地址字段,填充上想要发送目标主机的IP地址,表示接收端
可能有人会说,为什么要源IP地址字段呢?根本就没有必要啊
我们说接收端接收到你发的数据,即便接收到数据后没有数据想要发送给发送端,但至少接收端需要向发送端发送一个响应报文,表明发送端发送的数据已经被接收端可靠的收到了(TCP)
那接收端怎么知道发送端的IP地址是什么呢?靠的就是我们的源IP地址字段
我们在之前自己实现socket编程的时候,其实也做过类似的事情,当一端想要发送数据给另一端时,必须要指明对端的IP地址和端口号,这里的IP地址就是我们往目的IP地址字段填充的内容,而源IP地址字段和源端口号,由于TCP/IP都是在操作系统内核当中实现的,所以数据在进行封装时,OS操作系统会自行为我们填充对应的源IP地址和源端口号

3位标志字段/13位片偏移/16位标识

3位标志字段:

第一位保留(保留的意思是现在不用, 但是还没想好说不定以后要用到).
第二位置为1表示禁止分片, 这时候如果报文长度超过MTU, IP模块就会丢弃报文.
第三位表示"更多分片", 如果分片了的话, 最后一个分片置为1, 其他是0. 类似于一个结束标记

13位分片偏移(framegament offset):

是分片相对于原始IP报文开始处的偏移.
其实就是在表示当前分片在原报文中处在哪个位置. 实际偏移的字节数是这个值 * 8 得到的.
因此, 除了最后一个报文之外, 其他报文的长度必须是8的整数倍(否则报文就不连续了).

16位标识(id)

唯一的标识主机发送的报文.
如果IP报文在数据链路层被分片了, 那么每一个片里面的这个
id都是相同的

关于分片的内容,我们后面会详谈

8位服务类型(4位TOS字段)

8位字段中,有3位优先权字段(已经弃用), 4位TOS字段, 和1位保留字段(必须置为0).
4位TOS分别表示: 最小延时, 最大吞吐量, 最高可靠性, 最小成本. 这四者相互冲突, 只能选择一个.
对于ssh/telnet这样的应用程序, 最小延时比较重要;
对于ftp这样的程序, 最大吞吐量比较重要

8位生存时间/16位首部校验和

我们在IP地址字段的时候,简单提了一嘴IP地址的认识,现在我们要讲第二个小故事
我们现在依旧想要从广东潮汕出发,到北京的故宫游玩
广东潮汕就是我们的源IP地址,北京故宫就是我们的目的IP地址
但是我们去旅游,很多时候不是直接坐飞机,一步直达
更为准确的说,我们不仅需要买⻜机票,还需要买地铁票等等,其间需先后乘坐⻜机、地铁、公交⻋才能抵达最终目的地故宫
同样的,在现实双方通信也是如此,数据不是直接到达,而是要有一个路由的过程
图来自小林网络coding

数据先从主机A到达路由器,路由器来进行路径选择,选择一条最优的路径(路由算法),将我们的数据送到主机B处
换句话说,路由器除了配有对应的IP地址外,还能进行对应的路由控制
而我们也把主机和路由器统称为一个个节点

8位生存时间(Time To Live,TTL)

那我们的数据在发送的时候,有可能出现对应路径中的路由器坏了吗?或者说对应的路由器坏了,给我们的数据选择了一条错误的路径,导致数据不断在一个圈里面循环(环路转发),一直占用路由器的资源
答案是有可能的,因此设计一个数据能够存活的时间就非常有必要
8位生存时间字段就是实现这个功能,它代表数据报到达目的地的最大报文跳数,一般是64,每经过一个路由,TTL -=1,一直减到0还没到达,那么就会直接丢弃

16位校验和

同样的,数据在转发过程中,可能会出现数据发生错误的情况吗?也是有可能的
所以,也有必须有一个字段对其进行校验,假如出错,也直接丢弃,这就是16位校验和的功能
它使用CRC进行校验,来鉴别数据报的首部是否损坏,但不检验数据部分

4位版本号

指定IP协议的版本(IPv4/IPv6),对于IPv4来说,就是4
下面我们讨论的全都是默认IPV4版本的情况

网段划分

为什么需要子网

我们说网络不能单纯只看做一个整体,现实中是有很多个网络所构成,那为什么我们需要划分网络呢?甚至还分公网和子网的概念
我们依旧是从一个小故事出发
现在假设以下条件是满足的

1.我们在学校的ID = 院号 + 学号
并且院号,学号都是有规律的,从0开始按序编号

假如你是物光学生,物光学院院号为08,ID就是08000-08996之间取值(总共996个学生)
假如你是电信学生,电信学院院号为09,ID就是090000-091078之间取值(总共1078个学生)
以此类推,每个学院学生前面的院号相同,但是剩下的学号不同,从0开始编号

2.ID是定位学生的唯一性

不讨论身份证等等另外定位一个人的身份,在学校一般就是采取学号

3.每个学校都有自己的辅导员

我们作为学生,很多时候是不关心其他人的学号的,更不用说一眼看到对应的院号,就能分辨出对应这个学生是哪个学院的,但是辅导员知道每个院的院号

4.每个学院都有自己的微信群,还有一个微信大群,每个辅导员都在里面

假如现在你在地上找到了一个钱包,里面有一个同学你并不认识,但是上面有对应的学号,请问你应该如何还给这个同学呢?
一个个找的话,当然可以,但是这效率也太低了吧,遍历我们学校所有的学生,时间效率为O(N)
更聪明的做法,我们可以直接转交给我们的学院辅导员,再由辅导员转交给对应的学生,这样效率明显就大大调高,它不再是以个人为单位查找,而是以群为单位
查找的本质是排除,当我们选择了一个群,其实就是排除了其它所有的群
其他人的ID我们不知道是什么,我也不关心,但是我很清楚知道这个学生不是我们学院的(院号不对)
钱包交给对应的院辅导员,她虽然也不认识这个学生,但是她知道他属于哪个学院,再由她转交给对应的院辅导员,最后便可以将钱包还给对应的学生
在我们上述例子中,一个个学院其实就相当于一个个子网,ID就是我们的IP地址,它由我们的院号(网络号)和学号(主机号)构成
学院辅导员就是一个个路由器
每个辅导员都存在的大群,我们称作转发集群,也称为公网,其它一个个院自己的群,就是一个个子网
我们发送端假如要发送数据给对应的接收端
第一步,是找到对应的子网
我们不知道目标主机在哪,但我们可以根据它的网络号,来判断它和我们是不是属于同一个网络,剩下的就是交给我们的路由器(辅导员)处理
第二步,再找主机,每个人都有自己的IP地址(ID),并且它是唯一的
在现实中,我们为什么要建一个个不同的微信群,本质也是相同的——提高查找效率,可以一次性发送消息,让所有人看到,而不用一个个私发
所以,为什么需要子网划分呢?
IP地址的构成是子网划分的结果,而不是原因
真正的原因是我们要提高全球中任意一台主机查找另一台主机的效率

谁建子网

那我们说有一个个子网,谁建的一个个学员群和辅导员大群呢?
答案是运营商,也就是我们熟知的中国移动,电信等等
国内的网络一定是被国内运营商顶层设计过的,IP地址管理也是如此
那我们说辅导员不单单在学院群,也在对应的学院大群,假如辅导员也有自己的ID,那她应该有两个ID,一个是学院的,另一个是学院大群的
同样的,每个路由器含有至少2个IP,一个是该子网的IP,另一个是其它子网的IP
图来自小林coding

并且路由器作为构建子网的设备,它一般都是子网里的第一台主机,对应主机标识编号为1
我们可能会说构建子网很抽象,什么是构建子网呢?网络又摸不着看不见,但实际我们可能早就构建过一个自己的子网
我们手机开热点共享,给其他人用时,我们就用我们手机构建了一个自己的子网
路由器也是如此,它除了我们上述提到在路由过程中数据包转发,还有构建子网的功能,我们连接wifi,本质其实就是向路由器动态申请IP地址
我们也把动态申请IP地址这个过程,称作DHCP服务(Dynamic Host Configuration Protocol,动态主机配置协议)
为什么要动态分配呢?
答案很简单,因为实际手动管理IP地址是一个非常麻烦的事情,不如直接交给路由器自动处理.
当子网中新增主机时需要给其分配一个IP地址,当子网当中有主机断开网络时又需要将其IP地址进行回收,便于分配给后续新增的主机使用.
DHCP是一个基于UDP的应用层协议,一般的路由器都带有DHCP功能,因此路由器也可以看作一个DHCP服务器.
更甚至,现在大部分路由器其实相当于一部小型电脑,还给我们提供应用层的服务,在路由器背面,可能会有对应的URL,输入到浏览器上,就会出现对应的网页操作界面

IP地址具体形式

那说了这么多,我们只知道IP地址是由网络号和主机号所构成,并且总长度为32位.

网络号: 保证相互连接的两个网段具有不同的标识;
主机号: 同一网段内, 主机之间具有相同的网络号, 但是必须有不同的主机号

但具体形式是怎样的呢?
实际为了⽅便记忆,我们往往不是直接写出32 位 IP 地址
而是采用点分十进制的标记⽅式,也就是将 32 位 IP 地址以每 8 位为组,共分为 4 组,每组以「 . 」隔开,再将每组转换成⼗进制.
这也是我们为什么称作IPV4,4这个数字的由来.
图来自小林coding

不难得出,32位数字总共可以表示2^32个IP地址,最大允许接近 43 亿台计算机连接到网络
在windows操作系统,我们可以输入ipconfig指令,查看我们自己的IP主机地址,如图中的172.26.182.64

在linux操作系统下,我们可以输入ifconfig指令,查看对应的主机IP地址

网段划分方案

但是,像我们之前提到过的,IP地址是被划分为网络号和主机号两大部分的,那如何根据IP地址得知对应的网络号和主机号呢?
在互联网诞⽣之初,IP 地址显得很充裕,于是计算机科学家们设计了分类地址.
IP 地址分类成了 5 种类型,分别是 A 类、B 类、C 类、D 类、E 类
图来自小林coding

当要判断一个IP地址是属于哪一类时,只需要遍历IP地址的前五个比特位,第几个比特位最先出现0值,那么这个IP地址对应就属于对应的A、B、C、D、E类地址
然后不同类地址,有规定的字节数充当网络号
对应所能连接的主机数,就是对应2^主机号位数 -2
为什么要减2呢?
因为在IP地址中,有两个主机号是特殊的,一个是全1,另一个是全0
图来自小林coding

主机号全为 1 成为广播地址,用于广播

所谓的广播就是将消息发给在这个网络中所有的主机(用于给同一个链路中相互连接的所有主机发送数据包),比如我们听到眼保健操广播音乐,全班人都会开始做眼保健操
在本网络内广播的叫做本地广播,不会到达除本网络以外的其他链路上.
在不同网络之间的广播叫做直接广播((不过由于直接广播存在安全问题,所以大部分路由器这个功能都被设置为禁止转发)

主机号全为 0 指定某个网络,也就是成为我们的网络号.

但是随着网络的不断发展,这样的方案弊端就逐渐暴露出来了
例如, 申请了一个B类地址, 理论上一个子网内能允许6万5千多个主机. A类地址的子网内的主机数更多.
然而实际网络架设中, 不会存在一个子网内有这么多的情况. 因此,大量的IP地址都被浪费掉了
但是其它C类地址,主机号又完全不够用,只有254个,可能一个网吧的电脑设备都不止254个

CIDR方案

所以为了解决上述网段划分的方案,后来提出了新的网段划分方法——CIDR(Classless Interdomain Routing)
引入子网掩码的概念,左边全为1,右边全为0,总长也同样是32个字节,也是将 32 位 以每 8 位为组,共分为 4 组,每组以「 . 」隔开,再将每组转换成⼗进制.
将IP地址和我们的子网掩码按位与,得到的结果就是我们的网络号
通过这种方式,我们就可以灵活调整子网掩码中1的个数,进而灵活调整我们网络号所占字节的大小,与这个IP地址是A类、B类还是C类无关
我们可以举两个具体的例子

任何数与1相与,保持不变;与0相与,输出为0
所以IP地址为140.252.20.68与子网掩码255.255.255.0相与
立马就可以得到网络号140.252.20.0,主机号为68
子网地址范围为140.252.20.0~140.252.20.255

同样的,IP地址为140.252.20.68与子网掩码255.255.255.240相与
140.252.20.()是确定的,但此时由于子网掩码最后不是0,所以还要经过实际计算一番

240 对应的二进制序列为1111 0000
68 对应的二进制序列为0100 0100
PS:2的几次幂,1后面就根据几个0,比如2,对应0010,比如8 = 2^3,对应1000

两者相与结果为0100 0000,即十进制64
主机号最多取为0100 1111,和子网掩码1111 0000相与,结果为0100 1111,即十进制79
子网地址范围为140.252.20.64~140.252.20.79

IP地址和子网掩码还有一种更简洁的表示方法
/x 表示前 x 位属于网络号, x 的范围是 0 ~ 32

例如140.252.20.68/24,表示IP地址为140.252.20.68, 子网掩码的高24位是1,也就是255.255.255.0
同样的,我们输入同样的ipconfig或者ifconfig指令,也可以查看对应的子网掩码

PS:同样的,我们说路由器一定要能级联多个子网,即有至少两个以上的IP地址,支持查找,那也同样有对应相同数目的子网掩码,每个子网与子网掩码是1:1配对的

IPV6出现的必然性

但是我们前面无论如何讲述,CIDR方案仅仅只是提高IP地址的利用率而已,总体IP地址数目并没有改变,还是43亿个
尽管有动态分配IP地址的操作(你用的时候给你,不用就回收),起到共享的目的

只给每一个接入网络的设备分配IP地址,因此同一个MAC地址的设备,每一次接入互联网,得到的IP地址不一定相同

这还不足够,所以,我们提出了NAT技术

它能够让不同局域网当中同时存在两个相同的IP地址
这样就不仅能解决IP地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机

当然,这样也只是治标不治本的方法,有没有简单粗暴的方法呢?
我们说IPV6方案就是如此,它直接增加地址的总数目
值得一提的是,它并不是IPv4的简单升级版. 这是互不相干的两个协议, 彼此并不兼容;
IPv6用16字节128位来表示一个IP地址;这个数目就非常恐怖了,甚至可以给地球上每一粒沙子对应的IP地址
虽然IPv6还没有普及,但是我国也一直在局部地区推行IPV6的发展,这是由于物联网时代,比如说自动驾驶,很多设备都需要接入网络,这样所需要的IP数量是非常夸张的,假如有IPV6作为支撑,那万物互联时代便真正有了可靠的技术基础

全球视角回顾

有了上面的铺垫,我们便可以从全球视角,去理解如何进行子网划分
首先,明确两个前提

1.我们并不直接参与网络的构建,运营商才是构建内网与外网的角色
2.IP地址是有限的资源,这也就意味着肯定会有竞争的存在,你争我也争

但是我们不能说国家实力强或者说地域面积越大等等来判断IP地址如何划分,所以在现实中,不是按国家为单位进行划分,而是以地区进行划分IP地址,下面的讲述,只是为了讲解方便,以国家或者某个具体的城市为基本单位
首先,在每一个国家中都会存在一个国际路由器,它直接与全球构建的公网相连,并且每个都有着自己独有的网络号

假如我们中国分到的对应的网络号为0000 0010…/8,此时我们国内运营商就会在这基础上,进一步构建我们国家自己的公网,并将我们的IP地址进一步划分到不同省,由不同省中的运营商进行管理,子网掩码进一步扩大
比如广东地区分到的是0000 0010 0000 01…/14的IP地址
陕西地区分到的是0000 0010 0000 10…/14的IP地址等等

在陕西地区内部,我们会继续进行划分,将我们的IP地址进一步划分到不同市,子网掩码会进一步扩大

以此类推,市又可以划分下去,县,镇等等地区,当然,这里并没有考虑自己构建局域网的情况.
假如我们现在是西安市里面的一台主机,通过对应的子网掩码,与我们自己的IP地址相与,以此得到我们自己的网络号
假如我们想要向广东的一台主机发送消息,我们不知道它具体位置在哪,但是我们可以判断出它并不属于我们这个子网,于是往上交付,扔给出口路由器负责
不断往上交付,直至找到对应的子网,然后往下交付,子网掩码越来越大,便越来越靠近我们的目标主机,直至完全重合,便找到对应的子网,剩下就是根据主机号锁定对应的主机.
总结一下:

1.IP地址的实际划分是按照区域进行划分
2.子网划分不是只能进行一次,我们可以在划分出来的子网的基础上继续进行子网划分.
3.一个数据在路由的时候,随着数据不断路由进入更小的子网,其网络号的位数是在不断增加的,这也同时意味着IP地址当中的主机号的位数在不断减少,直至我们网络号相同,剩下就是根据主机号锁定对应主机.

私有IP和共有IP地址

在我们上述讨论的情况中,我们并没有涉及构建局域网的知识,而在实际中,构建自己的局域网是很常见的事,比如我们自己学校会搭建自己的内部网,实验室也有可能搭建自己的网络等等
到了某个城市的区域,我们就不再用公网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)
现在我连接的是校园内部网,所以用ipconfig指令查看对应的IP地址
可以看到为172.26…,属于我们的第二种情况,属于私有IP地址

我们连接对应的云服务器,使用ifconfig指令也可以查看对应的IP地址
直接连接的这个IP地址43.139.61.70:22就是云服务器的公网IP地址
inet 10.0.12.3就是我们的私有IP地址,对应的子网掩码netmask为255.255.252.0

还有一个lo,代表的是本地环回的意思
127.*的IP地址用于本机环回(loop back)测试,通常是127.0.0.1
本机环回会将数据贯穿网络协议栈,但最终并不会将数据发送到网络当中,相当于本机环回时不会将数据写到网卡上面.
本机环回的目的就是将数据自顶向下贯穿协议栈,进行一次数据封装的过程的过程,然后再自底向上贯穿协议栈,进行一次数据的解包和分用,用于测试本地的网络功能是否正常.

运营商

接下来,我们讨论一下运营商的相关问题

第一个我们讨论一下,为什么我们要付钱给对应的运营商?

答案也很直白,运营商为我们建设了各种基站以及配置各种路由器,有了这些通信的基础设施,我们才能实现跨网络数据传输,才能访问对应互联网公司的服务器,从而获取对应的服务,更不用说,我们之前提到过的诸如网段划分,构建子网等等一系列操作,实际都是运营商做的.
所以,运营商的地位其实是非常高的,不能一直吹捧互联网公司,它们固然为我们全国经济作出了巨大的贡献,但是没有运营商建设基础的通信设备,就谈不上互联网公司提供对应的网络服务,这也是我们所说的不是马云造就时代,而是时代成就马云.
而基站,路由器这些基础设施都是需要投资的,我们交网费的本质,对于运营商来说,实际就是将投资的钱赚回来

第二个具体运营商是怎么做的呢?

一般在我们家里除了路由器外,还会有另一台设备,称作调制解调器,也有的人称为猫

该设备的作用,就是纯解决硬件问题,将数字转模拟,模拟转数字
运营商会安排对应的工作人员上门提供服务,将调制解调器的一端入网口,现在一般都是用光纤,即我们俗称的光纤入户
另一端就是接在我们的路由器上
工作人员完成对相应路由器的配置,比如创建用户,设置账号和密码等等,只要该账号充钱,我们就可以连wifi上网了
在路由器中一般会配置两套密码,第一套密码用于账号验证,这个账号是我们对应的付费账号,在运营商那进行管理(唯一性),并检测你是否欠费
我们发送的所有报文,首先都会经过对应的路由器,此时路由器会对报文进行检测,看该账号是否欠费,假如欠费,报文会被直接丢弃,所以此时我们即便我们连上wifi,也发不出去消息
但是我们打10086的电话,却依旧可以打通,也是因为路由器检测到报文中对应的设置,没有丢弃对应报文的原因
还有我们俗称的翻墙,这个墙是什么?也就很好理解了.
这就好比我们的高速公路收费站,你给钱了,就可以上高速公路,不然就会在路口被拦截下来.
第二套密码其实就是无线路由器的密码,用于我们家里各种网络的验证,当我们输入对应的密码后,我们便被动态分配一个IP地址,可以将我们的消息发给对应的路由器,然后经过路由,到达对应的接收端.

第三个运营商是如何推广网络的呢?

这里有一点需要明确,运营商之所以能够迅速建设推广网络,背后肯定都是有国家大力支持,只有国家支持网络环境,才能做到迅速设计与建设网络.
不然运营商作为垄断企业,我们离不开网络,它的价格是可以被提到很高的,价格之所以可以降下来,并维持在一定水平,需要国家力量进行调控.
在早年,营业厅大面积被推广铺设,并且还有入网送手机等等很多的优惠,这些措施其实都是为了吸引老百姓入网
并且流量费用非常贵,1M可能就几毛钱,从某种意义上来说,也倒逼我们每个月及时将流量用完
而现在国内网络的建设基本已经完善,可以发现营业厅数量就少了很多,只有少数几间还开着,可能为我们提供注销卡等等服务,其实也是将门槛设高,将用户尽可能地留住
并且由于早年间流量费用非常昂贵,所以,此时流量即便增多,费用不断下降,也会有一种真实的对比感,尽可能将自己每个月的流量用完,或者购买更加贵的流量套餐

NAT技术

无论是有线还是无线,总而言之,运营商通过布设基站,路由器等等通信设备,从顶层设计并构建起了我们的网络
前面我们还提到过路由器内部会配备至少两个IP地址,此时也可以理解是什么意思了
我们家里面网络,称为一个小的局域网,它对应的IP,我们称为LANIP(子网IP),它是对内的
一般就是198.168.1.x

LAN口(Local Area Network):表示连接本地网络的端口,主要与家庭网络中的交换机、集线器或PC相连

而路由器对外连接运营商路由器的广域网IP,我们称为WANIP(公网IP),它是对外的

WAN口(Wide Area Network):表示连接广域网的端口,一般指互联网

图来自博主2021dragon
我们使用的电脑、家用路由器、运营商路由器、广域网以及我们要访问的服务器之间的关系大致如下:

所以,现在我们要将对应的报文,比如说src(源IP):192.168.1.200的数据报文,发送到对应dst(目的IP):122.77.241.3的主机(比如贵州部署的一台抖音服务器)


虽然我不知道它在哪里,但是我能判断出来,这台服务器绝对不是我这个局域网(内网)的,于是我们无脑直接向上交付,交给对应的家用路由器,这个过程也被称为缺省路由

同样的,家用路由器查找对应的路由表(类似于我们之前提到过的每个院对应的院号),每个人在这个转发集群都相互认识,发现这个网络号不对,依旧不属于我们这个子网,于是继续缺省路由,无脑继续向上交付给运营商路由器

此时运营商路由器,再将我们的数据报文发送到广域网中,一步步向上交付,直到找到对应的目标服务器为止,整个过程,和我们之前的描述是一致的
但是,问题来了,假如目标主机收到了对应报文,它又如何将对应的应答报文发送回我们的发送端呢?
这个问题一开始可能听起来很怪,但是仔细一想,发现确实是个问题
毕竟我们允许多个子网存在,而多个子网里面的私有IP地址是可以一样的,比如都是198.168.1.201等等,接收端如何知道哪台主机向自己发送数据了呢?报文就出现回不去的问题了
为了解决这个问题,我们便提出了NAT技术,它使得不同局域网当中能够同时存在两个相同的IP地址,还能够进一步缓解IP地址不足的问题

PS1:虽然不同局域网当中能够同时存在两个相同的IP地址,但私有IP地址不能出现在公网中,IP地址要能唯一标识公网上的一台主机,不然报文肯定回不去

PS2:如果希望我们自己实现的服务器程序,能够在公网上被访问到,就需要把程序部署在一台具有外网IP的服务器上,这样的服务器可以在阿里云/腾讯云上进行购买

在实际操作中,源IP地址在内网环境是会不断替换的
WAN口IP会不断被LAN口IP所替换
当我们的数据报文交付给家用路由器时,它会把我们的源IP地址替换掉,从src(源IP):192.168.1.200/24变为src(源IP):10.1.1.2/24

继续向上交付给运营商路由器时,它会把我们的源IP地址替换掉,从src(源IP):10.1.1.2/24变为src(源IP):122.77.241.4/24

这下我们我们的接收端服务器就可以根据对应的src源目的IP进行溯源了,两者换个位置,数据报文就可以沿原路返回了
当然,最终数据报文还是只能返回到对应的内网当中,也就是家用路由器的转发子群,依旧无法区分是哪台主机发送的消息,如何做内网转发呢?
所以,这里其实还需要补充对应的知识

NAPT(映射转换表)

我们将上述最后的过程可以简化为这样一幅图

现在报文顺利回到了我们的出口路由器,准备进入子网,但是此时却不知道是哪台主机发送的消息
但是其实这样的描述并不准确
更准确的说,应该是下面这两种说法

1.同一个主机可能有多个相同的进程,比如我一台电脑,同时登录多个QQ,此时它们的IP都是相同的,不同的只是端口号
2.不同的主机可能打开同一个客户端,此时它们的IP都是不同的,但端口号是有可能相同的,毕竟是OS系统随机分配

所以,我们的基本单位根本不应该是哪台主机,而应该是一个个进程,网络通信的本质应该是两个进程在相互通信
因此,对上面图更为准确的理解应该是将左端看作多个网络客户端进程,将右端看作服务器端进程
IP+Port端口号两者结合,给每一个进程起了一个唯一的ID
内网里的每个进程都有一个唯一的ID
外网也是如此
此时两者之间的交互才是一对一的

所以,在实际操作中,我们不仅仅要改变IP地址,还要改变对应的端口号,并将改变前后的IP+Port用一张表记录下来,这张转换表我们便称作NAPT
PS:路由器往往都具备NAT设备的功能, 通过NAT设备进行中转, 完成子网设备和其他子网设备的通信过程

比如上图中客户端A,客户端C同时访问服务器,它们的端口号是有可能相同,都是1025的,假如此时替换时,只替换IP地址,即便有NAPT也回不来
正确的做法,应该是IP和Port同时都发生替换,主机A原本为(10.0.0.10:1025)替换为(202.244.174.37:1025);主机B原本为(10.0.0.11:1025)替换为(202.244.174.37:1026)
并且转换表将这两种映射关系记录下来,那返回时,只要查表就可以知道对应是哪个进程发送的数据!
可能还有细心的人发现,为什么还要维护目的IP和Port号呢?
我们的NAPT可以只维护黑色框框里面的IP和Port号吗?

答案是不能
我们希望,想要进内网的数据,肯定以前都要在内网有过请求
换句话说,只有请求的主机能给出对应的响应,否则局域网的安全性将无法得到保障
我只要有对应NAT路由器的IP,就可以随便向我们内网发送数据了,直接穿透ANT路由器,我们也把这种行为称为NAT穿越

NAT技术缺陷

由于NAT依赖这个转换表, 所以有诸多限制:

1.无法从NAT外部向内部服务器建立连接; (我们也不希望有)
2.转换表的生成和销毁都需要额外开销;
3.通信过程中一旦NAT设备异常, 即使存在热备,所有的TCP连接也都会断开;

一些补充

这里还可以补充一些概念,比如我们可能会听说城域网,它是什么呢?其实无非就是运营商构建的一个大的局域网,以一个城市规模为基本单位建设,简称MAN(Metropolitan Area Network)
还有我们之前说国家已经在一步步部署IPV6的使用,具体如何一步步推行呢?我们把LAN口替换为IPV6,WAN口依旧使用IPV4,并且将两者建立联系不就可以了吗?
神不知鬼不觉,我们在发送报文的时候,WAN口IP被替换为LAN口IP,此时就已经开始按照IPV6协议来进行对应的IP地址管理了.
现在我们类似知乎,微博等等软件,我们在发送消息的时候,都有对应的IP定位,比如陕西,广东等等,也一定程度证明了我们的IP地址是可以溯源的

代理服务器

代理服务器可以分为两种,一种是正向代理,另外一种是反向代理

正向代理

对于正向代理,最常见的例子就是我们大学校园的内部网
这类服务器建在我们的校园机房里面
当我们连上校园网时,我们发出的所有数据在到达对应的出口路由器,转向其它网络之前,都会经过它

它主要有下面两大功能

1.缓存资源,提高访问效率,比如说我们从外网下载了一部战狼2电影,服务器内部就会对它进行缓存,当有另外一个同学也想看对应的资源时,速度就会大大提高
2.过滤非法请求,禁止我们学生访问一些非法网站


像是我们的翻墙技术,其实利用的就是正向代理
我们需要先在windows或安卓下,安装一些代理软件
它能够加密我们的HTTP请求,运营商这堵墙无法辨别,于是会对其转发
然后选择对应的代理服务器(节点),我们的加密请求就发给对应的代理服务器,它能够替我们访问外网
再将对应的响应数据加密返回给我们,运营商这堵墙同样无法辨别,于是同样会对其转发
最后我们代理软件对其解密,便获取到了对应资源

同样的,我们的游戏加速器也是利用正向代理的原理
我们不是直接访问对应的服务器,毕竟距离太远,速度可能很慢,而是直接访问代理服务器中的缓存数据,这样速度就能迅速提高

反向代理

对于反向代理来说,最常见于公司内部
在我们公司可能会有很多台服务器,我们称之为集群
当流量比较大时,也就是很多用户同时使用客户端访问服务器时,有可能出现只访问一台服务器的情况,一台服务器超负荷运转,而其它服务器就很清闲,造成忙闲不均,没有充分利用集群资源
反向代理服务器的作用就是将我们用户不同请求收集起来,较为分散给集群中的每一台服务器,做到负载均衡
一般这种服务器具有高并发的特性,能够将输过来的大量请求,同时并发分配处理(如Nginx)

和NAT技术的区别

1.从应用上讲, NAT设备是网络基础设备之一, 解决的是IP不足的问题. 代理服务器则是更贴近具体应用, 比如通过代理服务器进行翻墙, 另外像迅游这样的加速器, 也是使用代理服务器.
2.从底层实现上讲, NAT是工作在网络层, 直接对IP地址进行替换. 代理服务器往往工作在应用层.
3.从使用范围上讲, NAT一般在局域网的出口部署, 代理服务器可以在局域网做, 也可以在广域网做, 也可以跨网.
4.从部署位置上看, NAT一般集成在防火墙, 路由器等硬件设备上, 代理服务器则是一个软件程序, 需要部署在服务器上.

路由

数据"问路"

所以,数据转发过程就像我们跳一跳游戏一样,从一个节点,转发到另一个节点
所谓“一跳”就是数据链路层中的一个区间,具体在以太网中指从源MAC地址到目的MAC地址之间的帧传输区间
IP数据包的传输过程中会遇到很多路由器,这些路由器会帮助数据包进行路由转发,每当数据包遇到一个路由器后,对应路由器都会查看该数据的目的IP地址,并告知该数据下一跳应该往哪跳
整个过程,就像我们问路一样
假如还没有导航,我们想要去某个景点,最直接的方法就是问人
问人总共会有几种不同的情况(不考虑无视你的情况)
1.直接知道怎么去那
2.不知道怎么去那,但是知道谁认识路
同样的,我们的数据报文也是如此
它到达一个路由器节点后,也有两种情况
1.路由器查自己对应的路由表,发现自己直接就认识,直接数据报文转发
2.发现自己不认识,但是直接默认路由,向上交付,上一级路由器认识的人更多,它会告诉你,你想发送的主机的子网路由器位置
当数据报文到达对应的目标网络(经路由器经过路由表查询后,得知就是当前所在的网络),此时路由器就会将该数据转给当前网络中对应的主机
这也和我们之前所有的说法是一一对应的
在路上,我们根据子网IP进行查找对应的城市(先找对应子网)
到了对应目标网络后,再询问对应的入口路由器,目标主机在哪
(再找对应主机)

route指令

在linux下,我们可以输入对应的route指令,查看我们对应设备内核IP路由表,比如下面是我这台云服务器的IP路由表

路由表的Destination是目的网络地址
Genmask是子网掩码
Gateway是下一跳地址
Iface是发送接口
Flags中的U标志(using)表示此条目有效(可以禁用某些条目),G标志表示此条目的下一跳地址是某个路由器的地址,没有G标志的条目表示目的网络地址是与本机接口直接相连的网络,不必经路由器转发
值得注意的是,我们的主机也是有对应的路由表的,不是说只有路由器才有路由表,不然,我们主机怎么知道数据不匹配时,应该转发给对应的家用路由器
我们可以举一个小例子进一步理解
假设某主机上的网络接口配置和路由表如下:

Lg1.如果要发送的数据包的目的地址是192.168.56.3

跟第一行的子网掩码做与运算得到192.168.56.0,与第一行的目的网络地址(192.168.10.0)不符
再跟第二行的子网掩码做与运算,得到192.168.56.0,正是第二行的目的网络地址,因此从eth1接口发送出去;
由于192.168.56.0/24正是与eth1接口直接相连的网络,因此可以直接发到目的主机,不需要经路由器转发;

Lg2.如果要发送的数据包的目的地址是202.10.1.2

依次和路由表前几项进行对比, 发现都不匹配;
按缺省路由(default)条目, 从eth0接口发出去, 发往192.168.10.1路由器;
由192.168.10.1路由器根据它的路由表决定下一跳地址

总结:
(先看FLAG,状态是否是U,还有没有使用)
用Genmask子网掩码看对应的网络号是否匹配
1.如果目的IP命中了目的网络地址, 就直接转发即可;
路由表中的最后一行,主要由下一跳地址和发送接口两部分组成
2.当目的地址与路由表中其它行都不匹配时,就按缺省路由条目规定的接口发送到下一跳地址

路由算法

参考资料:
Dijkstra算法:https://www.youtube/watch?v=EFg3u_E6eHU&t=7s
距离向量:https://www.youtube/watch?v=jJU2AVX6gpU
距离向量:https://www.bilibili/video/BV1oJ411e7q5

Dijkstra算法

Dijkstra算法的核心思想是贪心算法,以局部最优,求解全局最优.
我们先来考虑一个最简单的问题,看下面这一张图

假如我们想要求解节点A到节点C的最短路径是什么?以及对应的消耗
应该如何求解这个问题呢?
从肉眼上不难直观看出,节点A到节点C总共有两条路径,(1)A直接到C,(2)A先到B再到C
这也符合我们的认知,一个节点到另外一个节点,无论选择哪条路,总归可以把它们分成这两种情况:

1.我和要去的节点直接相连,直接一步到位;
2.需要先去其它节点,再从其它节点到我们要去的目标节点。

正是由于第二种情况的存在,所以我们不能一步到位,自信满满的说出来最短路径是什么
但是有一种特殊情况,假如A到我们的目标节点是直接相连的(如图中的A和C),并且A到C的距离是A到其它节点的距离也是最短的(3<4<5<7)

则我们可以直接一步说出节点A到节点C的最短路径必定是A到C直接相连,不管你A到C有多少条路径可以选择
因为这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得 A顶点到 C顶点的路程进一步缩短了。
A顶点到其它顶点的路程肯定没有A顶点到 C顶点短,这就是A到C节点的最佳路径.
有没有可能将这种思想进一步延续下去呢?
答案是肯定的!
我们每次选择路径的时候,都是挑选消耗最小的路来继续探索(求解局部最优)
有没有可能出现漏掉最短路径的可能性呢?比如虽然它不是最短,但是总体相加起来还是消耗最小?
我们说绝对不可能出现这种情况,当我们把探索出来的节点加到我们的路径,然后往外继续探索的时候,实际上可以将它们整体又看作一个新的大节点,问题又回归到我们最开始我们构建的基本模型当中。

只不过此时的节点A是抽象的节点,它不一定只有一个实际的物理节点,可能是一条路径,但不管怎么样,只要它和我们想要去往的目标节点直接相连,并且消耗还最小,它就是最短路径,毫无例外。
我们可以举一个例子,进一步理解Dijkstra算法

假如现在要求节点X到其它节点的最短路径
不管三七二十一,先将X节点到其它节点的所有路径消耗列出来

从中挑选出最少的消耗,也就是节点X到节点V,然后将节点V加入路径
我们现在就可以直接说,自信喊出,它就是节点X到节点V的最短路径,没有之一!!!
因为这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得 X节点到 V顶点的路程进一步缩短了。
第二步,从V这出发,更新到其它节点的路径(假如更短的话),然后继续找最短路径(假如有多个一样,随便选一个),加入路径!

从V这继续出发,探索节点y,t,u这三个节点的路径消耗,是否比我们之前消耗的要短
原先由于节点X和节点t,u都不是直接相连的,因此,现在可以全部都更新为新的距离,从∞更新到7和6;
节点X到节点Y的距离则不用变,因为3 + 8 = 11比6要大。

更新完后,我们发现有多个消耗都是6,那我们就挑选X到y作为新的路径,从节点y继续向外探索.
有可能出现一条路径,它会比从从节点X直接到节点y要短吗?
绝对不可能。
此时节点X,V就相当于一个新的大节点X1,从这个大节点X1出发,所有相邻节点消耗里面,和y节点是最小的(和X节点相连的点,我们在第一次探测的时候已经全部探测过了,这也是为什么我们第二次是从V节点出发开始探测.)
PS:不要主观认为X1节点只能固定是节点X和节点V,它是可以动态变化的,每次我们挑选最短路径,实际上就是在确定我们的X1节点.
因为这个图所有的边都是正数,那么肯定不可能通过第三个节点中转,使得 X1节点到 y顶点的路程进一步缩短了
第二次探索后,其实可以等价为下图(其中x1节点和y节点本来有11和6两条边相连的,但是我们只挑选最短的作为连接节点的边)

因为这个图所有的边都是正数,那么肯定不可能通过第三个节点中转,使得 X1节点到 y顶点的路程进一步缩短了,故到y节点的最短路径就是6

以此类推,第三步,我们从节点y出发,继续向外探索(此时X1节点等于节点X和节点Y的组合),更新到其它节点的路径(假如更短的话),然后继续找最短路径(假如有多个一样,随便选一个),加入路径!

结果发现,没有一个消耗需要更新,那我们直接选出其中最短的,挑一个{x,w}

而实际上,假如真正理解背后的思想,那当出现多个距离相同的时候,它们就已经是最短路径了
即节点x到节点y(x->y),节点w(x->w),节点u(x->v->u)就是最短路径,这三个节点完全没有必要再研究下去
真正要研究的只有节点t和节点z而已
当然,为了完整,我们还是把剩余所有步骤补充完整,以供参考
第四次从节点w出发(此时X1节点等于节点X和节点w的组合),没有更新(9大于6),选择最短消耗6


第五次从节点u出发(此时X1节点等于节点x,节点v,节点u的组合),由于3 + 3 + 2 = 8>7,因此还是没有更新,选择最短路径7

最后一次从节点u出发,由于和z不是直接相连,都没有必要关注下去了,直接得出结论
最下面一行,即为节点x到不同节点的最短路径与消耗

当然,在实际编程中,并不是采取我们上面列表的形式,还有一点点不同.
第一. 我们需要增加一个数组(列表),用来标记未被选中的点Unvisited Nodes

第二. 我们不需要记录下完整的路径,只需要记录上一跳是哪一个节点即可。

其余的操作都是相同的
总结一下,可以概括为下面五个步骤
第一. 将所有点标记为未访问(Marked all nodes as unvisited)
第二. 初始化目标起始点到临近点的所有距离(Assign to all nodes to a tentative distance value)
第三. 对于当前访问的点,计算它到所有未访问邻居节点的距离(For the current node Calculate the distance to all unvisited neighbours)
假如比之前记录的距离要短,则更新距离,并记录对应的上一跳节点(update shortest distance,if new distance is shorter than old distance);反之,则不做调整
第四. 标记当前点为已经访问(marked current node as visited)
第五. 从未访问的节点里面挑选距离最小的作为新节点继续访问(choose new current node from unvisited nodes with minimal distance),即不断重复第三-第五个步骤,直到所有节点都被访问过为止

距离向量算法

Bellman-Ford公式

了解完Dijkstra算法后,我们可以开始了解第二种路由算法——距离向量算法
但是我们说不要担心它太难,我们要耗费很多精力去学习.
因为它的本质其实和Dijkstra算法是一样的思路,只不过每台路由器是相互配合,而不是像Dijkstra算法单打独斗.
我们先来看一下它的核心式子(Bellman-Ford公式)


比如下图中,x只有一个邻近点v, C x , v C_{x,v} Cx,v= 3, D v ( y ) D_{v}(y) Dv(y)= 5 < 6

这个所谓的Bellman-Ford公式,其实很直观
假如我想要求解节点x到节点y的最短距离 D x ( y ) D_{x}(y) Dx(y),它必定经过和自己直接相连的点v,把自己的直接相连的节点v作为自己的第一跳,距离为 C x , v C_{x,v} Cx,v,加上节点v到节点y的最短距离
由于直接相连的节点v可能不止一个,所有要计算所有的 C x , v C_{x,v} Cx,v+ D v ( y ) D_{v}(y) Dv(y),然后从中取最小值,它就是我们的最短距离 D x ( y ) D_{x}(y) Dx(y)

有没有觉得这种思想很类似?其实它和我们的Dijkstra算法的核心思路是完全相通的(将大
问题拆成小问题,逐步击破),Dijkstra算法是求解局部最优,一步步到全局最优,我们的Bellman-Ford公式也是如此,想要求解节点x到不相连节点y的最短距离,它肯定等于到它
的邻近节点的距离,再加上邻近节点到节点y的最短距离。
我们还可以举一个例子,进一步理解我们刚刚的说法
假如我们想要求解 D u ( z ) D_{u}(z) Du(z),和u直接相连的节点为v,x,w
不难看出来 D v ( z ) D_{v}(z) Dv(z)= 5, D x ( z ) D_{x}(z) Dx(z) = 3, D w ( z ) D_{w}(z) Dw(z)= 3

D u ( z ) D_{u}(z) Du(z) = min{ C u , v C_{u,v} Cu,v + D v ( z ) D_{v}(z) Dv(z), C u , x C_{u,x} Cu,x + D x ( z ) D_{x}(z) Dx(z), C u , w C_{u,w} Cu,w + D w ( z ) D_{w}(z) Dw(z)} = min{2 + 5,1 + 3,5+3} = 4

三步走

了解了Bellman-Ford公式后,我们便可以了解具体vd算法是怎么操作的了.
其实并没有引入什么新的思想,不过每台路由器开始协同配合起来
对于每一个节点,它都会进行下面三个操作(循环进行,直到所有节点的VD都不改变)
1)等待邻居v发来自己的距离向量表DV
2)我收到之后,便知道邻居v到其它节点y的距离 D v ( y ) D_{v}(y) Dv(y),然后根据我们刚刚学过的Bellman-Ford公式重新更新我们自己的距离向量表DV
3)假如我的距离向量表DV有发生更改,那我就将我新的DV发送给我自己的邻居,让它也跟着一起变动DV,否则什么都不做

从我们上述的描述中也可以看出VD算法有两大显著的优势
第一.它是异步的(asynchronous),不一定所有路由器都要相约一个时间固定进行更
新自己的VD,就算每台路由器的迭代速度不同,只要经过一定的时间,最后都能收敛到稳
定状态
第二.它是自起停(self-stopping)的,用人话说,就是只有必要的时候,才将自己的
VD发送给邻居,假如没有收到任何消息,不会采取任何动作.

我们可以举一个例子加深理解,假如现在t = 0时刻,a路由器的DV表如下图所示

t = 1时刻,每个路由器互发自己的DV

然后每台路由器根据Bellman-Ford公式重新更新自己的DV

比如说此时路由器b,收到了来自路由器a,c,e的DV,就可以根据Bellman-Ford公式更新自己的DV

类似的,路由器C也是这样更新自己的DV

最后,假如更新后的DV有发生变动,则将新的DV发送给自己的邻居

T = 2时,继续重复上述操作,直到稳定,所有的DV都不再更新为止.
而不要看这个过程很复杂,其实每操作一次,探测的距离就越来越广(state information diffusion),所以实际上更新的速度还是很快的.(扩散式)

link lost changes

当节点路径损失发生改变的时候,也是同样的操作
比如节点x到节点y的损失由4变为1

节点路由器y探测到节点损失发生变化,首先更新自己的DV,然后将它发送给自己的邻居节点x和z
接下来,就是重复上述之前步骤,不断迭代,直到稳定下来即可,这里不再赘述.

但是这样会导致一个致命的问题出现,假如节点损失发生变化,不是往小的变,而是往大的变,而且还不是一般的变大,是变得特别大
即两个节点之间可能不再直接相连,物理链路出了问题,如下图的由4变成60

此时就会出现循环更新DV的问题
节点y看到自己和节点x相连的损失变成60,这也太大了,但是从节点z到节点x的距离仅仅只需要5,根据Bellman-Ford公式, C y , z C_{y,z} Cy,z + D z ( x ) D_{z}(x) Dz(x) = 6,然后节点y会告诉节点z, D y ( x ) D_{y}(x) Dy(x) = 6的新DV,以此类推,虽然最后依旧可以更新稳定状态,但是这样经过的时间就非常久了,而且RIP报文在两个节点之间一直来回发送,所消耗的资源会很夸张.

解决这个问题(减少出现概率)有很多办法
这里简单列举其中一种
我们可以限制最大路径为15(16即为不可到达),一旦cost超过15,则直接丢弃,判定此时节点不再相连,这样就不用循环到60才傻乎乎的停止了.(简单粗暴,但有效)

分片和组装

为什么需要分片

在学习TCP滑动窗口知识的时候,可能会有人有疑惑,为什么不把滑动窗口里面的范围报文直接打一个包,整体发出呢?
为什么一般教材都是将滑动窗口拆成多个报文的形式,然后在对端传输层又重新根据TCP序号拼接起来?
根本原因其实是因为我们的数据链路层
它规定了它传输的有效载荷(IP报文)不能超过MTU(Maximum Transmission Unit),这个值一般为1500字节
在linux系统下,我们可以输入对应的ifconfig指令来查看对应的MTU大小

但是,IP层可以决定数据发送的大小吗?
答案是不可以,IP决定不了数据发送的大小,它只是一个跑腿的
这就很难受了,TCP层往下给IP层数据,然后数据链路层告诉你IP层数据不能超过MTU个字节,不然我会直接丢弃,IP层表示自己夹在两个人之中,里外不是人.
所以分片的出现是必然的,它是数据链路层运输数据必须小于MTU个字节所导致的最终结果.
这就好比我们送快递,数据链路层就相当于我们的顺丰快递,它的货车规定了运送的货物一次不能超过多少kg;
TCP就是我们的顾客,它决定快递公司要送什么货物;
而IP层就是我们顺丰快递的工作人员
假如某个大型货物(数据)超过了对应的重量,货车是无法直接运输的,必须将它拆散成不同小份,分别直接运输,然后考虑拼装,这就是所谓的分片和组装(它是由IP层完成的)
但是分片越多越好吗?后面我们会讲述分片的特征,或者说坏处,在网络中,我们实际要分片尽量成为少数情况
我们说虽然层与层之间是解耦的,但是你TCP层也应该尊重IP层,不能不考虑到下层的压力,即分片策略会引起TCP层发送策略的调整
怎么调整呢?
不就是我们前面提到过的将滑动窗口数据拆成多个报文的形式发送

怎么做

那具体怎么做呢?

在我们之前讲述IP报头的时候,曾经提到过三个字段,说它们和分片有关,然后就没有进一步讲述了,我们就在这节进一步讲述它们分别是什么功能

3位标志字段:

它的本质其实就相当于一个位图,总共三位
第一位现在不用
第二位置为1表示禁止分片, 这时候如果报文长度超过MTU, IP模块就会丢弃报文.,为1表示允许分片.
第三位表示"更多分片"
如果没有分片,该位是0
如果有分片,但不是最后一个分片,该位为1;
如果有分片,但是是最后一个分片,该位依旧为0

13位分片偏移(framegament offset):

是分片相对于原始IP报文开始处的偏移.
其实就是在表示当前分片在原报文中处在哪个位置. 实际偏移的字节数是这个值 * 8 得到的.
因此, 除了最后一个报文之外, 其他报文的长度必须是8的整数倍(否则报文就不连续了).
假如没有分片,片偏移就为0

16位标识(id)

唯一的标识主机发送的报文.
如果IP报文在数据链路层被分片了, 那么每一个片里面的这个
id都是相同的
它就好比我们TCP层学过的序号字段,可以根据这个字段来判别该报文是哪个主机发的
举个简单的例子
现在我们对一个IP报文进行分片
由于它们都是相同的IP报文,所以16位标识都相同(相当于ID),这里我们写为1234
除了最后一个报文外,其它分片的更多分片标志位应该都为1
片偏移是分片相对于原始IP报文开始处的偏移量,分别为0,1500,3000

细节

但是上面仅仅是粗粒度的了解了分片所要了解的预备知识而已,我们还需要进一步了解其中的细节
我们知道网络通信双方是全双工的,即发送端和接收端两者的地位对等,因此,下面的讨论就直接从服务器端视角看待,客户端也是一样的.

细节1

第一,在同一个时间段内,是有很多台不同的主机(多个客户端)访问服务器的

那请问你怎么区分不同来自不同的客户端的数据报文?
答案是我们之前学过的源IP地址,有了它我们便具有区分不同客户端的能力
进一步延申,16位标识能够表示的主机数目其实非常少,有没有可能16位标识重复呢?
答案是有可能的,但是我们不怕,源IP地址可以对它进行进一步过滤

细节2

第二.你怎么知道一个IP报文分片了?
思考的角度当然肯定是从我们上面学过的三个字段出发
16位标识区分是否是同一个IP报文,在这里没有什么用
看来关键就是我们的3位标识和片偏移
假如片偏移不是0,那提取标识,那肯定分片了(更多分片是否为1,也可以判断)
假如片偏移是0,此时可能该数据是开头,进一步我们看更多分片,假如它也是1,那肯定也分片了

细节3

第三.你怎么保证把分片收全了?
我们说IP报文被拆成了一块块小片数据,有开头,中间,结束数据
所以这个问题的本质,其实就是问我们,你怎么保证开始,中间,结束的这些报文全部没有丢呢?
换言之,我们这些报文的特征是什么呢?
不难得出,它们的特征如下:

开始报文:更多分片为1,偏移量为0
中间报文:更多分片为1,偏移量>0
结束报文:更多分片为0,偏移量>0

有人会说,不是还有种情况,两个字段都为0吗?
那不就是独立报文?就没有分片这一说法
通过两个字段就可以穷举出所有的可能性,并顺手解决了知道分片和收全分片问题,不可谓不优雅

细节4

第四.收全后,你怎么把它们组装到一起呢?
这个问题我们说解决收全问题后,就变得异常简单
a.头尾丢失,我们能够知道
b.每个分片报文都有对应的偏移量
所以整个过程其实就是收全报文后,对偏移量进行排序,线性遍历即可.

细节5

第五.你怎么保证组合在一起的报文一定是对的呢?
IP报头有没有可能在传输的过程中发生错误呢?
我们说有可能,但是我们不怕
因为在IP字段中有一个16位首部校验和字段,它能够校验我IP报头是否发生错误
报头没问题,有效载荷有没有可能出问题呢?
答案也是有的,但是我们也不怕
因为IP报文的有效载荷,其实就是上层交付下来的TCP报文!
TCP报文里面不是有16位校验和字段吗?它能够保证我们的TCP报文数据不会在传输过程中发生错误.
所以,有人会说为什么IP报头还需要设计一个16位首部校验和字段,这不会冗余吗?
现在便可以回答这个问题,整个设计并不冗余,相反,环环相扣
IP报头16位首部校验和字段校验它自己的IP报头,不保证可靠性
TCP报头16位校验和字段校验有效载荷,即数据是否出现问题,保证可靠性
可靠性由上层来保证

细节6

如果收到重复报文(分片),如何处理呢?
对于这个问题,我们说序号是有可能重复的,但是源IP可以对它进行过滤,这样接收端就可以区分不同客户端
所以问题被简化为,同一个客户端,发送重复分片的问题
而在IP层会对分片进行组装,根据对应的片偏移线性遍历,片偏移相同后,旧的和新的分片会继续对比,假如数据什么的都一样,便直接丢弃.
但是,丢弃没有成本,比对没有成本吗?OS操作系统会被你弄得心态崩溃的,一直发重复的分片过来
并且,假如丢包率为百分之一,一个报文丢包的概率,和多个分片报文丢包的概率,明显后者高于前者
过多的分片,容易增加丢包率
除了这个角度外,还有数据补发的问题.
只要分片报文当中的某一个出现了丢包,此时传输层都需要将数据整体进行重传,因为传输层并不知道底层IP对数据进行了分片,当传输层发送出去的数据得不到应答时传输层就只能将数据整体进行超时补发
而为什么这样设计其实也是有原因的,我们固然可以让IP层知道分片数据对应的是TCP滑动窗口的某个报文中的哪个部分,然后让TCP层对那一部分数据进行重传即可,但是这样两层之间关系就会很紧密
所以实操中并不会如此,层与层之间配合,但也需要解耦
所以,我们之前提到过,在网络中,分片应该让它尽量成为少数情况,能不分片就不分片
那如何做到尽量少分片呢?
TCP滑动窗口分块向下交付,让TCP协议发送的数据量不要太大
所以TCP层会有一个MSS(max segement size)的概念,也被称为最大段尺寸
假如不考虑IP报头的特殊选项,则IP报头长度为20字节,那有效载荷的总字节个数为1480
同样,我们不考虑TCP报头的特殊选项,那TCP报头同样是20字节,所以TCP有效载荷数据最多为1460字节,MSS此时便是1460字节
PS:MSS不一定是1460字节,假如有选项,则可能更小
TCP在建立连接的过程中, 通信双方会进行MSS协商.(不单单交互双方窗口大小)
最理想的情况下, MSS的值正好是在IP不会被分片处理的最大长度(这个长度仍然是受制于数据链路层的MTU).
双方在发送SYN的时候会在TCP头部写入自己能支持的MSS值.
然后双方得知对方的MSS值之后, 选择较小的作为最终MSS,对应MSS的值就是在TCP首部的40字节变长选项中(kind=2)

我们自己进行一次分片

现在我们已经了解了整个IP层关于分片和组装的过程和细节问题,现在可以自己体验一次分片
现在我们面前有一个3000个字节的IP报文(包括IP报头20字节)

怎么对它进行分片呢?
永远不要忘记一个原则,分片以后,每一片都是IP报文,也有对应的报头!
所以直接对半切,各1500个字节,我们就说白学了.
正确的做法是,第一段可以直接切1500个字节,然后,后面对应有效载荷的数据,分为1480个字节和20字节,并都要补上对应的IP报头
IP报头里面的位偏移,分别填充上对应的原始偏移量
16位标识填充为相同的IP报文序号
3位标识的更多分片分别填上1,1,0

ICMP协议

一项技术被发明出来,一定要留出对应的调试接口
不然我们都无法检测它是否顺利,哪里出了问题
所以在网络层中,同样存在另外一个网络层协议——ICMP协议
它的作用就是进行网络调试
一个新搭建好的网络, 往往需要先进行一个简单的测试, 来验证网络是否畅通;
但是IP协议并不提供可靠传输. 如果丢包了, IP协议并不能通知传输层是否丢包以及丢包的原因
此时ICMP协议便闪亮登场
它的主要功能有如下几点:

1.确认IP包是否成功到达目标地址.
2.通知在发送过程中IP包被丢弃的原因.
3.ICMP也是基于IP协议工作的. 但是它并不是传输层的功能,因此人们仍然把它归结为网络层协议;
4.ICMP只能搭配IPv4使用. 如果是IPv6的情况下, 需要是用ICMPv6;

主要类型有两类

一类是通知出错原因
一类是用于诊断查询

当然具体原理我们并不了解,只提一个重点,ICMP协议是直接绕过TCP/UDP传输层进行网络调试的,这也就意味着它根本就没有所谓端口号的概念
我们在应用层实现的ping命令,它的底层实现就和我们的ICMP协议有关,因此当有人问你ping对应的端口号是什么,这实际是一个大坑.

本文标签: 协议 网络 IP