1 前言

之所以写本文章,是因为在我停止维护多年前写的NetworkSocket组件两年多来,还是有一些开发者在关注这个项目,我希望有类似需求的开发者明白为什么要停止更新,可以使用什么更好的方式来替换(其实很大原因是我把时间花在开发WebApiClient上面了)。那时.netcore还没有生下来,asp.net除了蜗居在iis里处理http,其它什么也不能干,而NetworkSocket是这样定义的:

NetworkSocket是一个以中间件(middleware)扩展通讯协议,以插件(plug)扩展服务器功能的支持SSL安全传输的通讯框架;目前支持http、websocket、fast、flex策略与silverlight策略协议。

2 Kestrel是什么

谈到asp.netcore,人们自然就想到它的默认服务器kestrel,在很多场景中,人们甚至认为kestrel等于Web服务器,或者说它只能处理http和http之上的东西。本文先在此下个定义:Kestrel是一款基于中间件来处理tcp连接的服务器,并内置了http(包含websocket、SignalR)解析中间件。也就是说,我们完全可以给kestrel添加其它中间件,用来处理非http的连接的业务场景,让kestrel使用一个端口支持多种协议或多协议一个端口一种协议的要求。

2.1 Kestrel的中间件是什么

在asp.netcore的Startup里,我们使用app.UseXXX的扩展方法来应用各种中间件,比如UseRouting、UseStaticFiles等等,它本质上还是调用了IApplicationBuilder.Use(Func<RequestDelegate, RequestDelegate> middleware),也就说Func<RequestDelegate, RequestDelegate>就是一个中间件。

对应的,在kestrel世界里,也有一个IConnectionBuilder.Use(Func<ConnectionDelegate, ConnectionDelegate> middleware)Func<ConnectionDelegate, ConnectionDelegate>就是kestrel的中间件,我们可以如下安装kestrel的中间件:

kestrel.ListenAnyIP(port: 80, listen =>
{
listen.Use(next => context =>
{
if(true)
{
// 中间件1的逻辑
}else
{
return next(context);
}
})
.Use(next => context =>
{
if(true)
{
// 中间件2的逻辑
}else
{
return next(context);
}
});
});

值得注意的是,kestrel的最后一个中间处理者是http中间件,以上代码,实际的kestrel已经包含3种处理者(文章后部分有中间件的篇幅,然后就容易理解了),逻辑1、逻辑2和http解析,我们可以简单理解为Startup的app对象,对应kestrel的内置的那个最后中间件。

2.2 Kestrel的ConnectionContext

在kestrel中间件里,最重要的对象就是ConnectionDelegate,它等同于Func<ConnectionContext,Task>,我们可以理解为它就是一个Hanlder,传入连接上下文,剩下就是我们要干的工作了,而中间件是除了这个Handler之外,我们还能拿到一个叫next的Handler,我们可以选择是否调用它,如果不调用,流程终止。

ConnectionContext是kestrel的一个Tcp连接抽象,其核心属性是Transport,表示双工传输层的操作对象,另外提供Abort()方法用于服务端主动关闭连接。基于ConnectionContext,很容易实现一个自定义协议的tcp双工通讯服务器,相比从Socket写起,我们可能可以减少100倍代码量,而得到的是更高性能的服务。

3 基于Kestrel的SignalR+Redis的推送服务

本实战中,我们使用asp.netcore内置的SignalR功能,外加自己实现的部分Redis协议(只简单实现发布订阅功能),来做一个消息从云端推送到客户端的服务,我们的服务对客户端支持redis协议订阅或Signal协议订阅,同时我们提供redis+signalR+http三种协议接口给云端其它微服务来发布消息,发布者不用关心客户端是什么协议,只需要选择自己喜欢的协议的发布接口来调用发布。

3.1 协议与ConnectionContext的关系

在我们的这个应用里,一个连接不允许同时使用SignalR和Redis并存协议,也就是说,一个连接在发起第一个请求里,就确定了它整个生命周期里的协议。所以,我们需要分析连接读取到的第一个数据包,确定它是否为Redis协议,如果不是redis协议,我们要将ConnectionContext传达到下一个中间件(即http中间件)。

3.2 使用Redis中间件

如下代码,Use里面就是Redis中间件,里面的个协议分析逻辑:

kestrel.ListenAnyIP(options.Port, listen =>
{
listen.Use(next => async context =>
{
if (await Protocol.IsRedisAsync(context))
{
logger.LogDebug($"{context.RemoteEndPoint} {nameof(ClientType.Redis)} 连接");
await redis.HandleAsync(context);
logger.LogDebug($"{context.RemoteEndPoint} {nameof(ClientType.Redis)} 断开");
}
else
{
logger.LogDebug($"{context.RemoteEndPoint} {nameof(ClientType.SignalR)} 连接");
await next(context);
logger.LogDebug($"{context.RemoteEndPoint} {nameof(ClientType.SignalR)} 断开");
}
});
});

