原标题:深入理解kestrel的应用
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 middleware) ,也就说 Func 就是一个中间件。
对应的,在kestrel世界里,也有一个 IConnectionBuilder.Use(Func middleware) , Func 就是kestrel的中间件,我们可以如下安装kestrel的中间件:
kestrel.ListenAnyIP(port: 80, listen =>
{
listen. Use(next => context =>
{
if( true)
{
// 中间件1的逻辑
} else
{
returnnext(context);
}
})
. Use(next => context =>
{
if( true)
{
// 中间件2的逻辑
} else
{
returnnext(context);
}
});
});
值得注意的是,kestrel的最后一个中间处理者是http中间件,以上代码,实际的kestrel已经包含3种处理者(文章后部分有中间件的篇幅,然后就容易理解了),逻辑1、逻辑2和http解析,我们可以简单理解为Startup的app对象,对应kestrel的内置的那个最后中间件。
2.2 Kestrel的ConnectionContext
在kestrel中间件里,最重要的对象就是ConnectionDelegate,它等同于 Func ,我们可以理解为它就是一个Hanlder,传入连接上下文,剩下就是我们要干的工作了,而中间件是除了这个Handler之外,我们还能拿到一个叫next的Handler,我们可以选择是否调用它,如果不调用,流程终止。
ConnectionContext是kestrel的一个Tcp连接抽象,其核心属性是Transport,表示双工传输层的操作对象,另外提供Abort方法用于服务端主动关闭连接。基于ConnectionContext,很容易实现一个自定义协议的tcp双工通讯服务器,相比从Socket写起,我们可能可以减少100倍代码量,而得到的是更高性能的服务。
3 基于Kestrel的SignalR+Redis的推送服务 3.1 协议与ConnectionContext的关系
在我们的这个应用里,一个连接不允许同时使用SignalR和Redis并存协议,也就是说,一个连接在发起第一个请求里,就确定了它整个生命周期里的协议。所以,我们需要分析连接读取到的第一个数据包,确定它是否为Redis协议,如果不是redis协议,我们要将ConnectionContext传达到下一个中间件(即http中间件)。
3.2 使用Redis中间件
如下代码,Use里面就是Redis中间件,里面的个协议分析逻辑:
kestrel.ListenAnyIP(options.Port, listen =>
{
listen.Use(next => asynccontext =>
{
if( awaitProtocol.IsRedisAsync(context))
{
logger.LogDebug( $"{context.RemoteEndPoint} { nameof(ClientType.Redis)} 连接");
awaitredis.HandleAsync(context);
logger.LogDebug( $"{context.RemoteEndPoint} { nameof(ClientType.Redis)} 断开");
}
else
{
logger.LogDebug( $"{context.RemoteEndPoint} { nameof(ClientType.SignalR)} 连接");
awaitnext(context);
logger.LogDebug( $"{context.RemoteEndPoint} { nameof(ClientType.SignalR)} 断开");
}
});
});
Protocol类
///
///连接的协议判断
///
publicstaticclassProtocol
{
///
///返回连接是否为redis协议
///
///
///
publicstaticasyncTask< bool> IsRedisAsync(ConnectionContext connection)
{
varresult = awaitconnection.Transport.Input.ReadAsync;
varstate = IsRedis(result);
connection.Transport.Input.AdvanceTo(result.Buffer.Start);
returnstate;
}
///
///返回数据是否为redis协议
///这里不必严格检查,只要能区分是http还是redis就行
///
///
///
privatestaticboolIsRedis(ReadResult result)
{
if(result.Buffer.IsEmpty)
{
returnfalse;
}
varspan = result.Buffer.FirstSpan;
returnspan.Length > 0 && span[0] == '*';
}
}
3.3 RedisConnectionHandler
在3.2代码里,有一个await redis.HandleAsync(context);这个redis就是RedisConnectionHandler实例,它的功能是处理一个redis连接从建立成功之后到断开的所有逻辑。
既然kestrel基于连接处理中间件,上层的asp.netcore也是基于请求处理中间件,我们完全也可以也依葫芦画瓢,造一个Redis命令中间件Builder,最后将所有Redis中间件串起来,Buid得一个Redis处理委托。
varbuilder = newPipelineBuilder(appServices, context=>
{
// 没有handler来处理
returncontext.Client.ResponseAsync(RedisResponse .Error( "unsupported cmd"));
})
.Use(( context, next) =>
{
this.logger.LogDebug( context.ToString);
// 验证客户端是否已授权
returncontext.Cmd.Name!= RedisCmdName .Auth&& context.Client.IsAuthed== false
? context.Client.ResponseAsync(RedisResponse .Error( "need auth password"))
: next;
});
// 添加各个cmd对应的handler条件分支
appServices
.GetServices
.ForEach(item => builder .When(item .CanHandle, item .HandleAsync));
this.handler= builder .Build;
在RedisConnectionHandler,每收一个Redis命令,将命令包装为RedisContext,然后使用build出来的handler对象来处理这个RedisContext就行。剩下的工作,就是我们一个命令实现一个IRedisCmdHanler对象就行,逻辑完全分开。
IRedisCmdHanler接口:
///
///定义redis命令处理者
///
interfaceIRedisCmdHanler
{
///
///返回是否可以处理
///
///
///
boolCanHandle(RedisContext context);
///
///处理
///
///
///
Task HandleAsync(RedisContext context);
}
3.4 统一Redis和Signal客户端操作接口 ///
///定义客户端的接口
///
publicinterfaceIClient
{
///
///获取唯一标识
///
stringId { get; }
///
///获取连接时间
///
DateTime ConnectedTime { get; }
///
///获取客户端类型
///
[ JsonConverter(typeof(JsonStringEnumConverter))]
ClientType ClientType { get; }
///
///发送消息
///
///
///
Task< bool> SendMessageAsync(Message message);
}
3.5 IClient管理器 3.6 SignalR部分
由于SignalR的内容非常简单,官方文档细节齐全,这里将不作任何讲解了。
4 总结
由于要讲解的内部比较多,篇幅和时间都有限,本文就只从思路上大概讲解Kestrel在多协议连接的场景的使用方式。一句话,中间件的使用,使得这些场景变得简单,那问题来了,什么是中间件,你理解了吗?返回搜狐,查看更多
责任编辑: