前言:
經過前面的專題中對網路層協議和HTTP協議的簡單介紹相信大家對網路中的協議有了大致的瞭解的, 本專題將針對HTTP協議定義一個Web伺服器,我們平常瀏覽網頁通過在瀏覽器中輸入一個網址就可以看到我們想要的網頁,這個過程中瀏覽器只是一個用戶端,瀏覽器(應用程式層應用程式)通過HTTP協議把使用者請求發送到服務端, 伺服器接受到發送來的HTTP請求,然後對請求進行處理和響應,最後把響應的內容發送給用戶端(瀏覽器這裡充當了使用者代理程式的用戶端),瀏覽器再對接受到的響應內容(一般是HTML檔案)進行解釋並且顯示出來。這就是一次完整的使用者請求/響應模型,本專題所講述的是一個簡單的Web伺服器,其他一些大型的Web伺服器(IIS,Apache)也是這樣的一個原理, 本專題只是簡單講述Web伺服器的實現原理。
一、Socket編程實現一個簡單的Web伺服器
Socket這個概念是在Unix系統中提出來的。在Unix的時代,為瞭解決傳輸層的編程問題,Unix提供了類似於檔案操作的網路操作方式——Socket,通過Socket,我們就可以像操作檔案一樣通過開啟、寫入、讀取、關閉等操作完成網路編程,這樣就使得網路編程可以統一到檔案操作方面,這樣就使我們更容易地編寫網路應用程式。需要注意的是,應用程式層的協議需要網路程式專門處理,Socket不負責應用程式層協議,僅僅負責傳輸層的協議。
現在介紹下網路連接埠號碼(port)的概念,在同一個網路地址中,為了區分使用相同協議的不同應用程式,為不同的應用程式分配一個數字編號,我們把這個編號就成為網路連接埠號碼(就是區分同一個網路地址中不同的進程)。連接埠號碼是由一個兩個位元組的整數,所以取值範圍為0~65535,這些連接埠號碼又分為三類:
- 第一類的範圍是0~1023,稱為眾所周知的連接埠,這些連接埠號碼由特定的網路程式使用,例如,TCP協議使用80連接埠來完成Http協議的傳輸。
- 第二類的範圍是1024~49151,稱為登記連接埠,一般情況下不應該在程式中使用。
- 第三類的範圍是49152~65535,稱為私人連接埠, 這些連接埠可以由普通使用者程式使用。
在我們用Socket開發網路應用程式中,還有一個就是端點的概念,在網路中,通過IP地址,協議和連接埠號碼可以唯一地確定網路上的一個應用程式,其中把IP地址和連接埠的組合叫做端點(EndPoint)。每個Socket需要綁定到一個端點上與其他端點進行通訊。
介紹完基本的一些概念後,下面示範通過Socket編程實現一個簡單的Web伺服器,此執行個體中就是簡單向瀏覽器返回一個固定的靜態頁面,實現代碼如下:
using System;using System.Net;using System.Net.Sockets;using System.Text;namespace WebServer{ /// <summary> /// 實現一個簡單的Web伺服器 /// 該伺服器向請求的瀏覽器返回一個靜態HTML頁面 /// </summary> class Program { static void Main(string[] args) { // 獲得原生Ip地址,即127.0.0.1 IPAddress localaddress =IPAddress.Loopback; // 建立可以訪問的斷點,49155表示連接埠號碼,如果這裡設定為0,表示使用一個由系統分配的閒置連接埠號碼 IPEndPoint endpoint = new IPEndPoint(localaddress,49155); // 建立Socket對象,使用IPv4地址,資料通訊類型為資料流,傳輸控制通訊協定TCP協議. Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); //將Socket綁定到斷點上 socket.Bind(endpoint); // 設定串連隊列的長度 socket.Listen(10); while (true) { Console.WriteLine("Wait an connect Request..."); // 開始監聽,這個方法會堵塞線程的執行,直到接受到一個用戶端的串連請求 Socket clientsocket =socket.Accept(); // 輸出用戶端的地址 Console.WriteLine("Client Address is: {0}", clientsocket.RemoteEndPoint); // 把用戶端的請求資料讀入儲存到一個數組中 byte[] buffer =new byte[2048]; int receivelength = clientsocket.Receive(buffer, 2048, SocketFlags.None); string requeststring = Encoding.UTF8.GetString(buffer, 0, receivelength); // 在伺服器端輸出請求的訊息 Console.WriteLine(requeststring); // 伺服器端做出相應內容 // 響應的狀態行 string statusLine ="HTTP/1.1 200 OK\r\n"; byte[] responseStatusLineBytes = Encoding.UTF8.GetBytes(statusLine); string responseBody = "<html><head><title>Default Page</title></head><body><p style='font:bold;font-size:24pt'>Welcome you</p></body></html>"; string responseHeader = string.Format( "Content-Type: text/html; charset=UTf-8\r\nContent-Length: {0}\r\n",responseBody.Length); byte[] responseHeaderBytes = Encoding.UTF8.GetBytes(responseHeader); byte[] responseBodyBytes = Encoding.UTF8.GetBytes(responseBody); // 向用戶端發送狀態行 clientsocket.Send(responseStatusLineBytes); // 向用戶端發送回應頭資訊 clientsocket.Send(responseHeaderBytes); // 發送頭部和內容的空行 clientsocket.Send(new byte[] { 13, 10 }); // 想用戶端發送主體部分 clientsocket.Send(responseBodyBytes); // 中斷連線 clientsocket.Close(); Console.ReadKey(); break; } // 關閉伺服器 socket.Close(); } }}
運行結果:
首先運行服務端後的介面:
在瀏覽器中輸入http://localhost:49155/ 則瀏覽器可以看到如下的所示的結果:
此時在伺服器端顯示的輸出為:
這裡只是簡單實現了一個web伺服器的功能,當然實際的Web伺服器通過使用者的發來的Http請求中獲得請求檔案類型,請求檔案名稱以及請求目錄等資訊,然後Web伺服器根據這些請求資訊從伺服器的物理目錄中尋找請求的檔案,如果在伺服器中找到請求的檔案,然後伺服器把響應內容發送給用戶端。這裡只是通過這個簡單的Web伺服器讓大家理解請求/響應模型以及Web伺服器的工作原理,一些複雜的Web伺服器也是在此基礎進行一些其他功能的擴充。
二、基於TcpListener的Web伺服器
在.net平台下, 為了簡化網路編程,.net對通訊端又進行了一次封裝,封裝後的類是在System.Net.Sockets命名空間下的TcpListener類和TcpClient類,使用TcpListener類用來監聽和接收傳入的串連請求,在該類的建構函式中只需要傳遞一組網路端點資訊就可以準備好監聽參數,而不需要設定使用的網路通訊協定等細節,調用Start方法後,監聽工作就開始(間接調用了Socket.Listen方法),AcceptTcpClient方法將阻塞進程,直到一個用戶端發來串連請求為止,這個方法返回一個
封裝了Socket的TcpClient對象,同時從傳入的串連隊列中刪除該用戶端的串連請求。此時通過這個TcpClient對象與用戶端進行通訊。
下面是基於TcpListener和TcpClient的一個簡單的Web伺服器的代碼:
using System;using System.Net;using System.Net.Sockets;using System.Text;namespace TcpWebserver{ class Program { static void Main(string[] args) { // 獲得原生Ip地址,即127.0.0.1 IPAddress localaddress =IPAddress.Loopback; // 建立可以訪問的斷點,49155表示連接埠號碼,如果這裡設定為0,表示使用一個由系統分配的閒置連接埠號碼 IPEndPoint endpoint = new IPEndPoint(localaddress, 49155); // 建立Tcp 監聽器 TcpListener tcpListener = new TcpListener(endpoint); // 啟動監聽 tcpListener.Start(); Console.WriteLine("Wait an connect Request..."); while (true) { // 等待客戶串連 TcpClient client =tcpListener.AcceptTcpClient(); if (client.Connected == true) { // 輸出已經建立串連 Console.WriteLine("Created connection"); } // 獲得一個網路流對象 // 該網路流對象封裝了Socket的輸入和輸出操作 // 此時通過對網路流對象進行寫入來返迴響應訊息 // 通過對網路流對象進行讀取來獲得請求訊息 NetworkStream netstream = client.GetStream(); // 把用戶端的請求資料讀入儲存到一個數組中 byte[] buffer = new byte[2048]; int receivelength = netstream.Read(buffer, 0, 2048); string requeststring = Encoding.UTF8.GetString(buffer, 0, receivelength); // 在伺服器端輸出請求的訊息 Console.WriteLine(requeststring); // 伺服器端做出相應內容 // 響應的狀態行 string statusLine = "HTTP/1.1 200 OK\r\n"; byte[] responseStatusLineBytes = Encoding.UTF8.GetBytes(statusLine); string responseBody = "<html><head><title>Default Page</title></head><body><p style='font:bold;font-size:24pt'>Welcome you</p></body></html>"; string responseHeader = string.Format( "Content-Type: text/html; charset=UTf-8\r\nContent-Length: {0}\r\n", responseBody.Length); byte[] responseHeaderBytes = Encoding.UTF8.GetBytes(responseHeader); byte[] responseBodyBytes = Encoding.UTF8.GetBytes(responseBody); // 寫入狀態行資訊 netstream.Write(responseStatusLineBytes, 0, responseStatusLineBytes.Length); // 寫入回應的頭部 netstream.Write(responseHeaderBytes, 0, responseHeaderBytes.Length); // 寫入回應頭部和內容之間的空行 netstream.Write(new byte[] { 13, 10 }, 0, 2); // 寫入回應的內容 netstream.Write(responseBodyBytes, 0, responseBodyBytes.Length); // 關閉與用戶端的串連 client.Close(); Console.ReadKey(); break; } // 關閉伺服器 tcpListener.Stop(); } }}
程式的輸出結果和前面的用Socket實現的效果相同,這裡就不再貼圖了,這裡實現的Web伺服器都是建立控制台的應用程式來實現的,感興趣的朋友也可以用Windows表單進行實現,同時這裡也只是簡單列出了採用同步的方式進行實現的,同時TcpListener類和TcpClient類同時支援非同步作業的方法,下面列出這個兩個類中非同步作業的方法如下表:
類 |
方法 |
說明 |
TcpListener |
BeginAcceptTcpClient |
開始一個非同步作業接受一個傳入的串連 |
EndAcceptTcpClient |
非同步接受傳入的串連,並建立新的TcpClient對象來處理用戶端的通訊 |
TcpClient |
BeginConnect |
開始一個對遠程主機串連的非同步請求 |
EndConnect |
非同步接受傳入的串連嘗試。 |
如果想瞭解線程同步和非同步朋友可以參考我的多執行緒系列:http://www.cnblogs.com/zhili/archive/2012/07/21/ThreadsSynchronous.html
三、總結
到這裡這篇文章就差不多介紹到這裡了,本專題是介紹如何自訂一個簡單Web伺服器,通過這個專題希望大家可以對Web伺服器的工作過程有一個簡單的瞭解。
另外在這個專題裡面我們是用IE瀏覽器進行發送客戶請求的,所以後面專題將介紹自訂一個瀏覽器,通過我們自訂的瀏覽器來對Web伺服器發送請求,然後在自己自訂的瀏覽器中把響應訊息顯示出來。