BeetleX实现HTTP协议详解

在传统网络服务中扩展中需要处理Bytes来进行协议的读写,这种原始的处理方式让工作变得相当繁琐复杂,出错和调试的工作量都非常大;组件为了解决这一问题引用Stream读写方式,这种方式可以极大的简化网络协议读写的工作量,并大大提高协议编写效率。接下来就体验一下组件的PipeStream在处理一个完整的HTTP 1.1协议有多简便。

结构定义

HTTP 1.1协议就不详细介绍了,网上的资源非常丰富;在这里通过对象结束来描述这个协议

Request

    class HttpRequest{public string HttpVersion { get; set; }public string Method { get; set; }public string BaseUrl { get; set; }public string ClientIP { get; set; }public string Path { get; set; }public string QueryString { get; set; }public string Url { get; set; }public Dictionary<string, string> Headers { get; private set; } = new Dictionary<string, string>();public byte[] Body { get; set; }public int ContentLength { get; set; }public RequestStatus Status { get; set; } = RequestStatus.None;}

以上是描述一个HTTP请求提供了一些请求的详细信息和对应的请求内容

Response

      class HttpResponse : IWriteHandler{public HttpResponse(){Headers["Content-Type"] = "text/html";}public string HttpVersion { get; set; } = "HTTP/1.1";public int Status { get; set; }public string StatusMessage { get; set; } = "OK";public string Body { get; set; }public Dictionary<string, string> Headers = new Dictionary<string, string>();public void Write(Stream stream){var pipeStream = stream.ToPipeStream();pipeStream.WriteLine($"{HttpVersion} {Status} {StatusMessage}");foreach (var item in Headers)pipeStream.WriteLine($"{item.Key}: {item.Value}");byte[] bodyData = null;if (!string.IsNullOrEmpty(Body)){bodyData = Encoding.UTF8.GetBytes(Body);}if (bodyData != null){pipeStream.WriteLine($"Content-Length: {bodyData.Length}");}pipeStream.WriteLine("");if (bodyData != null){pipeStream.Write(bodyData, 0, bodyData.Length);}Completed?.Invoke(this);}public Action<IWriteHandler> Completed { get; set; }}

以上是对应请求的响应对象,实现了IWriteHandler接口,这个接口是告诉组件如何输出这个对象。

协议分析

结构对象有了接下来的工作就是把网络流中的HTTP协议数据读取到结构上.

请求基础信息

        private void LoadRequestLine(HttpRequest request, PipeStream stream){if (request.Status == RequestStatus.None){if (stream.TryReadLine(out string line)){var subItem = line.SubLeftWith(' ', out string value);request.Method = value;subItem = subItem.SubLeftWith(' ', out value);request.Url = value;request.HttpVersion = subItem;subItem = request.Url.SubRightWith('?', out value);request.QueryString = value;request.BaseUrl = subItem;request.Path = subItem.SubRightWith('/', out value);if (request.Path != "/")request.Path += "/";request.Status = RequestStatus.LoadingHeader;}}}

以上方法主要是分解出Method,UrlQueryString等信息。由于TCP协议是流,所以在分析包的时候都要考虑当前数据是否满足要求,所以组件提供TryReadLine方法来判断当前内容是否满足一行的需求;还有通过组件提供的SubRightWithSubLeftWith方法可以大简化了字符获取问题。

头部信息获取

        private void LoadRequestHeader(HttpRequest request, PipeStream stream){if (request.Status == RequestStatus.LoadingHeader){while (stream.TryReadLine(out string line)){if (string.IsNullOrEmpty(line)){if (request.ContentLength == 0){request.Status = RequestStatus.Completed;}else{request.Status = RequestStatus.LoadingBody;}return;}var name = line.SubRightWith(':', out string value);if (String.Compare(name, "Content-Length", true) == 0){request.ContentLength = int.Parse(value);}request.Headers[name] = value.Trim();}}}

头信息分析就更加简单,当获取一个空行的时候就说明头信息已经解释完成,接下来的就部分就是Body;由于涉及到Body所以需要判断一个头存不存在Content-Length属性,这个属性用于描述消息体的长度;其实Http还有一种chunked编码,这种编码是分块来处理最终以0\r\n\r\n结尾。这种方式一般是响应用得比较多,对于提交则很少使用这种方式。

读取消息体

        private void LoadRequestBody(HttpRequest request, PipeStream stream){if (request.Status == RequestStatus.LoadingBody){if (stream.Length >= request.ContentLength){var data = new byte[request.ContentLength]; ;stream.Read(data, 0, data.Length);request.Body = data;request.Status = RequestStatus.Completed;}}}

读取消息体就简单了,只需要判断当前的PipeStream是否满足提交的长度,如果可以则直接获取并设置到request.Data属性中。这里只是获了流数据,实际上Http体的编码也有几种情况,在这里不详细介绍。这些实现在FastHttpApi中都有具体的实现代码,详细可以查看 https://github.com/IKende/FastHttpApi/blob/master/src/Data/DataConvertAttribute.cs

