天天看点

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