4516 字
23 分钟
计算机网络基础1227

重要程度#

  • 必须会的:三次握手必须会,netstat命令,为什么不是两次和四次

第三次握手丢失会重传吗#

  • 正常在通信,一个ACK丢了,后面发的包带的ACK可以替代掉前面丢失的

    • 因为ACK是告诉对方前面的包我收到了,是一个应答,如果一直在通信,后面来的包可以确认对方收到前目的包了,虽然ACK丢了但也确认了,已经干了ACK的活了,不用强制再发一个ACK了
  • 很久没通信的话,一个ACK丢了,会发生重传

什么是SYN攻击#

  • 增大半连接队列:扬汤止沸,但时间解决问题
  • 增大内核网卡缓存?
  • 减少重传次数:让半连接队列清理的更快,也是短时间解决问题
  • 使用SYN cookie:给你个值,如果你能给我带回来,说明你完成前两次握手了,带不回来就说明你不是一个正常连接

3.3 TCP连接断开#

3.3.1 TCP四次挥手过程#

3.3.2 为什么挥手需要四次#

3.3.3 第一次挥手丢失会有什么问题#

3.3.4 第二次挥手#

3.3.5 第三次挥手#

3.3.6 第四次挥手丢失#

3.3.7 为什么TIME_WAIT等待时间是2MSL#

  • 重要

  • 经过最大路由数TTL

close()和shutdown()#

  • 调用close(),事情交给内核去做
  • 调用shutdown(),将四次挥手分成两个部分

3.3.8 为什么需要 TIME_WAIT#

3.3.9 TIME_WAIT过多#

3.3.10 如何优化TIME_WAIT#

3.3.12 服务器出现大量#

3.3.13 如果已经建立了连接,但是客户端突然出现故障了怎么办?#

  • 客户端出现故障指的是客户端的主机发生了宕机,或者断电的场景。发生这种情况的时候,如果服务端一直不会发送数据给客户端,那么服务端是永远无法感知到客户端宕机这个事件的,也就是服务端的 TCP 连接将一直处于 ESTABLISH 状态,占用着系统资源

  • 为了避免这种情况,TCP 搞了个==保活机制==(心跳检测)。这个机制的原理是这样的:

    • 定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序

    • 在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:

      Terminal window
      net.ipv4.tcp_keepalive_time=7200
      net.ipv4.tcp_keepalive_intvl=75
      net.ipv4.tcp_keepalive_probes=9
      • tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制
      • tcp_keepalive_intvl=75:表示每次检测间隔 75 秒
      • tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接
  • 也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接

20231227160324

  • 注意,应用程序若想使用 TCP 保活机制需要通过 socket 接口设置 SO_KEEPALIVE 选项才能够生效,如果没有设置,那么就无法使用 TCP 保活机制

  • 如果开启了 TCP 保活,需要考虑以下几种情况:

    • 第一种,对端程序是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来
    • 第二种,对端主机宕机并重启。当 TCP 保活的探测报文发送给对端后,对端是可以响应的,但由于没有该连接的有效信息,会产生一个 RST 报文,这样很快就会发现 TCP 连接已经被重置
    • 第三种,是对端主机宕机(注意不是进程崩溃,进程崩溃后操作系统在回收进程资源的时候,会发送 FIN 报文,而主机宕机则是无法感知的,所以需要 TCP 保活机制来探测对方是不是发生了主机宕机),或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡
  • TCP 保活的这个机制检测的时间是有点长,我们可以自己在应用层实现一个心跳机制

  • 比如,web 服务软件一般都会提供 keepalive_timeout 参数,用来指定 HTTP 长连接的超时时间。如果设置了 HTTP 长连接的超时时间是 60 秒,web 服务软件就会启动一个定时器,如果客户端在完成一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,就会触发回调函数来释放该连接

20231227160928

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

  • TCP 的连接信息是由==内核==维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并==不需要进程的参与==,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程
  • 我自己做了个实验,使用 kill -9 来模拟进程崩溃的情况,发现在 kill 掉进程后,服务端会发送 FIN 报文,与客户端进行四次挥手

3.3.15 拔掉网线后, 原本的 TCP 连接还存在吗?#

  • 拔掉网线几秒,再插回去,原本的 TCP 连接还存在吗?
  • 可能有的同学会说,网线都被拔掉了,那说明物理层被断开了,那在上层的传输层理应也会断开,所以原本的 TCP 连接就不会存在的了。就好像, 我们拨打有线电话的时候,如果某一方的电话线被拔了,那么本次通话就彻底断了
  • 真的是这样吗?
    • 上面这个逻辑就有问题。问题在于,错误的认为拔掉网线这个动作会影响传输层,事实上并不会影响
    • 实际上,TCP 连接在 Linux 内核中是一个名为 struct socket 的结构体,该结构体的内容包含 TCP 连接的状态等信息。当拔掉网线的时候,操作系统并不会变更该结构体的任何内容,所以 TCP 连接的状态也不会发生改变
  • 我在我的电脑上做了个小实验,我用 ssh 终端连接了我的云服务器,然后我通过断开 wifi 的方式来模拟拔掉网线的场景,此时查看 TCP 连接的状态没有发生变化,还是处于 ESTABLISHED 状态

