问题来源:
老式方法:UDP传输设定超时未N秒,发送一个请求后等待N秒钟,若超时都没有收到确认,则重发请求,重发一定次数后便丢弃。
老式方法不合理的原因:由于网络上影响因素的不同,可能RTT差别较大,设定一个固定的超时时间使资源不能得到合理应用。
较好的方法:根据实测的RTT及其他因素考虑在内来估计超时时间。
术语
RTO:重传超时
Srtt:平滑化的RTT估算因子
Reevar:平滑化平均偏差估算因子
G:施加在RTT估算因子上的增益1/4
H:施加在平均偏差估算因子上的增益1/8
计算规则:
下一次待用的RTO = srtt+4*rttvar; 初始化为4*0.75=3S
Delta = RTT –srtt;
Srtt+=g*delta;
Rttvar+=h*(|delta| - rttvar);
RTO = srtt + 4*rttvar;
Jacobson算法:当重传定时器到时,对下一个RTO实行指数回退。每次测得一个RTT就计算一次RTO。
Jacobson算法不足:第一次请求发出后,超时后为收到,接着发出第二次请求,刚好又收到第一次请求的应答。 这样第二次的请求会把第一次的应答当做他的应答来计算RTT,这明显有二义性。当客户收到一个应答时,他不知道这个应答时针对那一次请求的。
Karn算法:只根据没有重传的请求产生的应答来计算RTT,对于重传而产生的应答不计算RTT。若收到重传的应答,这个RTO还是用于下一次请求。
实际应用karn算法时,给每个请求冠一个服务器必须回射的序列号和服务器同样必须回射的时间戳。
发送一个请求时,把这个时间戳保存一并发出,服务器端并不修改时间戳。服务器端回射这个请求时,再将这个回射的时间戳返回来。 客户根据本地时间与时间戳之差就可以计算RTT。
#include "unp.h"
#include <setjmp.h>
#include <time.h>
static sigjmp_buf buf;
void func(int signo){
return siglongjmp(buf, 0);
}
int main(){
/*other process*/
signal(SIGALRM, func);
//清零重发计数器
again:
sendto();
if(sigsetjmp(buf, 1) != 0){/*由信号处理函数返回时*/
/*超时处理*/
if(/*超过重试次数*/){
errno = ETIMEOUT;
return -1;
}
else{
/*添加指数回退*/
goto again;
}
}
for( ; ; ){
n = recvfrom();
if(/*当前收到的应答的序列号和请求序列号相同*/)
break;
}
alarm(0);/*在超时范围内成功接收,关闭定时器*/
//重新计算RTO用于下一次发送请求
return n;
}
实现:
struct rtt_info{
float rtt_rtt; /*RTT 往返延时*/
float rtt_srtt; /*SRTT 平滑后的RTT估算因子*/
float rtt_rttval; /*rttval 平滑后的平均偏差估算因子*/
float rtt_rto; /*RTO 重传超时*/
int rtt_nrexmt; /*重传计数器,每个请求都初始化为0*/
uint32_t rtt_base; /*时间戳基数*/
};
#define RTT_RXTMIN 2 /*最小重传超时时间*/
#define RTT_RXTMAX 60 /*最大重传超时时间*/
#define RTT_MAXNREXMT /*最大重传次数*/
#define RTT_RTOCALC(ptr) (ptr)->rtt_srtt + 4*(ptr)->rtt_rttval /*计算RTO宏*/
int rtt_init(struct rtt_info *info){/*仅调用一次用于初始化*/
struct timeval t;
bzero(info, sizeof(struct rtt_info));
info->rtt_rttval = 0.75;/*初始化RTO为3S*/
info->rtt_rto = info->rtt_rtt +(info->rtt_rttval)*4;/*3秒*/
if(gettimofday(&t, NULL) < 0 )
return -1;
info->rtt_base = t.tv_sec; /*取得时间戳基数,用于计算RTT*/
return 0;
}
uint32_t rtt_ts(struct rtt_info *info){/*取得当前的时间戳*/
struct timeval *t;
if(gettimeofday(&t, NULL) < 0)
return -1;
return (t.tvsec - info->rtt_base)*1000 + t.tv_usec / 1000; /*毫秒为单位*/
}
void rtt_newpack(struct rtt_info *info){/*在第一次发送请求时使用*/
info->rtt_nrexmt = 0;
}
float rtt_minmax(float rto){ /*RTO的最大最小限制*/
if(rto > RTT_RXTMAX)
rto = RTT_RXTMAX;
else if(rto < RTT_RXTMIN){
rto = RTT_RXTMIN;
}
return rto;
}
void rtt_stop(struct rtt_info *info, uint32_t ms){/*收到应答后,计算RTT估算因子和RTO*/
double delte;
info->rtt_rtt = ms / 1000;
delta = info->rtt_rtt - info->rtt_srtt;
info->rtt_srtt += delta/8;
info->rtt_rttval +=(abs(delta) - info->rtt_rttval)/4;
info->rtt_rto = rtt_minmax(RTT_RTOCALC(info));
}
int rtt_timeout(struct rtt_info *info){
info->rtt_rto *=2;/*指数回退*/
if(info->rtt_nrexmt++ T_MAXNREXMT)
return -1
else
return 0
}
UDP 超时重传机制
最新推荐文章于 2021-11-13 22:26:17 发布