最常见的12道计算机网络面试题

本篇总结了12道最常见的计算机网络面试题,并给出了一些自己的看法,若有不妥之处万望指正。

1. 请详细介绍一下 TCP 的三次握手机制,为什么要三次握手?

在讲三次握手之前首先要介绍 TCP 报文中两个重要的字段:一个是序号字段,另一个是确认号字段,这两个字段将在握手阶段以及整个信息传输过程起到重要作用。

  1. 第一步:客户端 TCP 向服务端的TCP发送一个不带额外数据的特殊 TCP 报文段,该报文段的 SYN 标志位会被置1,所以把它称为 SYN 报文段。这时客户端会选取一个初始序列号(假设为 client_num),并将此编号放置在序号字段中。该报文段会被封装在一个IP数据报中发送给服务器。
  2. 第二步:服务器接收到 SYN 报文段后,会为该 TCP 分配缓存和变量,并发送允许连接的确认报文。在允许连接的报文中,SYN 标志位仍被置为1,确认号字段填的是 client_num + 1的值。最后服务端也会选取一个 server_num 存放到序号字段中,这个报文段称为 SYNACK 报文段。
  3. 第三步:在接收到 SYNACK 报文段后,客户端最后也要向服务端发送一个确认报文,这个报文和前两个不一样,SYN 标志位置0,在确认号字段中填上 server_num + 1的值,并且这个报文段可以携带数据。一旦完成这3个步骤,客户端和服务器之间就可以相互发送包含数据的报文了。

如果不是三次握手,二次两次的话,服务器就不知道客户端是否接收到了自己的 SYNACK 报文段,从而无法建立连接;四次握手就显得多余了。

2. 讲一讲SYN超时,洪泛攻击,以及解决策略

什么 SYN 是洪泛攻击? 在 TCP 的三次握手机制的第一步中,客户端会向服务器发送 SYN 报文段。服务器接收到 SYN 报文段后会为该TCP分配缓存和变量,如果攻击分子大量地往服务器发送 SYN 报文段,服务器的连接资源终将被耗尽,导致内存溢出无法继续服务。

解决策略: 当服务器接受到 SYN 报文段时,不直接为该 TCP 分配资源,而只是打开一个半开的套接字。接着会使用 SYN 报文段的源Id,目的Id,端口号以及只有服务器自己知道的一个秘密函数生成一个 cookie,并把 cookie 作为序列号响应给客户端。

如果客户端是正常建立连接,将会返回一个确认字段为 cookie + 1 的报文段。接下来服务器会根据确认报文的源Id,目的Id,端口号以及秘密函数计算出一个结果,如果结果的值 + 1等于确认字段的值,则证明是刚刚请求连接的客户端,这时候才为该 TCP 分配资源

这样一来就不会为恶意攻击的 SYN 报文段分配资源空间,避免了攻击。

3. 详细介绍一下TCP的四次挥手机制,为什么要有TIME_WAIT状态,为什么需要四次握手?服务器出现了大量CLOSE_WAIT状态如何解决

当客户端要服务器断开连接时,客户端 TCP 会向服务器发送一个特殊的报文段,该报文段的 FIN 标志位会被置1,接着服务器会向客户端发送一个确认报文段。然后服务器也会客户端发送一个 FIN 标志位为1的终止报文段,随后客户端回送一个确认报文段,服务器立即断开连接。客户端等待一段时间后也断开连接。

其实四次挥手的过程是很容易理解的,由于 TCP 协议是全双工的,也就是说客户端和服务端都可以发起断开连接。两边各发起一次断开连接的申请,加上各自的两次确认,看起来就像执行了四次挥手。

为什么要有 TIME_WAIT 状态?因为客户端最后向服务器发送的确认 ACK 是有可能丢失的,当出现超时,服务端会再次发送 FIN 报文段,如果客户端已经关闭了就收不到了。还有一点是避免新旧连接混杂。

大量 CLOSE_WAIT 表示程序出现了问题,对方的 socket 已经关闭连接,而我方忙于读或写没有及时关闭连接,需要检查代码,特别是释放资源的代码,或者是处理请求的线程配置。

详情可参考以下博客:

https://www.cnblogs.com/sunxucool/p/3449068.html

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

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

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

接着就讲讲什么是流量控制窗口,什么是拥塞控制窗口。

先讲流量控制:

