【计算机网络笔记十】计算机网络面试问题总结

1. 计算机网络的各层协议及作用?

计算机网络体系可以大致分为一下三种,OSI 七层模型、TCP/IP 四层模型和五层模型。

  • OSI 七层模型:大而全,但是比较复杂、而且是先有了理论模型,没有实际应用。
  • TCP/IP 四层模型:是由实际应用发展总结出来的,从实质上讲,TCP/IP 只有最上面三层,最下面一层没有什么具体内容,TCP/IP 参考模型没有真正描述这一层的实现。
  • TCP/IP 五层模型:五层模型只出现在计算机网络教学过程中,这是对七层模型和四层模型的一个折中,既简洁又能将概念阐述清楚。

在这里插入图片描述

七层网络体系结构各层的主要功能:

  • 应用层为应用程序提供交互服务。在互联网中的应用层协议很多,如域名系统DNS,支持万维网应用的HTTP协议,支持电子邮件的SMTP协议等。

  • 表示层主要负责数据格式的转换,如加密解密、转换翻译、压缩解压缩等。

  • 会话层负责在网络中的两个节点之间建立、维持和终止通信,如服务器验证用户登录便是由会话层完成的。

  • 运输层:有时也译为传输层,向主机进程提供通用的数据传输服务。该层主要有以下两种协议:

    • TCP:提供面向连接的、可靠的数据传输服务;
    • UDP:提供无连接的、尽最大努力的数据传输服务,但不保证数据传输的可靠性
  • 网络层选择合适的路由和交换结点,确保数据及时传送。主要包括 IP 协议。

  • 数据链路层:数据链路层通常简称为链路层。将网络层传下来的 IP 数据包组装成,并在相邻节点的链路上传送帧。

  • 物理层实现相邻节点间比特流的透明传输,尽可能屏蔽传输介质和通信手段的差异。

2. TCP和UDP的区别?

对比如下:

UDP TCP
是否连接 无连接 面向连接
是否可靠 不可靠传输,不使用流量控制和拥塞控制 可靠传输,使用流量控制和拥塞控制
是否有序 无序 有序,消息在传输过程中可能会乱序,TCP 会重新排序
传输速度
连接对象个数 支持一对一,一对多,多对一和多对多交互通信 只能是一对一的点对点通信
传输方式 面向报文 面向字节流
首部开销 首部开销小,仅 8 字节 首部最小20字节,最大60字节
适用场景 适用于实时应用(IP电话、视频会议、直播等) 适用于要求可靠传输的应用,例如文件传输

总结:

  • TCP 用于在传输层有必要实现可靠传输的情况,UDP 用于对高速传输和实时性有较高要求的通信。TCP 和 UDP 应该根据应用目的按需使用。

3. UDP 和 TCP 对应的应用场景是什么?

TCP面向连接,能保证数据的可靠性交付,因此经常用于:

  • FTP 文件传输
  • HTTP / HTTPS

UDP 面向无连接,它可以随时发送数据,再加上 UDP 本身的处理既简单又高效,因此经常用于:

  • 包总量较少的通信,如 DNS 、SNMP 等
  • 视频、音频等多媒体实时通信
  • 广播通信

在这里插入图片描述

4. 详细介绍一下 TCP 的三次握手机制?

  • 第一次握手:客户端发送 SYN = 1 seq = x 给服务端请求建立连接。SYN = 1 表示这是一个TCP连接请求报文,seq = x 表示客户端进程使用序号 x 作为初始序号。客户端进入 SYN_SENT 状态,等待服务器确认
  • 第二次握手:服务端发送 SYN = 1 ACK = 1 ack = x + 1 seq = y 给客户端。SYN = 1 ACK = 1 表示当前报文是对客户端请求的确认报文,确认号ack = x + 1 ,同时选择 seq = y 作为初始序号,此时服务器进入 SYN_RECV 状态。
  • 第三次握手:客户端发送 ACK = 1 ack = y + 1 seq = x + 1 给服务端。ACK = 1 表当前报文是对服务端请求的确认报文确认号ack = y + 1序列号seq = x + 1,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。

在这里插入图片描述

理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

5. 为什么需要三次握手,而不是两次?

TCP 是可靠的传输控制协议,而三次握手是保证数据可靠传输又能提高传输效率的最小次数。

主要有三个原因:

  1. 三次握手才能让双方均确认自己和对方的发送和接收能力都正常。

    第一次握手:客户端只是发送处请求报文段,什么都无法确认,而服务器可以确认自己的接收能力和对方的发送能力正常;

    第二次握手:客户端可以确认自己发送能力和接收能力正常,对方发送能力和接收能力正常;

    第三次握手:服务器可以确认自己发送能力和接收能力正常,对方发送能力和接收能力正常;

    可见三次握手才能让双方都确认自己和对方的发送和接收能力全部正常,这样就可以愉快地进行通信了。

  2. 告知对方自己的初始序号值,并确认收到对方的初始序号值。

    为了实现可靠数据传输, TCP协议的通信双方,都必须维护一个序列号和一个确认号字段, 以标识发送出去的数据包中,哪些是已经被对方收到的。这两个字段的值会在初始序号值的基础上递增。

    三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤。如果只是两次握手, 至多只有连接发起方的起始序列号能够被确认, 而另一方选择的序列号则得不到确认。

  3. 防止已过期(如超时重传)的连接请求报文突然又传送到服务器,因而产生错误和资源浪费。

    如果采用两次握手,则第一次请求之后,服务端就确认已建立连接,那么此时如果客户端关闭连接之后,服务端又收到了客户端之前已失效的 TCP 连接请求后,向客户端发送确认请求报文,此时客户端因为是关闭状态无法响应服务端,则服务端会因为不知道客户端已经处于关闭状态,还在一直不停的给客户端发送确认请求报文,导致浪费资源。

6. 为什么要三次握手,而不是四次?

因为三次握手已经可以确认双方的发送接收能力正常,双方都知道彼此已经准备好,而且也可以完成对双方初始序号值得确认,也就无需再第四次握手了。

  • 第一次握手:服务端确认“自己收、客户端发”报文功能正常。
  • 第二次握手:客户端确认“自己发、自己收、服务端收、服务端发”报文功能正常,客户端认为连接已建立。
  • 第三次握手:服务端确认“自己发、客户端收”报文功能正常,此时双方均建立连接,可以正常通信。

7. 什么是 SYN 洪泛攻击?如何防范?

SYN洪泛攻击属于 DOS 攻击的一种,它利用 TCP 协议缺陷,通过发送大量的半连接请求,耗费 CPU 和内存资源。

原理:

  • 在三次握手过程中,服务器发送 [SYN/ACK] 包(第二个包)之后、收到客户端的 [ACK] 包(第三个包)之前的 TCP 连接称为半连接(half-open connect),此时服务器处于 SYN_RECV(等待客户端响应)状态。如果接收到客户端的 [ACK],则 TCP 连接成功,如果未接受到,则会不断重发请求直至成功。
  • SYN 攻击的攻击者在短时间内伪造大量不存在的 IP 地址,向服务器不断地发送 [SYN] 包,服务器回复 [SYN/ACK] 包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时。
  • 这些伪造的 [SYN] 包将长时间占用未连接队列,影响了正常的 SYN,导致目标系统运行缓慢、网络堵塞甚至系统瘫痪。

检测:当在服务器上看到大量的半连接状态时,特别是源 IP 地址是随机的,基本上可以断定这是一次 SYN 攻击。

防范:

  • 通过防火墙、路由器等过滤网关防护。
  • 通过加固 TCP/IP 协议栈防范,如增加最大半连接数,缩短超时时间。
  • SYN cookies技术。SYN Cookies 是对 TCP 服务器端的三次握手做一些修改,专门用来防范 SYN 洪泛攻击的一种手段。

8. 三次握手连接阶段,最后一次ACK包丢失,会发生什么?

服务端:

  • 第三次的ACK在网络中丢失,那么服务端该 TCP 连接的状态为 SYN_RECV,并且会根据 TCP 的超时重传机制,会等待3秒、6秒、12秒后重新发送 SYN + ACK 包,以便客户端重新发送 ACK 包。

  • 如果重发指定次数之后,仍然未收到客户端的 ACK 应答,那么一段时间后,服务端自动关闭这个连接。

客户端:

  • 客户端认为这个连接已经建立,如果客户端向服务端发送数据,服务端将以 RST 包(Reset,标示复位,用于异常的关闭连接)响应。此时,客户端知道第三次握手失败。

9. 详细介绍一下 TCP 的四次挥手过程?

  • 第一次:客户端发送 FIN = 1 ACK = 1 seq = u ack = v 给服务端。FIN = 1 表示这是一个 TCP 释放连接的请求报文。

  • 第二次:服务端发送 ACK = 1 seq = v ack = u + 1 给客户端。表示确认了客户端第一次的释放连接请求报文。

    此时 A → B 方向的连接释放了,TCP 处于半关闭状态,虽然 B 确认了 A 不会再发送数据了,但 B 仍有可能给 A 发送数据。

  • 第三次:服务端发送 FIN = 1 ACK = 1 seq = w ack = u + 1 给客户端,表示服务端释放连接的请求报文。

    • 序列号 seq = w,即服务端上次发送的报文的最后一个字节的序号 + 1
    • 确认号 ack = u + 1,与第二次挥手相同,因为这段时间客户端没有发送数据
  • 第四次:客户端发送 ACK = 1 seq = u + 1 ack = w + 1 给服务端。表示确认了服务端的关闭请求。

    此时服务端直接进入关闭状态客户端进入 TIME_WAIT 等待状态,等待时长为 2MSL,即最长报文生命周期的 2 倍,如果这期间没有收到对方的报文,客户端才最终进入关闭状态。

在这里插入图片描述

可以看到,服务端结束 TCP 连接的时间要比客户端早一些。

10. 为什么连接的时候是三次握手,关闭的时候却是四次握手?

  1. 服务器在收到客户端的 FIN 报文段后,可能还有一些数据要传输,所以不能马上关闭连接,但是会做出应答,返回 ACK 报文段。

    接下来可能会继续发送数据,在数据发送完后,服务器会向客户单发送 FIN 报文,表示数据已经发送完毕,请求关闭连接。 服务器的 ACK 和 FIN 一般都会分开发送,从而导致多了一次,因此一共需要四次挥手。

  2. TCP 是全双工的连接,必须两端同时关闭连接,连接才算真正关闭。

    如果 A 已经准备关闭写,但是它还可以读 B 发送来的数据。此时,A 发送 FIN 报文给 B 收到后,B 回复 ACK 报文给 A。

    当 B 也准备关闭写,发送 FIN 报文给 A,A 回复 ACK 给 B。此时两端都关闭了,TCP 连接才正常关闭。

    所以对全双工模式来说,为了彻底关闭,就需要通信两端的 4 次交互才行互相确认对方的关闭状态。

11. 为什么客户端的 TIME-WAIT 状态必须等待 2MSL ?

  1. 保证可靠的终止 TCP 连接。确保 ACK 报文能够到达服务端,从而使服务端正常关闭连接。

    如果第四次客户端给服务端发送的确认报文丢失了,则服务端会因为没有收到确认报文超时重传 FIN/ACK 报文给客户端。

    但是如果此时客户端处于关闭状态,就会无法响应服务端了,那么服务端就会一直在重传 FIN/ACK 报文,也就是说服务端一直得不到确认,最终会无法进入 CLOSED 状态。

    MSL 是报文段在网络上存活的最长时间。客户端等待 2MSL 时间,即「客户端 ACK 报文 1MSL 超时 + 服务端 FIN 报文 1MSL 传输」,就能够收到服务端重传的 FIN/ACK 报文,然后客户端重传一次 ACK 报文,并重新启动 2MSL 计时器。如此保证服务端能够正常关闭。

    如果服务端重发的 FIN 没有成功地在 2MSL 时间里传给客户端,服务端则会继续超时重试直到断开连接。

  2. 防止已失效的连接请求报文段出现在之后的连接中。

    TCP 要求在 2MSL 内不使用相同的序列号。客户端在发送完最后一个 ACK 报文段后,再经过时间 2MSL,就可以保证本连接持续的时间内产生的所有报文段都从网络中消失。这样就可以使下一个连接中不会出现这种旧的连接请求报文段。或者即使收到这些过时的报文,也可以不处理它。

