这学期学计算机网络,但是我感觉我们学校的某位任课老师讲的不太行,听完introduction部分以后,我决定脱离学校课程,直接按照408去学习,备战考研。
我购买了408推荐参考教材《计算机网络》,是谢希仁版本的第八版,网上对应的视频不是很多,我找到了方诗虹老师的课程,他用的书就是408的。虽然方老师的任职学校并不是很好,但是就我的经历来说,408本来就没什么特别深的东西,关键在于能不能讲好,经过我的调查,方老师评价还不错,所以我就直接来听她的课,搭配书籍食用。
本文是系列笔记中的第二篇,内容为网络层、传输层、应用层。
计算机网络笔记——概述、物理层、链路层(方老师408课程)
计算机网络笔记——网络层、传输层、应用层(方老师408课程)
[考研整理——还没写]
最重要的一层,没有之一。
链路层解决了局域网
内部帧
的传输,而网络层的关键任务是将世界上不同的局域网连接在一起,即异构网络之间的连接和资源共享问题,细分为如下方面:
在设计网络层前,关键的问题是网络层到底要实现什么功能?进一步说,就是网络层是否应该负责可靠传输
因为全球互联网络的传输路径太长了,每一个节点都有一定概率出问题,叠加起来难以保证传输质量。因此现在是以不可靠传输为主,可靠传输为辅:
无连接的
、尽最大努力交付的数据报服务
最开始人们认为分组交换比较好,后面大家觉得为语音、视频服务的电路交换更好,现在随着互联网邮件,web应用的出现,大家又觉得为这种突发流量服务的分组交换更好。
时至今日,分组交换已经占据绝对主流,物理网络也已经定型。改变硬件是很困难的,所以现在的应用程序也都是在适应分组交换网络,不需要保证带宽。
目前使用虚电路网络的只有MPLS和租用专线等,很少,很贵。
不同局域网使用的技术不同,无法直接通信。把世界的所有网络技术统一不够现实,也会限制技术发展,不如就使用中间设备转发。
这个中间设备叫路由器,IP协议运行在路由器上。
使用路由器将世界上的局域网连在一起后,就形成了一个虚拟互联网络,即互联网,用户只需要接入互联网,就可以和另一台接入互联网的设备通讯。
网络层其实不止有一个IP协议,而是有三大类:
下图中,一个信息要经过5跳,前4跳都是交付给路由器,是间接交付。而最后一跳是交付给目标主机,这是直接交付。
IP数据报有如下部分:
接下来逐步解析首部:
核心其实就是3个,从哪里来,到哪里去,干什么用。
其他的各种字段都是用来辅助的。
举个具体的例子。一个ICMP数据报,20B的首部,8B的ICMP首部,3500B的其他数据,所以数据部分为3508B。
以太网MTU=1500字节(以太网帧最大是1518字节,MTU只是其数据部分,即IP数据报),所以假设报头只有20B,则IP数据报片的数据部分最多只有1500-20=1480B
所以3508B切分为3片,bit位为0-1479,1480-2959,2960-3507。
逐部分解析:
IP地址和接口
一一对应,不会重复。一般一台电脑只有一个接口,也就只有一个IP地址,但是一个路由器有很多接口,则会有很多IP地址。
IP地址是32位的,人手一个都不够用,更别说一个人有好几台设备了,毕竟当时也没想着给全世界的人用。于是现在诞生了128位的IPv6,号称给地球的每一粒沙子一个IP地址,但是此处我认为,现在的视野还是局限在地球的,万一以后扩展到了宇宙,IPv6还是不够用,还得用新的网络协议。
因为IPv4不够用,所以在管理方面是很严格的。最上面是世界,之后是地区级(RIR),地区下面是国家(NIR),国家下面是ISP(比如移动联通电信)
ISP拿到一批IP,之后根据自己的业务自行分配。
基本的IP地址结构分为两部分,网络号代表了你在哪个局域网,主机号代表了你是这个局域网里的哪个设备。
这种分级结构优点如下:
32位2进制太难记,于是切分成4部分记录,即点分十进制记法。每一部分占8bit,所以其范围是0-255。
一个地址对应一个局域网,两个局域网是不可以合并的,所以一个IP地址的主机号位数越多,那么其所在的局域网可以容纳的主机数量(IP数量)也就越多,由此根据位数对IP地址分级,通过开头的bit位特殊序列区分类别,A最高,C最低。
除此之外,还有一个1110开头的多播地址,后面会说。1111开头的特殊地址保留备用。
IP地址细节很多,首先要理解固定位以及两类特例:
由上即可得出下表,括号里是具体计算过程,先解释一下1,4列:
刚开始学,可能会认为这三类分法会浪费掉一些网络号,其实不完全是,ABC三类的IP地址的范围是连续的
0
000 0000到0
111 1111,即0-12710
00 0000.0到10
11 1111.255,即128.0到191.255110
0 0000.0.0到110
1 1111.255.255,即192.0.0到223.255.2551110
0000.0.0.0到1110
1111.255.255.255,即224.0.0.0到239.255.255.2551111
0000.0.0.0到1111
1111.255.255.255,即240.0.0.0到255.255.255.255也就是说,从0.0.0.0到255.255.255.255,这5大类网络的划分理论上是连续的,只不过具体上有一些浪费,浪费在哪里呢?
所以说到头,实际上没有浪费,只需要知道两个特例让IP地址不完全连续,并且知道断在哪里即可。
一个网络号里主机号太多,可以划分几个子网出来,便于管理。
简单来说,子网号就是取原来主机号的前m位,因此,m取决于你要分的子网数量。如果是5-8个子网,那么就取m=3
可以看出,子网划分会浪费一些IP地址:
因为路由器是以网络号转发的,那么可变长度的子网号就是个问题。由此,干脆就制造一个固定长度的子网掩码,网络号部分全1,其他全0,只需要让IP地址与子网掩码进行与运算即可得到网络号+子网号=当前子网的网络地址
如何判断多个IP地址是否在同一个网络?其实就是从子网掩码上下手:
这两个是充要条件,代表两个IP的网络地址位数与值一样,其实就是网络地址完全一样。
IPv4分类IP地址便于管理,在前期做出巨大贡献,但是缺点很多,后期逐渐凸显:
前缀长度灵活可变,因此地址块大小是任意的,而不是ABCDE这几类大小。其中有三个特殊前缀长:
其中注意31,其实按照规定,主机号是不能全0全1的,但是31比较特殊,就允许这么做了。所以用30替代31也是可以的,30的话,就有4个主机,按规定去除00和11,同样是只剩下两个主机号可用。
总的来说,30和31都可以用,老习惯用30,新规定可以用31。
连续的网络前缀可以合并(路由聚合,构成超网),变成一个更大的局域网,而网络号不可以,两个C类就是两个C类。
下图中,四个25长度的前缀先两两合并,变成两个24长度前缀,再两两合并成一个23前缀。
聚合有什么好处呢?虽然没办法解决IP地址不足的问题,但是可以让网络前缀总量变少,这样就可以减轻路由表的条目数量,提高路由速度。
来个真实的例子:
IP分组经过路由器一跳一跳的转发,转发依据转发表。
转发表基本格式为(目标网络地址,下一跳地址),解释一下这两项:
转发的时候,路由器需要扫描路由表,判断出要转发的目标IP所在的网络地址是什么,找到对应的网络地址就可以转发到下一跳。
关键在于,IP数据报里没有网络前缀长度的信息,因此不可能具体计算出目标IP的网络地址的
回归问题本身,其实转发不需要知道目标网络号具体是多少,只需要知道我要选择转发表里的哪个条目即可,也就是只需要匹配就行,具体你是什么我不用管。
匹配那就简单了,只需要把前缀长度信息记录在转发表里,到时候分别用转发表里的IP和目标IP计算各自的网络前缀,看看是否一样,一样就匹配。
如果使用分类的IP地址,那么两个IP如果掩码不一样,计算出的网络号是不可能一样的,因为掩码不一样就代表类别不同,则前四个bit就不会一样。本质上,是因为不同类别的IP网络号是互斥的,没有交集。
但是CIDR不一样,它不分类,所以一个短的网络前缀(111)完全有可能包含一个长的网络前缀(1110和1111)。此时,一个目标IP,使用不同长度计算出的两个网络前缀,可能会和转发表里的两项同时匹配,匹配的这两个网络前缀必然是一个包含另一个。
既然都已经包含了,那么我们不妨选择最细致的一个网络前缀,就好比送快递地址越细越好,即选择最长的网络前缀,其定位最精确。
下图中,选择26那个。
正因为有这个原则,所以一般在排列转发表条目的时候,是按照前缀长度逆序排列的,这样的话,按序扫描的第一个就是最佳匹配项。
为了加速扫描,还有一个方法就是用唯一网络前缀构造二叉线索树。具体原理还是有点模糊,如果考研要考的话我后面再研究一下TODO。
如果都扫不出来,最后一般会放一个0.0.0.0/0,即默认路由。
按照前缀匹配方式计算,这个条目可以和任意一个IP匹配,也就是默认缺省转发。其实就是,反正已经匹配不到了,就千方百计地把你送出去,说不定别人能把你转到目标位置,尽最大努力。
不过,还有一个特例,即255.255.255.255,这个受限广播IP是不允许通过缺省路由转发的,否则会引起全球的广播风暴。直接广播的IP是可以的,比如2.255.255.255/8
总的来说,转发表大致有四种路由:
路由器通过IP地址转发帧,那MAC地址还有用吗?其实问这个问题前,你应该问,为什么会有IP地址,MAC不也是唯一的吗?
因为IP地址可以通过值来确定是否处于一个局域网,但是MAC地址没有明显的规律,只是唯一,但是不适合路由转发。
但是呢,MAC地址作为链路层的关键技术,是网络层的基础,所以MAC必不可少。总之IP和MAC都必不可少,只是功能不同,IP地址主要用于划分局域网和路由,而MAC地址是在局域网内部使用,用于区分主机的:
具体转发时,首先在网络层封装IP地址,之后在链路层封装MAC地址,发送到另一台机器,向上解封,到网络层处理完后继续向下封装。
从网络层来看,源地址和目标地址都是不变的,因为使用的是IP地址,从链路层来看,原地址和目标地址一直在变,因为机器在变。两者从各自的层次来看,是互不相关的,实际上是在不断转化的。
在路由转发过程中,从下往上走很容易,因为只是解包而已。但是从上往下就比较难了,在网络层,我只有下一台机器的IP地址,必须获取其MAC地址才能封装链路层的帧,ARP协议规定了通过IP获取MAC地址
的流程。
实际情况要更加复杂一些,但是不外乎是这三种ARP的组合:
到现在为止,网络层整体架构已经成形,但是还有一些问题需要解决,完善。
ICMP用于辅助网络层工作,简单来说就是发数据前先确认一下能不能跑通,能再发,不然发一堆路上全没了,浪费。
ICMP是用于报告网络层状态的,如果出问题,可以让源头知道问题所在。
类型值可以分为两类:
什么时候会有ICMP差错报告报文呢?就是当路由器丢弃了一个分组时,就会反向朝发送端回馈一个ICMP。
回馈是在网络层是装在IP数据报的数据部分的,但是ICMP差错报告报文不仅仅只有ICMP的前8字节,还要加上丢了的那个分组的IP报头+数据前8字节。
之所以要加这些辅助信息,是为了帮助源头确定丢的是哪个分组,以便于后面断点续传之类的。
什么时候不发送ICMP差错报文呢?
这个报文比较有趣,是强制回复,因为是协议规定的,只要你收到了,就要回。
利用IP数据报中TTL字段,因为是不可能的端口号,所以会一直走,直到目标机器。中途TTL=n的,会在n个路由器被丢弃,回传ICMP报文,这就获取了路由器IP。
理想丰满,现实骨感,路由器也不能让你就这么获取了人家的IP,所以有一些路由器会屏蔽,告诉你请求超时。
ping就是典型的回送请求和回答工具。
回送请求可以被利用,针对目标主机实施DDoS攻击,因此可以在主机前面架一个路由器。
协议规定主机接收到回送请求后得回答,又没说路由器得回答,因此路由器一收到了回送请求,就直接丢弃,从而屏蔽发往被保护主机的回送请求。
IPv4有大问题
但是协议一旦定下来,就已经无法去改变了,因此只能开发新一代协议,IPv6,这个新的协议需要解决IPv4的问题,同时要对IPv4有兼容能力,逐渐过渡。
IPv6的改进很多,内容如下,可以看完后面回头再看一遍:
这些变化一开始你可能看的一头雾水,但是你后面看完具体的IPv6数据报格式以后,就会理解的更深了。
IPv6数据报=基本首部+有效载荷(扩展首部+数据部分)
IPv6把IPv4中很多功能都从首部剥离,放到扩展首部里,更加灵活,成本更低。因此扩展首部虽然有首部功能,但是实际是在载荷里。
基本首部从IPv4的20字节变成了40字节,但是内容却简短了很多,主要是两个128位(16字节)地址占了大头,其他部分逐一解释:
流
指的是一系列IPv6数据报,属于同一个流的数据报流标号一样,在资源预分配的时候很有用。
首部字段数大大减少,因此路由器处理速度很快,转发速度提升了很多。
之所以把一些东西放到扩展首部里,是因为这些东西其实不那么经常用得上。比如分片,其实在IPv6里,分片是发送端预分片的,不到特殊情况干脆就不需要分片,其他也是如此,如果每个数据报都需要检测这些东西,那就有点麻烦了,不如就放进可选里。
注意,一定要按照顺序出现,比如要分片,前面的逐跳和路由就得填上。
128位,已经大到想不到了。
通信方式多了个任播,就是在周围找个最近的发过去。广播貌似是没有了。
128位很长,如果用点分10进制,那么将会很长,因为是8数变3个数,压缩率比较低,因此IPv6用冒号16进制,4数变1数,压缩率更高。
冒号十六进制中,128bit,每个节有4个16进制数,即16bit,因此可以分为8个节。
正如IPv4的网络地址,类似于CIDR,IPv6也是用斜线记法,但是彻底取消了子网掩码。IPv6和斜线记法一起用还有个好处,就是写网络前缀的时候,可以把后缀零压缩了,很方便。
说到后缀,点分十进制和冒号16进制其实是可以互相转换的,一个16进制段可以转换成2个IPv4段,这种混用经常在IPv4到IPv6的转换中使用:
但是需要注意,IPv4只能是后缀部分。
前两种是从IPv4继承过来的,新加了一个无状态地址自动配置。凭什么能保证地址独一无二呢?
IPv4太广泛了,因此只能慢慢过渡。
DNS通过域名,确认想去的地方是IPv4还是IPv6。
并不是说所有机器都得这么干,只是可以在IPv6和IPv4网络之间,用双协议栈机器转换。
针对双协议栈缺点,隧道技术就是要保证数据报的完整。
把IPv6数据报当成IPv4数据报的数据部分来传。
缺点是,徒增成本。
实际上,是ICMPv6把这两个吞并了,更加复杂,强大,多了ND协议和MLD协议。
路由表里的条目很多,人工编辑只能编辑一小部分,更多的是自动通过算法生成的,这个自动生成的过程就是路由选择协议。
换句话说,路由选择协议就是用来发现目标路由,并且找到最佳路径的算法。
路由选择是非常复杂的一个问题,因为一个路由器的选择涉及到其他路由器,涉及到复杂多变的网络环境,而且网络拥塞的时候,很难获得所需的路由选择信息。
各种各样的协议出现,所以需要分个类。
路由选择协议根据其是否能自适应可以划分为两个种类:
路由选择协议还有另一种分类方法,是基于层次路由的。下面直接列出,分为IGP(内部网关路由协议)和EGP(外部网关路由协议)但是在具体介绍这些之前,要先讲一下层次路由。
为什么要分层呢?假设不分层,那么网络上主机的规模是很大的,路由表的项目就会很多,而且主机越多,项目也就得随之增加,这很明显不是个办法,因此有两种思路:
层次路由的核心思路就是分层,或者说分区。比如下图中,若干路由器,被分成3个大区域,左边大区域内部还可以被分成多个小区域。小区域内部自治,小区域之间互不干扰,大区域统一管理小区域。
实际上,这个区域就叫自治系统(AS,Autonomous System),其有如下特点:
私有AS
看下面,还是这张图。有三个AS,AS内部可以继续划分私有AS。AS内部IGP统一,自使用各自的IGP,AS之间的IGP可以不同,但是AS之间交流的EGP是要统一的。
层次路由可以解决两个问题:
RIP(Routing Information Protocol)是一种分布式的、基于距离向量的路由选择协议。
最大优点是简单,只需要路由器维护从他到其他目标网络的距离纪录即可,路由表条目结构简单(目的网络,距离,下一跳),而且两个网络之间只会维护距离最短的那一条路。
什么是距离呢?我们这里的“距离”,指的是从路由器到目标网络的距离,这个距离等于经过的路由器数量+1,如果是直接交付,那么就是0+1=1。
这个也就是RIP中路由器的“跳数”。我们以前学过一个同名概念,但是是从一个主机到另一个主机的跳数=经过路由器的数量。
这两个跳数之间其实是一个东西,比如A>X>B
所以这两个其实是一个东西,只不过描述的主体不同罢了,补上的那个1其实是路由器自己。
下图中,A经过BE到目标网络的路由器跳数为2(BE)+1=3。左边网络中某个主机到达右边网络的跳数也是3
这个距离有限制,距离为1-15,16就是最大值,代表不可达,类似于编程中的INF值。
这个算法是迭代算法,一个路由器,最开始只知道和直连网络的距离(1),以一定频率向相邻路由器发送当前路由器记录的距离信息,路由器收到这个距离信息后,会结合自己的距离信息更新路由表。可以看到,这个信息是在网络上逐渐传播的,所以需要时间去迭代收敛。
这个算法每隔30s执行一次,或者是当路由器的拓扑改变,也会马上执行一次。伪代码太抽象,其实挺简单,直接看例子。如下图。
RIP的问题在于,他是为ABC类网络设计的,没有掩码这个概念。RIP2做了很多改进,略过。
其他的都比较好理解,这里解释下为什么出错后会出现慢收敛,简单说就是套娃,反复传,最后才确认出错。
假定有下面的网络,正确的流程应该是这样的:
如果网1坏了,正确的流程应该是这样的:
但是呢,我们不能保证是R1先发送给R2,如果R2先发送给R1,就会出现很大的问题:
RIP太简单,容易出问题,而且不适应越来越复杂的网络,OSPF是为了克服RIP的问题做出来的。
RIP中,衡量距离的是跳数,但是现在的网络容易拥堵,跳数太过粗暴,就好比是堵车了绕道反而更快。
OSPF本质上仍然是路由选择协议,只要是这个协议,原理一定是指定一个开销的指标,选择开销最小的那条路。
与RIP相比,OSPF的指标更加复杂,维护的信息也更加全面,维护过程更加繁琐。
首先就是,OSPF如何衡量链路是否优质?这个就是其记录的链路状态:
这些信息被记录在链路状态数据库(LSDB,link state data base)中,在频繁的链路状态信息交换过程中,所有路由器的LSDB逐渐丰富并且统一。
本质上来说,LSDB就是区域内的路由器拓扑结构图,即每个路由器拥有当前区域内全部的路由器地图。
维护过程类似于RIP,就是一个路由器向其他路由器发送链路状态信息,美其名曰洪泛。
相邻
的所有路由器的链路状态改变
的时候才会洪泛。以可靠
的洪泛法向全网更新链路信息。当然每个一段时间也会更新一下链路状态。理论上这些东西差不多就够了,但是我们还要进一步细化。
邻居关系并不是连在一起那么简单,是有一个构建过程的,过程中需要用到5类信息:问候信息,数据库信息,链路状态信息。
还有就是,什么是可靠洪泛?其实就是带ACK确认的洪泛。
可以看到,LSDB应该是一个很大的数据库,如果路由器数量太多,LSDB会很大,所以OSPF使用层次划分区域。
OSPF的AS分区,主干区域是核心,其他分支之间互不沟通,每个区域有个32位编号,主干是0.0.0.0,其他的依次增加,看起来像IP,实际没啥关系。
分区后的OSPF路由器种类如下:
分区域以后,洪泛只限制在一个区域内,同步统一以后,再告诉骨干区,再转交其他区域。
BGP协议是AS之间通信的协议,力求选择一条从一个AS到另一个AS的路径,路径比较好但是不要求最好,毕竟这太复杂了。
BGP采用的是路径向量(Path Vector)的路由选择协议,分为两个子协议:
BGP协议的通信仅限于经过发言者,从外到内和从内到外。如果想让内部的一个路由器经过发言者到达另一个发言者,这是不允许的,是一种浪费。
说到头,BGP到底传递的是什么信息呢?BGP的目标就是找一条路径,因此其信息就是路径:
【前缀(目的网络),路径,下一跳路由器接口】
其中这个路径是可以拼接的。下图给出3个路径:
由此可见,路径信息其实是随着传播而逐渐变长的。当多条路径同时出现,按照如下策略选择,其中AS跳数也在考虑之中。
此外,一条路由还不能出现兜圈子现象,这也是资源的浪费。检测也很简单,当一个路由器(比如AS3),收到的BGP信息的AS-PATH字段中出现了自己,说明自己就在环上,这个路由不能要。
在这种前提下,你就可以知道,AS-PATH中不可能出现重复的AS号了。
我们前面讲路由器的转发,那个其实只是路由器下层的功能。实际上路由器分为上下两层:
路由表
转发表
转发IP数据报具体来说,路由表和转发表不完全是一个东西。
再具体来说一下转发过程。总的来说就是:
中间如果校验失败,查询失败,都会丢弃,所以网络层并不可靠。
最后插播一点额外的知识,家用路由器其实和企业路由器大不相同。
我们学的是企业路由器,而家用路由器实际上不是路由器,其最基本的路由功能简单极了,只需要送到一个目标口(静态路由),压根就没有动态路由协议,真正的功能是下面一大堆附加的。
以前就只有单播,广播,IP多播就是将IP报定向发送给目标的一组主机,可以看到没有IP数据报被浪费。多播的应用场景很多,本质上就是一对多。
多播地址用D类地址,为[224,240),一个多播地址就是一个多播组。
此外,还有硬件地址(00-00-5E-xx-xx-xx),与多播IP一一对应。
如何将一些主机放到一个多播组里呢?在IPv4网络中使用网际组管理协议(IGMP,Internet Group Management Protocol)
首先,R1-R4都是多播路由器,其记录了多播信息,比如自己局域网的某些主机在某某多播组里。这个信息通过两个IGMP报文更新:
IGMPv1是标准,其他的都只是建议标准。
多播路由比单播更加复杂。实际上就是要找出以源主机为根节点的多播转发树,数据报就沿着树发送就可以,每分支就复制一次。
多播树是类似于STP,采用一定算法生成的。
如果树上的节点发生变化,要进行对应的剪枝和嫁接。说白了就是没主机了就剪掉路由,有新主机就把路上的路由器加进树里。
此外,有一些网络不支持多播,所以只能通过隧道技术包装多播数据报,等到达另一个支持多播的网络里再次传播。
因此,隧道也可以理解为在多播树上的一条比较长的不分支的路径。
给定一个确定的多波束,数据报采用洪泛的方式沿着多播路由树发送,可能会有兜圈子或者绕远路现象。因此采用反向路径广播RPB(Reverse Path Broadcasting)策略来控制。
假设R已经知道了最优路径,而最优路径就代表最快,所以在组播中,从最优路径传过来的包一定是最先到的。反过来说,只要是从其他路径过来的,就一定是排在最优路径后面的,已经重复了。
下图中③就是对这两种情况的处理,最先到的(最优)就向子节点转发,其他的都是重复的,不转发。
密集分布是我们前面学的常规场景。多播的发送者一定是服务器,对每一个发送者(对应一个多播组)构建一颗源点树。
如果多播组的成员分布的非常零散,那么为这一个组单独弄一个多播树,就有点浪费资源,树上的路由器负载太低。
于是稀疏分布的情况下,多个多播组公用一个核心树,让这颗核心树的负载提升到一个合理的程度。
首先要知道,PN是什么。其实我们前面说的局域网,就是专用网络,专用网络是物理上用线缆连接的一个局域网。
而VPN上的设备不一定用线缆连接,只需要他们都连接在公网上,就可以构成一个虚拟的局域网。之所以需要VPN,原因如下:
如何做到VPN呢?下图中,部门A和B没有直接连在一起,而是通过公网连在一起,但是属于一个VPN。
在部门AB内部,IP是私有的,如果要做到在公网上通信,需要将私有IP转换为公网IP。为了保证公网上的安全,需要进行隧道加密。
隧道具体来说就是把一个VPN里的两个私有地址加密封装到数据部分里,然后用R1和R2的共网IP光明正大地传播,不用担心VPN内部数据被窃取。
世界上的IP数量有限,对于一个物理局域网,其内部的主机是没有公网IP,只能共享一个对外的公网IP,通过路由器交流。
这个将私有地址转换为公网IP,再从公网IP转回去的过程,就是NAT。
从私有地址转换为公网地址很简单,但是公网地址怎么转回私有地址是个问题,假如有两个私有地址都从这一个口出去,那转回去的时候该怎么办呢。
一种解决办法是一一对应,即一个公有地址同时只对应一个私有地址,也就是说,如果只有一个公有地址,那么专有网络里的主机只能轮换使用这一个公有IP。总之一个公有IP同时只对应一个私有IP。
这种方式需要频繁更换转换表项。
另一种方法是NAPT(Network Address and Port Translation)。NAPT相比于NAT技术来说,可以让多太主机同时使用一个公网IP。这似乎破坏了一一对应的原则,实则不然,NAPT还多了一个Port字段,维持住了一一对应。
具体是将TCP端口号作为Port,和网络层的IP混用。Port字段是放在IP数据报的数据部分的。
NAT是可以节省IP,但是会破坏网络层的完整性。
NAT和VPN虽然都有地址转换,但是本质上完全不同,切莫混淆。
NAT是物理意义上的,将私有地址与公网IP进行转换的。
而VPN虽然也会进行转换,但是这个转换只是附带的,或者说是基于NAT的,VPN真正的作用是让两个局域网通过公网构成一个虚拟局域网,是利用公网扩展局域网的技术。
路由表会随着网络的变大而变大,查表成本会越来越大。而且查表太久,会把缓存填满,分组更容易丢失。
所以需要解决的就是查表速度太慢。MPLS应运而生,这是一个建议标准,所以只有一部分设备支持,技术点如下:
标签凭什么能缩减路由表呢?以前,每一个目标网络必然对应一个路由表项,实际上很多目标网络的转发接口是一样的,那么这几个网络的路由表项实际上是可以合并到一起的,构成一个标签。由此,标签实现了路由表项的多合一,简化数量,自然就加快了转发。
如何实现:
可以看出,标签介于链路层首部和IP首部之间,所以MPLS其实是介于2-3层之间的,MPLS不仅仅可以减少路由表项目,减少查表时间,而且还可以把转发放到链路层,少了解包和封装的过程,也可以节省不少时间。
MPLS首部介于2-3层,知道这个就行。
SDN是2009年才提出的,是很新的东西。
在传统的5层结构中,每一层各司其职,但是实际上每一层都有类似的功能,都是查表,转发。而且还有一个重大的问题,就是分布式的协议又复杂又慢,其实用集中式的控制也不是不可以,或许会更好,这就是SDN的初衷。
SDN实际上是把各层(链路,网络,传输层)的查表功能抽离出来,做成几个上层的功能模块,用于控制,比如路由选择,接入控制,负载均衡。
控制模块生成统一的转发表,下放给下层的转发机,转发机器是统一的,统称交换机。
至于如何上层对下层的控制,其实门道就在转发表里,转发表不一样,下层的逻辑就不同,自然就实现了控制。
SDN的思想还有一个好处就在于,以前路由器的技术只掌握在少数厂家手里,一台路由器集成了控制层面,数据层面,协议的实现,现在分离了,能让更多的软件开发商参与进来。
来看看SDN目前主流的协议OpenFlow的具体实现。
在以前,我们每一层都有一个转发表,比如链路层有mac表,网络层有路由表,传输层应该也有某个表(还没学到)。我们如果用5层网络架构去说,那在每一层都得去查表,最后转发。而OpenFlow,把三个表变成三个流表,放到一起,进行集中匹配,匹配后进行对应操作(可能是转发,丢弃)
听起来很抽象,总之给我的感觉就是把中间三层合一了。可以看到,同时匹配3层的字段,然后进行综合了中间3层的更复杂的操作。这看起来不符合解耦的思路,让这个匹配过程压力很大,但是实际上在上层控制模块的作用下,匹配过程也可以做到很快,这就是SDN有潜力的一个点,整体更快。
传输层要解决两个问题:
传输层也是很重要的,和网络层一样承上启下,如下图,下接通信,上接应用。因此,传输层是保证数据稳定性的最后一个关口,所以会尽可能提供不同程度的稳定性保证,但是此时就无法保证延迟,带宽了。
从程序员角度说,网络通信其实是进程通信。
通信分为三个步骤:
具体到传输层的服务,其提供基本的服务和扩展的服务。
基本的服务仅仅是简单扩展网络层,是最低限度的,对应UDP协议:
传输层的增强服务用于保证可靠传输,丰富且复杂,对应TCP协议:
进程是比较复杂的,不同系统和不同系统都是不一样的,所以传输层选择把这些东西丢给应用层,然后再应用层和传输层之间抽象出一个界面,端口就是进程号和传输层之间的界面,进程和端口绑定由应用和操作系统来解决,而传输层只交付端口。
端口很像接口,其实本质上是一个东西,只是在软件上习惯叫端口,硬件叫接口,便于区分。
端口是16位的,只具有本地意义,是用于区分本计算机上的不同进程的,和其他计算机没关系。类型有3种,如下图:
到此为止,通过IP:PORT套接字,就可以实现在互联网上找到一台主机的一个进程。
前面已经大概介绍了一点,直接来UDP特点:
UDP的特点:
很精简,8字节报头:
TCP特点:
20个字节的固定长度,和IP数据报的固定长度一样:
在可选字段中,有一些其他概念:
如何在不可靠传输的基础上实现可靠传输呢?那必然是高强度的检验机制+流量控制。
直接给出可能的情况:
正常情况:
分组错误或分组丢失:
确认丢失和确认迟到:
再给出概括:
看下图,停等协议的效率取决于斜率,斜率由RTT决定,传的越慢,停等的效率就越低。
解决停等协议低效率的方式就是流水线,干脆就不等了。因此必然更容易出现问题,所以需要更多的保障措施。
可靠传输协议采用滑动窗口的方式去收发,是全双工的,我们仅从单工角度去解释,实际可以做到双工。
GBN是比较粗糙的流水线协议,因为其接受窗口大小=1,如果经常回退,会造成重大浪费,还不如停等。
GBN有明显缺陷,就是回退的浪费有点大,所以自然而然就想到,能不能只重传超时的一个分组?
SR协议做出如下改变:
看起来SR协议是要比GBN好很多,但是还是有一点小的缺陷,其实GBN和SR是各有千秋:
TCP的缓冲区可以理解为字节数组。滑动窗口是缓冲区上面的一片区域,因此TCP传输的最小单位是字节。
注意,这只是最小单位,实际上可以一次性发送100个字节的数据,封装到一个TCP报文段中,然后若干个TCP报文按照流水线方式发送。
TCP协议采取等大窗口+累计确认+回退N的方式:
具体滑动的过程,就是和我们前面的ARQ类似,区别有两点:
超时重传是一个合理的策略,但是这个时间如何确定呢?
这很难,因为网络情况很复杂,而且有波动,如果只有一个固定的超时重传时间,不太行,应该有一个算法去实时更新。
直接给公式:
R T O = R T T S + 4 × R T T D RTO=RTT_S+4\times RTT_D RTO=RTTS+4×RTTD
RTO由两项求和而得。 R T T S RTT_S RTTS代表RTT的平均值, R T T D RTT_D RTTD代表RTT波动的平均值。所以RTO其实就是在RTT均值的基础上考虑到了网络波动容错,RTO要总是略大于RTT。
具体看,这两项平均值都是滑动加权平均。其实这和机器学习思路很像, α \alpha α实际上就是个学习率。推荐的 R T T S RTT_S RTTS学习率是0.125,而 R T T D RTT_D RTTD是0.25,这表明我们对波动要更加敏感,因为波动本身就是一个瞬息万变的东西。
在传输的过程中,RTO的更新流程如下:
为什么要先计算新的加权偏差呢?因为偏差就是新旧的差异嘛,所以要新-旧。如果先计算新的RTT,那么就是新-新了,不合理。
因为TCP有超时重传机制,所以TCP确认存在二义性:
既然无法确定,那就一刀切,只要发生重传,就干脆不采样这次的RTT值。
新的问题又来了,原始Karn算法面对网络巨变导致的RTT骤增,会出现无法更新的bug。
如果真的是因为网络巨变,导致RTT大大增加,那么此后的包一定都会重传,按照Karn算法,这些重传RTT都不去采样,RTT就不会更新了。因此,即使是重传不采样RTT了,也要对RTO进行一定的变化,这就是修正的Karn算法:
一般情况下,RTO基本稳定,出现网络巨变就会触发Karn算法,RTO骤增,等网络巨变真正过去以后,RTO算法又会根据标准更新方法逐渐回落。
我们前面说了,虽然TCP报文是以字节为单位计数的,但是通常一个TCP报文里面是放了很多数据的(最多1500-20-20=1460B),若干TCP报文采用流水线方式发送。
那么问题来了,如果使用GBN算法,一旦一个TCP报文出问题,就要重传N个TCP报文,这个负担是很大的,如果能只选择性重传一个TCP报文就行,这就是我们前面的SR算法。
当然,TCP还是比较特殊的,特殊就特殊在虽然是以字节为单位,但是实际是以TCP报文(字节段)为单位的。
如果按我们以前的SR算法,我每发送一个数据,都要回传一个ACK,这个ACK是单独确认的,可是问题来了,如果只是利用ACK里面的确认号字段,这一个字段无法确认字节段的两个边界的。
所以TCP只能增加一个SACK可选字段,用来报告左右边界。不过没有绝对的好事情,SACK字段是可选字段,会增加报头负担,效率是否提高是个未知数。
为什么要流量控制?
因为接收方的接受能力有限,所以在负担大的时候,应该减少流量。
如何实现?窗口大小协商
在TCP报头中,有一个窗口大小字段,这个字段是接收方的接收窗口大小,代表了接收方的接受能力,反馈给发送方后,发送方会将发送窗口大小变成这个字段的值(实际上会有一个放大倍数,参考TCP扩展报头中的窗口扩大字段)
直接看例子,发送方始终以流水线方式发送TCP报文段,直到可用窗口=0。接收方在反馈ACK的时候,也会带上rwnd字段,通知发送方及时修改发送窗口大小。
从下图中可以看出,如果接收方想少来点数据,就返回一个小的rwnd,如果不想接受,就返回一个0值rwnd。这就实现了从接收方的角度控制流量。
当然,这个控制也有缺陷,因为从接收方调整接收窗口大小到发送方收到新的窗口大小之间有一段0.5×RTT的时间间隔,这个时间段内可能又会发出更多的数据,这一部分接收方没法接受,也就可能浪费了。
当rwnd=0,那么发送方之后就不会发送数据了。同时,接收方因为没有收到数据,也不会反馈数据报,也就不会反馈新的rwnd,这就是一个死锁局:
为了解决这个死锁,使用持续计时器。
前面讨论了rwnd=0的情况,还有一种不好的情况,是rwnd太小,比如1。
每次TCP发送报文段,那么长的报头,结果数据只有1,效率极低,所以如何处理rwnd太小的情况呢?
直观的思路是等接收方的rwnd变大再一次性发送。
在思考如何让TCP等待之前,先了解TCP什么时候会发送:
nagle算法适用于糊涂窗口综合征,此时接收方接受能力有限,发送方只能一点一点发送。
采用Nagle算法的接收方,会根据数据选择是否反馈ACK。
接收方如果收到的数据太少,而且接收方自己负载很高,则接收方不会反馈ACK,此时发送方因为收不到ACK,也就不会继续发送了。
在等待的时间内,发送方数据会逐渐积攒,接收方的接受能力也会逐渐恢复,等到发送方觉得可以发了(实际上不一定),发送方才发。
定义拥塞
:网络整体上多处同时拥堵,而且这个时候负荷越大,吞吐量越小。拥塞本身很复杂,还会正反馈加剧拥塞。
表现
:分组延迟增大,吞吐量下降,甚至瘫痪
起因
:用户对资源的总需求>网络可用资源(节点缓存,链路容量,处理机能力)
增加资源有用吗?有点用,但不多。如果只增加一部分资源,那么就会把压力转嫁到其他资源上,可能反而会令网络更坏(把其他资源直接压崩了)。如果是整体增加资源,也没用,因为用户对于网络资源的需求是无限的,后面还是会拥堵。
关键在于,约束用户的发送量,即拥塞控制。
拥塞控制
是针对整个网络进行的发送量控制,在空间和时间上都非常复杂,是个动态问题,所以很难办,而且很多时候就是拥塞控制本身引发了网络恶化。
如果没有拥塞控制,吞吐量会在后期走下坡路。一个理想的拥塞控制,可以让网络的吞吐量随着负载逐渐增加,最后稳定在一个最好的状态。实际上的拥塞控制介于两者之间,可以让这个下坡路来的更晚,而且也可以在跌落之后更快的恢复。
拥塞控制有两种方式:
TCP拥塞控制采用CWND(拥塞窗口)控制发送速率。
这个CWND和前面流量控制的RWND平级,代表要发送的数据量。后面会详细对比两者之间的相互作用关系。
拥塞控制中,速率公式如下:
r a t e = c w n d R T T B y t e s / s e c rate=\dfrac{cwnd}{RTT} Bytes/sec rate=RTTcwndBytes/sec
直观理解,当cwnd大,RTT小时,rate会很大。这是因为cwnd大,代表要发送的数据攒的够多了,所以就要快点发。RTT小,代表网络环境不错,那么也可以快点发,快上加快。
拥塞控制其实就是控制这个cwnd来间接控制rate
慢开始门限(ss-thresh)
:防止cwnd增长过大导致拥塞。ssthresh代表了慢开始的基本预期,或者说是慢开始的目标。
之所以用ssthresh,是因为指数增长是比较危险的,越到后面,增长量就越大,有可能因为一次性增长量过大,直接把网络压瘫痪,所以不要等那个时候再停止,应该是到达一个阈值就提前停止。
假设发生了拥塞,要先进行一些处理才能进行慢开始:
所以说,拥塞+慢开始=跌倒了快速追赶,3ACK+快重传+快恢复=踉跄了一下后慢速恢复
这个图看着还是挺复杂的,其实没啥可怕的,整体可以说是一个AIMD过程,Additive Increase Multiplicative Decrease,无非就是增长和下调
整体过程:
来看个例子。
其实还是很简单的,就是往上爬,出意外就退,然后继续爬。
流量控制和拥塞控制,都是在控制发送流量,只是其源头不同。流量控制受制于接收方接受能力,由接收方通知发送方,而拥塞控制受制于网络拥塞状况,需要发送方自行判断。
前面提到了cwnd和rwnd是平级的,具体关系其实是两者共同决定了发送窗口的上限。因为发送方发送数据要考虑到通路是否通常(是否拥塞),也要考虑目的地是否有足够的接受能力(接收窗口),二者构成水桶效应。
其实拥塞控制已经讲完了,其中拥塞判断是根据丢包来判断的。路由器本身是有丢包策略的,这个会影响到TCP的拥塞控制,AQM是一种理论上可行实际不咋地的丢包策略。
路由器默认的丢包策略是尾部丢弃,一旦超过最大队列长度,后面进入的包全部会被丢弃。
当路由器队列长度变大,出现尾部丢弃,此时会丢弃连续若干的分组,此时就会触发超时,让发送方跌回慢启动状态。也就是说,队列满=拥塞=超时
如果只是一台主机这样,也就罢了,如果是多台主机呢?如果出现路由器队列排满的情况,那么他们基本会同时超时,然后同时进入慢启动,同时重传。
重传并不可怕,可怕的是同时,这就会导致拥塞,然后拥塞导致同时重传,重传再导致拥塞。
我们可以从“同时”下手,只要这些主机不是同时重传,这个问题就没了,因为主机是用丢包判断拥塞的,所以只要随机丢包,就不容易同时判断拥塞,也就不会出现同时重传的问题。
我们将以前的最大队列长度变成最大门限,将最大门限分成两个区域,前面的区域是一定不被丢弃的,后面的,来得越晚越容易被丢弃,如此打乱丢弃顺序,就有可能缓解同时判断为拥塞的问题。
然而实际上AQM没啥大用,现在也有很多其他方法,但是核心思路就是让路由器通过对分组的排队进行智能管理,也承担一部分解决拥塞的责任。
TCP是面向连接的,所以肯定是分成三个阶段:
链接是全双工可靠的,所以这个过程的管理还是有点复杂的。
连接建立的作用:
连接前,服务器应该处于监听状态,此后由客户(client)向服务器(server)主动发起请求,分为三步:
总结:前两个连接报文的SYN=1,三个报文中,除了第一次ACK=0,其他都是ACK=1。
因为是全双工传输,所以释放连接要释放两个方向的链接,因此更复杂一些。
建立连接后,双方是同级的,任何一方都可以主动释放连接。假设A要先释放连接:
时间等待计时器
,超时后才会彻底断开。总结:断连通知的FIN=1,4个分组中,除了第一次ACK=0,其他都是ACK=1。
前面说的是正常的连接和断开,如果过程中客户死机了,又或者客户应用程序只是占用链接而不发送数据,浪费服务器资源呢?此时就需要服务器去主动断开。
保活计时器运行在服务器上,2小时内没有收到客户的信息,就进行探测。发送10个探测报文,间隔75s,此时会有两种可能:
最后给出状态机,很复杂,看看得了。
应用层基于传输层,向用户提供服务。应用层的协议很多,都是针对某个具体而广泛的应用而设计的,目标就是用于协同不同主机上不同应用之间的通信。
本章具体内容多且杂,比较实用。
DNS(Domain Name System)域名系统。
www是一个访问全球资源网络的协议。
URL=资源
html是资源的主要形态,而资源还有其他的文件形态,可以通过html显示,或者直接通过网络下载。
这个取的过程就是http