网络编程——TCP连接的三次握手和“四次”挥手小结

版权声明:本文为CrazyMo_原创,转载请在显著位置注明原文链接 https://blog.csdn.net/CrazyMo_/article/details/88921939

引言

TCP 作为传输层最基本的通信协议,任何网络通信,都离不开TCP协议,无论是原生Sockect还是应用程协议HTTP、HTTPS都离不开TCP协议,所以了解TCP的的基本知识是进阶的重要途径,也是编写高性能网络协议的前提,同时TCP的机制的设计思想也是值得我们在开发中去借鉴和学习的。

一、TCP概述

众所周知,世界上所有HTTP通信都是由TCP/IP承载的,而TCP/IP协议是全球计算机及网络设备使用的一种最常见的分组交换网络分层协议。因此,客户端应用程序打开一条TCP连接就可以连接到世界上任务服务器上的应用程序。
这里写图片描述

1、TCP流是分段、由IP分组传送

TCP数据是通过名为IP数据报的小数据块来分发的,所以HTTP就是"HTTP over TCP over IP"这个协议栈的顶层了,而HTTPS则是HTTP的安全版本,即在HTTP和TCP之间插入了一个密码加密层(TLS或SSL)。
这里写图片描述

2、TCP连接的定义

TCP连接一般是通过四个值来辨识的:
<源IP地址 源端口号 目的IP地址 目的端口号>
这四个值一起可以唯一定义一条连接,两条不同的TCP连接不能拥有四个完全相同的地址组件值,但是不同连接的部分组件可以相同。

二、TCP的状态

TCP连接过程是状态的转换,促使发生状态转换的是用户调用:OPEN,SEND,RECEⅣE,CLOSE,ABORT和STATUS。传送过来的数据段,特别那些包括以下标记的数据段SYN,ACK,RST和FIN。还有超时,上面所说的都会时TCP状态发生变化。

状态名称 说明
CLOSED 初始状态。
LISTEN 表示服务器端的某个SOCKET处于监听状态,可以接受连接了。
SYN_RCVD 表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。
SYN_SENT 与SYN_RCVD遥相呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
ESTABLISHED 表示连接已经建立了。
FIN_WAIT_1 其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。
FIN_WAIT_2 实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。
TIME_WAIT 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也就会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
CLOSE_WAIT 表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是查看你是否还有数据发送给对方,如果没有的话,那么你也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。
LAST_ACK 它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。

摘自百科

三、TCP 的三次握手

所谓的“三次握手”(three times handshake或者three-way handshake)是对每次发送的数据量是怎样跟踪进行协商使数据段的发送和接收同步,根据所接收到的数据量而确定的数据确认数及数据发送、接收完毕后何时撤消联系,并建立虚连接。为了提供可靠的传输,TCP在发送新的数据之前,会以特定的顺序将数据包编号Seq,并在接到这些包传送到目标机之后的确认消息Ack才开始进行数据传输。因此TCP总是用来发送大批量的数据,当应用程序在收到数据后要做出确认时也要用到TCP。简而言之,发起网络请求时,会首先建立起底层的TCP连接,在客户端与服务器建立TCP连接的时候会进行三次握手;而在断开TCP连接的时候需要进行四次挥手,简记连三断四。最初两端的TCP进程都处于CLOSED关闭状态,而S端程序启动并自动创建TCB 开始监听,随时等待响应C端的请求。
在这里插入图片描述

1、第一次握手

C端的TCP进程主动打开连接创建TCB,向C端将标志位SYN置为1,随机产生一个值Seq=x([SYN] Seq=x,即头部的同步位SYN=1,初始序号Seq=x),并将该数据包发送给S端请求连接(不过SYN=1的报文段不能携带数据,但要消耗掉一个SEQ)进入SYN_SENT(syn package has been sent)状态,完成第一次握手,等待S端确认。

2、 第二次握手

S端收到请求报文段(由标志位SYN=1得知是C端请求建立连接)同意之后被动打开连接,马上向C端发送确认包([SYN,ACK] Ack=x+1,Seq=y)S端进入到SYN_RCVD状态(syn package has been received) ,完成第二次握手。

3、第三次握手

