核心類:MyWebServer
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Cnnbsun.WebServer
{
public class MyWebServer
{
private TcpListener listener;
private readonly int port = 8080;
private readonly string webRoot = @"E:\MyWebServerRoot\"; // 虛擬目錄
public MyWebServer()
{
try
{
listener = new TcpListener(port);
listener.Start();
Console.WriteLine("Web Server Running... Press ^C to Stop...");
new Thread(StartListen) { IsBackground = true }.Start();
}
catch (Exception ex) { Console.WriteLine("啟動失敗: " + ex); }
}
void StartListen()
{
while (true)
{
Socket socket = listener.AcceptSocket();
if (!socket.Connected) continue;
Console.WriteLine($"\nClient Connected: {socket.RemoteEndPoint}");
byte[] buffer = new byte[1024];
int received = socket.Receive(buffer);
string request = Encoding.ASCII.GetString(buffer, 0, received);
// 僅處理 GET
if (!request.StartsWith("GET"))
{
Console.WriteLine("僅支持 GET");
socket.Close(); continue;
}
// 解析請求行:GET /path/file HTTP/1.0
int httpIdx = request.IndexOf("HTTP", 1);
string httpVer = request.Substring(httpIdx, 8);
string reqLine = request.Substring(0, httpIdx - 1).Replace("\\", "/");
// 目錄補全
if (reqLine.IndexOf('.') < 0 && !reqLine.EndsWith("/")) reqLine += "/";
// 提取文件名
int lastSlash = reqLine.LastIndexOf('/') + 1;
string fileName = reqLine.Substring(lastSlash);
if (string.IsNullOrEmpty(fileName)) fileName = "index.html";
// 物理路徑
string physicalPath = Path.Combine(webRoot, fileName);
Console.WriteLine($"Request: {physicalPath}");
// 響應
if (!File.Exists(physicalPath))
{
SendError(socket, httpVer, "404 Not Found", "404 File Not Found
");
}
else
{
byte[] fileBytes = File.ReadAllBytes(physicalPath);
SendResponse(socket, httpVer, "text/html", fileBytes, "200 OK");
}
socket.Close();
}
}
void SendResponse(Socket s, string httpVer, string mime, byte[] body, string status)
{
string header = $"{httpVer} {status}\r\n" +
$"Server: CustomCSharpServer\r\n" +
$"Content-Type: {mime}\r\n" +
$"Content-Length: {body.Length}\r\n\r\n";
byte[] headerBytes = Encoding.ASCII.GetBytes(header);
s.Send(headerBytes);
s.Send(body);
}
void SendError(Socket s, string httpVer, string status, string html)
{
byte[] body = Encoding.ASCII.GetBytes(html);
SendResponse(s, httpVer, "text/html", body, status);
}
public static void Main() => new MyWebServer();
}
}
關鍵點解析
- TcpListener:阻塞式監聽端口,
AcceptSocket 返回已連接 Socket。
- 請求解析:簡易字符串解析,提取方法、路徑、HTTP 版本。
- 靜態文件服務:讀取文件字節 → 發送 HTTP 頭 + 體。
- 線程模型:單線程循環處理,高併發下會阻塞。生產環境需改用
ThreadPool、async/await 或 Kestrel。
- 安全性:路徑遍歷風險(
../../etc/passwd),需規範化路徑並限制在 webRoot 內。
編譯與測試
csc webserver.cs
webserver.exe
// 瀏覽器訪問 https://localhost:8080/index.html
擴展方向
- 支持 POST、HEAD、PUT 等方法。
- MIME 類型映射表(
.css→text/css 等)。
- 目錄瀏覽、默認文檔列表。
- HTTPS (TLS) 支持:
SslStream 包裝 Socket。
- 配置文件驅動端口、根目錄、日誌級別。