网络事件
TCP三次握手
- 第一次握手:
- 起始状态:客户端为CLOSE,服务端为LISTEN
- 客户端发送SYN报文,初始化序列号client_isn,标志位SYN置1
- 结束状态:客户端为SYN_SENT,服务端为LISTEN
- 第二次握手:
- 服务端发送SYN ACK报文,初始化序列号sever_isn,在确认应答号填入client_isn+1,SYN与ACK置1
- 结束状态:客户端为SYN_SENT,服务端为SYN_RCVD
- 第三次握手:
- 客户端发送ACK报文,在确认应答号填入sever_isn+1,ACK置1
- 结束状态:客户端为ESTABLISHED,服务端在收到ACK报文后也为ESTABLISHED
TCP四次挥手
- 第一次挥手
- 起始状态:客户端为ESTABLISHED,服务端为ESTABLISHED
- 主动关闭方发送FIN报文,把标志位FIN置1:标志着主动方不再发送数据
- 结束状态:主动方为FIN_WAIT_1,被动方为ESTABLISHED
- 第二次挥手
- 被动方接收FIN报文,发送ACK报文
- 结束状态:主动方接收ACK报文后为FIN_WAIT_2,被动方为CLOSE_WAIT
- 第三次挥手
- 被动方数据发送结束后,发送FIN报文:标志着被动方不再发送数据
- 结束状态:主动方为FIN_WAIT_2,被动方为LAST_ACK
- 第四次挥手
- 主动方接收被动方的FIN报文后,发送ACK报文
- 结束状态:主动方为TIME_WAIT,被动方接收ACK报文后即CLOSE
- 主动方收到FIN报文发送ACK报文后,等待2MSL时间,若没收到重传的FIN,主动方自动进入CLOSE状态
TLS四次握手(在TCP三次握手之后)
- 基于RSA算法的握手过程:
-
ClientHello:客户端向服务端发起加密通信请求,内容:客户端支持的TLS版本+密码套件列表+第一个随机数
-
ServerHello:服务端响应客户端的请求,内容:确认TLS版本+所选择的密码套件列表+第二个随机数
-
Client Key Exchange:客户端收到ServerHello后验证服务端数字证书,取出公钥加密生成的第三个随机数,双方在这一步用随机数计算出会话密钥;
change cipher spec:算法改变告知(接下去都是加密通话)+客户端握手阶段结束告知;
Finished:握手信息摘要(验证加密通信是否可用以及是否被篡改)
-
服务端收到pre-master-key后:change cipher spec:通信算法改变告知+服务端握手阶段结束告知
Finished:握手信息摘要
-
- 基于ECDHE算法的握手过程:
- ClientHello:客户端向服务端发起加密通信请求,内容:客户端支持的TLS版本+密码套件列表+随机数
- ServerHello:服务端响应客户端的请求,内容:确认TLS版本+密码套件列表+随机数+证书+椭圆曲线
- 密码套件如:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- 密钥协商算法使用ECDHE,签名算法使用RSA,握手后通信使用密钥长度256,分组模式GCM的AES对称算法,摘要算法使用SHA384
- 紧接着发送Certificate消息,发送服务端证书
- 接着发送Server Key Exchange消息:选择椭圆曲线以及基点G;生成随机数作为服务端椭圆曲线的私钥保留到本地;根据基点G和私钥计算出椭圆曲线公钥,用RSA签名后公开给客户端
- 最后发送Server Hello Done消息
- 客户端回应:验证服务端证书合法性;用证书公钥验证曲线公钥的签名;生成随机数作为客户端椭圆曲线的私钥,生成客户端椭圆曲线公钥
- 接着发送Server Key Exchange,把公钥发给服务端:利用双方随机数和ECDHE算法算出来的共享密钥共同生成会话秘钥
- 最后告知通信算法改变,以及发送握手信息摘要,用以验证对称密钥是否可用
- 可以不等服务端的最后一次TLS握手,就可以提前发出加密的HTTP数据,节省一个RTT
DHCP动态主机配置协议过程
- 客户端发起DHCP发现报文,使用UDP广播,目标IP地址为255.255.255.255端口67,源IP地址为0.0.0.0端口68
- 客户端将该报文发给链路层,链路层将帧广播到所有网络中设备
- 服务器收到报文后,回复DHCP提供报文,目标IP地址仍为255.255.255.255,在报文中携带它提供的可租约IP地址、子网掩码、默认网关、DNS服务器以及IP地址租用期
- 客户端收到1个或多个服务器的DHCP提供报文后,选择一个服务器提供的东西
- 客户端发起DHCP请求报文响应所选中的DHCP服务器,请求一些必要参数
- 服务端用DHCP ACK应答客户端所要求的参数
如果租约的IP地址快过期了,客户端会发送DHCP请求;服务器如果同意继续租用,就用DHCP ACK;不同意继续租用就用DHCP NACK
使用广播只能在一个局域网里传播,那岂不是每个网络都要有一个DHCP服务器?:不需要,只要在每个网络里放一个DHCP中继代理路由器,它接收到广播后通过单播传给DHCP服务器
一个完整的网络过程
-
第一步,浏览器解析URL(http(协议名)://(web服务器名)/(目录名)/……/(文件名)),如果web服务器名后面的元素省略,将会访问根目录下事先设置的默认文件。当解析结束后,浏览器确定了web服务器和文件名,开始利用这些信息生成HTTP请求消息
-
第二步,生成HTTP消息之后,客户端将发出一个DNS请求给本地DNS服务器询问目标web服务器的IP地址,本地DNS服务器首先查看缓存里是否有记录,若没有便询问根DNS服务器然后得知对应的顶级域DNS服务器地址,接着本地DNS去询问顶级域服务器并得到对应的区域权威DNS服务器地址,最后在权威DNS服务器哪里得到web服务器的IP地址,然后本地DNS服务器将该地址返回给客户端,到这里完成了第二步:查询到服务器域名对应的IP地址
(相关知识:域名的层级关系是越往右层级越高,如www.server.com,根DNS为. 顶级域DNS为.com,权威DNS为server.com)
-
得到IP地址之后,浏览器通过调用socket库委托操作系统中的协议栈工作,其中包括TCP协议层,UDP协议层,IP协议层等
- 第三步,在TCP协议层上,TCP报文的头部格式包括源端口号、目的端口号、序号、确认号、状态位以及做流量控制的窗口大小等,在TCP传输前,需要先进行三次握手建立连接,保证双方都有收发数据的能力,在建立好连接之后,TCP为HTTP报文加上TCP头部生成TCP报文后交给网络层处理
(相关知识:序号:解决包乱序问题,确认号:确认对方是否收到解决丢包问题)
(相关知识:在linux可通过
netstat -napt查看TCP连接状态)(相关知识:当HTTP请求消息较长,TCP协议会按MSS为单位拆分数据)
-
第四步,当TCP报文交给网络层之后,IP模块为TCP报文加上IP头部,其中包含了源ip地址、目的ip地址以及传输层的协议号比如06就代表了TCP协议
(相关知识:客户端有多个网卡即有多个ip地址,要如何选择源ip地址?—— 通过路由表,将目的ip地址与子网掩码进行与运算,得到的结果与路由表中的
destination相匹配,若都匹配失败,将使用默认网关)(相关知识:在linux可通过
route -n查看路由表) -
第五步,生成了IP头部之后,网络包还需要在报文前再加上MAC头部,包括了接收方和发送方的MAC地址以及协议类型,其中发送方的MAC地址直接读取网卡ROM里的MAC值即可,接收方的MAC地址需要借由ARP协议,首先查询ARP缓存中是否有目的ip地址对应的MAC地址的记录,若没有将在以太网中以广播的形式询问该子网下的所有设备,最终得到目的ip地址对应的接收方MAC地址
-
第六步,在操作系统的协议栈中生成了最终的网络包后,通过网卡驱动获得该数据包,将其复制到网卡内的缓存区,在开头加上报头和起始帧分界符以及在末尾加上检测错误的FCS,最后由网卡将数字信息转换成电信号,通过网线发送
-
第七步,电信号到达了二层交换机的接口,转化为数字信息后通过FCS判断是否出错,无误则放入缓冲区,通过查询MAC地址表查询目的MAC地址和本交换机的哪个端口关联,并将信号发送到相应端口
(相关知识:MAC地址表中无指定MAC地址:交换机将该包转发到源端口外的所有端口上,而只有对应的接受者才会接受该包,其他设备将忽略)
-
第八步,网络包由交换机发送给了路由器,首先将其转化为数字信号,fcs错误校验,检查接收方MAC地址,若不是发给自己的则丢弃,随后丢掉数据包开头的MAC头部,根据IP头部的目的ip地址和子网掩码与运算的结果,在路由表里判断转发目标,根据路由表的网关列判断下一跳的ip地址,根据ARP协议加上对应MAC头部,随后转化成电信号发送
(相关知识:MAC地址只能使用于两个设备直接传递消息使用,在网络包传输过程中MAC地址是时刻变动的,而源ip地址和目的ip地址是固定不变的)
-
第九步,发出去的网络包通过交换机到达下一个路由器,下下一个路由器……直到最终到达目的地:服务端
-
第十步,服务端逐层地拆解MAC头部、IP头部、TCP头部,将包发给HTTP进程,解析网络包后得知客户端的请求,封装好相应的数据到HTTP响应报文里,再逐层加上TCP,IP,MAC头部,通过前九步的过程发给客户端,客户端接收并拆解响应的数据包,最后交给浏览器渲染页面
网络异常事件
主机机制
- 超时重传机制,触发条件:超时时间RTO每次翻倍+最大超时次数,若一直得不到回应则断连
- 快速重传,触发条件:接收到三个相同ACK报文
- 服务端半连接队列和全连接队列限制
- 延迟确认机制:服务端不马上确认收到的数据,而是将确认消息延迟到跟新数据一起发送
- 接收到对方数据时,或序列号不符预期、或客户端为close状态、或服务端未监听端口都将回RST
- 服务端处于TIME_WAIT状态,如果收到合法SYN则直接进入SYN_RECV状态;如果收到非法的SYN报文则再发送一次四次挥手的ACK报文
- 服务端处于Established状态,如果收到新的SYN则以原连接的序列号和确认应答号来发送一个Challenge ACK
- 服务端keepalive保活机制:在一个时间段内,如果一个连接没有数据交互则启动该机制;每隔一段时间发送对端一个检测报文,若连续几个检测报文没有响应,则断开连接
- 客户端处于FIN_WAIT_2状态,收到乱序报文(包括FIN报文)会放进队列里,当收到正确顺序数据包时检索队列有没有接下去按序的报文,如有则读取,如是FIN报文则进入TIME_WAIT状态
- 客户端处于FIN_WAIT_2状态,有一个持续时间限制,如果超过这个时间还没有收到服务端的FIN报文,则直接关闭
- 客户端处于TIME_WAIT状态,如果在2ML时间内又收到了符合预期的FIN报文,则将重置时间
针对三次握手
-
第一次握手丢失了
:客户端没收到SYNACK,进行SYN超时重传
-
第二次握手丢失了
:服务端没收到ACK报文,进行SYNACK超时重传
-
第三次握手丢失了
:服务端没收到ACK报文,误以为自己的SYNACK丢失了,进行超时重传
-
为什么要三次握手?
:三次握手才可以阻止重复历史连接的初始化;
假设有一个客户端发送的SYN报文滞留在网络中,很久后才到达服务端,服务端会照常回复一个SYNACK:如果只有两次握手的话,服务端的SYNACK到达客户端时就会建立连接,但如果是三次握手的话,客户端可以判断这是一个旧SYN连接,通过最后的ACK告知服务端中止这个过期连接
:才可以可靠地同步双方的初始序列号;
如果缺少了第三次握手,那前两次握手只能保证客户端的初始序列号被服务端成功接收,而服务端无法确定客户端是否已经成功接收自己的序列号
才可以避免资源浪费;
由于没有第三次握手,服务端无法确认客户端是否成功接收自己的SYNACK,所以如果客户端由于网络问题重复发送同一个SYN报文,服务端只能重复创建相同的无用连接
针对四次挥手
-
第一次挥手丢失了
:主动方没收到ACK,进行FIN超时重传
-
第二次挥手丢失了
:主动方没收到ACK,进行FIN超时重传
-
第三次挥手丢失了
:被动方没收到第四次挥手的ACK,进行FIN超时重传
:主动方处于TIME_WAIT_2状态,若超过限制时间没收到FIN报文,则直接断开连接
-
第四次挥手丢失了
:被动发没收到ACK,进行FIN超时重传
:主动方又收到了FIN报文,发送ACK,并重置2ML等待时间
-
为什么要四次挥手?
因为服务端要等待完成数据的发送和处理,所以它的FIN和ACK是分开发送的,所以会比三次握手多一次
-
为什么需要TIME_WAIT状态?
:防止具有相同四元组的旧数据包被收到
如果没有TIME_WAIT状态,当前端口立刻关闭后可能被立刻复用,那么有可能,旧连接产生的相同四元组数据包因为网络原因,滞留到现在才到达客户端端口,这会导致新连接接收到旧连接数据
:保证最后的ACK能让被关闭一方接收,保证它的正确关闭
假如客户端最后的ACK丢失了,那么客户端可以在TIME_WAIT状态等待服务端重发的FIN报文,否则如果直接关闭的话服务端重发的FIN报文被close的客户端接收会返回一个RST,这对于一个可靠的协议来说是不优雅的
-
为什么TIME_WAIT时间是2MSL
:TIME_WAIT从客户端发出最后ACK开始计时,2MSL正好支持报文的一去一回(一去是ACK到达服务端,一回是服务端可能的重发FIN);时间太长的话会占用内存资源、以及端口资源