signed

QiShunwang

“诚信为本、客户至上”

计网学习笔记十二.传输层(下)

2021/4/26 16:06:02   来源:

TCP概述

段结构

segment-翻译为段,报文段。

有些字段没用,所以当时设计现在没用,还有改进空间

序列号和ACK

  • 序列号是段中的第一个字节大小,不是第几个段。(序号的具体细节在连接管理中介绍,三次握手四次挥手)
  • 建立tcp,双方各有一个序号,在三次握手时会细讲
  • ACK n+1,代表n之前的包(包括n)都收到了,希望收到的下一个包序号为n+1。
  • 乱序到达的包,接受放怎么处理,由实现者决定。

TCP可靠数据传输

这些特点是GBN和SR中和的,每个特点下面会降到。包括两种触发重传的情况

如何设置定时器的超时时间?

TCP发送方细讲

  • 从应用层收到数据就这几个操作。
  • 超时后,重发引起超时的段,注意是引起超时的段,不是所有,假设发送了1-10,1-5收到了ACK,6超时还没收到,只重传6,每个段的超时时间可能不一样。
  • 重发后,应该重启定时器,重新计时
  • 收到ACK n,如果之前没确认过的分组,则更新sendbase=n。 如果窗口还有没确认的分组,重启计时器?这个不明白,收到1组,为什么要重启2组的计时器?

三种协议的超时区别:

  • GBN:首先明白,三种都是流水线协议,即可以同时发送多个分组。比如一次发了10个,GBN这十个是同一个计时器,如果超时,则重发10个分组
  • SR:同时发送10个分组,但每个分组设置一个定时器,谁超时了重发谁,互不影响
  • TCP:课上没有明确讲,结合后面的内容,下面程序似乎是多个组共用一个计时器,但百度百科上说每个报文都有一个计时器!有四种计时器,这块待定

三种协议的ACK:

  • GBN:接收方发ACK n,代表n之前都正确接受。如果乱序到达,接收方直接丢弃
  • SR:ACK n,只代表第n组正确接受。如果乱序到达,会缓存乱序的分组
  • TCP:ACK n,代表n-1及其之前都正确接受,期待下一组序号是n。乱序到达,没规定怎么处理,由实现者决定。

发送端程序

  • 初始化SendBase,和NextSeqNum
  • 从上层接收到数据,创建段,使用初始的NextSeqNum做序号,开启计时器,发送数据到IP层,下一个发送的段序号为:NextSeqNum+数据字节数
  • 超时,重发没有收到ACK的最小序号的段
  • ACK收到,如果对应的段序号y>sendbase,则意味着y以前的包都正确接受,将sendbase更新=y。如果窗口仍然有未收到ACK的段,则重启计时器!不明白,难道窗口中共用一个计时器?

TCP重传示例

ACK丢失,100=92+8,代表接收方期望下个段序号是100.超时后,发送方重新发送。

  • 纵坐标写的是序号92的超时时间,没说是100的超时,即发送了两个段,虽然接收方ACK了两个,但发送方认为92超时,重发92。
  • 重发后收到了两个ACK,就分别更新sendbase=100,再次更新120,表示两个段已正确发送,更新窗口大小。
  • 接收方一直期望下个段是120,但却收到重发的100,于是丢弃100段,再次ACK120,标明接收方需要120

  • 发送了两个段92和100,但第一个ACK100丢失了
  • 第二个ACK120正确给了发送方,因为还没到92组的超时时间就收到了ACK120,所以发送方认为120以前的分组全部正确发送,更新窗口大小到120.

TCP接收方细讲

  1. 到达了一个按序的段,在此段之前,所有段都已经ACK了,对应操作:延迟发此段的AK,等到500ms,如果没有下个段来,就发ACK
  2. 如果达到一个按序段,在此之前,有一个段还没有发送ACK,对应操作:立刻发送这两个段的累计应答ACK,这样只发一个ACK就可以确认两个段。
  3. 如果乱序段到达,对应操作:立刻发送"重复ACK",标明接收方需要的段是哪个,并非当前的乱序段。

