UDP如何实现可靠传输

UDP如何实现可靠传输

  • 前言
  • 1 可靠性传输
    • 1.1 常见保证可靠性的策略有哪些?
    • 1.2 重传机制
      • 1.2.1 重传的三种策略
      • 1.2.2 重传的时机
    • 1.2 流量控制
    • 1.3 拥塞控制
      • 1.3.1 慢启动
      • 1.3.2 快速恢复
    • 1.4 基本概念
  • 2 UDP可靠性设计
    • 2.1 协议设计
    • 2.2 具体实现
  • 3 UDP使用场景
  • 4 UDP编程需要注意的一些坑
    • 4.1UDP乱序问题
    • 4.2 发送包大小
    • 4.3 接收数据

前言

一般而言对于大部分的应用使用TCP既可以满足工程的需求,因为TCP即保证的可靠性,又是一个相对绅士的协议,对于网络拥塞提供了流量和拥塞控制保证了网络友好性。那为啥还需要使用UDP呢?主要是因为在一些实时性要求比较高的情景中TCP没办法满足要求同时对于数据量不大的低功耗情景也是考虑使用的UDP,但是需要在用户态自行保证可靠性,也就是接下来需要阐述的内容。

1 可靠性传输

UDP的可靠性,其实是借鉴TCP可靠性传输的策略,并且化繁为简取其精华。首先我们需要了解TCP是如何保证可靠性的。

1.1 常见保证可靠性的策略有哪些?

(1)ACK机制
(2)重传机制
(3)序号机制
(4)重排机制
(5)窗口机制

ACK机制比较好理解,即当接收到包即发送ACK进行确认。序号机制和重排机制用的也比较多,网络接收到数据包有可能是乱序的这时候需要在发送之初把每一个包的序号先加上,接收到之后进行重排即可。重传和窗口机制比较复杂,下面分开阐述。

1.2 重传机制

1.2.1 重传的三种策略

1. 停等协议
这是一种比较古老的方式,效率比较低,主要是每发送一帧数据后需要接收到对方的回复之后才发送下一帧数据,主要是用于对话的模式,一问一答。
UDP如何实现可靠传输_第1张图片

2. 回退N帧重传
这种方式目前TCP仍然有在使用,就是发送一串连续的包,当中间存在丢包的情况后,会回复确认连续收到包的最大值,对应丢包以后的数据包都需要重传。
UDP如何实现可靠传输_第2张图片
3. 选择性重传
从上面回退N帧的方式发现比较浪费带宽,因此想到一一个方式,就是我们只重传对方丢失的包。可以正向告诉发送端,哪一个报文丢失,重传丢失报文即可。

1.2.2 重传的时机

(1)发送方没有收到ack,则重传对应的报文;
(2)接收方收到的序号有缺失,这时候发送信息告诉发送方重传相应的包。

1.2 流量控制

1. 流量控制的目的
主要是是为了保证网络带宽的通畅,防止带宽浪费,而引入的机制,主要是在发送方进行流量的控制,即控制发送方的发送速率。
2. 流量控制原理
利用发送端和接收端的窗口进行调节当前的流量。边界条件,当接收端窗口为0时,发送方需要终止发送。即没有可以接收的空间,需要终止发送。
3. 终止发送后,什么时候恢复发送速率?
(1)接收端读数据时,需要更新窗口给发送方。
(2)隔一段时间发一个探测包去询问。接收方需要回应窗口大小。

1.3 拥塞控制

这里介绍两种策略,这幅图估计大部分人都不陌生。UDP如何实现可靠传输_第3张图片

1.3.1 慢启动

慢启动:其实并不慢主要是起点低,从一个包开始发送,然后指数递增。
UDP如何实现可靠传输_第4张图片

1.3.2 快速恢复

快速恢:快速恢复并不快采用的是线性递增的方式,主要是起点高从阈值的一半开始。
UDP如何实现可靠传输_第5张图片

1.4 基本概念

RTO:
定时器超时时间,即超时后需要进行数据包重传。
注意:
tcp超时计算:第一次重传时间:RTO2,连续丢包三次之后变成:RTO8,延迟非常大。
RTT:
网络往返时延,需要记录发送方和接收方的时间戳。

2 UDP可靠性设计

基于应用层的可靠传输,udp可靠设计没有万精油,需要根据具体的使用场景。

2.1 协议设计

协议设计:
|同步字|总字节大小|分片数|分片编号|载荷大小|预留|荷载|
typedef struct packet
{
    int recv_pieces;								//当前已经接收的数量;
    int total_size; 								//总数据大小
    int left; 										//最后一片大小
    int paiece_size; 								//分片大小
    int recv_len;  									//接收数据长度
    uint8_t *recv_buf;								//保存接收数据
    uint8_t * send_pt; 								//指向发送数据buffer
   	uint8_t piece_buf[PIECE_FIX_SIZE+HEAD_SIZE+1] ;	//单帧的buf
   	circular_buffer_t *circular_buffer; 			//环形缓存
}packet;

2.2 具体实现

这里不展开写具体实现,但是需要注意的几个点,UDP可靠性传输需要考虑的几个点,一个是重传机制,丢包需要进行重传,可以用ACK也可以用NACK的方式;第二是重排机制,我们在收到乱序数据一定需要增加一个缓冲区进行数据重排;第三是超时机制,长时间没收收到对方的回复需要进行重试;第四,流量控制,在局域网内一般是不考虑这部分,实现起来比较复杂,收益不是那么大。

3 UDP使用场景

  1. 实时性考虑
  2. 资源消耗考虑

具体场景主要包括下面几方面:

1 音视频通话(网络延时,tcp不可以控制重传,延时太大,udp可以控制重传时间);
2 游戏开发(实时性操作:王者荣耀;传输位置,延迟会造成卡顿)
3 DNS查询(一问一答;一个包就可以,丢包直接重发就行)
4 物联网设备监控,用电池(活跃状态耗电,睡眠,发送数据量不大)
5 心跳机制 监测设备在不在线心跳包

4 UDP编程需要注意的一些坑

4.1UDP乱序问题

一般采用接收缓冲区进行排序

4.2 发送包大小

  • sendto一次发送1400字节
  • 需要小于最小的MTU,以太网数据帧一般是1500字节
  • 经验值:1400 (实时通讯)500(游戏)主要是包比较小有一定的优先权

4.3 接收数据

  • recvfrom一次需要完整读取报文
  • udp一次只能收一个包,没有边界问题(报文传输)
  • tcp一次可以收一部分数据,有黏包问题(流式传输)

你可能感兴趣的:(Linux网络编程,网络,服务器,udp)