今天有人问到如何增加UDP的可靠性,我猜大概就是模仿TCP的一些性能吧,做完实验回来后(学的是通信,不是计算机,有点小尴尬)自己看了一下讲UDP可靠性的UNP22章中的某小节,感觉略有收获,跟大家分享一下。
给UDP增加可靠性的方法主要有如下两种(真的就是借鉴TCP):
1.超时和重传
2.序列号
既然有超时和重传,那么我们就要设定定义超时的时间界限并给出重传的具体机制,那么超时时限RTO怎么计算呢,重传具体机制又是怎么样的呢,让我们先来看一下整体结构(原理写在注释中):
/*我们每次发送报文测得一个RTT(实际往返时间),就按照如下公式计算new_RTTs: new_RTTs = (1-a) * old_RTTs + a * RTT 实际的重传时间RTO会设置得更大一些,毕竟误差是要考虑的,具体公式如下: RTO = new_RTTs + 4 * RTT_delay 其中RTT_delay计算公式如下: new_RTT_delay = (1-b) * old_RTT_delay + b * |old_RTTs - RTT| 对于上面的a,b,RFC给出的建议值为a = 1/8, b = 1/4 */ /*书上用的公式形式是: delta = 测得RTT - srtt srtt = srtt + 1/8*delta rttvar = rttvar + 1/4*(|delta|-rttvar) RTO = srtt + 4*rttvar 实际上变换一下就是上面的式子 */ static struct rtt_info { float rtt_rtt; //测得的RTT float rtt_srtt; //上述的RTTs float rtt_rttvar; //上述的RTT_delay float rtt_rto; //RTO int rtt_nrexmt; //重传次数 uint32_t rtt_base; //时间,从1970.1.1至今经过的秒数 }rttinfo; #define RTT_RXTMIN 2 //最短重传时间 #define RTT_RXTMAX 60 //最长重传时间 #define RTT_MAXNREXMT 3 //最大重传次数 static int rttinit = 0; void rtt_debug(struct rtt_info *); void rtt_init(struct rtt_info *); //初始化rtt void rtt_newpack(struct rtt_info *); //把重传次数设置为0 int rtt_start(struct rtt_info *); //以秒为单位返回RTO void rtt_stop(struct rtt_info *, uint32_t ms); //更新RTT估算因子并计算新的RTO int rtt_timeout(struct rtt_info *); //重传定时器期满时调用 uint32_t rtt_ts(struct rtt_info *); //返回当前时间戳 extern int rtt_d_flag; static struct msghdr msgsend, msgrecv; static rttinit = 0; static struct hdr { uint32_t seq; //序列号 uint32_t ts; //发送的时间戳 }sendhdr, recvhdr;
接下来介绍一下上述函数
扫描二维码关注公众号,回复:
1621730 查看本文章
void
rtt_init(struct rtt_info *ptr)
{
struct timeval tv;
Gettimeofday(&tv, NULL);
ptr->rtt_base = tv.tv_sec; /* # sec since 1/1/1970 at start */
ptr->rtt_rtt = 0;
ptr->rtt_srtt = 0;
ptr->rtt_rttvar = 0.75;
ptr->rtt_rto = rtt_minmax(RTT_RTOCALC(ptr)); // 初始化 RTO = (srtt + (4 * rttvar)) = 3 秒
}
//重传次数置0
void rtt_newpack(struct rtt_info *ptr) { ptr->rtt_nrexmt = 0; }
int rtt_start(struct rtt_info *ptr) { return((int) (ptr->rtt_rto + 0.5)); /* round float to int */ /* 4return value can be used as: alarm(rtt_start(&foo)) */ }
void rtt_stop(struct rtt_info *ptr, uint32_t ms) { double delta; ptr->rtt_rtt = ms / 1000.0; /* measured RTT in seconds */ /* * Update our estimators of RTT and mean deviation of RTT. * See Jacobson's SIGCOMM '88 paper, Appendix A, for the details. * We use floating point here for simplicity. */ delta = ptr->rtt_rtt - ptr->rtt_srtt; ptr->rtt_srtt += delta / 8; /* g = 1/8 */ if (delta < 0.0) delta = -delta; /* |delta| */ ptr->rtt_rttvar += (delta - ptr->rtt_rttvar) / 4; /* h = 1/4 */ ptr->rtt_rto = rtt_minmax(RTT_RTOCALC(ptr)); } /* end rtt_stop
//返回当前时间
uint32_t
rtt_ts(struct rtt_info *ptr)
{
uint32_t ts;
struct timeval tv;
Gettimeofday(&tv, NULL); //将当前时间放入tv
ts = ((tv.tv_sec - ptr->rtt_base) * 1000) + (tv.tv_usec / 1000);
return(ts);
}
int rtt_timeout(struct rtt_info *ptr) { ptr->rtt_rto *= 2; /*指数回退,我们规定重传定时器期满时,必须对下一个RTO应用某个指数级回退,即如果第一个RTO是2秒,若期间未收到应答则下一个RTO变为4秒,再发生该情况RTO变为8秒、16秒...以此类推,其实也很好理解,如果连应答都收不到,说明网络状况可能极差,这时指数级地增大重传时间应该可以缓解网络拥塞的情况*/ if (++ptr->rtt_nrexmt > RTT_MAXNREXMT) return(-1); /* time to give up for this packet */ return(0); }
当然,我们在不断计算更新RTO时可能会遇到“重传二义性"的问题。"重传二义性"是指重传定时器超时的原因可能有三种:
1.请求丢失
2.应答丢失
3.RTO太小
其中只有请求丢失和应答丢失并重传后所接收到的包是对应重传请求的包,而RTO太小使得重传后所收到的包是第一次请求的应答。为了解决这个问题,Karn提出了一个算法,当然还有更精妙的办法,所以这里不打算介绍Karn的算法。更精妙的解决办法是为每一个请求冠以一个服务器必须回射的序列号外,还为每个请求冠以服务器同样必须回射的时间戳。每次发送一个请求时,我们把当前时间保存在该时间戳中,当收到一个应答时,我们从当前时间减去由服务器在其应答中回射的时间戳就可算出RTT。这样计算出的RTT就不再有二义性。
以上就是UDP中加入重传机制的大致方法,若是有时间可以自己试着实现一下。