TCP连接从建立到释放

TCP连接

连接的建立

经典的三次握手

Alt text

首先,客户端是主动打开连接,服务端是被动打开连接

服务端首先创建传输控制块(TCB),准备接受客户端的连接请求,然后服务端就进入LISTEN状态,等待连接请求并作出响应。

客户端也是首先创建传输控制块TCB,创建完成收向服务端发送请求报文段,这时首部的同步位SYN=1,同时选择一个初始序号seq=x。(TCP规定SYN报文段即SYN=1的报文段不能携带数据但是需要消耗一个序号),这时客户端进入SYN-SENT(同步已发送)状态。

服务端接收到连接请求报文之后,如果同意建立连接,就向A发送确认。在确认报文段中把SYN和ACK位都置1,确认号是ack=x+1,同时也为自己选择一个初始序号seq=y。这个报文段也不能携带数据,但要消耗一个序号,这时服务端进入SYN-RCVD(同步已收到)状态。

客户端接收到服务端的确认之后,还需要向服务端发出一个确认。确认报文段的ACK置1,确认号ack=y+1,自己的序号seq=x+1。TCP标准规定:ACK报文段可以携带数据,但是如果不携带数据就不消耗序号,在这种情况下,下一个数据报文段的序号仍是seq=x+1。这时,TCP连接已经建立,客户端进入ESTABLISHED状态。

当服务端接收到客户端的确认之后,也进入ESTABLISHED状态。

那么为什么是三次握手呢?

主要是为了防止已经失效的连接请求报文段突然又传送到了服务端,因而产生错误。

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

现在假设一种情况,第一个报文段没有丢失,而是被长时间滞留了,那么服务端就会收到这个早已失效的报文段,但是服务端在收到这个报文段之后,会误以为是客户端又发出的一次新连接,于是就向客户端发送确认。如果不使用三次握手,那么只要服务端对这个早已经失效的报文发出确认,服务端就会建立连接,而客户端其实并没有发出请求,服务端就会一直等待接收数据,直到TCP的保活计时器失效。

至于为什么不是四次握手。。。这种脑瘫问题真的很让人蛋疼,因为没有必要四次握手,会浪费资源。

数据的发送

在三次握手之后,客户端就能够向服务端发送数据了。

下面介绍一下关于可靠传输所使用的协议

注:仅仅是讨论可靠传输,TCP的可靠传输实现原理还在下方。

自动重传请求ARQ(Automatic Repeat reQuest)

因为是讨论可靠传输的原理,所以把发送的数据单元称为分组,不考虑数据是在哪一个层次上进行传输的。

TCP连接从建立到释放_第1张图片

A向B发送一个分组,发送完成后,就暂停发送,等待接收B的确认。

如果B接收到了这个分组,就会向A发送确认,A接收到确认之后,就再发送下一个分组,然后以此来保证可靠传输。

TCP连接从建立到释放_第2张图片

在A当中会有一个超时计时器,如果A在向B发送数据之后,这个分组没有到达B,或者B的确认没有到达A,那么A就会在超时计时器时间到达之后,直接向B重传这个分组,如果分组正确到达两端,那么超时计时器就会被撤销,直接发送下一个分组。

注意下三点:

  1. A在发送完一个分组后,必须暂时保留已发送的分组的副本,只有收到对应的确认之后才能清除副本
  2. 分组和确认分组都必须进行编号,这样才能明确是哪一个分组收到了确认
  3. 超时计时器设置的重传时间应该比数据在分组传输中的平均往返时间RTT更长一点,具体要看网络的情况

如果确认丢失了,那么A就需要重传,这时,B可能会收到两个相同的分组,第一个是A第一次发送的分组,第二个是A重传的分组。此时B应该采取两个行动:

  1. 丢弃重复的第二个分组,不用向上层交付
  2. 向A发送确认

如果确认不是丢失而是在网络中滞留,那么A会收到两个相同的确认,这时A会直接丢弃重复的确认。

通过这个机制,就可以实现在不可靠网络上实现可靠通信

然而这种传输方法会很慢,因为是发送一个分组,接收到确认之后才能发送下一个分组,信道利用率很低。

连续ARQ协议

img

通常和滑动窗口协议一起使用,这是TCP的精髓

发送方会维持一个发送窗口:位于发送窗口内的n个分组,可以连续发送出去(不需要发一个分组等一个确认)

接收方采用累积确认的方法,也就是不需要逐个对收到的分组进行确认,而是在收到几个分组之后,对按序到达的最后一个分组发送确认,这表示:发送确认的分组之前的分组都已经正确收到了。

优点:即使确认丢失也不必重传

缺点:不能向发送方反映出接收方已经正确收到的所有分组的信息。比如:此时收到1,2,3,5,分组4丢失,此时接收方会发送3的确认,而发送方不知道5已经正确到达了,也会重传4,5分组。

TCP连接从建立到释放_第3张图片

TCP可靠传输的实现

