wireshark tcp data中文_TCP的可靠传输

说到TCP,大家可能最先想到的都是全双工,面向流的可靠的传输协议.

TCP主要依靠校验和、确认应答+序列号、重传机制、流量控制、拥塞控制(校序重流拥)来保证它的可靠性

一、校验和

由发送端计算,然后由接收端验证.当接收端检测到校验和有差错,则该段报文会被直接丢弃.

二、确认应答+序列号

在这里我们使用了一个socket Demo 建立连接并且使用WireShark进行相关数据的抓包

try {//默认使用socket创建一个tcp
Socket clientSocket = new Socket("127.0.0.1",44444);
String data = "hello world!";
OutputStream out = clientSocket.getOutputStream();out.write(data.getBytes());out.close();
} catch (Exception e) {
e.printStackTrace();
}

使用WireShark软件过滤掉tcp.port==44444可以看到数据

e65d8c075985ef38e75fb12495b54bae.png

当接收端每次收到数据后,都会对传输方进行确认应答,也就是发送ACK报文.这个ACK报文中会带有对应的确认序号Seq,告诉发送方下一次的数据该从哪里发.

c34f9abb4d70bfbb796b8df8ce998e51.png

这里解释一下Seq,Ack,Length字段的含义

                    Seq                              

sequence number:TCP数据包的编号,对于一次性需要发送的大量数据必须分成多个包.TCP协议为每个包编号,以便接收的一方按照顺序还原.第一个包的编号是一个随机数.

一个包长度是1400字节,比如,一个10MB的文件,需要发送7100多个包.

674e56bb509ab1a39913add37ba61fe0.png

从上图可以看出Sequence number: 210961972,而[Next sequence number: 210961990],可以算出这个包的负载是18字节,也对应了图中的Len:18

                    Ack                          

acknowledgement:ACK携带了期待要收到的下一个数据包的编号和接收方的接收窗口的剩余容量.

Length

076ded7052885d14efe331e5ef95d850.png

由于刚才的数据有点少,我们在for循环里把发送端的数据增加一些.

for (int i = 0; i < 10; i++) {
String data = "send:" + "hello world!" + i;
out.write(data.getBytes());
}

28526fd6e4ed055f002cb07ce5eac393.png

用表格将其中的seq/ack 序号罗列一下

47ccff044a7f75e2d950cc3fdb7eb7c6.png

第1步

seq:发送端第一次发送packet,即第一次握手,Seq=210961899
ack:此时接收方之前从未发送过数据,所以期望的接收端回传的seq应该从第一个开始,所以ack为0

第2步

seq:发送方发送第一次发送数据,第二次握手,Seq=1794185644
ack:由于第一步中接收到SYN数据包,ACK=210961900

第3步--ACK确认包

seq:第三次握手,上一次为210961899,所以这次为210961900
ack:上次为0,这次为1794185645

第4步--数据包

len:18
seq:上一次为210961899,所以这次为210961900
ack:上次接收是为第2步,第2步中seq为1794185644,所以增加1位1794185645

第5步--纯ACK包

seq:上一次发送时为1794185644,所以这次为1794185645
ack:上次接收是为第4步,第2步中ack为210961900,由于数据包为18,所以为210961918

第6步-数据包

seq:上一次发送为第四步发送,4中seq为210961900,len为18,所以此时变成
210961918
ack:上次接收是第五步,所以此时变成1794185645

第7步--纯ACK包

seq:1794185645
ack:上次接收是第6步,所以此时变成210961918+18=210961936

第8步-数据包

seq:210961936
ack:仍然请求响应,故数据为1794185645

第9步--纯ACK包

seq:1794185645
ack:上次接收是第8步,所以此时变成210961936+18=210961954

第10步-数据包

seq:210961954
ack:仍然请求响应,故数据为1794185645

注意

  • 1.对于纯ACK包,虽然它有一个seq,但是这次传输在整个data stream中是不占位置的,所以下一次实际的数据传输依旧会从上一次发送数据的acq开始

  • 2.SYN/FIN的传输虽然没有data,但是会让下一次的Seq序号增加一

