TCP/IP協議學習(四) 基於C# Socket的Web伺服器---靜態資源處理

來源:互聯網
上載者:User

標籤:set   asn   exception   rdk   串連   es6   log4   sda   substring   

目錄

1. C# Socket通訊

2. HTTP 解析引擎

3. 資源讀取和返回

4. 伺服器測試和代碼下載

  Web伺服器是Web資源的宿主,它需要處理使用者端瀏覽器的請求,並指定對應的Web資源返回給使用者,這些資源不僅包括HTML檔案,JS指令碼,JPG圖片等,還包括由軟體產生的動態內容。為了滿足上述需求,一個完整的Web伺服器工作流程:

  1) 伺服器獲得瀏覽器通過TCP/IP串連向伺服器發送的http請求資料包。

  2) HTTP請求經過Web伺服器的HTTP解析引擎分析得出要求方法、資源地址等資訊,然後開始處理。

  3) 對於靜態請求,則在伺服器上查詢請求url路徑下檔案,並返回(如果未找到則返回404 No Found)。

  4) 涉及動態請求,如CGI, AJAX, ASP等,則根據http方法,採取不同處理。

  

  Web伺服器的核心由C# Socket通訊,http解析引擎,靜態資源檔案尋找,動態資料接收和發送4部分組成,本節因為個人編寫進度原因主要實現前3個部分(即能夠查詢靜態資源的Web伺服器),動態資料處理因為涉及的處理方式CGI,AJAX,ASP的方法不同,後續完成後在總結相關知識。

1. C# Socket通訊 

  C# Socket通過對TCP/IP協議進行封裝,用於實現滿足TCP通訊的API。在B/S架構中,伺服器端的處理和C/S串連基本相同,主要工作包含:建立Socket通訊端,監聽串連,建立串連,獲得請求,處理並返回資料,關閉串連等。

  程式入口函數,採用輪詢方式實現對用戶端請求的監聽。

          //建立監聽線程          Thread Listen_thread = new Thread(socket_listen);          Listen_thread.IsBackground = false;          Listen_thread.Start();        

  監聽線程,建立Socket通訊端,綁定並監聽指定連接埠,等待串連建立,串連建立後,考慮到網頁請求高並發的特性,採用另開線程的方式來處理建立的串連,從而實現並發伺服器模式。

            Socket server_socket = null;            //監聽的IP地址和連接埠 作為伺服器,綁定的只能是本機Ip地址或者迴路位址(不能與系統其它進程連接埠衝突)            //如果綁定為本節IP地址,區域網路下其它裝置可以通過http://host:port來訪問當前伺服器            string host = "127.0.0.1";            int port = 3000;            IPAddress ip = IPAddress.Parse(host);            IPEndPoint ipe = new IPEndPoint(ip, port);            //建立Socket通訊端,綁定在指定的連接埠並開始監聽            server_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            server_socket.Bind(ipe);            server_socket.Listen(100);            Console.WriteLine("Server Binding at " + host + ":" + port.ToString() +"...");            Console.WriteLine("Wait for connect....");            while (true)            {                Socket CurrentSocket;                //三向交握成功,建立一個串連                CurrentSocket = server_socket.Accept();                Console.WriteLine("New TCP Socket Create...");                //單開一個線程用來處理伺服器收發, 並發伺服器模式                ParameterizedThreadStart tStart = new ParameterizedThreadStart(socket_process);                Thread process_thread = new Thread(tStart);                process_thread.IsBackground = false;                process_thread.Start(CurrentSocket);            }

  串連處理,socket通訊處理主要負責接收串連產生的資料,並將http引擎處理後資料提交給用戶端瀏覽器。

          Socket CurrentSocket = (Socket)obj;          try          {                string recvStr = "";                byte[] recvBytes = new byte[2000];                int length;                //獲得當前Socket串連傳輸的資料,並轉換為ASCII碼格式                length = CurrentSocket.Receive(recvBytes, recvBytes.Length, 0);                recvStr = Encoding.ASCII.GetString(recvBytes, 0, length);                //http引擎處理,返回獲得資料                byte[] bs =  http_engine(recvStr, length);                //通過socket發送引擎處理後資料                CurrentSocket.Send(bs, bs.Length, 0);                Console.WriteLine("File Send Finish, Socket Close....\r\n");                //關閉socket串連                CurrentSocket.Close();            }            catch (Exception exception)            {                Console.WriteLine(exception);                CurrentSocket.Close();            }

  C# Socket通訊架構的實現和C/S結構沒有什麼區別,如果瞭解過Socket可以輕鬆實現上述socket通訊架構。 不過下面這部分將講述Web伺服器的實現核心--http解析引擎,這也是B/S架構和C/S架構中伺服器端最大的區別。

2. http解析引擎

  Web伺服器主要實現對瀏覽器請求資料包的處理,並返回指定的http資源或者資料,這些都是由http解析引擎實現,在寫http解析引擎之前,我們要知曉接收到的資料才能進行後續的處理,這裡提供通過WireShark抓取的http請求包:

  雖然HTTP請求包的內容很多,但因為目前實現的功能較少,所以關注的只有http報文起始行就可以,而首部欄位可以直接丟棄不處理,後續如果使用認證機制,如白名單,黑名單過濾,帳號/密碼保護,資源許可權管理等,首部仍然要處理。

  對於http報文起始行, 內部以space隔開,並以‘\r\n‘作為結尾與首部隔開。其中GET:HTTP方法, ‘/‘ :資源路徑url, HTTP/1.1:協議版本,參照http權威指南的內容,  HTTP協議的常見方法有GET, PUT, DELETE, POST, HEAD這5種,本節中的靜態伺服器主要涉及到GET方法。瞭解了需要如何解析HTTP請求報文後,我們先定義一個HTTP報文解析結構,用於儲存到解析的資訊。

        public class HTTPPrase        {           //http方法           public string http_method;           //http資源           public string url;           //http版本號碼           public string version;           //url解析的請求網頁類型           public string type;        };

  下面我們就要開始利用C#提供的String方法來截取http報文來實現上述結構體內參數的初始化。

            int pos;           //根據\r\n截斷,擷取http報文首部並轉換為小寫,方便後續處理           //Get / HTTP/1.1/r/n           pos = str.IndexOf("\r\n");           string str_head = str.Substring(0, pos);           str_head = str_head.ToLower();            //根據‘ ‘來截斷起始行,並賦值給對應參數           string[] arr = Regex.Split(str_head, @"\s+");           HTTPServer.HTTPPrase http_head = new HTTPServer.HTTPPrase();           http_head.http_method = arr[0];      // "Get"           http_head.url = arr[1];              // "/"           http_head.version = arr[2];          // "HTTP/1.1"            //判斷是否有通過ajax要求獲得或者提交的動態資料            http_head.ajax_status = str_head.IndexOf(".ajax") != -1 ? true : false;           byte[] bs = http_head.ajax_status == true ? ajax_process(http_head, str) : static_process(http_head, str);           return bs;

  下面就可以把資料提交給後端介面,進行處理。因為動態網頁處理需要網頁端和後端相互的配合,工作量較大,因此本節主要闡述靜態網頁請求的實現。

3. 資源讀取和返回

  區域網路Web請求一般是通過ip+port的模式直接存取伺服器端,所以第一個接收到的請求的url為‘/‘,這時我們需要將它映射到服務端定位的訪問首頁,目前設定為index.html,對於其它請求,url的值一般是‘/xxx/xxx.js", ‘/xxx/xxx.jpg"等,而在伺服器中讀取時我們需要定義絕對位址,所以還要在前面添加資源儲存的根地址,目前將程式當前所在檔案夾+html作為資源的根地址,而且作業系統儲存的資料路徑為\xxx\xxx.js,所以對於請求中url資料還要替換為‘\\‘(為了保證轉義符能夠轉變為路徑符,需要用‘\\‘表示實際的‘\‘),此外為了後續的http響應報文中返回正確的Content-Type欄位,還有截取‘.‘後欄位,來擷取請求檔案的類型。

              //獲得當前程式所在的檔案夾              string url_str = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;              if (string.Compare(head.url, "/") == 0)              {                 //對於首個請求127.0.0.1:3000/ 返回index.html                 url_str += "html\\index.html";                 head.type = "html";              }              else              {                 //其它請求 如/spring.js 替換為 ...\html\spring.js便於C#查詢檔案路徑                 url_str =url_str + "html\\" + head.url.Substring(1);                 url_str = url_str.Replace(‘/‘, ‘\\‘);                 int pos = url_str.IndexOf(‘.‘);                //獲得當前請求的網頁類型                 head.type = url_str.Substring(pos + 1);              }

   到此為止,完成了整個http解析的過程,包括http方法, url資源地址獲得並轉換為windows系統路徑,協議版本獲得三個部分。對於靜態網頁請求,後續就比較簡單,查詢系統路徑下資源,通過檔案流開啟,並以字元流的形式放置在記憶體中,作為http響應報文的本文部分。

             //以檔案流的方式開啟指定路徑內檔案             using (FileStream fs = new FileStream(url_str, FileMode.Open, FileAccess.Read))             {                  //StreamReader temp = new StreamReader(fs, Encoding.Default);                  int fslen = (int)fs.Length;                  byte[] fbyte = new byte[fslen];                  int r = fs.Read(fbyte, 0, fslen);                  fs.Close();                                 //......            }    

    檔案開啟成功後,我們就要產生http響應報文了,http響應報文和請求報文相同,也由三部分構成。

  狀態代碼:主要為用戶端提供一種理解交易處理結果的便捷方式。主要實現的有:

  HTTP/1.1 200 OK 請求沒有問題,實體的主體部分包含請求的資源

      HTTP/1.1 400 Bad Request 通知用戶端它發送了一個錯誤的請求

  HTTP/1.1 401 Unauthorized 與適當的首部一同返回,通知用戶端進行相應的認證

      HTTP/1.1 404 No Found 說明伺服器無法找到請求的URL

 響應首部:為用戶端提供額外的關於伺服器的訊息,本項目中實現比較簡單:

  Content-type:CurrentType\r\n

  Server:C# Web\r\n

  Content-Length:CurrentLength\r\n

  Connection: close

   其中Contenet-type需要根據我們上文獲得的type類型來替換,這裡闡述常見的替換規則。

   

    Content-Length欄位是http響應報文本文的長度,即我們獲得資源的總長度(上文中fslen), 最後將狀態代碼,響應首部和本文資料整合在一起通過socket發送到用戶端,就實現了靜態伺服器的全部過程。

                            string HTTP_Current_Head = HTTPServer.HTTP_OK_Head.Replace("CurrentLength", Convert.ToString(fslen));                            //根據不同url需要返回不同的首部類型 具體對比詳見http://tool.oschina.net/commons                           switch (head.type)                            {                                case "jpg":                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "application/x-jpg");                                    break;                                case "png":                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "image/png");                                    break;                                case "html":                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "text/html");                                    break;                                case "gif":                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "image/gif");                                    break;                                case "js":                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "application/x-javascript");                                    break;                                case "asp":                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "text/asp");                                    break;                                default:                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "text/html");                                    break;                            }                                                        send_str = HTTPServer.HTTP_OK_Start + HTTP_Current_Head;                            byte[] head_byte = new byte[send_str.Length];                            head_byte = Encoding.UTF8.GetBytes(send_str);                            //字串流合并,產生傳送檔案                             //之前採用的是byte[]->string, string合并, string->byte[],這種方法讀取圖片亂碼                            //因此修改為,string合并, string->byte[], byte[]合并方式,讀取圖片成功                            byte[] send_byte = new byte[send_str.Length + fbyte.Length];                            Buffer.BlockCopy(head_byte, 0, send_byte, 0, head_byte.Length);                            Buffer.BlockCopy(fbyte, 0, send_byte, head_byte.Length * sizeof(byte), fbyte.Length);                            Console.WriteLine("File Send....");                            return send_byte;

4. 伺服器測試和代碼下載

  到現在為止,一個簡單的靜態web伺服器就實現了,將希望訪問的資源檔放入當前程式檔案夾/html/下, 並將首頁定義為index.html, 點開伺服器程式,瀏覽器中輸入http://127.0.0.1:3000, 就可以查看返回的網頁。

具體程式參考:Web伺服器下載

TCP/IP協議學習(四) 基於C# Socket的Web伺服器---靜態資源處理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.