之前的章節里已經講述了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%左右的優化空間.



