一步步搭建本身的web服務器

IIS或者其餘Web服務器究竟作了哪些工做,讓瀏覽器請求一個URL地址後顯示一個漂亮的網頁?要想弄清這個疑問,我想咱們能夠本身寫一個簡單的web服務器。javascript

思路:css

  1. 建立socket監聽瀏覽器請求。
  2. 鏈接成功,接受瀏覽器的請求數據。
  3. 響應瀏覽器的請求。(咱們只響應靜態文件) 。

好的,思路很清晰。下面就跟着我動手一步步用代碼實現。html

一、建立socket監聽瀏覽器請求。

  當瀏覽器請求域名,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 }
View Code

 

  這個類的代碼比較多,可是不難,都是按照順序一步步往下走的。其實web服務器大體就作了這些事情。只不過封裝的東西更多,功能更完善。

資源:

項目源碼下載地址

相關文章
相關標籤/搜索