亚洲乱色熟女一区二区三区丝袜,天堂√中文最新版在线,亚洲精品乱码久久久久久蜜桃图片,香蕉久久久久久av成人,欧美丰满熟妇bbb久久久

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網站管理員

C#開發(fā)輕量級 高性能HTTP服務器

admin
2025年7月2日 15:37 本文熱度 174

前言 


http協議是互聯網上使用最廣泛的通訊協議了。Web通訊也是基于http協議;對應c#開發(fā)者來說ASP.NET Core是最新的開發(fā)Web應用平臺。


由于最近要開發(fā)一套人臉識別系統(tǒng),對通訊效率的要求很高。雖然.NET Core對http處理很優(yōu)化了,但是我決定開發(fā)一個輕量級http服務器;不求功能多強大,只求能滿足需求,性能優(yōu)越。本文以c#開發(fā)windows下http服務器為例。


經過多年的完善、優(yōu)化,我積累了一個非常高效的網絡庫《.NET中高性能、高可用性Socket通訊庫》以此庫為基礎,開發(fā)一套輕量級的http服務器難度并不大。花了兩天的時間完成http服務器開發(fā),并做了測試。


同時與ASP.NET Core處理效率做了對比,結果出乎意料。我的服務器性能是ASP.NET Core的10倍。對于此結果一開始我也是不相信,經過多次反復測試,事實卻是如此。此結果并不能說明我寫的服務器優(yōu)于ASP.NET Core,只是說明一個道理:合適的就是最好,高大上的東西并不是最好的。


1、HTTP協議特點




HTTP協議是基于TCP/IP之上的文本交換協議。對于開發(fā)者而言,也屬于socket通訊處理范疇。只是http協議是請求應答模式,一次請求處理完成,則立即斷開。http這種特點對sokcet通訊提出幾個要求:


a)、能迅速接受TCP連接請求。TCP是面向連接的,在建立連接時,需要三次握手。這就要求socket處理accept事件要迅速,要能短時間處理大量連接請求。


b)、服務端必須采用異步通訊模式。對windows而言,底層通訊就要采取IOCP,這樣才能應付成千上萬的socket請求。


c)、快速的處理讀取數據。tcp是流傳輸協議,而http傳輸的是文本協議;客戶端向服務端發(fā)送的數據,服務端可能需要讀取多次,服務端需要快速判斷數據是否讀取完畢。


以上幾點只是處理http必須要考慮的問題,如果需要進一步優(yōu)化,必須根據自身的業(yè)務特點來處理。


2、快速接受客戶端的連接請求


采用異步Accept接受客戶端請求。這樣的好處是:可以同時投遞多個連接請求。當有大量客戶端請求時,能快速建立連接。


異步連接請求代碼如下:


public bool StartAccept()
{
   SocketAsyncEventArgs acceptEventArgs = new SocketAsyncEventArgs();
   acceptEventArgs.Completed += AcceptEventArg_Completed;
   bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArgs);
   Interlocked.Increment(ref _acceptAsyncCount);
   if (!willRaiseEvent)
   {
       Interlocked.Decrement(ref _acceptAsyncCount);
       _acceptEvent.Set();
       acceptEventArgs.Completed -= AcceptEventArg_Completed;
       ProcessAccept(acceptEventArgs);
   }
   return true;
}


可以設置同時投遞的個數,比如此值為10。當異步連接投遞個數小于10時,立馬再次增加投遞。有一個線程專門負責投遞。


_acceptAsyncCount記錄當前正在投遞的個數,MaxAcceptInPool表示同時投遞的個數;一旦_acceptAsyncCount小于MaxAcceptInPool,立即增加一次投遞。

private void DealNewAccept()
{
   try
   {
       if (_acceptAsyncCount <= MaxAcceptInPool)
       {
           StartAccept();
       }          
   }
   catch (Exception ex)
   {
       _log.LogException(0, "DealNewAccept 異常", ex);
   }
}


3、快速分析從客戶端收到的數據


比如客戶端發(fā)送1M數據到服務端,服務端收到1M數據,需要讀取的次數是不確定的。怎么樣才能知道數據是否讀取完?


這個細節(jié)處理不好,會嚴重影響服務器的性能。畢竟服務器要對大量這樣的數據進行分析。


http包頭舉例


POST / HTTP/1.1
Accept: */*
Content-Type: application/x-www-from-urlencoded
Host: www.163.com
Content-Length: 7
Connection: Keep-Alive
body


分析讀取數據,常規(guī)、直觀的處理方式如下:


1) 、將收到的多個buffer合并成一個buffer。如果讀取10次才完成,則需要合并9次。


2) 、將buffer數據轉成文本。


3) 、找到文本中的http包頭結束標識("\r\n\r\n") 。


4) 、找到Content-Length,根據此值判斷是否接收完成。


采用上述處理方法,將嚴重影響處理性能。必須另辟蹊徑,采用更優(yōu)化的處理方法。


優(yōu)化后的處理思路


1、多緩沖處理


基本思路是:收到所有的buffer之前,不進行buffer合并。將緩沖存放在List<byte[]> listBuffer中。通過遍歷listBuffer來查找http包頭結束標識,來判斷是否接收完成。


類BufferManage負責管理buffer。


public class BufferManage
{
   List<byte[]> _listBuffer = new List<byte[]>();
   public void AddBuffer(byte[] buffer)
   
{
       _listBuffer.Add(buffer);
   }
   public bool FindBuffer(byte[] destBuffer, out int index)
   
{
       index = -1;
       int flagIndex = 0;
       int count = 0;
       foreach (byte[] buffer in _listBuffer)
       {
           foreach (byte ch in buffer)
           {
               count++;
               if (ch == destBuffer[flagIndex])
               {
                   flagIndex++;
               }
               else
               {
                   flagIndex = 0;
               }

               if (flagIndex >= destBuffer.Length)
               {
                   index = count;
                   return true;
               }
           }
       }
       return false;
   }
   public int TotalByteLength
   {
       get
       {
           int count = 0;
           foreach (byte[] item in _listBuffer)
           {
               count += item.Length;
           }
           return count;
       }
   }

   public byte[] GetAllByte()
   
{
       if (_listBuffer.Count == 0)
           return new byte[0];
       if (_listBuffer.Count == 1)
           return _listBuffer[0];

       int byteLen = 0;
       _listBuffer.ForEach(o => byteLen += o.Length);
       byte[] result = new byte[byteLen];

       int index = 0;
       foreach (byte[] item in _listBuffer)
       {
           Buffer.BlockCopy(item, 0, result, index, item.Length);
           index += item.Length;
       }
       return result;
   }
   public byte[] GetSubBuffer(int start, int countTotal)
   
{
       if (countTotal == 0)
           return new byte[0];
       byte[] result = new byte[countTotal];
       int countCopyed = 0;
       int indexOfBufferPool = 0;
       foreach (byte[] buffer in _listBuffer)
       {
           //找到起始復制點
           int indexOfItem = 0;
           if (indexOfBufferPool < start)
           {
               int left = start - indexOfBufferPool;
               if (buffer.Length <= left)
               {
                   indexOfBufferPool += buffer.Length;
                   continue;
               }
               else
               {
                   indexOfItem = left;
                   indexOfBufferPool = start;
               }
           }

           //復制數據
           int dataLeft = buffer.Length - indexOfItem;
           int dataNeed = countTotal - countCopyed;
           if (dataNeed >= dataLeft)
           {
               Buffer.BlockCopy(buffer, indexOfItem, result, countCopyed, dataLeft);
               countCopyed += dataLeft;
           }
           else
           {
               Buffer.BlockCopy(buffer, indexOfItem, result, countCopyed, dataNeed);
               countCopyed += dataNeed;
           }
           if (countCopyed >= countTotal)
           {
               Debug.Assert(countCopyed == countTotal);
               return result;
           }
       }
       throw new Exception("沒有足夠的數據!");
       // return result;
   }
}


類HttpReadParse借助BufferManage類,實現對http文本的解析。


public class HttpReadParse
{
   BufferManage _bufferManage = new BufferManage();
   public void AddBuffer(byte[] buffer)
   
{
       _bufferManage.AddBuffer(buffer);
   }
   public int HeaderByteCount { get; private set; } = -1;
   string _httpHeaderText = string.Empty;
   public string HttpHeaderText
   {
       get
       {
           if (_httpHeaderText != string.Empty)
               return _httpHeaderText;

           if (!IsHttpHeadOver)
               return _httpHeaderText;

           byte[] buffer = _bufferManage.GetSubBuffer(0, HeaderByteCount);
           _httpHeaderText = Encoding.UTF8.GetString(buffer);
           return _httpHeaderText;
       }
   }
   string _httpHeaderFirstLine = string.Empty;
   public string HttpHeaderFirstLine
   {
       get
       {
           if (_httpHeaderFirstLine != string.Empty)
               return _httpHeaderFirstLine;

           if (HttpHeaderText == string.Empty)
               return string.Empty;
           int index = HttpHeaderText.IndexOf(HttpConst.Flag_Return);
           if (index < 0)
               return string.Empty;

           _httpHeaderFirstLine = HttpHeaderText.Substring(0, index);
           return _httpHeaderFirstLine;
       }
   }

   public string HttpRequestUrl
   {
       get
       {
           if (HttpHeaderFirstLine == string.Empty)
               return string.Empty;

           string[] items = HttpHeaderFirstLine.Split(' ');
           if (items.Length < 2)
               return string.Empty;

           return items[1];
       }
   }
   public bool IsHttpHeadOver
   {
       get
       {
           if (HeaderByteCount > 0)
               return true;
           byte[] headOverFlag = HttpConst.Flag_DoubleReturnByte;

           if (_bufferManage.FindBuffer(headOverFlag, out int count))
           {
               HeaderByteCount = count;
               return true;
           }
           return false;
       }
   }
   int _httpContentLen = -1;
   public int HttpContentLen
   {
       get
       {
           if (_httpContentLen >= 0)
               return _httpContentLen;
           if (HttpHeaderText == string.Empty)
               return -1;
           int start = HttpHeaderText.IndexOf(HttpConst.Flag_HttpContentLenth);
           if (start < 0) //http請求沒有包體
               return 0;
           start += HttpConst.Flag_HttpContentLenth.Length;
           int end = HttpHeaderText.IndexOf(HttpConst.Flag_Return, start);
           if (end < 0)
               return -1;
           string intValue = HttpHeaderText.Substring(start, end - start).Trim();
           if (int.TryParse(intValue, out _httpContentLen))
               return _httpContentLen;
           return -1;
       }
   }
   public string HttpAllText
   {
       get
       {
           byte[] textBytes = _bufferManage.GetAllByte();
           string text = Encoding.UTF8.GetString(textBytes);
           return text;
       }
   }
   public int TotalByteLength => _bufferManage.TotalByteLength;
   public bool IsReadEnd
   {
       get
       {
           if (!IsHttpHeadOver)
               return false;
           if (HttpContentLen == -1)
               return false;
           int shouldLenth = HeaderByteCount + HttpContentLen;
           bool result = TotalByteLength >= shouldLenth;
           return result;
       }
   }
   public List<HttpByteValueKey> GetBodyParamBuffer()
   
{
       List<HttpByteValueKey> result = new List<HttpByteValueKey>();
       if (HttpContentLen < 0)
           return result;
       Debug.Assert(IsReadEnd);
       if (HttpContentLen == 0)
           return result;
       byte[] bodyBytes = _bufferManage.GetSubBuffer(HeaderByteCount, HttpContentLen);
       //獲取key value對應的byte
       int start = 0;
       int current = 0;
       HttpByteValueKey item = null;
       foreach (byte b in bodyBytes)
       {
           if (item == null)
               item = new HttpByteValueKey();
           current++;
           if (b == '=')
           {
               byte[] buffer = new byte[current - start - 1];
               Buffer.BlockCopy(bodyBytes, start, buffer, 0, buffer.Length);
               item.Key = buffer;
               start = current;
           }
           else if (b == '&')
           
{
               byte[] buffer = new byte[current - start - 1];
               Buffer.BlockCopy(bodyBytes, start, buffer, 0, buffer.Length);
               item.Value = buffer;
               start = current;
               result.Add(item);
               item = null;
           }
       }
       if (item != null && item.Key != null)
       {
           byte[] buffer = new byte[bodyBytes.Length - start];
           Buffer.BlockCopy(bodyBytes, start, buffer, 0, buffer.Length);
           item.Value = buffer;
           result.Add(item);
       }
       return result;
   }
   public string HttpBodyText
   {
       get
       {
           if (HttpContentLen < 0)
               return string.Empty;
           Debug.Assert(IsReadEnd);
           if (HttpContentLen == 0)
               return string.Empty;
           byte[] bodyBytes = _bufferManage.GetSubBuffer(HeaderByteCount, HttpContentLen);
           string bodyString = Encoding.UTF8.GetString(bodyBytes);
           return bodyString;
       }
   }
}


4、性能測試


采用模擬客戶端持續(xù)發(fā)送http請求測試,每個http請求包含兩個圖片。一次http請求大概發(fā)送70K數據。服務端解析數據后,立即發(fā)送應答。


注:所有測試都在本機,客戶端無法模擬大量http請求,只能做簡單壓力測試。


1)本人所寫的服務器,測試結果如下



每秒可發(fā)送300次請求,每秒發(fā)送數據25M,服務器cpu占有率為4%。


2)ASP.NET Core 服務器性能測試




每秒發(fā)送30次請求,服務器cpu占有率為12%。


測試對比


本人開發(fā)的服務端處理速度為ASP.NET Core的10倍,cpu占用為對方的三分之一。ASP.NET Core處理慢,有可能實現了更多的功能;只是這些隱藏的功能,對我們也沒用。


后記


如果沒有開發(fā)經驗,沒有清晰的處理思路,開發(fā)一個高效的http服務器還有很困難的。


本人也一直以來都是采用ASP.NET Core作為http服務器。因為工作中需要高效的http服務器,就嘗試寫一個。


不可否認,ASP.NET Core各方面肯定優(yōu)化的很好;但是ASP.NET Core 提供的某些功能是多余的。如果化繁為簡,根據業(yè)務特點開發(fā),性能未必不能更優(yōu)。


該文章在 2025/7/2 16:58:15 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業(yè)務管理,結合碼頭的業(yè)務特點,圍繞調度、堆場作業(yè)而開發(fā)的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved