官术网_书友最值得收藏!

1.2 最簡單的Web服務器

1.2.1 網絡插座Socket

在遙遠的Unix時代,為了解決傳輸層的編程問題,從4.2 BSD Unix開始,Unix提供了類似于文件操作的網絡操作方式-Socket。通過Socket,程序員可以像操作文件一樣通過打開、寫入、讀取、關閉等操作完成網絡編程。這使得網絡編程可以統(tǒng)一到文件操作之下。通過Socket幫助程序員解決網絡傳輸層的問題,而系統(tǒng)中的網絡系統(tǒng)負責處理網絡內部的復雜操作,這樣程序員就可以比較容易地編寫網絡應用程序。需要注意的是,應用層的協(xié)議需要針對網絡程序專門處理,Socket不負責應用層的協(xié)議,僅僅負責傳輸層的協(xié)議。

當然,網絡畢竟不是簡單的文件,所以,在使用Socket的時候,程序員還是需要設置一些網絡相關的細節(jié)問題參數(shù)。

當通過Socket開發(fā)網絡應用程序的時候,首先需要考慮所使用的網絡類型,主要包括以下三個方面:

1)Socket類型,使用網絡協(xié)議的類別,IPv4的類型為PF_INET。

2)數(shù)據(jù)通信的類型,常見的數(shù)據(jù)報(SOCK_DGRAM )、數(shù)據(jù)流(SOCK_STREAM)。

3)使用的網絡協(xié)議,比如:TCP協(xié)議。

在同一個網絡地址上,為了區(qū)分使用相同協(xié)議的不同應用程序,可以為不同的應用程序分配一個數(shù)字編號,這個編號稱為網絡端口號(port)。端口號是一個兩字節(jié)的整數(shù),取值范圍從0~65535。 IANA (Internet Assigned Number Authority,互聯(lián)網地址分配機構)維護了一個端口分配列表,這些端口分為三類,第一類的范圍是0~1023,稱為眾所周知的端口,由IANA進行控制和分配,由特定的網絡程序使用,例如,TCP協(xié)議使用80號端口來完成HTTP協(xié)議的傳輸。第二類的范圍是1024~49151,稱為登記端口,這些端口不由IANA控制,但是IANA維護了一個登記的列表,如果沒有在IANA登記的話,也不應該在程序中使用。但是,大多數(shù)的系統(tǒng)中,在沒有沖突的情況下,也可以由用戶程序使用。第三類的范圍是49152~65535,稱為動態(tài)或者私有端口,這些端口可以由普通用戶程序使用。

對于一個網絡應用程序來說,通過地址、協(xié)議和端口號可以唯一地確定網絡上的一個應用程序。其中地址和端口的組合稱為端點(EndPoint)。每個Socket需要綁定到一個端點上與其他端點進行通信。

在.NET中,System.Net命名空間提供了網絡編程的大多數(shù)數(shù)據(jù)類型以及常用操作,其中常用的類型如下:

□IPAddress類用來表示一個IP地址。

□IPEndPoint類用來表示一個IP地址和一個端口號的組合,稱為網絡的端點。

□System.Net.Sockets命名空間中提供了基于Socket編程的數(shù)據(jù)類型。

□Socket類封裝了Socket的操作。

常用操作如下:

□Listen:設置基于連接通信的Socket 進入監(jiān)聽狀態(tài),并設置等待隊列的長度。

□Accept:等待一個新的連接,當新連接到達的時候,返回一個針對新連接的Socket對象。通過這個新的Socket對象,可以與新連接通信。

□Receive:通過Socket接受字節(jié)數(shù)據(jù),保存到一個字節(jié)數(shù)組中,返回實際接收的字節(jié)數(shù)。

□Send:通過Socket發(fā)送預先保存在字節(jié)數(shù)組中的數(shù)據(jù)。

下面的示例代碼演示了如何通過Socket編程創(chuàng)建一個簡單的Web服務器。這個服務器通過49152 號端口提供訪問,向瀏覽器返回一個固定的靜態(tài)網頁。在這個示例中,請求的消息由瀏覽器生成,并發(fā)送到服務器,這個程序將簡單地顯示請求的消息。回應的消息由服務器程序生成,通過Socket傳輸層返回給瀏覽器。

