開篇:每當咱們將開發好的ASP.NET網站部署到IIS服務器中,在瀏覽器正常瀏覽頁面時,可曾想過Web服務器是怎麼工做的,其原理是什麼?「紙上得來終覺淺,絕知此事要躬行」,因而咱們本身模擬一個簡單的Web服務器來體會一下。javascript
每個HTTP請求都會經歷三個步湊:請求-處理-響應:每當咱們在瀏覽器中輸入一個URL時都會被封裝爲一個HTTP請求報文發送到Web服務器,而Web服務器則接收並解析HTTP請求報文,而後針對請求進行處理(返回指定的HTML頁面、CSS樣式表、JS腳本文件亦或是加載動態頁面生成HTML並返回)。最後將要返回的內容轉爲輸出流並封裝爲HTTP響應報文發送回瀏覽器。css
固然,瀏覽器接收到響應報文後會加載HTML、CSS與JS並顯示在頁面中,最後成爲咱們看到的最終效果。html
Web服務器本質上來講就是一個Socket服務端,在不停地接受着客戶端的請求,而後針對每個客戶端的請求進行處理,處理完畢就即時關閉鏈接。而咱們的瀏覽器則是一個Socket客戶端,經過TCP協議向服務端發送HTTP請求報文。java
About:Socket很是相似於電話插座,以一個電話網爲例:電話的通話雙方至關於相互通訊的2個程序,電話號碼就是IP地址。任何用戶在通話以前,首先要佔有一部電話機,至關於申請一個Socket;同時要知道對方的號碼,至關於對方有一個固定的Socket。而後向對方撥號呼叫,至關於發出鏈接請求。對方假如在場並空閒,拿起電話話筒,雙方就能夠正式通話,至關於鏈接成功。雙方通話的過程,是一方向電話機發出信號和對方從電話機接收信號的過程,至關於向Socket發送數據和從Socket接收數據。通話結束後,一方掛起電話機至關於關閉socket,撤消鏈接。數據庫
Internet的基本協議是TCP/IP協議(傳輸控制協議和網際協議),目前普遍使用的 FTP、HTTP(超文本傳輸協議)、Archie Gopher都是創建在TCP/IP上面的應用層協議,不一樣的協議對應不一樣的應用。而HTTP協議是Web應用所使用的主要協議。瀏覽器
HTTP協議是基於請求響應模式的。客戶端向服務器發送一個請求,請求頭包含請求的方法、 URI、協議版本、以及包含請求修飾符、客戶端信息和內容的相似MIME的消息結果。服務器則以一個狀態行做爲響應,相應的內容包括消息協議的版本、成功或者錯誤編碼加上包含服務器信息、實體元信息以及可能的實體內容。服務器
HTTP是無狀態協議,依賴於瞬間或者近乎瞬間的請求處理。請求信息被當即發送,理想的狀況是 沒有延時的進行處理,不過,延時仍是客觀存在的。HTTP有一種內置的機制,在消息的傳遞時間上由必定的靈活性:超時機制。一個超時就是客戶機等待請求 消息的返回信息的最長時間。多線程
(1)HTTP請求報文示例app
(2)HTTP響應報文示例socket
TIP:關於HTTP協議的詳細介紹,能夠瀏覽一下小坦克大神的這篇:HTTP協議詳解
(1)處理用戶的靜態文件請求:主要是指html/css/js文件的請求;
(2)處理用戶的動態文件請求:這裏只處理ASP.NET請求,即ashx與aspx文件的請求;
(1)HttpRequest、HttpResponse與HttpContext類
根據咱們對ASP.NET請求處理機制的分析,咱們知道在HttpRuntime的ProcessRequest方法中構造了一個HttpContext對象。在HttpContext的構造函數中,根據HttpWorkerRequest對象建立了HttpContext對象,這是一個重要的Http上下文對象,兩個重要類型的字段也隨之被初始化:HttpRequest對象和HttpResponse對象。所以,咱們在設計時也能夠設計一個HttpContext類將HttpRequest和HttpResponse兩個實例進行封裝。
TIP:有關ASP.NET請求處理機制的分析,能夠瀏覽個人另一篇文章:ASP.NET請求處理機制探索之二-核心
(2)IHttpHandler接口與實現IHttpHandler接口的HttpApplication類
針對每一個Http請求都有一個抽象的HttpApplication對象來進行處理,而爲了考慮擴展性(能夠是ashx,也能夠是aspx),封裝了一個IHttpHandler接口,讓不一樣的處理對象實現這個接口便可。IHttpHandler接口很簡單,就聲明瞭一個ProcessRequest方法,每一個實現的類只須要實現這個方法便可。
private void btnStart_Click(object sender, EventArgs e) { // 建立Socket->綁定IP與端口->設置監聽隊列的長度->開啓監聽鏈接 socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socketWatch.Bind(new IPEndPoint(IPAddress.Parse(txtIPAddress.Text), int.Parse(txtPort.Text))); socketWatch.Listen(10); // 建立Thread->後臺執行 threadWatch = new Thread(ListenClientConnect); threadWatch.IsBackground = true; threadWatch.Start(socketWatch); isEndService = false; txtIPAddress.ReadOnly = true; txtPort.ReadOnly = true; btnStart.Enabled = false; ShowMessage("~_~消息:【您已成功啓動Web服務!】"); } private void ListenClientConnect(object obj) { Socket socketListen = obj as Socket; while (!isEndService) { Socket proxSocket = socketListen.Accept(); byte[] data = new byte[1024 * 1024 * 2]; int length = proxSocket.Receive(data, 0, data.Length, SocketFlags.None); // Step1:接收HTTP請求 string requestText = Encoding.Default.GetString(data, 0, length); HttpContext context = new HttpContext(requestText); // Step2:處理HTTP請求 HttpApplication application = new HttpApplication(); application.ProcessRequest(context); ShowMessage(string.Format("{0} {1} from {2}", context.Request.HttpMethod, context.Request.Url, proxSocket.RemoteEndPoint.ToString())); // Step3:響應HTTP請求 proxSocket.Send(context.Response.GetResponseHeader()); proxSocket.Send(context.Response.Body); // Step4:即時關閉Socket鏈接 proxSocket.Shutdown(SocketShutdown.Both); proxSocket.Close(); } }
在監聽線程中,經過HttpApplication類對象調用其ProcessRequest方法進行具體的處理。最重要的,處理完畢後當即經過Socket發送響應信息,並及時關閉Socket鏈接。
(1)HttpContext
public class HttpContext { public HttpRequest Request { get; set; } public HttpResponse Response { get; set; } public HttpContext(string requestText) { Request = new HttpRequest(requestText); Response = new HttpResponse(); } }
(2)HttpRequest
public class HttpRequest { public HttpRequest(string requestText) { string[] lines = requestText.Replace("\r\n", "\r").Split('\r'); string[] requestLines = lines[0].Split(' '); // 獲取HTTP請求方式、請求的URL地址、HTTP協議版本 HttpMethod = requestLines[0]; Url = requestLines[1]; HttpVersion = requestLines[2]; } // 請求方式:GET or POST? public string HttpMethod { get; set; } // 請求URL public string Url { get; set; } // Http協議版本 public string HttpVersion { get; set; } // 請求頭 public Dictionary<string, string> HeaderDictionary { get; set; } // 請求體 public Dictionary<string, string> BodyDictionary { get; set; } }
(3)HttpResponse
public class HttpResponse { // 響應狀態碼 public string StateCode { get; set; } // 響應狀態描述 public string StateDescription { get; set; } // 響應內容類型 public string ContentType { get; set; } //響應報文的正文內容 public byte[] Body { get; set; } // 生成響應頭信息 public byte[] GetResponseHeader() { string strRequestHeader = string.Format(@"HTTP/1.1 {0} {1} Content-Type: {2} Accept-Ranges: bytes Server: Microsoft-IIS/7.5 X-Powered-By: ASP.NET Date: {3} Content-Length: {4} ", StateCode, StateDescription, ContentType, string.Format("{0:R}", DateTime.Now), Body.Length); return Encoding.UTF8.GetBytes(strRequestHeader); } }
這裏須要注意的是在HttpResponse類中,爲了生成響應頭信息,須要格式化一個固定格式的信息,而且在最後保留兩個CRLF(即換行符)做爲頭部結束標誌,能夠看看下面的格式說明:
public interface IHttpHandler { void ProcessRequest(HttpContext context); }
仿照ASP.NET內部實現,咱們也設計一個IHttpHandler接口,只定義了一個方法:ProcessRequest;
public class HttpApplication : IHttpHandler { // 對請求上下文進行處理 public void ProcessRequest(HttpContext context) { // 1.獲取網站根路徑 string bastPath = AppDomain.CurrentDomain.BaseDirectory; string fileName = Path.Combine(bastPath+"\\MyWebSite", context.Request.Url.TrimStart('/')); string fileExtension = Path.GetExtension(context.Request.Url); // 2.處理動態文件請求 if(fileExtension.Equals(".aspx") || fileExtension.Equals(".ashx")) { string className = Path.GetFileNameWithoutExtension(context.Request.Url); IHttpHandler handler = Assembly.Load("MyWebServer").CreateInstance("MyWebServer.Page." + className) as IHttpHandler; handler.ProcessRequest(context); return; } // 3.處理靜態文件請求 if (!File.Exists(fileName)) { context.Response.StateCode = "404"; context.Response.StateDescription = "Not Found"; context.Response.ContentType = "text/html"; string notExistHtml = Path.Combine(bastPath, @"MyWebSite\notfound.html"); context.Response.Body = File.ReadAllBytes(notExistHtml); } else { context.Response.StateCode = "200"; context.Response.StateDescription = "OK"; context.Response.ContentType = GetContenType(Path.GetExtension(context.Request.Url)); context.Response.Body = File.ReadAllBytes(fileName); } } // 根據文件擴展名獲取內容類型 public string GetContenType(string fileExtension) { string type = "text/html; charset=UTF-8"; switch (fileExtension) { case ".aspx": case ".html": case ".htm": type = "text/html; charset=UTF-8"; break; case ".png": type = "image/png"; break; case ".gif": type = "image/gif"; break; case ".jpg": case ".jpeg": type = "image/jpeg"; break; case ".css": type = "text/css"; break; case ".js": type = "application/x-javascript"; break; default: type = "text/plain; charset=gbk"; break; } return type; } }
這裏,咱們封裝一個抽象的HttpApplication類,它實現了IHttpHandler接口,對通常的請求作一個通用的處理操做。若是是靜態文件請求,則直接讀取文件並生成響應流,若是是動態文件請求,則經過反射方式生成對應的Page對象實例,將HttpContext對象傳入其ProcessRequest方法中進行處理,最後的響應內容都封裝到了HttpConext中的HttpResponse對象的Body屬性中。
public class DemoPage : IHttpHandler { public void ProcessRequest(HttpContext context) { StringBuilder sbText = new StringBuilder(); sbText.Append("<html>"); sbText.Append("<head><meta http-equiv='Content-Type' content='text/html; charset=utf-8'/><title>DemoPage</title></head>"); sbText.Append("<body style='margin:10px auto;text-align:center;'>"); sbText.Append("<h1>用戶信息列表</h1>"); sbText.Append("<table align='center' cellpadding='1' cellspacing='1'><thead><tr><td>ID</td><td>用戶名</td></tr></thead>"); sbText.Append("<tbody>"); sbText.Append(GetDataList()); sbText.Append("</tbody></table>"); sbText.Append(string.Format("<h3>更新時間:{0}</h3>", DateTime.Now.ToString())); sbText.Append("</body>"); sbText.Append("</html>"); context.Response.Body = Encoding.UTF8.GetBytes(sbText.ToString()); context.Response.StateCode = "200"; context.Response.ContentType = "text/html"; context.Response.StateDescription = "OK"; } private string GetDataList() { StringBuilder sbData = new StringBuilder(); string strConn = System.Configuration.ConfigurationManager.ConnectionStrings["MyConn"].ToString(); using (SqlConnection conn = new SqlConnection(strConn)) { conn.Open(); using (SqlCommand cmd = conn.CreateCommand()) { cmd.CommandText = "SELECT * FROM UserInfo"; using(SqlDataAdapter adapter = new SqlDataAdapter(cmd)) { DataTable dt = new DataTable(); adapter.Fill(dt); if(dt != null) { foreach (DataRow row in dt.Rows) { sbData.Append("<tr>"); sbData.Append(string.Format("<td>{0}</td>",row["ID"].ToString())); sbData.Append(string.Format("<td>{0}</td>", row["UserName"].ToString())); sbData.Append("</tr>"); } } } } } return sbData.ToString(); } }
這裏咱們模擬一個Page頁面類,它也實現了IHttpHandler接口,在ProcessRequest方法經過ADO.NET訪問了數據庫並讀取數據做爲輸出內容。這裏,咱們主要是經過分析ASP.NET WebForm中的aspx對象,它雖然直接繼承的類是Page類,可是Page類倒是實現了IHttpHandler接口的。在具體的處理方法中,都是經過調用這個接口的ProcessRequest方法進行處理的。
(1)開啓監聽服務
(2)請求靜態頁面
(3)請求動態頁面
本次模擬的一個超級簡單的Web服務器軟件,實現了靜態文件和動態文件(經過模擬aspx頁面對象)的處理和響應。可是,還有不少的功能並未實現,由於一個真正的Web服務器須要考慮的東西不少不少,例如:多線程的處理優化、高效的IO模型等。不過,對於一個最基本的Web服務器所須要瞭解的最基本的原理:Socket的監聽和鏈接、基於TCP協議的HTTP協議、動態文件類的反射與調用等,模擬開發本次的DEMO的過程是能夠達到的。
MyWebServer v1.0:http://pan.baidu.com/s/1mgtC1HA