首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> 關於ASP.NET

從零打造迷你 HTTP 伺服器(一):協議解析與 HttpStack 設計

     編輯:關於ASP.NET

目標:純 Socket 實作簡易 HTTP 伺服器,深入理解協議細節

不使用 HttpListener 或 ApplicationHost 等高階封裝,直接操作 Socket API,實作能收發 HTTP 封包的網路服務。不求完整符合 RFC 2616,重在學習協議解析、非同步 I/O、執行緒模型等核心機制。

HTTP 協議關鍵特徵回顧

  1. 標頭與本文間以雙 \r\n(0x0D 0x0A)分隔。
  2. 標頭行之間以單 \r\n 分隔。
  3. 標頭數量不定,但有上限。
  4. 本文長度由 Content-Length 標頭決定。
  5. 標頭名值對以半形冒號 : 分隔。
  6. 第一行(Request Line / Status Line)識別協議版本、方法/狀態碼。

HttpStack 設計原則

1. 避免過早字串化,保持 Byte[] 操作

網路封包可能分多次接收(甚至單一中文字元拆兩包),若每次收到就 Encoding.GetString() 再 IndexOf/Split,效能極差且易出錯。策略:

  • 維護 List<byte> 緩衝區,持續累積收到的位元組。
  • 掃描緩衝區尋找 \r\n\r\n 定位標頭結束位置。
  • 僅將標頭部分解析為字串(ASCII 安全),解析出 Content-Length。
  • 本文部分直接暴露為 Stream(MemoryStream 或檔案流),由上層應用決定如何處理(JSON、表單、檔案上傳等)。

2. 非同步接收與解耦

void OnReceive(IAsyncResult ar) {
    Socket sock = (Socket)ar.AsyncState;
    int received = sock.EndReceive(ar);
    if (received > 0) {
        buffer.AddRange(receivedBytes.Take(received));
        // 關鍵:先啟動下一次接收,再解析本次資料
        sock.BeginReceive(...);
        ParseBuffer(); // 解析完整封包並分派給應用層
    }
}

為何先 BeginReceive? 防止解析過程拋出例外或執行緒阻塞導致後續封包無法接收。雖有觀點建議「收完一個完整封包再 BeginReceive」,但實測現代 IOCP 架構下,先重新投遞接收反而能最大化併發吞吐量。

3. 單一執行緒擁有資料原則

解析邏輯直接在 IOCP 完成埠執行緒上執行,不引入額外佇列與解析執行緒。原則:一個執行緒只操作自己的私有資料,跨執行緒資料傳遞改用 Post 訊息給擁有者執行緒處理,避免鎖競爭。

效能優化細節

  • Socket.IOControl(FIONREAD) 或 socket.Available 可一次讀取緩衝區所有資料,但微軟不建議頻繁呼叫(內部需鎖定計算),實測固定 8KB~64KB 緩衝區循環讀取效能最穩定。
  • 標頭解析採「指標掃描」而非 Split:手寫 ParseHeaders(byte[] buf, int offset, int count) 回傳 Dictionary<string,string>,零配置、零分配。
  • 支援 Transfer-Encoding: chunked 時,本文解析器改為狀態機逐塊讀取。

下一步預告

下篇將實作 HttpRequest/HttpResponse 物件模型、路由對應、靜態檔案服務與動態處理管線,逐步完成功能可用的迷你 ASP.NET Core 風格伺服器。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved