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


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

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

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

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

圖3

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

如果對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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM