使用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%左右的優化空間.

下載代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM