TCP握手与挥手
技术革命
格雷厄姆在《黑客与画家》中提到一个观点,历史上财富的积累无非两种方式---偷窃和抢夺。新技术革命的出现,财富的积累的新方式则是技术进步。
技术进步创造了无数的财富,而这个技术指的就是网络技术。相对于人类的文明,网络的历史如同昙花一现,恰恰是这么短的时间内,所创造的文明和财富却是前所未有。相对于网络自身的发展,其实已经算是历史悠久了。然而自从网络诞生到现在,网络的基础架构理论,基本的通信协议改变也不是很大。
我们所熟知的Internet,把全世界的人连接起来,Inernet构建于TCP/IP模型基础之上。TCP/IP模型有着很多协议,其中与网络(web)应用开发者息息相关的莫过于TCP协议。想要了解TCP协议的本质,起始于TCP的三次握手和四次挥手。
TCP 握手连接
连接是一个通信的行为。建立连接,就能使用连接进行通信。连接作用于两个节点。TCP是有状态的协议,因此两个节点之间想要通过tcp发送数据,必须先建立可靠有效的连接。tcp是双通道的通信模式,因此tcp的连接(逻辑连接)其实是两条物理连接,即客户端-->连接
和服务端--->客户端
的连接。
上图即是经典的TCP三次握手。握手前,服务端创建socket对象,绑定地址,开启监听;客户端创建socket,准备连接。然后进行三次握手连接:
- clinet向server端发送一个[SYN]包,seq=x。
- server收到[SYN]包之后,向clinet发送[SYN ACK] seq=y,ack=x+1
- clinet收到server的syn和ack之后,再向server端发送[ACK] ack=y+1包,至此三次握手完成。
创建一个tcp连接,通过三次握手即可。其实三次握手的clinet和server端是在不同的变化状态。下面详细讨论下三次握手中的两端的状态变化。
TCP 握手c/s端状态
针对三次握手的详细过程,有如下过程:
-
发起握手的时候,clinet发送[SYN]包之后,自身马上变成
SYN_SENT
状态,server则是进行了listen,自身的状态则变成LISTEN
。接受到[SYN]包之后,自身变成SYN_RCVD
状态。这个过程主要含义是clinet向server建立一条发送数据的连接。 -
server端收到了client的SYN之后,马上会发送一个[SYN ACK]包,这里一共有两个作用。ACK用于应答client,表示client->server的连接已经建立,同时server也想向client建立一条发送数据的连接,因此也需要发送一个[SYN]包。
-
client收到了server的[SYN ACK]。通过server发的ACK确定了client->server这条连接建立。自身状态变成了
ESTABLISHED
,表示可以正常的发送数据给server了。同时为了响应server发送的建立连接的SYN请求,再次给server做一次ACK的应答,一旦server收到clinet的ACK应答,server的状态也会变成ESTABLISHED
。
上述的过程中涉及到几个状态,其中SYN_SENT
和SYN_RCVD
非常短暂,使用nc等工具也很难看到这种状态的。syn
包表示希望建立一个连接,ack
包表示应答。连接的本质是:
client ----- 发送syn ------> server 希望创建连接
client <----- 发送ack ------ server 确定创建连接
之所以是三次握手,而不是第四次握手,是因为第二次握手的时候,server把 [SYN] 和 [ACK]一起发送了。
客户端 | 服务端 | ||||
---|---|---|---|---|---|
开始状态 | 过程 | 结束状态 | 开始状态 | 过程 | 结束状态 |
CLOSED | client创建socket | CLOSED | CLOSED | server创建socket,绑定地址,打开监听 | LISTEN |
CLOSED | [第一次握手]client向server发送SYN包 | SYS-SENT | LISTEN | 等待client发送的SYN | LISTEN |
SYS-SENT | 发送syn之后等待server发送ack | SYS-SENT | LISTEN | [第二次握手]接受client的syn,同时向client发送[SYN ACK] | SYN-RECEIVED |
SYN-SENT | 接受server的SYN+ACK,通过ack确定client->server连接创立,[第三次握手]同时针对SYN发送ACK | ESTABLISHED | SYN-RECEIVED | 等待clinet发送的ACK确认 | SYN-RECEIVED |
ESTABLISHED | 等待server完成对ack的响应,等待完成server->clinet的连接 | ESTABLISHED | SYN-RECEIVED | 接受client的ACK确定,完成server->clinet的连接 | ESTABLISHED |
ESTABLISHED | 发送接受数据 | ESTABLISHED | ESTABLISHED | 发送接收数据 | ESTABLISHED |
TCP 挥手断连
了解了tcp的握手方式,那么挥手方式就很容易理解啦。与创建连接syn不一样,想要端口连接需要发送的是fin包。同样为了确定断开连接,需要发送ack应答确认包。大概方式入下图:
Termination.png断开tcp需要四步,断开连接既可以是client主动,server被动,也可以server主动断开。两种的状态变化也是相对而言。下面以client主动断开为例:
- client发送[FIN]包,表示要断开clinet->server的连接。
- server收到[FIN]之后,发送一个[ACK]包表示确定断开连接啦。
- 同时server也会向clinet再发一个[FIN]包,表示也想断开server->clinet的连接。
- clinet收到server的[ACK]包,确定了clinet->server的连接的断开,该连接将不会发送数据啦。由于client也会收到server的[FIN]包,因此也要为断开server->clinet的连接发送[ACK]给server确定。
至此,四次挥手完成。下面详细四次交互过程两个端的状态。
client和server端传送数据的时候,双方的状态都是ESTABLISHED
。一旦clinet发送了fin之后,自身变成FIN-WAIT-1
的状态,意思是等待server端的ack确定。此时,该通道不再向server发送数据,但是仍然可以接收server数据了。
server收到了clinet发送的ack之后,自身的状态由ESTABLISHED
变成CLOSE_WAIT
。client收到ack后,自身由FIN-WAIT-1
变成FIN-WAIT-2
,断开了连接。此时client在等待server端的fin信号。
server 发送 ack之后,自身就由CLOSE_WAIT
变成LAST-ACK
状态,即等待client的最后的ack确定。client收到server的fin包之后,自身就由 FIN-WAIT-2
变成 TIME_WAIT
状态,等待一个2MSL时间之后,就变成了CLOSED
状态。
最后server收到client的ack确定之后,自身也变成CLOSED
状态。
客户端 | 服务端 | ||||
---|---|---|---|---|---|
开始状态 | 过程 | 结束状态 | 开始状态 | 过程 | 结束状态 |
ESTABLISHED | 向server发送FIN,表示想关闭clinet->server的连接 | FIN-WAIT-1 | ESTABLISHED | 正常服务,直到收到FIN | ESTABLISHED |
FIN-WAIT-1 | 发送完毕FIN,等待server的确认应答,此时将不会再发送数据给server | FIN-WAIT-1 | ESTABLISHED | 接受FIN,发送ACK确认断开连接 | CLOSE-WAIT |
FIN-WAIT-1 | 接收server发送的ACK,确定client->server连接关闭 | FIN-WAIT-2 | CLOSE-WAIT | 发送FIN,表示关闭server->client的连接 | LAST-ACK |
FIN-WAIT-2 | 收到server的FIN包,发送ACK作为应答 | TIME-WAIT | LAST-ACK | 等待 client发送的ACK | LAST-ACK |
TIME-WAIT | 等待MSL时间,保证ACK能被对方收到,等待fin否则重发 | TIME-WAIT | LAST-ACK | 接受ACK应达,关闭server->clinet的连接 | CLOSED |
TIME-WAIT | MSL 时间过期 | CLOSED | CLOSED | 连接关闭 | CLOSED |
问题
三次握手保证了TCP连接的可靠性,假设没有三次握手只有两次。如果client发送第一个syn的时候延迟很大,导致client发送了第二个,第二个syn很快就到达server端。于是server发送ack确定连接,并发送syn。在两次握手的情况下,服务器认为连接都建立好了。如果此时第一个syn又抵达了服务器,那么服务器将会再次应答,向client端发送连接请求。这样会造成无效的连接,通过第三次握手,可以在server应答无效连接的时候提前终止。也就是最后一次握手不再发送ack,那么server就不会再创建连接。
在关闭连接的时候,虽然两个端都统一关闭连接,并且四次交互也发送完毕。假设如果网络延迟很大,或者丢包严重,就很难保证client最后一次ack一定能被server收到。如果server收不到,会重发fin。为了解决这个问题,通常在client发送ack之后2MSL时间,才由TIME_WAIT
变成CLOSED
状态,在此期间,client可以针对server补发的fin重发ack。
还有一个CLOSING
状态,这个状态表示client发送了FIN之后,并没有收到ACK,反而先收到了server的FIN。当然这种情况十分罕见的“异常”状态。双方都同时关闭连接,有可能在四次交互的时候,出现某些包延迟。
总结
TCP 连接和断开的过程中,都是一问一答的方式,创建连接的问是syn,回答是ack,同样断开连接的问是fin,回答是ack。有问必有答,那么连接就能正常的创建和关闭。因为tcp通信是双通道的,因此一个TCP逻辑连接,实际上是两条成对client和server端的物理连接。无论连接和断开,都需要把这两个连接都处理完毕才能完成。也因为这两个过程,中间衍生出了很多状态。这些状态在创建连接的时候有client和server端的区分,在断开的连接的时候就没有特别区别,只有主动断开和被动断开的差别。
至于tcp三次握手中两端的“两个连接”的通信通道,他们的具体原理和过程,我们将会在TCP连接与Python中详细讨论。