三次握手
三次握手涉及的核心函数调用
Server::socket, bind, listen
Client::socket, connect
便于理解的一些解释
关于三次握手, 几句话解释清楚
1.信道不安全 保证通信需要一来一回
2.客户端的来回和服务端的来回 共四次 这是最多四次
3.客户端的回和服务端的来合并成一个,就是那个SYN k 和 ACK j+1
4.这样就是三次握手
为什么需要三次握手,不是四次?
tcp 的连接需要确保双方收和发消息的能力都是正常的。
客户端第一次发送握手 SYN 消息 j 到服务端, 服务端收到握手 SYN 消息 j 后把自己 的握手消息 SYN k 和 握手应答 ACK j+1 一并发送给客户端, 这是第二次握手,
当客户端收到服务端送来的第二次握手消息后,客户端可以确认“服务端的收发能力都OK,客户端的收发能力也OK”,
但是服务端只能确认“客户端的发送OK,服务端的接受OK”,
所以还需要第三次握手, 客户端收到服务端的第二次握手消息后,发起第三次ACK k+1 ,服务端收到第三次消息后,就能够确定“服务端的发送OK, 客户端的接收OK”,
至此,客户端和服务的都能够确认自己和对方的收发能力OK, tcp 连接建立完成。
四次挥手
流程概述
假设 发起端 主机A,接收端 主机B
- A 发送 FIN 报文 m , 进入FIN_WAIT_1
- B 回复 ACK 报文 m + 1 ,进入 CLOSE_WAIT 状态
- A 收到 ACK m + 1 , 进入FIN_WAIT_2
- 同时,B 通过read 调用获得 EOF,并将此结果通知应用程序进行主动关闭操作,发送 FIN 报文 n
- A 回复 ACK n + 1 ,进入 TIME_WAIT
- B 进入 CLOSED
通常在 TIME_WAIT 停留持续时间是固定的,是最长分节生命期 MSL(maximum segment lifetime)的两倍,一般称之为 2MSL。和大多数 BSD 派生的系统一样,Linux 系统里有一个硬编码的字段,名称为TCP_TIMEWAIT_LEN,其值为 60 秒。也就是说,Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。
只有发起连接终止的一方会进入 TIME_WAIT 状态,现在我们来思考一下 TIME_WAIT 的作用。
为什么不直接进入 CLOSED 状态,而要停留在 TIME_WAIT 这个状态?
这样做是为了确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。
举个例子:以防一些情况下TCP 报文传输会出错,需要重传,如果ACK 传输失败,那么FIN 报文会被对端再次发出,若没有维护 TIME_WAIT 状态,直接进入CLOSED 状态,就失去了当前状态的上下文,只能回复RST 报文,从而导致被动关闭方出现错误。防止延时的报文‘迟到’, 此时旧连接已经不存在,但是恰巧有四元组相同的新连接,这个时候报文会被误发,对TCP 通信产生影响。所以,按照TCP 设计的规范,经过2MSL 的时间,旧报文就会被丢弃,从而解决上述这个问题。
2MSL 的时间是从主机 1 接收到 FIN 后发送 ACK 开始计时的;如果在 TIME_WAIT 时间内,因为主机 1 的 ACK 没有传输到主机 2,主机 1 又接收到了主机 2 重发的 FIN 报文,那么 2MSL 时间将重新计时。道理很简单,因为 2MSL 的时间,目的是为了让旧连接的所有报文都能自然消亡,现在主机 1 重新发送了 ACK 报文,自然需要重新计时,以便防止这个 ACK 报文对新可能的连接化身造成干扰。
TIME_WAIT 的坏处
内存资源占用, 这个问题几乎可以忽略
对端口资源的占用, 一个 TCP 连接至少消耗一个本地端口。要知道,端口资源也是有限的,一般可以开启的端口为 32768~61000 ,也可以通过net.ipv4.ip_local_port_range 指定,如果 TIME_WAIT 状态过多,会导致无法创建新连接。
如何优化 TIME_WAIT ?
- net.ipv4.tcp_max_tw_buckets 默认为18000 , 这个值的含义是,当系统中处于 TIME_WAIT 的连接一旦超过这个值时,系统就会将所有 TIME_WAIT 连接状态重置,并打印出警告信息。不过这个方法过于暴力,治标不治本,带来的问题远比解决的问题多,不推荐使用。
调低 TCP_TIMEWAIT_LEN,重新编译系统,需要一些内核方面的知识,操作起来有一定的困难。
设置 SO_LINGER ,比较危险,暂不详细阐述
net.ipv4.tcp_tw_reuse
那么 Linux 有没有提供更安全的选择呢?
就是net.ipv4.tcp_tw_reuse选项。
Linux 系统对于net.ipv4.tcp_tw_reuse的解释如下:
Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0.It should not be changed without advice/request of technical experts.
这段话的大意是从协议角度理解如果是安全可控的,可以复用处于 TIME_WAIT 的套接字为新的连接所用。
那么什么是协议角度理解的安全可控呢?
主要有两点:
- 只适用于连接发起方(C/S 模型中的客户端);
- 对应的 TIME_WAIT 状态的连接创建时间超过 1 秒才可以被复用。
使用这个选项,还有一个前提,需要打开对 TCP 时间戳的支持,即net.ipv4.tcp_timestamps=1(默认即为 1)。
要知道,TCP 协议也在与时俱进,RFC 1323 中实现了 TCP 拓展规范,以便保证 TCP 的高可用,并引入了新的 TCP 选项,两个 4 字节的时间戳字段,用于记录 TCP 发送方的当前时间戳和从对端接收到的最新时间戳。由于引入了时间戳,我们在前面提到的 2MSL 问题就不复存在了,因为重复的数据包会因为时间戳过期被自然丢弃。
总结
- TIME_WAIT 的引入是为了让 TCP 报文得以自然消失,同时为了让被动关闭方能够正常关闭;
- 不要试图使用SO_LINGER设置套接字选项,跳过 TIME_WAIT;
- 现代 Linux 系统引入了更安全可控的方案,可以帮助我们尽可能地复用 TIME_WAIT 状态的连接。