网络事件

149 阅读14分钟

网络事件

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状态

image.png

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);时间太长的话会占用内存资源、以及端口资源