close
實作 Web Server

下載: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;
    }
}
	

陳鍾誠 (2010年06月15日),(網頁標題) 以 C# 實作 Web Server,(網站標題) 免費電子書:C# 程式設計,2010年06月15日,取自 http://cs0.wikidot.com/webserver ,網頁修改第 8 版。

arrow
arrow

    Johnson峰 發表在 痞客邦 留言(0) 人氣()