TCP 是全双工的,客户端和服务器均可作为发送方或接收方,我们现在假设一个发送方向接收方发送数据的场景来讲解流量控制。首先我们的接收方有一块接收缓存,当数据来到时会先把数据放到缓存中,上层应用等缓存中有数据时就会到缓存中取数据。假如发送方没有限制地不断地向接收方发送数据,接收方的应用程序又没有及时把接收缓存中的数据读走,就会出现缓存溢出,数据丢失的现象,为了解决这个问题,我们引入流量控制窗口。

假设应用程序最后读走的数据序号是 lastByteRead,接收缓存中接收到的最后一个数据序号是 lastByteRcv,接收缓存的大小为 RcvSize,那么必须要满足 lastByteRcv - lastByteRead <= RcvSize 才能保证接收缓存不会溢出,所以我们定义流量窗口为接收缓存剩余的空间,也就是Rcv = RcvSize - (lastByteRcv - lastByteRead)。只要接收方在响应 ACK 的时候把这个窗口的值带给发送方,发送方就能知道接收方的接收缓存还有多大的空间,进而设置滑动窗口的大小。

接着讲解拥塞控制:

拥塞控制是指发送方先设置一个小的窗口值作为发送速率,当成功发包并接收到ACK时,便以指数速率增大发送窗口的大小,直到遇到丢包(超时/三个冗余ACK),才停止并调整窗口的大小。这么做能最大限度地利用带宽,又不至于让网络环境变得太过拥挤。

最终滑动窗口的值将设置为流量控制窗口和拥塞控制窗口中的较小值。

5. 讲一下HTTP与HTTPS的区别

HTTP和HTTPS的主要区别在于HTTP协议传递的是明文数据,而HTTPS传递的是加密过的数据,也就是说HTTPS更具有安全性。也正由HTTPS需要保证安全性,所以它的性能要比HTTP差一点。

单说安全性肯定是不够的,我打算扩展讲一下HTTPS是怎么解决安全性问题的,通过这些HTTP没有机制,反映出HTTPS与HTTP的区别。下面尝试把HTTPS加密的过程推导出来。推导过程不涉及复杂的实现细节:

如何安全地进行数据传输?

假设现在A和B要进行安全的通信,那么究竟怎样才算是安全的通信?很自然地会想到:A和B之间传递数据,这些数据只有A和B才看得懂,中间人就算截取了信息但也看不懂,这才算得上安全。

安全通信的处理手段:

为了能让A和B才能看懂,就必须要对数据进行加密,而且首先想到的就是对称加密。对称加密的意思是A和B各持有一个相同的密钥,它们传递信息时会用密钥给信息加密,在消息到达端给消息解密,完成安全通信。

在对称加密中又会涉及到加密算法的选择问题。现实世界中,通常是多个客户端面向一个服务器的情况,不可能让每个客户端和服务器之间都采用相同的加密算法,如果是这样那和没加密差不多。所以注定每个客户端和服务器之间都会采用不同的加密方式。

如何让每个客户端与服务器之间都采用不同的加密方式?

要想对不同的机器使用不同的加密方式,最直接想到的就是使用随机数。也就说客户端和服务器之间每次都基于一个随机数产生加密算法。(具体实现时为了保证随机,用到还不止一个随机数)

这个产生加密算法的过程称之为协商,现在问题是协商的过程是透明的,也就是说中间人可以截获协商的过程,从而知道我们的加密方式。为了解决这个问题,我们需要对协商的过程进行加密。

如何对协商的过程进行加密?

之所以能来到这一步,是因为我们一开始就选择使用了对称加密,也就说一开始的对称加密导致了现在的问题,所以这时我们不能再使用对称加密了,否则会陷入死循环。

在密码学领域,还有一种加密过程叫非对称加密,它的逻辑是这样的:通信双方一方持有私钥,一方持有公钥,经过私钥加密的信息,都能通过公钥进行解密。但是经过公钥加密的数据,只有私钥可以解密。

按照非对称加密的规则,我们让服务器持有私钥,让客户端持有公钥。这样就能保证客户端给服务器发送消息的时候是安全的(相反,服务器给客户端发送消息就是不安全的),我们可以把协商时重要的逻辑安排在客户端给服务器发送信息的过程中,从而保证了协商过程的安全性。

客户端如何获得公钥?

现在用非对称加密算法解决了协商的安全问题,但是非对称加密的前提是客户端需要获得公钥,这又是一个问题了,客户端与服务器打交道之前是互不知道双方身份的,怎么才能让客户端获得公钥呢?

