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

使用 C# 實作簡易 Web 伺服器:從 HTTP 協定到程式碼實作

     編輯:關於C#

HTTP 協定運作原理

World Wide Web (WWW) 基於客戶端/伺服器運算模型,由瀏覽器(客戶端)與 Web 伺服器(伺服器端)組成,兩者透過超文字傳輸協定(HTTP)進行通訊。HTTP 協定建立在 TCP/IP 之上,屬於應用層協定,具有通用、無狀態、面向物件的特性。其運作流程包含四個關鍵步驟:

1. 建立連線

瀏覽器與伺服器建立連線,開啟一個稱為 Socket(套接字)的虛擬檔案,標誌著連線成功建立。

2. 發送請求

瀏覽器透過 Socket 向伺服器提交請求,常見為 GET 或 POST 命令(POST 用於表單參數傳遞)。GET 指令格式為:GET 路徑/檔名 HTTP/1.0,其中檔名指定存取檔案,HTTP/1.0 表示瀏覽器使用的協定版本。

3. 伺服器應答

伺服器接收請求後進行處理,將結果透過 HTTP 回傳給瀏覽器顯示。為告知內容類型,伺服器會先傳送 HTTP 標頭資訊,再傳送實體內容(HTTP Body),兩者以空行分隔。常見標頭包含:

  • HTTP/1.0 200 OK:回應狀態碼,200 代表請求成功。
  • MIME_Version: 1.0:指示 MIME 版本。
  • Content-Type: text/html:指定回傳資料為 HTML 文件。
  • Content-Length: 長度值:指示 Body 長度(位元組)。

4. 關閉連線

應答結束後雙方必須斷開連線,以釋放資源供其他客戶端使用。

C# 實作 Web 伺服器程式設計

依據 HTTP 協定原理,實作支援 GET 請求的簡易 Web 伺服器步驟如下:

  1. 建立 TcpListener 物件,監聽指定連接埠(如 8080)。
  2. 等待並接受客戶端連線,取得連線的 Socket。
  3. 從 Socket 關聯的輸入串流讀取請求資訊(格式:GET 路徑/檔名 HTTP/1.0)。
  4. 解析請求類型,若為 GET 則取得請求的 HTML 檔名(無檔名時預設 index.html)。
  5. 檢查檔案是否存在:存在則讀取檔案,發送 HTTP 標頭與內容;不存在則回傳錯誤訊息。
  6. 關閉與該客戶端連線的 Socket。

核心程式碼實作

namespace cnnbsun.webserver
{
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;

    class MyWebServer
    {
        private TcpListener myListener;
        private int port = 8080;

        public MyWebServer()
        {
            try
            {
                myListener = new TcpListener(port);
                myListener.Start();
                Console.WriteLine("Web Server Running... Press ^C to Stop...");
                Thread th = new Thread(new ThreadStart(StartListen));
                th.Start();
            }
            catch (Exception e)
            {
                Console.WriteLine("監聽連接埠時發生錯誤: " + e.ToString());
            }
        }

        public void SendHeader(string sHttpVersion, string sMIMEHeader, int iTotBytes, string sStatusCode, ref Socket mySocket)
        {
            string sBuffer = "";
            if (sMIMEHeader.Length == 0) sMIMEHeader = "text/html";
            sBuffer += sHttpVersion + sStatusCode + "\r\n";
            sBuffer += "Server: cx1193719-b\r\n";
            sBuffer += "Content-Type: " + sMIMEHeader + "\r\n";
            sBuffer += "Accept-Ranges: bytes\r\n";
            sBuffer += "Content-Length: " + iTotBytes + "\r\n\r\n";
            byte[] bSendData = Encoding.ASCII.GetBytes(sBuffer);
            SendToBrowser(bSendData, ref mySocket);
            Console.WriteLine("Total Bytes: " + iTotBytes);
        }

        public void SendToBrowser(string sData, ref Socket mySocket) => SendToBrowser(Encoding.ASCII.GetBytes(sData), ref mySocket);

