0%

计算机网络(5)——TCP

概念

TCP是计算机网络中运输层的一个协议。他有一下几个特点:面向连接、点对点、全双公服务。

  • 面向连接:当一个服务器向另一个服务器发送数据的时候,必须要先通过握手建立连接,才能发送数据。
  • 点对点:这保证数据的传输只有单个的发送方和单个接收方。和UDP的广播和多播是不一样的。
  • 全双工服务:表示建立TCP连接的两个主机既可以发送数据,也可以接收数据。

上面的三个特点保证了TCP的连接是可靠的。

可靠性

具体来讲,TCP/IP详解一书中提到了多个TCP保证可靠性的方式,我将它分为以下两类,一类从数据传输来说,一类是对下层ip协议的可靠性

  • 数据传输:1. tcp在数据传输时,将数据分割成小的数据块,分块发送可以保证在数据传输的时候,如果出现错误需要重穿,只需要将出错的一段重新发送即可,不需要将整个数据包重新上传。

  • 数据传输:2. 定时器机制——当TCP发出一个段时,他会启动一个定时器,等待目的端确认收到这个报文段。如果不能够及时收到确认的话,将重新发送这个报文段。

  • 数据传输:3. 当TCP收到发自TCP连接另一端的数据的时候,它将发送一个确认,这个确认不是立即发送,通常会推迟几分之一秒,这是因为,我们希望能够将数据确认和接下来需要发送的数据一起发送给另一端,这样可以节省发送的次数。

  • 数据传输:4.流量控制,TCP连接的每一端都会有一个固定大小的缓冲空间。这个缓冲空间可以保证发送过来的数据不能及时处理,就会放在缓冲空间中,可以提高吞吐率。即使有缓冲区也有可能会导致缓冲区溢出,所以TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据,这将防止较快主机致使较慢主机的缓冲区溢出。

  • 数据传输:5.TCP将保持它的首部和数据的校验和。

  • 数据传输:6.TCP对传输字节流的内容不做解释。TCP不知道传输的数据字节流是二进制数据还是ASSCII字符。字节流的解释交由引用层来处理。

  • 数据传输:7.当发送端从引用层发送多个字节时,TCP会按照自己的方式将字节转换为小数据块。比如一方的应用程序先传10字节,又传20字节,再传50字节,连接的另一方无法知道发送方每次发了多少字节。接收方可以分4次接收这80个字节,每次接收20字节。

  • ip可靠性保证:1.失序重排:既然TCP报文段作为IP数据包来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达可能失序。如果必要,TCP将对收到的数据进行重排序,将收到的数据以正确的顺序交给应用层。

  • ip可靠性保证:2.重复丢弃:IP数据报会发生重复,TCP的接收端必须丢弃重复的数据。

TCP结构

upload successful

TCP报文主要包括首部和数据两个部分,具体的结构如上图所示,下面我们来具体介绍一下这几个部分。

  • 端口号:每个TCP端都包含两个16位的源端和目的端口号,用于寻找发送端和接收端的进程。这两个值加上IP首部的源端IP地址、目的端IP地址可以唯一确定一个TCP连接。我们通常将IP地址+端口号一起成为socket(端口号)。既然是16位的也就是说端口号最大为2^16-1;

  • 序号:序号用来标示从TCP发送端向TCP接收端发送的数据字节流,他标示在这个报文段中的第一个数据字节的序号。如果将字节流看作在两个应用程序间的单项流动,则TCP用序号对每个字节进行计数,序号是32bit的无符号数,序号达到2^32-1后又从0开始,要注意的是,SYN和FIN标示位是会消耗一个序号。也就是说即使数据是空的,当SYN和FIN的被标志的时候,序号仍然会加1.

upload successful

比如我们发送端要给接收端发送9000字节的数据,我们将这个9000字节的数据进行切分成2500大小的数据报文。那么序号就表示位每一个数据的第一个字节,比如第一个数据的序号就为1,第二个位2500。

  • 确认序号应当是上次成功收到数据字节序号加1.只有ACK标志为1的时候确认序号字段才有效。
    我们还用上面的图进行解释,当接收端收到发送端发来的第一个数据报,并已经确认之后,当接收端向发送端发送数据的时候,接收端希望下一次收到的报文的序号为2500,这样数据才能接上,所以确认序号的值就是2500.

  • 发送的ACK是不占用任何序号的,因为32bit的确认序号和ACK标志一样,总是TCP首部的一部分,因此我们看到,一旦一个连接建立起来,这个字段总是被设置,ACK标志也总是被设置为1.

  • TCP可以表述为一个没有选择确认或者否认的滑动窗口协议,我们说TCP缺少选择确认是因为TCP首部的确认序号表示发送方已经成功接收的字节,但还不包含确认序号所指的字节。当前还无法对数据流中选定的部分进行确认。如下图所示:

upload successful
发送端向接收端发送1000~1999的数据,接收端发送确认序号2000,接着接受端收到3000~3999的数据,但是这不是接收端想要的数据,由于无法选择确认后面的序号,所以只能重新发一次确认序号2000,表明没有收到序号为2000的数据。
没有否认的意思是,如果接收端收到想要的数据,但是校验和不通过,因为不能直接发送否认序号,所以只能发送一个确认序号为2000的新的请求。

  • 首部长度给出首部32位的数目,需要这个值是因为任选字段是可变的,这个字段占4bit,因此TCP最多有60字节的首部,然而,没有任选字段,正常的长度是20字节。因为TCP单位是32位也就是4字节,首部长度占4bit最大可以表示15,15x4=60,所以TCP首部最大60字节。

  • TCP中的标志位有6个,它们中多个可以同时被设置为1.
    URG 紧急指针有效
    ACK 确认序号有效
    PSH 接收方应该尽快将这个报文交给应用层
    RST 重建连接
    SYN 同步序号用来发起一个连接。
    FIN 发端完成发送任务。

  • TCP的流量控制由连接的每一端通过申明窗口大小来提供。窗口大小为字节数,起始与确认序号字段指明的值,这个值是接受端正期望接收的字节。窗口大小是16bit字段,因而窗口大小最大为65535字节。

  • 检验和覆盖了整个TCP报文段。

  • 最常见的可选字段是最长报文大小,又称为MSS。每个连接方通常都在通信的第一个报文段中指明这个选项。它指明本端所能接收的最大长度的报文段。

  • 我们注意到TCP报文段的数据是可选的。当一个连接建立连接和连接终止时,双方交换的报文端只有首部,在处理超时情况下,通常也会发送空数据的报文段。

TCP连接的建立和终止

TCP是一个面向连接的协议。无论哪一个方向另一方发送数据之前,都必须先在双方之间建立一条连接。

TCP连接三次握手

TCP的连接,客户端会向服务端发送一个连接请求,接着服务端会向客户端发送一个请求确认,最后客户端会继续向服务端发送一个请求确认,这就表明了两个主机之间完成了TCP的连接,也叫三次握手。
如下图所示:
upload successful

    1. 请求端发送一个TCP的SYN标志位置1的包,指明客户端打算连接的服务器的端口,以及一个初始的序列号x,保存在包头的序列号字段里面。此时进入SYN_SEND阶段。
    1. 服务端发回包含服务器的初始序号Y的SYN报文段作为应答。同时,将确认的初始序列号ISN加1,放在确认序号中,即X+1.发送完毕后服务端进入SYN_RECV阶段。
    1. 客户端必须将确认序号设置为服务端的ISN加1以对服务器的SYN报文段进行确认。

以上三步,我们称之为三次握手,值得注意的是,三次握手中SYN置为1只出现在前两个连接中,ACK置为1只出现在后两个连接中。选择项MSS只在SYN报文中出现,最终由客户端和服务毒案共同协议,如果两个mss不一样,则选择最小的报文段,如果不接受对方的MSS选择,则MSS就定为默认值536.一般来说,如果没有分段发生,MSS还是越大越好。报文段越大允许每个报文段传送的数据就越多,相对IP和TCP首部就有更高的网络利用率。当TCP发送一个SYN时,或者是因为一个本地应用进程想发起一个连接,或者是因为另一段的主机收到了一个连接请求,他能将MSS值设置为外出接口上的MTU的长度减去固定的IP首部和TCP首部长度。对于一个以太网可以达到1460字节。当MSS的值确定之后,以后的数据交换都不能超过MSS的值。

