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

C# 自製簡易 HTTP Web Server 完整實作教學

     編輯:關於C語言

專案架構

  • 根目錄:C:\MyPersonalWebServer\
  • 資料目錄:C:\MyPersonalWebServer\Data\
  • 設定檔 (放於 Data 目錄):
    • Mimes.dat - 副檔名對應 MIME 類型
    • VDirs.dat - 虛擬目錄對應實體路徑
    • Default.dat - 預設文件名清單

設定檔範例

# Mimes.dat
.html;text/html
.htm;text/html
.jpg;image/jpeg
.png;image/png
.css;text/css
.js;application/javascript

# VDirs.dat
/;C:\MyWebServerRoot
/images/;C:\MyWebServerRoot\Images

# Default.dat
index.html
default.html
index.htm
default.htm

核心類別:MyWebServer

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Imtiaz
{
    public class MyWebServer
    {
        private TcpListener listener;
        private int port = 5050;
        private string webRoot = @"C:\MyWebServerRoot\";

        public MyWebServer()
        {
            try
            {
                listener = new TcpListener(port);
                listener.Start();
                Console.WriteLine("Web Server Running on port {0}... Press Ctrl+C to Stop", port);
                Thread t = new Thread(StartListen);
                t.Start();
            }
            catch (Exception ex)
            {
                Console.WriteLine("啟動失敗: " + ex.Message);
            }
        }

        // 取得預設文件名
        private string GetDefaultFile(string localDir)
        {
            try
            {
                using (StreamReader sr = new StreamReader("Data\\Default.dat"))
                {
                    string line;
                    while ((line = sr.ReadLine()) != null)
                    {
                        string full = Path.Combine(localDir, line.Trim());
                        if (File.Exists(full)) return line.Trim();
                    }
                }
            }
            catch { }
            return "";
        }

        // 解析虛擬目錄對應實體路徑
        private string GetLocalPath(string virtualDir)
        {
            try
            {
                using (StreamReader sr = new StreamReader("Data\\VDirs.dat"))
                {
                    string line;
                    while ((line = sr.ReadLine()) != null)
                    {
                        line = line.Trim();
                        if (line.Length == 0) continue;
                        int idx = line.IndexOf(';');
                        if (idx <= 0) continue;
                        string vdir = line.Substring(0, idx).ToLower();
                        string real = line.Substring(idx + 1);
                        if (vdir == virtualDir.ToLower()) return real;
                    }
                }
            }
            catch { }
            return "";
        }

        // 依副檔名取得 MIME 類型
        private string GetMimeType(string fileName)
        {
            string ext = Path.GetExtension(fileName).ToLower();
            try
            {
                using (StreamReader sr = new StreamReader("Data\\Mimes.dat"))
                {
                    string line;
                    while ((line = sr.ReadLine()) != null)
                    {
                        line = line.Trim();
                        if (line.Length == 0) continue;
                        int idx = line.IndexOf(';');
                        if (idx <= 0) continue;
                        if (line.Substring(0, idx).ToLower() == ext)
                            return line.Substring(idx + 1);
                    }
                }
            }
            catch { }
            return "text/html"; // 預設
        }

        // 發送 HTTP 標頭
        private void SendHeader(Socket socket, string httpVer, string mime, int contentLen, string status)
        {
            string header = $"{httpVer} {status}\r\n" +
                            "Server: MyCSharpWebServer/1.0\r\n" +
                            $"Content-Type: {mime}\r\n" +
                            "Accept-Ranges: bytes\r\n" +
                            $"Content-Length: {contentLen}\r\n\r\n";
            byte[] data = Encoding.ASCII.GetBytes(header);
            socket.Send(data);
        }

        // 發送資料
        private void SendData(Socket socket, byte[] data)
        {
            try { if (socket.Connected) socket.Send(data); }
            catch { }
        }

        // 主監聽迴圈
        private void StartListen()
        {
            while (true)
            {
                Socket client = listener.AcceptSocket();
                if (!client.Connected) continue;

                Console.WriteLine("Client connected: {0}", client.RemoteEndPoint);

                byte[] buffer = new byte[1024];
                int received = client.Receive(buffer);
                string request = Encoding.ASCII.GetString(buffer, 0, received);

                // 僅支援 GET
                if (!request.StartsWith("GET "))
                {
                    client.Close();
                    continue;
                }

                // 解析請求行: GET /path/file.html HTTP/1.1
                int httpIdx = request.IndexOf("HTTP", StringComparison.OrdinalIgnoreCase);
                string httpVer = request.Substring(httpIdx, 8); // "HTTP/1.1"
                string requestLine = request.Substring(0, httpIdx - 1).Trim(); // "GET /path/file.html"
                string url = requestLine.Substring(4).Trim(); // "/path/file.html"
                url = url.Replace('\\', '/');

                // 目錄結尾補 / 以利尋找預設文件
                if (!url.Contains(".") && !url.EndsWith("/")) url += "/";

                // 分離虛擬目錄與檔名
                int lastSlash = url.LastIndexOf('/');
                string virtualDir = url.Substring(0, lastSlash + 1);
                string fileName = url.Substring(lastSlash + 1);

                // 解析實體目錄
                string localDir = (virtualDir == "/") ? webRoot : GetLocalPath(virtualDir);
                if (string.IsNullOrEmpty(localDir))
                {
                    SendError(client, httpVer, "404 Not Found", "Directory not found");
                    client.Close();
                    continue;
                }

                // 若無檔名,尋找預設文件
                if (string.IsNullOrEmpty(fileName))
                {
                    fileName = GetDefaultFile(localDir);
                    if (string.IsNullOrEmpty(fileName))
                    {
                        SendError(client, httpVer, "404 Not Found", "No default file");
                        client.Close();
                        continue;
                    }
                }

                string physicalPath = Path.Combine(localDir, fileName);
                string mime = GetMimeType(fileName);

                if (!File.Exists(physicalPath))
                {
                    SendError(client, httpVer, "404 Not Found", "File not found: " + fileName);
                    client.Close();
                    continue;
                }

                // 讀取檔案並回應
                try
                {
                    byte[] fileBytes = File.ReadAllBytes(physicalPath);
                    SendHeader(client, httpVer, mime, fileBytes.Length, "200 OK");
                    SendData(client, fileBytes);
                }
                catch (Exception ex)
                {
                    SendError(client, httpVer, "500 Internal Server Error", ex.Message);
                }
                finally
                {
                    client.Close();
                }
            }
        }

        private void SendError(Socket socket, string httpVer, string status, string msg)
        {
            string html = $"

{status}

{msg}

"; byte[] data = Encoding.ASCII.GetBytes(html); SendHeader(socket, httpVer, "text/html", data.Length, status); SendData(socket, data); } } }

程式進入點

class Program
{
    static void Main()
    {
        new MyWebServer();
        Console.ReadLine(); // 保持主執行緒存活
    }
}

編譯與執行

csc /out:MyWebServer.exe MyWebServer.cs
MyWebServer.exe

測試方式

瀏覽器輸入 https://localhost:5050/ 或 https://localhost:5050/test.html。

限制與擴充方向

  • 單執行緒循序處理,高併發效能差 → 改用 ThreadPool 或 async/await 非同步 Socket。
  • 不支援 POST、HEAD、PUT 等方法。
  • 不支援 CGI/ASP.NET/PHP 等動態腳本。
  • 無 HTTPS/SSL 支援。
  • 生產環境請使用 IIS、Kestrel (ASP.NET Core) 或成熟開源伺服器。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved