首页 > 代码库 > 《C#高级编程》读书笔记(十九):Windows服务

《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         #endregion114 115         public void RefreshQuotes()116         {117             ReadQuotes();118         }119 120     }121 122     [Serializable]123     public class QuoteException : Exception124     {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服务