使用beetle简单地实现高效的http基础服务

之前的章节里已经讲述了Beetle对不同应用协议的扩展和处理,在这章会讲解Beetle实现一个比较通用的应用协议HTTP扩展.组件对于HTTP协 议的扩展也是一件非常简单的事情,同时也能得到组件出色的性能和稳定性所支持.不过在实现之前必须对HTTP协议组成部分有个大概的了解.HTTP协议主 要由两大部分组件分别是消息头和消息体,消息头是必须的有于描述获取相关内容和附带的一些属性如:GET /images/logo.gif HTTP/1.1,通过回车换行符来标记消息头结束.对于消息休则是可选的如果存在消息体必须在消息头里标识Content-Length.对于HTTP 更详细的内容可以查看http://zh.wikipedia.org/zh/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE

接下就详细讲述Beetle实现HTTP服务的过程,具体内容如下:

  • 制订对应的HTTP对象消息结构
  • 制订HTTP协议分析器
  • 实现一个HTTP服务并在浏览中访问
  • 性能测试
  • 总结

制订HTTP对象消息

既然HTTP协议由两大部分组成,那就可以根据这制订相应的协议对象

  • 消息头
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//http头描述
public  class  HttpHeader : HttpMessage
{
     public  HttpHeader()
     {
         Headers = new  Hashtable(8);
     }
     //消息头常量定义,详细参考http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
     #region headers
     public  const  string  HTTP_ContentLength = "Content-Length" ;
     public  const  string  HTTP_Request_Accept = "Accept" ;
     public  const  string  HTTP_Request_AcceptCharset = "Accept-Charset" ;
     public  const  string  HTTP_Requst_AcceptEncoding = "Accept-Encoding" ;
     #endregion
     //获取http头键值表
     public  Hashtable Headers
     {
         get ;
         set ;
     }
     //获取设置方法信息如GET /images/logo.gif HTTP/1.1或返回信息
     public  string  MethodInfo { get ; set ; }
     //获取或设置消息休长度
     public  int  ContentLength
     {
         get
         {
             object  value = Headers[HTTP_ContentLength];
             if  (value != null )
                 return  int .Parse(value.ToString().Trim());
             return  0;
         }
         set
         {
             Headers[HTTP_ContentLength] = value.ToString();
         }
     }
     public  string  this [ string  key]
     {
         get
         {
             return  ( string )Headers[key];
         }
         set
         {
             Headers[key] = value;
         }
     }
}
  • 消息体
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//消息体数据块
public  class  HttpDataSegment : HttpMessage
{
     public  HttpDataSegment()
     {
         Data = HttpPackage.BufferPools.Pop();
         Data.SetInfo(0, 0);
     }
     //当前块是否尾部
     public  bool  Eof
     {
         get ;
         set ;
     }
     //获取数据块内容
     public  ByteArraySegment Data
     {
         get ;
         set ;
     }
     //释放数据块对应的buffer
     protected  override  void  OnDispose()
     {
         base .OnDispose();
         if  (Data != null )
         {
             HttpPackage.BufferPools.Push(Data);
             Data = null ;
         }
     }
}

由于消息体有可能比较大,如果是几百MB的情况也不太可能用一个Buffer来处理,所以消息设计由多个数据块组件.

  • 消息适器
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//消息适配器用于对象写入流转换
public  class  HttpAdater : IMessage
{
     public  HttpMessage Message
     {
         get ;
         set ;
     }
     //从流加载数据,由于直接有协议分析器来处理了所以不需要实现相关方法
     public  void  Load(BufferReader reader)
     {
         throw  new  NotImplementedException();
     }
     //把http协议对象写入网络流
     public  void  Save(BufferWriter writer)
     {
         if  (Message is  HttpHeader)
         {
             OnSaveHeader(writer, Message as  HttpHeader);
         }
         else  if  (Message is  HttpDataSegment)
         {
             OnSaveDataSegment(writer, Message as  HttpDataSegment);
         }
         else
         {
         }
         Message.Dispose();
     }
     //写入消息头信息
     private  void  OnSaveHeader(BufferWriter writer, HttpHeader header)
     {
         writer.WriteString(header.MethodInfo + "\r\n" );
         foreach  ( object  key in  header.Headers.Keys)
         {
             writer.WriteString( string .Format( "{0}: {1}\r\n" , key, header.Headers[key]));
         }
         writer.WriteString( "\r\n" );
     }
     //写入消息体信息
     private  void  OnSaveDataSegment(BufferWriter writer, HttpDataSegment segment)
     {
         writer.Write(segment.Data.Array, segment.Data.Offset, segment.Data.Count);
     }
}

