计算机网络基础滚动复习


1. 为什么需要有TCP/IP网络模型?

对于同一台设备上的进程间通信,可以使用:

管道:其实就是通过fork()指令来创建父子进程,通过父子进程的共享数据的方式来实现进程间数据的共享,通常就是通过共享底层文件系统的fd来实现的。

消息队列:消息队列就是一个消息结构体,通过封装一个队列,将消息结构体push()到这个队列中,基于先进先出的原则,实现进程间的缓存区通信的功能

共享内存:就是对于进程独有的内存空间做一个共享,比如说两个进程中的页表地址是一致的,这样的话就可以实现内存之间的相同映射,基于这个相同的映射,就可以实现两个进程同时操作同一个内 存

信号量:信号量机制在底层有两个字段,第一个是一个count计数,假设这个值是n吧,那么当n > 0的时候,那么这时候count就代表可用的资源个数,当n < 0的时候,这时候就代表当前有多少个线程正在被阻塞,这时候会与第二个字段BlockQueue阻塞队列关联,通过这个队列就可以知道哪些线程被阻塞了

既然进程间有这样的IPC通信协议,他们建立起来的基础是这些进程处于同一个操作系统中,通过底层所实现的这些数据结构,就可以实现进程间的通信,但是网络间的通信,这意味着这些主机的操作系统、中间传输设备(如路由器)通常是异构的,所以为了要兼容多种多样的设备,于是就协商出来了一套通用的网络协议。

为什么网络要进行分层?

关于这个问题,可以这样进行回答:

首先,网络通信这件事情是十分复杂的,如果要拆解,一次成功的网络通信,要求:

底层的物理设备不出差错,能够以正确的方式解析电磁波

数据包在两个主机之间有一条路径,可寻址

如果数据包在网络上丢失了,这时候要做处理/维护数据包的语义完整性

网络通信要完成用户指定的功能

假设将这些功能全部在一起完成,那么就会导致网络设计的耦合度太强了,比如说我底层使用的物理设备需要更换的时候,这时候就要修改整一套系统,假设你建立了一条TCP连接,理论上来讲,这条TCP连接是在底层的物理设备的支持下完成连接的,如果物理设备更换了,那么就会导致TCP连接直接被重置,甚至需要对TCP相关的代码需要重新编码,这是一件十分不合适的事情,这就无法实现热插拔等相关功能了

2. TCP/IP的应用层是干什么的?

应用层协议是直接面向用户的,因此它负责完成两个进程之间的通信,一般来说,它就是专门用来完成用户提出的请求的,比如说用户需要完成HTTP传输,那么就需要使用HTTP协议,并且在头部字段定义相关的字段,比如说用来传输普通的文本等,这个协议并不关心底层是如何传输的,它只关心如何将用户的请求翻译成协议的字段

应用层是工作在操作系统的用户态的,传输层以及以下是工作在内核态中的

3. TCP/IP的传输层是干什么的?

关于传输层的作用,可以这样说,传输层负责接收来自于上层的应用层的数据包,然后将这些数据包做一些相关的封装,下发到网络层,然后让网络层完成主机间的传递

网络层是负责的是主机间的传输

传输层负责的是主机间进程的传输

也就说网络层负责将一个主机上的数据包发送到另一台主机设备的网络适配器缓冲区上,然后由操作系统的TCP/IP协议栈,负责处理这个数据包,然后将这个数据包头部含有的信息进行解析,也就是一般来说就是通过端口来进行检查,用端口+协议来标识某一个特定的应用

在传输层中,有两个协议,一个是叫做TCP协议,一个是叫做UDP协议,这两个协议的存在是为了适应不同的网络环境下的不同应用的需求,比如说有:

UDP协议:UDP协议的特点是面向数据报的,面向无连接的,尽最大努力传输的协议,它只负责对上层的应用数据包进行封装,它非常简单,头部的字段只有目的端口、源端口、校验和、包长度,因此在这样的基础上,就可以实现简单的转发功能,适用场景比如说直播,视频通话等这些要求时效性强的场景

TCP协议:TCP协议的特点是面向字节流的,面向连接的,可靠的传输协议,它的特点主要就是可靠,可以用来做一个网络文件的传输,可以保障数据的完整性,UDP其中的一个特点是面向数据报的,这个特点决定了UDP在收取上层应用数据包的时候,这时候不会将数据进行分片,而是将分片的工作交给了网络层的IP协议

如果TCP采用这样的原则的话,那么就可能导致一个现象,就是一个大的TCP报文段在经过IP协议的分片后,如果其中一个小报文段丢失了,那么最终就可能导致整个TCP报文段的重新传输,这是十分不划算的。

因此TCP有自己的一套分片规则,它会根据TCP连接建立的时候协商好的MSS最大报文段,根据这个最大报文段来进行分片,即使中途有一个MSS大小的报文段丢失了,只需要重传这一个报文段即可,不然的话就还需要重传一个TCP报文段。

4. TCP/IP的网络层是干什么的?

传输层的功能主要是为了处理两个进程之间的通信问题,它的设计理念是只需要负责好上层应用之间的数据传递就可以了,但是它没有完成两个进程之间的数据包,在错综复杂的网络中是如何传输的问题,因此在这样的情况下,还设计了一个网络层,网络层的作用主要可以概括为:

路由选择分组转发,这也就是路由器的功能,而这其中最关键的一个协议就是IP协议,IP协议的主要作用就是提供了一套规范,通过这个规范来确定IP包的首部字段的功能,功能其实是比较复杂的,首部字段比较重要的有IP源地址,IP目的地址,以及一些例如校验和之类的字段,通过<目的地址,源地址>的这个字段对,就可以实现基本的网络路由寻址。

寻址的过程大致是这样的:一个数据包被发送到一个路由器的缓存队列中,然后路由器中的处理程序取出这个数据包,然后根据内部的iptables来确定这个数据包要发送到哪一个端口,然后沿着端口的这条路径不断传送,直到到达指定的路径即可。

为什么有了MAC地址还要有IP地址?

MAC地址对于每一个物理设备来讲都是独一无二的,通过MAC地址,在数据链路层就可以实现局域网寻址,局域网可以假设是非常小规模的,因此通信的时候可以使用带载波监听的多路访问技术CSMA/CD,可以通过这个技术来广播一个物理帧,然后这个物理帧上就会带着目标设备的MAC地址,然后对应的设备就会检查这个帧头部的MAC地址是否和本物理设备上的MAC地址是否相同,如果相同,那么就接收

MAC地址是物理地址,它的构成是这样的<厂商标识码,厂商自行安排的尾数>,那么如果只有MAC地址的话,那么就意味着这些设备的标识地址都没有规律,在这样的情况下,为了找到网络上的每一台设备,我们需要一个巨大的中央服务器,这个中央服务器要存储所有设备的MAC地址,在当前的网络规模下,是无法想象这样的设计的

因此在这样的情况下,基于MAC地址还开发出来了IP地址,IP地址通过<网络号,主机号>的方式来对网络设备进行划分,这样的话就可以实现这样一种方案,就是知道了对方的IP地址,我就知道要往哪里转发,因为一个设备归属于一个局域网,那么我只需要知道这个局域网的入口路由器是哪个,就可以知道我要将这个数据包转发到哪里了,这个情况下,只需要每个路由器维护自己的一张表,而不需要安排一个巨大的服务器来进行存储。

为什么有了IP协议还要有UDP协议?

首先要搞清楚,IP协议是在网络层上工作的,而网络层上的工作是为了实现从主机到主机的寻址,我们说的计算机网络通信通常指的是一个进程到另外一个进程的通信,因此在这样的情况下,我们需要在传输层设计一个尽最大努力传输的协议,来和TCP协议的使用场景进行区分

