前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >长篇tcp 网络,汇集大小厂经典问题

长篇tcp 网络,汇集大小厂经典问题

作者头像
八点半的Bruce、D
发布2022-12-05 10:04:34
3920
发布2022-12-05 10:04:34
举报
文章被收录于专栏:八点半技术站八点半技术站

作者:Bruce.D

github:https://github.com/doukoi-BDB

今日主题:

1、大小厂,面试中 tcp 中的问题;

2、偶尔来个故事、还是技术服务读友;

01

开场

根据公众号读友们的反馈,年底了。该分享分享一些大小厂核心面试【模块】点了,特意总结了周围一波朋友的【 tcp 网络】的面试点。因此本篇有点长,建议收藏慢慢看,你用的到,我也用的到。

除此之外、按照社群各位反馈,在补充几个微信支付的案例、类库,本周也会更新,更新后会特意发一篇文章内容更新公告。

02

常见tcp问题

分为3块进行讲解:tcp 的基础问题、tcp的连接问题、tcp的断开问题。 下面我的回答会相对简化一些,有需要的朋友,可以跟着问题去搜索更深入的细节。

模块一:tcp 的基础问题

1、 什么是 tcp ?

2、什么是tcp 连接?

3、tcp 头部格式?

4、tcp 最大链接数是多少?

5、udp 与 tcp 的区别?

6、udp与tcp的场景?

7、为什么tcp 头部没有【包长度】字段呢?

模块二:tcp 的连接建立问题

8、 tcp 三次握手过程&状态变化?

9、 linux系统中如何查看tcp状态?

10、为什么是3次握手?而不是其他次数?

11、tcp每次链接为什么初始化序列号都不一致呢?

12、初始化序列号 如何产生的?

模块三:tcp 的连接断开问题

13、握手中断,会发生什么?

14、tcp 四次挥手的过程&状态变化?

15、为何time_wait 等待时间是2msl?

16、为什么需要time_wait 这个状态?

17、time_wait 过多什么危害?

18、如何优化time_wait?

解答: 1、什么是 tcp ?

tcp 是面向连接、可靠的、基于字节流的传输层通信协议。

对于面向连接:一对一才能连接,不像udp 可以一个主机同事向多个主机发送消息。对于可靠的:无论网络链路种出现了怎么样的变化,tcp都可以保证一个报文一定能够达到接收端。对于字节流:用户消息通过tcp协议传输时,消息可能会被操作系统分成多个tcp报文,接收方如果不知道消息的边界,是无法读出有效用户信息的。

2、什么是 tcp 连接?

官方定义连接:上述可靠性和流量控制机制要求TCP初始化并维护每个数据流的特定状态信息。这些信息的组合,包括套接字、序列号和窗口大小,称为连接。

通俗解释连接:用于保证可靠性+流量控制维护某些状态信息的组合,包括(socket、序列号、窗口大小)俗称连接。

简单解释:socket:ip地址+端口号组成;序列号:用来解决乱序问题;窗口大小:用来做流量控制。

3、tcp头部格式?

序列号(32位)、确认应答号(32位),控制位(ack、rst、syn、fin)。序列号:上面也说了,解决网络包乱序问题;确认应答号:用来解决丢包问题(例如:发送者收到数据序列号后,发送端收到确认应答后,会认为前面传输都是正常);控制位:单独解释一波:(ack:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 );(rst:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接);(syn:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。);(fin:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。)

4、tcp最大连接接数是多少?

服务端通常固定在某个本地端口监听,等待客户端链接请求。因此客户端 ip + 端口也是可变的,所以上公式:最大tcp 连接数 = 客户端ip数 * 客户端端口数 。

仅仅是理论上限:对 IPv4,客户端的 IP 数最多为 2 的 32 次方,客户端的端口数最多为 2 的 16 次方,也就是服务端单机最大 TCP 连接数,约为 2 的 48 次方。实际影响:文件描述符限制、系统级、用户级、进程级。

5、udp与tcp的区别?