制订HTTP协议分析器

组件对协议的支持并不需要修改组件核心代码,都是通过扩展的方式实现.只需要继承Package实现相关方法即可.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/// <summary>
/// 网络数据导入方法
/// </summary>
/// <param name="data">接收数据流</param>
/// <param name="start">开始索引</param>
/// <param name="count">总长度</param>
public  override  void  Import( byte [] data, int  start, int  count)
{
     int  index = 0;
     if  (mHeader == null )
     {
         //从池中获取头加载内存块
         if  (mHeaderData == null )
         {
             mHeaderData = BufferPools.Pop();
             //初始化块内容
             mHeaderData.SetInfo(0, 0);
         }
         //查询http头结束标记
         index = ByteIndexOf(data, EofData, start, count);
         if  (index >= 0)
         {
             //把http头数据流复制到当前分析缓冲区中
             Buffer.BlockCopy(data, start, mHeaderData.Array, mHeaderData.Offset, index + 1);
             mHeaderData.Count += index + 1;
             start = index + 1;
             count = count - index + 1;
             //分析头信息
             OnCreateHeader();
             MessageArgs.Message = mHeader;
             //触发消息接收事件
             OnReceiveMessage(MessageArgs);
         }
     }
     //如果存在接收内容
     if  (ContentLength > 0)
     {
         //新建一个数据块消息
         HttpDataSegment hds = new  HttpDataSegment();
         //把数据流复制到相应的数据块中
         Buffer.BlockCopy(data, start, hds.Data.Array, 0, count);
         hds.Data.SetInfo(0, count);
         ContentLength -= count;
         //如果获取数据流完整后设置为结束块
         if  (ContentLength == 0)
             hds.Eof = true ;
         MessageArgs.Message = hds;
         //触发消息接收事件
         OnReceiveMessage(MessageArgs);
     }
     //清除当前接收请求内容
     if  (mHeader != null  && ContentLength == 0)
     {
         DisposeHeaderData();
         mHeader = null ;
     }
}

通过实现Import方法来处理协议数据分析,对于http头的拆分可以通过下载完整代码查看.

实现一个HTTP服务并在浏览中访问

