【计算机网络】——TCP三次握手和四次挥手详解



1、三次握手



1.1connect()、listen()和accept()三者之间的关系

在我之前的一篇博文中就有详细的提到过TCP的编程流程TCP详解及编程流程。在那篇博文中我们就详细提到了connect()、listen()和accept()这三个函数的使用。并且我们了解到了,连接的建立是在listen()之后accept之前
那么,具体的他们这三个函数内部的关系是怎样的呢?

1、connect()函数分析
【功能】
客户端主动连接服务器,建立连接是通过三次握手。

【注意】

  • 这个连接的过程是内核完成的,不是这个函数完成的,这个函数的作用仅仅是通知给linux内核,让linux内核自动完成TCP三次握手连接
  • 通常情况下,客户端的connect()函数会默认一直阻塞,直到三次握手成功或超市失败才返回

2、listen函数分析
【功能】
将该套接字和套接字对应的连接队列长度告诉linux内核。因此,可以看出,只要TCP服务器调用了listen(),客户端就可以通过connect()和服务器建立连接,而这个连接的过程是内核完成的
【注意】

  • listen()函数不会阻塞
  • 在被动状态的socket有两个队列,一个是正在进行三次握手的socket队列,一个是完成三次握手的socket队列。在握手完成后会从正在握手队列移到握手完成的队列,此时已经建立连接。

3、accept()函数分析
【功能】
从连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。



1.1三次握手的过程

在 TCP/IP 协议中,TCP 协议提供可靠的连接服务,采用三次握手建立一个连接。因为TCP连接的建立采用客户服务器方式。主动发起连接建立的应用进程叫做客户,而而被动等待连接建立的应用进程叫做服务器。
用三次握手建立TCP连接的流程图如下:
在这里插入图片描述
第一次握手
客户端执行connect函数,向服务器发送连接请求报文段,这时TCP报文首部的同步位SYN=1.这时==客户端进入SYN_SENT(同步已发送)==状态,等待服务器的确认

第二次握手
服务器收到请求报文段后,如果同意建立连接,则向A发送确认,在确认报文段中SYN=1,ACK=1。这时服务器就进入了SYN_RCVD(同步收到)状态客户端进入ESTABLISHED状态

第三次握手
客户端在收到服务器的确认过后,还要给服务器给出确认,确认报文段将ACK置1.TCP连接已经建立服务器都进入ESTABLISHED状态。完成三次握手,随后客户端与服务器之间就可以传输数据了。

三次握手的结果就是服务器知道了客户端发送数据的序列号,客户端也知道了服务器发送数据的序列号,并且双方都知道彼此知道了。



1.2三次握手的状态转换和安全问题

1、状态类型
1、半连接状态:发生在TCP三次握手过程中,客户端向服务器发起连接,服务器也进行了回应,但是客户端却不进行第三次握手
2、半打开状态:在TCP连接中,如果某一端异常关闭,则该连接处于半打开状态。
3、半关闭状态:当TCP链接中客户端向服务器发送 FIN 请求关闭,服务端回应ACK之后,并没有立即发送 FIN 给客户端,客户端就处于半关闭状态,此时客户端可以接收服务器发送的数据,但是客户端已经不能再向服务器发送数据。

2、SYN flood攻击

2.1 SYN flood攻击的含义
攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送syn包,服务器回复确认包,并等待客户的确认。
由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列(Linux默认的限制一般是256个),正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络阻塞甚至系统瘫痪。
在这里插入图片描述
2.2解决办法

  1. 修改TCP协议实现:一般在较新的的TCP/IP协议栈都对这一过程进行修正来防范syn攻击
  2. SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间



1.3三次握手的问题汇总

1、为什么需要三次握手?
假设现在有两个人A,B需要进行数据通信,那么就有下列的过程:

  • B---->A,B先给A说,我一会给你发的数据,数据开始序号为ISN,是10,你读数据的时候应该从10往后读,10前面的数据都是非法的。
  • A---->B,A给B说,好,我知道了,我一会给你发的数据,数据开始序号为,你读数据时应该聪聪20往后读,20前面都是非法的。
  • B---->A,B给A回复说,好,我知道了。