整合到服务

以上针对RequestResponse的协议处理已经完成,接下来就集成到组件的TCP服务中

        public override void SessionReceive(IServer server, SessionReceiveEventArgs e){var request = GetRequest(e.Session);var pipeStream = e.Stream.ToPipeStream();if (LoadRequest(request, pipeStream) == RequestStatus.Completed){OnCompleted(request, e.Session);}}private RequestStatus LoadRequest(HttpRequest request, PipeStream stream){LoadRequestLine(request, stream);LoadRequestHeader(request, stream);LoadRequestBody(request, stream);return request.Status;}private void OnCompleted(HttpRequest request, ISession session){HttpResponse response = new HttpResponse();StringBuilder sb = new StringBuilder();sb.AppendLine("<html>");sb.AppendLine("<body>");sb.AppendLine($"<p>Method:{request.Method}</p>");sb.AppendLine($"<p>Url:{request.Url}</p>");sb.AppendLine($"<p>Path:{request.Path}</p>");sb.AppendLine($"<p>QueryString:{request.QueryString}</p>");sb.AppendLine($"<p>ClientIP:{request.ClientIP}</p>");sb.AppendLine($"<p>Content-Length:{request.ContentLength}</p>");foreach (var item in request.Headers){sb.AppendLine($"<p>{item.Key}:{item.Value}</p>");}sb.AppendLine("</body>");sb.AppendLine("</html>");response.Body = sb.ToString();ClearRequest(session);session.Send(response);}

只需要在SessionReceive接收事件中创建相应的Request对象,并把PipeStream传递到相应的解释方法中,然后判断完成情况;当解释完成后就调用OnCompleted输出相应的Response信息,在这里简单地把当前请求信息输出返回.(在这里为什么要清除会话的Request呢,因为Http1.1是长连接会话,必须每个请求都需要创建一个新的Request对象信息)。

这样一个基于BeetleX解释的Http服务就完成了,是不是很简单。接下来简单地测试一下性能,在一台e3-1230v2+10Gb的环境压力测试

测试结果的有15万的RPS,虽然这样一个简单服务但效率并不理想,相对于也是基于组件扩展的FastHttpApi来说还是有些差距,为什么简单的代码还没有复杂的框架来得高效呢,其实原因很简单就是对象复用和string编码缓存没有用上,导致开销过大(这些细节上的性能优化有时间会在后续详解)。

下载完整代码 https://github.com/IKende/BeetleX-Samples/tree/master/TCP.BaseHttp

 使用优惠口令「dotnet123

????到手仅¥89,限前200

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/312259.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

euclidea4攻略_Euclidea几何构建11.4通关攻略

Euclidea几何构建10.2通关攻略Euclidea游戏10.2怎么过&#xff1f;下面小编为大家带来Euclidea几何构建10.2通关攻略&#xff1a;更多攻略不断更新中——Euclidea游戏全关卡通关攻略大全分两次做图第一次&#xff0c;画圆就成了&#xff0c;具体看图应该能懂。第二次&#xff0…

C++set容器-构造和赋值

set基本概念 简介&#xff1a; 所有元素都会自动在插入时自动被排序&#xff0c;set容器也叫集合容器 本质&#xff1a; set/multiset属于关联式容器&#xff0c;底层结构是用二叉树排序 set和multiset区别&#xff1a; 1.set不允许容器中有重复的元素 2.multiset允许容器中有…

(1)解锁MongoDB replica set核心姿势

本文倒腾目前大热的MongoDB Replica Set集群&#xff0c;在倒腾的同时串讲一些 MongoDB特性。 副本集Replica Set是一个术语&#xff0c;定义具有多节点的数据库集群&#xff0c;这些节点具有主从复制(master-slave replication) 且节点之间实现了自动故障转移。 这样的结构通常…

java写dnf外掛_dnf卡盟_Java的泛型详解(一)

Java实现DDD中UnitOfWorkdnf卡盟Java的泛型详解泛型的利益编写的代码可以被差别类型的工具所重用。由于上面的一个优点&#xff0c;泛型也可以削减代码的编写。泛型的使用简朴泛型类public class Pair {private T first;private T second;public Pair() {first null;second n…

C++set容器-大小和交换

set大小和交换 功能描述&#xff1a; 统计set容器大小以及交换set容器 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <set> //set容器 大小和容器void printSet(set<int > &s) {for (set<int …

Asp.Net Core下的开源任务调度平台ScheduleMaster—快速上手

概述ScheduleMaster是一个开源的分布式任务调度系统&#xff0c;它基于Asp.Net Core平台构建&#xff0c;支持跨平台多节点部署运行。它的项目主页在这里&#xff1a;https://github.com/hey-hoho/ScheduleMasterCore关于它的简单介绍可以看这里&#xff1a;https://www.cnblog…

C++set容器-插入和删除

set插入和删除 功能描述&#xff1a; set容器进行插入数据和删除数据 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <set>void printSet(set<int > &s) {for (set<int >::iterator it s.be…

基于Jenkins的持续交付全流程设计与实践

1 从理论开始什么是DevOps?近年来&#xff0c;随着DevOps理念的逐渐深入人心&#xff0c;企业逐渐意识到从看似重复的手工劳动中实现自动化流程处理&#xff0c;对于提高企业劳动生产力已经非常重要&#xff0c;尤其是面向互联网的开发者&#xff0c;往往每次上线时&#xff0…

C++set容器-查找和统计

set查找和统计 功能描述&#xff1a; 对set容器进行查找数据以及统计数据 函数原型&#xff1a; 代码如下: #include <iostream> using namespace std; #include <set>//set查找和统计void test01() {//查找set<int >s1;//插入数据s1.insert(10);s1.inse…

C++set和multiset区别

区别&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <set>//set容器 和multiset容器的区别 void test01() {set<int >s;pair<set<int >::iterator, bool> ret s.insert(10);if (ret.second) {cout &…

.NET CORE(C#) WPF简单菜单MVVM绑定

阅读导航 本文背景 代码实现 本文参考 源码 1. 本文背景 WPF中垂直导航菜单大家应该都常用&#xff0c;本文介绍使用MVVM的方式怎么绑定菜单&#xff0c;真的很简单。 2. 代码实现 使用 .Net Core 3.1 创建名为 “MenuMVVM” 的WPF模板项目&#xff0c;添加两个Nuget库&…

C++pair对组的创建

pair对组创建 功能描述&#xff1a; 成对出现的数据&#xff0c;利用对组可以返回两个数据 两种创建方式&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <cstring>void test01() {//第一种方式pair<string, int>…

避免在 ASP.NET Core 3.0 中为启动类注入服务

本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇。 Part 1 - 将.NET Standard 2.0 类库转换为.NET Core 3.0 类库 Part 2 - IHostingEnvironment VS IHostEnvironent - .NET Core 3.0 中的废弃类型 Part 3 - 避免在 ASP.NET Core 3.0 中为启动类注入服务&#xff08;本篇&…

C++set容器-内置类型指定排序

set容器排序 利用访函数&#xff0c;可以改变排序规则 一、set存放内置数据类型 代码如下&#xff1a; #include <iostream> using namespace std; #include <set>class Mycompare {public:bool operator()(int v1, int v2) {return v1 > v2;} };//set容器排序…

workman php 安装,workerman安装及遇到的问题解决

下面由workerman入门教程栏目给大家介绍workerman安装及遇到的问题解决方法&#xff0c;希望对需要的朋友有所帮助&#xff01;1、workerman安装workerman是php的一个socket框架&#xff0c;简化了socket编程&#xff0c;已经为很多企业所用&#xff0c;今天在centos的ngixphp下…

.NET CORE(C#) WPF亚克力窗体

微信公众号&#xff1a;Dotnet9&#xff0c;网站&#xff1a;Dotnet9&#xff0c;问题或建议&#xff1a;请网站留言&#xff0c; 如果对您有所帮助&#xff1a;欢迎赞赏。.NET CORE(C#) WPF亚克力窗体阅读导航本文背景代码实现本文参考源码1. 本文背景本文介绍使用FluentWPF控…

C++map容器-构造和赋值

map基本概念 map构造和赋值 功能描述&#xff1a; 对map容器进行构造和赋值操作 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; //map容器 构造和赋值 #include <map>void printMap(map<int, int> &m) {fo…

ABP框架v2.0 和 ABP商业版

ABP框架2.0版已经在本周公布.这篇文章解释了为什么我们发布了一个抢先主版本,和2.0版本中的变化.除了v2.0版本,我们很高兴地宣布ABP商业版,这是建立在开源ABP框架的之上的一套专业的模块,工具,主题和服务.ABP框架V2.0为什么2.0,而不是1.2&#xff1f;本来在V1.1.2发布后计划发布…

C++map容器-大小和互换

map大小和互换 功能描述&#xff1a; 统计map容器大小以及交换map容器 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <map>//map容器 大小和交换 void test01() {map<int, int>m;m.insert(pair<in…

分享一些提高逻辑能力的心得

大家好&#xff0c;我是Z哥。最近有个很明显的感受。就是随着年龄的增长&#xff0c;我越来越发现曾经一些我认为理所应当的事情&#xff0c;在现在看来是如此的傻&#xff0c;如此的没有逻辑。有一句话是这么说的。花半秒钟就看透事物本质的人&#xff0c;和花一辈子都看不清事…