20231227161514

  • 通过上面这个实验结果,我们知道了,拔掉网线这个动作并不会影响 TCP 连接的状态
  • 接下来,要看拔掉网线后,双方做了什么动作
  • 所以, 针对这个问题,要分场景来讨论:
    • 拔掉网线后,有数据传输;
    • 拔掉网线后,没有数据传输;

拔掉网线后,有数据传输#

  • 在客户端拔掉网线后,服务端向客户端发送的数据报文会得不到任何的响应,在等待一定时长后,服务端就会触发超时重传机制,重传未得到响应的数据报文
  • 如果在服务端重传报文的过程中,客户端刚好把网线插回去了,由于拔掉网线并不会改变客户端的 TCP 连接状态,并且还是处于 ESTABLISHED 状态,所以这时客户端是可以正常接收服务端发来的数据报文的,然后客户端就会回 ACK 响应报文
  • 此时,客户端和服务端的 TCP 连接依然存在的,就感觉什么事情都没有发生
  • 但是,如果如果在服务端重传报文的过程中,客户端一直没有将网线插回去,服务端超时重传报文的次数达到一定阈值后,内核就会判定出该 TCP 有问题,然后通过 Socket 接口告诉应用程序该 TCP 连接出问题了,于是服务端的 TCP 连接就会断开
  • 而等客户端插回网线后,如果客户端向服务端发送了数据,由于服务端已经没有与客户端相同四元祖的 TCP 连接了,因此服务端内核就会回复 RST 报文,客户端收到后就会释放该 TCP 连接
  • 此时,客户端和服务端的 TCP 连接都已经断开了

那 TCP 的数据报文具体重传几次呢?#

  • 在 Linux 系统中,提供了一个叫 tcp_retries2 配置项,默认值是 15,如下图:

20231227161932

  • 这个内核参数是控制,在 TCP 连接建立的情况下,超时重传的最大次数
  • 不过 tcp_retries2 设置了 15 次,并不代表 TCP 超时重传了 15 次才会通知应用程序终止该 TCP 连接,内核会根据 tcp_retries2 设置的值,计算出一个 timeout(如果 tcp_retries2 =15,那么计算得到的 timeout = 924600 ms),如果重传间隔超过这个 timeout,则认为超过了阈值,就会停止重传,然后就会断开 TCP 连接
  • 在发生超时重传的过程中,每一轮的超时时间(RTO)都是倍数增长的,比如如果第一轮 RTO 是 200 毫秒,那么第二轮 RTO 是 400 毫秒,第三轮 RTO 是 800 毫秒,以此类推
  • 而 RTO 是基于 RTT(一个包的往返时间) 来计算的,如果 RTT 较大,那么计算出来的 RTO 就越大,那么经过几轮重传后,很快就达到了上面的 timeout 值了
  • 举个例子,如果 tcp_retries2 =15,那么计算得到的 timeout = 924600 ms,如果重传总间隔时长达到了 timeout 就会停止重传,然后就会断开 TCP 连接:
    • 如果 RTT 比较小,那么 RTO 初始值就约等于下限 200ms,也就是第一轮的超时时间是 200 毫秒,由于 timeout 总时长是 924600 ms,表现出来的现象刚好就是重传了 15 次,超过了 timeout 值,从而断开 TCP 连接
    • 如果 RTT 比较大,假设 RTO 初始值计算得到的是 1000 ms,也就是第一轮的超时时间是 1 秒,那么根本不需要重传 15 次,重传总间隔就会超过 924600 ms
  • 最小 RTO 和最大 RTO 是在 Linux 内核中定义好了:
Terminal window
#define TCP_RTO_MAX ((unsigned)(120*HZ))
#define TCP_RTO_MIN ((unsigned)(HZ/5))
  • Linux 2.6+ 使用 1000 毫秒的 HZ,因此TCP_RTO_MIN约为 200 毫秒,TCP_RTO_MAX约为 120 秒
  • 如果tcp_retries设置为15,且 RTT 比较小,那么 RTO 初始值就约等于下限 200ms,这意味着它需要 924.6 秒才能将断开的 TCP 连接通知给上层(即应用程序),每一轮的 RTO 增长关系如下表格: 20231227162215

