如何创建一个标准的Windows服务
在很多时候,我们需要一个定时器,当间隔某段时间或者在某一个时刻的时候,触发某个业务的处理,这个时候,我们就可能需要引入Windows服务来做这个事情,如某些数据的同步操作、某些工作任务的创建或者侦听某些端口的工作等等。
做过Windows Forms开发的人,对开发Windows服务可能会熟悉一些,其实它本身应该算是一个Windows Forms程序。基本上整个Windows服务的程序分为几个部分:安装操作实现、程序启动、服务操作等。
本例子创建一个Windows服务,服务可以在整点运行,也可以在某段间隔时间运行,通过配置指定相关的参数。
完整的服务代码请下载文件进行学习:
https://files.cnblogs.com/wuhuacong/AutoSyncService.rar
1)安装操作类的实现
首先需要继承System.Configuration.Install.Installer类,并且需要增加ServiceProcessInstaller、ServiceInstaller两个对象来处理,另外您需要重载BeforeUninstall 和 AfterInstall 来实现服务在安装前后的启动和停止操作。
Code
[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函数里面,添加下面的代码即可
Code
ServiceBase[] ServicesToRun;
ServicesToRun
=
new
ServiceBase[] {
new
SocketService() };
ServiceBase.Run(ServicesToRun);
上面是标准的启动代码,但很多时候,我们需要调试服务,因此会加入一个跳转的开关
Code
#region
调试程序时使用的代码
//
使用方法:在该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。完整的类如下
Code
public
class
SocketService : ServiceBase
{
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中的内容如下
Code
<?
xml version="1.0" encoding="utf-8"
?>
<
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服务的命令如下:
Code
@ECHO
OFF
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服务的命令如下:
Code
@ECHO
OFF
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
标签: none