C端接到S端的确认包(会首先检查是否是回应自己第一次握手发的请求包,即会检查Ack是否等于x+1,如果确认操作系统则为改TCP连接分配缓存和变量)之后再向S端发送确认包([ACK] Ack=y+1,Seq=x+1)(ACK报文段可以携带数据,不携带数据则不消耗Seq),S端也会检查(检查是否是回应自己第二次握手发的确认包,即会检查Ack是否等于y+1,如果确认操作系统则为改TCP连接分配缓存和变量),完成第三次握手,TCP连接已经建立,C端进入ESTABLISHED状态,最后S端收到C端的确认包后也进入ESTABLISHED状态,双方开始再次利用TCP包进行数据传输

在这里插入图片描述

以上是理论方面去分析,接下来从我们抓的包来直观观察下TCP的三次握手:
在这里插入图片描述
C、S端初始关闭状态CLOSED,接着S端监听状态LISTEN,TCP三次握手流程可以简化为:

  • 发送SYN包到服务器并进入SYN_SENT状态,等待确认
  • 服务器收到并确认,再发送一个SYN包(即SYN+ACK包),进入SYN_RECV状态;
  • 客户端收到服务器的SYN+ACK包并向服务器发送确认包,此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

4、TCP 的数据传输

在这里插入图片描述

5、为什么要进行三次握手?为什么C端还要向S端发送一次确认包呢?

第一次由C端发起的握手,如果S端接到请求,则说明C端的发送能力没有问题。但是仅仅是知道C端的发送能力,还需要确认C端的接收能力,因而S端会向C端发送第二次握手,此时C端就确认了两件事,S端发送和接收能力都没有问题以及自身的发送能力,但是呢S端并不知道C端的发送能力,所以需要第三次握手,而且三次握手机制,还可以防止已失效的连接请求报文段突然又传送到了S端,因而产生错误。假如在C端发出连接请求,但因连接请求报文暂时丢失而导致S端未收到确认,于是C端再重传一次连接请求,第二次S端接收到并进行了确认,建立了连接进行数据传输,完毕后,就释放了连接。而在这种情况下C端向S端发出了两个连接请求报文段,其中第一个暂时丢失,第二个到达了S端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达S端,此时S端会误认为C端又发出一次新的连接请求,于是就向C端发出确认报文段,而此时C端已经不需要与S端通信了,S端会一直等待C端发送数据,因此不采用三次握手的话,只要S端发出确认,就建立新的连接了,有可能导致资源浪费。

6、Server端易受到SYN攻击?

S端的资源分配是在第二次握手时分配的,而客户端的资源是在完成第三次握手时才分配的,所以服务器容易受到SYN洪泛攻击,SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向S端不断地发送SYN包,S端则回复确认包,并等待Client确认,由于源地址不存在,因此S端需要不断重发直至超时,而这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。可以通过降低主机的等待时间使主机尽快的释放半连接的占用,短时间受到某IP的重复SYN则丢弃后续请求来防范SYN攻击。

四、TCP“四”次挥手

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接,收到一个 FIN只意味着这一方向上没有数据流动,而一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。HTTP是短连接的协议,所以每次通信完成时,TCP都会释放连接,在释放TCP连接的时候会发送四次挥手
在这里插入图片描述
举个例子假设S端发起中断连接请求(即发送FIN报文),C端接到FIN报文后,就知道:“S端没有数据要发给自己了”,但是如果C端还有数据未处理完成就不必马上关闭socket,还可以继续发送数据,但你先发送ACK告知S端"你的请求我收到了,但是我还没准备好,请继续你等我的消息"。此时S端就进入FIN_WAIT状态,继续等待C端的FIN报文。当C端确定数据已处理完毕不会再向S端发送数据时才向S端发送FIN报文,告知S端"好了,我这边数据发完了,准备好关闭连接了"。但是C端不确定S端是否已经收到了,所以会在收到S端的ACK之后才会进入CLOSED状态,而S端则继续等待了2MSL后依然没有收到回复,则证明C端已正常关闭,于是S端也就关闭了,TCP连接释放。

1、TCP的第一次挥手

