TCP的乱序和丢包判断(附Reordering更新算法)-实例case

本文通过多个packetdrill脚本示例,详细解析TCP协议中的乱序空洞边缘确定、RACK机制与reordering值关系、reordering更新与快速重传顺序及TCP记分板Marklost算法自适应性等核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写前一篇文章TCP的乱序和丢包判断(附Reordering更新算法)-理论的时候,我觉得我在一边拉一边吃,玩的都是排泄物,言之无味,不知所云,我想把一些能看得见摸得着的东西独立出来,就成了本文,如果有一天我忘掉了TCP的细节,我想我直接把本文的例子跑一遍,应该就能拾起个大概了。

声明

本文完全旨在解释上一篇文章里那些枯燥的理论,我实在是觉得自己文字功底差,一直以来都倾向于用例子来给出解释。花了点时间整理了几个用例,希望能把问题解释清楚。在用实例解释问题的时候,最忌讳的是把很多因素杂糅了一起,因为本来就是通过特例解释,并没有完备性支撑,所以我尽量把问题孤立化,加之以前也写了不少文章解释其它的方面,所以本文的所有用例均采用以下的配置:

net.ipv4.tcp_fack = 0
net.ipv4.tcp_sack = 1
net.ipv4.tcp_congestion_control = cubic

关于fack开启的情形,请自行分析。另外本文的主要目的是解释reordering相关的,不是拥塞算法,所以默认采用了cubic算法,至于其它的比如bbr算法(以及任何携带cong_control回调的拥塞控制算法)下是什么表现,本文不涉及。

  另外,本文的所有实例均是packetdrill脚本,如果你不会的话就请自行谷歌百度packetdrill的使用方法并下载安装,本文并不负责介绍关于packetdrill的任何东西,给出一个链接算是比较厚道了:Packetdrill 简明使用手册

case 1:认识乱序空洞左边缘确定

这个用例可以让你清晰看懂如何来确定乱序空洞的边缘在哪里以及如何确定。为此我准备了两个packetdrill脚本来进行对比性解释。首先看第一个,packetdrill代码如下:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000
// 确保已经确认到了1001
+.1 < . 1:1(0) ack 1001 win 32792
// 连续发送10个段
+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
// 重复确认1001,同时携带一个6001-7001的SACK
+.1 < . 1:1(0) ack 1001 win 257 <sack 6001:7001,nop,nop>
// 随即重复确认1001,同时再携带两个SACK
+.0 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>
+.0 %{ print tcpi_reordering }%

// 最终完全确认10个数据包
+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

以下是脚本的输出:

3  
8  
8

下面我给出一个解析,解释发生了什么。我把整个事情发生的过程整理成下图:

这里写图片描述

可见,虽然输出的是3,8,8,但事实上,由于reordering值的更新是单调递增的,所以reordering值经过的第二次8到7的更新是失败的,真实的reordering更新尝试应该是3,8,7.

  最终我们来看一下以上脚本过程的抓包:

这里写图片描述

第一个脚本到此结束了。

  接下来我们看下case 1的第二个例子,该例子说明了乱序空洞的右边缘如何确定。首先还是先给出packetdrill脚本的代码:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3  
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0 

+0  bind(3, ..., ...) = 0  
+0  listen(3, 1) = 0  

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>  
+0  > S. 0:0(0) ack 1 <...>  
+.1 < . 1:1(0) ack 1 win 32792  
+0  accept(3, ..., ...) = 4  

+0  write(4, ..., 1000) = 1000 
+.1 < . 1:1(0) ack 1001 win 32792  
+0  write(4, ..., 10000) = 10000  

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>  
+.0 < . 1:1(0) ack 1001 win 257 <sack 6001:7001,nop,nop>  
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257 
+.0 %{ print tcpi_reordering }%

它的输出是:

3
4
7

以下是关于该脚本运行时的图解:

这里写图片描述

依然有抓包来确认:

这里写图片描述

也许,到底为止,事情应该结束了,你也应该完全明白了乱序空洞左右边缘的确定方法以及reordering值的更新机制,但是且慢,还有一个更好的例子呢。该case的最后一个例子,旨在解释在收到携带SACK的ACK包时,遍历传输队列以求取乱序空洞左右边缘的过程。该例子的packetdrill代码如下:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000
+.1 < . 1:1(0) ack 1001 win 32792
+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 3001:4001,nop,nop>
+.0 < . 1:1(0) ack 1001 win 257 <sack 4001:5001 9001:10001,nop,nop>
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

它的输出是:

3
3
3

为什么reordering值没有变化?这是一个问题。按照左边缘和右边缘的确定方法,右边缘显然是9001开始的数据包,而左边缘是4001开始的数据包,这样一来reordering的值应该更新为6才对啊!但事实证明,reordering值并没有变化,依然是3,这是为什么?因为后一个ACK包携带的第一个SACK包在之前的最右边的SACK包3001之后,这明显是一个按序的确认,何来乱序呢?

  从常理上分析更容易理解,第一个SACK确认了3001开始的一个数据包,第二个SACK确认了从4001开始以及从9001开始的两个数据包,先发送的先被确认,这并不能表明出现了乱序啊,事实证明确实没有判定为乱序。确认了这一点之后,我们再来看收到SACK之后遍历TCP传输队列以确定乱序空洞左右边缘的算法:

for each skb in write-queue  
    if thisSACK contains skb && skb.SACKed == FALSE && skb.RETRANS == FALSE && skb < Hr
        Hl = skb
    if thisSACK contains skb  
        Hr = skb
        skb.SACKed = TRUE

if Hr > Hl && (Hr - Hl + 1 > reordering)
    reordering = Hr - Hl + 1

请注意skb < Hr这个判断条件,想要让一个最新被SACK的数据包成为左边缘,不仅仅要求它此前没有被SACK以及此前没有被重传,还要求它不超过此前的右边缘。有了这个判定,对于事情的理解就简单多了。有了这个理解,我们来猜一下下面脚本的结果:

...
+0  write(4, ..., 10000) = 10000
+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 3001:4001 8001:9001,nop,nop>
+.0 < . 1:1(0) ack 1001 win 257 <sack 4001:5001 9001:10001,nop,nop>
+.0 %{ print tcpi_reordering }%
+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

显然,答案是:

3
6
8

好的,现在我们进入下一个更加简单些的用例。


case 2 认识RACK机制与reordering值的关系

依旧先看一个脚本:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>
// 间隔一些时间再SACK新的数据包
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:6001,nop,nop>
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出是:

3 
5 
6

以下给出一个图解,解释一下为什么:

这里写图片描述

下面是抓包的分析确认:

这里写图片描述

如果我们连续发送两个SACK,不给RACK定时器超时的机会会怎样?下面我们就试一下,请运行下面的packetdrill脚本:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>
// 立即SACK新的数据包
+.0 < . 1:1(0) ack 1001 win 257 <sack 5001:6001,nop,nop>
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出是:

3 
5 
7

下面是一个图解:

这里写图片描述

依然给出抓包分析:

这里写图片描述

看来,归根结底这并不是RACK的影响,而是RACK定时器的触发影响了TCP的拥塞窗口,导致两种情况下重传的数据包数量不同,进而造成了两种情况下乱序空洞的左边缘不同(左边缘不能被重传过),最终左右边缘的间距不同。

  接下来,我们看个更简单的用例。


case 3 认识reordering更新与快速重传的先后顺序

假设当前的reordering值是默认值3,开启SACK的情况下,有人会说,为什么已经有3个数据包被SACK,依然没有触发快速重传呢?比如下面的脚本所示:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:6001 9001:10001,nop,nop>
// 如果下面的这行不是随即确认,而是间隔一段时间(把+.0改成+.1),待RACK超时发生重传后,情况将会有大不同,请自行分析!
+.0 < . 1:1(0) ack 1001 win 257 <sack 2001:3001,nop,nop>
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出如下:

3  
8  
8

从输出上看,在收到第二个包含两个SACK段的包后,虽然被SACK的包量达到了3个,但是此时的reordering值已经更新,可见reordering值的更新在判断快速重传条件之前!

  下面是一个图解:

这里写图片描述

下面给出抓包分析:

这里写图片描述

注意,虽然数据包被重传了,但不是快速重传所触发,而是RACK超时所触发!作为一个反过来的例子,下面的脚本可以印证上面的说法:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000

+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:6001 9001:10001,nop,nop>
+.0 < . 1:1(0) ack 1001 win 257 <sack 7001:8001,nop,nop>
+.0 %{ print tcpi_reordering }%

+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出如下:

3   
3  
6