序列号ISN的确定并不是从0开始的,而是根据按照随时间增加而不断增加的,如果在某一时刻请求端发送请求,他会根据某个函数获取初始的ISN的值,当然不同的操作系统ISN计算方式不同。有些攻击者,可以根据定时的发送请求,来根据两个请求的时间差计算出操作系统使用的是哪一个。

连接超时
很多情况下会导致无法建立连接,一种情况是服务器主机没有处于正常状态。这个时候客户端每隔一定的时间会发送一次请求连接,直到到过一定的阈值。这个时间间隔会越来越大。

二次握手和四次握手
TCP建立连接的时候需要进行三次握手,才能确定连接的双方都能够正常通信,为什么不用两次握手或者四次握手呢。我们知道双方在建立连接的时候,实质上是确定双方的序号以及MSS的大小。双方需要知道自己首部的序号是否已经同步,这样才能在后面发送数据。

两次握手

upload successful
我们想以下,在进行两次握手的时候,发送端想接收端发送SYN的连接请求包,并带了自己的ISN序号,当服务端收到请求,并进行回应。服务端会把自己的SYN序号发给客户端,此时客户端已经知道服务端已经确认了通信,并保证从客户端想服务端发数据是可靠的,但是我们知道TCP是全双工的通道,我们只进行两次握手,服务端是无法知道自己发送的SYN包是否到达了数据库,不知道自己往客户端发送数据的通道是否可靠。如果这个SYN包丢失了,A和B的初始序列号无法达成一致的。

TCP的设计者将SYN这个同步标志SYN设计成占用一个字节的编号(FIN标志也是),既然是一个字节的数据,按照TCP对有数据的TCP segment必须确认的原则,所以这里客户端必须给服务端一个去二人,以确定A已经接收到B的同步信号。

那么三次握手是如何保证可靠的呢,如果客户端发给服务端的ack包丢失了怎么办。客户端会重传这个ACK吗?不会!TCP不会为没有数据的ACK超时重穿。此时服务端会重传自己的SYN同步信号,一直到A的ACK为止。

第一个包,即A发给B的SYN 中途被丢,没有到达B

A会周期性超时重传,直到收到B的确认

第二个包,即B发给A的SYN +ACK 中途被丢,没有到达A

B会周期性超时重传,直到收到A的确认

第三个包,即A发给B的ACK 中途被丢,没有到达B

A发完ACK,单方面认为TCP为 Established状态,而B显然认为TCP为Active状态:

a. 假定此时双方都没有数据发送,B会周期性的超时重传,直到收到A的确认,收到之后的B的TCP连接也为Established状态,双向可以发包。

四次握手
我们再来看一下四次握手,如图

upload successful

看起来很简单,就是将接收端发给客户端的syn包,拆分成了两份。这也很容易就能够看出来,这样的效率并不高。三次握手就可以提高效率。

TCP连接四次挥手

upload successful
TCP连接的建立需要3次握手,但是TCP的终止是需要4次挥手,这是由于TCP的半关闭造成的,因为TCP是一个全双工的连接,每个方向需要分别关闭通道。当一方结束数据传输的时候,就会发送一个FIN包来终止这个方向的连接。当接收端收到一个FIN包的时候,只是说明了来自这个方向的数据传输终止了,但是接收端仍然可以向发送端发送数据。当接收端结束数据传输的时候,他会向客户端发送一个FIN包,用来终止连接。

  • 第一次挥手:数据传输结束后,客户端向服务端发送一个FIN包,并停止发送数据,此时FIN=1,seq=u;
  • 第二次挥手:客户端收到FIN包后,会发送一个确认请求,此时ACK=1,seq=v,ack=u+1。此时服务端处于半关闭状态,客户端收到确认包以后就不会再向服务端发送数据,而服务端仍然会向客户端发送数据。
  • 第三次挥手:若服务器已经没有要向客户端发送的数据,其应用进程就通知服务器释放TCP连接。这个阶段服务器所发出的最后一个报文的首部应为:FIN=1,ACK=1,seq=w,ack=u+1。
  • 第四次挥手:客户端收到连接释放报文段之后,必须发出确认:ACK=1,seq=u+1,ack=w+1。 再经过2MSL(最长报文端寿命)后,本次TCP连接真正结束,通信双方完成了他们的告别。
    在这个过程中,通信双方的状态如下图,其中:ESTAB-LISHED:连接建立状态、FIN-WAIT-1:终止等待1状态、FIN-WAIT-2:终止等待2状态、CLOSE-WAIT:关闭等待状态、LAST-ACK:最后确认状态、TIME-WAIT:时间等待状态、CLOSED:关闭状态