Protocol类

/// <summary>
/// 连接的协议判断
/// </summary>
public static class Protocol
{
/// <summary>
/// 返回连接是否为redis协议
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
public static async Task<bool> IsRedisAsync(ConnectionContext connection)
{
var result = await connection.Transport.Input.ReadAsync();
var state = IsRedis(result);
connection.Transport.Input.AdvanceTo(result.Buffer.Start);
return state;
} /// <summary>
/// 返回数据是否为redis协议
/// 这里不必严格检查,只要能区分是http还是redis就行
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
private static bool IsRedis(ReadResult result)
{
if (result.Buffer.IsEmpty)
{
return false;
} var span = result.Buffer.FirstSpan;
return span.Length > 0 && span[0] == '*';
}
}

3.3 RedisConnectionHandler

在3.2代码里,有一个await redis.HandleAsync(context);这个redis就是RedisConnectionHandler实例,它的功能是处理一个redis连接从建立成功之后到断开的所有逻辑。

我们知道,Redis有好几十个命令,单单是实现发布和订阅功能,我们也要实现必要的8个命令。说到这里,我的脑海里又闪现出一个长长的switch(收到的cmd) case xxx的代码了,我们甚至还需要在switch之前写公共性的代码,比如打印收到的cmd内容,还需要在switch里特别强调default分支:我们不支持这个命令。。。

既然kestrel基于连接处理中间件,上层的asp.netcore也是基于请求处理中间件,我们完全也可以也依葫芦画瓢,造一个Redis命令中间件Builder,最后将所有Redis中间件串起来,Buid得一个Redis处理委托。

var builder = new PipelineBuilder<RedisContext>(appServices, context =>
{
// 没有handler来处理
return context.Client.ResponseAsync(RedisResponse.Error("unsupported cmd"));
})
.Use((context, next) =>
{
this.logger.LogDebug(context.ToString()); // 验证客户端是否已授权
return context.Cmd.Name != RedisCmdName.Auth && context.Client.IsAuthed == false
? context.Client.ResponseAsync(RedisResponse.Error("need auth password"))
: next();
}); // 添加各个cmd对应的handler条件分支
appServices
.GetServices<IRedisCmdHanler>()
.ForEach(item => builder.When(item.CanHandle, item.HandleAsync)); this.handler = builder.Build();

在RedisConnectionHandler,每收一个Redis命令,将命令包装为RedisContext,然后使用build出来的handler对象来处理这个RedisContext就行。剩下的工作,就是我们一个命令实现一个IRedisCmdHanler对象就行,逻辑完全分开。

IRedisCmdHanler接口:

/// <summary>
/// 定义redis命令处理者
/// </summary>
interface IRedisCmdHanler
{
/// <summary>
/// 返回是否可以处理
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
bool CanHandle(RedisContext context); /// <summary>
/// 处理
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task HandleAsync(RedisContext context);
}

3.4 统一Redis和Signal客户端操作接口

在Signal和Redis订阅之后,我们将他们的连接包装为统一接口的IClient对象,IClient提供PublishAsync()方法用于发布消息。

/// <summary>
/// 定义客户端的接口
/// </summary>
public interface IClient
{
/// <summary>
/// 获取唯一标识
/// </summary>
string Id { get; } /// <summary>
/// 获取连接时间
/// </summary>
DateTime ConnectedTime { get; } /// <summary>
/// 获取客户端类型
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
ClientType ClientType { get; } /// <summary>
/// 发送消息
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
Task<bool> SendMessageAsync(Message message);
}

3.5 IClient管理器

我们还需要维护一份单例的IClient管理器对象,用于维护正在订阅的客户端,在发布消息时,从这个管理器里查找IClient,并调用SendMessageAsync()方法发布消息内容。

3.6 SignalR部分

由于SignalR的内容非常简单,官方文档细节齐全,这里将不作任何讲解了。

4 总结

由于要讲解的内部比较多,篇幅和时间都有限,本文就只从思路上大概讲解Kestrel在多协议连接的场景的使用方式。一句话,中间件的使用,使得这些场景变得简单,那问题来了,什么是中间件,你理解了吗?

