编写 window 服务程序
一、直观认识windows服务。
打开windows“控制面板/管理工具/服务”,系统显示windows服务列表。
双击服务,可以显示和更改服务属性。在这个对话框中,可以控制服务的启动、暂停和停止。在这里还可以配置服务的启动类型,令服务在系统启动时自行启动。因此,windows服务经常作为服务器程序运行。在故障恢复这个属性页,可以配置该服务失败后系统的相应。一些病毒程序就是在这里做文章,将病毒程序激活的。
二、windows服务的开发要点
visual studio的随机文档里,详细介绍了windows服务程序的开发步骤,并且带有实例,笔者不再赘述。读者只需注意几个要点:
1、创建一个派生自servicebase的入口类。这个入口类管理这个windows服务的生存期。
public class myservice : system.serviceprocess.servicebase { …… }
2、在入口类的main方法里将服务向windows的服务控制器(service control manager, scm)注册,代码: …… system.serviceprocess.servicebase[] servicestorun;
servicestorun = new system.serviceprocess.servicebase[] { new myservice() };
system.serviceprocess.servicebase.run(servicestorun); ……
3、重写 onstart 、onstop ,或onpause 和 oncontinue 方法来响应服务状态的更改。通常需要重写 onstart 方法,结束服务时在 onstop 方法中释放资源,酌情重写onpause 和 oncontinue方法。
4、windows服务通常启动一个定时器来定时或轮询进行业务处理。
5、windows服务需要安装后才能使用。通常通过两个办法安装windows服务:在命令行运行installutil.exe;在windows服务程序的代码中添加projectinstraller类的实例,里面包含serviceprocessinstaller类和serviceinstaller类的实例。
上述两个办法在framework的随机文档中均有描述,在此不再赘述。
6、windows服务在windows的服务控制器(service control manager, scm)中运行,因此调试起来不像其他visual studio应用程序那样简单。关于windows服务的调试,在visual studio的随机文档里面有介绍,在此不再赘述。
三、windows服务的异常处理
windows服务没有用户界面,在运行过程中难以将异常通知给用户。通常情况下,windows服务在运行过程中发生了异常,可能导致服务运行挂起,但没有任何提醒。
推荐的一个做法是在windows服务中捕获异常,并把异常信息写在windows的事件日志中。打开windows的“控制面板/管理工具/事件查看器”,系统显示windows事件日志。
在一个实际的应用中,笔者除了把异常和提示记录在事件日志中,还把严重错误自动通过邮件发送给相关人员。同时,所有记录在事件日志中的信息,还重定向到一个自行开发的控制台程序中,用以随时监控服务。
三、windows事件日志的开发要点和技巧
visual studio的随机文档里,在介绍windows服务程序的开发步骤的同时,也介绍了如何向windows服务中加入事件日志,笔者不再赘述。开发要点如下: 1、在需要写入日志的类中创建eventlog的实例eventlog,在构造函数里加入代码:
if (!system.diagnostics.eventlog.sourceexists("mysource")) {
system.diagnostics.eventlog.createeventsource("mysource","myeventlog"); }
eventlog.source = " mysource ";
eventlog.log = " myeventlog ";
2、在需要写事件日志的地方写日志,例如:
protected override void onstop() {
eventlog.writeentry("in onstop.");
}
读者可以在实际应用中尝试使用下面的技巧。
1、把写windows事件日志的代码封装成独立的class,这样不仅在windows服务中,而且在其他的业务代码中都可以使用windows事件日志。代码见附件。
2、为方便调试和跟踪,visual sdudio提供了trace类。在应用程序的debug编译版本中,用trace类可以把调试和跟踪信息写到控制台。有一个技巧,可以同时把写入trace的内容写入windows事件日志。要点如下:
首先声明一个事件监听类eventlogtracelistener的实例,
static private eventlogtracelistener ctracelistener = new eventlogtracelistener( m_eventlog );
将eventlogtracelistener的实例加入trace的监听列表:
trace.listeners.add( ctracelistener );
此后,凡是写入trace的调试信息,均写入windows事件日志中。如果不希望将trace继续写入事件日志,运行下面代码即可:
trace.listeners.remove( ctracelistener );
3、写入事件日志的信息,还可以同时写入其他应用程序窗体中的显示控件。
首先打开窗体的设计视图,从工具箱/组件中选择eventlog并加入窗体,配置eventlog的enableraisingevents属性为true。
加入eventlog的entrywritten事件处理方法,该事件的第二个参数类行为system.diagnostics.entrywritteneventargs,其中包含了windows事件日志条目中的必要内容,将该内容显示在窗体中的某个显示控件中即可。示例代码如下:
/// <summary>/// 监听事件日志/// </summary>
/// <param name="sender"></param>///
<param name="e"></param>
private void eventlog_entrywritten(object sender, system.diagnostics.entrywritteneventargs e){
try {
// 把日志内容写到名为listeventlog的list控件中
listeventlog.items.insert( 0, e.entry.timewritten + " " + e.entry.message );
// list控件保存不超过500行的日志
while( listeventlog.items.count > 500 )
{
listeventlog.items.removeat( listeventlog.items.count-1 );
}
}
catch( exception ex )
{
messagebox.show( ex.message );
}}
四、与windows服务的通讯
在应用程序或其他服务中,可以与windows服务通讯,包括:
管理windows服务的生命期,即开启、停止、暂停和重启服务;
获得windows服务的属性和状态;
获得特定计算机上的服务列表;
向特定的服务发送命令。
这些操作是通过servicecontroller 类完成的。servicecontroller是一个可视化控件,可以在工具箱中找到。
比较有意思的是servicecontroller 中executecommand这个方法,调用这个方法,可以向windows服务发送命令,指挥windows服务的一些操作。例如,在windows服务的入口类中有一个复写oncustomcommand()的方法:
/// <summary> /// 执行用户自定义消息 /// </summary>
/// <param name="command">消息编号</param>
protected override void oncustomcommand( int command )
{
try
{
switch( command )
{
case 1: // 业务操作
dobusiness1();
break;
case 2: //业务操作
dobusiness2();
break;
default:
…… break;
}
}
catch( exception ex )
{ // 错误信息
string strerrormsg = string.format("异常:{0}\n", ex.message );
// 写日志
tlineeventlog.dowriteeventlog( strerrormsg, eventtype.error );
// 给管理员发邮件
cmail.sendmail( propertymanager.strmailfromaddress, propertymanager.strmailadminaddress, "","异常信息提示",strerrormsg );
// 写trace
trace.writeline( strerrormsg );
}
}
在另外一个应用程序中通过servicecontroller的executecommand()方法向这个windows服务发送命令:
mycontroller.executecommand(2);
windows服务将执行业务方法:dobusiness2();
应该承认,利用servicecontroller与windows服务通讯的功能目前还十分薄弱。通过executecommand只能与windows服务进行简单而有限的通讯。
笔者在实际的应用中,分别用一个命令行程序、一个控制台程序和一个webservice和windows服务进行通讯,启动、停止服务,或通过executecommand控制服务的行为。
附件:操纵windows事件日志的通用类using system;using system.diagnostics;using system.configuration; namespace mycommon.eventlog{ public enum eventtype { error,information,warning }
/// <summary> /// /// </summary>
public class tlineeventlog {
// 任务日志
static private eventlog m_eventlog = new eventlog(); // 源名称,从配置文件中读取
static private string m_streventsource = configurationsettings.appsettings["f_eventlog.source"].tostring().trim();
// 日志名称,从配置文件中读取
static private string m_streventlog = configurationsettings.appsettings["f_eventlog.log"].tostring().trim();
// 调试信息写入日志
static private eventlogtracelistener ctracelistener = new eventlogtracelistener( m_eventlog );
// 缺省构造函数。配置文件读取失败时,提供默认的源名称和日志名称
public tlineeventlog() {
if( m_streventsource.length == 0 )
m_streventsource = "mysource";
if( m_streventlog.length == 0 )
m_streventlog = "mylog";
m_eventlog.source = m_streventsource;
m_eventlog.log = m_streventlog;
}
// 构造函数。提供源名称和日志名称。
public tlineeventlog( string streventsource, string streventlog )
{
m_streventsource = streventsource;
m_streventlog = streventlog;
m_eventlog.source = m_streventsource;
m_eventlog.log = m_streventlog;
}
/// <summary> /// 写事件日志 /// </summary>
/// <param name="strmessage">事件内容</param>
/// <param name="eventtype">事件类别,错误、警告或者消息</param>
static public void dowriteeventlog( string strmessage, eventtype eventtype )
{
if (!system.diagnostics.eventlog.sourceexists( m_streventsource ))
{
system.diagnostics.eventlog.createeventsource( m_streventsource,m_streventlog );
}
eventlogentrytype entrytype = new eventlogentrytype();
switch(eventtype)
{
case eventtype.error:
entrytype = eventlogentrytype.error;
break;
case eventtype.information:
entrytype = eventlogentrytype.information;
break;
case eventtype.warning:
entrytype = eventlogentrytype.warning;
break;
default:
entrytype = eventlogentrytype.information;
break;
}
m_eventlog.writeentry( strmessage, entrytype );
}
/// <summary> /// 写事件日志,默认为消息 /// </summary>
/// <param name="strmessage">事件内容</param>
static public void dowriteeventlog( string strmessage )
{
if (!system.diagnostics.eventlog.sourceexists( m_streventsource ))
{
system.diagnostics.eventlog.createeventsource( m_streventsource,m_streventlog );
}
m_eventlog.writeentry( strmessage );
}
/// <summary> /// 调试信息写入日志 /// </summary>
public static void opentrace()
{
if( ctracelistener != null )
{
if( !trace.listeners.contains( ctracelistener ) )
{
trace.listeners.add( ctracelistener );
}
}
}
/// <summary> /// 调试信息不写入日志 /// </summary>
public static void closetrace() {
if( trace.listeners.indexof(ctracelistener) >= 0 )
{
trace.listeners.remove( ctracelistener );
}
}
}}