// 取得本機的loopback網絡地址,即 127.0.0.1
IPAddress address = IPAddress.Loopback;
// 創(chuàng)建可以訪問的端點,49152表示端口號,如果設置為0,表示使用一個空閑的端口號
IPEndPoint endPoint = new IPEndPoint(address, 49152);
// 創(chuàng)建socket,使用IPv4地址,傳輸控制協(xié)議 TCP,雙向、可靠、基于連接的字節(jié)流
Socket socket = new Socket(
   AddressFamily.InterNetwork,
   SocketType.Stream,
   ProtocolType.Tcp);
// 將socket 綁定到一個端點上
socket.Bind(endPoint);
//設置連接隊列的長度
socket.Listen(10);
Console.WriteLine("開始監(jiān)聽, 端口號:{0}.", endPoint.Port);
while (true)
{
  // 開始監(jiān)聽,這個方法會阻塞線程的執(zhí)行,直到接收到一個客戶端的連接請求
  Socket client = socket.Accept();
  // 輸出客戶端的地址
  Console.WriteLine(client.RemoteEndPoint);
  // 準備讀取客戶端請求的數(shù)據(jù),讀取的數(shù)據(jù)將保存在一個數(shù)組中
  byte[]buffer = new byte[4096];
  // 接收數(shù)據(jù)
  int length = client.Receive(buffer, 4096, SocketFlags.None);
  // 將請求的數(shù)據(jù)翻譯為 UTF-8
  System.Text.Encoding utf8 = System.Text.Encoding.UTF8;
  string requestString = utf8.GetString(buffer, 0, length);
  // 顯示請求的消息
  Console.WriteLine(requestString);
  // 回應的狀態(tài)行
  string statusLine = "HTTP/1.1 200 OK\r\n";
  byte[]statusLineBytes = utf8.GetBytes(statusLine);
  // 準備發(fā)送到客戶端的網頁
  string responseBody ="<html><head><title>From Socket Server</title></
     head><body><h1>Hello,world.</h1></body></html>";
  byte[]responseBodyBytes = utf8.GetBytes(responseBody);
  // 回應的頭部
  string responseHeader =
      string.Format(
          "Content-Type: text/html;
          charset = UTF-8\r\nContent - Length:{0}\r\n",
          responseBody.Length
         );
  byte[]responseHeaderBytes = utf8.GetBytes(responseHeader);
  // 向客戶端發(fā)送狀態(tài)信息
  client.Send(statusLineBytes);
  // 向客戶端發(fā)送回應頭
  client.Send(responseHeaderBytes);
  //頭部與內容的分隔行
  client.Send(new byte[]{13, 10});
  // 向客戶端發(fā)送內容部分
  client.Send(responseHeaderBytes);
  // 斷開與客戶端的連接
  client.Close();
  if (Console.KeyAvailable)
        break;
}
//關閉服務器
socket.Close();

運行后,在瀏覽器的窗口中輸入服務器的地址:http://localhost.:49152/,則瀏覽器中可以看到如圖1-5所示的結果。

圖1-5 通過瀏覽器訪問自定義的Web服務器

在命令行窗口可以看到如圖1-6所示的輸出。

圖1-6 自定義Web服務器的輸出

主站蜘蛛池模板: 盐边县| 五莲县| 鸡泽县| 柳河县| 禹城市| 安新县| 合作市| 吉木萨尔县| 图片| 洞头县| 曲松县| 蓬莱市| 东丰县| 石屏县| 河津市| 普安县| 西贡区| 孙吴县| 平塘县| 鄂尔多斯市| 余庆县| 米泉市| 本溪| 屯门区| 新安县| 永兴县| 拉萨市| 嘉善县| 当涂县| 成武县| 香港| 漳州市| 太湖县| 西乌珠穆沁旗| 内乡县| 富裕县| 贡山| 克拉玛依市| 察哈| 济阳县| 海林市|