C#中使用Socket實現簡單Web服務器

上一篇博客中介紹了怎樣使用socket訪問web服務器。關鍵有兩個:html

  • 熟悉Socket編程;
  • 熟悉HTTP協議。

上一篇主要是經過socket來模擬瀏覽器向(任何)Web服務器發送(HTTP)請求,重點在瀏覽器端。本篇博客則反過來說一下怎樣使用socket來實現Web服務器,怎樣去接收、分析、處理最後回覆來自瀏覽器的HTTP請求。web

HTTP協議是瀏覽器和Web服務器都須要遵照的一種通訊規範,若是咱們編寫一個程序,正確遵照了HTTP協議,那麼理論上講,這個程序能夠具有瀏覽器、甚至Web服務器的功能。數據庫

圖1編程

如上圖1所示,Web服務器和瀏覽器之間不管是發送數據仍是接收(解析)數據均遵照了HTTP協議。能夠很肯定地講,只要咱們充分熟悉HTTP協議結構,那麼不管瀏覽器的實現仍是Web服務器的實現,均只是「簡單的」Socket程序的開發過程,除此以外,無其它神祕高深的東西。而Socket程序開發,稍微知道一點socket的有關知識,均能寫得出一個大概demo。瀏覽器

從系統架構來說,Web架構形式的系統均符合「生產者-消費者」模式(實質上,現實生活中大部分系統均屬於該模式)。瀏覽器端不斷產生數據(請求),而Web服務器端不斷處理請求,長時間持續如此。服務器

圖2架構

如上圖2所示,圖中左邊部分爲Web服務器中的「泵」結構,所謂泵,就是指它可以持續長時間循環運做。圖中右邊顯示「來自瀏覽器請求」部分即爲「生產者」,生產者不斷髮出請求,由左邊(Web服務器)不斷進行處理,最後回覆給瀏覽器。注意圖2中顯示,Web服務器中處理數據在循環體內部,換句話說,前一次HTTP請求處理結束以前,後一次HTTP請求不能開始,也就是每次請求處理均會阻塞循環的執行。這種串行處理數據的方式明顯效率不高,爲了解決該問題,咱們能夠在接收到瀏覽器端的HTTP請求後,並不立刻在當前線程中進行處理,而是開闢獨立線程來處理請求(在.NET中可使用異步編程實現)。這樣一來,請求處理並不會阻塞當前循環過程,見下圖3異步

圖3socket

如上圖3所示,接收到請求後,開闢其它線程來處理,這種並行處理數據的方式不會影響後續請求處理。ide

若是對Socket編程比較熟悉,以上所說的徹底能夠輕鬆實現(徹底按照Socket編程去作)。如今難點是,Web服務器端怎樣解析來自瀏覽器的請求數據(一串字符串文本),以及應該以怎樣的格式去回覆瀏覽器?答案就是必須充分了解HTTP協議格式。上一篇博客中已經提到過,有關HTTP協議格式請參見http://www.cnblogs.com/riky/archive/2007/04/09/705848.html。咱們必須讀懂瀏覽器發送的請求數據,並按照正確格式發送回覆。下圖4顯示瀏覽器請求數據格式:

圖4

圖中紅色部分即爲數據傳輸方式(post或get)、請求路徑(url中不含主機地址部分)以及HTTP協議版本號。下面以「鍵:值」格式的文本均爲瀏覽器發送給服務器的一系列數據信息(注意這些項可選),若是瀏覽器以post方式提交數據,那麼數據會緊跟在下面(圖中沒顯示)。Web服務器讀懂瀏覽器發送的請求數據,並處理完畢後,必須按照圖5的格式將結果回覆給瀏覽器:

圖5

如上圖5所示,最上面的以「鍵:值」的格式文本是Web服務器發送給瀏覽器的一些數據信息(這些項部分可選),緊接着,下面即是須要發送給瀏覽器的HTML文檔(若是返回的是頁面)。瀏覽器必須讀懂Web服務器發送的回覆數據,而後進行渲染(顯示)。

圖6

圖6顯示了瀏覽器發起的一次HTTP請求,顯示展現了Web服務器端處理該請求的過程。咱們能夠看到,Web服務器在一次Socket鏈接過程當中只處理一個HTTP請求。屢次HTTP請求會伴隨着Socket不斷的鏈接與斷開。

文章最後上傳一個使用Socket編寫的簡單Web服務器,可以實現如下功能:

  • 運行Web服務器後,能夠綁定端口,接收來自任何瀏覽器的HTTP請求;
  • 可以顯示一個默認首頁,如index.html;
  • 首頁提供「登陸」功能,按照Post方式傳遞數據處處理頁面「login.zsp」(後綴名可自定義);
  • Web服務器端接收接收瀏覽器發送的數據,可以解析(解析方式很隨意)出post傳遞的參數,並模擬訪問數據庫檢查登陸狀況、模擬耗時等待等;
  • Web服務器生成登陸成功後的靜態頁,回覆給瀏覽器。頁面顯示登陸名和當前時間。

整個demo徹底就是一個Socket程序,只是增長了「HTTP協議」的環節,服務器端不管是接收(解析)數據仍是發送數據,均須要遵照HTTP協議。Web服務器中最終的請求處理泵代碼以下:

 1         static Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //偵聽socket
 2         static void Main(string[] args)
 3         {
 4             _socket.Bind(new IPEndPoint(IPAddress.Any, 8081));
 5             _socket.Listen(100);
 6             _socket.BeginAccept(new AsyncCallback(OnAccept), _socket);  //開始接收來自瀏覽器的http請求(實際上是socket鏈接請求)
 7             Console.Read();
 8         }
 9         static void OnAccept(IAsyncResult ar)
10         {
11             try
12             {
13                 Socket socket = ar.AsyncState as Socket;
14                 Socket new_client = socket.EndAccept(ar);  //接收到來自瀏覽器的代理socket
15                 //NO.1  並行處理http請求
16                 socket.BeginAccept(new AsyncCallback(OnAccept), socket); //開始下一次http請求接收   (此行代碼放在NO.2處時,就是串行處理http請求,前一次處理過程會阻塞下一次請求處理)
17 
18                 byte[] recv_buffer = new byte[1024 * 640];
19                 int real_recv = new_client.Receive(recv_buffer);  //接收瀏覽器的請求數據
20                 string recv_request = Encoding.UTF8.GetString(recv_buffer, 0, real_recv);
21                 Console.WriteLine(recv_request);  //將請求顯示到界面
22 
23                 Resolve(recv_request,new_client);  //解析、路由、處理
24 
25                 //NO.2  串行處理http請求
26             }
27             catch
28             {
29 
30             }
31         }
View Code

注意以上代碼中的NO.1和NO.2處,socket.BeginAccept()方法放在NO.1處時,服務器端會並行處理請求,而放在NO.2處時,服務器會串行處理請求。讀者能夠每種方式都試一下,在串行處理請求時,請求處理過程會阻塞後續請求的處理(好比登陸耗時10秒鐘,其它人沒法訪問網站)。

如下是demo效果圖:

圖7:Web服務器運行後,瀏覽器訪問首頁:

圖7

圖8:瀏覽器中首頁顯示(包含登陸框):

圖8

圖9:用戶點擊「登陸」按鈕,以Post方式提交數據,Web服務器解析、處理,返回新頁面:

圖9

文章有點長,部分截圖還失真了(部分圖之前整理的,沒有找到大圖,因此就湊合看:))

源碼下載:http://files.cnblogs.com/xiaozhi_5638/socket_webServer.rar

相關文章
相關標籤/搜索