内容简介:我们首先要明确一点,这里讨论的TCP协议并不是指TCP/IP协议簇,而是单指TCP协议,即Transmission Control Protocol,指传输控制协议,位于传输层(OSI七层模型的第四层,或是TCP/IP四层协议的第三层)都应该清楚,传输层负责报文从进程到进程的传递,也可以说是从端口到端口的协议,所以TCP协议也属于一种端对端的协议。常用的端口号有:TCP是一个面向流的协议,允许发送进程以字节流的方式传递数据,同样地,接收方也以字节流的方式接收数据,这就存在了一个抽象的“管道”,管道的建立与拆
我们首先要明确一点,这里讨论的TCP协议并不是指TCP/IP协议簇,而是单指TCP协议,即Transmission Control Protocol,指传输控制协议,位于传输层(OSI七层模型的第四层,或是TCP/IP四层协议的第三层)
都应该清楚,传输层负责报文从进程到进程的传递,也可以说是从端口到端口的协议,所以TCP协议也属于一种端对端的协议。常用的端口号有:
- 20:FTP(文件传输协议)的数据传输
- 25:SMTP(简单邮件传输协议)
- 53:DNS(域名系统)
- 80:HTTP(超文本传输协议)
- 111:RPC(远程过程调用)
TCP是一个面向流的协议,允许发送进程以字节流的方式传递数据,同样地,接收方也以字节流的方式接收数据,这就存在了一个抽象的“管道”,管道的建立与拆除,就对应着TCP连接的创建和销毁,在接下来我们就来详细讲解这一过程
3. TCP分组
在讲解具体的过程之前,先来做一些准备工作。首先,TCP是一个全双工的通信协议,也就是连接的双方都能够发送和接收数据,这些数据,也就是字节,TCP会对其进行编号,这是因为TCP需要保证接收数据的顺序。编好号之后,TCP会为发送的每一个段分配一个序号,实际就是段中第一个字节的编号
发送的段,在TCP中也叫做分组,分组首部(首部不包含数据)的格式是如下这样的:
源端口、目的端口、序号和校验和的概念相信应该大家都能理解,这里就不再展开了,数据偏移、保留部分、紧急指针、选项,以及填充部分相对来说不太重要,感兴趣的可以自行了解。剩余相对重要的确认号、控制位(URG/ACK/PSH...)和窗口中,我们这里指讲和本文相关的前两种
如果了解过数据链路控制的相关知识,应该会知道ack的概念,就相当于我们这里的确认号,指的是我们期待接收到的分组的序号,因为我们发送的每一个分组都是为了期待对方给我们回应,为了避免接收到不正确的数据,就使用确认号机制,来明确我们需要的是什么数据
接着是控制位,一共有6种,其各自含义如下:
- URG:紧急指针有效
- ACK:确认有效
- PSH:请求急迫
- RST:连接复位
- SYN:同步序列号
- FIN:终止连接
在这6种中,我们用到的至少有3种:ACK、SYN,还有FIN,ACK表示分组是一个ACK响应分组,SYN表示分组是一个请求连接分组,FIN表示分组是一个终止连接分组。我们要注意的是,这些控制位之间并不一定是互斥的,比如ACK可以和SYN同时有效,一个分组中并不是只能存在一个控制位
4. 连接的建立过程
即使完全不了解TCP协议,也应该听说过“三次握手与四次挥手”的概念,三次握手指的就是连接创建的过程,四次挥手就是连接拆除的过程
我们先来看“三次握手”的过程,首先来看图示:
我们把主动建立连接的一方叫做客户端,另一方叫做服务端,整个过程可以简要表述为以下这样:
- 背景:客户端和服务端均处于CLOSED状态,等待进一步操作
- 准备:服务端进入LISITEN状态,监听客户端建立连接的请求
- 第一次握手: 客户端 向 服务端 发起建立连接的请求( SYN分组 ),自身进入SYN-SENT状态
- 第二次握手: 服务端 接收到 客户端 的请求,给 客户端 发送确认消息( SYN + ACK分组 ),自身进入SYN-RCVD状态
- 第三次握手: 客户端 向 服务端 发送最终确认消息( ACK分组 ),自身进入ESTABLISHED状态,可以进行数据传送
- 连接建立: 服务端 接受到 客户端 的确认消息,自身也进入ESTABLISHED状态,可以进行数据传送
我们可以在图中看到,每一次握手消息的发送都携带了seq,即序列号,这里需要明确一点,前两次握手消息均 不携带任何数据 ,但是仍占用一个序列号,最终传送数据的序列号就是基于握手消息的序列号(连接的一方发送的分组序列号是连续的,每次加1)
我们刚才指说道前两次握手消息不携带数据,并占用一个序列号,但是我们并没有提到第三次握手,是因为第三次握手是一个纯ACK消息,也就是单纯地进行响应(对服务端响应的消息进行响应),所以这个报文 如果 不携带数据,则不占用序列号(不占用序列号的意思是不使用新的序列号,而不是不携带序列号)
这里还有一种很少见的情况,就是双方同时建立连接,则彼此都发送SYN+ACK段,直接建立连接,但是这种情况毕竟很少,所以不再进行进一步的讨论
虽然连接创建的过程已经讲完了,但是可能很多人疑惑为什么创建的过程要设计的这么麻烦,这里我们从正反两方面来解释:
为什么不使用一次握手或者两次握手?
首先我们要知道,定义TCP协议的人肯定希望连接的创建过程尽量简化,现在三次握手已经能解决问题了,所以我们这里就不讨论为什么不使用四次握手或者五次握手
那为什么不使用一次握手?其实一次握手和两次握手遇到的问题都是一样的,所以我们放在一起说。一次握手和两次握手都有一个共同的特点,那就是接收到第一次握手的一方会立刻建立一条TCP连接通路,这就带来了一个问题。因为数据通信的理论之一是网络链路的不可靠性,所以我们不能以发送的数据包都能被接收作为协议的前提。TCP协议制订了一个超时重传的机制,如果发送的分组一定时间内没有响应,就会触发超时重传,进行分组的再次发送
我们再谈回一/两次握手,如果发送的第一次握手消息没有在指定时间内响应,客户端就会重新发送一份握手消息,现在我们想一下,如果连接被拆除后,因为网络原因第一次发送的分组才慢悠悠地传到服务端,则会导致创建一个新的TCP通路,显然这种情况会导致资源被浪费
如果用更正式的说法就是,为了防止已失效的连接请求报文段重新突然发送到服务端,导致建立不必要的连接,所以一次握手和两次握手均是不可行的,为了解决这种情况要么使用三次握手,要么就只能消耗更多的资源来进行额外的判断
为什么要使用三次握手
三次握手协议是有其本身的意义的,每一次握手消息都不是多余的。TCP连接创建的前提就是确认连接双方都能够正常的接收和发送消息,因为不存在第三方确认机构,所以只能由连接双方自行判断,每一次握手消息都是在进行询问与判断:
- 服务端接收到第一次握手消息:服务端确认了客户端可以 正常发送消息 (发送了握手消息)
- 客户端接收到第二次握手消息:客户端确认了服务端可以 正常接收消息 (接收了自己发送的握手消息),也可以 正常发送消息 (发送了握手消息)
- 服务端接收到第三次握手消息:服务端确认了客户端可以 正常接收消息 (接受了自己发送的握手消息)
这样,经过至少三次握手之后,连接的一方才能够对另一方接收和发送消息的能力有一个准确的判断,我个人认为这种解释相比于反证的方式要更为清晰
5. 连接的拆除
连接的拆除,或者叫连接的释放,是一个四次挥手的过程,我们同样来看图:
我们把主动提出关闭请求的一方叫做客户端,另一方叫做服务端(实际上一般情况下都是由客户端发起主动关闭),整个过程可以分为以下步骤:
- 背景:客户端和服务端都处于ESTABISHED状态,等待进一步操作
- 第一次挥手: 客户端 向 服务端 发送连接拆除的请求( FIN分组 ),自身进入FIN-WAIT-1状态
- 第二次挥手: 服务端 收到 客户端 的请求, 单向 关闭 客户端 的连接,向 客户端 发送响应消息( ACK分组 ),自身进入CLOSE-WAIT状态
- 单向数据传送: 客户端 接收到 服务端 的响应,不再发送数据(但是可以进行响应),仅由 服务端 发送一些剩余的数据,进入FIN-WAIT-2状态
- 第三次挥手: 服务端 的剩余消息发送完毕,向 客户端 发送连接拆除请求( FIN + ACK分组 ),自身进入LAST-ACK状态
- 第四次挥手: 客户端 收到 服务端 的请求,向 服务端 发送响应消息( ACK分组 ),自身进入TIME-WAIT状态
- 服务端连接断开: 服务端 收到 客户端 的响应消息,断开连接,自身进入CLOSED状态
- 客户端连接断开: 客户端 等待一段时间后,自行断开连接,自身进入CLOSED状态
有了三次握手的基础,相信四次挥手的过程理解起来会容易很多。挥手信息中发送的序列号和确认号我就不再进一步讲解了,图中已经很详细了,这里就提两点:
- 即使FIN分组中不携带任何数据,也要占用一个序列号
- 如果FIN + ACK分组中不携带任何数据,则仅占用一个序列号
- 第四次挥手的ACK分组不携带数据,也不占用序列号
四次挥手的过程与三次握手大同小异,我们这里只针对最重要的两个点进行讨论
为什么要使用四次挥手?
讲三次握手的地方已经证明了一次和两次握手的不可行性,但是我们很容易就能想到,为什么不采用三次挥手?其实三次挥手是可行的,但是在TCP中,一端停止发送数据后,仍可以继续接收数据,也就是半关闭的特性。为了这一特性,所以在挥手过程中插入了一个单向数据传送的过程,使得服务端可以在客户端主动关闭后继续发送数据
TIME-WAIT状态是什么
TIME-WAIT状态的字面意义是等待时间,和三次握手不一样,客户端发送最后的ACK段之后并没有直接进入CLOSED状态,而是等待一段时间后再自行关闭,这一段时间默认为2MSL(Maximum Segment Lifetime,即分组最长存活时间),可以在系统配置文件中手动更改。如果在TIME-WAIT期间收到了服务端的消息,依然可以进行响应
我们以客户端发送的最后一次ACK段为起点分析,如果这个ACK由于网络问题暂时没有被服务端接收到,服务端的定时器到期,会进行FIN段的重传,假设我们没有TIME-WAIT状态,而是在发送ACK后立刻进入CLOSED状态,资源都已经被销毁了,客户端无法对服务端的消息作出响应,这就导致服务端一方得不到对FIN的响应消息,造成了连接的异常
TIME-WAIT的存在理由知道了,那么为什么要设定为2MSL呢?刚才我们提到了,MSL指的就是一个分组在网络中存活的最大时间,换句话来说,如果一个分组经过了MSL还没有传送到目的方,那么完全就能够认为这个分组在网络中丢失,不可能再次被目的方接收。一个分组一来一回最多为2MSL,也就是说如果2MSL之后没有任何消息发送过来,就能够确信对方已经对自己的消息做出了回应
在实际生产中,如果发现系统存在大量的TIME-WAIT状态,可以适当调小这个值
补充
最后再提一个保活时间,并不是很重要的一个点,感兴趣的可以自行了解,其意义是当客户端故障后,服务端在保活时间后会发送探测消息,来决定是否释放连接
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 《Effective Java》学习笔记(一)——创建和销毁对象
- 销毁servlet
- Python:线程之定位与销毁
- OC内存管理--对象的生成与销毁
- Android可见APP的不可见任务栈(TaskRecord)销毁分析
- android – 如何告诉我的自定义FragmentPagerAdapter来停止销毁我的碎片?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Design systems
Not all design systems are equally effective. Some can generate coherent user experiences, others produce confusing patchwork designs. Some inspire teams to contribute to them, others are neglected. S......一起来看看 《Design systems》 这本书的介绍吧!