Linux网络编程---详解TCP的三次握手和四次挥手

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shanghx_123/article/details/83243429

我们知道,在TCP/IP协议中,TCP协议提供可靠的连接服务,是因为它有许多保证可靠连接的机制。可以分为3个方面:
1.确认应答机制:指的是不管哪一端发送数据都需要确认回复一下。
2.超时重传机制,发送后等待一段时间,不管是发送失败或者是还没有收到回复,那么就认为数据传输失败了;此时将会数据重传。这个超时是递增变化,但次数有限制,超过了重传次数就认为网络断开了。
3.序号/确认序号 :序号不一定从0开始,在一开始连接时,两端会协商好,然后根据发送的数据大小按字节进行排序,之后确认序号在序号的基础上加上数据的大小,发送给对端表明数据已经接收到。
例如A端从1开始发送数据,(这个序号1在进行数据传输前,A端和B端会协商好),发送了1000个字节数据,那么B端如果收到给A端回复时,确认序号就是1001,表示前1000个字节数据接受到),数据传到服务端之后,会根据序号将数据存放到B端缓冲区对应的位置上。
TCP因为要保证可靠传输,因此性能有很大的消耗,为了提高TCP传输性能,又需要有其他的一些机制。 这些机制包括:
1.滑动窗口
2.流量控制
3.拥塞控制
我们今天主要讲述第1个很重要的机制:确认应答机制。因为这个机制当中设计到许多知识 ,需要我们深刻理解。我们一步一步来看。

1.TCP分段的格式

TCP的协议数据单元被称为分段(Segment),TCP通过分段是交互来建立连接,传输数据。发出确认,进行差错控制,流量控制及关闭连接。分段分为分段头和数据两部分。分段头就是TCP为了实现端到端可靠传输所加上的控制信息,而数据则是指由高层即应用层来的数据。下面我们来介绍TCP协议的分段各部分的内容。

在这里插入图片描述

源端口(Source Port): 16位的源端口其中包含初始化通信的端口。源端口和源IP地址的作用是标示报文的返回地址。
目的端口(Destination port): 16位的目的端口域定义传输的目的。这个端口指明报文接收计算机上的应用程序地址接口。
序列号(序列码,Sequence Number): 32位的序列号表示该分段的数据在发送方数据流中的位置,用来保证到达数据顺序的编号。当SYN出现,序列码实际上是初始序列码(ISN),而第一个数据字节是ISN+1。在 SYN 标志位置0时,该字段指示了用户数据区中第一个字节的序号;在 SYN 标志位置1时,该字段指示的是初始发送的序列号。
解释: 如果发送方给接收方发送了一个连接请求(SYN)的分段,此时这分段的初始序列号假如是X,这个分段中数据占用的字节是1000个字节,那么发送给接收方的这个序列号就是X+1000。这个序列号就告诉了接收方这个数据在总的数据流中的所占的位置。即使发送方的数据不按顺序发送,因为有了这个序列号,那么接受方在存放数据时,也会将数据按顺序存放。

确认号(Acknowledgment Number): 目的主机给源主机返回确认号,使源主机知道某个或几个报文段已被接收。如果 ACK 控制位被设置为 1,则该字段有效。确认号等于顺序接收到的最后一个报文段的序号加 1,这也是目的主机希望下次接收的报文段的序号值。返回确认号后,计算机认为已接收到小于该确认号的所有数据。

数据偏移量(HLEN): 占4个比特位,表示该TCP头部有多少个32位bit(有多少个4字节),最大时是15;所以TCP头部最⼤⻓度是15 * 4 =60个字节。即TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远,它指示何处数据开始。
保留(Reserved): 6位值域,这些位必须是0。为了将来定义新的用途所保留。

紧急比特URG: 此位置位为 1时,表明启用了紧急指针字段,它告诉系统此报文段中有紧急数据,应尽快传送。

确认比特ACK: 仅当 ACK = 1 时,确认号字段才有效,TCP 规定,在连接建立后所有传达的报文段都必须把 ACK 置 1。

推送比特PSH: 请求急迫操作,即分段一到马上发送应用程序而不等到缓冲区满时才发送应用程序。

解释:当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下,TCP 就可以使用推送(push)操作,这时,发送方 TCP 把 PSH 置 1 ,并立即创建一个报文段发送出去,接收方收到PSH = 1 的报文段,就尽快地(即“推送”向前)交付给接收应用进程,而不再等到整个缓存都填满后再向上交付。

复位比特RST: 复位连接,置1时重建连接。如果接收到RST位时候,通常发生了某些错误。如主机崩溃,也可用于拒绝非法的分段或拒绝连接请求。

