在 DotNetty 中实现同步请求

1、背景

DotNetty 自己是一个优秀的网络通信框架,不过它是基于异步事件驱动来处理另外一端的响应,须要在单独的 Handler 去处理相应的返回结果。而在咱们的实际使用当中,尤为是 客户端程序 基本都是 请求-响应 模型,在发送了数据时候须要等待服务器的响应才能进行下一步操做,若是服务器返回的是错误信息,则须要进行特殊的处理。html

相似于下面这种方式:git

public async void Button1_Click()
{
    var result = await DotNettyClient.SendData("Hello");
    
    if(result == "Error")
    {
        throw new Exception("服务器返回错误!");
    }
    
    Console.WriteLine($"Hello {result}");
}

2、解决思路

参阅了大部分资料以后,发如今 Java 的 Netty 当中可使用 Future / Promise 来实现,那么 C# 是否有相似的组件呢?答案是有的,他们对应的就是 TaskTaskCompletionSource,前者是给调用者的任务,然后者则是用于设置响应任务的结果。github

那么咱们就能够这么来处理,当客户端发送请求时,附带惟一的一个请求 ID,并将 TaskCompletionSource 放在一个请求字典当中,请求 ID 做为字典的 Key,值是 TaskCompletionSource,以后返回一个 Task。当客户端接收到服务器响应的时候,经过 TaskCompletionSource 设置以前那个 Task 的结果,这样咱们接收到响应以后,就会从以前 await 的地方继续执行。服务器

这里我本身的需求仅仅是相似于 同步阻塞式 的操做,因此我直接使用一个队列来作简单处理,并无用惟一的请求 ID 来表示不一样的请求,也没有使用字典,由于我能够 保证在同一时间内有且仅有一个客户端请求被发起,并且也作了响应的超时处理机制。网络

3、代码实现

实现起来超级简单,只须要在发起请求的时候,建立一个 TaskCompletionSource<TResponse> 对象。这个泛型参数指的是你想要的返回值类型,这里我以 TResponse 代替,下面的 DEMO 我会用 string 类型进行演示。框架

建立好一个 TaskCompletionSource<TResponse> 以后,在发送方法里面,咱们能够将其对象放在一个先进先出的队列当中,而后将其 Task 属性做为发送方法的返回值。异步

咱们再来处处理服务器响应的 Handler 当中,从队列里面拿去以前存放的 TaskCompletionSource<TResponse> 对象,调用其 SetResult() 方法,将具体响应进行设置。async

经过以上的操做,咱们在发送数据的时候,就可使用 await 关键字等待服务端的响应,但不会阻塞线程,当客户端接收到服务端响应时,就会恢复到以前 await 的位置继续执行。ide

数据发送方法:线程

public static class DotNettyClient
{
    static DotNettyClient()
    {
        RequestQueue = new Queue<TaskCompletionSource<string>>();
    }
    
    public static Queue<TaskCompletionSource<string>> RequestQueue { get; set; }
    
    public static async Task<string> SendData(string data)
    {
        var resultTask = new TaskCompletionSource<string>();
        
        var buffer = new Unpooled.Buffer();
        buffer.WriteBytes(Encoding.UTF8.GetBytes(data));
        await _clientChannel.WriteAndFlushAsync(buffer);
        
        RequestQueue.Enqueue(resultTask);
        
        return await resultTask.Task;
    }
}

服务端响应处理:

public class ProtocolHandler : ChannelHandlerAdapter
{
    public override void ChannelRead(IChannelHandlerContext context, object message)
    {
        if(message is string response)
        {
            if(!DotNettyClient.RequestQueue.TryDequeue(out TaskCompletionSource<string> result)) return;
            result.SetResult(response);
        }
    }
}

这里我就再也不编写解析器,主要说明一下代码的思路,下面在使用的时候就如同第一节说的同样,直接使用 await 关键字等待响应结果便可。

4、缺陷

在这里我并无展现多个异步请求的状况,若是是用户同时发起多个请求的时候,你能够经过数据的惟一 ID 来标识每个请求,读取时根据惟一 ID 从字典获取数据,这样在接收服务端响应的时候就能处理这种状况了。

5、参考资料

相关文章
相关标签/搜索