IIS或者其餘Web服務器究竟作了哪些工做,讓瀏覽器請求一個URL地址後顯示一個漂亮的網頁?要想弄清這個疑問,我想咱們能夠本身寫一個簡單的web服務器。javascript
思路:css
好的,思路很清晰。下面就跟着我動手一步步用代碼實現。html
當瀏覽器請求域名,DNS將域名解析爲IP地址。帶着缺省的端口80訪問咱們的服務器。因此Socket的監聽,也須要綁定IP和端口。java
// 窗體加載 private void Form1_Load(object sender, EventArgs e) { // 關閉線程操做檢測 Control.CheckForIllegalCrossThreadCalls = false; Log("服務器啓動中 >>>"); IPAddress ip = IPAddress.Parse(txtIP.Text); IPEndPoint p = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); socketWatch.Bind(p); socketWatch.Listen(10); // 爲了避免阻塞主線程,開啓後臺線程,監聽瀏覽器請求。 Thread t = new Thread(Listen); t.IsBackground = true; t.Start(); Log("服務器啓動成功。"); }
Listen 進程只須要無限循環接受瀏覽器發送過來的請求。web
private void Listen()瀏覽器
{服務器
while (true)socket
{ide
Socket sender=socketWatch.Accept(); ui
Log(sender.RemoteEndPoint.ToString() + "鏈接成功");
}
}
輔助方法,顯示提示信息。(不重要)
// 顯示提示信息 private void Log(string msg) { txtMsg.AppendText(msg + "\r\n"); }
打開咱們的winform程序。
打開瀏覽器訪問: http://192.168.95.1:3099/ 地址
就能看到如下結果:
瀏覽器的請求到達服務器後,咱們要接待他,就要問他是過來幹嗎的。
要和瀏覽器交流,那麼服務器和瀏覽器就應該說一種語言。這個語言就是http協議。
(對HTTP協議還不太瞭解的園友能夠參考:http協議知識整理)
根據HTTP協議,瀏覽器請求服務器的格式都是規定好的,
因此咱們只須要根據格式進行拆分,獲取咱們感興趣的數據。
這件事情,已經不屬於form1.cs這個類的能力範圍了。因此咱們找一個管家HttpManager來幫咱們處理。至於怎麼處理form1不用管,他只須要告訴管家就能夠了。
在form1.cs的Listen中建立管家
HttpManager manager = new HttpManager(sender, Log);
管家接收基礎的數據後,再交給專門處理請求數據的對象HttpRequest。
class HttpManager { private Socket socket; private Action<string> Log; public HttpManager(Socket socket, Action<string> act) { this.socket = socket; this.Log = act; // 接收消息 string msg = ReceiveMsg(); Log(msg); try { // 去找請求對象,拿到咱們須要的數據。 HttpRequest request = new HttpRequest(msg); } catch (Exception ex) { Log(ex.Message); } } // 接受瀏覽器請求報文 private string ReceiveMsg() { byte[] date = new byte[1024 * 1024]; int count = socket.Receive(date); return Encoding.UTF8.GetString(date, 0, count); } }
核心的HttpResponse:
class HttpRequest { // 請求原始地址 /xxx/xxx.xxxx public string RawUrl { get; set; } // 請求類型 Post/Get public string Method; public HttpRequest(string msg) { string[] lines = msg.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); string[] arrays = lines[0].Split(' '); Method = arrays[0]; RawUrl = arrays[1]; } }
響應過程其實也很是簡單,IO讀取瀏覽器須要請求的數據,拼接響應報文返回給瀏覽器。
在HttpManager類接收完數據後,調用響應對象。來幫咱們處理響應。
// 構造響應報文,響應請求。 HttpResponse response = new HttpResponse(request.RawUrl, socket, Log);
核心HttpResponse代碼
1 class HttpResponse 2 { 3 public int StatusCode { get; set; } 4 public Dictionary<int, string> Dic = new Dictionary<int, string>(); 5 public string ContentType { get; set; } 6 public int ContentLength { get; set; } 7 public byte[] Data { get; set; } 8 public Socket Socket { get; set; } 9 public Action<string> Log { get; set; } 10 public HttpResponse(string rawUrl, Socket socket, Action<string> log) 11 { 12 this.Socket = socket; 13 FillDic(); 14 this.Log = log; 15 // 判斷是否爲靜態資源 16 if (Judge(rawUrl)) 17 { 18 // IO 讀取資源 19 Data = ReadFile(rawUrl); 20 if (Data == null) 21 { 22 // 404 沒有這個文件 23 StatusCode = 404; 24 string msg = "沒有找到這個文件。"; 25 ContentType = "text/html"; 26 Data = Encoding.Default.GetBytes(msg); 27 ContentLength = Data.Length; 28 ContentType = "text/html"; 29 SendData(); 30 } 31 else 32 { 33 ContentLength = Data.Length; 34 StatusCode = 200; 35 ContentType = GetContentType(rawUrl); 36 // 響應請求 37 SendData(); 38 } 39 } 40 else 41 { 42 // 不處理 43 string msg = "只處理靜態文件。"; 44 Data = Encoding.Default.GetBytes(msg); 45 ContentLength = Data.Length; 46 StatusCode = 500; 47 ContentType = "text/html"; 48 // 響應請求 49 SendData(); 50 } 51 52 53 } 54 55 56 // 判斷文件是否爲靜態資源 57 private bool Judge(string rawUrl) 58 { 59 bool isStatic = false; 60 string ext = Path.GetExtension(rawUrl); 61 switch (ext) 62 { 63 case ".html": 64 case ".htm": 65 case ".jpg": 66 case ".png": 67 case ".gif": 68 case ".js": 69 case ".css": 70 case ".ico": isStatic = true; break; 71 } 72 return isStatic; 73 } 74 75 // 響應 76 private void SendData() 77 { 78 StringBuilder sb = new System.Text.StringBuilder(); 79 sb.Append("HTTP/1.1 " + StatusCode + " " + Dic[StatusCode] + "\r\n"); 80 sb.Append("Server: ksn/1.0\r\n"); 81 sb.Append("Content-Type: " + ContentType + "\r\n"); 82 sb.Append("Content-Length: " + ContentLength + "\r\n"); 83 sb.Append("\r\n"); 84 Log("響應報文: \r\n" + sb.ToString()); 85 86 byte[] head = Encoding.UTF8.GetBytes(sb.ToString()); 87 List<byte> res = new List<byte>(); 88 res.AddRange(head); 89 if (this.Data != null) 90 { 91 res.AddRange(this.Data); 92 } 93 Socket.Send(res.ToArray()); 94 95 } 96 97 98 private string GetContentType(string rawUrl) 99 { 100 string ct = "text/html"; 101 string ext = Path.GetExtension(rawUrl); 102 switch (ext) 103 { 104 case ".js": ct = "text/javascript"; 105 break; 106 case ".css": ct = "text/css"; 107 break; 108 case ".jpg": ct = "image/jpeg"; 109 break; 110 case ".png": ct = "image/png"; 111 break; 112 case ".gif": ct = "image/gif"; 113 break; 114 case ".ico": ct = "image/x-ico"; 115 break; 116 } 117 return ct; 118 } 119 120 // 初始化狀態碼 121 private void FillDic() 122 { 123 Dic.Add(200, "Ok"); 124 Dic.Add(404, "Not Found"); 125 Dic.Add(500, "Internal Error"); 126 } 127 128 // 讀取請求的資源 129 private byte[] ReadFile(string rawUrl) 130 { 131 string path = AppDomain.CurrentDomain.BaseDirectory; 132 path = path + "web" + rawUrl; 133 if (File.Exists(path)) 134 { 135 using (FileStream fs = new FileStream(path, FileMode.Open)) 136 { 137 byte[] data = new byte[fs.Length]; 138 fs.Read(data, 0, data.Length); 139 return data; 140 } 141 } 142 else 143 { 144 return null; 145 } 146 147 148 } 149 }
這個類的代碼比較多,可是不難,都是按照順序一步步往下走的。其實web服務器大體就作了這些事情。只不過封裝的東西更多,功能更完善。