网络编程——TCP协议面向连接的可靠的传输协议——建立与断开连接

TCP/IP不是一个协议,而是一个协议族的统称。里面包括了IP协议,IMCP协议,TCP协议,以及我们更加熟悉的http、ftp、pop3协议等等。

TCP协议的特点

TCP协议提供面向连接的,端对端的,字节流的,可靠的,传输层的服务。TCP是在IP网络层之上的传输层协议。

端对端(port到port):IP层负责数据包从一个IP到另一个IP的传输。IP层之上的TCP层加上端口后,就面向进程了,每个端口port都可以对应到用户进程。

可靠的:包括收包后确认包、丢包后重发等措施来保证可靠性。由于带宽和不同机器处理能力不同,TCP要能控制流量。

字节流:TCP把应用程序的数据,组包成报文段,发送数据时设置超时,确认被其他端点接收到的数据,给到达顺序杂乱的数据进行重新排序,丢弃重复的数据,提供端到端的校验和。 
TCP必须把字节流转换成一组IP可以携带的分组。这被称为【组包】(packetization)。
这些分组,包含序列号,序列号在TCP中代表了,每个分组的第一个字节,在整个数据流中的字节偏移,而不是分组号。
这允许分组在传送中是可变大小的,并允许它们组合,称为【重新组包】( repacketization)。
应用程序数据,被打散成TCP认为的最佳大小的块来发送,使得每个报文段,按照不会被分片的单个IP层数据报,的大小来划分。这与UDP不同,应用程序每次写入就产生一个UDP数据,其大小就是写入的大小(加上头部)。
由TCP传给IP的块称为报文段(segment)。
 

TCP维持了一个强制的校验和

该校验和涉及它的头部、任何相关应用程序数据和IP头部的所有字段。这是一个端到端的伪头部,它用于检测传送中,引入的比特差错。如果一个带有无效校验和的报文段到达,那么TCP会丢弃它,不为被丢弃的分组发送任何确认。然而,TCP接收端可能会对一个以前的(已经确认的)报文段进行确认,以帮助发送方计算它的拥塞控制。TCP校验和,所用的数学函数与其他互联网协议(UDP、 ICMP等)一样。对于大数据的传送,对这个校验和是否不够强壮的担心是存在的,所以仔细的应用程序应该应用自已的差错保护方法(更强的校验和或CRC)。或者使用某种中间层来达到同样的效果。

 

TCP给应用程序提供全双工服务

数据可在两个方向流动,两个方向互相独立。连接端点必须对两个方向,维持数据流的一个序列号。一个完整的TCP连接是双向对称的,数据可以在两个方向上平等地流动。
使用序列号,一个TCP接收端可丢弃重复的报文段和记录以杂乱次序到达的报文段。
TCP是一个字节流协议,TCP不会以杂乱的次序把数据发送给上层应用程序。TCP接收端会被迫先保持大序列号的数据不交给应用程序,直到缺失的小序列号的报文段被填满,才交给应用程序。


TCP的封装和头部结构

     

一个TCP连接的建立与终止

ACK,接收方给发送方发信号以确定自已已经接收到一个分组,这种方法称为确认acknowledgment,ACK。
如果一个ACK丢失了,发送方不能轻易地把这种情况与原分组丢失的情况区分开来,所以它简单地再次发送原分组。

SYN,用于初始化一个连接的同步序列号;
FIN  ,该报文段的发送方已经结束向对方发送数据;

三次握手过程

由客户端发起一个三次握手过程。客户端与服务器利用SYN报文段交换彼此的初始序列号(包括客户端的初始序列号和服务器的初始序列号)。在通信双方都发送了一个FIN数据包并收到来自对方的相应的确认数据包后,该连接终止。通过发送上述3个报文段就能够完成一个TCP连接的建立。它们也常称作三次握手。三次握手的目的不仅在于让通信双方了解一个连接正在建立吗,还在于利用数据包的选项来承载特殊的信息,交换初始序列号(Initial Sequence Number, ISN)。

断开连接四次挥手

因为TCP是全双工的,所以,发送方和接收方都需要关闭(Fin)和确认(Ack)。所以看上去就成了“4次挥手”。

一个TCP连接由一个4元组构成,它们分别是两个IP地址和两个端口号。更准确地说,
一个TCP连接是由一对端点或套接字构成,其中通信的每一端都由一对(IP地址,端口号)所唯一标识。

建立一个TCP连接需要3个报文段,而关闭一个TCP连接需要4个报文段。

服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
当收到对方的FIN报文时,仅仅表示对方不再发送数据了,但是我方还能接收和发送数据,收到Fin报文时,我方未必已把全部数据都发送给对方了,所以我可以发送一些数据给对方后,再发送FIN报文给对方,来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。

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

先进行关闭的一方执行主动关闭,而被动一方执行被动关闭。
TCP连接是全双工的,两个方向都要关闭。当一方完成数据发送任务后,发送FIN报文告知对方:我将要关闭连接。一方收到对方发来的FIN报文,意味着它不会再收到对方发来的数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。
 

