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

结构定义

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

Request

  1. class HttpRequest
  2. {
  3.  
  4. public string HttpVersion { get; set; }
  5.  
  6. public string Method { get; set; }
  7.  
  8. public string BaseUrl { get; set; }
  9.  
  10. public string ClientIP { get; set; }
  11.  
  12. public string Path { get; set; }
  13.  
  14. public string QueryString { get; set; }
  15.  
  16. public string Url { get; set; }
  17.  
  18. public Dictionary<string, string> Headers { get; private set; } = new Dictionary<string, string>();
  19.  
  20. public byte[] Body { get; set; }
  21.  
  22. public int ContentLength { get; set; }
  23.  
  24. public RequestStatus Status { get; set; } = RequestStatus.None;
  25.  
  26. }

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

Response

  1. class HttpResponse : IWriteHandler
  2. {
  3.  
  4. public HttpResponse()
  5. {
  6. Headers["Content-Type"] = "text/html";
  7. }
  8.  
  9. public string HttpVersion { get; set; } = "HTTP/1.1";
  10.  
  11. public int Status { get; set; }
  12.  
  13. public string StatusMessage { get; set; } = "OK";
  14.  
  15. public string Body { get; set; }
  16.  
  17. public Dictionary<string, string> Headers = new Dictionary<string, string>();
  18.  
  19. public void Write(Stream stream)
  20. {
  21. var pipeStream = stream.ToPipeStream();
  22. pipeStream.WriteLine($"{HttpVersion} {Status} {StatusMessage}");
  23. foreach (var item in Headers)
  24. pipeStream.WriteLine($"{item.Key}: {item.Value}");
  25. byte[] bodyData = null;
  26. if (!string.IsNullOrEmpty(Body))
  27. {
  28. bodyData = Encoding.UTF8.GetBytes(Body);
  29. }
  30. if (bodyData != null)
  31. {
  32. pipeStream.WriteLine($"Content-Length: {bodyData.Length}");
  33. }
  34.  
  35. pipeStream.WriteLine("");
  36. if (bodyData != null)
  37. {
  38. pipeStream.Write(bodyData, , bodyData.Length);
  39. }
  40. Completed?.Invoke(this);
  41. }
  42.  
  43. public Action<IWriteHandler> Completed { get; set; }
  44. }

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

协议分析

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

请求基础信息

  1. private void LoadRequestLine(HttpRequest request, PipeStream stream)
  2. {
  3. if (request.Status == RequestStatus.None)
  4. {
  5. if (stream.TryReadLine(out string line))
  6. {
  7. var subItem = line.SubLeftWith(' ', out string value);
  8. request.Method = value;
  9. subItem = subItem.SubLeftWith(' ', out value);
  10. request.Url = value;
  11. request.HttpVersion = subItem;
  12.  
  13. subItem = request.Url.SubRightWith('?', out value);
  14. request.QueryString = value;
  15. request.BaseUrl = subItem;
  16. request.Path = subItem.SubRightWith('/', out value);
  17. if (request.Path != "/")
  18. request.Path += "/";
  19. request.Status = RequestStatus.LoadingHeader;
  20. }
  21. }
  22. }

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

头部信息获取

  1. private void LoadRequestHeader(HttpRequest request, PipeStream stream)
  2. {
  3. if (request.Status == RequestStatus.LoadingHeader)
  4. {
  5. while (stream.TryReadLine(out string line))
  6. {
  7. if (string.IsNullOrEmpty(line))
  8. {
  9. if (request.ContentLength == )
  10. {
  11. request.Status = RequestStatus.Completed;
  12. }
  13. else
  14. {
  15. request.Status = RequestStatus.LoadingBody;
  16. }
  17. return;
  18. }
  19. var name = line.SubRightWith(':', out string value);
  20. if (String.Compare(name, "Content-Length", true) == )
  21. {
  22. request.ContentLength = int.Parse(value);
  23. }
  24. request.Headers[name] = value.Trim();
  25. }
  26. }
  27. }

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

