之前的章節裡已經講述了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結果