《C#高级编程》读书笔记(十九):Windows服务

Posted 逍遥king

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《C#高级编程》读书笔记(十九):Windows服务相关的知识,希望对你有一定的参考价值。

1,Windows 服务

  Windows 服务是可以在系统启动时自动打开的程序。如果需要在没有用户交互操作情况下运行程序,或者在权限比交互式用户更大的用户下运行程序,就可以创建 Windows 服务。

2,Windows 服务的体系架构

  操作 Windows 服务需要3种程序:

  • 服务程序

  • 服务控制程序

  • 服务配置程序

  服务程序本身用于提供需要的实际功能。

  服务控制程序可以把控制请求发送给服务,如开始、停止、暂停和继续。

  使用服务配置程序可以安装服务,也可以在以后改变服务的配置。

3,服务程序

  服务程序实现服务的功能。服务程序需要 3 个部分:  

  • 主函数

  • service-main 函数

  • 处理程序

  服务的主函数是程序的一般入口点,即Main()方法,它可以注册多个 service-main 函数,service-main 函数包含服务的实际功能。服务必须为所提供的每项服务注册一个 service-main 函数。

4,Windows 服务的类

  可以在System.ServiceProcess名称空间中找到实现服务的 3 部分的服务类:

   • 必须从 ServiceBase 类继承才能实现服务。ServiceBase 类用于注册服务、响应开始和停止请求。

   • ServiceController类用于实现服务控制程序。使用这个类,可以把请求发送给服务。

   • ServiceProcessInstaller类和ServiceInstaller类用于安装和配置服务程序。

5,创建Windows服务程序

  实例程序对于客户发出的每一个请求,引用服务器都返回引用文件的一个随机引用。解决方案由 3 个程序集完成,一个用户客户端,两个用于服务器。

  QuoteServer 类库包含实际的功能,QuoteClient 是 WPF 胖客户端应用程序,这个应用程序创建客户端套接字,以便与 Quote Server 进行通信。第三个程序集是实际的服务。Quote Service 开始和停止 QuoteServer,服务将控制服务器。

  QuoteServer.cs

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Diagnostics;
  4 using System.Diagnostics.Contracts;
  5 using System.IO;
  6 using System.Linq;
  7 using System.Net;
  8 using System.Net.Sockets;
  9 using System.Text;
 10 using System.Threading.Tasks;
 11 
 12 namespace QuoteService
 13 {
 14     public class QuoteServer
 15     {
 16         private TcpListener listener;
 17         private int port;
 18         private string filename;
 19         private List<string> quotes;
 20         private Random random;
 21         private Task listenerTask;
 22 
 23         public QuoteServer()
 24             :this("quotes.txt")
 25         {
 26         }
 27 
 28         public QuoteServer(string filename) 
 29             : this(filename, 7890)
 30         {
 31         }
 32 
 33         public QuoteServer(string filename, int port)
 34         {
 35             Contract.Requires<ArgumentNullException>(filename != null);
 36             Contract.Requires<ArgumentException>(port >= IPEndPoint.MinPort &&
 37                 port <= IPEndPoint.MaxPort);
 38             this.filename = filename;
 39             this.port = port;
 40         }
 41 
 42         protected void ReadQuotes()
 43         {
 44             try
 45             {
 46                 quotes = File.ReadAllLines(filename).ToList();
 47                 if (quotes.Count == 0)
 48                 {
 49                     throw new QuoteException("quote file is empty");
 50                 }
 51                 random = new Random();
 52             }
 53             catch (IOException ex)
 54             {
 55                 throw new QuoteException("I/O Error",ex);
 56             }
 57         }
 58 
 59         protected string GetRandomQuoteOfTheDay()
 60         {
 61             var index = random.Next(0, quotes.Count);
 62             return quotes[index];
 63         }
 64 
 65         protected void Listener()
 66         {
 67             try
 68             {
 69                 IPAddress ipAddress = IPAddress.Any;
 70                 listener = new TcpListener(ipAddress, port);
 71                 listener.Start();
 72                 while (true)
 73                 {
 74                     Socket clientSocket = listener.AcceptSocket();
 75                     string message = GetRandomQuoteOfTheDay();
 76                     var encoder = new UnicodeEncoding();
 77                     byte[] buffer = encoder.GetBytes(message);
 78                     clientSocket.Send(buffer, buffer.Length, 0);
 79                     clientSocket.Close();
 80                 }
 81             }
 82             catch (SocketException ex)
 83             {
 84                 Trace.TraceError($"QuoteServer {ex.Message}");
 85                 throw new QuoteException("socket error",ex);
 86             }
 87         }
 88 
 89         #region 控制
 90 
 91         public void Start()
 92         {
 93             ReadQuotes();
 94             listenerTask = Task.Factory.StartNew(Listener,
 95                 TaskCreationOptions.LongRunning);
 96         }
 97 
 98         public void Stop()
 99         {
100             listener.Stop();
101         }
102 
103         public void Suspend()
104         {
105             listener.Stop();
106         }
107 
108         public void Resume()
109         {
110             Start();
111         }
112 
113         #endregion
114 
115         public void RefreshQuotes()
116         {
117             ReadQuotes();
118         }
119 
120     }
121 
122     [Serializable]
123     public class QuoteException : Exception
124     {
125         public QuoteException() { }
126         public QuoteException(string message) : base(message) { }
127         public QuoteException(string message, Exception inner) : base(message, inner) { }
128         protected QuoteException(
129         System.Runtime.Serialization.SerializationInfo info,
130         System.Runtime.Serialization.StreamingContext context)
131           : base(info, context) { }
132     }
133 }

 创建测试程序并调用 QuoteServer 类的 Start() 方法,测试程序是一个 C#控制台应用程序TestQuoteServer,并引用QuoteServer类的程序集。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace QuoteService
{
    class Program
    {
        static void Main(string[] args)
        {
            var qs = new QuoteServer("quotes.txt", 7890);
            qs.Start();
            Console.WriteLine("Hit return to exit");
            Console.ReadLine();
            qs.Stop();
        }
    }
}