读取消息体

  1. private void LoadRequestBody(HttpRequest request, PipeStream stream)
  2. {
  3. if (request.Status == RequestStatus.LoadingBody)
  4. {
  5. if (stream.Length >= request.ContentLength)
  6. {
  7. var data = new byte[request.ContentLength]; ;
  8. stream.Read(data, , data.Length);
  9. request.Body = data;
  10. request.Status = RequestStatus.Completed;
  11. }
  12. }
  13. }

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

整合到服务

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

  1. public override void SessionReceive(IServer server, SessionReceiveEventArgs e)
  2. {
  3. var request = GetRequest(e.Session);
  4. var pipeStream = e.Stream.ToPipeStream();
  5. if (LoadRequest(request, pipeStream) == RequestStatus.Completed)
  6. {
  7. OnCompleted(request, e.Session);
  8. }
  9. }
  10.  
  11. private RequestStatus LoadRequest(HttpRequest request, PipeStream stream)
  12. {
  13. LoadRequestLine(request, stream);
  14. LoadRequestHeader(request, stream);
  15. LoadRequestBody(request, stream);
  16. return request.Status;
  17. }
  18. private void OnCompleted(HttpRequest request, ISession session)
  19. {
  20. HttpResponse response = new HttpResponse();
  21. StringBuilder sb = new StringBuilder();
  22. sb.AppendLine("<html>");
  23. sb.AppendLine("<body>");
  24. sb.AppendLine($"<p>Method:{request.Method}</p>");
  25. sb.AppendLine($"<p>Url:{request.Url}</p>");
  26. sb.AppendLine($"<p>Path:{request.Path}</p>");
  27. sb.AppendLine($"<p>QueryString:{request.QueryString}</p>");
  28. sb.AppendLine($"<p>ClientIP:{request.ClientIP}</p>");
  29. sb.AppendLine($"<p>Content-Length:{request.ContentLength}</p>");
  30. foreach (var item in request.Headers)
  31. {
  32. sb.AppendLine($"<p>{item.Key}:{item.Value}</p>");
  33. }
  34. sb.AppendLine("</body>");
  35. sb.AppendLine("</html>");
  36.  
  37. response.Body = sb.ToString();
  38. ClearRequest(session);
  39. session.Send(response);
  40. }

只需要在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. Python--day43--补充之主键和外键

    主键只有一个,但是可以用两列不为空的值组成:

  2. UVa 12325 - Zombie's Treasure Chest-[分类枚举]

    12325 Zombie’s Treasure Chest Some brave warriors come to a lost village. They are very lucky and fi ...

  3. H3C配置Trunk端口

  4. H3C查看保存的配置文件

  5. 初识Maven POM

    POM Project Object Model项目对象模型定义了项目的基本信息,用于描述项目如何构建,申明项目依赖,等等. pom元素: <modelVersion>4.0.0</ ...

  6. 慕课网electron写音乐播放器教程,代码跟随教程变动(十)

    添加播放状态,首先是歌曲名称和时间 在index.html中添加 <div class="container fixed-bottom bg-white pb-4"> ...

  7. (摘录)ISO C++ Lambda表达式

    ISO C++ 11 标准的一大亮点是引入Lambda表达式.基本语法如下: [捕获列表](形参列表) mutable ->返回值类型 复合语句 其中除了"[]"(其中捕获列 ...

  8. GapMinder气泡图:在线互动图表数据平台

    GapMinder:在线互动图表数据平台是一个将国际统计数据转换成活动的.交互的和有趣的图表,以在线统计数据为基础的互动图表集的完美世界.目的是通过增进对可以自由访问的公共统计数据的使用和理解,以促进 ...

  9. sql临时表与变量表

    1)临时表存储在 tempdb 中,当不再使用时会自动删除 一般使用如下: --创建临时表 select * into #temp from TABLE --使用临时表 select * from # ...

  10. Android APP前后台状态切换

    getActivity().getApplication().registerActivityLifecycleCallbacks(new Application.ActivityLifecycleC ...