P2PServer是我们设计的两大核心类之一。 它要能以高效率接收、发送udp请求,并且要能处理可靠和不可靠两种udp请求模式。为了方便使用,它还要易于扩展、自定义,所以我们模仿了asp .net core的设计,并且给它加入了构造函数依赖注入功能。
源代码
细节设计内容
为了让使用者可以高度自定义对不同udp请求的处理方法,我们设计了规范的请求接口。并且采用了注册处理类的模式。用户只需要自己写一个处理类,并且注册进来,请求到来的时候就会自动调用处理类里的函数。
处理类就和普通的类一样,设计上对应asp .net core中的Controller,其中的处理函数必须带上HandlerAttribute,这个Attribute需要接受一个CallMethod参数,该参数决定了它会用来处理哪样的请求。 处理方法同时需要接收一个UdpContext类型的参数,这里边包含了书记请求的数据。处理类的构造函数可以包含参数,这些参数每次请求到来的时候会自动从依赖注入容器中获得。
注册处理类使用AddHandler<T>方法。
注册处理类的时候,我们会通过反射获取它的元数据信息并且保存在字典中。之后每次请求到来的时候使用元数据构造这个类,并且调用相应的处理方法。
所以不宜在Handler的构造函数中加入大运算量的操作。如果必须用到,请考虑通过依赖注入的方式来传入运算结果。
依赖注入和asp .net core的依赖注入基本一样,用ConfigureServices方法来配置。P2PServer默认会将自身注册入容器。
接收的时候,使用了一个死循环,用ReceiveAsync方法不断地接收他人的消息。接收到之后,会使用线程池里的线程来进行后续处理,接收线程会直接心如下一个循环。也就是说多个处理函数可能会并发地执行。所以如果大消息分片,需要考虑处理函数的线程安全问题。
可靠的udp信息会携带一个ReqId参数,一个可靠udp的传输过程如下:
发送方:
设定重传初始次数为0,生成一个ReqId
将ReqId带在发送数据里发送,等待ACK
若一定时间内收到ACK,则结束发送的Task<bool>,返回true。
若超过设定的最大等待时间,则重传次数++
若重传次数大于最大重传次数,则结束发送的Task<bool>,返回false。否则回到1.2
接收方:
初始化一个字典,键是Guid类型,值是DateTime类型
等待接收发送方的信息
接收到信息
删除字典中所有值记录的时间早于现在10秒的键值对
若带有ReqId参数,返回ACK信息,带上ReqId
检查键为ReqId的信息是否在字典中
若不存在,则将键值对ReqId:DateTime存入字典,调用对应的Handler。
若存在,则不进行任何操作
返回第二步
需要注意,UdpClient类中的方法并不是线程安全的。它能够同时接收且发送,但是不能同时在多个线程中使用发送或者接收。
所以我设计了一个发送队列,使用一个MsgQueue实现了死循环的异步遍历。最大限度地增加发送速度而保证了线程安全。
所以,实际上发送信息的操作只是将需要发送的消息enqueue。
关于MsgQueue的细节参考此处
本文章使用limfx的vscode插件快速发布