12. 如果已经建立了连接,但是客户端出现故障了怎么办?

通过定时器 + 超时重试机制,尝试获取确认,直到最后会自动断开连接。

具体而言,TCP 设有一个保活计时器服务器每收到一次客户端的数据,都会重新复位这个计时器,时间通常是设置为 2 小时。若 2 小时还没有收到客户端的任何数据,服务器就开始重试:每隔 75 秒钟发送一个探测报文段,若一连发送 10 个探测报文后客户端依然没有回应,那么服务器就认为连接已经断开了

13. TIME-WAIT 状态过多会产生什么后果?怎样处理?

服务器来讲,短时间内关闭了大量的Client连接,就会造成服务器上出现大量的TIME_WAIT连接,严重消耗服务器的资源,此时部分客户端就会显示连接不上

客户端来讲,客户端TIME_WAIT过多,就会导致端口资源被占用,因为端口就65536个,被占满就会导致无法创建新的连接

解决办法:

  • 服务器可以设置 SO_REUSEADDR 套接字选项来避免 TIME_WAIT状态,此套接字选项告诉内核,即使此端口正忙(处于 TIME_WAIT状态),也请继续并重用它。
  • 调整系统内核参数,修改/etc/sysctl.conf文件,即修改net.ipv4.tcp_tw_reuse 和 tcp_timestamps
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
  • 强制关闭,发送 RST 包越过 TIME_WAIT 状态,直接进入 CLOSED 状态。

14. TIME_WAIT 是服务器端的状态? 还是客户端的状态?

TIME_WAIT 是主动断开连接的一方会进入的状态,一般情况下,都是客户端所处的状态; 服务器端一般设置不主动关闭连接。

TIME_WAIT 需要等待 2MSL,在大量短连接的情况下,TIME_WAIT 会太多,这也会消耗很多系统资源。对于服务器来说,在 HTTP 协议里指定 KeepAlive(浏览器重用一个 TCP 连接来处理多个 HTTP 请求),由浏览器来主动断开连接,可以一定程度上减少服务器的这个问题。

15. TCP协议如何保证可靠性?

TCP主要提供了检验和、序列号/确认应答、超时重传、滑动窗口、拥塞控制 和 流量控制等方法实现了可靠性传输。

  • 检验和:通过检验和的方式,接收端可以检测出来数据是否有差错和异常,假如有差错就会直接丢弃TCP段,重新发送。

  • 序列号/确认应答

    序列号的作用不仅仅是应答的作用,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据。

    TCP传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答。也就是发送ACK报文,这个ACK报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。

  • 滑动窗口:滑动窗口既提高了报文传输的效率,也避免了发送方发送过多的数据而导致接收方无法正常处理的异常。

  • 超时重传:超时重传是指发送出去的数据包到接收到确认包之间的时间,如果超过了这个时间会被认为是丢包了,需要重传。最大超时时间是动态计算的。

  • 拥塞控制:在数据传输过程中,可能由于网络状态的问题,造成网络拥堵,此时引入拥塞控制机制,在保证TCP可靠性的同时,提高性能。

  • 流量控制:如果主机A 一直向主机B发送数据,不考虑主机B的接受能力,则可能导致主机B的接受缓冲区满了而无法再接受数据,从而会导致大量的数据丢包,引发重传机制。而在重传的过程中,若主机B的接收缓冲区情况仍未好转,则会将大量的时间浪费在重传数据上,降低传送数据的效率。所以引入流量控制机制,主机B通过告诉主机A自己接收缓冲区的大小,来使主机A控制发送的数据量。流量控制与TCP协议报头中的窗口大小有关。

16. 详细讲一下TCP的滑动窗口?

在进行数据传输时,如果传输的数据比较大,就需要拆分为多个数据包进行发送。TCP 协议需要对数据进行确认后,才可以发送下一个数据包。这样一来,就会在等待确认应答包环节浪费时间。
为了避免这种情况,TCP引入了窗口概念。窗口大小指的是不需要等待确认应答包而可以继续发送数据包的最大值。

在这里插入图片描述

从上面的图可以看到滑动窗口左边的是已发送并且被确认的分组滑动窗口右边还没有轮到的分组

滑动窗口里面也分为两块,一块是已经发送但是未被确认的分组,另一块是窗口内等待发送的分组。随着已发送的分组不断被确认,窗口内等待发送的分组也会不断被发送。整个窗口就会往右移动,让还没轮到的分组进入窗口内。

可以看到滑动窗口起到了一个限流的作用,也就是说当前滑动窗口的大小决定了当前 TCP 发送包的速率,而滑动窗口的大小取决于拥塞控制窗口流量控制窗口的两者间的最小值

17. 详细讲一下拥塞控制?

TCP 一共使用了四种算法来实现拥塞控制:

  • 慢开始 (slow-start);
  • 拥塞避免 (congestion avoidance);
  • 快速重传 (fast retransmit);
  • 快速恢复 (fast recovery)

发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。

  • 当cwnd<ssthresh时,使用慢开始算法。
  • 当cwnd>ssthresh时,改用拥塞避免算法。
  • 当cwnd=ssthresh时,慢开始与拥塞避免算法任意。

慢开始:不要一开始就发送大量的数据,由小到大逐渐增加拥塞窗口的大小。

拥塞避免:拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1而不是加倍。这样拥塞窗口按线性规律缓慢增长。

快重传:我们可以剔除一些不必要的拥塞报文提高网络吞吐量。比如接收方在收到一个失序的报文段后就立即发出重复确认,而不要等到自己发送数据时捎带确认。快重传规定:发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期

在这里插入图片描述

快恢复主要是配合快重传。当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半(为了预防网络发生拥塞),但接下来并不执行慢开始算法,因为如果网络出现拥塞的话就不会收到好几个重复的确认,收到三个重复确认说明网络状况还可以。