TIME_WAIT

TIME_WAIT状态 是为了保证全双工的 TCP 连接正常终止。为了保证网络中迷失的数据包正常过期。

网络双方要进行四次握手才能成功断开连接,如果缺少了其中一个环节,连接将会处于假死状态,连接占用的资源将不会被释放。网络服务器程序要同时管理大量连接,所以,很有必要保证无用连接完全断开,否则大量僵死的连接,会浪费许多服务器资源。在众多TCP状态中,最值得 注意的状态有两个:CLOSE_WAIT和TIME_WAIT。

MSL(最大报文段生存时间)(max segment lifetime)

TCP协议中有TIME_WAIT这个状态,主要有两个原因:
1、防止上一次连接中的数据包,迷路后延迟到来,影响新连接(经过2MSL,上一次连接中所有的包都会失效消失)
2、可靠地关闭TCP连接。由主动关闭方发送的最后一个 ack(fin) ,有可能在网络里丢失,这时被动方会重新发fin报, 如果这时主动方处于 CLOSED 状态 ,就会响应 RST。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED状态 。

网络是不可靠的,被动关闭的一方在处于LAST_ACK状态下的Socket,可能在规定的超时时间内未能收到ACK报文,而重发FIN报文 ,TIME_WAIT 状态由主动关闭的一方来保持,它发出的数据包,到了 被动关闭方 后,由于关闭连接了,会响应 RST 包,这个 RST 包最长也会在最长 MSL 时长后到达主动关闭方,所以,主动关闭只要保持 TIME_WAIT 有 2MSL个时间 就能保证网络中所有连接的包都消失。


TCP连接的Close默认行为,是将socket标记为关闭然后立即返回。此时socket描述符不能继续被进程使用:作为read和write的参数。但是socket依然向对端发送已经缓存在队列中的数据,当所有数据都发生完毕后,socket进入TCP的四次包交换关闭时序。主动调用Close的一端在收到对端发来的FIN之后进入TIME_WAIT状态,并持续2MSL的时间。MSL一般是30秒到2分钟之间,所以2MSL为1~4分钟。socket再进入closed状态之前端口是一直被占用的,因此在TCP短连接大规模并发时很快便耗尽了可用的端口,因此就出现了10048或者99的错误。

为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)(max segment lifetime)才能返回到CLOSE状态?

一、保证TCP协议的全双工连接能够可靠关闭。
二、保证这次连接的重复数据段从网络中消失。

如果Client直接CLOSED了,那么由于IP协议的不可靠性或者其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。

再说第二点,如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。

TIME_WAIT状态也称为2MSL等待状态。在该状态中, TCP将会等待两倍于最大段生存期(Maximum Segment Lifetime, MSL)的时间,有时也被称为【加倍等待】。每个系统都为最大段生存期选择一个数值。它代表任何报文段在被丢弃前在网络中被允许存在的最长时间。
TCP报文段是以IP数据报的形式传输的,IP数据报拥有TTL字段和跳数限制字段。这两个字段限制了IP数据报的有效生存时间。 有的将最大段生存期设为2分钟。然而在常见实现中,最大段生存期的数值可以是30秒、 1分钟或者2分钟。这一数值是可以修改的。
在Windows系统,注册键值保存了超时时间:
HKLM\ SYSTEM\currentcontrolSet\ Services \Tcpip\parameters \TcpTimedWaitDelay
该键值的取值范围是30 - 300秒。
       当主动关闭方发送最终的ACK时,连接必须处于TIME_WAIT状态,并持续两倍最大生存期(MSL)的时间。才能够让TCP重新发送最终的ACK以避免出现丢失的情况。

       重新发送最终的ACK,是因为对方重传了它的FIN (它消耗一个序列号)。 TCP总是会重传FIN,直到它收到一个最终的ACK。当TCP处于等待状态时,通信双方将该连接(客户端IP地址、客户端端口号、服务器IP地址、服务器端口号)定义为不可重新使用。只有当2MSL等待结束时,或一条新连接使用的初始序列号超过了连接之前的实例所使用的最高序列号时[RFCⅡ22],或者允许使用时间戳选项来区分之前连接实例的报文段以避免混淆时[RFC6191],这条连接才能被再次使用。在这些系统中,如果一个端口号被处于2MSL等待状态的任何通信端所用,那么该端口号将不能被再次使用。许多实现与API都提供了绕开这一约束的方法。一个连接处于2MsL等待状态时,任何延迟到达的报文段都将被丢弃。因为一条连接是通过地址和端口号的4元组定义的。
       如果该连接处于2MSL等待状态,那么它在这段时间内将不能被重新使用。当这条正确的连接最终被建立起来后,这条连接之前的实例所传输的延迟报文段是不能被当作新连接的一部分来解读的。对于交互式应用程序而言,客户端通常执行主动关闭操作并进人TIME_WAIT状态,服务器通常执行被动关闭操作,并且不会直接进人TIME_WAIT状态。其中的含义是,终止一个客户端后立刻重新启动同一客户端,那么新的客户端也不能重新使用相同的本地端口号。客户端通常使用的是由操作系统分配的临时端口号,而且它们也并不关心被分配的端口号究竟是什么(由于一个客户端能够快速产生大量的连接,尤其是针对同一个服务器),因此它不得不在临时端口号供应紧张时,延迟一会儿来等待其他连接的终止。对于服务器而言,情况则大不相同。服务器通常使用一些知名的端口。如果我们终止一个已经建立了一条连接的服务器进程,然后立即尝试重新启动它,服务器不能为该程序的通信端分配对应的端口号(它将会收到一个“地址已占用”的绑定错误)。这是因为当连接进人2MSL等待状态时,端口号仍然是连接的一部分。根据系统对MSL数值的不同设定,服务器可能需要花费1 - 4分钟才能重新启动。我们可以利用sock程序观察这一场景。我们启动服务器,从客户端连接该服务器,然后终止服务器。

