World Wide Web (WWW) 基於客戶端/伺服器運算模型,由瀏覽器(客戶端)與 Web 伺服器(伺服器端)組成,兩者透過超文字傳輸協定(HTTP)進行通訊。HTTP 協定建立在 TCP/IP 之上,屬於應用層協定,具有通用、無狀態、面向物件的特性。其運作流程包含四個關鍵步驟:
瀏覽器與伺服器建立連線,開啟一個稱為 Socket(套接字)的虛擬檔案,標誌著連線成功建立。
瀏覽器透過 Socket 向伺服器提交請求,常見為 GET 或 POST 命令(POST 用於表單參數傳遞)。GET 指令格式為:GET 路徑/檔名 HTTP/1.0,其中檔名指定存取檔案,HTTP/1.0 表示瀏覽器使用的協定版本。
伺服器接收請求後進行處理,將結果透過 HTTP 回傳給瀏覽器顯示。為告知內容類型,伺服器會先傳送 HTTP 標頭資訊,再傳送實體內容(HTTP Body),兩者以空行分隔。常見標頭包含:
HTTP/1.0 200 OK:回應狀態碼,200 代表請求成功。MIME_Version: 1.0:指示 MIME 版本。Content-Type: text/html:指定回傳資料為 HTML 文件。Content-Length: 長度值:指示 Body 長度(位元組)。應答結束後雙方必須斷開連線,以釋放資源供其他客戶端使用。
依據 HTTP 協定原理,實作支援 GET 請求的簡易 Web 伺服器步驟如下:
TcpListener 物件,監聽指定連接埠(如 8080)。GET 路徑/檔名 HTTP/1.0)。index.html)。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 專案。