5. TCP/IP的网络接口层是干什么的?

我们说网络层负责的是跨网段的传输,也就是说从一个网段中传输数据包到另外一个网段,那么网络接口层就是负责局域网内的传输,也就是说,当一个数据包完成了跨网段的识别

从一个网络中的路由器发送到另外一个网络上的路由器的时候,这时候就是网络层完成的事情

那么在同一个网络中,路由器和具体设备的通信的时候,实际上是依赖一个叫做数据链路层的层级来完成操作的,也就是说,从这个数据链路层中,它完成的是局域网通信,主要完成网络适配器的识别和数据包的匹配

负责完成底层数字信号的调频调幅调相的相关功能

什么是局域网?

一种私有网络,将分布在小范围内的网络设备串联在一起的一种小型网络,通常不经过路由器,而是通过交换机网桥等设备进行连接

6. 输入一个URL,问你这期间发生了什么?

这个问题 比较复杂,需要从几个方面进行回答

6.1 从B/S应用架构来进行回答

首先浏览器发起了一个HTTP请求,然后这个请求就会被转发到后台服务器上,后台服务器就会解析这个请求的含义,通常来说,HTTP请求的格式是请求行(包括有HTTP版本号,URL,请求方法GetOrPost)|请求头(包含了HTTP协议中规定的应用字段)|空行(其实就是一个cr lf回车,用来分割控制信息的头部)|请求体(具体携带的负荷)

然后后端服务器就会解析这个HTTP请求,然后从Web服务器上取出需要相应的资源,然后将资源封装起来原路返回

这个资源被浏览器接收到了之后,然后就会浏览器就会将这个HTTP响应解析出来,然后将数据渲染到浏览器上

HTTP响应的格式是响应行(包括有版本号,状态码,短语(也就是当前状态码所代表的含义是什么))|响应头(包括本次返回响应的数据长度是多少?)|空行(用来分割响应头和响应体)|响应体

6.2 从计算机网络协议栈的角度来进行回答

以上是从B/S架构交互流程来进行说明的,如果要从网络协议栈来说明的话,可以基于TCP/IP四层协议来进行说明,比如说我输入了一个www.baidu.com,那么大致将经过:

首先,第一步,用户输入了这个URL,于是浏览器就需要负责将这个URL的相关信息封装成一个HTTP请求,比如说www.baidu.com,浏览器内置选用了HTTP/1.1的版本的协议,然后使用了Get请求,然后请求体是空的,然后准备交付到下一层,此时就经历一个委托的过程,我们知道TCP/IP四层的通信,主机之间的通信是基于IP协议进行通信的,因此在交付到下层的时候,这时候还缺少一个IP地址,因此在交付之前,还需要查询一下IP地址

于是第二步,就 需要执行一个DNS查询,DNS查询是基于一个UDP协议来完成的,具体的流程是:

  • 客户端会先向本地域名服务器查询,问它知不知道www.baidu.com的IP地址,如果它知道就直接返回
  • 如果本地域名无法查询到,于是就获取顶级域名服务器的地址,也就是.的地址,这个地址是所有服务器都知道的,这时候本地域名服务器就会向根域名服务器查询www.baidu.com的地址
  • 注意,根域名服务器并不起到一个直接解析的作用,而是起到一个指路的作用,这时候它会告诉顶级域名服务器的地址,也就是.com的地址
  • 然后本地域名服务器就会向.com的域名服务器进行查询,如果.com的域名服务器也没有,它就会告诉他掌管www.baidu.com的权威域名服务器的地址
  • 然后本地域名服务器最终就会向这个域名服务器请求IP地址,这时候就能够查询成功了

查询成功之后,这时候就能够将IP地址投入委托使用了

第三步,获取了通信的必要信息,比如说IP地址,端口号等,这时候应用程序就会调用封装好的Socket中的接口,调用这些接口实际上就发起了系统调用,操作系统根据通信需要用到的协议,将消息发送到具体的通信模块中,比如说发送到TCP处理模块中,然后进行一系列的网络传输操作

第五步,建立TCP连接,TCP连接是一个非常复杂的协议,首部字段主要有:

  • 源端口号、目的端口号
  • 报文段的序号:用来保证传输过程中不乱序传输,可靠传输的必要条件,传输过程中如果出现了乱序,会根据滑动窗口中的序号限定来确定哪些报文段可以被接收,哪些需要丢弃
  • 确认号:接收方表示从ack-1之前的所有字节都被接收到了
  • 状态位,我们说TCP连接的变化,其实就是一个状态机不断变化的过程,状态转移需要依赖于双方交换的报文段中的状态位,比如说有FINACKRSTSYN等通过这些状态位来确定当前TCP连接状态
  • 窗口大小,它是TCP的精髓所在,在实现拥塞控制(控制数据包注入网络中的速度),流量控制(控制发送的速率)的时候有很大作用

然后执行TCP的三次握手,这时候会经历:

一开始,服务端和客户端都是处在一个CLOSE的状态,然后这时候服务端被动打开连接,客户端主动建立连接,也就:

  • 客户端发送一个SYN+clinet_seq的报文段,请求服务端的连接,进入SYN_SENT的状态
  • 服务端接收到这个报文后,进入SYN_RCVD,返回一个SYN+ACK=1+ack=client_seq+1+server_seq的报文段
  • 客户端接收到这个报文之后,就会发送一个ACK,进入了ESTABLISHED的状态,服务端在接收到这个ACK之后,也就会进入到一个ESTABLISHED

然后就可以基于这个TCP进行数据的发送了,注意这个过程,建立TCP报文的时候,实际上也是需要底层的IP支持的,因此在第一个报文发出的时候,会经历这样的过程:

第六步,底层的IP协议接收到来自上层的TCP报文段之后,这时候就会将上层的TCP报文段包装成一个IP包,IP 包的最重要的字段就是源IP地址和目的IP地址

主要的过程是这样的,首先主机拿到这个IP报文,然后它会比对操作系统内核中提供的一个叫做路由表的数据结构,将当前的目标IP地址和每一项的子网掩码进行与运算,如果与运算后的结果和条目表中的IP地址是相同的,那么就选择这个条目中规定的网卡进行数据的发送,这就确定了源IP地址

当匹配到最后一项的时候,这代表着当前主机并不直接和这个网络下的网络设备连接,而是要通过本网络的网关进行转发,具体的格式是0.0.0.0 GateWay ,然后主机就将数据转发到网关了

当转发到网关的时候,这时候网关就查询路由表,具体的就是<目标IP地址,子网掩码,转发端口号>,也就是将IP地址和子网掩码进行与运算,如果和目标IP地址吻合,那么就直接通过这个端口转发出去,否则还需要经过默认网关

第七步,经过了大量的路由转发,这时候到了目标主机所在的子网内,这时候就通过ARP协议来进行IP地址和MAC地址的转换,具体来说是这样的:

(1)当ARP缓存中IP地址=>MAC地址的映射的话,这时候就直接取出MAC地址,然后封装MAC帧发送即可

(2)当没有的时候,这时候就需要执行一个广播,也就是说,在局域网内广播谁的IP是{IP}?,请发送你的MAC地址过来,然后这时候收到验证的设备就会回播一个MAC地址,收到这个MAC地址之后,就会在ARP缓存中存下来

在局域网内,就通过CSMA/CD协议发送数据包,然后在发送出去之前,还需要FCS的计算等操作,然后通过调频调幅调相的过程,将数字信号转换为频带信息,这时候就进行真正的网络传输了

当这个数据包到达对方主机后,就会通过数据链路层=>网络层=>传输层=>应用层这样的顺序,不断拆解头部,然后就可以得到实际的数据了