这时,A,B都知道了彼此的序列号,A,B也知道对方发过来的ISN正确的位置开始读数据,故都可以放下的发数据。功能如下:

  • 双方都知道彼此的的起始序列号,并且也知道对方知道。意思就是双方都做好了发送数据的准备工作,也知道对方准备好了。
  • 在握手的过程中,双方对初始化序号seq进行协商,确认

2、两次握手可以吗?
【情况假设之“已失效的报文段”】
A客户端发出的第一个连接请求报文段在某些网络结点长时间滞留了,但是并没有丢失,导致此报文段延误到连接释放后的某个时间才到达服务器B。这个请求连接报文段是一个已失效的报文段,但是B不知道,它以为A客户端又发出一次新的连接请求,于是就向A发出确认报文段,同意建立连接。以下针对三次握手和两次握手情况分析:

  • 两次握手:B向A发出确认报文段,但是A并没有发出建立连接的请求,所以它不会理会B发出的确认报文。但是B却以为新的连接已经建立,状态变为ESTABLISTED,并一直等待A发数据,用自己的资源去维护这个连接,这样B的资源就白白被浪费了。
  • 三次握手:B向A发出确认报文段,此时处于SYN_RCVD状态,A没发请求连接,自然不给B服务器确认报文段发出确认。B服务器由于收不到确认,就知道A并没有要求建立连接,就不会建立连接,状态不会变为ESTABLISHED

两次握手造成的影响

造成服务器资源的浪费:从上面的例子我们可以看出,不进行第三次握手的确认,服务器可能就会维护许多不成功的连接,这些连接都不能正常通信,只能白白浪费服务器资源。

3、在三次握手过程中,如果服务器一直收不到客户端的ack会发生什么?
服务器会给每个待完成的半连接都设一个定时器,如果超过时间还没有收到客户端的ACK消息,则重新发送一次SYN-ACK消息给客户端,直到重试超过一定次数时才会放弃。

4、初始序列号Seq为什么要随机初始化?
主要是为了保证网络安全,如果不是随机产生初始化序列号,黑客将会以很容易的方式获取到你与其他主机之间通信的初始化序列号,并且伪造序列号进行攻击。



2、四次挥手



2.1四次挥手的过程

在客户端与服务器通信完毕过后,主动断开方调用close,开始进行四次挥手。一般来说,主动断开方都是客户端。
在这里插入图片描述
第一次挥手
客户端进程先向TCP发出链接释放报文段,并停止再发送数据,主动关闭TCP连接。连接释放报文段首部FIN=1。C进入FIN_WAIT_1状态。
第二次挥手
服务器收到连接释放报文段后即发出确认,把确认报文段首部ACK置1。然后S进入CLOSE_WAIT(等待关闭)状态。C收到S的确认后,就进入FIN_WAIT_2(终止等待2)状态,等待服务器发出的连接释放报文段
此时的TCP连接处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端仍要接收。也就是说,从服务器到客户端这个方向的连接并没有关闭,这个状态可能会持续一些时间
第三次挥手
若S已经没有要向C发送的数据,其应用进程就通知TCP释放连接,这时S发出的连接释放报文段必须使FIN=1,。这时S就进入LAST_ACK(最后确认)状态,等待C的确认
第四次挥手
C在收到S的连接释放报文段后,必须对此发出确认。在确认报文段中把ACK置1。然后客户端进入到TIME_WAIT(时间等待)状态。
注意!!!

  • 现在TCP连接还没有释放掉,必须经过时间等待计时器设置的2MSL后,A才进入CLOSE状态。MSL叫做最长报文段寿命,即报文段存活最大时间
  • 服务器收到客户端的确认报文段过后就会进入close状态。服务器在撤销相应的传输控制块TCB后,就结束了这次的TCP连接,B结束TCP连接的时间要比客户端早一些



1.2四次挥手的问题汇总