区别:1)连接:tcp是面向连接的传输层协议,传输数据前先要建立连接;udp是不需要连接,即刻传输数据。2)服务对象:tcp是一对一的两点服务,即一条连接只有两个端点。udp是支持一对一、一对多、多对多的交互通信。3)可靠性:tcp是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。udp是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议。4)流量控制:tcp有拥塞控制和流量控制机制,保证数据传输的安全性。udp:则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。5)首部开销:tcp首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。udp首部只有 8 个字节,并且是固定不变的,开销较小。6)传输方式:tcp是流式传输,没有边界,但保证顺序和可靠。udp:是一个包一个包的发送,是有边界的,但可能会丢包和乱序。

6、udp与tcp的场景?

1)tcp场景:由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:FTP 文件传输;HTTP / HTTPS;2)udp场景:由于 UDP 面向无连接,它可以随时发送数据,再加上 UDP 本身的处理既简单又高效,因此经常用于:包总量较少的通信,如 DNS 、SNMP 等;视频、音频等多媒体通信;广播通信

7、为什么tcp 头部没有【包长度】字段呢?

原因是 TCP 有可变长的「选项」字段,而 UDP 头部长度则是不会变化的,无需多一个字段去记录 UDP 的首部长度。

8、tcp 三次握手过程&状态变化?

  • 一开始,客户端和服务端都处于 CLOSE 状态。先是服务端主动监听某个端口,处于 LISTEN 状态
  • 客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的「序号」字段中,同时把 SYN 标志位置为 1 ,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 SYN-SENT 状态。
  • 服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYNACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。
  • 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次「确认应答号」字段填入 server_isn + 1 ,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于 ESTABLISHED 状态。
  • 服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态。

从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的,这也是面试常问的题。

一旦完成三次握手,双方都处于 ESTABLISHED 状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了。

9、linux系统中如何查看tcp状态?

TCP 的连接状态查看,在 Linux 可以通过 netstat -napt 命令查看。

10、为什么是3次握手?而不是其他次数?

笼统回答:“因为三次握手才能保证双方具有接收和发送的能力。”

精细回答:我们知道了什么是 TCP 连接:用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗口大小称为连接。所以,重要的是为什么三次握手才可以初始化Socket、序列号和窗口大小并建立 TCP 连接。接下来,以三个方面分析三次握手的原因:

  • 三次握手才可以阻止重复历史连接的初始化(主要原因)首要原因是为了防止旧的重复连接初始化造成混乱。
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

11、tcp每次链接为什么初始化序列号都不一致呢?

主要原因有两个方面:1)为了防止历史报文被下一个相同四元组的连接接收(主要方面);2)为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收;

12、初始化序列号 如何产生的?

起始 ISN 是基于时钟的,每 4 微秒 + 1,转一圈要 4.55 个小时。

RFC793 提到初始化序列号 ISN 随机生成算法:ISN = M + F(localhost, localport, remotehost, remoteport)。

  • M 是一个计时器,这个计时器每隔 4 微秒加 1。
  • F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择。

可以看到,随机数是会基于时钟计时器递增的,基本不可能会随机成一样的初始化序列号。

13、握手中断,会发生什么?

客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「超时重传」机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的。

当客户端超时重传 3 次 SYN 报文后,由于 tcp_syn_retries 为 3,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次握手(SYN-ACK 报文),那么客户端就会断开连接。

14、tcp 四次挥手的过程&状态变化?

双方都可以主动断开连接,断开连接后主机中的「资源」将被释放,四次挥手的过程如下图:

  • 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
  • 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
  • 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
  • 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
  • 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
  • 服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
  • 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。

你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。

这里一点需要注意是:主动关闭连接的,才有 TIME_WAIT 状态。

15、为何time_wait 等待时间是2msl?

网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。

MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。

16、为什么需要time_wait 这个状态?

主动发起关闭连接的一方,才会有 TIME-WAIT 状态。

需要 TIME-WAIT 状态,主要是两个原因:

  • 防止历史连接中的数据,被后面相同四元组的连接错误的接收;
  • 保证「被动关闭连接」的一方,能被正确的关闭;