7. TCP的基本认识

7.1 TCP的头部字段

TCP的头部字段通常有:序列号(在建立连接的时候,由计算机产生的随机数作为初始值,通过SYN包传给接收端的主机,每次发送一个数据,就累加一次该数据字节数的大小,用来解决网络包乱序的问题)确认应答号(指下一次期望收到的序列号),控制位,主要是用来沟通两台主机的状态变化

ACK:当这个位为1的时候,表示是一个确认上一次应答的包,此时ack的值是生效的

RST:当这个位为1的时候,表示检测到当前的TCP环境产生了异常,必须强制断开连接

SYN:表示希望建立连接,并且在seq字段中进行初始化

FIN:表示希望关闭这一方的数据传送通道

7.2 为什么需要TCP协议?

TCP协议工作在IP协议之上,那么就要谈到IP协议设计的功能了:

路由选择分组转发,假设没有TCP协议,那么假设传输过程中路由器故障,那么就会导致数据全部丢失,而对于传输双方来说是无法感知的,因此在这样的情况下,就必须要有一个工作在IP层之上的协议,来控制这个IP协议的传输了,IP协议它无法保证网络包的可靠交付,不保证网络包的按序交付,也不保证网络包的数据完整性

因此在这样的情况下,TCP的出现可以使得网络数据包的可靠传输,按序交付,数据报的完整性

8. TCP是什么?

TCP叫做传输控制协议,它的特点是:

面向连接的可靠的基于字节流的传输协议

所谓面向连接,就是要建立起一条逻辑连接信道之后,才能进行后续的传输,它一定是一对一的通信传输连接,一对多是无法做到的

所谓可靠的,就是无论底层的网络出现了怎么样的变化,它在允许的情况下(超时重传次数允许的情况下,一定能够完成数据报的可靠交付)

所谓面向字节流的这个概念,要和面向数据报的概念进行联合讲解,所谓面向数据报,说的是上层应用交付怎么样的数据报,传输层就传输怎么样的数据回去,因此对等端传输地数据总是按照用户的意愿的

但是面向字节流可能会将上层应用交付的一个应用数据包拆分成多个报文段,然后分别发送,如果消息报文段没有规定确切的边界,那么是无法读取出有效的信息的

9. 如何来描述一个TCP连接

一个TCP连接是用于保证可靠性和流量控制的某些状态信息,这些信息的组合,包括有Socket序列号窗口大小用来描述一个连接

Socket:ip和端口号

序列号:用来解决乱序问题的

窗口大小:做流量控制的

如何唯一确定一个TCP连接?

TCP的四元组=>{目的IP地址,目的端口号,源IP地址,源端口号}

有一个IP的服务端监听了一个端口,它的TCP的最大连接数是多少?

IP数最多有2^32,端口数最多有2^16,因此最多可以维持2^48个连接

连接还会受到系统的其他资源的限制

文件描述限制:每个TCP连接都是一个文件,如果文件描述符被占满了,那么就会发生TooManyOpenFile的异常

内存限制:每个TCP连接都要占用一定的内存,操作系统的内存是有限的,如果内存被打满了,那么就会发生OOM

10. TCP和UDP有什么区别?

关于TCP和UDP的区别,可以从以下几个方面进行区分:

(1)连接

  • TCP是面向连接的传输协议,传输数据之前先要建立连接
  • UDP是面向无连接的传输协议,传输数据之前不需要创建连接

(2)数据载体

  • TCP是面向字节流的,消息没有边界,但是可以保障有序性和可靠性
  • UDP是面向数据报的,可能导致丢包和乱序

(3)可靠性

  • TCP基于滑动窗口+序列号+响应机制实现了可靠性
  • UDP不保证可靠性

(4)首部的开销

  • TCP的首部一定要占20个字节,当采用了例如SACK之类的机制的时候,这时候还会占用最多20个字节,因此最多40个字节
  • UDP首部只有8个字节,四个字段,分别是{目的端口,源端口,校验和,包长度}

(5)服务对象

  • TCP的服务对象是一对一的
  • UDP的服务对象可以是多个

(6)拥塞控制和流量控制

  • TCP基于响应应答机制+滑动窗口可以实现拥塞控制
  • UDP无论网络情况如何,都是直接发送数据的

(7)分片不同

  • TCP会在传输层实现分片
  • UDP不会分片,它会将分片的任务交给IP层

11. 简述一下TCP三次握手的流程

(1)初始化的情况下,ClientServer都处于一个CLOSED的状态,然后此时Server被动打开连接,进入到一个LISTEN的状态,然后客户端请求建立连接,也就是进入一个SYN_SEND的状态

这个报文包含有的信息主要有client初始化之后的序列号,以及带有SYN标记的数据包,表示希望和Server建立连接

(2)服务端在收到第一个握手包之后,进入到一个SYN_RCVD的状态,然后此时就要发送回去一个响应,这时候同时会初始化自己的序列号

这个报文包含有的信息主要有server初始化之后的序列号,以及带有SYN标记的数据包,同时会将ACK=1,ack =client_isn+1,表示正在和client建立连接,正确收到了上一次连接请求包

(3)客户端在收到服务端的ACK+SYN之后,这时候就进入了一个ESTABLISHED的状态,这也就代表着可以发送数据了,这时候会对上一次的SYN做一次ACK,也就是:

这个报文包含的信息主要有ACK=1,ack = server_isn+1,表示已经正确收到server的同步连接请求包,注意,由于此时是ESTABLISHED的状态,因此这时候可以携带数据,也可以不携带数据

(4)服务端接收到客户端的ACK = 1,ack = server_isn+1的包之后,这时候就会进入到ESTABLISHED的状态了

如何查看TCP连接的状态?

netstat -napt

12. 为什么TCP的握手需要三次?

12.1 从建立全双工连接的角度上来说

从全双工的角度上来看,三次握手可以实现双方的收发正常,非常好理解

(1)第一次握手报文,什么都确定不了,客户端无法确定自己的报文是否到达服务端,也就是无法确认自己是否有发送能力,同时也无法确认接收方是否有接收能力和发送能力

(2)第二次握手报文,此时客户端可以确定自己的报文到达了服务端,也就是可以确认自己具有发送能力,同时具备接收能力,于是它就进入了ESTABLISHED的状态,但是服务端只可以确认自己具有接收能力,但是不确定具有发送能力,于是只是保持SYN_RCVD的状态,于是客户端此时发送ACK

(3)第三次握手报文,此时服务端收到后,进一步确定了发送能力,于是进入了ESTABLISHED

12.2 从确定一条TCP连接的角度上来说

从这个角度上来看,三次握手的首要原因是为了要排除历史连接的错误

(1)排除历史连接的错误,首先,假设这样一个场景,也就是说,当TCP建立的时候,这时候网络阻塞了,于是TCPSYN报文一直发送不到服务端,然后客户端重置连接,在网络通畅的时候,这时候就会重新发送SYN,这时候就出现了一种情况了:网络中同时存在新的连接报文和旧的连接报文,而且这两个报文什么时候到达服务端是不确定的,因此下面就来讨论一下:假设新的连接报文序号是90,假设旧的连接报文序号是100

  • 如果新的连接报文比旧的连接报文要先到达

这时候新的连接报文到达服务端之后,服务端会返回SYN+ACK+ack = 91,客户端收到这个ACK之后,然后发送ACK,此时TCP连接就建立好了,如果此时旧的连接报文到达的时候,这时候就会触发一个ChallengeACK,这个ACK的作用主要是为了检查当前的SYN是否有效,也就是回复一个SYN+ACK+ack=91的包回去,给客户端检查,如果客户端检查发现和上下文环境不符合,那么就发送RST报文。

  • 如果旧的连接报文比新的连接报文要先到达