同步比特SYN: 与ACK合用以建立TCP连接。仅在三次握手建立 TCP 连接时有效。当 SYN = 1而 ACK = 0时,表明这是一个连接请求报文段,对方若同意建立连接,则应在相应的报文段中使用 SYN = 1和ACK = 1。因此SYN置1就表示这是一个连接请求和连接接受报文。

终止比特FIN: 用来释放一个连接。当 FIN = 1时表示发送方已无数据要发送,从而释放连接。但接收方仍可继续接收发送方此前发送的数据。
窗口(Window): 16位,这个值是发送方期望一次接收的字节数,此字段使用可变大小的滑动窗口协议用来进行流量控制。
校验位(Checksum): 16位,用于对分段首部和数据进行校验。通过将所有16个比特位以补码的形式相加,然后再对和取补码,正常情况下结果为0。源机器基于数据内容计算一个数值,收信息机要与源机器数值结果完全一样,从而证明数据的有效性。

紧急指针(紧急,Urgent Pointer): 占16位,仅在 URG = 1 时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据),即指出了紧急数据的末尾在报文中的位置,注意:即使窗口为零时也可发送紧急数据。如果 URG 为 1 ,则紧急指针标志着紧急数据的结束。其值是紧急数据最后 1 字节的序号,表示报文段序号的偏移量。例如,如果报文段的序号是 1000,前 8 个字节都是紧急数据,那么紧急指针就是 8 。紧急指针一般用途是使用户可中止进程。
选项(Option): 长度不定,但长度必须是一个字节。如果没有选项就表示这一个字节的域等于0。
介绍了TCP协议的分段,下来我们看看三次握手的过程。

三次握手的基本过程

在这里插入图片描述

准备阶段:刚开始客户端和服务端处于CLOSED状态,表示可以建立连接。当要建立连接时,此时服务端开始监听,并处于LISTEN状态。
在此之前,需要注意ACK、SYN、Seq(序号)、Ack(确认序号)的所表示的含义。
第一次握手:客户端将SYN设置为1,表示要建立一个新的连接,并随机产生一个序列值Seq=M(100),并将该数据包发给服务器客户端,进入SYN_SEND状态;

第二次握手:服务器收到数据包后,由标志位SYN=1知道客户端要建立一个连接,服务器将确认ACK置为1,并且将Ack=M+1(101)表示确认序号,之后,再将SYN置为1,并随机产生一个Seq=N(200),表示服务端也想和客户端建立连接,之后将该数据包发给客户端,服务器进入SYN_RECV状态;

第三次握手:客户端收到确认后,检查ACK是否为1,Ack是否为M+1(101),服务器有时候不同意建立一个连接(有可能达到了服务器建立客户端的上限),那么这里的ACK=0,表示服务端不同意建立连接。如果ACK=1,则服务端同意建立连接;并且在第二次握手时,服务端也向客户端发送了一个SYN请求,此时客户端收到在收到服务端的应答请求ACK的同时也收到了服务端想和客户端建立连接的SYN请求,此时客户端会也会给服务端回复请求。因此客户端将ACK置为1,Ack置为N+1(201),之后将数据包发送给服务端。当服务端收到数据包之后,,客户端和服务器都进入ESTABLISHED状态,建立连接完成,随后服务器和客户端之间就开始传输数据了!这就是大名鼎鼎的三次握手!!!!

有人肯定觉得这样的连接会很烦,建立连接一次,两次应该也可以搞定啊,为什么要偏偏是三次?或者又有人脑洞比较特别,会问咋不是四次呢?

在此之前,我们要知道TCP是要保证可靠数据传输的,也就是TCP是一个全双共通信协议。
我们来一一解释;
我们不妨举个打电话例子来帮助理解。
假如A给B打电话。
1.一次握手的情况: A问B,“你能听到吗?”。就这一句,也就表明是一次握手,此时如果B没有收到,那么A 接下来说的话 (也就是要发送的数据) B根本就收不到。可是,A并不知道B能不能收到,因为B没有给A回复,所以接下来A只管自己嗨(直接给B发送数据),而B一脸茫然,这样数据就发送不到B,就不能保证A和B对话的可靠性。

2.两次握手的情况: A问B,“你能听到吗?”,此时B收到,然后给A回复了一句“可以”,因为要保证B说的话A也要能收到,所以B也问A:你能听到我吗?”。好了,连接此时就已经结束。我们分析下,首先站在A的角度,A说的话是能保证B收到,因为B给A回复了;那么站在B的角度,B自己说的话不能保证A一定能收到,因为A并没有给B回复。所以如果只是两次握手,然后就进行数据传输,很明显,B发送的数据A是很有可能收不到的。

3.三次握手的情况: 在两次的基础上,已经能保证A说的话B能收到。如果再进行一次握手,即B所说的话能给被A回复,那么B也就知道自己说的话A是能收到的。所以三次握手是保证可靠连接的最小次数。