在这里插入图片描述

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

常见状态码:

  • 200:请求被服务器成功处理
  • 204:请求被受理但没有资源可以返回
  • 206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行 GET方法,相应报文中通过 Content-Range 指定范围的资源。
  • 301:永久性重定向
  • 302:临时重定向
  • 400:客户端请求报文有语法错误,不能被服务器识别
  • 401:请求需要认证
  • 403:请求的对应资源禁止被访问,服务器收到请求,但是拒绝提供服务。
  • 404:服务器无法找到对应资源
  • 500:服务器内部错误,服务器遇到错误,无法完成请求。
  • 503:服务器正忙

状态码开头代表类型:

在这里插入图片描述

19. 状态码301和302的区别是什么?

共同点:301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)。

不同点

  • 301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;

  • 302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。 SEO中302好于301。

重定向原因:

  1. 网站调整(如改变网页目录结构);
  2. 网页被移到一个新地址;
  3. 网页扩展名改变(如应用需要把.php改成.Html或.shtml)。

20. HTTP 常用的请求方式?

在这里插入图片描述

为了方便记忆,可以将 PUT、DELETE、POST、GET 理解为客户端对服务端的增删改查。

  • PUT:上传文件,向服务器添加数据,可以看作增
  • DELETE:删除文件
  • POST:传输数据,向服务器提交数据,对服务器数据进行更新。
  • GET:获取资源,查询服务器资源

21. GET请求和POST请求的区别?

使用上的区别:

  • GET使用URL或Cookie传参,而POST将数据放在BODY中,这个是因为HTTP协议用法的约定。
  • GET方式提交的数据有长度限制,则POST的数据则可以非常大,这个是因为它们使用的操作系统和浏览器设置的不同引起的区别。
  • POST比GET安全,因为数据在地址栏上不可见,这个说法没毛病,但依然不是GET和POST本身的区别。

本质区别

  • GET和POST最大的区别主要是GET请求是幂等性的,POST请求不是。这个是它们本质区别。

  • 幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着对同一URL的多个请求应该返回同样的结果。

22. 解释一下HTTP长连接和短连接?

在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次 TCP 连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的 Web页中包含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。

从 HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头有加入这行代码:Connection: keep-alive

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP 连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。

HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。

23. HTTP请求报文和响应报文的格式?

请求报文格式:

  1. 请求行(请求方法+URI+协议版本)
  2. 请求头部
  3. 空行+CRLF回车换行
  4. 请求主体

在这里插入图片描述

GET /sample.jsp HTTP/1.1 请求行
Accept:image/gif.image/jpeg, 请求头部
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding:gzip,deflate

username=jinqiao&password=1234 请求主体

响应报文:

  1. 状态行(协议版本+状态码+原因短语)
  2. 响应头部
  3. 空行+CRLF
  4. 响应主体

在这里插入图片描述

HTTP/1.1 200 OK
Server:Apache Tomcat/5.0.12
Date:Mon,6Oct2003 13:23:42 GMT
Content-Length:112

<html>
<head>
<title>HTTP响应示例<title>
</head>
<body>
Hello HTTP!
</body>
</html>

24. HTTP 1.0 和 HTTP 1.1 的区别?

HTTP1.0 最早在网页中使用是在 1996 年,那个时候只是使用一些较为简单
的网页上和网络请求上,而 HTTP1.1 则在 1999 年才开始广泛应用于现在的各大浏览器网络请求中,同时 HTTP1.1 也是当前使用最为广泛的 HTTP 协议。

主要区别主要体现在:

  • 长连接HTTP 1.1 支持长连接和请求的流水线处理在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP 1.1 中默认开启 Connection:keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。
  • 缓存处理:在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP 1.1 则引入了更多的缓存控制策略例如 Entity tag, If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓
    存策略。
  • 带宽优化及网络连接的使用:HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
  • 错误通知的管理:在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
  • Host头处理:在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP 1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)

25. HTTP 1.1 和 HTTP 2.0 的区别?

HTTP2.0相比HTTP1.1支持的特性:

  • 新的二进制格式:HTTP1.1的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
  • 多路复用,即连接共享,即每一个request都是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
  • 头部压缩,HTTP1.1的头部(header)带有大量信息,而且每次都要重复发送;HTTP2.0使用encoder来减少需要传输的header大小通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
  • 服务端推送:服务器除了对最初请求的响应外,服务器还可以额外的向客户端推送资源,而无需客户端明确的请求。

HTTP2.0 的多路复用和 HTTP1.X 中的长连接复用有什么区别?

  • HTTP/1.0 一次请求-响应,建立一个连接,用完关闭;每一个请求都要建立一个连接;
  • HTTP/1.1 Pipeling 解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的队头阻塞
  • HTTP/2 多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行;

26. HTTP 与 HTTPS 的区别?

在这里插入图片描述
在这里插入图片描述

HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):一般理解为 HTTP + SSL/TLS,通过 SSL 证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。

  • HTTP 的 URL 以 http:// 开头,而 HTTPS 的 URL 以 https:// 开头
  • HTTP 是不安全的,而 HTTPS 是安全的
  • HTTP 标准端口是 80 ,而 HTTPS 的标准端口是 443
  • 在 OSI 网络模型中,HTTP 工作于应用层,而 HTTPS 的安全传输机制工作在传输层
  • HTTP 无法加密,而 HTTPS 对传输的数据进行加密
  • HTTP 无需证书,而 HTTPS 需要 CA 机构颁发的 SSL 证书

27. HTTPS 的优缺点?

优点

  • 安全性
    • 使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
    • HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性
    • HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。
  • SEO方面:谷歌曾在2014年8月份调整搜索引擎算法,并称“比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高”。

缺点:

  • 在相同网络环境中,HTTPS 相比 HTTP 无论是响应时间还是耗电量都有大幅度上升。
  • HTTPS 的安全是有范围的,在黑客攻击、服务器劫持等情况下几乎起不到作用。
  • 在现有的证书机制下,中间人攻击依然有可能发生。
  • HTTPS 需要更多的服务器资源,也会导致成本的升高。