深入理解kestrel的应用的更多相关文章

  1. .NET Core 3.0之深入源码理解Kestrel的集成与应用(一)

      写在前面 ASP.NET Core 的 Web 服务器默认采用Kestrel,这是一个基于libuv(一个跨平台的基于Node.js异步I/O库)的跨平台.轻量级的Web服务器. 在开始之前,先回 ...

  2. .NET Core 3.0之深入源码理解Kestrel的集成与应用(二)

      前言 前一篇文章主要介绍了.NET Core继承Kestrel的目的.运行方式以及相关的使用,接下来将进一步从源码角度探讨.NET Core 3.0中关于Kestrel的其他内容,该部分内容,我们 ...

  3. 深度理解IIS下部署ASP.NET Core2.1 Web应用拓扑图

    原文:深度理解IIS下部署ASP.NET Core2.1 Web应用拓扑图 IIS部署ASP.NET Core2.1 应用拓扑图 我们看到相比Asp.Net, 出现了3个新的组件:ASP.NET Co ...

  4. .net core 源码解析-web app是如何启动并接收处理请求(二) kestrel的启动

    上篇讲到.net core web app是如何启动并接受请求的,下面接着探索kestrel server是如何完成此任务的. 1.kestrel server的入口KestrelServer.Sta ...

  5. 理解storm的ACKER机制原理

    一.简介:       storm中有一个很重要的特性: 保证发出的每个tuple都会被完整处理.一个tuple被完全处理的意思是: 这个tuple以及由这个tuple所产生的所有的子tuple都被成 ...

  6. ASP.NET 5 (vNext) 理解和概述

    概述 ASP.NET 5 (又称为vNext) 是自ASP.NET产生15年以来一次革命性的更新, 我们可以从以下几点来理解其概貌和意义: ASP.NET 5是开源的 ASP.NET 5开发的WebA ...

  7. Storm入门(六)深入理解可靠性机制

    转自http://blog.csdn.net/zhangzhebjut/article/details/38467145 一 可靠性简介 Storm的可靠性是指Storm会告知用户每一个消息单元是否在 ...

  8. netcore高性能Web服务器Kestrel分析

    Kestrel是aspnetcore中的web服务器之一,其本身有跨平台,轻量级,高性能的特点 在 ryzen 1600 12核cpu 测试环境中,瞬间每秒处理请求数能达到2w5以上,与netty不相 ...

  9. 理解ASP.NET 5 Web Apps

    来源https://docs.asp.net/en/latest/conceptual-overview/understanding-aspnet5-apps.html ASP.NET 5引入了几个新 ...

随机推荐

  1. 使用skimage处理图像数据的9个技巧

    介绍 我们非常熟悉结构化(表格)数据的预处理步骤.你可以找到缺失的值然后添补它,然后检测并处理异常值,等等这些步骤.这有助于我们建立更好.更健壮的机器学习模型.但是当我们处理图像数据时,应该如何进行预 ...

  2. 如何利用python实现为每行添加行数编号

    可能还有更好的方法,在这里我是这么写的,针对小文件可以,但是如果文件内容太多,这种方法感觉不太好 先把所有的数据读取出来,然后利用W覆盖写入模式打开文件进行写入 遍历枚举类型数据后,默认是从0开始,然 ...

  3. coding++:error Could not read JSON: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class java.lang.Object

    Spring源码中是使用容器中的ObjectMapper对象进行序列化和反序列化. 当我们将自定义的ObjectMapper对象放入IOC容器中后,会自动覆盖SpringBoot自动装载的Object ...

  4. TCP连接与HTTP请求

    一道经典面试题: 从 URL 在浏览器被被输入到页面展现的过程中发生了什么? 相信大多数准备过的同学都能回答出来,但是如果继续问:收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式.什么顺 ...

  5. [HDU2546]饭卡<dp 01背包>

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=2546 #题目描述: 电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额.如果购买一个商品之前, ...

  6. VLAN基础

    VLAN(Virtual Local Area Network)的中文名为"虚拟局域网".是将一个物理的局域网在逻辑上划分成多个广播域,从而实现二层隔离的技术. 一.VLAN的优点 ...

  7. Python——NumPy库入门

    1.数据的纬度 维度:一组数据的组织形式 1.1 一维数据 一维数据由对等关系的有序或无序数据构成,采用线性方式组织 ,对应列表.数组和集合等概念 列表:数据类型可以不同 ,如 3.1413, 'pi ...

  8. 使用 Visual Studio 开发、测试和部署 Azure Functions(二)测试,部署

    1,引言 上一篇介绍了使用使用 Visual Studio 开发 "Azure Functions" 函数,此篇介绍 “Azure Functions” 的测试以及直接从 Vist ...

  9. 《快速认识 Three.js 》

    此文仅作备份之用,为了更好的阅读体验,建议访问原文链接:<Three.js - 走进3D的奇妙世界.> ,感谢原作者的好文.

  10. c++类模板之分文件编写问题及解决

    我们在实际项目中一般习惯头文件(.h)和源文件(.cpp)分开写,这样做的好处良多,但是如果遇到了类模板,这样可能会有一点儿问题. 我们通过一个例子来看: person.h: #pragma once ...