后面的步骤依次类推,由于此次主要针对数据传输,就先不分析后面的FIN报文四次挥手过程了,后面会另外开一篇文章

通过上面的分析我们可以看出,无论对于数据发送方还是接收方,收到的seq序号一定要和最后一次发送的packet的ack相等.

最后一次发送的packet的ack是对下一次接收的packet的seq的预测,如果两者不相等,表明中途有数据丢失了

三、超时重传

发送方在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的ACK报文,那么就重新发送数据,直到发送成功为止.

重传超时时间(Retransmission TimeOut)RTT如果设置过长,则会导致已经丢失的报文迟迟无法重传,数据丢失严重.相反,如果RTT设置过短,就会导致可能由于ACK信号仍在发送途中此时频繁重传,网络传输压力增大.该如何设定才能够有效而快速的定位到ACK报文缺失的问题呢?

在思考如何找到这个时间限定前,我们再来看另一个名词,RTT(Round Trip Time):一个连接的往返时间,是指数据发送时刻到接收方确认的时刻的差值.

在实际传输过程中,由于网络波动,每个RTT都是动态变化的,因此计时器的超时时间RTO也应该随着RTT动态变化

RTT 估计器 

RFC 2988中建议RTO的计算方式为:

ec81f0269ba605df7d81c95ffacd217e.png

其中RTTs为加权平均往返时间,RTTd是偏差的加权平均值.后续针对重传数据包确认和原来数据包确认区分问题,对该取值进行了修正:取旧重传时间的2倍作为新的重传时间.当不再发生数据包重传时,才重新根据上图所示公式计算超时重传时间.

                    快速重传                             

TCP的超时重传带来了一些问题

  • 1.当发送方发现ACK丢失时,需要等待一定的超时时间才会重传数据,增加了端到端的时延

  • 2.在等待超时的过程中,后续发送的数据也迟迟得不到确认,发送也以为该数据丢失,进行了不必要的重传.

如何解决这些问题呢?

快速重传的机制是:在没有达到超时重传定时器超时时间之前,发送方接收到连续的三个重复冗余ACK,不需要等待重传定时器超时,这就提高了重传的效率

fe5c55649e88a50a23de9f5bcf494b52.png

从上图可以看出,报文段1成功接收后,接收方发送ACK 2期待收到后续报文,然而报文段2发送失败,后续的报文3,4,5成功发送给接收方,接收方迟迟收不到报文2,所有的回复报文都是ACK 2,此时仍未到达TCP的超时重传时间,但是累加了3个ACK冗余报文,发送方推断出报文2丢失,此时重传报文2,减少了等待时间.

                    为什么要把重复冗余的次数约定为3呢?                              

首先要明白一点,由于TCP包是封装IP包内,IP包在传输时乱序,即使发送方有序的数据在到达接收端仍然可能是乱序的,乱序也可能造成ACK冗余。

那发送冗余ACK到底是由于乱序还是丢包造成的呢,如何选好次数

我们来假设一个场景

A为发送端,B为接收端
A的待发报文序号分别为N-1,N,N+1,N+2
假设报文N-1成功到达

假设报文N没丢失,只是到达顺序不一样

情况1

N-1 N N+1 N+2
A收到1个ACK(N)

情况2

N-1 N N+2 N+1
A收到1个ACK(N)

情况3

N-1 N+1 N N+2
A收到2个ACK(N)

情况4

N-1 N+1 N+2 N
A收到3个ACK(N)

情况5

N-1 N+2 N N+1
A收到2个ACK(N)

情况6

N-1 N+2 N+1 N
A收到3个ACK(N)

假如N丢了,没有到达

情况1

N-1 N+1  N+2
A收到3个ACK(N)

情况2

N-1 N+2 N+1
A收到3个ACK(N)

从上述情况可以看出,没有丢失的情况中,有1/3的可能出现冗余ACK

在乱序的情况下必定出现2次3个冗余ACK