17、time_wait 过多什么危害?

过多的 TIME-WAIT 状态主要的危害有两种:

  • 第一是占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等;
  • 第二是占用端口资源,端口资源也是有限的,一般可以开启的端口为 32768~61000,也可以通过 net.ipv4.ip_local_port_range参数指定范围。

客户端和服务端 TIME_WAIT 过多,造成的影响是不同的。

如果客户端(主动发起关闭连接方)的 TIME_WAIT 状态过多,占满了所有端口资源,那么就无法对「目的 IP+ 目的 PORT」都一样的服务端发起连接了,但是被使用的端口,还是可以继续对另外一个服务端发起连接的。具体可以看我这篇文章:客户端的端口可以重复使用吗?(opens new window)

因此,客户端(发起连接方)都是和「目的 IP+ 目的 PORT 」都一样的服务端建立连接的话,当客户端的 TIME_WAIT 状态连接过多的话,就会受端口资源限制,如果占满了所有端口资源,那么就无法再跟「目的 IP+ 目的 PORT」都一样的服务端建立连接了。

不过,即使是在这种场景下,只要连接的是不同的服务端,端口是可以重复使用的,所以客户端还是可以向其他服务端发起连接的,这是因为内核在定位一个连接的时候,是通过四元组(源IP、源端口、目的IP、目的端口)信息来定位的,并不会因为客户端的端口一样,而导致连接冲突。

如果服务端(主动发起关闭连接方)的 TIME_WAIT 状态过多,并不会导致端口资源受限,因为服务端只监听一个端口,而且由于一个四元组唯一确定一个 TCP 连接,因此理论上服务端可以建立很多连接,但是 TCP 连接过多,会占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等。

18、如何优化time_wait?

这里给出优化 TIME-WAIT 的几个方式,都是有利有弊:

  • 打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项;
  • net.ipv4.tcp_max_tw_buckets
  • 程序中使用 SO_LINGER ,应用强制使用 RST 关闭。

方式一:net.ipv4.tcp_tw_reuse 和 tcp_timestamps

如下的 Linux 内核参数开启后,则可以复用处于 TIME_WAIT 的 socket 为新的连接所用。有一点需要注意的是,tcp_tw_reuse 功能只能用客户端(连接发起方),因为开启了该功能,在调用 connect() 函数时,内核会随机找一个 time_wait 状态超过 1 秒的连接给新的连接复用。

代码语言:javascript
复制
net.ipv4.tcp_tw_reuse = 1

使用这个选项,还有一个前提,需要打开对 TCP 时间戳的支持,即

代码语言:javascript
复制
net.ipv4.tcp_timestamps=1(默认即为 1)

这个时间戳的字段是在 TCP 头部的「选项」里,它由一共 8 个字节表示时间戳,其中第一个 4 字节字段用来保存发送该数据包的时间,第二个 4 字节字段用来保存最近一次接收对方发送到达数据的时间。

由于引入了时间戳,我们在前面提到的 2MSL 问题就不复存在了,因为重复的数据包会因为时间戳过期被自然丢弃。

方式二:net.ipv4.tcp_max_tw_buckets

这个值默认为 18000,当系统中处于 TIME_WAIT 的连接一旦超过这个值时,系统就会将后面的 TIME_WAIT 连接状态重置,这个方法比较暴力。

方式三:程序中使用 SO_LINGER

我们可以通过设置 socket 选项,来设置调用 close 关闭连接行为。

结尾:

《UNIX网络编程》一书中却说道:TIME_WAIT 是我们的朋友,它是有助于我们的,不要试图避免这个状态,而是应该弄清楚它。

如果服务端要避免过多的 TIME_WAIT 状态的连接,就永远不要主动断开连接,让客户端去断开,由分布在各处的客户端去承受 TIME_WAIT。

github 仓库代码11月9~10日更新,敬请期待,仓库地址在文章顶部开头标注。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 八点半技术站 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档