首先是主动断开连接方A向被动断开连接方B发送连接释放包[FIN,ACK] Seq=j Ack=k),并停止再向B发送数据,A进入FIN_WAIT_1(完成等待1)状态,等待B的确认…

2、TCP的第二次挥手

B方接到连接释放包之后,立马响应并向A方发送确认包[ACK] Seq=m Ack=j+1),B端进入CLOSE_WAIT(关闭等待)状态,而A收到B的确认后,进入FIN_WAIT_2(完成等待2)状态,等待B发出的连接释放报文段,此时的TCP处于半关闭状态(即单向关闭)…

3、TCP的第三次挥手

在B方确认自己不再向A方发送任何数据之后,马上向A方法发送连接释放包([FIN,ACK] Seq=z ,Ack=j+1)告知A自己也不再向A发送任何数据了,B进入LAST_ACK(最后确认)状态,等待A的确认…

4、TCP的第四次挥手

A收到B的连接释放报文段后立即响应发出确认包[ACK] Seq=j+1,Ack=z+1),A进入TIME_WAIT(时间等待)状态,此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,A才进入CLOSED状态。

最长报文寿命MSL(maximium segment lifetime),由官方RFC协议规定的2个MSL就是2分钟。

在这里插入图片描述
以上是理论方面去分析,接下来从我们抓的包来直观观察下TCP的四次挥手:
在这里插入图片描述

注意:四次挥手也并不总是四次挥手,中间的两个动作有时候是可以合并一起进行的,这个时候就成了三次挥手,主动关闭方就会从fin_wait_1状态直接进入到time_wait状态,跳过了fin_wait_2状态。而且前面所述的算法无论是分包还是发包都是由底层操作系统的协议栈去实现的,对于目前绝大部分的很多所谓的自定义协议都是在TCP/UDP的基础上再封装而已。

TCP的“四”次挥手可以简化为(以S端主动断开连接为例):C、S端初始ESTABLISHED状态——>S端主动断开向C端发送FIN包,S端进入FIN_WAIT_1状态——>C端马上响应发送ACK包进入CLOSE_WAIT状态——>S端接到C端的响应ACK包进入FIN_WAIT_2状态——>C端确认不再发送任何数据到S端,向S端发送FIN包,进入LAST_ACK状态——>C端接到S端的响应进入CLOSE状态——>S端等待2MLS之后进入CLOSE状态

5、为什么A在TIME-WAIT状态必须等待2MSL的时间?

它是主动关闭的一方在回复完对方的挥手后进入的一个长期状态,这个状态标准的持续时间是4分钟,4分钟后才会进入到closed状态,释放套接字资源。一方面可以保证重传最后一个ack报文,确保对方可以收到。因为如果对方没有收到ack的话,会重传fin报文,处于time_wait状态的套接字会立即向对方重发ack报文;另一方面在这段时间内,该链接在对话期间于网际路由上产生的残留报文(因为路径过于崎岖,数据报文走的时间太长,重传的报文都收到了,原始报文还在路上)传过来时,都会被立即丢弃掉。4分钟的时间足以使得这些残留报文彻底消逝,防止“已失效的连接请求报文段”出现在本连接中可能会干扰新的链接。

6、为什么连接的时候是三次握手,关闭的时候却是四次握手?

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手,首先主动断开连接A端向被动断开连接B端发送FIN包开始第一次挥手,以A端为服务器,B端为客户端为例,客户端接到这个FIN包之后就明确知道了服务端不再向自己发送数据了,客户端马上发送确认包告知服务器已收到了执行第二次挥手,但是可能由于此时客户端的数据还未处理完成,在第二次挥手之后还需要经过一些清理等步骤,因此只有在确定不需要向服务端发送任何数据之后才会向服务端发送FIN包进行第三次挥手,而服务器接到FIN包就之后就知道客户端再也不会像自己发送任何数据了,就把响应包发送给客户端完成第四次挥手。

小结

本文仅对一些简单的基础知识点进行了小结,所以以上还只是TCP的冰山一角,路漫漫其修远兮,吾将上下而求索,敬请期待…最后感谢依然范儿特西的动图。

猜你喜欢

转载自blog.csdn.net/CrazyMo_/article/details/88921939