快速重传

  • 如果发送超时,重发段后,新设置的超时时间会加倍!
  • 接收方收到乱序到达段,会发送ACK n,标明需要的是段n
  • 如果发送方收到三次ACK n,发送方就认为 段n丢失了,不会等到n 的计时器超时,就可以重传 段n

TCP可靠性遗留问题

四种计时器?重发计时器,可能是每个报文一个计时器,不是共用一个,课上对计时器说的太模糊.

网上补充:

TCP中的四个计时器包括重传计时器、坚持计时器、保活计时器、时间等待计时器

重传计时器(Retransmission Timer):
目的:为了控制丢失的报文段或者丢弃的报文段。这段时间为对报文段的等待确认时间。
创建时间:在TCP发送报文段时,会创建对次特定报文段的重传计时器。
可能发生的两种情况:在截止时间(通常为60秒)到之前,已经收到了对此特定报文段的确认,则撤销计时器;在截止时间到了,但为收到对此特定报文段的确认,则重传报文段,并且将计时器复位。
重传时间:2*RTT(Round Trip Time,为往返时间)


坚持计时器(Persistent Timer):
目的:主要解决零窗口大小通知可能导致的死锁问题
死锁问题的产生:当接收端的窗口大小为0时,接收端向发送端发送一个零窗口报文段,发送端即停止向对端发送数据。此后,如果接收端缓存区有空间则会重新给发送端发送一个窗口大小,即窗口更新。但接收端发送的这个确认报文段有可能会丢失,而此时接收端不知道已经丢失并认为自己已经发送成功,则一直处于等待数据的状态;而发送端由于没有收到该确认报文段,就会一直等待对方发来新的窗口大小,这样一来,双方都处在等待对方的状态,这样就形成了一种死锁问题。如果没有应对措施,这种局面是不会被打破的。为了解决这种问题,TCP为每一个连接设置了坚持计时器。
工作原理:当发送端TCP收到接收端发来的零窗口通知时,就会启动坚持计时器。当计时器的期限到达时,发送端就会主动发送一个特殊的报文段告诉对方确认已经丢失,必须重新发送。【这个特殊的报文段就称为探测报文段,探测报文段只有1个字节的大小,里边只有一个序号,该序号不需要被确认,甚至在计算其他部分数据的确认时该序号会被忽略。】
截止期的设置:设置为重传时间的值。但如果没有收到接收端的响应,则会发送另一个探测报文段,并将计时器的值加倍并复位,直到大于门限值(一般为60秒)。在此之后,发送端会每隔60秒发送一个探测报文段,直到窗口重新打开。


保活计时器(Keeplive Timer):
目的:主要是为了防止两个TCP连接出现长时间的空闲。当客户端与服务器端建立TCP连接后,很长时间内客户端都没有向服务器端发送数据,此时很有可能是客户端出现故障,而服务器端会一直处于等待状态。保活计时器就是解决这种问题而生的。
工作原理:每当服务器端收到客户端的数据时,都将保活计时器重新设置(通常设置为2小时)。过了2小时后,服务器端如果没有收到客户端的数据,会发送探测报文段给客户端,并且每隔75秒发送一个,当连续发送10次以后,仍没有收到对端的来信,则服务器端认为客户端出现故障,并会终止连接。


时间等待计时器(Time_Wait Timer):
时间等待计时器是在连接终止期间使用的。
当TCP关闭连接时并不是立即关闭的,在等待期间,连接还处于过渡状态。这样就可以使重复的FIN报文段在到达终点之后被丢弃。
时间设置:一般为报文段寿命期望值的2倍。

