没有前言。
Windows服务可以在系统启动时自动打开,如果需要在没有用户操作的情况下运行的程序,就可以创建Windows服务。
比如:
一个Windows服务包括三个部分:
服务程序用于提供实际功能;服务控制程序可以把控制请求发送给服务,比如:开始,暂停,结束等,在编程时,我们只需要重写ServiceBase类与之对应的方法即可;服务配置程序可以安装服务,服务的信息需要写进注册表,供服务控制管理器(SCM)用于启动、停止服务。
服务程序需要三个部分:
服务控制管理器(SCM)是操作系统的组成部分,它的作用是与各种各样的服务进行通信。这种通信的流程为:
SCM ----启动服务进程----> 服务
SCM <----注册service-main函数---- 服务
SCM ----service-main----> 服务
SCM <----注册处理程序---- 服务
在系统启动时,将启动每个服务进程,进而调用该进程的主函数,它是该服务的入口。一个服务进程可能包含了多个服务,每项都会被该服务注册一个service-main函数入口点,service-main函数包含服务的实际功能。
service-main函数的一个重要任务是用SCM注册一个处理程序。处理程序用来响应来自服务控制程序的事件,如SCM:停止,暂停或重新开始。
在VS中选择Windows Service模板,新建项目。双击自动生成的Service1,可以看到一个与winForm类似的设计器,我们不需要往上面拖动任何控件。我们可以在属性界面来调整该服务的属性值。
右键查看Service1的代码,可以发现它继承自ServiceBase类,并重写了OnStart和OnStop方法,ServiceBase类是.Net Framework框架下用来开发Windows服务的基类。服务类实际上是使用一个称为NativeMethods的辅助类与SCM通信,当然,我们并不需要了解过多的细节。
我们来查看Program类里的主函数:
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
}
可以看出,一个服务进程可以运行多个服务,ServicesToRun数组中的对象需要继承自ServiceBase。
ServiceBase类注册处理程序,并在调用了OnStart()方法后把服务启动成功的消息通知给SCM。在调用Run方法后,主线程会处于阻塞状态,直到服务结束。所以初始化操作应在Run方法之前完成,且不应超过30s。如果有长时间的初始化任务,应该将服务线程化。因为调用Run方法超过30s,SCM会任务该服务启动失败。每个服务启动时,会调用其OnStart方法。我们可以在OnStart方法里写具体的功能实现。
这个列子的作用是在系统上运行监听程序,向客户端的任何请求都回复指定文件里的随机一行内容。Windows服务并不能直接调试,我们可以通过记录日志,附加到进程等方式进行间接调试。我们最好先建一个方便调试的功能程序,调试无误后再移植到Windows服务程序中。
QuoteService类
TCP模块:
请注意看catch代码块里注释掉的内容,如果服务运行时,程序抛出了异常的话,服务是可能停掉的。
protected void Listener()
{
try
{
IPAddress ipAddress = IPAddress.Any;
tcpListener = new TcpListener(ipAddress, port);
tcpListener.Start();
while (true)
{
var client = tcpListener.AcceptTcpClient();
string message = GetRandomQuoteOfTheDay();
var coder = new UnicodeEncoding();
byte[] sendBuffer = coder.GetBytes(message);
client.Client.Send(sendBuffer);
client.Close();
}
}
catch (SocketException ex)
{
//Trace.TraceError($"QuoteService{ex.Message}");
//throw new QuoteExecption("socket Exception");
Log("SocketException: " + ex.Message);
}
}
暴露的Start和Stop方法
public void Start()
{
listenerTask = Task.Factory.StartNew(Listener,TaskCreationOptions.LongRunning);
}
public void Stop()
{
tcpListener.Stop();
}
QuoteWindowsService : ServiceBase 服务类
public partial class QuoteWindowsService : ServiceBase
{
private QuoteService quoteService;
public QuoteWindowsService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
quoteService=new QuoteService();
quoteService.Start();
}
protected override void OnStop()
{
quoteService.Stop();
}
}
你也可以重写OnPause,OnShutdown,OnContinue等方法,OnPowerEvent()在系统电源状态发生变化时触发。注意OnStart()方法不能被阻塞,它必须及时的返回给调用者(ServiceBase类的ServiceMainCallBack方法)。
安装组件在安装它的系统上注册单个服务,并让服务控制管理器知道存在该服务。
https://docs.microsoft.com/zh-cn/dotnet/framework/windows-services/how-to-add-installers-to-your-service-application
需要注意的是ServiceInstaller的ServiceName属性需要与ServiceBase的ServiceName属性保持一致。
而且,每个ServiceBase的派生类(即每个服务)都应有一个与之对应的ServiceInstaller实例,而ServiceProcessInstaller是用于配置服务进程,一个服务进程可以包含多个服务。
ProjectInstaller类派生自InStall.InStaller类,后者是所有自定义安装程序的基类,它可以构建基于事务的安装程序。InStaller类有Install(),Commit(),Rollback(),Uninstall()方法,这些都从安装程序中调用。可以看到它有一个特性RunInstaller,它的值为True代表安装程序集时会检查该特性。
我们可以使用installutil.exe来安装服务。
如果服务生成失败,可能是存在同名服务,或是没有以管理员权限运行安装命令等。
服务安装以后,你可以在 任务管理器-服务 中查看,也可以在 服务 中查看详细信息。如果想更新服务程序,只需要先停掉服务,重新生成解决方案即可。
另外,我们可以在cmd窗口,使用sc命令+服务名直接管理服务,比如:sc delete 服务名,删除某个服务。这是一个很强大的命令,它可以检查服务的状态,配置等,当服务卸载程序无法正常工作,我们可以使用它来卸载程序。比如你设置了ServiceBase的CanStop属性为false, 那么installutil /u 方法就不好用了,因为你无法将服务停下来再删除。