1、为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
因为当S收到C的SYN连接请求报文后,可以直接发送SYN+ACK(其中ACK报文时用来应答的,SYN报文时用来同步的)。
但是关闭连接的时候S收到FIN报文后,很可能不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉C端“你发的的FIN报文段我收到了”
只有等到S端所有的报文段都发送完毕了,所有的数据也就处理完了之后,S端才能发送FIN报文表示自己能够断开连接。因此不能一起发送,所以需要四次挥手。

2、CLOSE_WAIT状态有什么影响?
当服务器收到客户端的FIN,并回复了ACK后,会进入CLOSE_WAIT状态,此时TCP链接处于半关闭状态。
CLOSE_WAIT状态一直存在就说明服务器没有调用close并没有发送FIN报文段
而服务期长期保持这个状态,就会一直占用这大量的socket文件描述符,大量的CLOSE_WAIT状态存在就会导致文件描述符被占用,一些客户端无法连接。

3、socket中的close是一次就关闭的吗?半关闭状态是怎么产生的?
使用close中止一个连接,但它只是减少文件描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为0的时候才关闭连接。
而客户端的半关闭状态是指收到服务器的ACK报文过后,此时客户端不能向服务器发送数据了,但是仍然可以接收到服务器发来的数据。

4、如果已经建立了连接, 但是客户端突发故障了怎么办?
TCP设有一个保活计时器。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常设置的是2个小时,若两个小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文段仍然没有反应,服务器就认为客户端出了故障,接着就关闭连接。

5、为什么需要TIME_WAIT状态?为什么 TIME_WAIT 状态还需要等 2MSL后才能返回到 CLOSED 状态?
TIME_WAIT状态指的是主动关闭的一方收到了对方发来的FIN报文并且本端发送了ACK报文确认后的状态。

首先,我们做一个测试,首先启动S,然后启动C,然后用Ctrl-C使S终止,这时马上再运行S,结果如下:
在这里插入图片描述
这是因为,虽然S的应用程序终止了,但是TCP协议层的连接并没有完全断开,因此不能再次监听同样的S端口,

  • TCP规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL的时间后才能回到CLOSE状态。
  • 我们使用Ctrl-C终止了S。所以S是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的S端口

再次,我们就可以回答为什么需要TIME_WAIT状态了。

  • 第一,为了保证客户端发送的最后一个ACK报文段能够到达服务器,即可靠的终止TCP连接因为这个ACK有可能会丢失,因而使处于LAST_ACK状态的服务器收不到释放连接的ACK确认,那么服务器就会重传一次FIN释放报文段,重新启动2MSL。最后,C和S都正常进入CLOSE状态。如果客户端在TIME_WAIT状态不等待一段时间,而是在发送完ACK报文段后直接释放连接,那么就无法收到B重传的FIN报文段,因而也不会再发送一次确认报文段,这样服务器就没有办法按正常步骤进入CLOSE状态。
  • 为了防止失效的连接请求报文段出现在本连接中客户端在发送完最后一个ACK报文段过后,再经过2MSL就可以使本连接持续的时间内产生的所有报文段从网络中消失,这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段

6、为什么是 2MSL?
2MSL的时间是从客户端收到FIN报文段后发送给服务器ACK开始计时的,考虑到重传的因素,那么就需要服务器再次给客户端传FIN+ACK报文段。
保证在两个传输方向上的尚未被接收或迟到的报文段都消失,理论上保证最后一个报文可靠到达,就需要2MSL,一个方向一个1MSL。

7、TIME_WAIT会带来哪些问题?

  • 作为服务器,短时间内关闭了大量的客户端连接,会造成服务器上出现大量的TIME_WAIT连接,占据大量的tuple(源IP地址、目的IP地址、协议号、源端口、目的端口),严重消耗着服务器的资源
  • 作为客户端,短时间内大量的短连接,会大量消耗C机器的端口,毕竟端口只有65535个,端口耗尽了,后续就无法启用新的连接了。

