好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

使用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协议由两大部分组成,那就可以根据这制订相应的协议对象

消息头

?

//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;

         }

     }

}

消息体

?

//消息体数据块

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来处理,所以消息设计由多个数据块组件.

消息适器

?

//消息适配器用于对象写入流转换

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实现相关方法即可.

?

/// <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服务并在浏览中访问

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

?

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服务已经实现了,由于测试用所以当接收请求后并没有分析对应的请求信息,直接测试内容.通过浏览器查询如下:

性能测试

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

Beetle结果

IIS结果

总结

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

下载代码

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于使用beetle简单地实现高效的http基础服务的详细内容...

  阅读:47次