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端口上——后面的内容将需要在客户机中使用这些设置