天天看點

C#GJBC-32.4.1使用套接字類庫

32.4.1  使用套接字的類庫
 
可以在服務中建立任何功能,例如掃描檔案、進行備份或病毒檢查,或者啟動
 .NET Remoting
 伺服器。但所有的服務程式總是有一些類似的地方。這種程式必須能啟動
 (
 并傳回給調用者
 )
 ,能停止和暫停。下面讨論用套接字伺服器實作的程式。
 

 對于Windows 2000 或Windows XP系統,Simple TCP/IP Services可以安裝為Windows元件的一個組成部分。Simple TCP/IP Services的一個部分是"quote of the day" TCP/IP伺服器,它的縮寫是“qotd”。這個簡單的服務在端口17處監聽,并使用檔案<windir>/system32/drivers /etc/quotes中的随機消息響應每一個請求。我們将在這裡建立一個相似的伺服器,它傳回一個Unicode字元串,而不是象“qotd”伺服器那樣傳回ASCII代碼。

 
首先建立一個類庫
 QuoteServer
 ,執行伺服器的代碼。下面詳細解釋
 QuoteServer.cs
 檔案中
 QuoteServer
 類的源代碼:
 

 using System;

 

 using System.IO;

 

 using System.Threading;

 

 using System.Net;

 

 using System.Net.Sockets;

 

 using System.Text;

 

 using System.Collections.Specialized;

 

  

 

 namespace Wrox.ProCSharp.WinServices

 

 {

 

    public class QuoteServer

 

    {

 

       private TcpListener listener;

 

       private int port;

 

       private string filename;

 

       private StringCollection quotes;

 

       private Random random;

 

       private Thread listenerThread;

 
重載
 QuoteServer()
 構造函數,把檔案名和端口傳遞給調用程式。隻接收檔案名的構造函數使用伺服器的
 7890
 預設端口,預設的構造函數把引用的預設檔案名定義為
 quotes.txt
 :
 
      public QuoteServer() : this ("quotes.txt")

 

       {

 

       }

 

       public QuoteServer(string filename) : this(filename, 7890)

 

       {

 

       }

 

       public QuoteServer(string filename, int port)

 

       {

 

          this.filename = filename;

 

          this.port = port;

 

       }

 
ReadQuotes()
 是一個幫助方法,它從構造函數指定的檔案中讀取所有的引用,所有的引用都被添加給
 StringCollection
 。此外,建立
 Random
 類的一個執行個體,用于傳回随機的引用:
 

       protected void ReadQuotes()

 

       {

 

          quotes = new StringCollection();

 

          Stream stream = File.OpenRead(filename);

 

          StreamReader streamReader = new StreamReader(stream);

 

          string quote;

 

          while ((quote = streamReader.ReadLine()) != null)

 

          {

 

             quotes.Add(quote);

 

          }

 

          streamReader.Close();

 

          stream.Close();

 

          random = new Random();

 

       }

 
另一個幫助方法是
 GetRandomQuoteOfTheDay()
 ,它傳回
 StringCollection
 引用的一個随機引用:
 

       protected string GetRandomQuoteOfTheDay()

 

       {

 

          int index = random.Next(0, quotes.Count);

 

          return quotes[index];

 

       }

 

 在Start()方法中,使用幫助函數ReadQuotes(),在StringCollection中讀取包含引用的完整檔案。在新的線程打開之後,它立即調用Listener()方法。這類似于第31章的TcpReceive示例。

 

 這裡使用了線程,因為Start()方法不能停下來等待客戶,它必須立即傳回給調用者(即SCM)。如果方法沒有及時傳回給調用者(30秒),SCM就假定啟動失敗:

 

       public void Start()

 

       {

 

          ReadQuotes();

 

          listenerThread = new Thread(

 

             new ThreadStart(this.Listener));

 

          listenerThread.Start();

 

       }

 
線程函數
 Listener()
 建立一個
 TcpListener
 執行個體。在
 AcceptSocket()
 方法中,我們等待客戶進行連接配接。客戶一連接配接,
 AcceptSocket()
 就傳回一個與客戶相關聯的套接字。我們使用
 socket.Send()
 ,調用
 GetRandom QuoteOfTheDay()
 把傳回的随機引用發送給客戶:
 

       protected void Listener()

 

       {

 

          try

 

          {

 

             IPAddress ipAddress = Dns.Resolve("localhost").AddressList[0];

 

             listener = new TcpListener(ipAddress, port);

 

             listener.Start();

 

             while (true)

 

             {

 

                Socket socket = listener.AcceptSocket();

 

                string message = GetRandomQuoteOfTheDay();

 

                UnicodeEncoding encoder = new UnicodeEncoding();

 

                byte[] buffer = encoder.GetBytes(message);

 

                socket.Send(buffer, buffer.Length, 0);

 

                socket.Close();

 

             }

 

          }

 

          catch (SocketException e)

 

          {

 

             Console.WriteLine(e.Message);

 

          }

 

       }

 
除了
 Start()
 方法之外,還需要有其他的方法來控制服務:
 Stop()
 、
 Suspend()
 和
 Resume()
 :
 

       public void Stop()

 

       {

 

          listener.Stop();

 

       }

 

       public void Suspend()

 

       {

 

          listenerThread.Suspend();

 

       }

 

       public void Resume()

 

       {

 

          listenerThread.Resume();

 

       }

 
另一個公共方法是
 RefreshQuotes()
 。如果包含引用的檔案發生了變化,就要使用這個方法重新讀取檔案:
 

       public void RefreshQuotes()

 

       {

 

          ReadQuotes();  

 

       }

 

    }

 

 }

 

 在伺服器上建立服務之前,首先應該建立一個測試程式,這個測試程式要建立QuoteServer的一個執行個體,并調用Start()。這樣,不需要處理與具體服務相關的問題,就能夠測試服務的功能。測試伺服器必須手動啟動,使用調試程式,很容易調試代碼。

 

 測試程式是一個C#控制台應用程式TestQuoteServer,我們必須引用QuoteServer類的程式集。包含引用的檔案必須複制到c:/ProCSharp/Winservices目錄中(或者必須在構造函數中改動參數,以指定在什麼地方複制檔案)。在調用構造函數之後,就調用QuoteServer執行個體的Start()方法。Start()在建立線程之後立即傳回,是以,在按下Return按鈕之前,控制台應用程式一直處于運作狀态。

 

       static void Main(string[] args)

 

       {

 

          QuoteServer qs = new QuoteServer(@"c:/ProCSharp/Services/quotes.txt",

 

                                       4567);

 

          qs.Start();

 

          Console.WriteLine("Hit return to exit");

 

          Console.ReadLine();

 

          qs.Stop();

 

       }

 

 注意,QuoteServer将運作在使用這個程式的本地主機4567端口上——後面的内容将需要在客戶機中使用這些設定