在很多时候,我们需要一个定时器,当间隔某段时间或者在某一个时刻的时候,触发某个业务的处理,这个时候,我们就可能需要引入Windows服务来做这个事情,如某些数据的同步操作、某些工作任务的创建或者侦听某些端口的工作等等。
做过Windows Forms开发的人,对开发Windows服务可能会熟悉一些,其实它本身应该算是一个Windows Forms程序。基本上整个Windows服务的程序分为几个部分:安装操作实现、程序启动、服务操作等。
本例子创建一个Windows服务,服务可以在整点运行,也可以在某段间隔时间运行,通过配置指定相关的参数。
完整的服务代码请下载文件进行学习:http://files.cnblogs.com/wuhuacong/AutoSyncService.rar
1.安装操作类的实现
首先需要继承System.Configuration.Install.Installer类,并且需要增加 ServiceProcessInstaller、ServiceInstaller两个对象来处理,另外您需要重载BeforeUninstall 和 AfterInstall 来实现服务在安装前后的启动和停止操作。
[RunInstaller( true )]
public class ListenInstaller : Installer
{
private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller;
private System.ServiceProcess.ServiceInstaller serviceInstaller;
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.IContainer components = null ;
public ListenInstaller()
{
InitializeComponent();
// 重新覆盖设计时赋予的服务名称
this .serviceInstaller.DisplayName = Constants.ServiceName;
this .serviceInstaller.ServiceName = Constants.ServiceName;
}
public override void Install(System.Collections.IDictionary stateSaver)
{
base .Install(stateSaver);
}
private void serviceInstaller_AfterInstall( object sender, InstallEventArgs e)
{
ServiceController service = new ServiceController(Constants.ServiceName);
if (service.Status != ServiceControllerStatus.Running)
{
try
{
service.Start();
}
catch (Exception ex)
{
EventLog loger;
loger = new EventLog();
loger.Log = " Application " ;
loger.Source = Constants.ServiceName;
loger.WriteEntry(ex.Message + " /n " + ex.StackTrace, EventLogEntryType.Error);
}
}
}
private void serviceInstaller_BeforeUninstall( object sender, InstallEventArgs e)
{
ServiceController service = new ServiceController(Constants.ServiceName);
if (service.Status != ServiceControllerStatus.Stopped)
{
try
{
service.Stop();
}
catch (Exception ex)
{
EventLog loger;
loger = new EventLog();
loger.Log = " Application " ;
loger.Source = Constants.ServiceName;
loger.WriteEntry(ex.Message + " /n " + ex.StackTrace, EventLogEntryType.Error);
}
}
}
...............
}
2.程序启动
程序的启动很简单,基本上是自动创建服务程序的时候就生成了,这里列出来解析是为了说明服务调试的操作。
程序的启动是在Main函数里面,添加下面的代码即可
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new SocketService() };
ServiceBase.Run(ServicesToRun);
上面是标准的启动代码,但很多时候,我们需要调试服务,因此会加入一个跳转的开关
// 使用方法:在该Project的属性页,设置输入参数"-T",即可进入下面这段代码,发布时请去掉参数;
if (args.Length >= 1 && args[ 0 ].ToUpper() == " -T " )
{
try
{
SocketService service = new SocketService();
service.Execute();
}
catch (Exception ex)
{
throw ex;
}
return ;
}
#endregion
上面的操作就是为了可以使用普通的调试功能调试Windows服务,其中的"-T"是在开发工具VS的IDE上设置的一个参数, 如下图所示。
3.服务操作
首先需要创建一个集成自System.ServiceProcess.ServiceBase的服务类,如SocketService服务类,在SocketService类的构造函数中,您可能需要初始化一些信息,如创建一个定时器,修改服务器 类的名称,读取配置参数等信息,以便初始化服务类的参数。
接着您需要重载服务基类的一些函数:OnStart、OnStop、OnContinue、OnPause、OnShutdown和定时器的触发函数timerReAlarm_Elapsed。完整的类如下:
{
private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private AppConfig appConfig = new AppConfig();
private System.Timers.Timer timerReAlarm;
private int ServiceCycle = 1 ; // 服务运行间隔,和整点运行相斥(单位分钟)
private int CycleCount = 0 ; // 间隔的服务运行计数(单位分钟)
private int ServiceRunAt = 0 ; // 整点运行服务时间,负数为禁用(单位小时)
private bool RunAtOnce = false ; // 整点运行服务是否已经运行
private bool GBLService = false ; // 是否启动GBL同步服务
private bool DomainService = false ; // 是否启动域用户同步
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.IContainer components = null ;
private System.Diagnostics.EventLog eventLog;
public SocketService()
{
InitializeComponent();
eventLog = new EventLog();
eventLog.Log = " Application " ;
eventLog.Source = Constants.ServiceName;
this .ServiceName = Constants.ServiceName;
try
{
// 系统心跳
int interval = int .Parse(appConfig.AppConfigGet( " TimerInterval " ));
interval = (interval < 1 ) ? 1 : interval; // 不能太小
timerReAlarm = new System.Timers.Timer(interval * 60000 ); // 分钟
timerReAlarm.Elapsed += new ElapsedEventHandler(timerReAlarm_Elapsed);
// 服务运行间隔
ServiceCycle = int .Parse(appConfig.AppConfigGet( " ServiceCycle " ));
ServiceCycle = (ServiceCycle < interval) ? interval : ServiceCycle; // 不能小于心跳
// 服务整点运行
ServiceRunAt = int .Parse(appConfig.AppConfigGet( " ServiceRunAt " ));
GBLService = Convert.ToBoolean(appConfig.AppConfigGet( " GBLService " ));
DomainService = Convert.ToBoolean(appConfig.AppConfigGet( " DomainService " ));
logger.Info(Constants.ServiceName + " 已初始化完成 " );
}
catch (Exception ex)
{
logger.Error(Constants.ServiceName + " 初始化错误 " , ex);
}
}
/// <summary>
/// 设置具体的操作,以便服务可以执行它的工作。
/// </summary>
protected override void OnStart( string [] args)
{
logger.Info(Constants.ServiceName + " 开始启动。 " );
timerReAlarm.Start();
eventLog.WriteEntry(Constants.ServiceName + " 已成功启动。 " , EventLogEntryType.Information);
CreateTask();
}
/// <summary>
/// 停止此服务。
/// </summary>
protected override void OnStop()
{
timerReAlarm.Stop();
}
/// <summary>
/// 暂停后继续运行
/// </summary>
protected override void OnContinue()
{
timerReAlarm.Start();
base .OnContinue();
}
/// <summary>
/// 暂停
/// </summary>
protected override void OnPause()
{
timerReAlarm.Stop();
base .OnPause();
}
/// <summary>
/// 关闭计算机
/// </summary>
protected override void OnShutdown()
{
base .OnShutdown();
}
private void timerReAlarm_Elapsed( object sender, System.Timers.ElapsedEventArgs e)
{
CreateTask();
}
/// <summary>
/// 使用线程池方式运行程序
/// 优点:快速启动Windows服务, 在后台继续程序操作.
/// </summary>
private void CreateTask()
{
ThreadPool.QueueUserWorkItem( new WaitCallback(ExecuteTask), null );
}
/// <summary>
/// 开始执行同步任务
/// </summary>
private void ExecuteTask( object status)
{
try
{
// 采用整点运行方式
if (ServiceRunAt > 0 )
{
if (DateTime.Now.Hour == ServiceRunAt)
{
if ( ! RunAtOnce)
{
Execute();
RunAtOnce = true ; // 标识整点已经运行过了
}
}
else
{
RunAtOnce = false ;
}
}
else // 采用间隔运行方式
{
// 不管服务间隔是否心跳的倍数,只要服务间隔时间大于等于间隔时间,就执行一次
if (CycleCount >= ServiceCycle)
{
Execute();
CycleCount = 0 ;
}
else
{
CycleCount ++ ;
}
}
}
catch (Exception ex)
{
logger.Error( " ExecuteTask()函数发生错误 " , ex);
eventLog.WriteEntry(Constants.ServiceName + " 运行时出现异常!/r/n " + ex.Message + " /r/n " + ex.Source + " /r/n " + ex.StackTrace);
}
}
public void Execute()
{
// 初始化数据库连接
string DatabasePassword = Sys.decode(ConfigurationSettings.AppSettings.Get( " DatabasePassword " ));
DAO.init(ConfigurationSettings.AppSettings.Get( " DatabaseConnect " ).Replace( " {$password} " , DatabasePassword), DAO.DATABASE_SQLSERVER); // 初始化数据库(SQL Server)访问对象
Lib.adPasswd = Sys.decode(ConfigurationSettings.AppSettings.Get( " password " ));
if (GBLService)
{
AutomatismXml xml = new AutomatismXml();
xml.AutomatismXmlData( 0 );
logger.Info(Constants.ServiceName + DateTime.Now.ToShortTimeString() + " 已成功调用了GBLService一次。 " );
eventLog.WriteEntry(DateTime.Now.ToShortTimeString() + " 已成功调用了GBLService一次。 " , EventLogEntryType.Information);
}
if (DomainService)
{
string msg = string .Empty;
string path = ConfigurationSettings.AppSettings.Get( " path " );
string username = ConfigurationSettings.AppSettings.Get( " username " );
string domain = ConfigurationSettings.AppSettings.Get( " domain " );
AD.init(path, username, Lib.adPasswd, domain);
DomainHelper.accountSync( true , false , true , ref msg, 1 );
Log.saveADLog( null , " 系统同步域用户: " + msg);
logger.Info(Constants.ServiceName + DateTime.Now.ToShortTimeString() + " 已成功调用了DomainService一次。 " );
eventLog.WriteEntry(DateTime.Now.ToShortTimeString() + " 已成功调用了DomainService一次。 " , EventLogEntryType.Information);
}
}
...................
}
4. 使用InstallUtil来安装和卸载服务
安装和卸载Windows服务,需要使用InstallUtil工具类进行操作,该工具是Dotnet框架附带的一个工具,在%SystemRoot%/Microsoft.NET/Framework/*** 对应的目录中。
其中App.config中的内容如下
< configuration >
< appSettings >
<!-- 心跳间隔,系统设置,单位(分钟) -->
< add key ="TimerInterval" value ="5" />
<!-- 运行同步服务的间隔时间(单位:分钟) -->
< add key ="ServiceCycle" value ="60" />
<!-- Windows服务在固定时刻(0~23时刻)运行,设置了该参数,同步服务间隔参数无效,负数为禁用 -->
< add key ="ServiceRunAt" value ="-1" />
<!-- 是否启动GBL信息自动同步服务 -->
< add key ="GBLService" value ="True" />
<!-- 是否启动域用户信息自动同步服务 -->
< add key ="DomainService" value ="True" />
</ appSettings >
</ configuration >
安装Windows服务的命令如下:
REM The following directory is for .NET1. 1
set DOTNETFX =% SystemRoot % /Microsoft.NET/Framework/v1. 1.4322
set PATH =% PATH % ; % DOTNETFX %
cd/
cd " %SystemRoot%/../Program Files/BornShine/用户信息同步服务 "
echo 正在安装 用户信息同步服务
echo ---------------------------------------------------
InstallUtil / i AutoSyncService.exe
echo ---------------------------------------------------
echo Done.
exit
卸载Windows服务的命令如下:
REM The following directory is for .NET1.1
set DOTNETFX = %SystemRoot% / Microsoft.NET / Framework / v1. 1.4322
set PATH = %PATH%;%DOTNETFX%
cd /
cd " %SystemRoot%/../Program Files/BornShine/用户信息同步服务 "
echo 正在卸载 用户信息同步服务
echo ---------------------------------------------------
InstallUtil / U AutoSyncService.exe
echo ---------------------------------------------------
echo Done.
exit