在丢失的情况下,必定出现3次冗余ACK

基于这样的概率,我们选择3个冗余ACK作为阈值.

所以再来看这张图

fe5c55649e88a50a23de9f5bcf494b52.png

无论是发送方收到三个连续的重复ACK,还是超时了还没有收到任何ACK,就会确认丢包,从而再次发送丢失包,这种机制保证了不会有数据包丢失.

四、流量控制

传输数据的时候,如果发送方传输数据的能力超过了接收方的处理能力,那么接收方会出现丢包,为了避免出现此类问题,流量控制要求数据传输双方在每次交互时声明各自的接收窗口 rwnd 大小,用来表示自己最大能保存多少数据.

而发送端只允许发送缓冲区能接纳的数据

在理解这些概念之前,我们回顾下第二步的确认应答+序列号的通信机制的基本流程

固定窗口

1519158790594312f9b58267d054ed13.png

每当发送方发送一个数据包时,接收方都会发送一个确认包,发送方收到确认包之后会再次发送下一个数据包,这样发送->确认->发送->确认,就解决了传输中最令人头疼的丢包和乱序等问题.

但是这种等到对方确认之后再应答发送之后的数据让数据传送变的非常之慢.网络的吞吐量变的低了很多.

试想一下,如果我们需要传输很多的数据,而当中的一个数据包多次传输失败,则会直接影响后续数据包的发送.非常影响数据传输的效率.

                    如何提高吞吐量                              

可以发送多个数据包等对方一起确认吗

累计确认机制

数据发送方会记录最早未被确认的字节序号sendBase,当部分ACK确认号在传输过程中丢失时,也就是确认号>sendBase时,此时该发送方会认为之前一个或多个未被确认的报文已经被确认接收.

滑动窗口

滑动窗口不需要对每个数据包进行确认,而是可以进行累积确认.

a8051927cb29a113fa5745cca593f1e1.png

  • 如上图所示,发送方第一步发送了Seq=1,len=9的数据,接收方的ACK包在传输过程中丢失

  • 发送方又发送了Seq=10,len=20的数据

  • 接收方的ACK=30的确认包成功发送给发送方

  • 上面我们说了发送方数据的超时机制,当到达第一个数据包的超时阈值仍未接收到ACK确认包时,发送方会启动重传机制,但是由于刚刚提到的累积确认机制,由于接收方收到了ACK=30的包,此时发送方会认为第一个数据包已经被成功发送,所以不会重新发送第一个数据包

  • 正是由于累积确认机制,由于网络问题丢失的ACK确认包会被后面成功发送的ACK确认包成功更新,避免了发送方的重传.

发送方在发送过程中保持着一个发送窗口,只有在发送窗口的数据才允许被发送

接收方也保持着一个接收窗口,只有落在接收窗口的数据才允许接收.

在通信过程中发送方和接收方都可以调整自己的窗口大小.

滑动窗口简易结构如下图:

b89059253510f87b438898f038341a88.png

发送方滑动窗口各结构

  • 发送且已确认窗口:已经发送成功并且受到ACK确认信号的数据

  • 发送但未确认窗口:已经发送成功还没收到ACK确认信号的数据

  • 未发送但允许发送的窗口:尚未发送但是允许被发送的数据

  • 不允许发送窗口:接收端暂时不允许发送的数据

6460dcb9564853b31ed5bc0b3d4bb6f5.png

接收方滑动窗口各结构

  • 接收但未处理窗口:已接收并回复ACK信号但是还没有被上层处理,被缓存在窗口内

  • 接收但未确认窗口:以接收仍未回复ACK信号的数据

  • 有空位但还未接收数据窗口:可以接收数据的空窗口

9527d52c46673795cb78ea42674ed012.png

从序列号5到序列号12就是我们所说的滑动窗口.当序列号5的数据接收到ACK信号后窗口就会向右滑动,此时处于13号窗口的不允许发送的数据这时也获得了可以发送数据的权利

fb155a2a3d4fe2d2ef87b3cea3174fcd.png