如果旧的连接报文比新的连接要先到达的时候,服务端也会返回SYN+ACK+ack = 100,客户端收到这个ACK,检查上下文环境是91,因此会发送RST报文,此时就会重置连接了,在后续新的报文到达的时候,就可以正常接收连接了

之所以要三次握手,可以假设只有两次握手会发生什么

如果只有两次握手,那么就意味着服务端返回了SYN+ACK+ack的话,就直接进入了ESTABLISHED,这实际上是非常草率的过程,也就是说服务端并不能够确认自己具有发送能力的同时,同时无法避免历史报文的影响,如果收到的是历史报文,那么就会建立一个历史连接,然后服务端就会发送数据,这会导致网络资源的浪费

(2)基于三次握手可以实现双方序列号的同步

序列号是实现TCP的关键数据结构,通过这个字段,可以实现:

  • 数据包的有序传输,可以将那些乱序的数据包丢弃
  • 数据包的可靠传输,便于超时重传
  • 可以知道哪些报文是已经被接收的了

通过三次握手,第一次握手,告知服务端,客户端的初始化序列号,不确定服务端收到没有

第二次握手,告诉客户端关于服务端的初始化序列号,不确定客户端收到没有,但是可以确定服务端收到了客户端的初始化序列号

第三次握手,告诉服务端,服务端的初始化序列号接收到了

(3)避免资源的浪费

假设只有两次的握手,资源的浪费主要体现在,历史连接导致的无效的数据传输、一个SYN报文就对应一个连接,如果是重传的SYN报文,那么就可能导致大量的无效连接,同时可能导致被不法分子利用,基于泛洪SYN来对服务器进行攻击

12.3 总结

  • 两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方的序列号
  • 四次握手:理论上三次握手就可以实现可靠的连接建立了,不需要更多的握手次数

13. 初始化的序列号相同该怎么办?会有什么问题?

首先假设初始化的序列号相同会发生什么,假设每一次的TCP连接都是从0开始的

假设ServerClient建立了连接,然后从0开始发送数据,然后发送到序号为50的时候,此时发送了大量的超时重传,然后超过了次数限制之后,就发送了RST报文,重置了这个连接,但是注意,序号为50的报文此时还是残留在网络中的,因此在这样的情况下

然后重新建立连接,从0开始发送数据,然后发送到序列号为50的时候,此时发送的数据包和超时数据包混合在一起,这时候就产生了数据异常了

为了解决这个问题,TCP设计的序列号初始化是随机的,尽量来避免历史连接的序列号来影响本次连接,但是要注意,序列号是有回绕现象的,也就是说当序列号到达了2^32-1之后,又会变成0,因此在这样的情况下,就要使用时间戳来实现了,简单来说就是看你这个数据包的时间戳是否是递增的,如果是递增的话那么就接收,否则就关闭

14. IP层会分片,为什么还需要MSS呢?

MTU:一个网络包的最大长度,以太网中一般是1500个字节

MSS:除去TCP和IP头部之后,一个网络包所能容纳的最大数据长度

假设只有IP的分层的话,那么在这样的情况下,首先对于一个IP报文而言,这个报文的数据部分是由TCP头部和TCP数据段组成的,如果IP包的大小超过了MTU的情况下,那么需要IP就进行分片了,那么在这样的情况下,假设一个IP分片丢失了,那么就会导致整个TCP报文段的重传,为了避免这样的现象影响效率,于是使用了MSS

这个MSS是这样起作用的,简单来说,就是当TCP组装报文段的时候,如果这时候报文段的长度超过了MSS,那么就会在传输层进行分片,如果一个TCP分片丢失了,那么就只需要重传这一个分片就可以了

15. 如果第一次握手报文丢失了,会发生什么?

注意,这种状态的特殊报文段一般的逻辑是这样的:

没有收到数据报=>触发超时重传=>重传次数到达上限=>断开连接=>客户端单方面断开连接

详细:当第一次握手报文,也就是SYN的报文传出去之后,然后这时候SYN+ACK没有被收到,由于发送的是SYN包,因此在这样的情况下,就会触发重传,第一次超时重传是1s之后,之后就会在*2,也就是1+2+4+8+16+32,当达到了这个上限之后,这时候就不会再重传了,而是操作系统内核将这条连接进行释放

16. 如果第二次握手报文丢失了,会发生什么?

首先明确这时候双方发送数据包的性质:

客户端发送的是SYN数据包,不是纯ACK包,因此当发生了超时的时候需要进行重传

服务端发送的是SYN+ACK数据包,不是纯ACK包,因此当发生了超时的时候需要进行重传

那么在这样的情况下,当第二次握手报文丢失的时候

从客户端的角度上来看,就是没有收到ACK,因此会重传SYN数据包

从服务端的角度上来看,就是没有收到客户端的第三次握手报文,于是会重传SYN+ACK数据包

然后从状态转移来看,如果客户端重传的SYN报文到达了服务端,那么这时候服务端就会重传SYN+ACK,因为是重传,那么无论是ChallangeACK亦或者是正常的ACK,都是原来的序列号

当客户端收到这个SYN+ACK之后就会进入到ESTABLISHED,然后继续后续的传输了

如果后续重传到达了一定的次数的时候,任意一端都会关闭连接

17. 如果第三次握手报文丢失了,会发生什么?

首先先来分析上下文的性质,首先:

第二次握手的报文属于是SYN+ACK,具有重传的义务

第三次握手的报文属于ACK报文,可以携带数据,因此需要分情况讨论

当第三次握手的报文没有携带数据的话,那么客户端就不会重传,这时候服务端就会不断重传SYN+ACK,达到超时的次数之后,就会通知操作系统内核释放这个连接

当第三次握手的报文有携带数据的时候,那么客户端就会重传,这时候断开连接的权利掌握在双方

18. 什么是SYN攻击?如何避免SYN攻击?

要理解这个问题,你首先要理解一次连接在操作系统内核是如何发生的

Linux系统为例,它是这样管理的,首先是一个主机的SYN报文发送过来,如何这时候操作系统内核就会将相关的信息封装成一个结构体,然后将这个结构体放入到一个内核中一个叫做SYN连接队列的数据结构

然后这时候内核会扫描这个队列,然后发送SYN+ACK给对等端,如果对等端发送一个ACK的时候,这时候就会从SYN连接队列pop()出来一个数据结构,然后将这个数据结构push()到一个accept队列中,然后操作系统内核就会提供一个系统调用accept(),如果调用accept()就会从这个队列中pop()出来,然后提供操作系统的系统调用来对这个socket进行读写。

至于如何防范SYN攻击,就需要从半连接队列如何被打满的这个角度来进行分析了

方法一:设置Linux系统内核中的半连接队列的大小,具体来说有三个参数

方法二分析:当发生了半连接队列被打满的时候,这时候最严重的后果就是后续的报文被丢弃,于是我们可以将保存多余数据包的调大,这时候就可以降低半连接队列被打满的副作用

方法三分析:半连接队列被打满的时候,会导致连接无法建立,那么在这样的情况下,我们可以绕过SYN半连接队列的方法来解决这个问题,具体的流程是这样的:

  • 当SYN队列满了之后,后续队列收到新的SYN包的时候,这时候不会创建数据结构并且push()到半连接队列中,这时候它会通过一定的算法,生成一个cookie,这个cookie后续可以通过一定的算法来验证这个Cookie的合法性,如果合法,那么就说明是之前本机生成的cookie
  • 如果合法,那么就会将对象放入到accept队列中

