以 C# 實作 Web Server

基礎篇

C# 簡介

開發環境

變數與運算

流程控制

陣列

函數

物件

例外處理

函式庫篇

檔案處理

資料結構

正規表達式

Thread

應用篇

視窗程式

媒體影音

網路程式

遊戲程式

手機程式

資料庫

雲端運算

特殊功能

委派

擴展方法

序列化

LinQ

WPF

網路資源

教學影片

投影片

教學文章

軟體下載

考題解答

101習題

下載:WebServer.zip

請注意:此範例必須在專案中加入 System.Web 套件引用。

根據 HTTP 協定,我們以 C# 實作了一個 Web Server 程式 (WebServer.cs),該程式是利用一個稱為 Socket 的物件來實作的,這個物件位於 C# 的網路函式庫 System.Net 中。

Socket 是根據博克萊 (U.C.Berkley) 大學早期發展的 Socket 概念寫成的,其設計理念是是將網路傳輸類比成檔案讀取與寫入 (傳送的動作被視為是寫入/接收的動作被視為是讀取),如此、傳送與接收就簡化為程式人員比較容易懂的
讀取與寫入,降低了網路程式的學習困難度。

要使用 Socket 的方式寫網路程式,首先要登記網路的埠號 (port),將該 port 占領下來,以防止其他程式使用該 port 而互相干擾,HTTP 協定所預設使用的是 port 80。

一但完成登記,就可以開始接受瀏覽器的請求,並根據請求回傳檔案訊息,以下程式為其 (接收/傳送) 程序的核心程式。

這個最簡單版以 Socket 的方式,不斷讀取資料直到發現有一空白行即結束,然而、這樣的程式是過度簡化的結果,無法處理有 POST 訊息的狀況,然而、何謂 POST 訊息呢 ?

所謂 POST 訊息、乃是 HTML 為了傳遞較大量的填表資料,所發展出來的一種訊息格式,以下是POST訊息的一個範例:

POST /getMsg.asp HTTP/1.0
Accept: image/gif, image/jpeg, application/msword, */*
Accept-Language: zh-tw
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0
Content-Length: 66
Host: ccc.kmit.edu.tw
Cache-Control: max-age=259200
Connection: keep-alive

user=ccc&password=1234&msg=Hello+%21+%0D%0AHow+are+you+%21%0D%0A++

其中的HTTP的訊息開頭以 POST 取代原來的 GET ,並且多了一個 Content-Length:66 的欄位,該欄位指示了訊息結尾會有 66 個位元組的填表資料,這些資料會被編碼成特輸的格式以利在網路上傳遞。

一但取得了瀏覽器傳來的 GET 或 POST 訊息後,我們就可以根據其訊息,決定回應的方式,在 WebServer.java 中,我們只是單純的將對應的檔案取出,並附在回應的訊息表頭後傳回,其程式碼如下。

以上就是 Web Server 的簡單設計方式,若想了解更多細節,請直接閱讀 WebServer.java 檔並執行之,執行時請安裝好 Visual Studio 後,並於 WebServer.cs 的存檔路徑上打 csc WebServer.cs, 之後再打 WebServer 即可啟動,其執行畫面如下:

程式範例

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

public class WebServer
{
   public static void Main()
   {
      IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 8085);

      Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

      newsock.Bind(ipep);
      newsock.Listen(10);

      while(true)
      {
         Socket client = newsock.Accept();
         IPEndPoint clientep = (IPEndPoint) client.RemoteEndPoint;

         // create a new thread and then receive message.
         HttpListener listener = new HttpListener(client);
         Thread thread = new Thread(new ThreadStart(listener.run));
         thread.Start();
      }
   }
}

class HttpListener 
{
    String[] map ={"mpeg=video/mpeg", "mpg=video/mpeg", "wav=audio/x-wav", "jpg=image/jpeg", 
"gif=image/gif", "zip=application/zip", "pdf=application/pdf", "xls=application/vnd.ms-excel", 
"ppt=application/vnd.ms-powerpoint", "doc=application/msword", "htm=text/html", 
"html=text/html", "css=text/plain", "vbs=text/plain", "js=text/plain", "txt=text/plain", 
"java=text/plain"};
    Socket socket;
    NetworkStream stream;
    String header;
    String root = ".";

    public HttpListener(Socket s)
    {
        socket = s;
    }

    public void run() 
    {
        stream = new NetworkStream(socket);
        request();
        response();
        stream.Close();
    }

    public void send(String str) {
        socket.Send(Encoding.UTF8.GetBytes(str));
    }

    public static String innerText(String pText, String beginMark, String endMark)
    {
        int beginStart = pText.IndexOf(beginMark);
        if (beginStart < 0) return null;
        int beginEnd = beginStart + beginMark.Length;
        int endStart = pText.IndexOf(endMark, beginEnd);
        if (endStart < 0) return null;
        return pText.Substring(beginEnd, endStart - beginEnd);
    }

    public void request()
    {
        try {
            StreamReader reader = new StreamReader(stream);
            header = "";
            while (true)
            {
                String line = reader.ReadLine();
                Console.WriteLine(line);
                if (line.Trim().Length == 0)
                    break;
                header += line + "\n";
            }
        } catch {
            Console.WriteLine("request error!");
        }
    }

    void response() 
    {
      try 
      {
        Console.WriteLine("========response()==========");
        String path = innerText(header, "GET ", "HTTP/").Trim(); // 取得檔案路徑 : GET 版。
        HttpUtility.UrlDecode(path);
        String fullPath = root+path;
        FileInfo info = new FileInfo(fullPath);
        if (!info.Exists) 
            throw new Exception("File not found !");
        send("HTTP/1.0 200 OK\n");
        send("Content-Type: "+type(fullPath)+"\n");
        send("Content-Length: "+info.Length+"\n");
        send("\n");
        byte[] buffer = new byte[4096];
        FileStream fileStream = File.OpenRead(fullPath);
        while (true) 
        {
            int len = fileStream.Read(buffer, 0, buffer.Length);
            socket.Send(buffer, 0, len, SocketFlags.None);
            if (len < buffer.Length) break;
        }
        fileStream.Close();
      } catch {
        try {
            send("HTTP/1.0 404 Error\n");
            send("\n");
        } catch {
            Console.WriteLine("Send Error Msg fail!");
        }
      }
    }

    String type(String path)
    {
        String type = "*/*";
        path = path.ToLower();
        for (int i = 0; i < map.Length; i++)
        {
            String[] tokens = map[i].Split('=');
            String ext = tokens[0], mime = tokens[1];
            if (path.EndsWith("." + ext)) type = mime;
        }
        return type;
    }
}
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License