首先,合适的类比并不是打电话的场景,而应该是两岸运送货物。
两岸的货物较多,所以不可能一艘船运完。
但是这个航道上不止一批的货物进行运送,所以为了接收地能正确的分认每一批货物且将其拼接起来,需要为货物打上序号(这个序号需要能真的将货物分批且拼接)。
建立TCP可靠连接的过程
-
首先Client发起连接,发送第一个报文,报文标记为SYN,而seq值给定随机值。
Client进入SYN-SENT状态。
-
Server收到报文,发送第二个报文,报文标记为SYN+ACK,而seq值给定随机值,ack值给定第一个报文的seq值+1。
Server进入SYN-RECEIVED状态。
-
Client收到第二个报文,进行如下确认:
- 是否第二个报文的ack为第一个报文的seq+1
- 是否有ACK标记
确认无误后,进入ESTABLISHED状态,并且发送第三个报文,报文标记为ACK,而seq值给定第一个报文的seq+1(=第二个报文的ack),ack值给定第二个报文的seq+1
从如上的连接过程中,有一点是没有变化的,当需要返回ack的时候,ack的值永远是上一个报文的seq+1,这表示收到了seq,并且收到了正确的seq。
三个过程后,Server收到第三个报文,同样会进行校对,同样校对的是自己上一次发送的seq是否在这个报文的ack里被+1,确认无误后,Server也会进入ESTABLISHED状态。
三次握手的能力
三次握手能防止旧复用链接的初始化导致问题,为了解决此问题,有reset这个特别的控制信号来处理。
当发现对方发来的数据包有某种错误(是旧包),那么会发起reset标记,表明要重置连接。
下面这个时序图表明了一个问题:当A发送给B的是一个旧包时,reset做到的反应:
TCP A TCP B
1. CLOSED LISTEN
2. SYN-SENT --> <SEQ=100><CTL=SYN> ...
3. (duplicate) ... <SEQ=90><CTL=SYN> --> SYN-RECEIVED
4. SYN-SENT <-- <SEQ=300><ACK=91><CTL=SYN,ACK> <-- SYN-RECEIVED
5. SYN-SENT --> <SEQ=91><CTL=RST> --> LISTEN
6. ... <SEQ=100><CTL=SYN> --> SYN-RECEIVED
7. SYN-SENT <-- <SEQ=400><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
8. ESTABLISHED --> <SEQ=101><ACK=401><CTL=ACK> --> ESTABLISHED
Recovery from Old Duplicate SYN
从第3步看到,这里发送了一个错误的旧包过去,但是B对此作出了响应,A却发现B的反应是不对的,因此发送了RST(第5步)过去,重新建立连接。(旧在途包发往新连接中的例子)
另外,三次握手避免了不必要的开销:
假如只是两次握手,客户端发送第一个报文给服务器后,服务器迅速认为要建立连接了,并且直接发送包含ACK SYN seq的第二个报文给客户端,但很可惜,这个报文遇到了网络拥堵,很久都没有到达。这个情况下,客户端迟迟收不到来自服务端的第二个报文,它就认为连接失败了,但是请注意:服务器是认为成功的。
服务器认为成功的代价就是,它会维持那个连接(不关闭端口)。
而客户端认为连接不成功后,他会重试,也就是重新发起一个新的连接,这就更进一步影响了服务端,因为服务端会再响应这个新的连接,那么服务端就会有没有用的连接(第一个连接),这就导致了不必要的开销。
TCP连接成功后的数据传输
传输的一个例子:
主机A分2次(分2个数据包)向主机B传递200字节的过程。
首先,主机A通过1个数据包发送100个字节的数据,数据包的 Seq 号设置为 1200。
主机B为了确认这一点,向主机A发送 ACK 包,并将 Ack 号设置为 1301。
从上述描述中可以看到,seq有表征报文顺序、数据顺序、确认收取的能力。第一个报文(A->B)的seq是1200,加上长度100,是1300,而第二个响应报文的seq正好是1300+1,这就表明B成功地收到了第一个报文,也告诉A自己收到了多少。