TCP连接管理
直接开始
建议一个TCP连接,需要有源IP、源端口、目标IP、目标端口,源IP和目标IP就存放在网络层,也就是在IPv4头上,而源端口和目标端口存放在传输层,也就是TCP或UDP,实际上在通信过程中源端口和目标端口是由应用层开始往下传递,不过这不重要,接下来先看看IPv4头的结构。

| 名称 | 解释说明 |
|---|---|
| 版本号 | IP所使用的版本,比如说是4还是6 |
| 首部长度 | IP头的长度 |
| 服务类型 | 优先级标志位和服务类型标志位,被路由器用来进行流量的优先排序 |
| 总长度 | IP头和数据包中数据的长度 |
| 标识符 | 当数据包过大时会被分片,那么就需要一个唯一标识来标志被分片的数据包 |
| 标识 | 用来标识一个数据包是否是一组分片数据包的一部分 |
| 分片偏移 | 标识被分片的数据包对于整个数据包的偏移量,用于在最后组装完整的数据包 |
| 存活时间 | 定义数据包的生存周期,以经过路由器的个数为描述 |
| 协议 | 标识传输层所使用的协议类型,比如TPC/UDP |
| 首部校验和 | 保证IP头的内容没有损坏或者篡改 |
| 源IP地址 | 发送数据包的IP地址 |
| 目标IP地址 | 接收数据包的IP地址 |
| 选项 | 额外IP选项,比如时间戳 |
| 数据 | 使用IP传递的实际数据 |
接着在结合Wireshark来看看,如图所示:

这样子就可以很容易理解了,说完了IP头接下来就该说说TCP头了,毕竟这才是主角。

| 名称 | 解释说明 |
|---|---|
| 源端口 | 传输数据包的端口 |
| 目标端口 | 接收数据包的端口 |
| 序号 | 表示一个TCP片段 |
| 确认号 | 确认号的发送方期待接收的下一个序列号 |
| 头部长度 | TCP头的长度 |
| 标志 | CWR(拥塞窗口)URG(紧急) ACK(确认) PSH(推送) RST(重置连接) SYN(初始连接) FIN(开始关闭连接) 用于标识数据包的类型 |
| 窗口大小 | TCP接收者所能接收的最大的数据包大小 |
| 校验和 | 用来保证TCP头和数据的内容在抵达目的地时的完整性 |
| 紧急指针 | 如果设置了URG位,则这个域将被检查作为额外的指令,告诉CPU从数据包的哪里开始读取数据 |
| 选项 | 额外TCP选项 |
接着在结合Wireshark来看看,如图所示:

实战
众所周知,TCP在请求响应前必须要先建立连接,现有场景如下:
1、有服务器192.168.1.105,暂且叫做S,在S上放了个spring-boot demo,其中端口号是8080
2、接着在S上使用tcpdump命令监听8080端口的流量,并保存到文件中,后续将会使用Wireshark进行分析
3、有另外一台服务器198.168.1.106,暂且叫做C,直接向S发送请求
4、操作如图所示:


使用Wireshark打开S上保存的文件ping.pcap,其实一眼就看出了S和C之间在建立时需要发送的信号及内容,打开Wireshark中统计栏下的流量图会更直观,如图所示:

简单说明一下,客户端发送了一个序列号为xxx的SYN信号到服务端S,该序列号称为:客户端的初始序列号,服务端接受到信号后发送了一个序列号为yyy、确认号为xxx + 1的SYN_ACK信号到客户端,也就是说在建立连接时,服务端的确认号是客户端上送的序列号 + 1,这么做无非就是告诉客户端,我这个SYN_ACK信号是针对哪个SYN信号的,因为由于网络的原因客户端不得不多次重传SYN信号,如果不使用唯一标识来辨别,客户端怎么才知道哪个SYN信号是有效的,同理,服务端压根就不管客户端由于什么原因而多次重传SYN信号,反正你给我发了,我就回你(服务端会存储SYN信号相关信息),所以这就会导致越来越多的资源被浪费,因为最终能与服务端建立连接的就只有一个,所以为了防止资源被合理利用,客户端还要告诉服务端,我要和你建立这个连接,即又发送了一个序列号为ttt、确认号为yyy + 1的ACK信号到客户端,到这里,客户端和服务端之间的连接就已经建立完毕,这就是TCP的三次握手。我们经常会被问到为什么一定要三次握手,不能是两次呢?其实上面已经回答了,这里在做个总结:
为什么TCP建立连接时要三次握手,不能是两次:通过序列号与确认号可以让客户端知道哪个连接是有效的(防止旧的重复连接被初始化造成混乱),这也进一步避免了资源的浪费,另外一个方面是从SYN到SYN_ACK的过程只能保证的客户端的信号已经被成功接收,但服务端却不知道自己的SYN_ACK有没有被接收,所以客户端还要在告诉服务端,你发的消息我收到了,否则将会出现重传。
接下来开始分析断开连接的过程。客户端发送了一个序列号为xxx的FIN信号到服务端(通常情况下会包含一个ACK用于对最近一次数据的确认),服务端接受到信号后马上发送了一个序列号为yyyy、确认号为xxx + 1的ACK信号,在这个时候服务端就知道了客户端不会在发送数据给我了,简单来说,客户端到服务端的连接已经被关闭了,但是服务端到客户端的连接还没有被关闭,所以等到服务端处理完手头上的事后,才会向客户端发送一个序列号为ttt、确认号为xxx + 1的FIN_ACK信号,相当于是在告诉客户端,我也不再给你发送数据了,也就是要关闭服务端到客户端之间的连接,客户端接收到后并不是马上就去处理服务端的连接,而是先给服务端发送了一个序列号为kkk,确认号ttt + 1的ACK信号,有问题的是那什么时候处理服务端的信号呢,为什么要如此呢?默认情况下是2MSL后就才会处理服务端的连接,之所以怎么做,其实是担心客户端最后发送的ACK无法被服务端正确接收,也就是说如果客户端发送的ACK信号丢失了,那么服务端将无法正常关闭,同时对于后续新的连接将会被终止,以上就是TCP的四次挥手。
四次挥手不能变成三次吗:所说的三次指的是将ACK同FIN_ACK一起发送,理论上来说,如果服务端没有什么要处理的数据,且也是随之关闭的话,是可以这么做的(就像截图上一样的情况),但通常情况下,客户端发起FIN只是关闭了客户端到服务端的连接而服务端到客户端的连接依然还在,也就是说客户端只是说了自己不会在发送数据了,但仍然还可以接受数据,所以是否使用三次挥手取决了服务端,通常情况下服务端都需要做相应的处理。
为什么TIME_WAIT需要等待2MSL:一方面是保证服务端能够正常关闭,实际上就是防止客户端发送的ACK信号没有被服务端接受,也就无法进入到CLOSED状态,另外一个方面是使当前连接的所有报文段都被丢弃,MSL指的是报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃,所有经过2MSL后足以让两个方向上的数据包都被丢弃,使得当前连接的数据包在网络中都自然消失,而再出现的数据包一定都是新建立连接所产生的。
在介绍下TCP状态转换图,直接拷贝了专业人员的,如图所示:

结束语
学习过程中,最好能够将理论与实战结合起来,否则很容易忘记或者理解不到位,Wireshark就是一款很好的实战工具。最后贴了大神的文章,有兴趣的可以直接参看,我是打算分成几篇文章来写。。。
浙公网安备 33010602011771号