也就只有两种办法:

  1. 客户端向服务器要公钥
  2. 客户端向一个远程的公共服务器获取公钥

方法2显然是不行的,尚且不说多了一个访问节点,如何找到公共服务器的地址也是一个待解决的问题,所以还是使用方法1。

但是方法1存在一个问题:如果中间人把服务器发送给客户端的公钥调包了怎么办?也就是说客户端无法知道发送公钥的是否是正真的服务器。

引入第三方机构解决问题

客户端无法辨识服务端和中间人的问题称为“身份验证”问题,也就是说我们需要为服务器向客户端发送公钥的过程进行加密。

这下完了,之前我们因遇到对称加密的瓶颈选择了非对称加密,现在使用非对称加密也遇到了瓶颈。显然这两种加密方式都是不可用的了,否则会再次陷入死循环。

接下来我们只好通过第三方机构的介入,解决这个问题。首先我们自己保存有第三方权威机构的公钥,然后第三方机构使用私钥对服务器将要发送给客户端的公钥进行加密,客户端接收到这个经加密的公钥后(数字证书),就能通过自己保存的第三方机构公钥进行解密。

到这里为止,我们解释了HTTPS中使用到的对称加密,非对称加密,CA,数字证书的概念,但是还差一个叫数字签名的概念没有解释。

在现实生活中,CA不单止会给我们正常公司发放证书,还会给中间人的坏公司发放证书,如果中间人把发放的证书调包了怎么办?这时我们仍能用CA的私钥进行解密,但是证书已经被调包了。

那么客户端怎样验证证书的真伪呢?答案是证书本身会告诉客户端如何辨认真伪。比方说证书上面有一个证书编号,还有一个如何计算证书编号的方法,客户端可以根据计算证书编号的方法计算出自己要获得的证书的编号,然后把这个编号和证书上的编号进行比对,如果一样证明没有被调包。

这里的证书编号指的就是数字签名,证书指的就是数字证书。

总结一下HTTPS:HTTPS想要保证客户端与服务器之间的通信安全,就得使用对称加密算法进行加密。协商对称加密算法的过程通过非对称加密算法来保证。在非对称加密算法中,客户端获得公钥的过程需要第三方机构(CA)通过颁发数字证书保证安全性。

总得来说通过这一系列机制协商出了一个对称加密算法后,客户端与服务器之间就能通过该算法进行安全的通信了。

6. 讲一讲TCP和UDP各有什么特点,两者有什么区别

  1. TCP 是面向连接的,使用 TCP 连接需要经过三次握手,断开连接需要经过四次挥手。相比于 TCP,UDP 则是面向无连接的,它像一个随时可以进行通信的协议,只要接收方创建了 Socket,设置了监听端口,就能接收到到达的 UDP 包,并且能随时建立 Socket 进行发包。
  2. TCP 可以对数据的发送速率作控制,它具有流量控制和拥塞控制机制,通过控制滑动窗口的大小控制数据的发送速率。而 UDP 理论上是可以无限地向网络中发数据包的,它的发送速率取决它的应用程序设置。
  3. TCP 保证可靠的数据传输,它根据速率控制,重传控制,还有数据校验保证提供可靠的数据传输服务。而 UDP 只是保证尽力而为地传输数据,并不保证数据的可靠性。
  4. TCP 的报文段头部占20各字节,比 UDP 的报文段头部多12个字节。消耗的资源更多。由于建立连接有握手的机制,TCP 的使用效率会比 UDP 要低一些。

7. 在地址栏键入URL后,网络世界发生了什么?

个人觉得这个问题还可以扩展一下,试想一下在键入 URL 之前,也就是刚开完机的时候,需要联网,然后才能上网。这个阶段包括了获取本机 IP 地址,获取 DNS 服务器 IP 地址,获得网关路由器 IP 和 MAC 地址等操作,把这些一起答上去会不会好一些?以下是回答:

获取本机IP地址,DNS服务器地址,网关路由器地址

首先我们需要准备一个 DHCP 报文,封装在一个 UDP 报文段中,里面包括本机端口号68和目的端口号67,然后到网络层封装成数据包里面包括了本机的初始IP0.0.0.0,和广播地址255.255.255.255。接着到链路层封装成链路层帧。里面包括广播地址和本机网卡的MAC地址。最后发送到本地局域网中

