最近在学习TCP,想写一个总结,还没学透,决定先占个坑,然后慢慢补充。
首先,TCP是面向连接的、可靠的协议,TCP必须检测并修补所有在IP层(或下面的层)产生的数据传输问题,比如丢包、重复包、错误包等。由于其传输前首先要建立连接,因此效率相对来说低一些所以适合于对实时性要求不是那么高,但是对数据的准确性什么的要求比较高的场景,比如说,我们的HTTP就是基于TCP的。
而UDP是无连接的,相对不可靠的协议,因此适合于对实时性要求高,对数据的可靠性要求不是那么高的场景,比如说我们的视频通话、语音通话等偶尔丢一两个包无大碍的场景。
好了,下面进入正题:
TCP连接的建立与终止
1.建立连接(三次握手)
一个TCP连接由一个四元组组成,它们分别是两个IP和两个端口号。
TCP建立连接通常分为三个阶段:启动、数据传输(连接建立)、和退出(关闭)。
1.如图,连接的主动开启者(一般情况下为客户端)首先发送SYN报文段(即一个SYN字段置位的报文),同时指明自己想要连接的的端口号和初始序列号(图中为seq),此后客户端的状态改为SYN-SENT。
2.服务器收到报文后,响应一个SYN报文段,报文同样包含初始序列号,此外,还包含一个ack(值为收到的seq+1),每发送一个SYN,序列号就会自动加一。这样如果出现丢包的现象,该SYN段将会重传。回复后,服务端转为SYN-RCVD状态。
3.客户端向服务端回复ack(值为收到的seq+1),至此,三次握手成功,连接建立,双方都转为ESTABLISH状态。
至此,我们可以说TCP是面向连接的、可靠的协议。那么TCP是100%可靠的吗?面向连接毫无缺陷吗?
答案是不是的,具体可参见我的另一篇文章:链接
2.连接终止(四次挥手)
连接的任何一方都可以发起关闭连接的操作,不过在传统情况下,发起关闭连接的一般为客户端,TCP协议规定通过发送一个FIN段(FIN字段置位的TCP报文段)来发起关闭操作,步骤如下:
1.连接的主动关闭者(一般为客户端)发送一个FIN报文段,包含一个seq,FIN报文段其实还可以包含一个ACK来确认对方最近一次发来的数据(图上未画出),此时,主动关闭者状态由ESTABLISH转为FIN-WAIT-1。
2.连接的被动关闭者回复ACK,包括seq,以及ack(收到的seq+1),此时,被动关闭者状态由ESTABLISH转为CLOSE-WAIT,主动关闭者状态由FIN-WAIT-1转为FIN-WAIT-2。
3.连接的被动关闭者发送自己的FIN,包括seq,以及ack(最近收到的seq+1),然后被动关闭者状态由CLOSE-WAIT转为LAST-ACK。
4.主动关闭者回复ack,被动关闭者收到ack后,就关闭连接。而主动关闭者状态由FIN-WAIT-2转为TIME-WAIT,等待2MSL后,连接关闭。
至此,连接成功关闭,双方的状态都转为CLOSED。
为什么要等待2MSL后,连接才关闭?
MSL:报文段最大生存时间,它是任何报文段被丢弃前在网络内的最长时间。
原因有二:
-
保证TCP协议的全双工连接能够可靠关闭
-
保证这次连接的重复数据段从网络中消失
情景一:假如主动关闭者在发送了最后的ack后直接关闭连接,如果这个ack包在传输过程中丢失,那么被动关闭者没有收到ack,会再次发送FIN包,但是此时主动关闭者已经进入了CLOSED状态,就不会回复ack 了,这样会导致被动关闭者这一端的连接无法关闭。所以我们的主动关闭者要等到2MSL时间。保证被动关闭者连接关闭。
情景二:假如主动关闭者(我们下面称之为客户端)在发送了最后的ack后直接关闭连接,又向和另一个服务器(我们下面称之为服务器2)建立了连接。假如(就是这么凑巧)客户端又使用了和上一个连接一样的端口号,也就是说客户端新连接和老连接的端口号是相同的。那么如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达主机2,由于新连接和老连接的端口号是一样的,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。