一、TCP 的建立
TCP 通过三次握手建立连接,具体步骤如下:
(1). 服务器通过一系列的动作(socket、bind、listen)来准备接受外来的连接,被称为被动打开。
(2). 客户端通过一系列的动作(socket、connect)进行连接到服务器(要保证服务器已经被动打开了)。connect 动作会使客户 TCP 发送一个 SYN(我想和你(服务器)同步) 分节,它告诉服务器客户将在(待建立)连接中发送数据的初试序列号。
(3). 服务器端在收到这个 SYN 分节后,要对其进行 ACK 确认(嗯,我知道你要同步了)。并且,自己也发送一个 SYN(那我们同步吧) 分节,它告诉客户服务器将在(待建立)连接中发送数据的初始序列号。注意:ACK 和 SYN 是放在同一个分节里的哦!
(4). 客户还要发送一个 ACK 对服务器发来的 SYN 进行确认(好嘞,谢谢哈,那开始了)!
连接过程图如下:
注意:一般 SYN 不携带数据,它只含有一个 IP 头部,一个 TCP 头部及可能有的 TCP 选项!
SYN 中可以含有若干个 TCP 选项。通常的选项有:
(1). MSS 选项:这个选项用来告诉对端它的最大分节大小MSS。即能接受的每个TCP 分节中的最大数据量。我们可以通过 TCP_MAXSEG 套接口选项来设置这个选项。
(2). 窗口规模选项:TCP 两端能够通知对端的最大窗口大小是 65535 ,因为 TCP 头部相应字节只有 16 位。但由于如今的网络上已经普及的高速连接或长延迟的路径要求有更大的窗口以获得尽可能大的吞吐量。这需要一个新的选项(窗口规模选项)指定 TCP 头部的通告窗口必须扩大(即左移)的位数(0-14),这使得最大窗口能扩展到几乎 1 G 字节,大大的提高了吞吐量,但这要求双方都支持这个新的选项。
(3). 时间戳选项:这个选项对于高速连接来说是必要的,它可以防止由失而复得的富足可能造成的损坏。它也是一个新的选项,所以处理类似于窗口规模选项。
二、TCP 的终止
TCP 通过四次握手来终止一个连接,具体步骤如下:
(1). 某个进程(可以是客户端也可以是服务器端)首先调用 close 进行主动关闭。这会使这段的 TCP 发送一个 FIN 分节(我要终止连接了哈)。
(2). 接收到 FIN 分节的另一端执行被动关闭。这个 FIN 由 TCP 进行确认,通过发送一个 ACK 分节。并将它(FIN)作为文件结束符放在接收端应用进程的接收队列的尾部!(表示收完队列中的所有数据就不再接收数据了)。
(3). 一段时间后(为什么是一段时间后呢?接收端的应用程序收到了(2)所说的文件结束符),应用进程将调用 close 关闭它的套接口。这导致它也发送一个 FIN(嗯,结束吧)。
(4). 接收到这个 FIN 分节的原发送端 TCP (即执行主动关闭的那一段)对它进行确认,通过发送一个 ACK 分节!
以上描述通俗点讲就是这样:主动关闭一端发个 FIN 说我要终止连接了。被动关闭的一端说我知道了(发送一个 ACK ),不过要等等,我进程中队列中的数据还没收完呢!等啊等,被动关闭端进程队列中的数据收完了!被动关闭端说好了,我们关闭吧(发送一个 FIN ),主动关闭端说好嘞(发送一个 ACK ),这样就真正关闭了!
终止连接过程图如下:
三、TCP 连接和终止的状态图
TCP 连接和终止可还不像上面那些图描述的那么抽象哦,还有更具体的状态呢,下面我们一起来看看:
什么这个状态图你不是很明白?确实,这个图将连接的建立和终止划到一块了,可能会绕晕,那我接下来来个绕不晕的:
这个图是顺序结构,比较好理解了!下面我用文字描述一下:
建立时:一开始客户端是 CLOSED 状态,在 connect 主动请求连接发送 SYN 分节之后变成 SYN_SENT 状态。服务器端一直是处于 LISTEN 状态,在收到一个 FIN 分节之后变为 SYN_RCVD 状态。然后服务器端发送一个确认 ACK 分节。客户端收到服务器端发送过来的确认之后由 SYN_SENT 状态变成 ESTABLISHED 状态,然后再给服务器端发送一个 ACK 分节进行确认。服务器在收到这个分节时由 SYN_RCVD 状态变成 ESTABLISHED 状态。
传送真正的数据时:客户和服务器就在 ESTABLISHED 状态下收发数据!
结束时:客户端应用进程主动执行 close 进行关闭,像服务器端发送一个 FIN 分节变成 FIN_WAIT_1,服务器端接收到客户端发来的 FIN 分节后给它发送一个确认 ACK,自己变成 CLOSED_WAIT 状态。客户端收到服务器端发送过来的确认后由 FIN_WAIT_1 变成了 FIN_WAIT_2 状态。服务器端在过一段时间后,也会执行 close 进行关闭,像客户端发送一个 FIN 分节,并由 CLOSED_WAIT 状态变为 LAST_ACK 状态。客户端在接收到服务器端发来的 FIN 分节之后,由 FIN_WAIT_2 状态变成 TIME_WAIT 状态。并像服务器端发送一个对 FIN 确认的分节 ACK。服务器端收到客户端发过来的 ACK 分节后由 LAST_ACK 状态变成 CLOSED 状态(最原始状态)!
三、TIME_WAIT 状态的作用
其中 TIME_WAIT 状态会持续相当于2倍 MSL 的时间,TIME_WAIT 状态主要有以下两个作用:
(1). 可靠的实现 TCP 全双工连接的终止。其实就是防止最后一个 ACK 丢失
(2). 允许老的重复分节在网络中消逝。
第一个作用解释如下:如果客户端不是变成 TIME_WAIT 状态,而是直接CLOSED 状态,那么它发送的最后一个 ACK 分节如果丢失了怎么办?因为他已经是 CLOSED 状态了不可能再重发 ACK 了,这样会导致这个连接无法正常结束。而如果给一个过渡的状态 TIME_WAIT,那么就可以重发丢失的 ACK 了。
第二个作用解释如下:如果此连接在发送的过程中有一个数据包由于某种原因在网络中迷路了,这样发送端的 TCP 在以为这个数据包丢失了,就会重传这个数据包。假设此时我们的应用进程终止了连接。那么这个迷路的数据包是不是应该让他消亡了呢?这个 TIME_WAIT 状态就是为了等待此次连接过程中类似于上面那个密室的数据的消亡。如果不等待它们消亡就直接 CLOSED 。那么会产生什么后果呢?刚才不是说应用进程终止了连接了吗?如果这时又建立一个和刚才一样的连接(同IP同端口),那么这个迷途的数据包在迷了一段时间后顺利的送到了目的地,但是此时这个连接已经不是上次那个了。这个送来的数据包显然不是任何一段想要的数据了。