该脚本几乎和上面的脚本没有什么区别,只是把第3个SACK从2001开始的包改成了从7001开始的包,按照reordering值的更新算法,这次将不会引发reordering值的更新,进而在收到3个SACK后触发快速重传,从下面的抓包分析中能看出来确实触发了快速重传:

这里写图片描述

以下是针对上述脚本的图解:

这里写图片描述

我们来看最后一个用例。


case 4 理解TCP记分板Mark lost算法的自适应性

这块内容比较独立,理论上的东西请参考我此前的文章:关于TCP快速重传的细节-重传优先级与重传触发条件。本文给出两个实际的例子来解释枯燥的理论。首先看第一个例子,该例子中,被SACK的包聚集于后面:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000
// 被SACK的包量大于3,触发快速重传,快速重传前进行数据包的LOST标记
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:10001,nop,nop>                     

+5 < . 1:1(0) ack 10001 win 257

很显然,按照Mark LOST算法,在最后保留reordering个被SACK的数据包,本例中,前面所有的数据包均将被标记为LOST,事实也正是如此!在我的probe输出中,一共有4个包被标记为LOST:

这里写图片描述

下面是此例的抓包分析:

这里写图片描述

再看一个例子,该例子中,被SACK的数据包聚集于前面:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0

+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0

+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4

+0  write(4, ..., 1000) = 1000

+.1 < . 1:1(0) ack 1001 win 32792

+0  write(4, ..., 10000) = 10000

+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:6001 9001:10001,nop,nop>
// 以下的打印不一定准确,因为存在一个异步的RACK定时器超时的过程,该过程同样会进行LOST标记,且与tcp_get_info的调用顺序不确定。
// 因此需要采用tcp_probe的方式,HOOK住tcp_xmit_recovery函数,然后打印tp->lost_out的值最为准确和实时。
+.0 %{ print tcpi_lost }%

+5 < . 1:1(0) ack 10001 win 257

依然用probe输出,被标记为LOST的包量仅为1!如下图所示:

这里写图片描述

我们再看下抓包以确认上述分析:

这里写图片描述

我们发现不止1个数据包被重传,然而仔细观察前面的时间戳后,发现从第二个重传包开始,便不再是快速重传所起的作用,而是RACK超时重传了,当RACK超时被触发后,有4个包被标记为LOST!在早期的内核中,RACK尚未文档化的时候,内核当然并没有引入RACK机制,没有RACK,就只能timeout了。引入了RACK,反而更加令人迷惑,然而正如抓包看到的那样,虽然没有触发快速重传,但不那么慢的间隔内,重传确实发生了,这就是RACK带来的东西。

遗留的问题

本文根本没有详述关于RACK的细节,但这并不意味着它不重要,RACK是作为时间序丢包判断最近才出现的,以下三类丢包判断是并列的:

  • 空间序-dupthresh: 3 OOO packets delivered (packet count)
  • 序列假定-FACK: sequence delta to highest sacked sequence (sequence space)
  • 时间序-RACK: sent time delta to the latest delivered packet (time domain)

这些问题并没有在本文中进行论述,因为这个话题有点大,所以说就先略过了。

