logo
0
0
WeChat Login

MRP: Multiplexed Reliable Packets, over UDP

代码实现要求

这段本文用于指导Golang代码实现本协议。 代码应满足如下要求:

  • 每个实例绑定1个UDP端口,可以接受外来连接,也可以对外发起连接。
  • 对外提供如下对象:
    • Socket,表示1个MRP实例,所有全局操作的载体,也可以创建Track;需要实现为无锁结构体,并且通过TrackId可以直接找到对应的track,而不是通过Remote。
    • Track,主要的数据流操作对象;需要实现为无锁结构体。
    • Remote,类似于连接对象,但是应该尽量弱化它的存在,以Track为核心。需要实现为无锁结构体,并且通过TrackId可以找到对应的track。
    • Window,收发包窗口。每个Track上面要绑定2个窗口(Window)对象,分别处理该Track上的收包和发包。
    • Frame,数据帧对象,是上层应用之间交互的最小单位批次。实际可能拆分成多个Packet处理。
    • Packet,用于操作数据包,是实际收发的UDP数据包负载。
  • 使用1个协程(即goroutine,下同)接收所有包。
  • 使用1个协议,定时间隔扫描所有track的发包窗口,尝试进行发包。
  • 对外提供的Send接口,在发包窗口期内没有堆积的包时,尝试直接进行发包。
  • 外部可以注册一个对象,以处理如下事件:
    • socket状态变化
    • track状态变化
    • 收到数据帧
    • 数据帧发送结果

1. 简介

本协议(MRP,即“Multiplexed Reliable Packets”)与QUIC协议十分类似但更简单。 设计上,它与QUIC的主要不同之处在于不使用加密,因为我们的主要目标场景是用作内网RPC通讯。其余都是相似之处。

与QUIC一样,它完全基于UDP。 它以数据帧的形式向应用层交换数据,而不是字节流。但是超过MTU大小的数据帧实际会分成多个UDP包中发送。 它支持多流(这里我们称之为track,与QUIC中的stream对等), 相同的流内的数据包是有序的,不同的流之间完全没有干扰,甚至因为基于UDP所以也没有HTTP2上因为TCP导致的对头阻塞。

2. 概念

  • socket: 绑定1个UDP端口,表示1个MRP实例。下面挂了若干track和若干remote
  • track: 流。数据包通过流进行传输。每个track在不同的实例上有不同的id。
  • frame: 帧。是上层应用之间交互的最小单位批次。实际可能拆分成多个packet处理。
  • packet: 包。用于操作数据包,是实际收发的UDP数据包负载。
  • 握手: 两个MRP实例之间,需要通过握手建立track。
  • 确认: 接收方告诉发送方“我已收到你发送的数据包”。
  • 窗口: 暂存收发的数据包,分为发送窗口和接收窗口两种。
    • 发送窗口:数据包还未发送、或发送后还未被接收方确认,就会留在发送窗口中。
    • 接收窗口:数据包已接收但尚未调用外部回调进行处理,就会留在接收窗口中。

3. 包结构

本协议所有数据都放在UDP包内部。其内容包括一个16字节的头部,之后都是负载数据(Payload Data)。

3.1 头部结构

头部长度16字节,其中的所有字段都是小端的。其结构如下表所示:

bytes0123
0-3FlagsDataSize
4-7TrackId
8-11SeqNrSafeNum
12-15AckBaseAckExt
16-(15+DataSize)Payload Data

每个字段具体定义如下:

  • byte[0:1] 是 Flags 字段。
  • byte[2:3] 是 DataSize 字段,是负载数据的字节数。
  • byte[4:7] 是 TrackId 字段,是接收端的track的id。当发起握手时,此字段置0。
  • byte[8:9] 是 SeqNr 字段,表示当前包的编号,是无符号数,溢出时回卷到0。当发起握手时,此字段存储发起端的TrackId的低16位。
  • byte[10:11] 是 SafeNum 字段,用于结合SeqNr校验包体的合法性。当发起握手时,此字段存储发起端的TrackId的高16位。
  • byte[12:13] 是 AckBase 字段,表示前面的SeqNr都已被接收到了。
  • byte[14:15] 是 AckExt 字段,用于扩展AckBase的表达能力,可以表示AckBase之后的16个包是否已经收到。

Flags字段共16位,每个位的定义如下:

  • bit[0] 是 Hello 标志,表示这是个握手包。
  • bit[1] 是 Fin 标志,表示发送者已经关闭了此track。
  • bit[2] 是 Ack 标志,表示带有Ack信息。
  • bit[3] 是 Ctn 标志,表示该Frame尚未完结,还有后续Packet。

4. 握手

需要通过握手,才能创建track。支持0-RTT握手。

4.1 发起握手

发送方实例,分配1个自己未使用的32位TrackId,填充到SeqNr和Secret字段中,并将TrackId字段置0、Hello标志置1,然后发出。 发送后,track进入HelloOut状态。 因为支持0-RTT握手,可以在发包时直接带上负载数据。

4.2 接收握手

收到带有Hello标志时,就是一个握手包。 对端的TrackId存于SeqNr和Secret字段中,这个值应保存下来,后续所有发给该track的包,TrackId字段都应填充此值。

若TrackId字段为0,就是对端发起的握手。此时,track进入HelloIn状态。 若有负载数据,需要酌情通过回调呈现给应用层。 需要答复一个带Hello标志的包,TrackId设置成对端的TrackId,SeqNr和Secret字段设成本端分配的TrackId, 且需要按照确认机制设置AckBase和AckExt字段,若有负载数据也可以同时带上。 答复后,track进入Established状态,即握手已经完成。

若TrackId字段非0,就是本端发起的握手、对端进行了答复。此时,track进入Established状态,即握手已经完成。 若有负载数据,需要酌情通过回调呈现给应用层。

5. 确认机制

收到数据包后,应该进行确认(即Acknowledge或简称Ack),以使发送方知晓数据包已经收到。 Ack信息通过头部的AckBase和AckExt字段发送给对方。

头部的Flags字段中的Ack标志为1时,表示AckBase和AckExt是有效的。

AckBase表示发送端发送的所有SeqNr在AckBase之前的包,都已收到。 因为SeqNr和AckBase是16位无符号整数,必须注意超过65535后回滚到0的情况。

AckExt表示AckBase之后的16个包的接收情况,每1位表示1个包,为1的位表示对应的包已经收到。 发送方收到Ack后应立刻根据AckExt表示的情况,将尚未收到的包进行重发。

6. 窗口

暂存收发的数据包,分为发送窗口和接收窗口两种。

  • 发送窗口:数据包还未发送、或发送后还未被接收方确认,就会留在发送窗口中。
  • 接收窗口:数据包已接收但尚未调用外部回调进行处理,就会留在接收窗口中。

About

No description, topics, or website provided.
Language
Go100%