这个数据包最终会被局域网中的 DHCP 服务器发现(有可能有多个 DHCP 服务器),DHCP 服务器会把可用的 IP 地址返回给我们的主机。然后操作系统选择一个 IP 地址并发送给 DHCP 服务器,最后 DHCP 服务器会返回一个包含本机 IP,DNS 服务器 IP,网关路由器 IP 的报文。

接下来我们需要通过网关路由器的 IP 地址去获得网关路由器的 MAC 地址,这样我们才可以把获取网站 IP 的 DNS 请求报文由网关发送给 DNS 服务器。这时候我们需要准备一个 ARP 请求报文,请求获取网关路由器的 MAC 地址,这个报文同样是以广播的方式发送到局域网中,网关路由器接受到请求报文就会把自己的 MAC 地址返回给本机。

获取域名的IP地址

接下来一切都准备好了,可以开始讲键入URL之后的事情了:

首先我们要访问 DNS 服务器获得网站对应的 IP 地址,这时我们需要把 DNS 报文封装到一个 UDP 报文中,进而封装到网络层的数据包中,填上源 IP,目的 DNS 服务器 IP 地址。接着封装链路层,填上网卡 MAC 地址和网关路由器 MAC 地址。接下来这个 DNS 请求报文就会经网关路由器发送给 DNS 服务器。

我们假设 DNS 服务器缓存有该网站的 IP 地址,(如果没有缓存会进一步向更高级的DNS服务器索要IP地址)。接着 DNS 服务器会返回该域名的 IP 地址。

三次握手建立TCP连接

拿到了该网站的IP地址后就可以与该网站的服务器建立 TCP 连接了。建立 TCP 连接需要经过三次握手,过程如下:(更详细的过程在开头)

  1. 本机的TCP首先生成一个不带任何数据,SYN标志位为1,序号字段假设为client_num的TCP报文,经过下层一系列网络栈后发送给目的ip服务器。
  2. 该服务器接受到TCP请求报文后,会回应一个同意连接的TCP报文,这个报文的SYN标志位也会被置1,序号字段假设为server_num,ACK响应字段为client_num + 1。
  3. 接受到同意连接的报文后,我方主机会进行响应,这次的TCP报文SYN位会被置0,序号字段为client_num + 1,ACK响应字段为server + 1。并且这次的响应报文是可以携带数据的。

建立连接后进行数据交互

在三次握手建立连接后,本机就可以向服务器发送 HTTP 请求了,服务器接受到了请求会做出响应的响应,把请求的数据发送给本机浏览器,最终浏览器把服务器响应的数据渲染显示出来,我们就看到了五彩缤纷的网页。

8. HTTP的常见状态码有哪些,代表什么含义?

首先状态码的开头不同代表不同的类型:
1xx:代表指示信息,表示请求已接收,继续处理
2xx:代表成功,表示请求已被成功接收,理解,接受
3xx:重定向,表示完成请求必须进行进一步的操作
4xx:客户端错误,请求有语法错误或请求无法实现
5xx:服务器端错误,服务器未能实现合法的请求

常见状态码:
200 OK:正常返回信息
400 Bad Request:客户端请求有语法错误,不能被服务器所理解
403 Forbidden:服务器收到请求,但是拒绝提供服务
404 Not Found:请求资源不存在,输入了错误的URL
500 Internal Server Error:服务器发生不可预期错误
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常

9. GET请求和POST请求的区别

  1. 从 HTTP 报文层面来看,GET 请求将信息放在 URL,POST 将请求信息放在请求体中。这一点使得 GET 请求携带的数据量有限,因为 URL 本身是有长度限制的,而 POST 请求的数据存放在报文体中,因此对大小没有限制。而且从形式上看,GET 请求把数据放 URL 上感觉不太安全,而 POST 请求把数据放在请求体里似乎安全一些。实际上想要获取 POST 请求中的内容还是很容易的,因此两者在安全性上其实没有太大差异,想要实现安全的信息传输还是得靠 HTTPS
  2. 从数据库层面来看,GET 符合幂等性和安全性,而 POST 请求不符合。这个其实和 GET/POST 请求的作用有关。按照 HTTP 的约定,GET 请求用于查看信息,不会改变服务器上的信息;而 POST 请求用来改变服务器上的信息。正因为 GET 请求只查看信息,不改变信息,对数据库的一次或多次操作获得的结果是一致的,认为它符合幂等性。安全性是指对数据库操作没有改变数据库中的数据。
  3. 从其他层面来看,GET 请求能够被缓存,GET 请求能够保存在浏览器的浏览记录里,GET 请求的 URL 能够保存为浏览器书签。这些都是 POST 请求所不具备的。缓存是 GET 请求被广泛应用的根本,他能够被缓存也是因为它的幂等性和安全性,除了返回结果没有其他多余的动作,因此绝大部分的 GET 请求都被 CDN 缓存起来了,大大减少了 Web 服务器的负担。

