請注意:此範例必須在專案中加入 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 版。