28. 讲一讲HTTPS 的原理?

在这里插入图片描述

加密流程按图中的序号分为:

  • ① 客户端请求 HTTPS 网址,然后连接到 server 的 443 端口 (HTTPS 默认端口,类似于 HTTP 的 80 端口)。

  • ② 采用 HTTPS 协议的服务器必须要有一套数字 CA (Certification Authority)证书。颁发证书的同时会产生一个私钥和公钥私钥由服务端自己保存,不可泄漏。公钥则是附带在证书的信息中,可以公开的。证书本身也附带一个证书电子签名,这个签名用来验证证书的完整性和真实性,可以防止证书被篡改。

  • ③ 服务器响应客户端请求,将证书传递给客户端,证书包含公钥和大量其他信息,比如证书颁发机构信息,公司信息和证书有效期等。

  • ④ 客户端解析证书并对其进行验证。如果证书不是可信机构颁布,或者证书中的域名与实际域名不一致,或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。

    如果证书没有问题,客户端就会从服务器证书中取出服务器的公钥A。然后客户端还会生成一个随机码 KEY,并使用公钥A将其加密

  • ⑤ 客户端把加密后的随机码 KEY 发送给服务器,作为后面对称加密的密钥

  • ⑥ 服务器在收到随机码 KEY 之后会使用私钥B将其解密

    经过以上这些步骤,客户端和服务器终于建立了安全连接,完美解决了对称加密的密钥泄露问题,接下来就可以用对称加密愉快地进行通信了。

  • ⑦ 服务器使用对称密钥 (随机码 KEY) 对数据进行对称加密并发送给客户端客户端使用相同的密钥 (随机码 KEY) 解密数据

  • ⑧ 双方使用对称加密愉快地传输所有数据。

29. 在浏览器中输入www.baidu.com后执行的全部过程?

  1. 域名解析(域名 www.baidu.com 变为 ip 地址)。

    浏览器搜索自己的DNS缓存(维护一张域名与IP的对应表);
    若没有,则搜索操作系统的DNS缓存(维护一张域名与IP的对应表);
    若没有,则搜索操作系统的hosts文件(维护一张域名与IP的对应表)。

    若都没有,则找 tcp/ip 参数中设置的首选 dns 服务器,即本地 dns 服务器递归查询),本地域名服务器查询自己的dns缓存,如果没有,则进行迭代查询。本地dns服务器将IP返回给操作系统,同时缓存IP。

  2. 发起 tcp 的三次握手,建立 tcp 连接。浏览器会以一个随机端口(1024-65535)向服务端的 web 程序 80 端口发起 tcp 的连接。

  3. 建立 tcp 连接后发起 http 请求。

  4. 服务器响应 http 请求,客户端得到 html 代码。服务器 web 应用程序收到 http 请求后,就开始处理请求,处理之后就返回给浏览器 html 文件。

  5. 浏览器解析 html 代码,并请求 html 中的资源。

  6. 浏览器对页面进行渲染,并呈现给用户。

在这里插入图片描述

一般情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接,然后如果客户端或者服务器在其头信息加入了这行代码 Connection: keep-alive ,TCP 连接在发送后将仍然保持打开状态,于是,客户端可以继续通过相同的连接发送请求,也就是说前面的 3 到 6,可以反复进行。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

30. 什么是 Cookie 和 Session ?

什么是 Cookie