TCP流量控制

  • Buffer是缓冲,RevBuffer是总共能缓冲的数据大小,数据从IP传来,RevWindow是能够接收的窗口大小,spareroom剩余空间可缓存数据的,然后application process从缓冲中拿数据。
  • 如果发送方发送过快,buffer占满,则接收方会把溢出的数据丢失。

  • buffer剩余空间=总大小-(LastByteRcvd - LastByteRead) ,上次接收到-上次读取的(读取的就是应用层拿的,应用层读取一个字节,则这个字节就可以删除不占用空间了,所有上次接受的-上次读取的就是应用层读取后还占据的空间)
  • 接收方会把剩余buffwe,即RevWindow接受窗口大小放在头部字段告诉发送方
  • 发送方就可以限制自己发送的数据量,防止buffer溢出
  • RecWindow=0? 如果发送方完全不发消息,即使接收方RevWindow有值了,发送方也不知道,会变为死锁。所以即使等于0,发送方依然可以发送一个很小的段,以便接收方回复信息,会告诉新的RevWindow,防止死锁

TCP连接管理(三次握手,四次挥手)

这里解释清双方 都有序号的问题,双方序号互不干涉

  • 建立连接,要初始化变量,有段序号、buffer
  • client是连接发起者,server是等待client建立连接请求

三次握手

  1. 握手1:客户端主机发送TCP SYN段到server,在前面的段结构中讲过几个标志位,S代表SYN,发送方的第一次握手中段会将SYN置为1,设置一个初始序号(),并且段不携带任何数据。 
  2. 握手2:服务端收到SYN报文段,回复一个SYNACK报文段,服务端就会分配初始资源buffer,确定一个server端的初始序号,并且把序号告诉client端。
  3. 握手3:客户端收到了SYNACK报文段,回复一个ACK报文段,这个段里,SYN标志位不是1,ACK应该是1,并且这个报文段就可以携带数据了。

为什么要三次握手?

为什么不是两次?确保client 和server端发出的报文段都能收到ACK,如果只有两次握手,那server发出的报文(第二次握手)就没有收到ACK。保证双方的消息都是有去有回

为什么不是四次,十次?完全可以给client的ACK再回复一个ACK,这样就无线套娃了,只要保证消息有去有回就可以。

而且大部分情况A 和 B 建立了连接之后,A 会马上发送数据的,一旦 A 发送数据,则很多问题都得到了解决。例如 A 发给 B 的应答丢了,当 A 后续发送的数据到达的时候,B 可以认为这个连接已经建立,或者 B 压根就挂了,A 发送的数据,会报错,说 B 不可达,A 就知道 B 出事情了。

当然你可以说 A 比较坏,就是不发数据,建立连接后空着。我们在程序设计的时候,可以要求开启 keepalive 机制,即使没有真实的数据包,也有探活包。

序号问题。

前面课程说是包中第一个字节数,下个段序号是初始序号+第一个段的字节大小,但这节课又说client发起连接的初始序号是随机很复杂,无语了前后不一。根据极客时间的课程,建立连接的初始序号跟时间有关,很复杂。以下是极客时间资料:

连接建立

  1. client发送报文段,SYN=1,client设置一个seq序号为client_isn
  2. server回复client报文段,SYN=1,server设置一个seq序号server_isn,ack序号为client_isn+1(表示期望收到下一个client报文段序号是client_isn+1)
  3. cient收到ACK,发送报文段SYN=0,seq=client_isn+1,ack是server_isn+1(表示期望收到的下一个server报文段序号是server_isn+1)
  4. 前面看到,在三次握手,第二次握手后,服务器会分配一些初始资源,如果第三次握手迟迟失败(客户端没有发送ACK),就会把这些资源释放掉。如果同一时刻,大量客户端建立连接,却不发送ACK确认真正建立连接,这样服务器会浪费很多资源,这就是一种网络攻击手段。

四次挥手(连接关闭)

正常情况

A:B 啊,我不想玩了。

B:哦,你不想玩了啊,我知道了。

B:A 啊,好吧,我也不玩了,拜拜。

A:好的,拜拜。

异常情况,为什么要四次挥手?

尽量保证client和server都能关闭资源!保证双方的消息都有回复。

  • client第一次FIN后,client立刻关闭,万一FIN没发送到sever,server就关不了
  • server发送了ACK,立刻关,client也不知道server已经关了,而且server还有未处理完的数据需要继续发送
  • server发送了FIN ACK,立刻关,万一FIN ACK丢失,client就不知道server关了
  • 最后client发送ACK,等待很长的时间,确保Server收到最后的ACK,如果server没收到ACK,会重发FIN ACK,client就知道server没收到最后的ACK,client会重发ACK。但client不能无限等下去,重发后还不行,会发送RST,就立刻关闭。

A 开始说“不玩了”,B 说“知道了”,这个回合,是没什么问题的,因为在此之前,双方还处于合作的状态,如果 A 说“不玩了”,没有收到回复,则 A 会重新发送“不玩了”。但是这个回合结束之后,就有可能出现异常情况了,因为已经有一方率先撕破脸。一种情况是,A 说完“不玩了”之后,直接跑路,是会有问题的,因为 B 还没有发起结束,而如果 A 跑路,B 就算发起结束,也得不到回答,B 就不知道该怎么办了。另一种情况是,A 说完“不玩了”,B 直接跑路,也是有问题的,因为 A 不知道 B 是还有事情要处理,还是过一会儿会发送结束

状态时序图

  • 断开的时候,我们可以看到,当 A 说“不玩了”(FIN报文段,FIN标识为1),就进入 FIN_WAIT_1 的状态,B 收到“A 不玩”的消息后,发送知道了,就进入 CLOSE_WAIT 的状态。
  • A 收到“B 说知道了”,就进入 FIN_WAIT_2 的状态,如果这个时候 B 直接跑路,则 A 将永远在这个状态。TCP 协议里面并没有对这个状态的处理,但是 Linux 有,可以调整 tcp_fin_timeout 这个参数,设置一个超时时间。
  • 如果 B 没有跑路,发送了“B 也不玩了”的请求到达 A 时,A 发送“知道 B 也不玩了”的 ACK 后,从 FIN_WAIT_2 状态结束,按说 A 可以跑路了,但是最后的这个 ACK 万一 B 收不到呢?则 B 会重新发一个“B 不玩了”,这个时候 A 已经跑路了的话,B 就再也收不到 ACK 了,因而 TCP 协议要求 A 最后等待一段时间 TIME_WAIT,这个时间要足够长,长到如果 B 没收到 ACK 的话,“B 说不玩了”会重发的,A 会重新发一个 ACK 并且足够时间到达 B。
  • A 直接跑路还有一个问题是,A 的端口就直接空出来了,但是 B 不知道,B 原来发过的很多包很可能还在路上,如果 A 的端口被一个新的应用占用了,这个新的应用会收到上个连接中 B 发过来的包,虽然序列号是重新生成的,但是这里要上一个双保险,防止产生混乱,因而也需要等足够长的时间,等到原来 B 发送的所有的包都死翘翘,再空出端口来。等待的时间设为 2MSL,MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 域,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。协议规定 MSL 为 2 分钟,实际应用中常用的是 30 秒,1 分钟和 2 分钟等。
  • 还有一个异常情况就是,B 超过了 2MSL 的时间,依然没有收到它发的 FIN 的 ACK,怎么办呢?按照 TCP 的原理,B 当然还会重发 FIN,这个时候 A 再收到这个包之后,A 就表示,我已经在这里等了这么长时间了,已经仁至义尽了,之后的我就都不认了,于是就直接发送 RST,B 就知道 A 早就跑了

A发了fin,B收到后会回复两条消息。 第一条是ack,b表示我知道了,但我还有数据需要继续处理。a收到后状态变为fin wait1. 当b处理完后,发了第二条消息fin ack,a收到后状态变为fin wait2。 然后a又发一条ack,a状态变为time wait,如果b顺利接到消息,则b结束,a等一会结束。 顺利的话,a发两条消息,b发两条消息。 不顺利,a为了确保b收到了最后的ack,会等一会包的失效时间,如果b没收到最后的ack,根据tcp机制,b会重复发一遍fin ack,a收到了会再发ack。 最后还有reset保证a告诉b,他a不干了

TCP状态机