胖客户端代码:

QuoteInformation.cs

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace QuoteClient
{
    public class QuoteInformation:INotifyPropertyChanged
    {
        public QuoteInformation()
        {
            EnableRequest = true;
        }

        private string quote;

        public string Quote
        {
            get { return quote; }
            internal set { SetProperty(ref quote, value); }
        }

       
        private bool enableRequest;

        public bool EnableRequest
        {
            get { return enableRequest; }
            internal set { SetProperty(ref enableRequest, value); }
        }

        private void SetProperty<T>(ref T field, T value,[CallerMemberName] string propertyName="")
        {
            if (!EqualityComparer<T>.Default.Equals(field, value))
            {
                field = value;
                var handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this,new PropertyChangedEventArgs(propertyName));
                }
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

MainWindow.xaml

        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="111,103,0,0" TextWrapping="Wrap" Text="{Binding Quote}" VerticalAlignment="Top" Height="144" Width="277"/>
        <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="216,47,0,0" VerticalAlignment="Top" Width="75" IsEnabled="{Binding EnableRequest}" Click="OnGetQuote"/>

MainWindow.xaml.cs

using System.Net.Sockets;
using System.Text;
using System.Windows;
using System.Windows.Input;

namespace QuoteClient
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        private QuoteInformation quoteInfo = new QuoteInformation();

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = quoteInfo;
        }

        protected async void OnGetQuote(object sender, RoutedEventArgs e)
        {
            const int bufferSize = 1024;
            Cursor currentCursor = this.Cursor;
            this.Cursor = Cursors.Wait;
            quoteInfo.EnableRequest = false;

            string serverName = Properties.Settings.Default.ServerName;
            int port = Properties.Settings.Default.PortNumber;

            var client = new TcpClient();
            NetworkStream stream = null;
            try
            {
                await client.ConnectAsync(serverName, port);
                stream = client.GetStream();
                byte[] buffer = new byte[bufferSize];
                int received = await stream.ReadAsync(buffer, 0, bufferSize);
                if (received <= 0)
                {
                    return;
                }
                quoteInfo.Quote = Encoding.Unicode.GetString(buffer).Trim(\0);
            }
            catch (SocketException ex)
            {
                MessageBox.Show(ex.Message, "Error Quote of the day",
                    MessageBoxButton.OK, MessageBoxImage.Error);
                throw;
            }
            finally
            {
                if (stream != null)
                {
                    stream.Close();
                }

                if (client.Connected)
                {
                    client.Close();
                }
            }

            this.Cursor = currentCursor;
            quoteInfo.EnableRequest = true;
        }
    }
}

服务程序代码:

QuoteService.cs

using System;
using System.IO;
using System.ServiceProcess;

namespace QuoteService
{
    public partial class QuoteService : ServiceBase
    {
        private QuoteServer quoteServer;
        public QuoteService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            quoteServer = new QuoteServer(Path.Combine(
                AppDomain.CurrentDomain.BaseDirectory,"quotes.txt"));
            quoteServer.Start();
        }

        protected override void OnStop()
        {
            quoteServer.Stop();
        }

        protected override void OnPause()
        {
            quoteServer.Suspend();
        }

        protected override void OnContinue()
        {
            quoteServer.Resume();
        }

        public const int commandRefresh = 128;

        protected override void OnCustomCommand(int command)
        {
            switch (command)
            {
                case commandRefresh:
                    quoteServer.RefreshQuotes();
                    break;
                default:
                    break;
            }
        }
    }
}

Program.cs

using System.ServiceProcess;

namespace QuoteService
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        static void Main()
        {
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[]
            {
                new QuoteService()
            };
            ServiceBase.Run(ServicesToRun);
        }
    }
}

6,服务的安装

  服务类切到设计视图,右键菜单“添加安装程序”,就可以给服务添加安装程序。

以上是关于《C#高级编程》读书笔记(十九):Windows服务的主要内容,如果未能解决你的问题,请参考以下文章

《C#高级编程》读书笔记

《C#高级编程》读书笔记

《C#高级编程》读书笔记:关于数组

《C#高级编程》读书笔记:运算符

《C#高级编程》读书笔记

《C#高级编程》读书笔记:委托