拔掉网线后,没有数据传输#

  • 针对拔掉网线后,没有数据传输的场景,还得看是否开启了 TCP keepalive 机制 (TCP 保活机制)
  • 如果没有开启 TCP keepalive 机制,在客户端拔掉网线后,并且双方都没有进行数据传输,那么客户端和服务端的 TCP 连接将会一直保持存在
  • 而如果开启了 TCP keepalive 机制,在客户端拔掉网线后,即使双方都没有进行数据传输,在持续一段时间后,TCP 就会发送探测报文:
    • 如果对端是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来
    • 如果对端主机宕机(注意不是进程崩溃,进程崩溃后操作系统在回收进程资源的时候,会发送 FIN 报文,而主机宕机则是无法感知的,所以需要 TCP 保活机制来探测对方是不是发生了主机宕机),或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡
  • 所以,TCP 保活机制可以在双方没有数据交互的情况,通过探测报文,来确定对方的 TCP 连接是否存活

TCP keepalive 机制具体是怎么样的?#

  • 这个机制的原理是这样的:
    • 定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序
  • 在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:
Terminal window
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
  • tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制

  • tcp_keepalive_intvl=75:表示每次检测间隔 75 秒

  • tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接

  • 也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接 20231227160324

  • 注意,应用程序若想使用 TCP 保活机制需要通过 socket 接口设置 SO_KEEPALIVE 选项才能够生效,如果没有设置,那么就无法使用 TCP 保活机制

TCP keepalive 机制探测的时间也太长了吧?#

  • 对的,是有点长。

  • TCP keepalive 是 TCP 层(内核态) 实现的,它是给所有基于 TCP 传输协议的程序一个兜底的方案

  • 实际上,我们应用层可以自己实现一套探测机制,可以在较短的时间内,探测到对方是否存活

  • 比如,web 服务软件一般都会提供 keepalive_timeout 参数,用来指定 HTTP 长连接的超时时间。如果设置了 HTTP 长连接的超时时间是 60 秒,web 服务软件就会启动一个定时器,如果客户端在完后一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,就会触发回调函数来释放该连接

总结#

  • 客户端拔掉网线后,并不会直接影响 TCP 连接状态。所以,拔掉网线后,TCP 连接是否还会存在,关键要看拔掉网线之后,有没有进行数据传输

  • 有数据传输的情况:

    • 在客户端拔掉网线后,如果服务端发送了数据报文,那么在服务端重传次数没有达到最大值之前,客户端就插回了网线,那么双方原本的 TCP 连接还是能正常存在,就好像什么事情都没有发生
    • 在客户端拔掉网线后,如果服务端发送了数据报文,在客户端插回网线之前,服务端重传次数达到了最大值时,服务端就会断开 TCP 连接。等到客户端插回网线后,向服务端发送了数据,因为服务端已经断开了与客户端相同四元组的 TCP 连接,所以就会回 RST 报文,客户端收到后就会断开 TCP 连接。至此, 双方的 TCP 连接都断开了
  • 没有数据传输的情况:

    • 如果双方都没有开启 TCP keepalive 机制,那么在客户端拔掉网线后,如果客户端一直不插回网线,那么客户端和服务端的 TCP 连接状态将会一直保持存在
    • 如果双方都开启了 TCP keepalive 机制,那么在客户端拔掉网线后,如果客户端一直不插回网线,TCP keepalive 机制会探测到对方的 TCP 连接没有存活,于是就会断开 TCP 连接。而如果在 TCP 探测期间,客户端插回了网线,那么双方原本的 TCP 连接还是能正常存在
  • 除了客户端拔掉网线的场景,还有客户端「主机宕机和进程崩溃 」的两种场景

    • 第一个场景,客户端宕机这件事跟拔掉网线是一样无法被服务端的感知的,所以如果在没有数据传输,并且没有开启 TCP keepalive 机制时,,服务端的 TCP 连接将会一直处于 ESTABLISHED 连接状态,直到服务端重启进程
      • 所以,我们可以得知一个点。在没有使用 TCP 保活机制,且双方不传输数据的情况下,一方的 TCP 连接处在 ESTABLISHED 状态时,并不代表另一方的 TCP 连接还一定是正常的
    • 第二个场景,客户端的进程崩溃后,客户端的内核就会向服务端发送 FIN 报文,与服务端进行四次挥手
      • 所以,即使没有开启 TCP keepalive,且双方也没有数据交互的情况下,如果其中一方的进程发生了崩溃,这个过程操作系统是可以感知的到的,于是就会发送 FIN 报文给对方,然后与对方进行 TCP 四次挥手
计算机网络基础1227
https://fuwari.cbba.top/posts/计算机网络基础1227/
作者
Chen_Feng
发布于
2023-12-27
许可协议
CC BY-NC-SA 4.0