三次握手
在握手正式开始之前,服务器必须准备好接收外来的连接。这通常通过调用socket, bind, listen这3个函数来完成,称之为被动打开。
- 第一次握手:客户通过调用connect发起主动打开。这导致客户TCP发送一个 SYN(同步)分节,它告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号(ISN)。此时客户处于SYN_SENT状态。
- 第二次握手:服务器必须确认(ACK)客户的SYN,同时自己也得发送一个 SYN,它含有服务器将在同一连接中发送的数据的初始序列号(ISN)。服务器在单个分节中发送SYN和对客户SYN的ACK。此时服务器处于SYN_RCVD状态。
- 第三次握手:客户确认服务器的 SYN。收到 SYN 报文后,会发送一个 ACK 报文。此时客户处于ESTABLISHED状态,服务器收到ACK报文后,也处于ESTABLISHED状态,此时,双方已建立起了连接。
注:通常SYN分节不携带数据,其所在IP数据报只含有一个IP首部,一个TCP首部及可能有的TCP选项。
进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。
MSS: 发送SYN的TCP一端使用本选项通告对端它的最大分节大小也就是它在本连接的每个TCP分节中愿意接收的最大数据量。
三次握手的目的
- 第一次握手:客户端发送网络包,服务端收包。
服务端得出结论:客户端的发送能力、服务端的接收能力是正常的。 - 第二次握手:服务端发包,客户端收包。
客户端得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。
不过此时服务器并不能确认客户端的接收能力是否正常。 - 第三次握手:客户端发包,服务端收包。
服务端得出结论:客户端的接收、发送能力正常,服务器的发送、接收能力也正常。
连接队列
服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。
已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了有可能会出现丢包现象。
初始序列号(ISN)
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。
这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。
三次握手的其中一个重要功能是客户端和服务端交换 ISN,以便让对方知道接下来接收数据的时候如何按序列号组装数据。 如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。
握手过程中能否携带数据 ?
第一次,第二次握手不可以,第三次握手可以。
假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。
因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文,这会让服务器花费很多时间、
内存空间来接收这些报文。
第三次握手的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,已经建立起连接了,并且也已经知道服务器的
接收、发送能力是正常的,所以携带数据也是可以的了。
四次挥手
- 第一次挥手:某个应用进程首先调用close,我们称该端执行主动关闭。该端的TCP于是发送一个 FIN 分节,表示数据发送完毕。此时该端处于FIN_WAIT1状态。
- 第二次挥手:接收到这个 FIN 的对端执行被动关闭,FIN 的接收意味着接收端应用进程在相应连接上再无额外数据可接收。接收到 FIN 后,会返回一个 ACK 报文,此时接收端处于CLOSE_WAIT状态。
- 第三次挥手:一段时间后,接收端将调用 close 关闭套接字。这导致接收端的 TCP 也发送一个 FIN,此时该端处于LAST_ACK状态。
- 第四次挥手:接收到这个最终 FIN 的原发送端 TCP 会发送一个 ACK 确认,此时该端处于TIME_WAIT状态,需要过一段时间确保对端接收到自己的 ACK 之后才会进入CLOSED 状态,对端接收到 ACK 之后就关闭连接,处于 CLOSED状态。
注:无论是客户还是服务器,任何一端都可以执行主动关闭。只不过通常情况都是客户端执行主动关闭
因为每个方向都需要一个 FIN 和一个 ACK,因此通常需要4个分节。
但是也有一些特殊情况:比如某些情况下第一次挥手的 FIN 随数据一起发送了;还有,第二次和第三次挥手发送的分节都出自同一端,有可能会被合并成一个分节。
为什么是四次挥手?
当对端收到发送端的 SYN 请求连接报文后,可以直接发送 ACK+SYN 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当对端端收到 FIN 报文时,可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文。只有等到该端所有的报文都发送完毕,才能发送 FIN 报文,因此不能一起发送。故需要四次挥手。
TIME_WAIT状态
该端点(执行主动关闭的一端)停留在这个状态的持续时间是最长分节生命期(Maximum Segment Lifetime,MSL)的两倍,有时候称之为2MSL。
MSL(Maximum Segment Lifetime):是任何数据报能够在因特网中存活的最长时间,超过这个时间报文将被丢弃。
TIME_WAIT状态存在的理由:
- 可靠地实现TCP全双工连接的终止。
假设最终的ACK丢失了。服务器将重新发送它的最终那个FIN,因此客户必须维护状态信息,以允许它重新发送最终那个ACK。
要是客户不维护状态信息,它将响应以一个RST (另外一种类型的TCP分节),该分节将被服务器解释成一个错误。如果TCP打算
执行所有必要的工作以彻底终止某个连接上两个方向的数据流(即全双工关闭),那么它必须正确处理连接终止序列4个分节中
任何一个分节丢失的情况。
这就说明了为什么执行主动关闭的那一端是处于TIME WAIT状态的那一端:因为可能不得不重传最终那个ACK的就是那一端。
- 允许老的重复分节在网络中消逝。
假设现在有一个TCP连接,我们关闭这个连接,过一段时间后在相同的IP地址和端口之间建立另一个连接。
后一个连接称为前一个 连接的化身( incarmation),
因为它们的IP地址和端口号都相同。TCP必须防止来自某个连接的老的重复分组在该连接已终止后再现,从而被误解成属于
同一连接的某个新的化身。为做到这一点, TCP将不给处于TIME WAIT状态的连接发起新的化身。既然TIME_ WAIT状态的
持续时间是MSL的2倍,这就足以让某个方向上的分组最多存活MSL秒即被丢弃,另一个方向上的应答最多存活MSL秒也被丢弃。
通过实施这个规则,我们就能保证每成功建立一个TCP连接时, 来自该连接先前化身的老的重复分组都已在网络中消逝了。
TCP状态转换图
参考:《UNIX网络编程 卷1:套接字联网API》