在传统网络服务中扩展中需要处理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, , 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 == )
{
request.Status = RequestStatus.Completed;
}
else
{
request.Status = RequestStatus.LoadingBody;
}
return;
}
var name = line.SubRightWith(':', out string value);
if (String.Compare(name, "Content-Length", true) == )
{
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, , 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

Beetlex实现完整的HTTP协议的更多相关文章

  1. 基于Java Netty框架构建高性能的部标808协议的GPS服务器

    使用Java语言开发一个高质量和高性能的jt808 协议的GPS通信服务器,并不是一件简单容易的事情,开发出来一段程序和能够承受数十万台车载接入是两码事,除去开发部标808协议的固有复杂性和几个月长周 ...

  2. 蓝牙接收苹果手机通知 ANCS协议分析

    蓝牙接收苹果手机通知 ANCS协议分析 转载,请注明出处:http://www.cnblogs.com/alexcai/p/4321514.html 综述 现在有许多蓝牙手表.手环都能接收苹果ipho ...

  3. Packetbeat协议扩展开发教程(3)

    原文链接:http://elasticsearch.cn/article/54 书接上回:http://elasticsearch.cn/article/53 前面介绍了Packetbeat的项目结构 ...

  4. 通过Java代码浅谈HTTP协议

    最近刚看了http协议,想写点东西加深一下理解,如果哪儿写错了,请指正. 1 介绍 HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写.它的发展是万维网协会(W ...

  5. SNMP概述–运维必知的协议基础

    一.什么是SNMP?   SNMP是  “Simple Network Management Protocol” 的缩写,中文意思是简单网络管理协议,它是由互联网工作小组在RFC1157中定义的应用层 ...

  6. OAuth 2.0 / RCF6749 协议解读

    OAuth是第三方应用授权的开放标准,目前版本是2.0版,以下将要介绍的内容和概念主要来源于该版本.恐篇幅太长,OAuth 的诞生背景就不在这里赘述了,可参考 RFC 6749 . 四种角色定义: R ...

  7. 协议系列之IP协议

    1.协议 协议(protocol)的定义:为计算机网络中进行数据交换而建立的规则.标准或约定的集合.两个终端相互通信时双方达成的一种约定,规定了一套通信规则,双方通信必须遵守这些规则.这些规则规定了分 ...

  8. [转]XModem协议

    出处:XModem协议 XModem协议介绍:XModem是一种在串口通信中广泛使用的异步文件传输协议,分为XModem和1k-XModem协议两种,前者使用128字节的数据块,后者使用1024字节即 ...

  9. 译《The Part-Time Parliament》——终于读懂了Paxos协议!

    最近的考古发现表明,在Paxos小岛上,尽管兼职议会成员都有逍遥癖,但议会模式仍然起作用.他们依旧保持了一致的会议记录,尽管他们频繁的进出会议室并且他们的信使还很健忘.Paxon议会协议提供了一种新方 ...

随机推荐

  1. HDU 1850 Nim-Sum思想总结、

    算法介绍: Nim游戏是指两个对手在m个堆中轮流随意从某一个堆中拿出n个元素,假定两个对手都是足够聪明,直至最后一次取的人将所有元素取出,此人取得胜利.与之相反的是Misere游戏,相同的游戏规则,但 ...

  2. Native memory allocation (mmap) failed to map xxx bytes for committing reserved memory

    遇到问题 在服务器上运行 nexus 出现Native memory allocation (mmap) failed to map 838860800 bytes for committing re ...

  3. ES6 set和map数据结构对对象数组去重简单实现

    自从有了es6的set数据结构,数组的去重可以简单用一行代码实现,比如下面的方式 let arr = [1, 2, 2, 3, 4] function unique (arr) { return [. ...

  4. H3C 多路径网络中环路产生过程(2)

  5. react-electron 项目打包体积尽可能减小的方法

    当一个react-electron项目做好之后就可以开始进行打包,首先就是打包react,这个自然不用多说,不过事先要记住一点,项目目录主进程文件main.js最好放在根目录,再执行打包命令 npm ...

  6. 企业级Docker私有仓库Harbor

    一.Harbor简介 1.Harbor介绍 Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,通过添加一些企业必需的功能特性,例如安全.标识和管理等,扩展了开源Docke ...

  7. Struts2注解详解(转)

    一,引入支持Struts2支持注解开发jar包: struts2-convention-plugin-2.1.8.1.jar(支持Struts2框架注解开发的jar包) 二,Struts2使用注解开发 ...

  8. linux中的文件类型标记方法

    在ls -l显示的详细信息中有以下信息: -rw-r--r-- drwxr-xr-x 一共10个字符,第一个字符表示文件类型,后面9个字符分成3组表示文件权限.前三个表示属主(拥有者)对文件的权限,中 ...

  9. unity3D 自定义公告牌

    前言 有时候我们希望公告牌跟随镜头旋转永远平行面向屏幕,同时跟随镜头缩放缩放大小不变(镜头远离物体,正常物体视觉效果变小,但公告牌视觉大小比例不变),或者跟随镜头缩放变化,本文记录C#脚本的两种实现方 ...

  10. 页面显示jsp源码问题

    问题错误在于WEB.XML 将/*改为*即可