以字节为单位的滑动窗口

TCP的滑动窗口是以字节为单位。

TCP连接从建立到释放_第4张图片

要描述一个发送窗口的状态需要三个指针,假设为p1,p2,p3:

  1. p1指向发送窗口内,最左边的一个数据,小于p1的数据表示已经发送并且收到确认的数据
  2. p2指向发送窗口内,准备发送的下一个数据,小于p2,大于等于p1的表示已经发送但未收到确认的数据
  3. p3指向发送窗口外,准备进入发送窗口的第一个数据,小于p3,大于等于p2的表示允许发送但还未发送的数据

窗口的大小根据AB双方的约定是可变的,但为了介绍的方便,选择一个特定时间段内,窗口不会改变的情况进行描述。

A发送数据,只会发送在发送窗口以内的字节,收到了确认,p2就会向后移动到收到确认的那个字节的下一位,也就是下一个即将发送的字节。

在A收到确认字节之后,窗口就能向右进行滑动,最多滑动到确认字节的下一个字节。

如果没有收到任何确认字节,即使发送窗口中的数据全部发送完成,也不能移动,因为可能数据丢失或滞留,也可能确认丢失或滞留。

TCP在发送方,需要将数据写入TCP的发送缓存,接收方需要从TCP的接收缓存读取字节流。

发送缓存和接收缓存是比发送窗口和接收窗口大的

发送缓存用来暂时存放:

  1. 发送应用程序传送给发送方TCP准备发送的数据
  2. TCP已经发送但尚未收到确认的数据

接收缓存用来暂时存放:

  1. 按序到达的、但尚未被接收应用程序读取数据的
  2. 未按序到达的数据

注意以下三点:

  1. 虽然A的发送窗口是根据B的接收窗口设置的,但在同一时刻,A的发送窗口并不总是和B的接收窗口一样大
  2. 对于不按序到达的数据,先临时存放在接收窗口当中,等到字节流中缺少的字节收到后,再按序交付上层的应用
  3. TCP要求接收方必须有累计确认的功能,这样可以减少传输开销,接收方可以在合适的时候发送确认,也可以在自己有数据要发送时把确认信息顺便捎带上,但是接收方不能过分推迟发送确认信息,防止超时重传

选择确认SACK

那么有没有一种方法能够让发送方只发送缺少的数据,而不是确认之后的全部数据呢?

那就是SACK(Selective ACK)

SACK是通过对不连续的字节块的边界进行报告的方式来确认重传哪些部分

如果使用SACK,在建立连接时,就要在首部的选项中加上“允许SACK”的选项,双方必须都事先商定好,如果使用SACK,那么原来首部中“确认号字段”的用法仍然不变,只是以后在TCP报文段的首部中都增加了SACK选项,以便报告收到的不连续的字节块的边界。

TCP流量控制的实现

所谓流量控制,就是让发送方的发送速率不要太快,要让接收方来得及接收

利用滑动窗口机制可以很方便地实现对发送方的流量控制。

在建立连接时,B告诉A接收窗口的大小,因此,发送方的发送窗口不能超过接收方给出的接收窗口的数值(单位是字节)

在传输数据时,接收方可以在确认当中加上窗口大小,实时对两个窗口的大小进行控制。

如果B给A发送了零窗口报文段之后不久,接收缓存又有空间了,于是B向A发送了一个新的窗口大小的报文段,然而这个报文段丢失了,A就会一直等待B发送的非零窗口通知,B一直等待A发数据,就会造成死锁。

这种情况,TCP为每个连接设置了一个持续计时器(persistence timer),只要连接的一方收到对方的零窗口通知,这个持续计时器就会启动,若时间到了还未收到非零窗口通知,就会发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值,如果窗口仍然是零,就重置持续计时器,否则就能打破死锁。

TCP拥塞控制的实现

慢开始和拥塞避免

发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量,大小取决于网络的拥塞程度,并且在动态地变化。发送方让自己的发送窗口等于拥塞窗口,如果再考虑到接收方的接受能力,那么发送窗口可能会小于拥塞窗口

发送方控制拥塞窗口的原则:只要网络没有出现拥塞,拥塞窗口就能再增大一些,以便把更多分组发送出去。只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。

那么如果知道网络发生拥塞?就是发送方没有按时收到应当到达的确认报文。

慢开始算法:从小到大逐渐增大发送窗口,通常在刚刚开始发送报文段时,先把拥塞窗口cwnd设置为一个最大报文段MSS的数值大小。而在每次收到对一个新的报文段的确认之后,把拥塞窗口增加至多一个MSS的数值,用这种方法逐步增大发送方的拥塞窗口cwnd

通过慢开始算法,传输报文段个数变化趋势就是指数级的,因为第一次一个MSS,收到确认后变成两个MSS,收到两个MSS后变成四个MSS,以此类推。因此每经过一个传输轮次,cwnd就会加倍。

传输轮次:把cwnd所允许发送的报文段都连续发送出去,并收到了对已发送的最后一个字节的确认。

为了防止cwnd增长过大,引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量,初始值为16

  1. 当cwnd
  2. 当cwnd>ssthresh时,停止使用慢开始算法,改用拥塞避免算法
  3. 当cwn=ssthresh时,两者都行

拥塞避免算法:让拥塞窗口cwnd缓慢地增大,每经过一个RTT,就把发送方的cwnd加1,而不是加倍。这样的话,cwnd就能线性增大了,比起慢开始,增长速率慢得多。

无论慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞,就把慢开始门限ssthresh设置为出现拥塞时的发送窗口值的一半(但不能小于2)。然后把cwnd重新设置为1,执行慢开始算法。

乘法减小:只要出现拥塞,就把ssthresh减半

加法增大:执行拥塞避免之后

快重传和快恢复

这是一个新的拥塞控制算法

快重传:接收方每收到一个失序的报文段之后,就立即发出重复确认,而不要等自己发送数据时捎带确认,让发送方较快接收到重复确认,以便较快重传

比如:A发了m1,m2,m3

B收到了m1,m2,此时m3丢失了。

之后,B又收到了A发送的m4,m5,m6

对于m4,m5,m6,B会重复确认m2三次,以便让A快点重传m3

快恢复:当发送方连续收到三个重复确认时,就执行“乘法减小”算法,预防网络发生拥塞。与慢开始不同的是,现在不执行慢开始算法(即cwnd设置为1),而是把cwnd设置为ssthresh减半后的数值,然后开始执行拥塞避免算法(加法增大)

在采用快恢复算法时,慢开始算法只是在TCP连接建立时和出现超时时才使用。

连接的释放

经典的四次挥手

Alt text

客户端和服务端的TCB都处于ESTABLISHED状态,在数据传输完成之后,客户端会主动进行关闭。

客户端的应用程序先向其TCP发出连接释放报文段,并停止再发送数据,主动关闭TCP连接。客户端把连接释放报文首部的终止控制为FIN置1,序号seq=u,它等于前面已经传送过的数据的最后一个字节的序号加1。此时客户端进入FIN-WAIT-1(终止等待1)状态,等待B的确认。

服务端收到连接释放报文段后即发送确认,确认号是ack=u+1,而这个报文段自己的序号是v,等于服务端前面已传送过的数据的最后一个字节的序号加1。然后服务端就进入CLOSE-WAIT(关闭等待)状态。TCP服务器进程这时应通知高层应用进程,因而从客户端到服务端这个方向的连接就释放了,这时的TCP连接处于半关闭状态,即客户端已经没有数据要发送,但要是服务端要发数据,客户端还是要接收,也就是说从服务端到客户端这个方向的连接还没有关闭。

客户端收到服务端的确认之后,就进入FIN-WAIT-2状态,等待服务端发出的连接释放报文段。

若服务端已经没有要向客户端发送的数据,其应用进程就通知TCP释放连接。这时服务端发出的连接释放报文段必须使FIN=1。现假定服务端的序号为w(在半关闭状态下又发送了一些数据),服务端还必须重复上次已发送过的确认号ack=u+1。这时服务端就进入LAST-ACK(最后确认)状态,等待客户端的确认。

客户端收到服务端的连接释放报文之后,必须对此发出确认,在确认报文段中把ACK置1,确认号ack=w+1,而自己的序号是seq=u+1,然后进入到TIME-WAIT(时间等待)状态(此时的TCP连接还没有释放掉)。然后经过时间等待计时器设置的时间2MSL(最长报文段寿命Maximum Segment Lifetime)之后,客户端才进入CLOSED状态,当然时间可以根据实际情况变化。

为什么是四次挥手?
我的理解是:因为需要双向关闭连接。

那么为什么要等待2MSL呢?

  1. 为了保证客户端发送的最后一个ACK报文段能到达服务端,这个报文段可能会丢失,会让服务端重传FIN+ACK报文段,而客户端就能再2MSL时间内收到重传的报文段,然后再发送确认,再进行计时器的重置。
  2. 防止连接中提到的“已经失效的连接请求报文段”出现在本连接中,客户端发完最后一个ACK报文段后,经过2MSL时间,就能使本连接持续时间内所有产生的报文段都从网络中消失,这样下一个连接就不会出现旧连接的报文段。

出了时间等待计时器以外,TCP还有一个保活计时器,用来防止客户端出现故障,服务端一直等待客户端传送数据。服务端只要收到一次客户端的数据就重置保活计时器,通常是两个小时,若在这时间内没有收到客户端的数据,服务端就发送一个探测报文段,以后每隔75分钟发送一次,如果连续10次还没有响应,就关闭连接。

以上就是TCP连接从建立到释放的全过程,是我在看谢希仁写的《计算机网络》做的笔记。
图片为网络图片,非本人制作
如果有不对的地方还请在评论当中指出。

你可能感兴趣的:(网络)