不管哪类场景,都是要保证可靠性,也就是质量,那么在UDP之上怎么实现可靠呢?答案就是重传。
重传模式
IP协议在设计的时候就不是为了数据可靠到达而设计的,所以UDP要保证可靠,就依赖于重传,这也就是我们通常意义上的RUDP行为,在描述RUDP重传之前先来了解下RUDP基本框架,如图:
图2
RUDP在分为发送端和接收端,每一种RUDP在设计的时候会做不一样的选择和精简,概括起来就是图中的单元。RUDP的重传是发送端通过接收端ACK的丢包信息反馈来进行数据重传,发送端会根据场景来设计自己的重传方式,重传方式分为三类:定时重传,请求重传和FEC选择重传。
定时重传很好理解,就是发送端如果在发出数据包(T1)时刻一个RTO之后还未收到这个数据包的ACK消息,那么发送就重传这个数据包。这种方式依赖于接收端的ACK和RTO,容易产生误判,主要有两种情况:
对方收到了数据包,但是ACK发送途中丢失。
ACK在途中,但是发送端的时间已经超过了一个RTO。
所以超时重传的方式主要集中在RTO的计算上,如果你的场景是一个对延迟敏感但对流量成本要求不高的场景,就可以将RTO的计算设计比较小,这样能尽最大可能保证你的延时足够小。例如:实时操作类网游、教育领域的书写同步,是典型的用expense换latency和Quality的场景,适合用于小带宽低延迟传输。如果是大带宽实时传输,定时重传对带宽的消耗是很大的,极端情况会用20%的重复重传率,所以在大带宽模式下一般会采用请求重传模式。
请求重传就是接收端在发送ACK的时候携带自己丢失报文的信息反馈,发送端接收到ACK信息时根据丢包反馈进行报文重传。如下图:
图3
这个反馈过程最关键的步骤就是回送ACK的时候应该携带哪些丢失报文的信息,因为UDP在网络传输过程中会乱序会抖动,接收端在通信的过程中要评估网络的jitter time,也就是rtt_var(RTT方差值),当发现丢包的时候记录一个时刻t1,当t1 + rtt_var < curr_t(当前时刻),我们就认为它丢失了,这个时候后续的ACK就需要携带这个丢包信息并更新丢包时刻t2,后续持续扫描丢包队列,如果他t2 + RTO <curr_t,再次在ACK携带这个丢包信息,以此类推,直到收到报文为止。这种方式是由丢包请求引起的重发,如果网络很不好,接收端会不断发起重传请求,造成发送端不停的重传,引起网络风暴,通信质量会下降,所以我们在发送端设计一个拥塞控制模块来限流,这个后面我们重点分析。除了网络风暴以外,整个请求重传机制也依赖于jitter time和RTO这个两个时间参数,评估和调整这两个参数和对应的传输场景也息息相关。请求重传这种方式比定时重传方式的延迟会大,一般适合于带宽较大的传输场景,例如:视频、文件传输、数据同步等。
除了定时重传和请求重传模式以外,还有一种方式就是以FEC分组方式选择重传,FEC(Forward Error Correction)是一种前向纠错技术,一般是通过XOR类似的算法来实现,也有多层的EC算法和raptor涌泉码技术,其实是一个解方程的过程。应用到RUDP上示意图如下:
图4
在发送方发送报文的时候,会根据FEC方式把几个报文进行FEC分组,通过XOR的方式得到若干个冗余包,然后一起发往接收端,如果接收端发现丢包但能通过FEC分组算法还原,就不向发送端请求重传,如果分组内包是不能进行FEC恢复的,就请求想发送端请求原始的数据包。FEC分组方式适合解决要求延时敏感且随机丢包的传输场景,在一个带宽不是很充裕的传输条件下,FEC会增加多余的冗余包,可能会使得网络更加不好。FEC方式不仅可以配合请求重传模式,也可以配合定时重传模式。