前言:經過前面的專題中對網絡層協定和HTTP協定的簡單介紹相信大家對網絡中的協定有了大緻的了解的, 本專題将針對HTTP協定定義一個Web伺服器,我們平常浏覽網頁通過在浏覽器中輸入一個網址就可以看到我們想要的網頁,這個過程中浏覽器隻是一個用戶端,浏覽器(應用層應用程式)通過HTTP協定把使用者請求發送到服務端, 伺服器接受到發送來的HTTP請求,然後對請求進行處理和響應,最後把響應的内容發送給用戶端(浏覽器這裡充當了使用者代理的用戶端),浏覽器再對接受到的響應内容(一般是HTML檔案)進行解釋并且顯示出來。這就是一次完整的使用者請求/響應模型,本專題所講述的是一個簡單的Web伺服器,其他一些大型的Web伺服器(IIS,Apache)也是這樣的一個原理, 本專題隻是簡單講述Web伺服器的實作原理。
一、Socket程式設計實作一個簡單的Web伺服器
Socket這個概念是在Unix系統中提出來的。在Unix的時代,為了解決傳輸層的程式設計問題,Unix提供了類似于檔案操作的網絡操作方式——Socket,通過Socket,我們就可以像操作檔案一樣通過打開、寫入、讀取、關閉等操作完成網絡程式設計,這樣就使得網絡程式設計可以統一到檔案操作方面,這樣就使我們更容易地編寫網絡應用程式。需要注意的是,應用層的協定需要網絡程式專門處理,Socket不負責應用層協定,僅僅負責傳輸層的協定。
現在介紹下網絡端口号(port)的概念,在同一個網絡位址中,為了區分使用相同協定的不同應用程式,為不同的應用程式配置設定一個數字編号,我們把這個編号就成為網絡端口号(就是區分同一個網絡位址中不同的程序)。端口号是由一個兩個位元組的整數,是以取值範圍為0~65535,這些端口号又分為三類:
1.第一類的範圍是0~1023,稱為衆所周知的端口,這些端口号由特定的網絡程式使用,例如,TCP協定使用80端口來完成Http協定的傳輸。
2.第二類的範圍是1024~49151,稱為登記端口,一般情況下不應該在程式中使用。
3.第三類的範圍是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類同時支援異步操作的方法,下面列出這個兩個類中異步操作的方法如下表:
三、總結
到這裡這篇文章就差不多介紹到這裡了,本專題是介紹如何自定義一個簡單Web伺服器,通過這個專題希望大家可以對Web伺服器的工作過程有一個簡單的了解。
另外在這個專題裡面我們是用IE浏覽器進行發送客戶請求的,是以後面專題将介紹自定義一個浏覽器,通過我們自定義的浏覽器來對Web伺服器發送請求,然後在自己自定義的浏覽器中把響應消息顯示出來。
原文連結:http://www.cnblogs.com/zhili/archive/2012/08/23/WebServer.html