TCP握手与挥手

技术革命

格雷厄姆在《黑客与画家》中提到一个观点,历史上财富的积累无非两种方式---偷窃和抢夺。新技术革命的出现,财富的积累的新方式则是技术进步。

技术进步创造了无数的财富,而这个技术指的就是网络技术。相对于人类的文明,网络的历史如同昙花一现,恰恰是这么短的时间内,所创造的文明和财富却是前所未有。相对于网络自身的发展,其实已经算是历史悠久了。然而自从网络诞生到现在,网络的基础架构理论,基本的通信协议改变也不是很大

我们所熟知的Internet,把全世界的人连接起来,Inernet构建于TCP/IP模型基础之上。TCP/IP模型有着很多协议,其中与网络(web)应用开发者息息相关的莫过于TCP协议。想要了解TCP协议的本质,起始于TCP的三次握手和四次挥手。

TCP 握手连接

连接是一个通信的行为。建立连接,就能使用连接进行通信。连接作用于两个节点。TCP是有状态的协议,因此两个节点之间想要通过tcp发送数据,必须先建立可靠有效的连接。tcp是双通道的通信模式,因此tcp的连接(逻辑连接)其实是两条物理连接,即客户端-->连接服务端--->客户端的连接。

上图即是经典的TCP三次握手。握手前,服务端创建socket对象,绑定地址,开启监听;客户端创建socket,准备连接。然后进行三次握手连接:

  1. clinet向server端发送一个[SYN]包,seq=x。
  2. server收到[SYN]包之后,向clinet发送[SYN ACK] seq=y,ack=x+1
  3. clinet收到server的syn和ack之后,再向server端发送[ACK] ack=y+1包,至此三次握手完成。

创建一个tcp连接,通过三次握手即可。其实三次握手的clinet和server端是在不同的变化状态。下面详细讨论下三次握手中的两端的状态变化。

TCP 握手c/s端状态

针对三次握手的详细过程,有如下过程:

  1. 发起握手的时候,clinet发送[SYN]包之后,自身马上变成SYN_SENT状态,server则是进行了listen,自身的状态则变成LISTEN。接受到[SYN]包之后,自身变成SYN_RCVD状态。这个过程主要含义是clinet向server建立一条发送数据的连接。
  2. server端收到了client的SYN之后,马上会发送一个[SYN ACK]包,这里一共有两个作用。ACK用于应答client,表示client->server的连接已经建立,同时server也想向client建立一条发送数据的连接,因此也需要发送一个[SYN]包。
  3. client收到了server的[SYN ACK]。通过server发的ACK确定了client->server这条连接建立。自身状态变成了ESTABLISHED,表示可以正常的发送数据给server了。同时为了响应server发送的建立连接的SYN请求,再次给server做一次ACK的应答,一旦server收到clinet的ACK应答,server的状态也会变成ESTABLISHED

上述的过程中涉及到几个状态,其中SYN_SENTSYN_RCVD非常短暂,使用nc等工具也很难看到这种状态的。

syn包表示希望建立一个连接,ack包表示应答。连接的本质是:

    client  ----- 发送syn ------>  server  希望创建连接
    client  <----- 发送ack ------  server  确定创建连接

之所以是三次握手,而不是第四次握手,是因为第二次握手的时候,server把 [SYN] 和 [ACK]一起发送了。

TCP 挥手断连

了解了tcp的握手方式,那么挥手方式就很容易理解啦。与创建连接syn不一样,想要端口连接需要发送的是fin包。同样为了确定断开连接,需要发送ack应答确认包。大概方式入下图:

断开tcp需要四步,断开连接既可以是client主动,server被动,也可以server主动断开。两种的状态变化也是相对而言。下面以client主动断开为例:

  1. client发送[FIN]包,表示要断开client->server的连接。
  2. server收到[FIN]之后,发送一个[ACK]包表示确定断开连接啦。
  3. 同时server也会向client再发一个[FIN]包,表示也想断开server->client的连接。
  4. [FIN-WAIT-2]client收到server的[ACK]包,确定了client->server的连接的断开,该连接将不会发送数据啦。由于client也会收到server的[FIN]包,因此也要为断开server->client的连接发送[ACK]给server确定。

至此,四次挥手完成。下面详细四次交互过程两个端的状态。

client和server端传送数据的时候,双方的状态都是ESTABLISHED。一旦client发送了fin之后,自身变成FIN-WAIT-1的状态,意思是等待server端的ack确定。此时,该通道不再向server发送数据,但是仍然可以接收server数据了。

server收到了client发送的ack之后,自身的状态由ESTABLISHED变成CLOSE_WAIT。client收到ack后,自身由FIN-WAIT-1变成FIN-WAIT-2,断开了连接。此时client在等待server端的fin信号。

server 发送 fin之后,自身就由CLOSE_WAIT变成LAST-ACK状态,即等待client的最后的ack确定。client收到server的fin包之后,自身就由FIN-WAIT-2变成TIME_WAIT状态,等待一个2MSL时间之后,就变成了CLOSED状态。

最后server收到client的ack确定之后,自身也变成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中详细讨论。