方法四分析,如果可以尽快确认这个SYN报文是无效的,并且将这个连接从内核中删除,那么一定程度上就可以解决这个问题,我们可以通过减少SYN+ACK的重传次数以及重传的间隔时间,就可以让这些SYN来得快,去得也快,最终就可以实现SYN队列的空余

19. 简述TCP四次挥手过程

关于TCP四次挥手的流程,可以这样讲述:

第一步,客户端完成了本机的数据传输,因此这时候就会发送一个控制位为FIN = 1的数据包到服务端,这时候客户端进入到一个FIN_WAIT_1的状态,这时候根据全双工的流程,客户端关闭了发送的通道,但是保留了接收的通道

第二步,服务端收到FIN = 1的数据包,这时候服务端因为是被动关闭,有可能还有数据没有发送完毕,因此这时候进入了CLOSE_WAIT,表示等待本机从剩余数据发送完,然后就会发送一个ACK,让对方知道之前FIN = 1的数据包已经收到了

第三步,服务端发送剩余数据

第四步,服务端数据发送完毕,这时候服务端就会发送一个FIN = 1的数据包,表示服务端也关闭发送的通道了,这是会需要等待客户端的最后ACK,因此进入了LAST_ACK的状态

第五步,客户端收到FIN = 1,表示客户端收到了服务端通知关闭发送通道的通知,然后发送ACK,表示确认,然后进入到TIME_WAIT

第六步,服务端收到了ACK,于是进入到了CLOSED状态

第七步,客户端TIME_WAIT结束,进入到CLOSED状态

注意:主动关闭连接的才会有TIME_WAIT的状态

20. 为什么挥手是四次?

首先分析一下,如果只有三次挥手会怎么样?

如果只有三次挥手,客户端发送一个FIN,表示此时关闭发送通道,然后服务端发送一个FIN+ACK,表示服务端关闭发送通道和接收通道,然后客户端收到这个ACK之后,还要回包ACK,表示知道了对方要关闭了

三次挥手在服务端没有多余的数据的时候,是可以变成三次挥手的,但是通常来说FIN+ACK是会分开发送的

21. 第一次挥手丢失了会发生什么?

第一次挥手是主动关闭者发送FIN报文,如果第一次挥手无法得到对方的ACK,那么就会触发超时重传,因此这时候的逻辑是这样的,会在一段时间内不断重传FIN报文,如果超过了重传的次数,那么就会直接进入到CLOSED的状态,后续的报文处理会基于操作系统内核对这个报文的定义,通常来说会基于RST报文来对这些进行限制

22. 第二次挥手丢失了会发生什么?

首先明确,第二次挥手是一个ACK报文,因此在这样的情况下,ACK报文不会重传,只会认为是对等端发送失败了,因此在这样的情况下,就会引发对等端不断重传FIN = 1的报文,当超过了次数之后,这时候客户端就会直接关闭掉了

那么这时候就会问了,那么服务端呢?服务端一旦进入了CLOSE_WAIT的状态之后,这时候关闭的权限不是交给操作系统内核的,而是交给了用户程序,只有在用户程序调用了CLOSE()之后,才会发送第三次的FIN报文,因此这里可能会有死等的风险,这个解决办法需要通过编码来避免,也就是一定要调用close()

23. 第三次挥手丢失了会发生什么?

上面提到了,如果服务端进入到了CLOSE_WAIT,除了用户程序调用close(),否则一直就卡在这个状态,在数据发送完毕后,这时候就会将FIN报文发送出去,如果发送的时候不断超时,不断重传,无法收到对方的ACK,那么这时候在超过了重传次数之后,就会直接关闭连接

24. 第四次挥手丢失了会发生什么?(重要)

第四次挥手错误是TCP处理中最为核心最为复杂的一个过程

当客户端收到了服务端的FIN之后,这时候就会发送第四次挥手,也就是回复一个ACK给上次的FIN,那么如果这个ACK一直没有被收到的话,那么服务端就会不断重传FIN,客户端一直处于一个TIME_WAIT的状态,于是在这样的情况下,会出现的情况是

服务端不断重传FIN直到达到超时的次数,然后断开连接

客户端无感知,它认为是ACK报文到达了,因此在2MSL之后,就会自动关闭连接

25. 为什么TIME_WAIT是2MSL?

首先你要理解什么是MSL,所谓的MSL就是报文最大生存时间,与之相对的有IP协议中的TTL,它表示IP数据报经过的跳数,在Linux设置中,通常是设置TTL是64,MSL是30s,那么在这个时间段内,TCP报文都是有效的,超过这个时间,那么就被认为是无效报文,会被路由转发设备给丢弃,这意味着,一个报文在传输过程中,如果超过了MSL,那么这个报文就会被丢弃。

比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 FIN 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去最多2个MSL。

如果在2MSL内,都没有再收到这个FIN,那么就认为对方已经收到了这个ACK

为什么不是4MSL或者8MSL呢?

这个问题我认为和超时重传的时间设置有关,一般来说,超时重传的时间会被设置为小于MSL,因此在这样的情况下,2MSL完全够用,这里说的够用,说的意思是能够确保服务端收到了这个ACK并且重传FIN能够被客户端感知到

如果大于了MSL,那么这时候就可能需要4MSL或者8MSL了,但是实际上不会这样来设置超时时间

亦或者是触发了超时重传,但是FIN报文阻塞在路由器中无法发出,这时候也会导致超过2MSL,但是这都是小概率事件,忽略这些事件比用复杂的逻辑去处理他们更加有性价比

26. 深入剖析TIME_WAIT的状态

之所以需要TIME_WAIT的状态,主要有两个原因:

防止历史连接中的数据,被后面相同的四元组接收

假设这样一种场景,如果没有TIME_WAIT的话,那么这就意味着直接关闭,网络上可能残留有上一次连接的数据报文,因此在这样的情况下,就可能导致下一次相同连接的四元组,恰好序列号又是也一样的,这样的话就会导致数据被错误接收,因此为了避免这样的情况,因此就可以使得上一次报文全部消失之后再建立下一次连接

保证被动关闭连接的一方,能被正确关闭

被动关闭的一方是我们的服务端,假设没有TIME_WAIT的状态,如果你客户端的ACK直接丢失了,那么服务端发来了FIN之后,虽然也能够被重置的RST报文进行重置,但是这不是一种正常关闭的状态,是不优雅的,因此设置了这个状态来进行过度

27. TIME_WAIT过多有什么危害?

占用系统资源,比如说文件描述符,内存资源,CPU资源,线程资源

占用端口资源

如果客户端的TIME_WAIT状态过多,占满了所有的端口资源,那么就无法对<目的IP,目的端口>发起连接了,但是使用的端口,还是可以继续对另外一个服务端发起连接,这是因为内核在识别数据要转发到哪个进程的时候,会根据定义socket的四元组来进行确定,只要有一个字段不同,就算一条不同的连接,就是一个不同的socket

如果服务端的TIME_WAIT过多,不会导致端口资源受限,因为服务端只监听一个端口,它操作的是连接对象,但是当TIME_WAIT过多的时候,这时候会导致系统资源被浪费

28. TIME_WAIT如何排查?

出现大量TIME_WAIT有什么原因?

TIME_WAIT只有在主动发起断开连接请求的一方才有,因此要从这个角度来入手

HTTP没有使用长连接

Connection: keep-alive
  • 客户端禁用了长连接,而服务端开启了长连接,谁来关闭连接?

服务端来关闭,这是因为客户端关闭了长连接,这意味者客户端仅发起一次请求就下线了,关闭连接的这件事情要交给服务端来确认,我们将关闭连接这件事情交给本次请求的末端是合理的

  • 客户端开启了长连接,而服务端关闭了长连接,谁来关闭连接?