<think>嗯,用户问视频电话语音电话中常说的RTP包抖动是否指的是乱序。我需要先明确RTP包抖动乱序的定义,然后分析它们之间的关系。 首先,RTP(实时传输协议)用于传输音视频数据,抖动通常指数据包到达时间的不稳定性,也就是延迟的变化。而乱序是指数据包到达的顺序与发送顺序不一致。虽然两者都属于网络传输问题,但它们是不同的概念。 接下来,用户可能想知道抖动是否由乱序引起,或者是否包含乱序的情况。需要解释抖动的成因,比如网络拥塞、路由变化等,而乱序可能由不同的路径或交换机缓冲导致。抖动的测量通常基于时间戳,而乱序则通过序列号检测。 然后,用户可能关心如何处理这些问题。比如,抖动缓冲可以缓解延迟变化,但乱序可能需要额外的重排序机制。同时,乱序严重时可能导致丢包或解码错误,进而影响音视频质量。 还要考虑用户可能的实际应用场景,比如在视频会议中遇到卡顿或声音断断续续,他们可能误将乱序归咎于抖动,需要明确区分。此外,用户可能希望了解如何诊断解决这些问题,比如使用Wireshark分析数据包的序列号时间戳。 最后,确保回答清晰区分抖动乱序,并提供实际例子解决方法,帮助用户理解两者的区别及关联。</think>RTP(实时传输协议)中的 **抖动(Jitter)** **乱序(Packet Reordering)** 是两个相关但不同的概念,需明确区分: --- ### **1. 抖动(Jitter)** - **定义**:指数据包到达时间的**延迟波动**,即相邻数据包的接收时间间隔不一致。 - **成因**: - 网络拥塞导致部分数据包被延迟。 - 路由路径变化(如动态路由切换)。 - 交换机/路由器的缓存机制。 - **测量方法**: - 根据RTP头部的 **时间戳(Timestamp)** **接收时间** 计算抖动值。 - 示例公式: $$ \text{Jitter} = |(R_i - S_i) - (R_{i-1} - S_{i-1})| $$ 其中 $R_i$ 为第 $i$ 个包的接收时间,$S_i$ 为发送时间(由时间戳推算)。 - **影响**: - 导致播放时音频卡顿、视频帧率不稳。 - 需通过 **抖动缓冲(Jitter Buffer)** 动态调整播放延迟以平滑抖动。 --- ### **2. 乱序(Packet Reordering)** - **定义**:数据包到达的顺序与发送顺序**不一致**。 - **成因**: - 数据包通过不同路由路径传输(如负载均衡)。 - 网络设备(如交换机)的缓存调度策略。 - **检测方法**: - 根据RTP头部的 **序列号(Sequence Number)** 判断顺序。 - 示例:若序列号 $n+1$ 的包在 $n$ 之前到达,则发生乱序- **影响**: - 解码器需缓存数据包以重新排序,可能增加延迟。 -乱序严重或超出缓存窗口,会导致丢包或解码错误(如视频花屏)。 --- ### **3. 抖动与乱序的关系** - **关联性**: - 乱序可能导致抖动增大(因后续包提前到达,需等待缺失包)。 - 但抖动不一定由乱序引起(如网络延迟波动本身可导致抖动)。 - **关键区别**: | **特性** | **抖动** | **乱序** | |----------------|----------------------------|---------------------------| | **核心指标** | 到达时间间隔波动 | 包顺序错乱 | | **检测依据** | 时间戳(Timestamp) | 序列号(Sequence Number) | | **主要影响** | 播放流畅性(延迟补偿) | 解码完整性(需重排序) | --- ### **4. 实际场景中的表现** #### **案例1:高抖动但无乱序** - **现象**:音频播放时出现周期性卡顿,但无杂音。 - **原因**:网络延迟波动大(如WiFi信号不稳定),但包顺序正常。 - **解决方案**:增大抖动缓冲区大小。 #### **案例2:严重乱序但低抖动** - **现象**:视频偶尔花屏,音频短暂失真。 - **原因**:网络设备缓存策略导致包顺序错乱(如QoS优先级调度)。 - **解决方案**: - 启用RTP的 **序列号修复(如RTCP SR/RR反馈)**。 - 在解码器端实现 **基于序列号的重排序缓存**。 --- ### **5. 如何诊断与解决** #### **诊断工具** - **Wireshark**: - 抖动分析:`Statistics → RTP → Stream Analysis`,查看Jitter曲线。 - 乱序检测:按序列号排序(`rtp.sequence_number`),检查缺失或错位的序列。 - **FFmpeg**: ```bash ffmpeg -i input.rtp -f null - 2>&1 | grep "packet loss" ``` #### **解决方法** - **抖动优化**: - 使用自适应抖动缓冲(Adaptive Jitter Buffer)。 - 优化网络QoS策略,优先保障实时流量。 - **乱序修复**: - 在接收端实现基于序列号的重排序缓存(如WebRTC的NetEq模块)。 - 配置网络设备禁用可能导致乱序的负载均衡策略。 --- ### **6. RTP协议设计的应对机制** - **时间戳(Timestamp)**: - 用于计算抖动并同步音视频播放。 - **序列号(Sequence Number)**: - 用于检测丢包乱序及重组数据流。 - **RTCP反馈**: - 接收端通过RTCP报告抖动、丢包率,发送端可动态调整码率或重传策略。 --- ### **7. 示例:Wireshark抓包分析** 1. **抖动分析**: - 显示过滤器:`rtp && ip.addr == <目标IP>` - 查看 `RTP Timestamp` `Delta`(相邻包时间间隔)。 2. **乱序检测**: - 添加列:`Right Click → RTP Sequence Number` - 按序列号排序,观察是否出现非递增序列。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值