为什么在TIME_WAIT后必须等待2MSL时间呢?

  1. 为了保证客户端(我们记为A端)发送的最后一个ACK报文段能够到达服务器端。这个ACK报文段有可能丢失,因而使处在LASK—ACK端的服务器端(我们记为B端)收不到对已发送的FIN+ACK报文段。B会超时重传这个FIN+ACK报文段,而A就能在2MSL时间内收到这个重传的FIN+ACK报文段。接着A重传一次确认,重新启动2MSL计时器。最后,A和B都正常进入到CLOSED状态。如果A在TIME_WAIT状态不等待一段时间,而是在发送完ACK确认后立即释放连接,那么就无法收到B重传的FIN+ACK报文段,因而也不会再发送一次确认报文段,这样,B就无法正常进入CLOSED状态。

  2. 我们都知道,假如A发送的第一个请求连接报文段丢失而未收到确认,A就会重传一次连接请求,后来B收到了确认,建立了连接。数据传输完毕后,就释放了连接。A共发送了两个连接请求报文段,其中第一个丢失,第二个到达了B。假如现在A发送的第一个连接请求报文段没有丢失,而是在某些网络节点长时间都留了,以至于延误到连接释放后的某个时间才到达B,这本来是已失效的报文段,但B并不知道,就会又建立一次连接。而等待的这2MSL就是为了解决这个问题的,A在发送完最后一个确认报后,在经过时间2MSL,就可以使本链接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。

为什么是四次挥手
我们都知道TCP是全双工,数据传输是双向的,如果客户端完成了数据的传输,发起主动关闭连接,此时服务端可能没有完全结束数据传输,如果只进行三次挥手,那么要求数据结束传输必须是同时的,这就会强制终止服务端的传输,这样数据的传输是不完整的。另一点,如果进行五次以上的挥手操作,一定会造成资源的浪费,使效率低。

半关闭状态
TCP提供了连接的一段在结束后还能够接收来自另一端数据的能力。这就是所谓的半关闭。正如我们早些时候提到的只有很少的应用程序使用它。
为了使用这种特性,编程接口必须为应用程序提供一种方式来说明,我已经完成了数据传送,因此发送一个文件结束给另一端,但我还想接收另一端发来的数据,直到它给我发来文件结束。

TCP连接异常问题

我们之前介绍了三次握手建立连接,四次挥手释放连接。但是如果出现了连接异常现象,TCP是如何处理的呢,这里就会用到TCP标志位RST。

到不存在的端口请求连接
当一个TCP的一个请求连接到达服务器,发现请求的端口未对外开放,这时服务端会回传一个RST报文段,告知客户端端口号不可达。

upload successful

TCP连接异常终止

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

upload successful

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

upload successful

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

upload successful

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

upload successful

异常终止有两个好处,丢弃所有待发的数据并直接发送复位的报文段,RST的接收方会区分对方是正常关闭还是异常关闭。

处理半连接状态
如果tcp的一段已经关闭或者异常终止连接而对方却不知道,我们将这样的连接称之为半打开。任何一端的主机异常都有可能会导致发生这种情况。只要不再半打开的连接上传输数据,仍然处于连接的一方就无法知道对方出现异常。
半打开的原因往往是应为连接的一端突然断电,而不是正常的程序关闭出现后再关机,比如说当客户端结束任务,直接拔掉电源。等到重新开机的时候,之前的连接信息全部丢失了,而服务端如果没有发送数据,就不会知道连接已经断开。此时如果服务端向客户端发送一条信息,由于客户端丢失了之前的连接信息,所以它并不知道报文段中的连接,这时TCP的处理方式就是发送一个RST报文来进行复位,告知服务端连接已经终止。

参考文献

TCP 为什么是三次握手,而不是两次或四次?
TCP/IP中MSL详解
简述TCP连接的建立与释放(三次握手、四次挥手)