8、解决TIME_WAIT状态引起的bind失败的方法?
因为服务器的TCP连接没有完全断开之前是不允许重新监听,在某些情况下是不合理的。因为一般我们的服务器都会处理大量的客户端的连接,由于请求量很大所以可能导致TIME_WAIT的连接数很多,每个连接都会占用一个通信tuple.
所以我们可以使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但是IP地址不同的多个socket描述符。
在这里插入图片描述
加上setsockopt之后,ctrl+c终止服务器之后也可以马上启动服务器效果如下:
在这里插入图片描述
9、三次挥手可以吗?
可以,当特殊情况发生时,客户端断开连接发送FIN,服务器收到后,刚好已经处理完了所有的数据,这时就可以将ACK+FIN一起发送过去,这样就是三次挥手。

10、TIME_WAIT和CLOSE_WAIT的区别?
CLOSE_WAIT状态是被动关闭的一端在接收到另一端关闭请求过后并将ACK发送出去后所处的状态。这种状态表示:收到了对端关闭的情况,但是本端还没有完成工作,未关闭。
TIME_WAIT状态是主动关闭一端在本端已经关闭的前提下,收到对端的关闭请求并且将ACK发送出去所处的状态。这种状态表示:双方都已经完成工作,只是为了确保迟来的数据报能被识别丢弃,可靠的终止TCP连接。



3.1TCP编程中三次握手和四次挥手的全过程

在这里插入图片描述



3.2TCP状态转换图

从上面图中详细描述的三次握手四次挥手的过程,我们可以总结出以下的状态转换

【服务器状态转换】

  • [CLOSED->LISTEN]:服务器调用listen后就进入LISTEN状态,等待客户端向自己发起连接
  • [LISTEN->SYN_RCVD] 一旦监听到连接请求,就会将该连接放入内核中维护的连接等待队列中,病象客户端发送SYN+ACK报文确认已收到客户端的连接请求
  • [SYN_RCVD->ESTABLISHED]:服务器一旦受到了客户端的确认报文段,就进入ESTABLISHED状态,就可以进行数据读写了
  • [ESTABLISHED->CLOSE_WAIT]:当客户端主动关闭连接(调用close)服务器收到客户端发送的结束报文段FIN,服务器向客户端返回确认收到关闭连接的报文后,就会进入CLOSE_WAIT
  • [CLOSE_WAIT->LAST_ACK]:当服务器真正调用close关闭连接时,会向客户端发送FIN报文,自己则进入LAST_ACK状态,等待最后一个ACK的到来
  • [LAST_ACK->CLOSED]:服务器收到了客户端对FIN报文段响应ACK,至此服务器关闭连接成功

【客户端状态转换】

  • [CLOSED->SYN_SEND]:客户端调用connect,向服务器发送同步报文段,表示想与服务器建立连接,自己进入SYN_SEND状态等待服务器的响应

  • [SYN_SEND->ESTABLISHED]:connect调用成功,客户端收到服务器的响应报文段,自己进入FIN_WAIT_1状态,等待服务器的响应

  • [ESTABLISHED->FIN_WIAT_1]:客户端主动调用close,向服务器发送结束报文段,自己进入FIN_WAIT_1状态,等待服务器响应

  • [FIN_WAIT_1->FIN_WAIT_2]:客户端收到服务器对结束报文段的确认,则进入FIN_wAIT_2状态,开始等待服务器的结束报文段

  • [FIN_WAIT_2 -> TIME_WAIT]:客户端收到服务器发来的结束报文段,进入TIME_WAIT,并发出对服务器发送的结束报文的响应LAST_ACK

  • [TIME_WAIT -> CLOSED]:客户端要等待一个2MSL的时间,才会进入CLOSED状态。
    在这里插入图片描述
    【补充——RST复位报文段】
    在上面的状态转换图中,我们可以看见从状态SYN_RECV到LISTEN状态通过RST复位报文段实现。作用就是通知对方关闭连接或者重新建立连接。主要有三种情况

  • 当客户端程序访问一个不存在的端口时,目标主机给它发送一个复位报文段

  • 异常终止连接,TCP提供了异常终止一个连接的方法,即给对方发送一个复位报文段,一旦发送了复位报文段,发送端所有排队等待发送的数据都将被丢弃

  • 处理半打开连接。对于半连接状态的,此时如果对端向连接写入数据,则会收到本端回复的复位报文段

Guess you like

Origin blog.csdn.net/daocaokafei/article/details/112692157