HTTP Cookie(也叫 Web Cookie或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。

Cookie 主要用于以下三个方面:

  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

什么是 Session

Session 代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去当客户端关闭会话,或者 Session 超时失效时会话结束

31. Cookie 和 Session 是如何配合的呢?

用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session ,请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器,浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名。

当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。

根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。

32. Cookie 和 Session 的区别?

  • 作用范围不同,Cookie 保存在客户端(浏览器),Session 保存在服务器端。
  • 存取方式的不同,Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserId 等。
  • 有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。
  • 隐私策略不同,Cookie 存储在客户端,比较容易遭到不法获取,早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取;Session 存储在服务端,安全性相对 Cookie 要好一些。
  • 存储大小不同, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie。

33. 什么是 Http 协议无状态协议?怎么解决?

无状态协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息也就是说,当客户端一次 HTTP 请求完成以后,客户端再发送一次 HTTP 请求,HTTP 并不知道当前客户端是一个”老用户“。

可以使用 Cookie 来解决无状态的问题,Cookie 就相当于一个通行证,第一次访问的时候给客户端发送一个 Cookie,当客户端再次来的时候,拿着 Cookie(通行证),那么服务器就知道这个是”老用户“。

34. URI 和 URL 的区别

URI,是 uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。Web 上可用的每种资源如 HTML 文档、图像、视频片段、程序等都是一个来 URI 来定位的。

URI 一般由三部组成:

  • ① 访问资源的命名机制
  • ② 存放资源的主机名
  • ③ 资源自身的名称,由路径表示,着重强调于资源。

URL 是 uniform resource locator,统一资源定位器,它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。URL 是 Internet 上用来描述信息资源的字符串,主要用在各种 WWW 客户程序和服务器程序上,特别是著名的 Mosaic。

采用 URL 可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL 一般由三部组成:

  • ① 协议(或称为服务方式)
  • ② 存有该资源的主机 IP 地址(有时也包括端口号)
  • ③ 主机资源的具体地址。如目录和文件名等

URN,uniform resource name,统一资源命名,是通过名字来标识资源,比如 mailto:[email protected]

如果把 URI 看成是一种抽象的接口定义,那么 URL 和 URN 都是一种 URI 的具体实现。笼统地说,每个 URL 都是 URI,但不一定每个 URI 都是 URL。这是因为 URI 还包括一个子类,即统一资源名称(URN),它命名资源但不指定如何定位资源。上面的 mailto、news 和 isbn URI 都是 URN 的示例。

在 Java 的 URI 中,一个 URI 实例可以代表绝对的,也可以是相对的,只要它符合 URI 的语法规则。而 URL 类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的。在 Java 类库中,URI 类不包含任何访问资源的方法,它唯一的作用就是解析。相反的是,URL 类可以打开一个到达资源的流。

35. TCP 粘包/拆包的原因及解决方法?

TCP 是以流的方式来处理数据,一个完整的包可能会被 TCP 拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。

TCP 粘包/分包的原因:

  • 应用程序写入的字节大小大于套接字发送缓冲区的大小,会发生拆包现象,

  • 而应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包现象;

  • 进行 MSS 大小的 TCP 分段,当 TCP 报文长度 - TCP 头部长度 > MSS 的时候将发生拆包

  • 以太网帧的 payload(净荷)大于 MTU(1500 字节)进行 ip 分片。

解决方法:

  • ① 消息定长,即每次发送固定长度字节。 这种方案简单,但是缺点是会浪费空间。 比如规定每10个字节表示一个消息,但是客户端发送的消息只包含1个字节,那么剩余9个字节就需要补空或者补0,浪费了。可以使用 FixedLengthFrameDecoder 类。
  • ② 包尾使用特殊分隔符,比如使用回车符(\n)作为分隔符,再例如 HTTP 报文头中就使用了回车符、换行符作为 HTTP 协议的边界。可以使用行分隔符类 LineBasedFrameDecoder 或自定义分隔符类 DelimiterBasedFrameDecoder
  • ③ 固定长度字段存储内容的长度信息,即将消息分为消息头和消息体,在消息头部附加一个固定长度的字段来存储本次消息内容的长度,例如在每个消息的头部使用固定4个字节的大小来表示消息内容的长度,在接收方解析时也是先读取固定长度的字段,获取长度,然后根据长度读取数据内容,精确定位数据内容,不需要转义。缺点:数据内容长度有限制,需要提前知道最长消息的字节数。可以使用 LengthFieldBasedFrameDecoder 类。

36. 请概要介绍下序列化

序列化(编码)是将对象序列化为二进制形式(字节数组),主要用于网络传输、数据持久化等;而反序列化(解码)则是将从网络、磁盘等读取的字节数组还原成原始对象,主要用于网络传输对象的解码,以便完成远程调用。

影响序列化性能的关键因素:序列化后的码流大小(网络带宽的占用)、序列化的性能(CPU 资源占用);是否支持跨语言(异构系统的对接和开发语言切换)。

  • Java 默认提供的序列化无法跨语言、序列化后的码流太大、序列化的性能差

  • XML,优点:人机可读性好,可指定元素或特性的名称。

    缺点:序列化数据只包含数据本身以及类的结构,不包括类型标识和程序集信息;只能序列化公共属性和字段;不能序列化方法;文件庞大,文件格式复杂,传输占带宽

    适用场景:当做配置文件存储数据,实时数据转换。

  • JSON,是一种轻量级的数据交换格式,

    优点:兼容性高、数据格式比较简单易于读写、序列化后数据较小,可扩展性好,兼容性好、与 XML 相比,其协议比较简单,解析速度比较快。

    缺点:数据的描述性比 XML 差、不适合性能要求为 ms 级别的情况、额外空间开销比较大

    适用场景(可替代XML ):跨防火墙访问、可调式性要求高、基于 Web browser 的 Ajax 请求、传输数据量相对小,实时性要求相对低(例如秒级别)的服务。

  • Fastjson,采用一种“假定有序快速匹配”的算法。

    优点:接口简单易用、目前 java 语言中最快的 json 库。

    缺点:过于注重快,而偏离了“标准”及功能性、代码质量不高,文档不全、安全漏洞较多。

    适用场景:协议交互、Web 输出、Android 客户端

  • Thrift,不仅是序列化协议,还是一个 RPC 框架。

    优点:序列化后的体积小, 速度快、支持多种语言和丰富的数据类型、对于数据字段的增删具有较强的兼容性、支持二进制压缩编码。

    缺点:使用者较少、跨防火墙访问时,不安全、不具有可读性,调试代码时相对困难、不能与其他传输层协议共同使用(例如 HTTP)、无法支持向持久层直接读写数据,即不适合做数据持久化序列化协议。

    适用场景:分布式系统的 RPC 解决方案

  • Protobuf,将数据结构以 .proto 文件进行描述,通过代码生成工具可以生成对应数据结构的 POJO 对象和 Protobuf 相关的方法和属性。

    优点:序列化后码流小,性能高、结构化数据存储格式(XML JSON 等)、通过标识字段的顺序,可
    以实现协议的前向兼容、结构化的文档更容易管理和维护。

    缺点:需要依赖于工具生成代码、支持的语言相对较少,官方只支持 Java 、C++ 、python。

    适用场景:对性能要求高的 RPC 调用、具有良好的跨防火墙的访问属性、适合应用层对象的持久化

37. select、poll、epoll 的区别?

select,poll,epoll 都是 操作系统实现 IO 多路复用的机制。 我们知道,I/O 多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。那么这三种机制有什么区别呢

1、支持一个进程所能打开的最大连接数

IO 多路复用名称 能打开的最大连接数
select 单个进程所能打开的最大连接数有 FD_SETSIZE 宏定义,其大小是 1024
当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响。
poll poll 本质上和 select 没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的
epoll 连接数基本上只受限于机器的内存大小

2、FD 剧增后带来的 IO 效率问题

IO 多路复用名称 IO 效率
select 因为每次调用时都会对连接进行线性遍历,所以随着 FD 的增加会造成遍历速度慢的“线性下降性能问题”。
poll 同上
epoll 因为 epoll 内核中实现是根据每个 fd 上的 callback 函数来实现的,只有活跃的 socket 才会主动调用 callback,
所以在活跃socket 较少的情况下,使用 epoll 没有前面两者的线性下降的性能问题,
但是所有 socket 都很活跃的情况下,可能会有性能问题。

3、 消息传递方式

IO 多路复用名称 消息传递方式
select 内核需要将消息传递到用户空间,都需要内核拷贝动作
poll 同上
epoll epoll 通过内核和用户空间共享一块内存来实现

总结:

综上,在选择 select,poll,epoll 时要根据具体的使用场合以及这三种方式的自身特点。

  1. 表面上看 epoll 的性能最好,但是在连接数少并且连接都十分活跃的情况下,selectpoll 的性能可能比 epoll 好,毕竟 epoll 的通知机制需要很多函数回调。
  2. select 低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善。

38. 什么是水平触发(LT)和边缘触发(ET)?

条件触发(Level_triggered):又叫水平触发,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait() 时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!

边缘触发(Edge_triggered):当被监控的文件描述符上有可读写事件发生时,epoll_wait() 会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait() 时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!

简单的理解,也就是一个会一直不停的通知用户,另一个只会通知用户一次

总结:

  • 条件触发:只要满足事件的条件,比如有数据需要读,就一直不断的把这个事件传递给用户
  • 边缘触发:只有第一次满足条件的时候才触发,之后就不会再传递同样的事件了
  • 边缘触发的效率比条件触发的效率高,epoll 支持边缘触发和条件触发,默认是条件触发,select 和 poll 都是条件触发

39. 什么是直接内存

在所有的网络通信和应用程序中,每个 TCP 的 Socket 的内核中都有一个发送缓冲区(SO_SNDBUF)和一个接收缓冲区(SO_RECVBUF),可以使用相关套接字选项来更改该缓冲区大小。

在这里插入图片描述

当某个应用进程调用 write 时,内核从该应用进程的缓冲区中复制所有数据到所写套接字的发送缓冲区。如果该套接字的发送缓冲区容不下该应用进程的所有数据(或是应用进程的缓冲区大于套接字的发送缓冲区,或是套接字的发送缓冲区中已有其他数据),假设该套接字是阻塞的,则该应用进程将被投入睡眠。

内核将不从 write 系统调用返回,直到应用进程缓冲区中的所有数据都复制到套接字发送缓冲区。因此,从写一个 TCP 套接字的 write 调用成功返回仅仅表示我们可以重新使用原来的应用进程缓冲区,并不表明对方的 TCP 或应用进程已接收到数据。

在这里插入图片描述

Java 程序自然也要遵守上述的规则。但在 Java 中存在着堆、垃圾回收等特性,所以在实际的 IO 中,在 JVM 内部的存在着这样一种机制:

  • 在 IO 读写上,如果是使用堆内存,JDK 会先创建一个 DirectBuffer,再去执行真正的写操作。
  • 这是因为,当我们把一个地址通过 JNI 传递给底层的 C 库的时候,有一个基本的要求,就是这个地址上的内容不能失效。
  • 然而,在 GC 管理下的对象是会在 Java 堆中移动的。也就是说,有可能我把一个地址传给底层的 write,但是这段内存却因为 GC 整理内存而失效了。
  • 所以必须要把待发送的数据放到一个 GC 管不着的地方。这就是调用 native 方法之前,数据—定要在堆外内存的原因。

可见,DirectBuffer 并没有节省什么内存拷贝,只是因为 HeapBuffer 必须多做一次拷贝,使用 DirectBuffer 就会少一次内存拷贝。相比没有使用堆内存的 Java 程序,使用直接内存的 Java 程序当然更快一点。

从垃圾回收的角度而言,直接内存不受 GC(新生代的 Minor GC) 影响,只有当执行老年代的 Full GC 时候才会顺便回收直接内存,整理内存的压力也比数据放到 HeapBuffer 要小。

堆外内存的优点和缺点

堆外内存相比于堆内内存有几个优势:

  1. 减少了垃圾回收的工作,因为垃圾回收会暂停其他的工作(可能使用多线程或者时间片的方式,根本感觉不到)
  2. 加快了复制的速度。因为堆内在 flush 到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。

而福之祸所依,自然也有不好的一面:
3. 堆外内存难以控制,如果内存泄漏,那么很难排查
4. 堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合。

40. 什么是零拷贝?

零拷贝(Zero-copy) 技术是指计算机执行操作时,CPU 不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省 CPU 周期和内存带宽。

  • 零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率。
  • 零拷贝技术减少了用户进程地址空间和内核地址空间之间因为CPU上下文切换而带来的开销

可见,零拷贝并不是说不需要拷贝,只是说减少冗余 [不必要] 的拷贝。

Linux 的 I/O 机制与 DMA

在早期计算机中,用户进程需要读取磁盘数据,需要 CPU 中断和 CPU 参与,因此效率比较低,发起 IO 请求,每次的 IO 中断,都带来 CPU 的上下文切换。因此出现了——DMA。

DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。

DMA 控制器,接管了数据读写请求,减少 CPU 的负担。这样一来,CPU 能高效工作了。现代硬盘基本都支持 DMA。

因此,实际 IO 读取,涉及两个过程:

  1. DMA 等待数据准备好,把磁盘数据读取到操作系统内核缓冲区;
  2. 用户进程,将内核缓冲区的数据 copy 到用户空间。

这两个过程,都是阻塞的。

传统数据传送机制

比如:读取文件,再用 socket 发送出去,实际经过四次 copy。伪码实现如下:

buffer = File.read()
Socket.send(buffer)
  1. 第一次:将磁盘文件,读取到操作系统内核缓冲区;
  2. 第二次:将内核缓冲区的数据,copy 到应用程序的 buffer;
  3. 第三步:将 application 应用程序 buffer 中的数据,copy 到 socket 网络发送缓冲区(属于操作系统内核的缓冲区);
  4. 第四次:将 socket buffer 的数据,copy 到网卡,由网卡进行网络传输。

在这里插入图片描述

分析上述的过程,虽然引入 DMA 来接管 CPU 的中断请求,但四次 copy 是存在“不必要的拷贝”的。实际上并不需要第二个和第三个数据副本。应用程序除了缓存数据并将其传输回套接字缓冲区之外什么都不做。相反,数据可以直接从读缓冲区传输到套接字缓冲区。

显然,第二次和第三次数据 copy 其实在这种场景下没有什么帮助反而带来开销,这也正是零拷贝出现的背景和意义。

同时,readsend 都属于系统调用,每次调用都牵涉到两次上下文切换:

在这里插入图片描述

总结下,传统的数据传送所消耗的成本:4 次拷贝,4 次上下文切换。4 次拷贝中,两次是 DMA copy,两次是 CPU copy

Linux 支持的(常见)零拷贝

目的:减少 IO 流程中不必要的拷贝,当然零拷贝需要 OS 支持,也就是需要kernel 暴露 api。

  • mmap 内存映射: 硬盘上文件的位置和应用程序缓冲区(application buffers)进行映射(建立一
    种一一对应关系),由于 mmap() 将文件直接映射到用户空间,所以实际文件读取时根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了一次数据拷贝,不再有文件内容从硬盘拷贝到内核空间的一个缓冲区。

    mmap 内存映射将会经历:3 次拷贝: 1 次 cpu copy,2 次 DMA copy;以及 4 次上下文切换
    在这里插入图片描述

  • sendfile: linux 2.1 支持的 sendfile,当调用 sendfile() 时,DMA 将磁盘数据复制到 kernel buffer,然后将内核中的 kernel buffer 直接拷贝到 socket buffer。在硬件支持的情况下,甚至数据都并不需要被真正复制到 socket 关联的缓冲区内。取而代之的是,只有记录数据位置和长度的描述符被加入到 socket 缓冲区中,DMA 模块将数据直接从内核缓冲区传递给协议引擎,从而消除了遗留的最后一次复制。

    一旦数据全都拷贝到 socket buffer,sendfile() 系统调用将会 return,代表数据转化的完成。socket buffer 里的数据就能在网络传输了。

    sendfile 会经历:3 次拷贝,1 次 CPU copy ,2 次 DMA copy;硬件支持的情况下,则是 2 次拷贝,0 次 CPU copy, 2 次 DMA copy。 以及 2 次上下文切换
    在这里插入图片描述

  • splice: Linux 从 2.6.17 支持 splice,数据从磁盘读取到 OS 内核缓冲区后,在内核缓冲区直接可将其转成内核空间其他数据 buffer,而不需要拷贝到用户空间。

    如下图所示,从磁盘读取到内核 buffer 后,在内核空间直接与 socket buffer 建立 pipe 管道。

    和 sendfile() 不同的是,splice() 不需要硬件支持。

    注意 splice 和 sendfile 的不同,sendfile 是将磁盘数据加载到 kernel buffer 后,需要一次 CPU copy,拷贝到 socket buffer。而 splice 是更进一步,连这个 CPU copy 也不需要了,直接将两个内核空间的 buffer 进行 pipe。

    splice 会经历 2 次拷贝: 0 次 cpu copy 2 次 DMA copy;以及 2 次上下文切换
    在这里插入图片描述

总结 Linux 中零拷贝

最早的零拷贝定义,来源于:

Linux 2.4 内核新增 sendfile 系统调用,提供了零拷贝。磁盘数据通过 DMA 拷贝到内核态 Buffer 后,直接通过 DMA 拷贝到 NIO Buffer(socket buffer),无需 CPU 拷贝。

这也是零拷贝这一说法的来源。这是真正操作系统 意义上的零拷贝(也就是狭义零拷贝)。

但是我们知道,由 OS 内核提供的 操作系统意义上的零拷贝,发展到目前也并没有很多种,也就是这样的零拷贝并不是很多;随着发展,零拷贝的概念得到了延伸,就是目前的减少不必要的数据拷贝都算作零拷贝的范畴。

41. Linux 中有哪些 IO 模型?

Linux 下的五种网络 IO 模型:

  • ① 阻塞式IO(blocking IO)
  • ② 非阻塞式IO(noblocking IO)
  • ③ IO复用式(select、poll和epoll)(IO multiplexing)
  • ④ 信号驱动式IO((signal-driven IO)
  • ⑤ 异步IO(asynchronous IO)

除了第 ⑤ 种,其他四种都属于同步 IO 。

5 个 I/O 模型的比较:
在这里插入图片描述

不同 I/O 模型的区别,其实主要在等待数据和数据复制这两个时间段不同。

42. epoll 高效的原理

  • 当某一进程调用 epoll_create() 方法时,Linux 内核会创建一个 eventpoll 结构体,在内核 cache 里建了个红黑树用于存储以后 epoll_ctl() 传来的 socket 外,还会再建立一个 rdllist 双向链表,用于存储准备就绪的事件,当 epoll_wait() 调用时,仅仅观察这个 rdllist 双向链表里有没有数据即可。有数据就返回,没有数据就 sleep,等到 timeout 时间到后即使链表没数据也返回。

  • 同时,所有添加到 epoll 中的事件都会与设备(如网卡)驱动程序建立回调关系,也就是说相应事件的发生时会调用这里的回调方法。这个回调方法在内核中叫做 ep_poll_callback,它会把这样的事件放到上面的 rdllist 双向链表中。

  • 当调用 epoll_wait() 检查是否有发生事件的连接时,只是检查 eventpoll 对象中的 rdllist 双向链表是否有 epitem 元素而已,如果 rdllist 链表不为空,则这里的事件复制到用户态内存(使用共享内存提高效率)中,同时将事件数量返回给用户。因此 epoll_wait() 效率非常高,可以轻易地处理百万级别的并发连接。

43. 如果要你来设计一个QQ,在网络协议上你会考虑如何设计?

登陆采用 TCP 协议和 HTTP 协议,你和好友之间发送消息,主要采用 UDP 协议,内网传文件采用了 P2P 技术。

总来的说:

  1. 登陆过程,客户端 client 采用 TCP 协议向服务器 server 发送信息,HTTP 协议下载信息。登陆之后,会有一个 TCP 连接来保持在线状态。
  2. 和好友发消息,客户端 client 采用 UDP 协议,但是需要通过服务器转发。腾讯为了确保传输消息的可靠,采用上层协议来保证可靠传输。如果消息发送失败,客户端会提示消息发送失败,并可重新发送。
  3. 如果是在内网里面的两个客户端传文件,QQ 采用的是 P2P 技术,不需要服务器中转。

猜你喜欢

转载自blog.csdn.net/lyabc123456/article/details/133372095