首页 > 代码库 > 《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服务