        public void SendToBrowser(byte[] bSendData, ref Socket mySocket)
        {
            int numBytes = 0;
            try
            {
                if (mySocket.Connected)
                {
                    if ((numBytes = mySocket.Send(bSendData, bSendData.Length, 0)) == -1)
                        Console.WriteLine("Socket Error cannot Send Packet");
                    else
                        Console.WriteLine("No. of bytes send {0}", numBytes);
                }
                else
                    Console.WriteLine("連線失敗....");
            }
            catch (Exception e) { Console.WriteLine("發生錯誤: {0}", e); }
        }

        public static void Main() => new MyWebServer();

        public void StartListen()
        {
            int iStartPos = 0;
            string sRequest, sDirName, sRequestedFile, sErrorMessage, sLocalDir, sMyWebServerRoot = @"E:\MyWebServerRoot\";
            string sFormattedMessage = "", sResponse = "";

            while (true)
            {
                Socket mySocket = myListener.AcceptSocket();
                Console.WriteLine("Socket Type: " + mySocket.SocketType);
                if (mySocket.Connected)
                {
                    Console.WriteLine("\nClient Connected!!\n==================\nCLient IP {0}\n", mySocket.RemoteEndPoint);
                    byte[] bReceive = new byte[1024];
                    int i = mySocket.Receive(bReceive, bReceive.Length, 0);
                    string sBuffer = Encoding.ASCII.GetString(bReceive);

                    if (sBuffer.Substring(0, 3) != "GET")
                    {
                        Console.WriteLine("僅處理 GET 請求...");
                        mySocket.Close();
                        continue;
                    }

                    iStartPos = sBuffer.IndexOf("HTTP", 1);
                    string sHttpVersion = sBuffer.Substring(iStartPos, 8);
                    sRequest = sBuffer.Substring(0, iStartPos - 1).Replace("\\", "/");

                    if (sRequest.IndexOf(".") < 1 && !sRequest.EndsWith("/"))
                        sRequest += "/";

                    iStartPos = sRequest.LastIndexOf("/") + 1;
                    sRequestedFile = sRequest.Substring(iStartPos);
                    sDirName = sRequest.Substring(sRequest.IndexOf("/"), sRequest.LastIndexOf("/") - 3);
                    sLocalDir = sMyWebServerRoot;

                    Console.WriteLine("請求檔案目錄: " + sLocalDir);

                    if (sLocalDir.Length == 0)
                    {
                        sErrorMessage = "

Error!! Requested Directory does not exists


"; SendHeader(sHttpVersion, "", sErrorMessage.Length, " 404 Not Found", ref mySocket); SendToBrowser(sErrorMessage, ref mySocket); mySocket.Close(); continue; } if (sRequestedFile.Length == 0) sRequestedFile = "index.html"; string sMimeType = "text/html"; string sPhysicalFilePath = sLocalDir + sRequestedFile; Console.WriteLine("請求檔案: " + sPhysicalFilePath); if (!File.Exists(sPhysicalFilePath)) { sErrorMessage = "

404 Error! File Does Not Exists...

"; SendHeader(sHttpVersion, "", sErrorMessage.Length, " 404 Not Found", ref mySocket); SendToBrowser(sErrorMessage, ref mySocket); } else { int iTotBytes = 0; sResponse = ""; using (FileStream fs = new FileStream(sPhysicalFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) using (BinaryReader reader = new BinaryReader(fs)) { byte[] bytes = new byte[fs.Length]; int read; while ((read = reader.Read(bytes, 0, bytes.Length)) != 0) { sResponse += Encoding.ASCII.GetString(bytes, 0, read); iTotBytes += read; } } SendHeader(sHttpVersion, sMimeType, iTotBytes, " 200 OK", ref mySocket); SendToBrowser(bytes, ref mySocket); } mySocket.Close(); } } } } }

編譯與測試

將程式碼編譯為 EXE 執行檔,設定虛擬目錄(如 E:\MyWebServerRoot\)並放入 HTML 檔案,即可透過瀏覽器存取 https://localhost:8080/檔名.html 進行測試。若需更完整的 ASP.NET 託管方案,可參考 Microsoft Cassini 專案。

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