那么滑动窗口是如何调节大小来控制数据传送速率的呢

5bd531845dbe3a76a872eb39afe70a03.png

  • 数据接收方将通告窗口大小告诉发送方,win=2500

  • 发送方发送1-1000的数据给接收方

  • 发送方发送1001-2000的数据给接收方

  • 接收方发送2001-2500的数据给接收方

  • 接收方收到1-1000的数据后发送1000的确认同时告知窗口剩余1500

  • 接收方收到1001-2000的数据后发送2000的确认同时告知窗口剩余500

  • 接收方收到2001-2500的数据后发送2500的确认同时告知窗口剩余0

  • 发送方陆续收到确认信号并且得知接收方此时没有剩余窗口,无法继续发送窗口

  • 当接收方的应用程序读取出部分字节后,空余出窗口接收数据,接收方发送信号告知接收方此时又窗口剩余.

  • 发送方接收到数据后开始传送数据

  • 后面的步骤同上

可以看到,当接收方发送窗口为0的数据包时,表明接收方已经接收了全部数据,或者接收方应用程序暂时没有能力读取数据,此时要求暂停发送.而当发送方接收到窗口为0的数据时,就会停止数据传输.

在数据发送过程中,通过滑动窗口机制,保证了流量传输不超过设备的负载能力.

这种通过滑动窗口来提高传输速率,告知对方窗口长度为0来暂停接收数据的机制有什么缺点呢?

                    死锁状态                             

如果接收端发送了零窗口的报文段,当窗口恢复了存储空间,接收端的windowSize报文在传输过程中丢失了,此时发送端一直在等待接收端的通知时,接收端以为自己的window报文已经发送了,也在等待数据,这样就发生了尴尬的死锁.

                    持续探测机制                               

TCP为每个连接设有一个持续计时器,当发送方收到对方零窗口通知时,就启动持续计时器.若持续计时器到期,发送方就会发哦是那个一个零窗口探测报文段,对方收到这个探测报文时会给出最新的窗口值.

                    糊涂窗口综合症                           

试想一个场景,接收方的缓存已满,接收方的应用层读取缓冲区的速度较慢(每次只能腾出一个字节),接收方每次都会向发送方发送一个字节的窗口大小.发送方接收到该窗口大小后,每次传输只发送一个字节,发送方和接收方一直维持着这种小数据量的传输.针对这种有效载荷只有1个字节,而传输开销有40字节(20字节的IP头+20字节的TCP头),这种现象就叫做糊涂窗口综合症.

对于这种糊涂窗口综合症,一般有两种解决方法

(1)Clark解决方法

只要有数据到达就发送确认,但是接收方的窗口始终为0,一直到缓存空间已能放入具有最大长度的报文段,或者缓存空间的一半已经空了,才开始发送窗口长度.

(2)Nagle算法

在说这个算法之前,我们先来引入一个概念,延迟ACK

如果TCP对每个数据包都发送一个ack确认,那么只是一个单独的ACK确认包代价有点高,所以TCP会延迟一段时间,如果这段时间内有数据发送给对端,则捎带发送ack.如果在延迟ack定时器触发的时候,发现ack尚未发送,则立即单独发送.

Nagle算法要求一个tcp连接上最多只能有一个未被确认的未完成的小分组,在该分组ack到达之前不能发送其他的小分组.

Nagle算法实质是在攒数据,只有满足了其中之一的条件才会发送数据.

  1. 等到窗口大小>=MSS或者数据大小>=MSS

  2. 等待时间超时200ms

Nagle算法在协议中是默认开启的,可以通过TCP套接字选项TCP_NODELAY关闭选项

通过这两种算法的比较,我们可以看到,CORK算法是杜绝发送小包,仅仅发送满包以及必须发送的小包,而Nagle算法没有禁止发送小包,只是禁止大量小包发送

五、拥塞控制

当网络拥塞时,减少数据的发送。

说完了流量控制,我们最后说一个概念 拥塞控制.

流量控制主要是接收方对数据接收能力给出的窗口大小.如果接收方的应用层处理能力很快,也就是窗口可以很大,但是由于非自身原因(如网络原因)导致拥堵或者丢包,发送方发出的很多包到达超时阈值仍未收到ACK确认包时.此时还有必要传输大量的数据给对方吗.

TCP的拥塞控制当网络拥塞时,会减少数据的发送.

发送方维护了一个拥塞窗口(cwnd),发送数据前会取接收方的接收窗口和拥塞窗口的大小,取最小值.

拥塞窗口该如何取值它又是如何随着网络的变化而动态变化呢

拥塞控制一个有四个算法

  • 慢启动

  • 拥塞避免

  • 拥塞发生

  • 快速恢复

  1      1慢启动

在TCP连接刚建立时,一点一点地提速,试探接收方的网络承受能力.

8d4e4de0ce5272f8dd118be6b4f56cda.png

  • 初始化拥塞窗口cwnd为1

  • 每当收到一个确认ACK,cwnd+1,呈线性上升

  • 每没过了一个往返时间RTT(Round Trip Time),cwnd乘以2,呈只是上升

  • TCP规定了一个ssthresh(slow start threshold),即上线,当cwnd>=ssthresh时,就会进入拥塞避免算法.

  • 慢启动算法的ssh=16

  2       1拥塞避免

当cwnd大于等于慢启动阈值ssthresh后,就进入拥塞避免算法

  • 收到一个ACK,则cwnd=cwnd+1/cwnd

  • 每当过了一个往返延迟时间RTT,cwnd大小加1

此时的窗口避免了之前的指数增长,过渡到缓慢增长期

  3       1拥塞发生

窗口一直在不停的增长,TCP什么时候判断进入拥塞状态呢?

TCP一般认为当发生丢包现象时就进入拥塞状态了.回忆一下丢包的两个场景

  • 1.当发送方开始超时重传RTO超时,这时需要超时重传

  • 2.发送方收到了三个冗余ACK,未到超时时间时启动的是快速重传机制

针对第一种的超时重传机制,就是拥塞发生算法

  • 1.由于发生丢包,将ssthresh设置为当前cwnd的一半,ssthresh=cwnd/2

  • 2.cwnd重置为1

  • 3.进入慢启动过程

d4ac1fac7af135f901088572c592a42a.png

可以看到在第2步发生超时时,发送方的cwnd被重置为1,后面又进入了慢启动过程,TCP认为此次超时重传是由于网络拥塞导致的,后面再增加拥塞窗口

然而这种一旦丢包拥塞窗口立马降为1的做法并不利于网络数据的稳定传递.

TCP Reno算法对此又进行了优化,当收到三个重复ACK时,TCP开启快速重传算法,不需要等到RTO超时再进行重传

  • 1.cwnd缩小为原来的一半

  • 2.ssthresh设置为缩小后的cwnd

  • 3.进入快速恢复算法

  4      1快速恢复算法
  • 1.cwnd = cwnd + 3 * MSS,加3 * MSS的原因是因为收到3个重复的ACK。

  • 2.重传DACKs指定的数据包。

  • 3.如果再收到DACKs,那么cwnd大小增加一。

  • 4.如果收到新的ACK,表明重传的包成功了,那么退出快速恢复算法。将cwnd设置为ssthresh,然后进入拥塞避免算法。

d4ac1fac7af135f901088572c592a42a.png

这里的第四步当发生3次冗余ACK时,先是cwnd缩小为一半,再将sstresh=cwnd,也就是避免了立马将cwnd降为1.

参考资料

TCP的快速重传机制 https://blog.csdn.net/whgtheone/article/details/80983882

糊涂窗口综合症和Nagle算法 https://www.jianshu.com/p/e9d2228ba015

TCP协议如何保证可靠传输 https://www.cnblogs.com/xiaokang01/p/10033267.html#_label0_5

TCP 拥塞控制算法 https://zhuanlan.zhihu.com/p/59656144

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值