服务端来关闭,这个又是需要基于操作系统的开销来考量了

如果是客户端关闭这个长连接,那么意味着服务端需要将这个连接放入到操作系统内核的等待队列中,然后不断监听连接事件,当发生了连接关闭事件之后,这时候就还需要发起系统调用,这时候socket会有不必要的等待时间+;两次系统调用

如果是服务端来关闭这个长连接,那么意味着服务端只需要调用一次close(),然后剩下的交给TCP协议栈处理就可以了

HTTP长连接超时

当超时的时候,这时候服务端就会自动调用close()来关闭连接

HTTP长连接请求到达了上限

比如 nginx 的 keepalive_requests 这个参数,这个参数是指一个 HTTP 长连接建立之后,nginx 就会为这个连接设置一个计数器,记录这个HTTP 长连接上已经接收并处理的客户端请求的数量。如果达到这个参数设置的最大值时,则 nginx 会主动关闭这个长连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接。

keepalive_requests 参数的默认值是 100 ,意味着每个 HTTP 长连接最多只能跑 100 次请求,这个参数往往被大多数人忽略,因为当 QPS (每秒请求数) 不是很高时,默认值 100 凑合够用。

但是,对于一些 QPS 比较高的场景,比如超过 10000 QPS,甚至达到 30000 , 50000 甚至更高,如果 keepalive_requests 参数值是 100,这时候就 nginx 就会很频繁地关闭连接,那么此时服务端上就会出大量的 TIME_WAIT 状态

解决方案?

方法一:开启TCP连接复用,也就是说,在处于TIME_WAIT状态的TCP连接中抽取连接,然后重置它的状态,使之成为一个新的连接对象,并且为之所用,这样的话就可以减少TIME_WAIT的数量

方法二:强制重置,也就是说当TIME_WAIT超过了一定的阈值之后,这时候会将之后的连接给直接重置

方法三:通过编程,在close()添加一个SO_LINER来绕过TIME_WAIT,直接重置连接即可

29. 什么是CLOSE_WAIT?大量出现了CLOSE_WAIT怎么办?

首先这个状态出现服务端收到FIN = 1的报文之后,这个状态的终止需要用户进程调用close()才能解决

一个普通的TCP服务端的流程:

将服务端socket,bind绑定端口,listen监听端口

将服务端socket注册到epoll中

epoll_wait等待连接到来的时候,调用accept()获取连接

epoll_wait等待事件发生

调用close()

第一个原因,没有使用accept(),因此积压了大量的CLOSE_WAIT

第二个原因,大量的客户端主动断开了连接,也就是说那种发送了第一个SYN之后,然后就失联的

第三个原因,没有监听socket,没机会调用close

第四个原因,没用close

30. 客户端突然出现了故障怎么办?

为了解决这个问题,提出了一个保活机制,也就是在一个连接上没有任何的数据传送的时候,这时候就基于保活机制,每隔一个时间间断,就会发送一个探测报文,如果连续好几个报文都没有响应回包,那么这时候就认为这个TCP连接就已经死亡了,系统内核就会将错误信息上报

一般来说,有三种情况:

第一种情况,TCP对等端存活,保活计时器重置

第二种情况,TCP对等端进程重启了,此时对等端会返回一个RST报文,然后重置连接

第三种情况,TCP对等端宕机,探测报文石沉大海,此时直接释放服务器的连接

31. 如果已经建立了连接,但是服务端的进程崩溃会发生什么?

TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。

我自己做了个实验,使用 kill -9 来模拟进程崩溃的情况,发现在 kill 掉进程后,服务端会发送 FIN 报文与客户端进行四次挥手

32. 什么是HTTP协议?

HTTP协议翻译过来就是超文本传输协议,关键字是如下三个:

超文本:它是HTTP协议传输数据的数据的格式,具体来说,超文本就是一个超越文本的文本,它是视频、图片、视频等链接的载体,能够从一个超文本跳转到另外一个超文本,这是超文本最重要的特性

传输:所谓传输,就是提供一个协议,这个协议可以实现一个进程和其他进程的通信,它是一个双向的协议,通过这个协议可以实现两个进程之间的数据传输的约定和规范

协议:它是HTTP的本质,在相同操作系统之中,可以基于IPC(进程间通信,包括有管道,消息队列,socket,信号量,共享内存)来实现进程间的通信,那么在网络上,由于不同进程之间,他们所在的操作系统可能是异构的,因此为了解决这个问题,提出了协议,所谓的协议,就是将要将网络上传输的这些数据的解析方式做一个限定,因为二进制数据是操作系统之间都可以识别的,通过协议,就可以将这些二进制数据进行正确的解析,然后就可以通过这些数据进行通信了

总而言之,HTTP协议是用来实现网络上两个进程之间的超文本传输的一个网络通信协议

33. HTTP常见的状态码有哪些?

1xx:这一类信息属于一个提示信息,是协议处理中的一个中间状态,实际中用到的比较少,比如说在升级WebSocket协议的时候,就会出现这个状态码,表示将要升级成WebSocket协议

2xx:这个状态码表示服务器成功处理了客户端的请求

200 OK:是最常见的状态码,表示一切正常,如果是非HEAD请求,服务器返回的响应头部会有body的数据

204 No Content:是成功的状态码,和200OK是基本相同的,但是响应头中没有body数据

206 Partial Content:是应用于HTTP分块下载或者断点续传,表示响应返回的body数据不是资源的全部,而是其中的一部分而已,它也表示服务器的处理是成功的

3xx:状态码表示客户端请求的资源发生了变动,需要使用新的URL重新发送请求获取资源,也就是重定向

301 Moved Permanently:表示永久重定向,说明请求的资源已经不存在了,需要使用新的URL再次访问

302 Found:表示临时重定向,说明请求的资源还在,但是暂时需要使用另外的一个URL来进行访问

301和302都会在响应的头部使用字段Location,来指明后续将要跳转的URL,浏览器将会自动重定向到新的URL

304 Not Modified:表示资源没有修改,重定向到已经存在的资源文件,也称为是缓存重定向,也就是告诉客户端可以继续使用缓存,用于做一个缓存的控制

4xx:表示发送的报文有错误,服务器无法处理,一般来说是前端发送的请求有错误

404 Bad Request:表示客户端请求的报文是有错误的,但这是个笼统的错误

403 Forbidden:表服务器禁止访问这个资源,不是你的请求有错误

404 Not Found:表示资源在服务器上找不到,因此无法提供给用户

5xx:表示客户端的请求报文是正常的,但是在服务器处理内部的时候发生了错误,属于这个服务器端的错误码

500 Internal Server Error:和400是类似的,它是一个笼统的错误码,表示服务器发生了错误,但是具体是啥错误不知道,非常笼统

501 Not Implemented:表示客户端请求的功能还不支持,类似于即将开业,敬请期待的意思

502 Bad GateWay:通常是服务器作为网关或者代理的时候返回的错误,表示服务器自身的工作是正常的,但是真实的后端服务器访问出错

503 Service Unavailable:表示服务器当前很忙,暂时无法响应客户端,请稍后再试

34. HTTP的头部字段有哪些?

Host:假设这样一个场景,在一个IP主机上,部署了三个网站(容器),那么这个请求过去,要被哪个服务器来解析呢?这时候就可以基于Host字段来实现了,可以通过这个字段,将请求发送到同一个服务器上的不同网站