总结一下:就是前两次握手是保证A端数据的可靠传输,而后两次握手是保证B端数据的可靠传输。

上面的例子是一个三次握手的原因,是为了帮助理解。

我们下来解释如果不是三次握手而是两次握手,就进行数据传输,会造成什么影响?

关于为什么A还要发送一次确认呢?这主要是为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误。

所谓“已失效的连接请求报文段”是这样产生的。考虑一种正常情况,A发出连接请求,但因连接请求报文丢失而未收到确认,于是A再重传一次连接请求,后来收到了确认,建立了连接,数据传输完毕后,就释放了连接。A共发送了两个连接请求报文段,其中第一个丢失,第二个到达了B.没有“已失效的连接请求报文段。

现假定出现一种异常情况,即A发出的第一个连接请求报文段并没有丢失,而是在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达B.本来这是一个早已失效的报文段,但B收到此失效的连接请求报文段后,就误认为是A又发出一次新的连接请求,于是就向A发出确认报文段,同意建立连接,假定不采用三次握手,那么只要B发出确认,新的连接就建立了。
由于现在A并没有发出建立连接的请求,因此不会理睬B的确认,也不会向B发送数据,但B却以为新的运输连接已经建立了,并一直等待A发来数据,B的许多资源就这样白自浪费了, 采用三次握手的办法可以防止上述现象的发生,例如在刚才的情况下,A不会向B的确认发出确认,B由于收不到确认,就知道A并没有要求建立连接。

四次挥手的基本过程

在这里插入图片描述

第一次挥手: 客户端发起一个FIN和一个Seq=M,要求关闭客户端到服务器之间的数据传递,之后客户端进入FIN_WAIT1状态;

第二次挥手: 服务器收到FIN后,发送一个ACK=1,和ack=M+1表示知道了,进入CLOSE_WAIT状态;时只是客户端告诉服务端他没有数据要发送了,但并不代表服务端没有数据可以发送,也就是说此时服务端可能给另外的客户端发送数据。,这也就是为什么是四次握手的原因,后面我们再介绍。

第三次挥手: 当服务器的数据传递完后,再发送一个FIN和一个Seq=N来确定断开连接,等待最后一个ACK的到来;此时服务端进入LAST_ACK状态。

第四次挥手: 此时一直等待的客户端接收到服务端FIN信号后,表示服务器也要断开了,没数据传送了,便发送一个ACK和Ack=N+1,之后就进入TIME_WAIT状态,当服务端收到ACK后,服务端就彻底断开连接,客户端就被动断开了。接着客户端等待2个MSL(Max Segment Life, 报⽂最⼤⽣存时间时间) 后,才会进入CLOSED状态。这就是大名鼎鼎的四次挥手!!!!!!

为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

这是因为服务端的 LISTEN 状态下的 SOCKET 当收到 SYN 报文的建连请求后,它可以把 ACK 和 SYN(ACK 起应答作用,而 SYN 起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的 FIN 报文通知时,它仅仅表示对方没有数据发送给你了,但是你还可以给对方发送数据,也有这么种可能,你还有一些数据在传给对方的途中,所以你不能立马关闭连接,也即你可能还需要把在传输途中的数据给对方之后,又或者,你还有一些数据需要传输给对方后,(再关闭连接)再发送FIN 报文给对方来表示你同意现在可以关闭连接了,所以它这里的 ACK 报文和 FIN 报文多数情况下都是分开发送的。

为什么 TIME_WAIT 状态还需要等 2MSL后才能返回到 CLOSED 状态?

1.为什么要等?

这是因为虽然双方都同意关闭连接了,而且握手的 4 个报文也都协调和发送完毕,按理可以直接回到 CLOSED 状态(就好比从 SYN_SEND 状态到 ESTABLISH 状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的 ACK 报文会一定被对方收到, 因此对方处于 LAST_ACK 状态下的 SOCKET 可能会因为超时未收到 ACK 报文而重发 FIN 报文,此时如果客户端直接进入到ClLOSED状态,那么它是肯定不会收到第2个服务端发送过来的FIN包,所以这个 TIME_WAIT 状态的作用等待第2个FIN包,并且用来重发可能丢失的的最后一个 ACK 报文。

2.为什么是2个MSL?

等待两个msl时间,是为了让网络上迷途的报文彻底的消失,防止对新的连接造成影响。TIME_WAIT持续存在2MSL的话就能保证在两个传输⽅向上的尚未被接收或迟到的报⽂段都已经消失(否则服务器⽴刻重启, 可能会收到来⾃上⼀个进程的迟到的数据, 但是这种数据很可能是错误的)。

猜你喜欢

转载自blog.csdn.net/shanghx_123/article/details/83243429
今日推荐