10. 什么是Cookie,Cookie的使用过程是怎么样的?

由于 http 协议是无状态协议,如果客户通过浏览器访问 web 应用时没有一个保存用户访问状态的机制,那么将不能持续跟踪应用的操作。比如当用户往购物车中添加了商品,web 应用必须在用户浏览别的商品的时候仍保存购物车的状态,以便用户继续往购物车中添加商品。

cookie 是浏览器的一种缓存机制,它可用于维持客户端与服务器端之间的会话。由于下面一题会讲到session,所以这里要强调cookie会将会话保存在客户端(session则是把会话保存在服务端)

这里以最常见的登陆案例讲解cookie的使用过程:

  1. 首先用户在客户端浏览器向服务器发起登陆请求
  2. 登陆成功后,服务端会把登陆的用户信息设置 cookie 中,返回给客户端浏览器
  3. 客户端浏览器接收到 cookie 请求后,会把 cookie 保存到本地(可能是内存,也可能是磁盘,看具体使用情况而定)
  4. 以后再次访问该 web 应用时,客户端浏览器就会把本地的 cookie 带上,这样服务端就能根据 cookie 获得用户信息了

11. 什么是session,有哪些实现session的机制?

session 是一种维持客户端与服务器端会话的机制。但是与 cookie 把会话信息保存在客户端本地不一样,session 把会话保留在浏览器端。

我们同样以登陆案例为例子讲解 session 的使用过程:

  1. 首先用户在客户端浏览器发起登陆请求
  2. 登陆成功后,服务端会把用户信息保存在服务端,并返回一个唯一的 session 标识给客户端浏览器。
  3. 客户端浏览器会把这个唯一的 session 标识保存在起来
  4. 以后再次访问 web 应用时,客户端浏览器会把这个唯一的 session 标识带上,这样服务端就能根据这个唯一标识找到用户信息。

看到这里可能会引起疑问:把唯一的 session 标识返回给客户端浏览器,然后保存起来,以后访问时带上,这难道不是 cookie 吗?

没错,session 只是一种会话机制,在许多 web 应用中,session 机制就是通过 cookie 来实现的。也就是说它只是使用了 cookie 的功能,并不是使用 cookie 完成会话保存。与 cookie 在保存客户端保存会话的机制相反,session 通过 cookie 的功能把会话信息保存到了服务端。

进一步地说,session 是一种维持服务端与客户端之间会话的机制,它可以有不同的实现。以现在比较流行的小程序为例,阐述一个 session 的实现方案:

  1. 首先用户登陆后,需要把用户登陆信息保存在服务端,这里我们可以采用 redis。比如说给用户生成一个 userToken,然后以 userId 作为键,以 userToken 作为值保存到 redis 中,并在返回时把 userToken 带回给小程序端。
  2. 小程序端接收到 userToken 后把它缓存起来,以后每当访问后端服务时就把 userToken 带上。
  3. 在后续的服务中服务端只要拿着小程序端带来的 userToken 和 redis 中的 userToken 进行比对,就能确定用户的登陆状态了。

12. session和cookie有什么区别

经过上面两道题的阐述,这道题就很清晰了

  1. cookie 是浏览器提供的一种缓存机制,它可以用于维持客户端与服务端之间的会话
  2. session 指的是维持客户端与服务端会话的一种机制,它可以通过 cookie 实现,也可以通过别的手段实现。
  3. 如果用 cookie 实现会话,那么会话会保存在客户端浏览器中
  4. 而 session 机制提供的会话是保存在服务端的。

参考:
《计算机网络:自顶向下方法》
https://www.cnblogs.com/zhangshitong/p/6478721.html
https://www.cnblogs.com/jiangxinyang/p/8453827.html

Java Geek Tech wechat
欢迎订阅 Java 极客技术,这里分享关于 Java 的一切。