1 文章目的
本文讲解基于kestrel开发实现了部分redis命令的redis伪服务器的过程,让读者了解kestrel网络编程的完整步骤,其中redis通讯协议需要读者自行查阅,文章里不做具体解析。
2 开发顺序
- 创建Kestrel的Redis协议处理者
- 配置监听的EndPoint并使用Redis处理者
- 设计交互上下文RedisContext
- 设计Redis命令处理者
- 设计Redis中间件
- 编排Redis中间件构建应用
3. 创建Redis协议处理者
在Kestrel中,末级的中间件是一个没有next的特殊中间件,基表现出来就是一个ConnectionHandler的行为。我们开发redis应用只需要继承ConnectionHandler这个抽象类来,当kestrel接收到新的连接时将连接交给我们来处理,我们处理完成之后,不再有下一个处理者来处理这个连接了。
Copy/// <summary> /// 表示Redis连接处理者 /// </summary> sealed class RedisConnectionHandler : ConnectionHandler { /// <summary> /// 处理Redis连接 /// </summary> /// <param name="context">redis连接上下文</param> /// <returns></returns> public async override Task OnConnectedAsync(ConnectionContext context) { // 开始处理这个redis连接 ... // 直到redis连接断开后结束 } }
4. 配置监听的EndPoint
4.1 json配置文件
我们在配置文件里指定监听本机的5007端口来做服务器,当然你可以指定本机具体的某个IP或任意IP。
Copy{ "Kestrel": { "Endpoints": { "Redis": { // redis协议服务器,只监听loopback的IP "Url": "http://localhost:5007" } } } }
Copy{ "Kestrel": { "Endpoints": { "Redis": { // redis协议服务器,监听所有IP "Url": "http://*:5007" } } } }
4.2 在代码中配置Redis处理者
为Redis这个节点关联上RedisConnectionHandler
,当redis客户端连接到5007这个端口之后,OnConnectedAsync()
方法就得到触发且收到连接上下文对象。
Copybuilder.WebHost.ConfigureKestrel((context, kestrel) => { var section = context.Configuration.GetSection("Kestrel"); kestrel.Configure(section).Endpoint("Redis", endpoint => { endpoint.ListenOptions.UseConnectionHandler<RedisConnectionHandler>(); }); });
5 设计RedisContext
在asp.netcore里,我们知道应用层每次http请求都创建一个HttpContext对象,里面就塞着各种与本次请求有关的对象。对于Redis的请求,我们也可以这么抄袭asp.netcore来设计Redis。
5.1 RedisContext
Redis请求上下文,包含Client、Request、Response和Features对象,我们要知道是收到了哪个Redis客户端的什么请求,从而请求命令处理者可以向它响应对应的内容。
Copy/// <summary> /// 表示redis上下文 /// </summary> sealed class RedisContext : ApplicationContext { /// <summary> /// 获取redis客户端 /// </summary> public RedisClient Client { get; } /// <summary> /// 获取redis请求 /// </summary> public RedisRequest Reqeust { get; } /// <summary> /// 获取redis响应 /// </summary> public RedisResponse Response { get; } /// <summary> /// redis上下文 /// </summary> /// <param name="client"></param> /// <param name="request"></param> /// <param name="response"></param> /// <param name="features"></param> public RedisContext(RedisClient client, RedisRequest request, RedisResponse response, IFeatureCollection features) : base(features) { this.Client = client; this.Reqeust = request; this.Response = response; } public override string ToString() { return $"{this.Client} {this.Reqeust}"; } }
5.2 ApplicationContext
这是抽象的应用层上下文,它强调Features,做为多个中间件之间的沟通渠道。
Copy/// <summary> /// 表示应用程序请求上下文 /// </summary> public abstract class ApplicationContext { /// <summary> /// 获取特征集合 /// </summary> public IFeatureCollection Features { get; } /// <summary> /// 应用程序请求上下文 /// </summary> /// <param name="features"></param> public ApplicationContext(IFeatureCollection features) { this.Features = new FeatureCollection(features); } }
5.3 RedisRequest
一个redis请求包含请求的命令和0到多个参数值。
Copy/// <summary> /// 表示Redis请求 /// </summary> sealed class RedisRequest { private readonly List<RedisValue> values = new(); /// <summary> /// 获取命令名称 /// </summary> public RedisCmd Cmd { get; private set; } /// <summary> /// 获取参数数量 /// </summary> public int ArgumentCount => this.values.Count - 1; /// <summary> /// 获取参数 /// </summary> /// <param name="index"></param> /// <returns></returns> public RedisValue Argument(int index) { return this.values[index + 1]; } }
RedisRequest的解析:
Copy/// <summary> /// 从内存中解析 /// </summary> /// <param name="memory"></param> /// <param name="request"></param> /// <exception cref="RedisProtocolException"></exception> /// <returns></returns> private static bool TryParse(ReadOnlyMemory<byte> memory, [MaybeNullWhen(false)] out RedisRequest request) { request = default; if (memory.IsEmpty == true) { return false; } var span = memory.Span; if (span[0] != '*') { throw new RedisProtocolException(); } if (span.Length < 4) { return false; } var lineLength = span.IndexOf((byte)'/n') + 1; if (lineLength < 4) { throw new RedisProtocolException(); } var lineCountSpan = span.Slice(1, lineLength - 3); var lineCountString = Encoding.ASCII.GetString(lineCountSpan); if (int.TryParse(lineCountString, out var lineCount) == false || lineCount < 0) { throw new RedisProtocolException(); } request = new RedisRequest(); span = span.Slice(lineLength); for (var i = 0; i < lineCount; i++) { if (span[0] != '$') { throw new RedisProtocolException(); } lineLength = span.IndexOf((byte)'/n') + 1; if (lineLength < 4) { throw new RedisProtocolException(); } var lineContentLengthSpan = span.Slice(1, lineLength - 3); var lineContentLengthString = Encoding.ASCII.GetString(lineContentLengthSpan); if (int.TryParse(lineContentLengthString, out var lineContentLength) == false) { throw new RedisProtocolException(); } span = span.Slice(lineLength); if (span.Length < lineContentLength + 2) { return false; } var lineContentBytes = span.Slice(0, lineContentLength).ToArray(); var value = new RedisValue(lineContentBytes); request.values.Add(value); span = span.Slice(lineContentLength + 2); } request.Size = memory.Span.Length - span.Length; Enum.TryParse<RedisCmd>(request.values[0].ToString(), ignoreCase: true, out var name); request.Cmd = name; return true; }
本站声明:
1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/295013.html