Content-Length字段:服务器在返回数据的时候,会有Content-Length字段,表明本次回应的数据长度,HTTP协议是基于一个TCP协议进行通信的,如果使用了TCP传输协议,就会存在一个粘包的问题,HTTP协议通过在头部设置一个换行符作为头部的边界,通过Content-Length作为单个HTTP Body的边界,这两个方式都是为了解决粘包的问题

Connection字段:常用于客户端要求服务器使用HTTP长连接机制,长连接就是讲只要任意一端没有明确提出断开连接的请求,就保持TCP的连接

Connection: Keep-Alive

Content-Type:用于服务器回应的时候,来告诉本次报文是什么格式,通常来说,这个字段和客户端的Accept:来联合使用的,通过这个字段,客户端就知道以什么样的编解码方式来获取本次的数据

Content-Encoding字段,说明数据的压缩方法,表示服务器返回的数据使用了什么样的压缩格式

Content-Encoding: gzip

35. Get请求和Post请求有什么区别

根据RFC规范,GET请求是从服务器中获取资源而不是推送资源,这个语义是只读的,这个资源是静态的文本、页面、图片视频等,GET请求的参数位置一般写在URL中,URL规定只能够支持ASCII,因此GET请求的参数只允许ASCII字符,并且对URL的长度是有限制的

根据RFC规范,POST请求向服务器推送资源,它不是安全的,也不是幂等的,POST请求数据通常写在body中,body中的数据格式是任意的,而且浏览器不会对body大小做限制

GET的语义是获取指定的资源,Get方法是安全和幂等的,可以被缓存的

Post的语义是根据请求负荷body中的字段对指定的资源做出处理,具体的处理方式根据资源类型的不同而不同,Post是不安全的,不幂等的,大部分实现是不可以缓存的

Get请求可以带body吗?

理论上可以的,因为从本质上来说,Get请求和Post请求都是一个HTTP报文,HTTP报文的格式是首部+报文的主体,同时POST请求中的URL也是可以带有一个参数的

36. HTTP缓存的实现方式?

对于一些具有重复性的HTTP请求,比如说每次请求得到的数据都是一样的,就可以将这一对请求=>响应的数据都缓存在本地,那么下一次就直接读取本地的数据,而不必在网络获取服务器的响应了,这样的话HTTP/1.1的性能就肯定有提升了,一般来说有强制缓存协商缓存

37. 什么是强制缓存?

所谓强制缓存,就是只要浏览器判断缓存没有过期,那么就直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器,通常来说会通过一个状态码200(from disk cache)这个字段来确定,你当前获取的资源是通过缓存来获取的,那么具体来说是怎么实现的呢?它和Redis中的缓存过期时间比较相似

Cache-Control:它是一个相对时间

Expires:是一个绝对的时间

Cache-Control:选项更多一些,设置得更加精细,具体的实现如下:

  • 当浏览器第一次请求访问服务器资源的时候,服务器会返回这个资源的同时,在请求头的首部加上一个Cache-Control,这个字段中设置了过期时间的大小
  • 浏览器去请求资源的时候,会先访问本地缓存,如果本地缓存的请求=>响应键值对中体现的Cache-Control过期了,那么就向服务器发送HTTP请求(不论资源是否变化,这时候服务器都会返回一份最新的数据),否则的话就走from disk cache
  • 服务器再次收到请求之后,会更新本地缓存中的Cache-Control

38. 什么是协商缓存?

首先先来理解强制缓存有什么弊端,强制缓存的弊端主要在于:

无论服务器上的资源和本地缓存中的资源是否相同,服务器在收到这个请求之后,都会发送一份最新的数据回来

因此在这样的情况下,可能导致无效的IO,于是就提出了协商缓存,所谓协商缓存,就是在本地缓存过期了之后,这时候会向服务器验证本地缓存和远程服务器资源的一致性,如果是相同的,那么就不会再发送最新的数据,而是发回一个信号,说资源没有改动过,本次不发送最新的数据,请你使用本地的资源就可以了

那么是怎么来实现的呢?一般来说有两种策略

  • 根据资源上一次被改动的时间,这个策略就是if-modified-since => modified-since来实现的

具体来说是这样的,就是当浏览器客户端发现本地资源过期了,然后它发现这个资源的响应头中有modifed-since字段,它就会将这个字段的值给赋值到if-modified-since中,然后在请求头上带上这个字段

然后在服务器的一端,收到这个请求之后,就会比较这个if-modified-since对应资源的修改时间,如果资源的修改时间更大,那么返回最新的数据,这时候返回的状态码是200 OK

如果是相同的话,那么就说明资源没有被改动过,于是在这样的情况下,返回状态码304 Not Modified,通知客户端走缓存

  • 根据资源本身的内容做一个摘要,通过这个摘要来确定资源是否被改动过了

具体来说,它的实现是这样的,就是当浏览器客户端发现本地资源过期了,然后它发现这个资源响应头中带有etag的字段,它就会将这个字段的值给赋值到if-none-match中,然后在请求头上带上这个字段

然后在服务器的一段,收到这个请求之后,就会比较这个if-none-match对应资源的etag,如果资源的etag和对应的if-none-match中的值是不相等的话,那么就返回最新的资源,走200 OK,否则的话走304 Not Modified,如果

为什么要使用eTAG?

对应缓存更新频率高的,如果一秒内更新了多次,那么就会造成协商缓存的失效

有时候没有修改文件,只是打开了文件,也可能导致文件修改时间的变化

文件修改时间与操作系统有关,无法很好地做到操作系统异构化下的同质操作

39. HTTP和HTTPS有什么区别?

具体的区别主要就是安全和不安全的问题,首先从连接建立的过程来说:

  • HTTP协议传输是不安全的,其中数据是明文传输的,而HTTPS通过在HTTP协议和TCP协议的中间引入了SSL/TLS协议,通过这样的方式来实现了数据的加密传输,即使HTTP数据包在网络中被截获了,对方也不知道其中的内容是什么

  • HTTP协议是基于TCP连接实现的,因此在TCP连接建立完毕后,就可以实现HTTP传输了,而HTTPS因为需要完成TLS/SSL的握手,因此连接建立的速度会更慢一些

  • HTTPS需要CA证书,来保证服务器的身份是可信的

  • HTTP的默认端口是80端口,HTTPS的默认端口是443端口

40. HTTPS解决了什么问题?

HTTP是明文传输协议,这意味着在公开信道上传输的时候,可能会导致数据包被截获,可能会有三个风险

第一个风险,就是数据明文传输,如果里面存储了敏感信息,那么容易被窃取

第二个风险,数据明文传输,如果被中间人截获并且添加了额外信息,那么信息容易被篡改

第三个风险,数据明文传输,如果没有加以验证对方的身份,对方不断伪造数据包,这样就可能导致用户一直访问的是一个冒牌服务器

而HTTPS通过:

第一个手段,通过加密密钥,通过这个密钥来对数据进行加密,实现了除了传输的双方,其他人都无法获取相关的数据

第二个手段,通过摘要算法+数字签名,基于摘要和数据内容的对比,实现了数据无法被篡改

第三个手段,通过CA证书,来保证服务器是可信的

41.详细说说三个手段的实现过程

混合加密实现了信息的机密性,即使数据包被截获,没有通信密钥,也无法对数据的查询

摘要算法实现了完整性,它能够为数据生成独一无二的指纹,指纹用来对数据的完整性做一个校验,解决了篡改的风险

将服务器公钥放入到数字证书中,通过权威机构的认证来确定服务器是可信的

  • 混合加密:所谓的混合加密,就是非对称加密传输通信密钥,对称加密传输通信报文

在建立之前,会在客户端和服务端生成一对公钥和私钥,所谓的公钥和私钥是这样的,首先服务器上有个公钥,这个公钥是大家都知道的,客户端上有一个私钥,这个私钥是由它自己管理的,那么在这样的情况下

服务端对通信密钥用公钥加密后,只有存储在客户端本地的私钥是可以解密的,因此可以保证通信密钥的传输安全

在完成了通信密钥的传输之后,之后所有的数据传输都使用这个通信密钥进行传输了

  • 摘要算法+数字签名:为了保证传输的内容不被篡改,我们需要对内容计算出一个指纹,然后同内容一起传输给对方

通过这个指纹,对方就可以将数据和摘要进行对比(指的是将数据通过公开的摘要算法计算出来的摘要和对方传过来的摘要进行对比),如果是一致的,那么就认为数据没有被篡改

但是要注意了,你的这个摘要包是完全有可能被伪造的,这是因为中间人可以通过自己构造出一个包,然后通过构造这个包的摘要,这样的话对等端进行验证之后,它依然发现是合法的

那么怎么来解决这个问题呢?可以通过数字签名的方式来实现

之前提到了有一个叫做公钥和私钥的东西,这两个东西对原始报文的加密顺序不同,达到的效果也是不一样的

  • 如果先用公钥加密,再用私钥解密,可以保证内容传输的安全,因为被公钥加密的内容,其他人是无法解密的,只有持有私钥的人,才能解密出实际的内容
  • 如果先用私钥加密,再用公钥解密,可以保证数据是对等端传过来的,因为私钥只有对等端才持有

一般来说,在使用这个方法的时候不会对数据内容进行直接签名,因为这样签名的效率实在太低,因此通常是对定长短小的摘要进行签名,这样的话可以提升效率

一般来说是这样做的,服务端内部保管了一个私钥,然后服务端对报文摘要进行私钥加密,然后客户端用这个公钥对摘要进行解密,在这样的情况下就可以实现签名认证了

  • 数字证书技术(重要)

通过哈希算法可以保证信息不会被篡改数字签名可以保证信息是持有私钥的人发送的

但是这其中的关键是,必须要保证你获取到的公钥是正确的,否则的话就无从谈起

那么如果在公开信道上传输公钥的时候,这个公钥被替换了,怎么办呢?

将会出现这样的情况,出现一个中间人,这个中间人截获了所有公钥包,然后替换为自己的公钥,于是在后面,所有人获取到的公钥都是这个中间人的公钥,那么这时候,其他真正的对等端反而无法接收真实的数据,而是只能够接收中间人的数据,这样也会造成冒充的风险

因此,在这样的情况下,为了验证公钥的合法性,引入了第三方权威机构,在使用公钥之前,使用权威机构的手段来对公钥进行验证,验证合法后才能继续使用

数字证书有什么用?

数字证书是为了验证你的这个公钥,确实是对等端的公钥,而不是中间人的公钥

因此数字证书中会有公钥信息,同时还会含有服务器的信息,通过验证服务器的信息,就可以知道你这个公钥是合法的了,具体验证流程是这样的:

首先服务器向CA机构提交服务器信息,包括有服务器的个人信息+公钥打包成一个数据包,你可以理解成将这些信息封装成一个字符串,然后通过摘要算法,将这些信息弄成一个定长的hash,然后用CA的私钥进行加密,然后呢,将数据包的信息和经过加密后的hash打包成一个数字证书,就可以使用了

那么客户端会怎么处理这个数字证书呢?

会将服务器的个人信息+公钥执行摘要算法,得到一个hash1,然后将证书中的CA数字签名CA公钥进行解密,得到hash2,然后比较hash1 == hash2?如果相等,那么证明这个证书就是可信的,可以使用公钥,否则的话就是不可信的

42. HTTPS是如何建立的?

SSL/TLS握手的基本流程:

第一步,客户端向服务端请求公钥并且验证公钥的合法性

第二步,双方协商生产会话密钥

第三步,双方采用会话密钥进行加密通信

首先要清楚,TLS建立之前,要先执行TCP的三次握手,然后此后基于这条TCP连接执行TLS/SSL的四次握手

握手的流程:

第一次握手,ClientHello,这个过程是客户端首先发起的,客户端请求与服务器建立一个HTTPS连接,于是告诉对方(1)自己支持的TLS/SSL协议的版本号(2)自己的支持密码套件有哪些(3)客户端这边自己产生的第一个随机数

第二次握手,ServerHello,这个过程是服务端给客户端的一个回应,一般来说会回应说(1)确认版本号(2)确认密钥套件,便于后续双方协商自己的密钥是如何生成的(3)服务器的CA证书(4)服务端自己产生的第二个随机数

第三次,ClientReply,首先,在收到你这个ServerHello的时候,这时候会验证你的证书的合法性,验证的流程是这样的:(1)拿到数字证书的服务器信息+公钥,做一个摘要算法,然后将数字证书中的CA签发的数字签名,进行CA公钥的解密,然后两个相同,验证通过(2)在客户端产生一个pre-master key,用公钥加密后传输出去(3)加密通信算法改变通知,表示之后都将使用对称会话密钥进行通信(4)客户端进入SSL/TLS的ESTABLISHED,将之前通信的内容做一个摘要提供给服务端进行检验

第四次,ServerReply,服务端收到了三个随机数,经过协商的加密算法,计算出本次的通信密钥,然后向客户端发送最后的消息:

(1)加密通信算法改变通知,表示随后的消息都将采用会话密钥进行通信

(2)服务器握手结束通知,表示服务器的握手阶段已经结束了,同时会将之前所有的内容做一个摘要,给客户端一个验证的机会

(3)TLS的握手阶段全部结束,之后都将使用普通的HTTP协议通信,只不过使用通话密钥来对数据报文进行加密##

43. HTTPS一定安全可靠的吗?

不一定,当用户信任了不可信的证书的时候,这时候就会导致HTTPS失守了,但是HTTPS本身是安全可靠的,出现这样的问题还是因为用户的操作不规范导致的

下面来讲讲中间人攻击是如何发生的

首先客户端向服务器正常发起一次HTTPS请求,首先也是要完成TLS的四次握手,但是这时候HTTPS请求就会被转发到一个假基站上,也就是说你的报文被人截获了

客户端发起一个Client Hello,携带了相关的密码套件信息,随机数,请求数字证书,TLS/SSL协议版本号,但是它不是直接发给Server的,而是经过了一个中间人服务器,这个中间人服务器截获了第一个随机数

中间人服务器截获后,伪装成客户端,向真正的服务器发送请求

然后接着,服务器发起一个Server Hello,携带了相关的密码套件确认,随机数,数字证书,TLS/SSL协议版本号,然后转发到中间人服务器,截获了第二个随机数

然后这时候中间人服务器将自己的证书转发到客户端上

接着,客户端收到Server Hello的时候,会进行CA证书,然后这时候就会发现不对劲了,就是你的这个CA证书是不合法的,然后就会弹窗:服务器证书不可信,然后客户端点击继续浏览,那么就会继续握手流程,它会用中间人的公钥对当前产生的密钥进行加密,然后发送给中间人服务器

中间人服务器收到ClientReply之后,就会计算和客户端的通信密钥A,同时会继续和真正的服务器进行握手操作,也就是会给服务器一个ClentReply,就会计算和客户端的通信密钥B

接下来的流程,就是中间人服务器用通信密钥A解密客户端的数据,用通信密钥B解密服务段的数据,这样的话数据就被偷看了

如何解决中间人攻击问题?

不要信任不合法的证书

采取双方验证,上面说的情况是:只有客户端验证对方的身份,如果服务端是正规服务器,那么它也会验证客户端的身份,具体的实现是这样的:

双向验证就是:客户端和服务端都有一个数字证书,这也就意味着双方都有一对公钥和私钥


文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录