在学习完成 TCP 的机制之后,我们可以对于 TCP 数据包的具体流程进行分析,相较于只管发送的 UDP,这复杂了许多。
三次握手:TCP 连接的建立
在真正发送应用层数据包之前,我们的客户端往往需要先和我们的服务器约法三章,就初始序列号等问题进行协商,同时在最初的沟通之中,判断通信管道的畅通与否。
TCP 数据包的基本结构
网上的许多文档喜欢放图,而不是放出描述数据包的代码,在我看来,对于结构简单的 UDP 而言,确实可以,而对于复杂的 TCP 数据包而言,则不太合适。
可以看到,TCP 有着许多的设置选项与许多的填写信息(这些纷繁复杂的数据类型,你可以理解为描述范围不一的整数)。
struct tcphdr
{
__be16 source;/* 我们主机的地址 */
__be16 dest;/* 目标主机的地址 */
__be32 seq;/* 序号 */
__be32 ack_seq;/* 应答序号 */
/* 以下都是标志位,我们需要结合实际情况进行判断 */
__u16 res1:4,
doff:4,
fin:1,/* FIN 位 “四次挥手" */
syn:1,/* SYN 位 “三次握手" */
rst:1,
psh:1,
ack:1,/* ACK 位 响应包 */
urg:1,
ece:1,
cwr:1;
__be16 window;/* 窗口缓冲区大小 */
__be16 check;
__be16 urg_ptr;
};
第一次握手:客户端程序发送 SYN 数据包
“客户端-服务器"模型要求我们的客户端率先发起沟通,而在 TCP 协议中,率先发起沟通的客户端,就会以 SYN 数据包作为一切的起始点。
流程也是非常简单的,可以看到我们预先已经在上面的代码里面做出了简单的注释,找到 SYN 那一位,将其设置为 1(置位),再设置一个初始的序号,进而将这个完成初步设置的数据包经网络层发送至目标主机。
第二次握手:来自服务器的第一次响应(ACK 数据包)
在接收到来自于客户端的 SYN 数据包之后,我们的服务器就将对我们客户端的相关信息进行记录,并构造出一个对应的 ACK 数据包(将 ACK 置位),设定服务器方面的初始序列号,发送给客户端。
第三次握手:来自于客户端的确认
如果只是一问一答,就容易出现一个问题:有一些僵尸流量(之前的冗余数据包算是其中一种)可能会因为种种因素到达我们的服务器,如果不加入这一确认的流程,我们的服务器就会一直维持着那些僵尸流量对应的资源,进而造成严重资源浪费。
确认的过程非常简单,构造数据包加以响应即可。
之后,我们就可以依照正常的流程进行双向交流,这就需要灵活的判断,不存在公式化的流程一说。
四次挥手:TCP 连接的结束
对于我们的客户端或是服务器而言,TCP 连接的结束都可以用四次挥手加以解决。
第一次挥手:发送 FIN 报文
想要结束连接的一方发起 FIN 报文到另一方。
第二次挥手:受到 FIN 报文的一方加以响应
如题所示。
之后的历程:取决于发起方是客户端还是服务器
- 对于我们的发起方是服务器而言
在完成 FIN 报文的发送之后,我们的服务器也就中断了连接,在这个基础之上,我们的客户端想要进行数据写入,就会引发异常,从而导致我们的 TCP 连接强行终止。
换而言之,在服务器主动终止的情况下,TCP 连接的中断是依靠异常而不是完整的四次挥手而结束的。 - 对于我们的发起方是客户端而言
在自己主动发送 FIN 到服务器并得到服务器的确认之后,我们需要静心等待服务器的 FIN 报文,这就是第三次握手。
而当我们收到来自服务器的 FIN 报文之后,我们需要对此加以响应,这就是第四次握手。
评价:TCP 适合于可靠性高的网络应用
对于我们的应用程序而言,提供诸多保障的 TCP 协议,往往可以有效地提升我们应用的数据传输可靠性,因此 TCP 适合于可靠性要求较高的网络应用,但是对于实时性要求高的应用而言,这往往意味着资源的浪费。