时延
总时延 = 排队时延 + 处理时延 + 传输时延 + 传播时延
1. 排队时延
分组在路由器的输入队列和输出队列中排队等待的时间,取决于网络当前的通信量(输入队列用于排队进入路由器,输出队列用于路由器处理完决定往哪转发后排队发出去)。
2. 处理时延
主机或路由器收到分组时进行处理所需要的时间,例如分析首部、从分组中提取数据、进行差错检验或查找适当的路由等。
3. 传输时延
主机或路由器传输数据帧所需要的时间。(从结点进入传输媒介的时间)
4. 传播时延
电磁波在信道中传播所需要花费的时间,电磁波传播的速度接近光速。
计算机网络体系结构
1. 五层协议
应用层 :为特定应用程序提供数据传输服务,例如 HTTP、DNS 等协议。数据单位为报文。
传输层 :为进程提供通用数据传输服务。由于应用层协议很多,定义通用的传输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数-0/.据单位为报文段;用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报(TCP对进程传下来的数据看成一串无意义的字节流,发送也不会以进程交付的界限发送,而是根据自己缓冲区来决定发送哪些字节;而应用层给UDP的数据,UDP不会将他拆分成不同的字节部分分开发送,而是应用层给什么,UDP发什么,不拆分,也不合并)。TCP 主要提供完整性服务,UDP 主要提供及时性服务。
网络层 :为主机提供数据传输服务。而传输层协议是为主机中的进程提供数据传输服务。网络层把传输层传递下来的报文段或者用户数据报封装成分组。
数据链路层 :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。
物理层 :考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。
2. OSI
其中表示层和会话层用途如下:
表示层 :数据压缩、加密以及数据描述,这使得应用程序不必关心在各台主机中数据内部格式不同的问题。
会话层 :建立及管理会话。
五层协议没有表示层和会话层,而是将这些功能留给应用程序开发者处理。
3. TCP/IP
它只有四层,相当于五层协议中数据链路层和物理层合并为网络接口层。
TCP/IP 体系结构不严格遵循 OSI 分层概念,应用层可能会直接使用 IP 层或者网络接口层。
链路层
基本问题
1. 封装成帧
将网络层传下来的分组添加首部和尾部,用于标记帧的开始和结束。
2. 透明传输
透明表示一个实际存在的事物看起来好像不存在一样。
帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的 判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符。如果数据部分出现转义字符,那么就在转义字符 前面再加个转义字符。在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉 不到转义字符的存在。
3. 差错检测
目前数据链路层广泛使用了循环冗余检验(CRC)来检查比特差错。
PPP 协议
互联网用户通常需要连接到某个 ISP 之后才能接入到互联网,PPP 协议是用户计算机和 ISP 进行通信时所使用的数据链路层协议。
ISP
互联网服务提供商 ISP 可以从互联网管理机构获得许多 IP 地址,同时拥有通信线路以及路由器等联网设备,个人或机构向 ISP 缴纳一定的费用就可以接入互联网。
MAC 地址
MAC 地址是链路层地址,长度为 6 字节(48 位),用于唯一标识网络适配器(网卡)。一台主机拥有多少个网络适配器就有多少个 MAC 地址。例如笔记本电脑普遍存在无线网络适配器和有线网络适配器,因此就有两个 MAC 地址。
局域网
局域网是一种典型的广播信道,主要特点是网络为一个单位所拥有,且地理范围和站点数目均有限。
主要有以太网、令牌环网、FDDI 和 ATM 等局域网技术,目前以太网占领着有线局域网市场。可以按照网络拓扑结构对局域网进行分类:星型 环型 直线型
以太网
以太网是一种星型拓扑结构局域网。
交换机
交换机具有自学习能力,学习的是交换表的内容,交换表中存储着 MAC 地址到接口的映射。正是由于这种自学习能力,因此交换机是一种即插即用设备,不需要网络管理员手动配置交换表内容。下图中,交换机有 4 个接口,主机 A 向主机 B 发送数据帧时,交换机把主机 A 到接口 1 的映射写入交换表中。为了发送数据帧到 B,先查交换表,此时没有主机 B 的表项,那么主机 A 就发送广播帧,主机 C 和主机 D 会丢弃该帧,主机 B 回应该帧向主机 A 发送数据包时,交换机查找交换表得到主机 A 映射的接口为 1,就发送数据帧到接口 1,同时交换机添加主机 B 到接口 2 的映射。
网络层
网络层向上只提供简单灵活的、无连接的、尽最大努力交互的数据报服务。
与 IP 协议配套使用的还有三个协议:
地址解析协议 ARP(Address Resolution Protocol)
网际控制报文协议 ICMP(Internet Control Message Protocol)
网际组管理协议 IGMP(Internet Group Management Protocol)
IP 数据报格式
版本 : 有 4(IPv4)和 6(IPv6)两个值;
首部长度 : 占 4 位,因此最大值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为固定部分长
度为 20 字节,因此该值最小为 5。如果可选字段的长度不是 4 字节的整数倍,就用尾部的填充部分来填充(5~60字节)。
区分服务 : 用来获得更好的服务,一般情况下不使用。
总长度 : 包括首部长度和数据部分长度。
生存时间 :TTL,它的存在是为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位,当 TTL为 0 时就丢弃数据报。
协议 :指出携带的数据应该上交给哪个协议进行处理,例如 ICMP、TCP、UDP 等。
首部检验和 :因为数据报每经过一个路由器,都要重新计算检验和,因此检验和不包含数据部分可以减少计算的工作量。
标识 : 在数据报长度过长从而发生分片的情况下,相同数据报的不同分片具有相同的标识符。
片偏移 : 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。
IP转发分组的流程
路由器中的路由表存储的不是所有主机的IP地址,而是网络的地址,以及到该网络应该如何转发。
比如某个路由表项:20.0.0.0 直接交付,接口0 表示到20.0.0.0网路直接交付即可
40.0.0.0 30.0.0.1 表示到40.0.0.0要先跳到IP地址为30.0.0.1的路由器。
这样,不必关心某个网络内部的结构,转发分组时,只是从一个路由器转发到下一个路由器,直到可以直接交付。
默认路由:如某个路由表有三个表项,第一个到N1,第二个到N2,则到其他任意一个网络都通过默认路由指定的路由器,如N3或N4,都先交付给默认路由指定的路由器。默认路由在表中网络地址表现为0.0.0.0
特定主机路由:大部分情况下,路由表中是目的网络的地址,但也允许有存放特地目的主机的地址与跳转到该主机下一跳所要跳到的路由器地址
在转发分组过程中,IP数据报中的源地址IP和目的IP是不变的,每一跳时都是将下一跳地址翻译成MAC地址(ARP协议),然后封装到链路层的MAC帧的首部,然后根据这个硬件地址找到下一跳路由器。
分组转发算法如下:
1.从数据包中提取目的IP地址D,得出目的网络N
2.若N在此路由器连接的某个网络内,从对应网络的那个接口进行直接交付。
3.若路由表中有目的地址为D的特定主机路由,则根据该表项跳到下一跳路由器。
4.若路由表中有到达N的路由,则跳到对应路由器
5.若无N的路由,则按默认路由跳转
6.否则,报告转发分组出错。
IP 地址编址方式
IP 地址的编址方式经历了三个历史阶段:
分类
子网划分
无分类
1. 分类
由两部分组成,网络号和主机号,其中不同分类具有不同的网络号长度,并且是固定的。
IP 地址 ::= {< 网络号 >, < 主机号 >}
对A类:
网络号占1个字节,可用位数为7位,则可指派的网络号有2^7-2
-2是因为:
网络号全为0表示本网络
全为1(011111111)用于环回测试,网络号为127的地址不是一个网络地址
主机号有3个字节,则最大主机数为 2^24-2;
-2是因为:全为0的主机号表示本主机所在的网络的网络地址。如5.6.7.8所在的网络的地址为5.0.0.0
全为1的主机号表示本网络上的所有主机
A类地址除了首位的0,共有2^31个IP地址,占了所有IP地址的50%
对B类:
网络号有2个字节,前两位已固定,可用网络号为2^14-1
-1是因为:
128.0.0.0是不指派的,因此最小的网络地址是128.1.0.0
主机数共有2^16-2
-2是因为:
扣除全0与全1的主机号
B类地址的IP共2^30个,占25%
对C类:
网络号有3个字节,有21位可分配。
网络数共2^21-1:
-1是因为:
192.0.0.0是不分配的,最小网络地址为192.0.1.0
主机数共2^8-2
-2是因为:
全0与全1的主机号不可用
C类共2^29个地址,占12.5%
D类和E类共12.5%
子网划分
为什么要使用子网:
IP地址空间利用率低。有的单位申请一个B类地址,但主机数不多,又不愿意申请一个C类地址,因为考虑到以后的发展。因此通过子网,将现在的区域划分为一个子网,可在剩余的区域建立新的物理网,这样也不用重新在申请一个网络地址。
每个物理网络分配一个网络号会使路由表过大。使用子网,每个物理网不用都分配一个网络地址,他们对外表现出同一个物理网络,进入这个网络后通过子网号进行划分,这样许多个物理网实际上只对应路由表里的一个网络号。
两级IP地址不够灵活。当某网络里新增加了一个物理网,现在又来不及去申请一个新的网络号,可以在本网络里给这个新增的物理网分配一个子网号,这样对外仍表现为一个网络,对内通过子网号区分不同的物理网。
通过在主机号字段中拿一部分作为子网号,把两级 IP 地址划分为三级 IP 地址。
IP 地址 ::= {< 网络号 >, < 子网号 >, < 主机号 >}
要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 11000000 00000000,也就是 255.255.192.0
如果外部传递一个分组,传递到本网络后,本网络通过子网掩码与对应的IP地址进行按位与,与得到的结果就是目的所处的子网网络地址,然后跳转到对应子网去交付。
另外增加子网会使可用IP地址变少,因为全0与全1的网络号是不可用的(全0表示本网络。而全1表示广播,因为子网是从A/B/C类中的主机号中拿走几位组成的,虽然内部看是不同网络,但对外表示同一个网络,因此外部要在这个网络进行广播时会设置主机号全为1,而子网会拿取部分主机位作为子网编号,这个广播地址也相当于把子网取走的号要全部置1,因此子网全1是不可行的。)。
使用子网时分组的转发:
1.从数据包提取目的地IP地址(IP层)D。
2.先判断是否直接交付。对与路由器直接相连的网络(注意,是直接相连的!!!),用各个子网的子网掩码(该子网所拥有的的子网掩码)与D进行按位与,如果找到匹配则交付。
3.否则,如果有目的地址为D的特定主机路由,则转发到对应的下一跳路由器。
4.对路由表中每一行,用对应的子网掩码进行按位与,如果与的结果与对应网络的地址匹配,则转至对应的下一跳路由器。
5.若有一个默认路由,则把数据包传至默认路由
6.报告转发分组出错。
3. 无分类
无分类编址 CIDR 消除了传统 A 类、B 类和 C 类地址以及划分子网的概念,使用网络前缀和主机号来对 IP 地址进行编码,网络前缀的长度可以根据需要变化。
IP 地址 ::= {< 网络前缀号 >, < 主机号 >}
CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法,例如 128.14.35.7/20 表示前 20 位为网络前缀。
CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为网络前缀的长度。
一个 CIDR 地址块中有很多地址,一个 CIDR 表示的网络就可以表示原来的很多个网络,并且在路由表中只需要一个路由就可以代替原来的多个路由,减少了路由表项的数量。把这种通过使用网络前缀来减少路由表项的方式称为路由聚合,也称为 构成超网 。
在路由表中的项目由“网络前缀”和“下一跳地址”组成,在查找时可能会得到不止一个匹配结果,应当采用最长前缀匹配来确定应该匹配哪一个。
为什么CIDR可以减少路由表项的数量?
答:
以地址聚合206.0.64.0/18为例
这里面该地址聚合将所有IP地址前18位为206.0.64.0的前18位的网络缩成了一个。而对于一个C类网络来说,它的网络号有24位,也就是说这样一个地址聚合可以表示若干个C类网络(地址聚合网络号为18位,而18到24间还有24-18=6位,也就是说有2^6个C类网络的前18位是相同的,也就是说有2^6=64个网络的前18位都为206.0.64.0的前18位,这样等价于说如果不采用CIDR,对于这个IP地址为206.0.64.0的ISP来说,其他与这个ISP的路由器交换路由信息的路由器的路由表中,都要存储64个网络地址来表示这64个C类网络,只有这样才知道,要到这64个网络中的某一个的话,要先调到这个ISP的路由器。而现在采用了CIDR,对于其他路由器来说,只要存储一个地址聚合,以64个C类网络中的任意一个为目标的转发分组,都会被这个地址聚合所匹配,从而调到对应的这个ISP)
前缀越短,其地址块能包含的地址数就越多,所节省的路由表项也就越多
最长前缀匹配:
由于采用了地址聚合,因此对于某个目的网络可能会有多个匹配。比如对 206.0.68.0/22与206.0.71.128/25这两个地址聚合在前22位上是相同的,因此往206.0.71.128/25上发送也会与22位的相匹配。这个时候应该与具有最长网络前缀的路由相匹配。这样就不用通过 206.0.68.0/22这个路由器,绕过他而去206.0.71.128/25这个路由器。
地址解析协议 ARP
网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的 地址始终不变,而 MAC 地址随着链路的改变而改变。
注意下图中ICMP,IGMP和ARP的位置。
ARP是作为网络层与数据链路层的接口,去翻译IP地址为MAC地址。
每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到 MAC 地址的映射表。
如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。
ARP是解决同一局域网上的主机或路由器的IP地址的映射。如果不在同一网络,则要借助路由器去转发分组。
当不在同一局域网时,如何使用ARP:
首先说A把数据传给A网关。A在给B发数据时,A通过本身地址及掩码与B地址做“与”运算,如果发现与B是同一网段,那么广播发送即可;如果发现与B不在同一网段,那么A会查找网关(A在电脑上配置的),发送ARP请求获得网关MAC地址,获得MAC后,数据包中源IP为A的IP,目的IP为B的IP,源MAC地址为A的MAC地址,目的MAC地址为A网关的地址,这样A就将包发给了网关。
然后说下A网关如何发给B的。A网关收到数据包后查看,根据目的IP(B的IP地址)查找路由表,找到通往目的网段的路由及下一跳,得到下一跳的MAC地址(ARP获得的),然后将数据包中原IP、目的IP保持不变,原MAC地址换成A网关的MAC地址,目的地址换成下一跳的MAC地址,转发到下一跳的设备(路由器,三层交换机等)。如果下一跳就是B的网关(不是B的网关,就重复上面的动作),网关收到后查看,发现目的IP在自己的内部(ARP表),将数据包中原IP、目的IP保持不变,原MAC地址换成B网关的MAC地址,目的地址换成B的MAC地址,将数据包发给B,B得到数据包后,完成A与B的通信。
网关与路由器的区别:
首先‘网关’一个大概念,不具体特指一类产品,只要连接两个不同的网络的设备都可以叫网关,所以网关它可以是路由器,交换机或者是PC任一;
而‘路由器’一般特指能够实现路由寻找和转发的特定类产品,路由器很显然能够实现网关的功能,而路由器的主要功能是把网络分给更多的设备使用网络。
路由器与交换机的区别:
https://blog.csdn.net/baidu_32045201/article/details/78305586
根据 OSI模型的网络体系划分,自底向上,路由器工作在第三层(网络层),而我们常说的交换机 工作在第二层(链路层)
它们的主要工作如下:
路由器:寻址,转发(依靠 IP 地址)
交换机:过滤,转发(依靠 MAC 地址)
我们可以看出这两者的主要工作就是转发数据,但是不同之处是,依靠的地址不同,这是一个根本区别!
路由器内有一份路由表,里面有它的寻址信息(就像是一张地图),它收到网络层的数据报后,会根据路由表和选路算法将数据报转发到下一站(可能是路由器、交换机、目的主机)
交换机内有一张MAC表,里面存放着和它相连的所有设备的MAC地址(交付到主机通过交换机来交付),它会根据收到的数据帧的首部信息内的目的MAC地址在自己的表中查找,如果有就转发,如果没有就放弃
通过拓扑图我们应该知道:
每一个路由器与其之下连接的设备,其实构成一个局域网
交换机工作在路由器之下,就是也就是交换机工作在局域网内
交换机用于局域网内网的数据转发
路由器用于自己所在的局域网与外网的连接
举个例子:
我们每个人相当于主机,路由器相当于快递员,宿管大爷相当于交换机,学校是一个局域网
快递员根据学校地址(IP)把包裹送到学校,再根据公寓号(子网IP)把快递交给这个公寓的宿管大爷(不同的交换机),宿管大爷根据你的名字(MAC)交给你
它们两个可不可以少一个?
交换机在局域网内工作,它根据 MAC 地址转发数据,如果没有了路由器在网络层寻址,那么我们的数据就不能发送到其他网络终端上去了
路由器内集成了交换机的功能,主机与路由器相连也可以实现数据转发,但是不足之处是:
可扩展的接口不如交换机多
交换机通常由硬件加速转发,路由器主要靠软件寻址,速度慢
实际网络数据转发过程
假设你使用电脑访问www.baidu.com
过程大致如下:
你的电脑先在应用层打包一个 HTTP报文,然后在传输层在打包成 TCP报文,然后再根据 DNS 查到的 IP 在网络层打包成 IP数据报,然后在通过链路层打包成以太网数据帧,发送给你的交换机(目的MAC为交换机的MAC):
你的交换机收到后,重新包装数据帧,再发送给你的路由器:
你的路由器利用 NAT(Network Address Translation),将你的主机IP(局域网IP)转换为外网IP,还会修改端口号,对外完全隐藏你的主机,再根据路由表选择一条合适的路径进行转发:
在接下来的过程中,每个节点都只改变 MAC 地址,然后在网络中一路向着目的地发
原文链接:https://blog.csdn.net/baidu_32045201/article/details/78305586
网际控制报文协议 ICMP
ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。
ICMP 报文分为差错报告报文和询问报文。
1. Ping
Ping 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。 Ping 的原理是通过向目的主机发送 ICMP Echo 请求报文,目的主机收到之后会发送 Echo 回答报文。Ping 会根据时 间和成功响应的次数估算出数据包往返时间以及丢包率。
2. Traceroute
Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。
Traceroute 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报(有的实现使用UDP,有的实现使用ICMP),并由目的主机发送终点不可达差错报告报文。
源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1,当 P1 到达路径上的第一个路由器 R1 时,R1 收下它并把 TTL 减 1,此时 TTL 等于 0,R1 就把 P1 丢弃,并向源主机发送一个 ICMP时间超过差错报告报文;
源主机接着发送第二个数据报 P2,并把 TTL 设置为 2。P2 先到达 R1,R1 收下后把 TTL 减 1 再转发给 R2,R2收下后也把 TTL 减 1,由于此时 TTL 等于 0,R2 就丢弃 P2,并向源主机发送一个 ICMP 时间超过差错报文。
不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是因为数据报封装的是无法交付的 UDP,因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。
之后源主机知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间。
虚拟专用网 VPN
由于 IP 地址的紧缺,一个机构能申请到的 IP 地址数往往远小于本机构所拥有的主机数。并且一个机构并不需要把所有的主机接入到外部的互联网中,机构内的计算机可以使用仅在本机构有效的 IP 地址(专用地址)。
有三个专用地址块:
10.0.0.0 ~ 10.255.255.255
172.16.0.0 ~ 172.31.255.255
192.168.0.0 ~ 192.168.255.255
VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指好像是,而实际上并不是,它有经过公用的互联网(功能上是只与本机构内的主机通信,但实现上是通过互联网互联个专用网实现的)。
下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3(同属于本机构内的不同专用网络)。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。
相当于一个虚拟网络里的所有主机都被一个路由器所表征。这样一个路由器只占用一个全球地址,但虚拟网里的所有主机都共享这一个全球地址,相当于复用了这个IP地址。
网络地址转换 NAT
专用网内部的主机使用本地 IP 地址又想和互联网上的主机通信时,可以使用 NAT 来将本地 IP 转换为全球 IP。
专用网内的A要与互联网上的B通信,首先将数据发给路由器,路由器将源地址IP改为路由器的全球IP,然后发给B,B收到后将响应发回给路由器,路由器将目的IP改为A的源地址。
通过NAT进行的通信必须由专用网内的主机发起,否则互联网上的主机发给路由器后,路由器不知道要转发给专用网内的哪个主机。
路由器的结构
路由器从功能上可以划分为:路由选择和分组转发。一个选择,一个用于真正实现处理
分组转发结构由三个部分组成:交换结构、一组输入端口和一组输出端口。
路由器分组转发流程
从数据报的首部提取目的主机的 IP 地址 D,得到目的网络地址 N。
若 N 就是与此路由器直接相连的某个网络地址,则进行直接交付;
若路由表中有目的地址为 D 的特定主机路由,则把数据报传送给表中所指明的下一跳路由器;
若路由表中有到达网络 N 的路由,则把数据报传送给路由表中所指明的下一跳路由器;
若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器;
否则报告转发分组出错
路由选择协议
路由选择协议都是自适应的,能随着网络通信量和拓扑结构的变化而自适应地进行调整(调整到达各目的地的最短路径及下一跳地址)。
互联网可以划分为许多较小的自治系统 AS,一个 AS 可以使用一种和别的 AS 不同的路由选择协议。
可以把路由选择协议划分为两大类:
IGP:自治系统内部的路由选择:RIP 和 OSPF
EGP:自治系统间的路由选择:BGP
1. 内部网关协议 RIP
RIP 是一种基于距离向量的路由选择协议(RIP)。距离是指跳数,直接相连的路由器跳数为 1。跳数最多为 15,超过 15 表示不可达。
RIP 按固定的时间间隔仅和相邻路由器交换自己的路由表,经过若干次交换之后,所有路由器最终会知道到达本自治系统中任何一个网络的最短距离和下一跳路由器地址。
距离向量算法:
对地址为 X 的相邻路由器发来的 RIP 报文(是UDP报文,这里有点奇怪,但书上的确是这样),先修改报文中的所有项目,把下一跳字段中的地址改为 X,并把所有的距离字段加 1;
对修改后的 RIP 报文中的每一个项目,进行以下步骤:
若原来的路由表中没有目的网络 N,则把该项目添加到路由表中;
否则:若下一跳路由器地址是 X,则把收到的项目替换原来路由表中的项目;
否则:若收到的项目中的距离 d小于路由表中的距离,则进行更新(例如原始路由表项为 Net2, 5, P,新表项为 Net2, 4, X,则更新);否则什么也不做。
若 3 分钟还没有收到相邻路由器的更新路由表,则把该相邻路由器标为不可达,即把距离置为 16(30秒交换一次)。
RIP的优点是简单,开销小。
缺点是使用RIP将限制网络的规模,最大距离为15.交换信息是一个路由器中的完整路由表,如果网络规模扩大,开销将变大。
且网络出现故障时,较长时间才能将此信息送达所有路由器。
比如R1能直接到网1,而R2要经过R1才能到网1,此时R1中网1的距离是1,R2中是2。若某刻不可到达网1了,R1修改为到网1距离为16,但马上R2的更新报文到达,R1还来不及将自己修改后的路由表给R2,此时R1收到R2的报文,发现经过R2可到达网1,因此修改自己的为到网1为3。其后R2收到R1的报文,修改到网1的距离为4;其后又更新R1。。。。。直到最后某个路由器到网1的距离为16后,在通知另一个路由器,这下他们才知道不可到达网1。这样坏消息穿的太慢。
且RIP选择的一定是最少路由器的路由路径,而不能选择一条高速但路由器较多的路由路径。
2. 内部网关协议 OSPF
开放最短路径优先 OSPF,是为了克服 RIP 的缺点而开发出来的。
开放表示 OSPF 不受某一家厂商控制,而是公开发表的;最短路径优先表示使用了 Dijkstra 提出的最短路径算法SPF。
OSPF 具有以下特点:
向本自治系统中的所有路由器发送信息,这种方法是洪泛法(因为每个路由器都有一个链路状态数据库,存放着全网的拓扑结构图,因此当某条链路发生变化要迅速通知全网更新他们的数据库)。
发送的信息就是与相邻路由器的链路状态,链路状态包括与哪些路由器相连以及链路的度量,度量用费用、距离、时延、带宽等来表示。
只有当链路状态发生变化时(如不可达),路由器才会发送信息(RIP每30秒更新一次)。
所有路由器都具有全网的拓扑结构图,并且是一致的。相比于 RIP,OSPF 的更新过程收敛的很快。
OSPF不像RIP使用UDP,而是直接使用IP数据包发送
当一个路由器刚开始工作时,它通过问候分组得知它相邻的路由器,如果让全网的路由器都广播来为这个刚工作的路由器更新数据库代价太大,因此这个路由器向相邻路由器请求摘要信息,摘要信息里是哪些路由器有哪些链路信息,然后向对方请求所缺少的链路状态信息。
3. 外部网关协议 BGP
BGP(Border Gateway Protocol,边界网关协议)
AS 之间的路由选择很困难,主要是由于:
互联网规模很大;
各个 AS 内部使用不同的路由选择协议,无法准确定义路径的度量;
AS 之间的路由选择必须考虑有关的策略,比如有些 AS 不愿意让其它 AS 经过。
BGP 只能寻找一条比较好的路由,而不是最佳路由。
传输层
UDP 和 TCP 的特点(区别)
用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信。
传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)。
UDP 首部格式
首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。
TCP 首部格式
序号 :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100字节,那么下一个报文段的序号应为 401。
确认号 :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。
数据偏移 :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。
确认 ACK :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把ACK 置 1。
同步 SYN :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。
终止 FIN :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。
URG(紧急位):表示本报文段中发送的数据是否包含紧急数据。URG=1,表示有紧急数据。后面的紧急指针字段只有当URG=1时才有效。
PSH:告诉对方收到该报文段后是否应该立即把数据推送给上层。如果为1,则表示对方应当立即把数据提交给上层,而不是缓存起来。
RST:只有当RST=1时才有用。如果你收到一个RST=1的报文,说明你与主机的连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接。或者说明你上次发送给主机的数据有问题,主机拒绝响应。
PSH:当发送多个最大段大小(MSS)的数据(例如,传输大文件)时,缓冲区允许更有效的数据传输。但是,在处理需要尽可能快地传输数据的实时应用程序时,大缓冲区的弊大于利。考虑一下Telnet会话会发生什么,例如,如果TCP在发送数据包之前等待有足够的数据来填充数据包:在第一个数据包进入远程设备之前,您必须键入超过一千个字符。不是很有用。
这是PSH标志进入的地方.TCP在会话级别提供的套接字可由应用程序写入,可选择立即“推送”数据,而不是等待其他数据进入缓冲区。发生这种情况时,传出TCP数据包中的PSH标志设置为1(打开)。在接收到设置了PSH标志的分组时,连接的另一侧知道立即将该分段转发到应用程序。总而言之,TCP的推送功能完成了两件事:
- 发送应用程序通知TCP应立即发送数据。
- TCP报头中的PSH标志通知接收主机应立即将数据推送到接收应用程序。
URG标志用于通知接收站段内的某些数据是紧急的并且应该优先。如果设置了URG标志,则接收站将评估紧急指针,即TCP标头中的16位字段。该指针指示从第一个字节开始计算的段中有多少数据是紧急的。URG=1,紧急指针指向包内数据段的某个字节(数据从第一字节到指针所指字节就是紧急数据,不进入接收缓冲就直接交给上层进程,余下的数据要进入接收缓冲的)
紧急数据:URG标志设置为1时,紧急指针才有效,紧急方式是向对方发送紧急数据的一种方式,表示数据要优先处理。他是一个正的偏移量,与TCP收不中序号字段的值相加表示紧急数据后面的字节,即紧急指针是指向紧急数据最后一个字节的下一个字节。这是协议编写上的错误,RFC1122中对此给出了更正说明,紧急指针是数据最后一个字节,不是最后字节的下一位置,TCP首部中只有紧急指针指出紧急数据的位置,他所指的字节为紧急数据,但没有办法指定紧急数据的长度。
URG=1,表示紧急指针指向包内数据段的某个字节(数据从第一字节到指针所指向字节就是紧急数据)不进入缓冲区(一般不都是待发送的数据要先进入发送缓存吗?就直接交个上层进程,余下的数据都是要进入接收缓冲的;一般来说TCP是要等到整个缓存都填满了后在向上交付,但是如果PSH=1的话,就不用等到整个缓存都填满,直接交付,但是这里的交付仍然是从缓冲区交付的,URG是不要经过缓冲区的
1、URG就想是有个很急的快递从仓库出发,被标记了URG的快件直接交给一个快递员直接送到你手中,不跟其他货件配送,这是单独紧急配送到你手中的。2、PSH可以用在总站坐车来比喻,客人甲是被标记成PSH的人,我在总站一上车,汽车就发车了(无论汽车是否满载),去到第二个城市客运站过安检,队列看到你直接把你和你前面的人安排成一批次人统一处理(不用再等你后面的人来了再处理)
窗口 :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
TCP 的三次握手
三次握手的原因
第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。
客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。
TCP 的四次挥手
以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。
A 发送连接释放报文,FIN=1。
B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。
当 B 不再需要连接时,发送连接释放报文,FIN=1。
A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。
B 收到 A 的确认后释放连接。
四次挥手的原因
客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。
四次挥手中为什么不能改为三次挥手:
1.为什么第二次挥手与第三次挥手不能像三次握手合并在一起:因为只是关闭了客户端的写入,客户端不在写数据,但服务端可能还要写数据。
2.为什么至于最后一次握手不能省略:
服务端要确认自己发送的第三次挥手已经到达了对端,没有第四次则不知道第三次挥手到底是到达了还是丢失了。
三次握手对应的SOCKET函数:
客户端:
创建套接字后,通过connect来进行第一次握手,当connect返回后,客户端到服务端的通道已经建立
服务端:
socket创建套接字, bind将套接字绑定到指定端口上,listen将创建的主动套接字转为监听套接字。
完成这三个后,服务端进入被动打开状态,等待客户端发送SYN进行第一次握手。
客户端通过connect进行第一次握手,Accept从阻塞被唤醒,在accept期间向客户端进行第二次以及第三次握手。
Accept返回后三次握手完毕,建立了连接。
四次握手对应的SOCKET函数:
见图
TIME_WAIT
客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:
1)可靠地实现TCP全双工连接的终止
在进行关闭连接四路握手协议时,最后的ACK是由主动关闭端发出的,如果这个最终的ACK丢失,服务器将重发最终的FIN,因此客户端必须维护状态信息允 许它重发最终的ACK。如果不维持这个状态信息,那么客户端将响应RST分节,服务器将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。因而,要实现TCP全双工连接的正常终止,必须处理终止序列四个分节中任何一个分节的丢失情况,主动关闭 的客户端必须维持状态信息进入TIME_WAIT状态。
2)允许老的重复分节在网络中消逝
TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个 原来的迷途分节就称为lost duplicate。在关闭一个TCP连接后,马上又重新建立起一个相同的IP地址和端口之间的TCP连接,后一个连接被称为前一个连接的化身 (incarnation),那么有可能出现这种情况,前一个连接的迷途重复分组在前一个连接终止后出现,从而被误解成从属于新的化身。为了避免这个情 况,TCP不允许处于TIME_WAIT状态的连接启动一个新的化身,因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个TCP连接的时 候,来自连接先前化身的重复分组已经在网络中消逝。
TCP 可靠传输
TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。
一个报文段从发送再到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下:
其中,0 ≤ a < 1,RTTs 随着 a 的增加更容易受到 RTT 的影响。
超时时间 RTO 应该略大于 RTTs,TCP 使用的超时时间计算如下:
其中 RTTd 为偏差的加权平均值。
如果出现超时重传,则RTT不好确定,因为无法判定确认报文到底是对那个超时的报文段的确认还是对重传的报文的确认,如果说是对重传报文的确认,但将它误认为对原来的报文的确认,则计算的RTTS和RTTO就会偏大。
Karn算法对这种情况进行了改善,即只要出现超时重传,则不将这次的RTT对RTTS进行更新。
但这样的问题时如果出现网络波动,传输时延增大了很多,则会出现重传报文,但又不会对RTTS进行更新,导致无法更新。
因此对Karn进行修正,报文每重传一次,将超时重传时间RTO增大2倍,直到不再发生重传,才按照RTO=RTTS+4*RTTd进行计算重传时间。
TCP 滑动窗口
窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。
发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。
TCP 流量控制
流量控制是为了控制发送方发送速率,保证接收方来得及接收。
接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为0,则发送方不能发送数据。
发送窗口与发送缓存:
发送缓冲中存放的有:
1.已发送但未被确认的数据
2.准备发送的数据
发送窗口只是发送缓存的一部分,发送窗口的后沿与发送缓存的后沿是重合的,发送缓存窗口的移动依靠的就是对字节的确认,如果确认了若干字节,则发送窗口的后沿与发送缓存的后沿都会向前移动。如果写入的速度太快,赶不上确认的速度,则后沿前移的速度追不上写入指针前移的速度,当写入指针与发送缓存的前沿重合,发送缓存中的可用空间为0,就无法在写入了。
接收缓存与接收窗口:
接收缓存中存放的有:
1.按序到达但未被应用程序读取的数据
2.未按序到达的数据
接收窗口是接收缓存的一部分,接收窗口的前沿与接收缓存的前沿是重合的。接收缓存窗口的移动依靠的是应用程序对接收缓存的读取,如果读取的过慢(相比于对端发送的速度来说),则滑动窗口由于不断确认,后沿向前移,这个前移的速度比接收缓存前移(靠读取数据)快,则最终接收窗口的后沿将赶上发送缓存的前沿,而发送缓存的前沿与接收窗口的前沿是重合的,最后接收窗口大小变为0,无法接收。
TCP 拥塞控制
为了提高网络利用率,肯定不能每次只发送一个报文段大小的数据,但如果发送的太多,又会造成网络拥塞
拥塞控制就是动态的调整发送速率,使网络和效率维持相对的平衡
如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。
TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。
这里的单位都是报文段,如cwnd=1表示大小为1个报文段大小,但实际上TCP是字节流单位的,这里的1指的是1个报文段大小的字节流
发送的最初执行慢开始,令 cwnd = 1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 ...
注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能性也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。
如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始(重新执行慢开始指:再将拥塞窗口设置为1,重新开始慢开始。)。
2. 快重传与快恢复
在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。
在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。
首先:
快重传要求接收端接收到数据后立即发送确认,而不能等到接收端发送数据时捎带确认。
其次,这三个重复确认为:
当M1,M2到达,发送对M2的确认,但M3丢了,此时发送端发送的M4到了,但M2与M4间缺了个M3,因此仍发送对M2的确认,M5,M6到达后也发送两次。
发送端一共接收到了4个对M2的确认,第一个为真正的确认,后面三个是由于M3丢失而M4,M5与M6达到而接到的确认。因此立即重传M3。
在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd =ssthresh,注意到此时直接进入拥塞避免。
慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd设定为 ssthresh。
快重传不是一种新的方案,而是指尽可能早的知道报文段丢失,并尽早的开启重传,这样能让发送端不会误认为发生了拥塞。
如果网络没有拥塞,则三个确认会很快就被发送端知晓,从而在超时重传之前就进行重传,从而刷新超时重传的计时器,这样就不会使发送方进入慢开始。
而如果网络真的很拥塞,则这三个确认不会很快就送到发送端,从而发送端在三个确认到来之前就超时重传了,这样就知道了网络的确很拥塞
快重传可以避免发送端错误的认为网络发生拥塞而降低发送率,使网络的吞吐量下降。
快恢复是跟随在快重传后的对门限和拥塞窗口的调整机制。
网络拥塞与丢失个别报文段的区别在于:
网络拥塞的话,发送端是接收不到确认报文,直到超时后,才开始重传,这是它认为网络发生了拥塞。
而对于快重传而言:
虽然丢失了M3,但M4,M5到达后仍在发送对M2的确认报文,只要能接收到确认报文,发送端就会重新设置计时器,而不会认为发送拥塞,直到多余的三个报文段到了后马上重传(即快重传).
end-----------------------------------------------
应用层
域名系统
DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传从而保证可靠性。在两种情况下会使用 TCP 进行传输:
如果返回的响应超过的 512 字节(UDP 最大只支持 512 字节的数据)。
区域传送(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。将一个区域文件复制到多个DNS服务器的过程被称为区域传输。它是通过从主服务器上将区域文件的信息复制到辅助服务器来实现的,当主服务器的区域有变化时,该变化会通过区域传输机制复制到该区域的辅助服务器上。
DNS查询:
1).递归查询:
当本地域名服务器不知道被查询域名的IP地址,那么本地域名服务器向根域名服务器查询,根服务器再向其他域名服务器进行递归查询,在得到IP地址后返回给本地域名服务器,再由本地域名服务器返回给主机。
2).迭代查询:
当本地域名服务器不知道被查询域名的IP地址,那么本地域名服务器向根域名服务器进行查询,根域名服务器要么给出答案,要么告诉本地域名服务器应该向哪个顶级域名服务器询问。本地域名服务器向指定顶级域名服务器查询后,要么得到答案,要么再向某个权限域名服务器进行请求,直到得到答案并返回给主机。
递归查询,每一次查询,被查询服务器都以DNS客户的身份向另一个服务器进行查询,就像压栈一样,得到答案后一层层返回弹栈。
迭代查询,每一次查询,都是由本地域名服务器向某个服务器发起。
主机向本地域名服务器的一定是递归查询,因为本地域名服务器一定是以DNS客户的身份去请求答案。
而本地域名服务器向其他服务器请求,可以是递归查询,也可以是迭代查询。
域名解析过程
关于DNS缓存的机制,有一篇非常详细的文章What really happens when you navigate to a URL。
简单来说,一条域名的DNS记录会在本地有两种缓存:浏览器缓存和操作系统(OS)缓存。在浏览器中访问的时候,会优先访问浏览器缓存,
如果未命中则访问OS缓存,最后再访问DNS服务器(一般是ISP提供),然后DNS服务器会递归式的查找域名记录,然后返回。
1.在浏览器中输入www.qq.com域名,操作系统会先检查自己本地的hosts文件是否有这个网址映射关系,如果有,就先调用这个IP地址映射,完成域名解析。
2.如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存,是否有这个网址映射关系,如果有,直接返回,完成域名解析。
3.如果hosts与本地DNS解析器缓存都没有相应的网址映射关系,首先会找TCP/IP参数中设置的首选DNS服务器,在此我们叫它本地DNS服务器,
此服务器收到查询时,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。
4.如果要查询的域名,不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析,此解析不具有权威性。
5.如果本地DNS服务器本地区域文件与缓存解析都失效,则根据本地DNS服务器的设置(是否设置转发器)进行查询,
如果未用转发模式,本地DNS就把请求发至 “根DNS服务器”,“根DNS服务器”收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP。
本地DNS服务器收到IP信息后,将会联系负责.com域的这台服务器。这台负责.com域的服务器收到请求后,如果自己无法解析,
它就会找一个管理.com域的下一级DNS服务器地址(qq.com)给本地DNS服务器。当本地DNS服务器收到这个地址后,就会找qq.com域服务器,重复上面的动作,进行查询,直至找到www.qq.com主机。
6.如果用的是转发模式,此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。
不管是本地DNS服务器用是是转发,还是根提示,最后都是把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机。
文件传送协议
FTP 使用 TCP 进行连接,它需要两个连接来传送一个文件:
控制连接:服务器打开端口号 21 等待客户端的连接,客户端主动建立连接后(告诉服务器自己的一个端口号,用于数据传输),使用这个连接将客户端的命令传送给服务器,并传回服务器的应答。
数据连接:用来传送一个文件数据。
根据数据连接是否是服务器端主动建立,FTP 有主动和被动两种模式:
主动模式:服务器端主动建立数据连接,其中服务器端的端口号为 20,客户端的端口号随机,但是必须大于1024,因为 0~1023 是熟知端口号。
主动模式FTP:
主动模式下,FTP客户端从任意的非特殊的端口(N > 1023)连入到FTP服务器的命令端口--21端口。然后客户端在N+1(N+1 >= 1024)端口监听,并且通过N+1(N+1 >= 1024)端口发送命令给FTP服务器。服务器会反过来连接用户本地指定的数据端口,比如20端口。
以服务器端防火墙为立足点,要支持主动模式FTP需要打开如下交互中使用到的端口:
- FTP服务器命令(21)端口接受客户端任意端口(客户端初始连接)
- FTP服务器命令(21)端口到客户端端口(>1023)(服务器响应客户端命令)
- FTP服务器数据(20)端口到客户端端口(>1023)(服务器初始化数据连接到客户端数据端口)
- FTP服务器数据(20)端口接受客户端端口(>1023)(客户端发送ACK包到服务器的数据端口)
-
在第1步中,客户端的命令端口与FTP服务器的命令端口建立连接,并发送命令“PORT 1027”。然后在第2步中,FTP服务器给客户端的命令端口返回一个"ACK"。在第3步中,FTP服务器发起一个从它自己的数据端口(20)到客户端先前指定的数据端口(1027)的连接,最后客户端在第4步中给服务器端返回一个"ACK"。
主动方式FTP的主要问题实际上在于客户端。FTP的客户端并没有实际建立一个到服务器数据端口的连接,它只是简单的告诉服务器自己监听的端口号,服务器再回来连接客户端这个指定的端口。对于客户端的防火墙来说,这是从外部系统建立到内部客户端的连接,这是通常会被阻塞的。
被动模式:客户端主动建立数据连接,其中客户端的端口号由客户端自己指定,服务器端的端口号随机。
被动模式FTP
为了解决服务器发起到客户的连接的问题,人们开发了一种不同的FTP连接方式。这就是所谓的被动方式,或者叫做PASV,当客户端通知服务器它处于被动模式时才启用。
在被动方式FTP中,命令连接和数据连接都由客户端,这样就可以解决从服务器到客户端的数据端口的入方向连接被防火墙过滤掉的问题。当开启一个FTP连接时,客户端打开两个任意的非特权本地端口(N >; 1024和N+1)。第一个端口连接服务器的21端口,但与主动方式的FTP不同,客户端不会提交PORT命令并允许服务器来回连它的数据端口,而是提交PASV命令。这样做的结果是服务器会开启一个任意的非特权端口(P >; 1024),并发送PORT P命令给客户端。然后客户端发起从本地端口N+1到服务器的端口P的连接用来传送数据。
对于服务器端的防火墙来说,必须允许下面的通讯才能支持被动方式的FTP:
-
- FTP服务器命令(21)端口接受客户端任意端口(客户端初始连接)
- FTP服务器命令(21)端口到客户端端口(>1023)(服务器响应客户端命令)
- FTP服务器数据端口(>1023)接受客户端端口(>1023)(客户端初始化数据连接到服务器指定的任意端口)
- FTP服务器数据端口(>1023)到客户端端口(>1023)(服务器发送ACK响应和数据到客户端的数据端口)
-
动态主机配置协议
DHCP (Dynamic Host Configuration Protocol) 提供了即插即用的连网方式,用户不再需要手动配置 IP 地址等信息。
DHCP 配置的内容不仅是 IP 地址,还包括子网掩码、网关 IP 地址。
DHCP 工作过程如下:
1. 客户端发送 Discover 报文,该报文的目的地址为 255.255.255.255:67(广播),源地址为 0.0.0.0:68,被放入 UDP中(广播当然是UDP),该报文被广播到同一个子网的所有主机上。如果客户端和 DHCP 服务器不在同一个子网,就需要使用中继代理(一般不会在每个网络上都配置一个DHCP服务器,因为这样会使DHCP服务器过多,因此是在每个网络中至少配置一个DHCP中继代理(通常是一台路由器),其中配置有DHCP服务器的IP地址信息,当中继代理收到主机广播的DISCOVER报文后,以单播向DHCP服务器转发此报文,收到DHCP服务器的offer报文后,再把这个报文转发给主机)。
2. DHCP 服务器收到 Discover 报文之后,发送 Offer 报文给客户端,该报文包含了客户端所需要的信息。因为客户端可能收到多个 DHCP 服务器提供的信息,因此客户端需要进行选择。
3. 如果客户端选择了某个 DHCP 服务器提供的信息,那么就发送 Request 报文给该 DHCP 服务器。
4. DHCP 服务器发送 Ack 报文,表示客户端此时可以使用提供给它的信息。
远程登录协议
TELNET 用于登录到远程主机上,并且远程主机上的输出也会返回。
TELNET 可以适应许多计算机和操作系统的差异,例如不同操作系统系统的换行符定义。
电子邮件协议
一个电子邮件系统由三部分组成:用户代理、邮件服务器以及邮件协议。
邮件协议包含发送协议和读取协议,发送协议常用 SMTP,读取协议常用 POP3 和 IMAP。
1. SMTP
SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME 并没有改动或者取代 SMTP,而是增加邮件主体的结构,定义了非 ASCII 码的编码规则。
连接建立:
发送人的邮件发送到邮件服务器的缓存之后,SMTP客户(发送邮件服务器)每隔一段时间对邮件缓存扫描一次。发现有邮件,就使用25号端口与接收方服务器建立TCP。建立后,接收方SMTP发送220 Service ready,然后SMTP客户(发送邮件服务器)向SMTP接收服务器发送HELO命令,附上发送方的主机名。SMTP服务器若能接收,则发送250 OK,表示已准备好接收。若不可用,则回答421 Service not available。
邮件传送:
邮件的发送先从MAIL命令开始,MAIL后面有发件人地址,如果SMTP服务器准备好接收邮件,则回答250 OK,否则返回一个代码指出错误原因。
下面跟一个或多个RCPT命令,指明收件人,每发送一个RCPT命令都要有一个信息返回,如250 OK,表明指明的邮箱在接受法的系统中或550 Nosuch user here表明不存在此邮箱。
RCPT命令的作用是:先弄清楚是否能接收邮件才发送正文,不至于发送了很长的内容后才知道地址错误,浪费通信资源。
在下面是DATA命令,表示开始传送邮件内容,服务器返回354 Start mail input:endwith <CRLF>.<CRLF>. <CRLF>表示回车换行,若不能接收邮件,则返回421 服务器不可用。接着SMTP客户发送邮件内容,发送完毕以<CRLF>.<CRLF>结尾。若邮件收到,则返回250 OK或返回差错代码。
3.连接释放:
邮件发送完毕后,SMTP客户发送QUIT,SMTP返回221 服务关闭,接着SMTP同意释放TCP。
SMTP的缺点:
发送地址可以随意填写,造成大量垃圾邮件。
SMTP本为传送ASCII码,虽然有了MIME但传送非ASCII码的长报文传输效率不高。
邮件是明文,不利于保密。
2. POP3
使用BS模型的工作方式。接收用户的用户代理必须运行POP3客户程序,收件人所连接的邮件服务器必须运行POP3服务器程序,同时也要运行SMTP服务器程序。
POP3 的特点是只要用户从服务器上读取了邮件,就把该邮件删除。
缺点是一旦读取就删除,如果换了电脑则无法读取已经读过的邮件。
3. IMAP
IMAP 协议中客户端和服务器上的邮件保持同步,如果不手动删除邮件,那么服务器上的邮件也不会被删除。IMAP这种做法可以让用户随时随地去访问服务器上的邮件。
缺点是如果没有将邮件复制到计算机上,则一直存放在IMAP服务器上,要想查阅必须上网。
常用端口及协议
Web 页面请求过程
https://blog.csdn.net/wenyun_kang/article/details/81290904
1. DHCP 配置主机信息
假设主机最开始没有 IP 地址以及其它信息,那么就需要先使用 DHCP 来获取。
主机生成一个 DHCP 请求报文,并将这个报文放入具有目的端口 67 和源端口 68 的 UDP 报文段中。
该报文段则被放入在一个具有广播 IP 目的地址(255.255.255.255) 和源 IP 地址(0.0.0.0)的 IP 数据报中。
该数据报则被放置在 MAC 帧中,该帧具有目的地址 FF:FF:FF:FF:FF:FF,将广播到与交换机连接的所有设备。
连接在交换机的 DHCP 服务器收到广播帧之后,不断地向上分解得到 IP 数据报、UDP 报文段、DHCP 请求报文,之后生成 DHCP ACK 报文,该报文包含以下信息:IP 地址、DNS 服务器的 IP 地址、默认网关路由器的 IP地址和子网掩码。该报文被放入 UDP 报文段中,UDP 报文段有被放入 IP 数据报中,最后放入 MAC 帧中。
该帧的目的地址是请求主机的 MAC 地址,因为交换机具有自学习能力,之前主机发送了广播帧之后就记录了MAC 地址到其转发接口的交换表项,因此现在交换机就可以直接知道应该向哪个接口发送该帧。
主机收到该帧后,不断分解得到 DHCP 报文。之后就配置它的 IP 地址、子网掩码和 DNS 服务器的 IP 地址,并在其 IP 转发表中安装默认网关。
2.ARP 解析 MAC 地址
主机通过浏览器生成一个 TCP 套接字,套接字向 HTTP 服务器发送 HTTP 请求。为了生成该套接字,主机需要知道网站的域名对应的 IP 地址。
主机生成一个 DNS 查询报文,该报文具有 53 号端口,因为 DNS 服务器的端口号是 53。
该 DNS 查询报文被放入目的地址为 DNS 服务器 IP 地址的 IP 数据报中。
该 IP 数据报被放入一个以太网帧中,该帧将发送到网关路由器。
DHCP 过程只知道网关路由器的 IP 地址,为了获取网关路由器的 MAC 地址,需要使用 ARP 协议。
主机生成一个包含目的地址为网关路由器 IP 地址的 ARP 查询报文,将该 ARP 查询报文放入一个具有广播目的地址(FF:FF:FF:FF:FF:FF)的以太网帧中,并向交换机发送该以太网帧,交换机将该帧转发给所有的连接设备,包括网关路由器。
网关路由器接收到该帧后,不断向上分解得到 ARP 报文,发现其中的 IP 地址与其接口的 IP 地址匹配,因此就发送一个 ARP 回答报文,包含了它的 MAC 地址,发回给主机。
3. DNS 解析域名
知道了网关路由器的 MAC 地址之后,就可以继续 DNS 的解析过程了。
网关路由器接收到包含 DNS 查询报文的以太网帧后,抽取出 IP 数据报,并根据转发表决定该 IP 数据报应该转发的路由器。
因为路由器具有内部网关协议(RIP、OSPF)和外部网关协议(BGP)这两种路由选择协议,因此路由表中已经配置了网关路由器到达 DNS 服务器的路由表项。
到达 DNS 服务器之后,DNS 服务器抽取出 DNS 查询报文,并在 DNS 数据库中查找待解析的域名。
找到 DNS 记录之后,发送 DNS 回答报文,将该回答报文放入 UDP 报文段中,然后放入 IP 数据报中,通过路由器反向转发回网关路由器,并经过以太网交换机到达主机。
HTTP
URI,URL,URN:
URL和URN都是URI的子集。
URL和URN都是URI,但是URI不一定是URL或者URN
关于URL:
URL是URI的一种,不仅标识了Web 资源,还指定了操作或者获取方式,同时指出了主要访问机制和网络位置。
关于URN:
URN是URI的一种,用特定命名空间的名字标识资源。使用URN可以在不知道其网络位置及访问方式的情况下讨论资源。
我们一起来看下面这个虚构的例子。这是一个URI:
http://bitpoetry.io/posts/hello.html#intro
http://是定义如何访问资源的方式。另外bitpoetry.io/posts/hello.html是资源存放的位置,那么,在这个例子中,#intro是资源。
URL是URI的一个子集,告诉我们访问网络位置的方式。在我们的例子中,URL应该如下所示:
http://bitpoetry.io/posts/hello.html
URN是URI的子集,包括名字(给定的命名空间内),但是不包括访问方式,如下所示:bitpoetry.io/posts/hello.html#intro
HTTP 方法
(GET,POST,HEAD)联合记忆 都是向服务器请求,GET得到响应,HEAD无响应内容,POST传输数据
(PUT,PATCH,DELETE)联合记忆 都是在服务器上操作,PUT添加,DELETE删除,都不安全;PATCH比PUT可以部分更新,PUT只能完全替换
GET
获取资源
当前网络请求中,绝大部分使用的是 GET 方法。
HEAD
获取报文首部
和 GET 方法类似,但是不返回报文实体主体部分。
主要用于确认 URL 的有效性以及资源更新的日期时间等。
HEAD和GET本质是一样的,区别在于HEAD不含有呈现数据,而仅仅是HTTP头信息。有的人可能觉得这个方法没什么用,其实不是这样的。想象一个业务情景:欲判断某个资源是否存在,我们通常使用GET,但这里用HEAD则意义更加明确。
POST
传输实体主体
POST 主要用来传输数据,而 GET 主要用来获取资源。
更多 POST 与 GET 的比较请见第九章。
PUT
上传文件
由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。
PATCH
对资源进行部分修改
PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。
DELETE
删除文件
与 PUT 功能相反,并且同样不带验证机制。
OPTIONS
查询支持的方法
查询指定的 URL 能够支持的方法。
会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。
CONNECT
要求在与代理服务器通信时建立隧道
使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。
TRACE
追踪路径
服务器会将通信路径返回给客户端。
发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。
通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪)。
根据幂等性区分POST与PUT的使用
了解REST后很长一段时间不能明确区分PUT和POST的区别,在使用时很容易混淆,完全可根据idempotent(幂等性)做区分。
举一个简单的例子,假如有一个博客系统提供一个Web API,模式是这样http://superblogging/blogs/{blog-name},很简单,将{blog-name}替换为我们的blog名字,往这个URI发送一个HTTP PUT或者POST请求,HTTP的body部分就是博文,这是一个很简单的REST API例子。
我们应该用PUT方法还是POST方法?
取决于这个REST服务的行为是否是idempotent的,假如我们发送两个http://superblogging/blogs/post/Sample请求,服务器端是什么样的行为?如果产生了两个博客帖子,那就说明这个服务不是idempotent的,因为多次使用产生了副作用了嘛;如果后一个请求把第一个请求覆盖掉了,那这个服务就是idempotent的。前一种情况,应该使用POST方法,后一种情况,应该使用PUT方法。
HTTP 状态码
1XX 信息
100 Continue :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
2XX 成功
(200,204,206)联合记忆 200成功 206成功但只是拿部分数据 204成功但返回的是无实体
200 OK
204 No Content :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端
往服务器发送信息,而不需要返回数据时使用。
206 Partial Content :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。
HTTP/206 “Partial Content”响应.这种响应是在客户端表明自己只需要目标URL上的部分资源的时候返回的.
你可以通过Range请求头辨认出一个部分内容请求.该请求头表明了客户端需要请求资源的哪一部分:在上图的请求中,客户端告诉服务器,它需要该视屏文件中从172,032到13,325,503字节范围内的数据.
3XX 重定向
301 Moved Permanently :永久性重定向
302 Found :临时性重定向
301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。
301重定向 代表永久性转移(Permanently Moved)
302 跳转代表暂时性转移(Temporarily Moved)两者之间的差别
1)对于用户
301和302没有区别,都是浏览器里面的URL跳转变成了一个新的URL地址
2)对于搜索引擎来说存在网址劫持问题
302重定向和网址劫持(URL hijacking)有什么关系呢?这要从搜索引擎如何处理302转向说起。从定义来说,从网址A做一个302重定向到网址B时,主机服务器的隐含意思是网址A随时有可能改主意,重新显示本身的内容或转向其他的地方。大部分的搜索引擎在大部分情况下,当收到302重定向时,一般只要去抓取目标网址就可以了,也就是说网址B。
实际上如果搜索引擎在遇到302转向时,百分之百的都抓取目标网址B的话,就不用担心网址URL劫持了。问题就在于,有的时候搜索引擎,尤其是Google,并不能总是抓取目标网址。为什么呢?比如说,有的时候A网址很短,但是它做了一个302重定向到B网址,而B网址是一个很长的乱七八糟的URL网址,甚至还有可能包含一些问号之类的参数。很自然的,A网址更加用户友好,而B网址既难看,又不用户友好。这时Google很有可能会仍然显示网址A。
由于搜索引擎排名算法只是程序而不是人,在遇到302重定向的时候,并不能像人一样的去准确判定哪一个网址更适当,这就造成了网址URL劫持的可能性。也就是说,一个不道德的人在他自己的网址A做一个302重定向到你的网址B,出于某种原因, Google搜索结果所显示的仍然是网址A,但是所用的网页内容却是你的网址B上的内容,这种情况就叫做网址URL劫持。你辛辛苦苦所写的内容就这样被别人偷走了
不同在于,301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了)搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址(想换个域名,旧的域名不用啦,这样用户访问旧域名时用301就重定向到新的域名。其实也是告诉搜索引擎收录的域名需要对新的域名进行收录。);;302表示旧地址A的资源还在(仍然可以访问),搜索引擎会抓取新的内容而保存旧的网址。这个重定向只是临时地从旧地址A跳转到地址B
301 意味着客户端可以对结果进行缓存, 搜索引擎或者浏览器都可以把跳转后的地址缓存下来,下一次不必发送这个请求。
302 就是客户端必须请求原链接。
返回是由服务器决定的,但是如何处理是客户端决定的,只不过按照 HTTP 协议规范规定 301 跳转是会被客户端记住。
当一个网站或者网页24—48小时内临时移动到一个新的位置,这时候就要进行302跳转,打个比方说,我有一套房子,但是最近走亲戚去亲戚家住了,过两天我还回来的。而使用301跳转的场景就是之前的网站因为某种原因需要移除掉,然后要到新的地址访问,是永久性的,就比如你的那套房子其实是租的,现在租期到了,你又在另一个地方找到了房子,之前租的房子不住了。
303 See Other :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源(重定向后使用GET)。
注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。
304 Not Modified :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。
如果客户端发送的是一个条件验证(Conditional Validation)请求,则web服务器可能会返回HTTP/304响应,这就表明了客户端中所请求资源的缓存仍然是有效的,也就是说该资源从上次缓存到现在并没有被修改过.条件请求可以在确保客户端的资源是最新的同时避免因每次都请求完整资源给服务器带来的性能问题.
当用户访问一个网页时,条件请求可以加速网页的打开时间(因为可以省去传输整个响应体的时间),但仍然会有网络延迟,因为浏览器还是得为每个资源生成一条条件请求,并且等到服务器返回HTTP/304响应,才能读取缓存来显示网页
307 Temporary Redirect :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的POST 方法改成 GET 方法。
303和307是HTTP1.1新加的服务器响应文档的状态码,它们是对HTTP1.0中的302状态码的细化(一个使用GET,一个不允许改为GET),主要用在对非GET、非HEAD方法的响应上。因为POST不是mideng
4XX 客户端错误
400 Bad Request :请求报文中存在语法错误。
401 Unauthorized :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
403 Forbidden :请求被拒绝。
404 Not Found
5XX 服务器错误
500 Internal Server Error :服务器正在执行请求时发生错误。
503 Service Unavailable :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。
HTTP 首部
有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段
请求首部字段
Host 请求资源所在服务器
User-Agent HTTP 客户端程序的信息
响应首部字段
Location 令客户端重定向至指定 URI
实体首部字段(HTTP实体首部描述了HTTP报文的内容。请求与响应中都可能有实体首部字段,只要传输了实体)
Content-Length 实体主体的大小
Content-Encoding 实体主体适用的编码方式
Content-Type 实体主体的媒体类型
------------------------------------------------------------------------------------
具体应用
连接管理
1. 短连接与长连接
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问的 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 Connection: close ;
在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 Connection : Keep-Alive 。
2. 流水线
默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
流水线是在同一条长连接上连续发出请求,而不用等待响应返回,这样可以减少延迟。
心跳检测:
轮询与心跳检测:
轮询:
概括来说是服务端定时主动的去与要监控状态的客户端(或者叫其他系统)通信,询问当前的某种状态,客户端返回状态信息,客户端没有返回或返回错误、失效信息、则认为客户端已经宕机,然后服务端自己内部把这个客户端的状态保存下来(宕机或者其他),如果客户端正常,那么返回正常状态,如果客户端宕机或者返回的是定义的失效状态那么当前的客户端状态是能够及时的监控到的,如果客户端宕机之后重启了那么当服务端定时来轮询的时候,还是可以正常的获取返回信息,把其状态重新更新。
心跳:最终得到的结果是与轮询一样的但是实现的方式有差别,心跳不是服务端主动去发信息检测客户端状态,而是在服务端保存下来所有客户端的状态信息,然后等待客户端定时来访问服务端,更新自己的当前状态,如果客户端超过指定的时间没有来更新状态,则认为客户端已经宕机或者其状态异常。
Cookie
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。
1. 用途
会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
个性化设置(如用户自定义设置、主题等)
浏览器行为跟踪(如跟踪分析用户行为等)
2. 创建过程
服务器发送的响应报文包含 Set-Cookie 首部字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。
客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。
3. 分类
会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
持久性 Cookie:指定过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie。
4. 作用域
Cookie有两个很重要的属性:Domain和Path,用来指示此Cookie的作用域:
Domain告诉浏览器当前要添加的Cookie的域名归属,如果没有明确指明则默认为当前域名,比如通过访问www.vinceruan.info添加的Cookie的域名默认就是www.vinceruan.info,通过访问blog.vinceruan.info所生成的Cookie的域名就是blog.vinceruan.info。
Path告诉浏览器当前要添加的Cookie的路径归属,如果没有明确指明则默认为当前路径,比如通过访问www.vinceruan.info/java/hotspot.html添加的Cookie的默认路径就是/java/,通过blog.vinceruan.info/java/hotspot.html生成的Cookie的路径也是/java/。
浏览器提交的Cookie需要满足以下两点:
1.当前域名或者父域名下的Cookie;
2.当前路径或父路径下的Cookie。要满足以上两个条件的Cookie才会被提交。
举个例子:有4个Cookie:
就是说.
path Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”。
domain 可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”
5. Secure
标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。
Secure属性是说如果一个cookie被设置了Secure=true,那么这个cookie只能用https协议发送给服务器,用http协议是不发送的。换句话说,cookie是在https的情况下创建的,而且他的Secure=true,那么之后你一直用https访问其他的页面(比如登录之后点击其他子页面),cookie会被发送到服务器,你无需重新登录就可以跳转到其他页面。但是如果这是你把url改成http协议访问其他页面,你就需要重新登录了,因为这个cookie不能在http协议中发送。
例子是:
前提条件:https://localhost:9102应用对cookie设置了Secure=true
1. 访问 https://localhost:9102/manager
2. 输入用户名、密码,用IE或者Chrome的developer tool会看到response的header里,set-cookie的值里有Secure属性
3. 登录后,继续访问https://localhost:9102/manager#user,可以正常看到内容
4. 修改url,访问http://localhost:9100/manager#domain,会跳转到登录页面,因为cookie在http协议下不发送给服务器,服务器要求用户重新登录
Session
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。
使用 Session 维护用户登录状态的过程如下:
用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID;
服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie值存入浏览器中;
客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。
浏览器禁用 Cookie
此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外,不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。
Cookie 与 Session 选择
Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选Session;
Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;
对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。
cookie,简而言之就是在本地计算机保存一些用户操作的历史信息(当然包括登录信息),并在用户再次访问该站点时浏览器通过HTTP协议将本地cookie内容发送给服务器,从而完成验证,或继续上一步操作。
session,简而言之就是在服务器上保存用户操作的历史信息。服务器使用session id来标识session,session id由服务器负责产生,保证随机性与唯一性,相当于一个随机密钥,避免在握手或传输中暴露用户真实密码。但该方式下,仍然需要将发送请求的客户端与session进行对应,所以可以借助cookie机制来获取客户端的标识(即session id),也可以通过GET方式将id提交给服务器。
cookie是将信息存储在客户端,在发送时服务端根据cookie来判断。而session是将信息存储在服务端,根据客户端cookie中或URL中session id的值,去数据库里找到对应的信息,再加以判断。
虽然都是由服务端进行判断,但cookie方式是将存储在客户端的信息以cookie的形式发过去,服务端直接根据这个来判断;而session需要服务端去服务端的数据库得到相应信息后才能判断,这里有一个找的过程,因为数据不在客户端,客户端只提供一个索引。
缓存
实现方法
让代理服务器进行缓存;
让客户端浏览器进行缓存。
Cache-Control
HTTP/1.1 通过 Cache-Control 首部字段来控制缓存
禁止进行缓存
no-store 指令规定不能对请求或响应的任何一部分进行缓存
Cache-Control: no-store
强制确认缓存
no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效时才能使用该缓存对客户端的请求进行响应。
使用 no-cache 指令的目的是为了防止从缓存中返回过期的资源。 客户端发送的请求中如果包含 no-cache 指令,则表示客户端将不会接 收缓存过的响应。于是,“中间”的缓存服务器必须把客户端请求转发 给源服务器。
Cache-Control: no-cache
私有缓存和公共缓存
private 指令规定了将资源作为私有缓存,只能被单独用户使用,一般存储在用户浏览器中。
Cache-Control: private
public 指令规定了将资源作为公共缓存,可以被多个用户使用,一般存储在代理服务器中。
Cache-Control: public
缓存过期机制
max-age 指令出现在请求报文,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。
max-age 指令出现在响应报文,表示缓存资源在缓存服务器中保存的时间。
Cache-Control: max-age=31536000
Expires 首部字段也可以用于告知缓存服务器该资源什么时候会过期。
Expires: Wed, 04 Jul 2012 08:26:05 GMT
在 HTTP/1.1 中,会优先处理 max-age 指令;
在 HTTP/1.0 中,max-age 指令会被忽略掉。
缓存验证
要理解ETage,首先要弄清楚 HTTP 响应头的last-modified.
在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端最后被修改的时间,格式类似这样:
Last-Modified: Fri, 12 May 2006 18:53:33 GMT
客户端第二次请求此URL时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过:
If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT
如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
那么,理论上根据last-modified 就可以判断该文件是否被修改过,是否使用本地缓存,将服务器端的返回状态为304。但是遇到如下情况,last-modified就无法解决了:
1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)
3、某些服务器不能精确的得到文件的最后修改时间;
为此,HTTP/1.1引入了Etag(Entity Tags).Etag仅仅是一个和文件相关的标记,可以是一个版本标记,比如说v1.0.0或者说"2e681a-6-5d044840"这么一串看起来很神秘的编码。但是HTTP/1.1 标准并没有规定Etag的内容是什么或者说要怎么实现,唯一规定的是Etag需要放在""内。
可以将ETag看做是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式:
ETag: "50b1c1d4f775c61:df3"
客户端的查询更新格式是这样的:
If-None-Match: W/"50b1c1d4f775c61:df3"
如果ETag没改变,则返回状态304然后不返回
内容协商
通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。
1. 类型
1.1 服务端驱动型
客户端设置特定的 HTTP 首部字段,例如 Accept、Accept-Charset、Accept-Encoding、Accept-Language,服务器根据这些字段返回特定的资源。
它存在以下问题:
服务器很难知道客户端浏览器的全部信息;
客户端提供的信息相当冗长(HTTP/2 协议的首部压缩机制缓解了这个问题),并且存在隐私风险(HTTP 指纹识别技术);
给定的资源需要返回不同的展现形式,共享缓存的效率会降低,而服务器端的实现会越来越复杂。
1.2 代理驱动型
服务器返回 300 Multiple Choices 或者 406 Not Acceptable,客户端从中选出最合适的那个资源。
2. Vary
Vary: Accept-Language
在使用内容协商的情况下,只有当缓存服务器中的缓存满足内容协商条件时,才能使用该缓存,否则应该向源服务器请求该资源。
例如,一个客户端发送了一个包含 Accept-Language 首部字段的请求之后,源服务器返回的响应包含 Vary:
Accept-Language 内容,缓存服务器对这个响应进行缓存之后,在客户端下一次访问同一个 URL 资源,并且Accept-Language 与缓存中的对应的值相同时才会返回该缓存。
范围请求
如果网络出现中断,服务器只发送了一部分数据,范围请求可以使得客户端只请求服务器未发送的那部分数据,从而避免服务器重新发送所有数据。
1. Range
在请求报文中添加 Range 首部字段指定请求的范围。
GET /z4d4kWk.jpg HTTP/1.1
Host: i.imgur.com
Range: bytes=0-1023
请求成功的话服务器返回的响应包含 206 Partial Content 状态码。
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/146515
Content-Length: 1024
...
(binary content)
2. Accept-Ranges
响应首部字段 Accept-Ranges 用于告知客户端服务端是否能处理范围请求,可以处理使用 bytes,否则使用 none。
Accept-Ranges: bytes
3. 响应状态码
在请求成功的情况下,服务器会返回 206 Partial Content 状态码。
在请求的范围越界的情况下,服务器会返回 416 Requested Range Not Satisfiable 状态码。
在不支持范围请求的情况下,服务器会返回 200 OK 状态码。
多部分对象集合
一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔,每个部分都可以有首部字段。
分块传输编码
Chunked Transfer Encoding,可以把数据分割成多块,让浏览器逐步显示页面。
虚拟主机
HTTP/1.1 使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。
通信数据转发
代理服务器接受客户端的请求,并且转发给其它服务器。
使用代理的主要目的是:
缓存
负载均衡
网络访问控制
访问日志记录
代理服务器分为正向代理和反向代理两种:
用户察觉得到正向代理的存在。
所谓正向代理就是顺着请求的方向进行的代理,即代理服务器他是由你配置为你服务,去请求目标服务器地址。
比如我们要去访问谷歌网站,我们直接访问不通,那么我们就可以找一个代理服务器为我们服务,我们通过代理服务器请求到谷歌网站。对于谷歌而言他只知道有一个服务器访问了自己,并不知道这件事你是访问不了他,找了一个代理服务器访问自己。
在举一个通俗的例子。你需要钱,C正好有钱,但是你C不直接借给你。你和B关系比较好,B可以找C借到钱。你和B沟通后,由B来找C借到钱后在给你。
上面的两个例子中的共同特点是 代理服务器和B都是你找到的,为你而服务的,代表你的利益。我们还可以让代理服务器给你代理到推特、Facebook等,他是代理的你。
所谓反向代理正好与正向代理相反,代理服务器是为目标服务器服务的,虽然整体的请求返回路线都是一样的都是Client到Proxy到Server。
比如 我们访问百度网站,百度的代理服务器对外的域名为 https://www.baidu.com 。具体内部的服务器节点我们不知道。现实中我们通过访问百度的代理服务器后,代理服务器给我们转发请求到他们N多的服务器节点中的一个给我们进行搜索后将结果返回。
再举例:我们同样需要钱,但是我们又不知道谁有钱,所以我们找了一家网贷平台,你提交资料后,网贷平台直接将钱打给你。但是你不知道,也不用关注网贷平台的钱从哪里来。网贷平台内部他们可能从哪一个财主哪里融的钱。对你而言网贷平台和他们的金主是一起的。
同样通过上面我们例子可以看到,此时的代理服务器和后面的目标主机是一个系统的(百度公司、网贷平台)。他们是对外提供服务的,所以称为反向代理,代理的是后的人。
HTTPS
HTTP的缺陷:
使用明文进行通信,内容可能会被窃听;
不验证通信方的身份,通信方的身份有可能遭遇伪装;
无法证明报文的完整性,报文有可能遭篡改。
HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信,也就是说HTTPS 使用了隧道进行通信。
这个隧道不是指在传输过程中先经过某个地方在转到对端,而是指:在应用层和传输层间增加一个隧道,本来是HTTP转到TCP在转到IP,现在是HTTP转到SSL,加密后在转到TCP,在转到IP
通过使用 SSL,HTTPS 具有了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)
加密
1. 对称密钥加密
对称密钥加密(Symmetric-Key Encryption),加密和解密使用同一密钥。
优点:运算速度快;
缺点:无法安全地将密钥传输给通信方。
2.非对称密钥加密
非对称密钥加密,又称公开密钥加密(Public-Key Encryption),加密和解密使用不同的密钥。
公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
非对称密钥除了用来加密,还可以用来进行签名。因为私有密钥无法被其他人获取(这样无法有人来冒充接收方进行签名),因此通信发送方使用其私有密钥进行签名,通信接收方使用发送方的公开密钥对签名进行解密,就能判断这个签名是否正确。
发送方先和接收方进行协商,接收方把一个公钥给发送方,发送方利用这个公钥对数据进行加密,接收方用私有密钥进行解码。这样即使公钥和数据都被窃取,由于只有接收方才有私有密钥,所以无法破解。
优点:可以更安全地将公开密钥传输给通信发送方;
缺点:运算速度慢。
3. HTTPS 采用的加密方式
HTTPS 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率。(下图中的 Session Key 就是对称密钥)
首先,接收方把一个公有密钥public key发给发送方,发送方同时生成一个用于对称加密的session key,并利用public key对这个session key进行加密。加密完成后发给接收方,接收方利用自己独有的私有密钥对数据解锁,得到了发送方的session key的加密和解密方法。
接下来两端的通信就使用session key的对称加密方法进行通信。
通过非对称加密保证对称加密的密钥的安全性,然后使用对称加密进行通信以保证效率。
认证
通过使用 证书 来对通信方进行认证。
数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。
服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。
进行 HTTPS 通信时,服务器会把证书发送给客户端。客户端取得其中的公开密钥之后,先使用数字签名进行验证,如果验证通过,就可以开始通信了。
https://blog.51cto.com/11883699/2160032
细心的人可能已经注意到了如果使用非对称加密算法,我们的客户端A,B需要一开始就持有公钥,要不没法开展加密行为啊。
这下,我们又遇到新问题了,如何让A、B客户端安全地得到公钥?
client获取公钥最最直接的方法是服务器端server将公钥发送给每一个client用户,但这个时候就出现了公钥被劫持的问题,如上图,client请求公钥,在请求返回的过程中被×××劫持,那么我们将采用劫持后的假秘钥进行通信,则后续的通讯过程都是采用假秘钥进行,数据库的风险仍然存在。在获取公钥的过程中,我们又引出了一个新的话题:如何安全的获取公钥,并确保公钥的获取是安全的, 那就需要用到终极武器了:SSL 证书(需要购买)和CA机构
风险:
解决:
在第 ② 步时服务器发送了一个SSL证书给客户端,SSL 证书中包含的具体内容有证书的颁发机构、有效期、公钥、证书持有者、签名,通过第三方的校验保证了身份的合法,解决了公钥获取的安全性(中间人的公钥没有第三方的保证)
以浏览器为例说明如下整个的校验过程:
(1)首先浏览器读取证书中的证书所有者、有效期等信息进行一一校验
(2)浏览器开始查找操作系统中已内置的受信任的证书发布机构CA,与服务器发来的证书中的颁发者CA比对,用于校验证书是否为合法机构颁发
(3)如果找不到,浏览器就会报错,说明服务器发来的证书是不可信任的。
(4)如果找到,那么浏览器就会从操作系统中取出 颁发者CA 的公钥,然后对服务器发来的证书里面的签名进行解密
(5)浏览器使用相同的hash算法计算出服务器发来的证书的hash值,将这个计算的hash值与证书中签名做对比
(6)对比结果一致,则证明服务器发来的证书合法,没有被冒充
(7)此时浏览器就可以读取证书中的公钥,用于后续加密了
下图中:
第三方的私钥加密的数据可被公钥解密(见上面非对称密钥加密的应用第二点)。
接收方接收到后,首先验证证书是否合法,如果合法使用对应的公钥进行解密,将解密的数据和服务器发来的数据进行hash计算后的值做对比,如果相等说明发过来的公钥(服务端的公钥)可信。
HTTPS要使客户端与服务器端的通信过程得到安全保证,必须使用的对称加密算法,但是协商对称加密算法的过程,需要使用非对称加密算法来保证安全,然而直接使用非对称加密的过程本身也不安全,会有中间人篡改公钥的可能性(如果篡改了公钥,则相当于中间人成为了接收方,因为篡改后的公钥与中间人手上的私钥才是一对),所以客户端与服务器不直接使用公钥,而是使用数字证书签发机构颁发的证书来保证非对称加密过程本身的安全。这样通过这些机制协商出一个对称加密算法,就此双方使用该算法进行加密解密。从而解决了客户端与服务器端之间的通信安全问题。
完整性保护
SSL 提供报文摘要功能来进行完整性保护。
HTTP 也提供了 MD5 报文摘要功能,但不是安全的。例如报文内容被篡改之后,同时重新计算 MD5 的值,通信接收方是无法意识到发生了篡改(因为篡改后也改了MD5值,这样对端检测不出来,因为为是相等的)。
HTTPS 的报文摘要功能之所以安全,是因为它结合了加密和认证这两个操作。试想一下,加密之后的报文,遭到篡改之后,也很难重新计算报文摘要,因为无法轻易获取明文。
HTTPS 的缺点
因为需要进行加密解密等过程,因此速度会更慢;
需要支付证书授权的高额费用。
HTTP/2.0
HTTP/1.x 缺陷
HTTP/1.x 实现简单是以牺牲性能为代价的:
客户端需要使用多个连接才能实现并发和缩短延迟(不能在同一个TCP里并发传送数据);
不会压缩请求和响应首部,从而导致不必要的网络流量;
不支持有效的资源优先级,致使底层 TCP 连接的利用率低下。
HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式的。
HTTP1.1和HTTP2.0:
在通信过程中,只会有一个 TCP 连接存在,它承载了任意数量的双向数据流(Stream)。
一个数据流(Stream)都有一个唯一标识符和可选的优先级信息,用于承载双向信息。
消息(Message)是与逻辑请求或响应对应的完整的一系列帧。
帧(Frame)是最小的通信单位,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。
注意下面:若干帧组成一条消息,一条消息对应的是HTTP1.X里的请求或响应报文。
关系为:
一个TCP连接里有若干个双向数据流
一个数据流里有两个方向的传输
每个方向的传输上以帧为基本单位,若干个帧组成一条消息,对应的就是HTTP1.X里的请求报文或响应报文。
服务端推送
HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 page.html 页面,服务端就把 script.js 和 style.css 等与之相关的资源一起发给客户端。
以服务端到客户端的这条方向上:
上面有stream1里服务端到客户端的方向,也有stream2里服务端到客户端的方向
首部压缩
HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。
HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见过的首部字段表,从而避免了重复传输。
不仅如此,HTTP/2.0 也使用 Huffman 编码对首部字段进行压缩。
如下面:
第一次请求为REQUEST1,因为第一次请求,所以表还未建好,因此要将很多信息都发送过去
而到了第二次请求REQUEST2,因为起变化的只有path,因此只要传输新的path就好了
HTTP/1.1 新特性
详细内容请见上文
默认是长连接
支持流水线
支持同时打开多个 TCP 连接
支持虚拟主机
新增状态码 100
支持分块传输编码
新增缓存处理指令 max-age
GET 和 POST 比较
作用
GET 用于获取资源,而 POST 用于传输实体主体(但GET可用于提交数据,但是是在url后面拼接数据进行传输)。
参数
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看。
因为 URL 只支持 ASCII 码,因此 GET 的参数中如果存在中文等字符就需要先进行编码。例如 中文 会转换为%E4%B8%AD%E6%96%87 ,而空格会转换为 %20 。POST 参数支持标准字符集。
安全
安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的。
GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发生了改变。
安全的方法除了 GET 之外还有:HEAD、OPTIONS。
不安全的方法除了 POST 之外还有 PUT、DELETE。
幂等性
幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。
所有的安全方法也都是幂等的。
在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。
GET /pageX HTTP/1.1 是幂等的,连续调用多次,客户端接收到的结果都是一样的:
POST /add_row HTTP/1.1 不是幂等的,如果调用多次,就会增加多行记录:
DELETE /idX/delete HTTP/1.1 是幂等的,即使不同的请求接收到的状态码不一样:
可缓存
如果要对响应进行缓存(浏览器进行缓存),需要满足以下条件:
请求报文的 HTTP 方法本身是可缓存的,包括 GET 和 HEAD,但是 PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的。
响应报文的状态码是可缓存的,包括:200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501。
响应报文的 Cache-Control 首部字段没有指定不进行缓存。
XMLHttpRequest
为了阐述 POST 和 GET 的另一个区别,需要先了解 XMLHttpRequest:
XMLHttpRequest 是一个 API,它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个通过URL 来获取数据的简单方式,并且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。
XMLHttpRequest 在 A JAX 中被大量使用。
在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做,例如火狐就不会。
而 GET 方法 Header 和 Data 会一起发送。
Socket
一个输入操作通常包括两个阶段:(将进程看做一系列指令的集合,这样输入相当于将数据输入进程,进程的利用CPU执行进程内部的指令,再讲结果输出。这样来看,输入就是从对端拿到数据计算,输出就是返回输出结果到对端)
等待数据准备好
从内核向进程复制数据
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。
Unix 有五种 I/O 模型:
阻塞式 I/O
非阻塞式 I/O
I/O 复用(select 和 poll)
信号驱动式 I/O(SIGIO)
异步 I/O(AIO)
阻塞式 I/O
应用进程被阻塞,直到数据从内核缓冲区复制到应用进程缓冲区中才返回。
应该注意到,在阻塞的过程中,其它应用进程还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其它应用进程还可以执行,所以不消耗 CPU 时间,这种模型的 CPU 利用率会比较高。
非阻塞式 I/O
应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知I/O 是否完成,这种方式称为轮询(polling)。
由于 CPU 要处理更多的系统调用,因此这种模型的 CPU 利用率比较低。
I/O 复用
使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读。这一过程会被阻塞,当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。
它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O,即事件驱动 I/O。
如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接,那么就需要创建相同数量的线程。相比于多进程和多线程技术(指只使用多进程或多线程,每个进程或线程处理一个socket),I/O 复用不需要进程线程创建和切换的开销,系统开销更小。
信号驱动 I/O
应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。
相比于非阻塞式 I/O 的轮询方式,信号驱动 I/O 的 CPU 利用率更高。
异步 I/O
应用进程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。
异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O(信号处理函数里调用recvfrom)。
五大 I/O 模型比较
同步 I/O:将数据从内核缓冲区复制到应用进程缓冲区的阶段(第二阶段),应用进程会阻塞。
异步 I/O:第二阶段应用进程不会阻塞。
同步 I/O 包括阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O ,它们的主要区别在第一个阶段。
非阻塞式 I/O 、信号驱动 I/O 和异步 I/O 在第一阶段不会阻塞。
同步IO与异步IO的区别在于第二部分,同步IO在将内核的数据复制进指定缓冲区时,还是会阻塞。异步IO只会在第二部分完成后直接通知进程,而不会在第二部分陷入阻塞。
而同步IO内的区别在于第一阶段,只有阻塞IO在等待数据到达时会陷入阻塞;非阻塞IO在数据未到达时会直接返回,IO复用虽然IO函数不会阻塞,但数据未到达时仍会阻塞在select上。
I/O 复用
select/poll/epoll 都是 I/O 多路复用的具体实现,select 出现的最早,之后是 poll,再是 epoll。
select
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
有三种类型的描述符类型:readset、writeset、exceptset,分别对应读、写、异常条件的描述符集合。fd_set 使用数组实现,数组大小使用 FD_SETSIZE 定义。
timeout 为超时参数,调用 select 会一直阻塞直到有描述符的事件到达或者等待的时间超过 timeout。
成功调用返回结果大于 0,出错返回结果为 -1,超时返回结果为 0。
fd_set为要监视的文件描述符集,将要监视的文件描述符加入到fd_set
select返回后仍要对所有的文件描述符进行轮询
poll
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
pollfd 使用链表实现。
比较
https://blog.csdn.net/maikelsong/article/details/80520279
1. 功能
select 和 poll 的功能基本相同,不过在一些实现细节上有所不同。
select 会修改描述符,而 poll 不会(select调用返回时,除了那些已经就绪的描述符外,select将清除readfds、writefds和exceptfds中的所有没有就绪的描述符);
select 的描述符类型使用数组实现,FD_SETSIZE 大小默认为 1024,因此默认只能监听 1024 个描述符。如果要监听更多描述符的话,需要修改 FD_SETSIZE 之后重新编译;而 poll 的描述符类型使用链表实现,没有描述符数量的限制(数组大小为32,而数组中每一个数的每一位代表一个文件描述符,如第一个数的第五位设置为1,代表文件描述符为5;而数组第二个数代表文件描述符范围从32到63.因此32位系统下最多32X32共1024个文件描述符,64位系统下为32X64=2048个文件描述符);
poll 提供了更多的事件类型,并且对描述符的重复利用上比 select 高。
如果一个线程对某个描述符调用了 select 或者 poll,另一个线程关闭了该描述符,会导致调用结果不确定。
2. 速度
select 和 poll 速度都比较慢。
select 和 poll 每次调用都需要将全部描述符从应用进程缓冲区复制到内核缓冲区。
select 和 poll 的返回结果中没有声明哪些描述符已经准备好,所以如果返回值大于 0 时,应用进程都需要使用轮询的方式来找到 I/O 完成的描述符。
3. 可移植性
几乎所有的系统都支持 select,但是只有比较新的系统支持 poll。
epoll
epoll_ctl() 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上,通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符。
从上面的描述可以看出,epoll 只需要将描述符从进程缓冲区向内核缓冲区拷贝一次,并且进程不需要通过轮询来获得事件完成的描述符。
epoll 仅适用于 Linux OS。
epoll 比 select 和 poll 更加灵活而且没有描述符数量限制(但首先与打开的文件描述符的数量)。
select的具体实现:
https://blog.csdn.net/lsgqjh/article/details/65629609
(1)使用copy_from_user从用户空间拷贝fd_set到内核空间
(2)注册回调函数__pollwait
(3)遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll)
(4)以tcp_poll为例,其核心实现就是__pollwait,也就是上面注册的回调函数。
(5)__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了。
(6)poll方法(指的是上面的tcp_poll)返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值(在这里更改了fd_set)。
(7)如果遍历完所有的fd,还没有返回一个可读写的mask掩码(上述过程是针对某一个fd,实际上对所有的fd都会进行4)~6)),则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。
(8)把fd_set从内核空间拷贝到用户空间。
总结:
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
poll实现
poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。
epoll
epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。
对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current(当前进程)轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。
对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
调用epoll_create时,做了以下事情:
内核帮我们在epoll文件系统里建了个file结点;
在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket;
建立一个list链表,用于存储准备就绪的事件。
调用epoll_ctl时,做了以下事情:
把socket放到epoll文件系统里file对象对应的红黑树上;
给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。
调用epoll_wait时,做了以下事情:
观察list链表里有没有数据。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪句柄而已,所以,epoll_wait仅需要从内核态copy少量的句柄到用户态而已。
即:
一颗红黑树,一张准备就绪句柄链表,少量的内核cache,解决了大并发下的socket处理问题。
执行epoll_create时,创建了红黑树和就绪链表;
执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据;
执行epoll_wait时立刻返回准备就绪链表里的数据即可。
总结:
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
epoll工作模式
epoll 的描述符事件有两种触发模式:LT(level trigger)和 ET(edge trigger)。
1. LT 模式
当 epoll_wait() 检测到描述符事件到达时,将此事件通知进程,进程可以不立即处理该事件,下次调用 epoll_wait()会再次通知进程。是默认的一种模式,并且同时支持 Blocking 和 No-Blocking。
2. ET 模式
和 LT 模式不同的是,通知之后进程必须立即处理事件,下次再调用 epoll_wait() 时不会再得到事件到达的通知。
很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
应用场景
很容易产生一种错觉认为只要用 epoll 就可以了,select 和 poll 都已经过时了,其实它们都有各自的使用场景。
1. select 应用场景
select 的 timeout 参数精度为 1ns,而 poll 和 epoll 为 1ms,因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。
select 可移植性更好,几乎被所有主流平台所支持。
2. poll 应用场景
poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。
3. epoll 应用场景
只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。
需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。
需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且epoll 的描述符存储在内核,不容易调试。