CLOSE_WAIT

CLOSE_WAIT是表示在等待关闭。当对方close一个SOCKET后,对方发送FIN报文过来,我方会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来的事情是查看我方是否还有数据需要发送给对方,如果有就继续发送发完,如果没有的话,我方也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以我方在CLOSE_WAIT状态下,需要完成的事情是等待我方发送完数据后,关闭连接。

LAST_ACK

是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。

RST包

RST(reset):重置连接、复位连接,用来关闭异常的连接。

在TCP协议中RST表示复位,用来异常地关闭连接,在TCP的设计中它是不可或缺的。发送RST包关闭连接的操作是,不必等缓冲区的包都发送出去,而是直接丢弃缓冲区中的包,发送RST包。而接收端收到RST包后,也不必发送ACK包来确认。

其实在网络编程过程中,各种RST错误其实是比较难排查和找到原因的。

TCP异常终止(reset报文)

TCP的异常终止是相对于正常释放TCP连接而言的,TCP连接建立是通过三次握手完成的,而TCP正常释放连接是通过四次挥手来完成,但是有些情况下,TCP在交互的过程中会出现一些意想不到的情况,导致TCP无法按照正常的四次挥手来释放连接。如果此时不通过其他方式来释放TCP连接的话,这个TCP连接将会一直存在,占用系统的部分资源。在这种情况下,我们就需要有一种能够释放TCP连接的机制,这种机制就是TCP的reset报文。reset报文是指TCP报头的标志字段中的reset位置一的报文。


TCP异常终止的常见情形

我们在实际的工作环境中,导致某一方发送reset报文(RST)的情形主要有以下几种:

1、客户端尝试与服务器未对外提供服务的端口建立TCP连接,服务器将会直接向客户端发送reset报文。

 

2、客户端和服务器的某一方在交互的过程中发生异常(如程序崩溃等),发生异常的一方将向对端发送TCP reset报文,告之对方释放相关的TCP连接。

3、接收端收到TCP报文,但是发现该报文,并不在接收端已建立的TCP连接列表内,则接收端直接向对发送端发送reset报文。

4、在交互的双方中的某一方长期未收到来自对方的确认报文,在超出一定的重传次数或时间后,会主动向对端发送reset报文释放该TCP连接。

5、有些应用开发者在设计应用系统时,会利用reset报文快速释放已经完成数据交互的TCP连接,以提高业务交互的效率。

什么时候发送RST包

建立连接的SYN包到达某端口,但是该端口上没有正在监听的服务。
TCP收到了一个根本不存在的连接上的分节。
请求超时。设置recv的超时时间。接收数据超时时,会发送RST包。

尝试手动发送RST包

使用shutdown、close关闭套接字,发送的是FIN,不是RST。
套接字关闭前,使用sleep。对运行的程序Ctrl+C,会发送FIN,不是RST。
套接字关闭前,执行return、exit(0)、exit(1),会发送FIN、不是RST。

以上几种方法,都不能发送RST包。 发送RST包,需要伪造数据包进行发送。

Reset报文的利用

1、安全设备利用reset报文阻断异常连接

安全设备(如防火墙、入侵检测系统等)在发现某些可疑的TCP连接时,会构造交互双方的reset报文发给对端,让对端释放该TCP连接。比如防火墙检测到黑客攻击的TCP连接,则它为被攻击端给黑客主机发送reset报文,让黑客主机释放攻击连接。

2 、利用reset报文实施攻击

安全设备可以利用reset报文达到安全防护的效果,黑客和攻击者也可以利用reset报文,实现对某些主机的入侵和攻击,最常见的就是TCP会话劫持攻击。

 

 

猜你喜欢

转载自blog.csdn.net/panjunnn/article/details/108761069