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端口上——後面的内容将需要在客戶機中使用這些設定