? 前言
C# 创建 Windows 服务的方式有很多种,Topshelf 就是其中一种方式,而且使用起来比较简单。下面使用 Visual Studio Ultimate 2013 演示一下具体的使用步骤:
1. 首先,新建一个控制台应用程序,Framework 版本选择4.5,用于测试和启动 Windows 服务。
2. 打开程序包管理器控制台,安装 Topshelft 所需的 dll 文件。
1) 安装 Topshelft.dll
1. 注意:因为需要跟当前项目的 .NET Framework 版本兼容,所以需要指定安装版本为 v3.3.1。
2. 控制台输入:Install-Package Topshelf -Version 3.3.1,如图:
2) 安装 Topshelf.Log4Net.dll
1. 同样,安装与 Topshelf 相同的版本,控制台输入:Install-Package Topshelf.Log4Net -Version 3.3.1
3) 安装完成后,包含如下图的引用:
3. 因为我们准备使用 log4net 来监控 Windows 服务的运行,所以配置一下 log4net。
1) 新建一个 log4net.config 文件。
2) 编辑 log4net.config 的内容:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="Encoding" value="utf-8" />
<param name="RollingStyle" value="date"/>
<param name="File" value="Logs\"/>
<param name="DatePattern" value="yyyy-MM-dd".log""/>
<param name="StaticLogFileName" value="false"/>
<param name="MaxSizeRollBackups" value="10"/>
<param name="AppendToFile" value="true"/>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%n%-6p%d{yyyy-MM-dd HH:mm:ss}%n消息:%m"/>
</layout>
</appender>
<root name="logerror">
<level value="all" />
<appender-ref ref="RollingLogFileAppender"/>
</root>
</log4net>
</configuration>
? 关于 log4net 可参考:C# 使用 log4net 记录日志
4. Topshelf 的两种运行方式
? Topshelf 有两种运行方式,下面把两种方式都一起贴出来,代码如下:
using log4net;
using Topshelf;
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]
namespace TopshelfWindowsService
{
public class Program
{
private static ILog Logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// 获取当前进程Id。
/// </summary>
/// <returns></returns>
public static int GetProcessId()
{
return System.Diagnostics.Process.GetCurrentProcess().Id;
}
/// <summary>
/// 获取当前线程Id。
/// </summary>
/// <returns></returns>
public static int GetThreadId()
{
return System.Threading.Thread.CurrentThread.ManagedThreadId;
}
/// <summary>
/// 自定义类加定时器。
/// </summary>
public class LogTimer
{
private System.Timers.Timer _timer;
public LogTimer()
{
_timer = new System.Timers.Timer(10000);
_timer.Elapsed += (sender, eventArgs) =>
{
Logger.InfoFormat("服务正在运行,PID:{0},TID:{1}", GetProcessId(), GetThreadId());
};
}
public void Start()
{
_timer.Start();
Logger.InfoFormat("服务已开启,PID:{0},TID:{1},调用 Start() 方法", GetProcessId(), GetThreadId());
}
public void Stop()
{
_timer.Stop();
Logger.InfoFormat("服务已停止,PID:{0},TID:{1},调用 Stop() 方法", GetProcessId(), GetThreadId());
}
public void Pause()
{
//...逻辑代码
Logger.InfoFormat("服务已暂停,PID:{0},TID:{1},调用 Pause() 方法", GetProcessId(), GetThreadId()); ;
}
public void Continue()
{
//...逻辑代码
Logger.InfoFormat("服务继续运行,PID:{0},TID:{1},调用 Continue() 方法", GetProcessId(), GetThreadId());
}
public void Shutdown()
{
//...逻辑代码
Logger.InfoFormat("服务已关闭,PID:{0},TID:{1},调用 Shutdown() 方法", GetProcessId(), GetThreadId());
}
}
/// <summary>
/// 自定义类加定时器,并实现 ServiceControl,ServiceSuspend,ServiceShutdown 接口。
/// </summary>
public class LogControl : ServiceControl, ServiceSuspend, ServiceShutdown
{
private System.Timers.Timer _timer = new System.Timers.Timer(10000);
public LogControl()
{
_timer.Elapsed += (sender, eventArgs) =>
{
Logger.InfoFormat("服务正在运行,PID:{0},TID:{1}", GetProcessId(), GetThreadId());
};
}
bool ServiceControl.Start(HostControl hostControl)
{
_timer.Start();
Logger.InfoFormat("服务已开启,PID:{0},TID:{1},调用 ServiceControl.Start(HostControl hostControl) 方法", GetProcessId(), GetThreadId());
return true;
}
bool ServiceControl.Stop(HostControl hostControl)
{
_timer.Stop();
Logger.InfoFormat("服务已停止,PID:{0},TID:{1},调用 ServiceControl.Stop(HostControl hostControl) 方法", GetProcessId(), GetThreadId());
return true;
}
bool ServiceSuspend.Pause(HostControl hostControl)
{
//...逻辑代码
Logger.InfoFormat("服务已暂停,PID:{0},TID:{1},调用 ServiceControl.Pause(HostControl hostControl) 方法", GetProcessId(), GetThreadId());
return true;
}
bool ServiceSuspend.Continue(HostControl hostControl)
{
//...逻辑代码
Logger.InfoFormat("服务继续运行,PID:{0},TID:{1},调用 ServiceControl.Continue(HostControl hostControl) 方法", GetProcessId(), GetThreadId());
return true;
}
void ServiceShutdown.Shutdown(HostControl hostControl)
{
//...逻辑代码
Logger.InfoFormat("服务已关闭,PID:{0},TID:{1},调用 ServiceShutdown.Shutdown(HostControl hostControl) 方法", GetProcessId(), GetThreadId());
}
}
public static void Main()
{
//Topshelf 服务的两种运行方式
//1. 自定义类加定时器
//Logger.InfoFormat("正在准备安装日志服务1,PID:{0},TID:{1}", GetProcessId(), GetThreadId());
//HostFactory.Run(o =>
//{
// o.Service<LogTimer>(b =>
// {
// b.ConstructUsing(x => new LogTimer());
// b.WhenStarted(x => x.Start());
// b.WhenStopped(x => x.Stop());
// b.WhenPaused(x => x.Pause());
// b.WhenContinued(x => x.Continue());
// b.WhenShutdown(x => x.Shutdown());
// });
// o.RunAsLocalSystem();
// o.SetServiceName("LogTimerService");
// o.SetDisplayName("日志服务1");
// o.SetDescription("自定义类加定时器,实现日志服务");
//});
//2. 自定义类加定时器,并实现 ServiceControl,ServiceSuspend,ServiceShutdown 接口
Logger.InfoFormat("正在准备安装日志服务2,PID:{0},TID:{1}", GetProcessId(), GetThreadId());
HostFactory.Run(o =>
{
o.Service<LogControl>();
o.RunAsLocalSystem();
o.SetServiceName("LogTimerControlService");
o.SetDisplayName("日志服务2");
o.SetDescription("自定义类加定时器,并实现 ServiceControl,ServiceSuspend,ServiceShutdown 接口,实现日志服务");
});
Console.WriteLine("OK");
}
}
}
? 可以看出,一个是普通类 LogTimer,一个是实现了 ServiceControl, ServiceSuspend, ServiceShutdown 接口的类 LogControl。
? 下面分别安装这两种服务:
? 安装注意事项:
1. 所安装的服务路径中不能包含空格。
2. 命令提示符尽量使用管理员运行,否则可能报错:The LogTimerService service can only be installed as an administrator
1. 安装第一种方式(自定义类加定时器)
1) 打开命令提示符,输入:X:\xxx\xxx.exe install,如图:
2) 这样服务就安装成功了,运行 service.msc,在服务列表中可查看:
3) 右键启动该服务,或者命令行输入:X:\xxx\xxx.exe start,如图:
4) 启动服务后,等待20秒后再停止服务,将看到如下日志:
5) 这样第一种方式的服务就运行起来了,接下来卸载该服务,命令行输入:X:\xxx\xxx.exe uninstall,如图:
2. 安装第二种方式(自定义类加定时器,并实现 ServiceControl,ServiceSuspend,ServiceShutdown 接口)
1) 首先,注释第一种方式,重新生成后再安装该服务,安装成功后如图:
2) 同样,启动服务后,等待20秒后再停止服务,将看到如下日志:
3) 第二种方式的服务也运行起来了,最后卸载该服务。
4) 可见第二种方式,具有更好的可读性,且比较规范,推荐使用该方式。
5. 最后,列出 Windows 服务的常用命令
1) X:\xxx\xxx.exe install 安装服务
2) X:\xxx\xxx.exe start 启动服务
3) X:\xxx\xxx.exe stop 停止服务
4) X:\xxx\xxx.exe pause 暂停服务
5) X:\xxx\xxx.exe continue 向服务发送 CONTINUE 控制请求
6) X:\xxx\xxx.exe delete 删除服务
7) X:\xxx\xxx.exe uninstall 卸载服务
? 总结
1. 经过以上步骤,一个 Windows 服务已经可以成功运行了,但只是一个简单的示例,使用了定时器模拟执行一些任务,在实际的工作中可能远远比这个复杂。
2. 可以发现,使用定时器执行任务,似乎有些受限或完全不能满足需要。所以,还需要一个任务调度框架—Quartz.NET,让这任务调度框架运行在 Windows Service 之上,去完成一些定时的任务。
3. Windows 服务常用的应用场景:
1) 调用存储过程,完成对数据库的操作,可能包含(生成报表、同步数据、更新数据等)。
2) 定时发送邮件、短信,主动处理业务等。
3) 定时调用某接口,完成对数据的更新或写入等。