组件提供基础的服务对象,只需要在继承指写对应的协议分析器即可,用起来也非常简单方便.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class  Program:ServerBase<Beetle.Packages.HttpPackage>
{
     static  void  Main( string [] args)
     {
         //初如化组件
         TcpUtils.Setup( "beetle" );
         Program server = new  Program();
         server.Listens = 1000;
         //在所有IP的8088端口上监听服务
         server.Open(8088);
         Console.WriteLine( "Beetle Http Server start@8088" );
         System.Threading.Thread.Sleep(-1);
 
     }
     protected  override  void  OnConnected( object  sender, ChannelEventArgs e)
     {
         base .OnConnected(sender, e);
         e.Channel.EnabledSendCompeletedEvent = true ;
         e.Channel.SendMessageCompleted = (o, i) => {
             HttpPackage.HttpAdater adapter = (HttpPackage.HttpAdater)i.Messages[i.Messages.Count - 1];
             //消息发送完成后判断是否关闭对应的连接
             if  (adapter.Message is  HttpPackage.HttpHeader)
             {
                 if  (((HttpPackage.HttpHeader)adapter.Message).ContentLength == 0 && e.Channel != null )
                     e.Channel.Dispose();
 
             }
             else  if  (adapter.Message is  HttpPackage.HttpDataSegment)
             {
                 if  (((HttpPackage.HttpDataSegment)adapter.Message).Eof && e.Channel != null )
                     e.Channel.Dispose();
             }
 
         };
        
     }
     //错误处理事件,可以获取协议分析和逻辑处理环节中出现的异常
     protected  override  void  OnError( object  sender, ChannelErrorEventArgs e)
     {
         base .OnError(sender, e);
        Console.WriteLine( "{0} error:{1}" , e.Channel.EndPoint, e.Exception.Message);
         Console.WriteLine(e.Exception.StackTrace);
     }
     //连接释放过程
     protected  override  void  OnDisposed( object  sender, ChannelDisposedEventArgs e)
     {
         base .OnDisposed(sender, e);
     }
     //消息接收处理事件
     protected  override  void  OnMessageReceive(PacketRecieveMessagerArgs e)
     {
         base .OnMessageReceive(e);
         if  (e.Message is  HttpPackage.HttpHeader)
         {
             OnRequestHeader(e.Channel, (HttpPackage.HttpHeader)e.Message);
         }
         else  if  (e.Message is  HttpPackage.HttpDataSegment)
         {
             OnRequestSegment(e.Channel, (HttpPackage.HttpDataSegment)e.Message);
         }
     }
     //得到请求头信息处理过程
     private  void  OnRequestHeader(TcpChannel channel, Beetle.Packages.HttpPackage.HttpHeader header)
     {
         //Console.WriteLine(header.MethodInfo);
         //foreach (object key in header.Headers.Keys)
         //{
         //    Console.WriteLine("{0}:\t{1}", key, header[(string)key]);
         //}
         HttpPackage.HttpHeader response = new  HttpPackage.HttpHeader();
         HttpPackage.HttpDataSegment responsedata = new  HttpPackage.HttpDataSegment();
         responsedata.Data.Encoding(Resource1.BEETLE_HTTP_TEST, channel.Coding);
         responsedata.Eof = true ;
         response.ContentLength = responsedata.Data.Count;
         response.MethodInfo = "HTTP/1.1 200 OK" ;
         response[ "Cache-Control" ] = "private" ;
         response[ "Connection" ] = "Close" ;
         response[ "Content-Type" ] = "text/html; charset=utf-8" ;
         response[ "Server" ] = "Beetle Http Server" ;
         //发送应答头
         channel.Send(response);
         //发送应答数据
         channel.Send(responsedata);
 
     }
     private  void  OnRequestSegment(TcpChannel channel, Beetle.Packages.HttpPackage.HttpDataSegment segment)
     {
     }
}

以上一个HTTP服务已经实现了,由于测试用所以当接收请求后并没有分析对应的请求信息,直接测试内容.通过浏览器查询如下:

使用beetle简单地实现高效的http基础服务_第1张图片

性能测试

为作一个服务型的应用需要关注的是其性能和稳定性,下面通过AB工具对这个服务进行压力测试,并和IIS7获取一个静态页进行对比,测试内容是分别请求100W次

Beetle结果

使用beetle简单地实现高效的http基础服务_第2张图片

IIS结果

使用beetle简单地实现高效的http基础服务_第3张图片

总结

Beetle 可以很灵活地实现不同的应用协议支持,而你在扩展的过程只需要关心协议和逻辑上的工作,对于性能和稳定性Beetle可以给你做保障.由于Beetle是 纯c#实现,所以也可以说明.net的socket 处理能力还是很不错的,由于Beetle并不是针对http这种短连接应用设计,所以在这应用上并没有真正发挥出.net socket在这方面的能力.总的来说应该还有10%-20%左右的优化空间.

下载代码

可靠、高性能的Socket TCP通讯组件
开源数据库访问组件
c#组件设计交流群:47164588 
c# socket :285021077

你可能感兴趣的:(http)