在TCP的六个标识符里,有一个代表RST,reset,但是翻译成重置连接似乎也不太妥当,报告错误也不太合适,作为一个优雅而又逻辑合理的协议,RST对于TCP中出现的错误而导致两端都进入错误处理有重要的作用。
情况没有按照预想的进行
有句诗叫做“不如意事常八九,能与人言无二三”,作为一个庞大,复杂,系统的协议,TCP通信中经常都会出现不是一帆风顺的情况。比如前面提过,在三次握手的第二阶段,如果在几次重发都失败的情况下,会回复一个RST包。正是TCP有这个设计才能让TCP称得上连失败了都处理的很优雅。
为什么要有这么一个RST包而不是在结束的时候直接粗暴的结束或者说啥也不发生的结束呢?还是先让我们看看现实中打电话的例子,现实中总有那种妈妈给自己的儿子女儿打电话,儿子女儿不耐烦的,甚至产生冲突。而这个时候如果儿女这一端简单的把电话放下,妈妈那一端就只能听到环境声,对于这边的情况完全不了解。先不说这种对于对端极不礼貌的做法,至少这样让对端感到很担心,不知道那一边是怎么情况。而即使你再生气但是你说,不说了,不说了,我挂了,然后挂上电话,哪怕是很重的摔下电话。至少对端可以从嘟嘟声中知道对端已经挂了电话,不用担心前面描述的那种担心了。
TCP也是一样,如果没有这个RST包,那么出现出现异常的对端只能等待,因为他根本可能知道对端怎么了。在资源极端重要的网络环境里,这样的做法不仅浪费资源更重要的是让TCP这种号称核弹都炸不毁的协议陷入了一种混沌的状态,就像C/C++中的野指针一样,无法管理无序而又混乱。RST包几乎涵盖在TCP每个状态中,下面在介绍什么时候会发送RST包之前,先得了解一下正常的状态下的TCP各种状态变迁,这有了解了正常才能更好理解异常嘛。
TCP状态变迁图
我一直认为在计算机领域,有两个状态机图最经典,一个是进程调度时候的进程状态变迁图,第二个就是TCP状态变迁图了。这个图清晰的表示了在使用TCP协议通信时,从建立连接到释放连接的每个过程,如果你能不靠任何外力的情况下画出这张图,那么不得不说你已经对TCP的每个阶段了如执掌了,如果再理解下TCP中的流量控制等等,那么其实关于这方面的面试你完全不用担心了。
废话不多说了,我从网上挑了一张我觉得我看着最舒服的版本:
这上面的所有状态在前面两节都说过了,除了Established状态,这个状态表示TCP处于连接传输数据的状态,算是一个比较稳定的状态。相比于建立连接和终止连接,这个状态在状态本身上并没有什么特别的,就像一条路修完之后,车子就上去跑就行了。但是在连接状态下,TCP的太多设计哲学就蕴含在这里面了,我们现实中的道路管理要能借鉴里面的一些思想,很多拥堵情况都能避免了。
言归正传,在这个图中,状态很多,为了演示下怎么样去看这种状态机状态图,我就叙述下怎么样从这个图中找到三次握手中的种种状态变迁。
状态机图主要有两个部分组成,框框表示某种状态,框框之间用线条连接,标识状态的变迁,而在这些线条之上往往会写着一些说明,这些说明被誉为激励条件,一个状态只有经过了某种条件的刺激或者激励才能变迁为另一个状态。打个比方吧,比如你在发呆,你处于空闲状态,这个时候我打你一下,给你一个刺激,你就会从空闲状态变迁为疼痛状态了。
所有的状态都从Closed开始,在这个图的叙述中,有两条说明,实现表示客户,虚线表示服务器,准确的来说,这种说法并不严谨,因为在TCP的通信过程中其实没有服务器和客户之分,两边都在同等的发送数据。而在这个图或者一般的表述中,主动发起连接的那一方一般会被称之为客户端,被动接受的那一段是服务器端。在了解了这些先决条件之后,那么就来一起找找这个图中的三次握手在哪里吧。
首先看Closed状态中的实线,也就是表示客户的状态,在右边,主动发送SYN之后,进入SYN_SENT状态,三次握手第一步完成。而在这之后,收到SYN和ACK包,然后发送ACK之后客户这边就进入了连接状态。
然后再看看另一条路,从Closed状态虚线进入listen状态,这里不需要任何激励条件就能完成,因为默认服务器一直在监听客户发来的请求,不然那你这个图就画不下去了。在收到SYN,发送SYN和ACK之后进入SYN_RCVD状态,也就是三次握手的第二步。然后就是继续收到对端的ACK之后,进入连接状态。
每个状态机图都能很方便的变成很多独立的小单元,这也是一个状态机图绘画的好与不好的一个标志。好的状态机图状态之间变迁关系清晰,彼此耦合性很小。而差的你就感觉在这种状态变化的指示下只会变成一团混乱。
仔细看一下这个状态机图,你会发现很多前面并没有提及的有趣的状态,他们中很多都是很边缘的情况,但是作为一个严谨的协议设计者,应该最大可能留下最小的漏洞。比如图中你会发现从listen状态可以直接变成SYN_SEND状态,回想一下前面说了服务器端会默认为是listen状态从而可以监听对端来的请求。但是如果这个时候服务器端主动发送了SYN,那么他其实就是我们定义中的“客户”了,所以图中用实线标识这种情况下会SYN_SEND状态。
另外一个在发起连接阶段的时候的特殊情况就是SYN_SEND和SYN_RCVD中间那条线,如果一个客户在进入SYN_SEND同时收到对端发送回来的SYN请求,通俗点说就是两端同时发起了连接,两边都是客户了,这个时候TCP不会就此终止,而是进入SYN_RCVD,然后再经历后面的过程之后,两边都进入了Established状态。
这就是这张图的上半部分,如果有兴趣可以看看下半部分整个的关闭流程作为联系。
RST状态变迁图
在了解了正常状态变迁之后,异常状态就不会显得特别的乱了,在网上我找到了这么一张图,我觉得画的特别好,就拿来借用了。
对比上面的图,这张图用红色的部分标识了发送RST的情况,但是也简化了一些上面有的状态变迁,不过这对于了解整个TCP过程中RST的状态变迁没有那么重要了。
从这个图中第一个可以学到的知识就是经历所有RST状态之后,连接都会进入Closed状态,也就是结束。RST的状态基本分成两种,超时和包丢失。
首先看看超时的情况,在图中就三种包重传超时,SYN,FIN和数据包,可以看到超时都是某种“主动的动作”。比如在三次握手的第二个阶段,重传SYN+ACK一直超时,服务器端就只能发送一个RST到对端了。同理在FIN超时和本身传输数据超时之后,RST也会发送到对方。
第二类是丢失,其实本质上上和上一种情况是一直的,在ACK丢失的情况下,在对端看来就是发送包超时,所以在这种情况下主要是接收RST。
那么在现实生活中,什么样的情况会导致超时呢?其实很多情况都可能发生,比如某一端程序突然崩溃,某一端突然没电了等等,这种情况下都会引起超时,从而会导致RST包的发送。如果用官方的语言总结下,RST包会在以下三种情况下被发送:
到达的端口不存在,比如说在客户端发送SYNC想建立连接的时候,对端并没有任何监听端口,那么就会发生超时,从而发送RST包。而在UDP这种没有连接的协议中,一个ICMP端口不可达消息就能解决这个问题(如果你对这些名词很陌生,可以看看前面的文章了)。
连接被异常终止, 比如说应用程序崩溃了。
检测半关闭连接,这个半关闭的概念其实在上一篇的时候有所提及,但是不详细,这里正好说明一下。半关闭连接主要发生在某一段已经关闭或者异常终止但是另外一段并不知道的情况下。比如在通信的过程中,突然拔掉服务器那边的网线,然后再重启服务器和其应用程序。这个时候连接就处于一个半关闭状态,此时客户机再向对方发一个消息,服务器端会回复一个RST消息,本端就知道刚才发生了什么。
说了这么多,言而总之其实RST主要作用有两个,报告本端异常,告知对方错误,记住这两句话就差不多记住了RST包的作用。