前几天做项目的时候需要用到window服务,研究一段时间,算是掌握了最基本的使用方法吧,现总结如下:
引言:在项目过程中碰到一个问题:需要不断的扫描一个大型数据库表,并获取dataset,以便做后续的复杂的逻辑处理。如果直接扫描获取并做逻辑处理,势必会有很大的性能负耗。现在使用windows服务扫描其变化而不获取dataset,只有当windows服务告诉我可以获取dataset时再进行获取,并做逻辑处理,大大提高了其性能。
windows服务应用程序是一个没有前台界面的应用程序,同时其占用内存资源较小便于长期运行,因此被众多编程者使用,多用于服务器环境下。由于其没有用户界面因此不会产生可视输出,并且可以对其启动和停止进行按需设定,非常方便。
一、创建windows服务:
平台:vs2010
工具:c#
功能实现:向一个txt文件写入当前时间。(完全是学习练习使用,所有没有涉及到特殊的功能)
首先在vs2010中创建一个windows服务工程,默认有一个service1.cs文件,打开此文件,创建一个计时器,每隔1秒钟扫描一次。在计时器的t1_Elapsed中添加编写日志的方法(writelog)。
Onstart控制服务启动;Onstop:控制服务停止。
在OnStart中加入计时器启动的方法,OnStop中加入计时器停止的方法。至此便完成一个简单的Windows服务的创建。
参考代码:
System.Timers.Timer t1 = new System.Timers.Timer(); public Service1() { InitializeComponent(); t1.Interval = 1000; t1.Elapsed += new System.Timers.ElapsedEventHandler(t1_Elapsed); } void t1_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { Writelog(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss")); } protected override void OnStart(string[] args) { t1.Start(); } protected override void OnStop() { t1.Stop(); }
二、安装、发布windows服务:
将service1.cs的视图设计器打开,右键点击添加按照程序,显现serviceProcessInstaller1和serviceInstaller1两个组件,点击serviceProcessInstaller1的属性,将其改为LocalService,点击serviceInstaller1的属性,可以在serviceName中更改服务的名字,在startType中更改服务的启动方式。最后生成解决方案。
使用InstallUtil.exe工具,安装生成的应用程序。提示:在解决方案生成的项目应用程序的目录下进行安装,可以debug,也可以是release。
在安装过程中可以使用批命令来实现:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil -u FirstService.exe
C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil FirstService.exe
其中前面是InstallUtil.exe的存放位置,可以根据不同的版本查找其确切位置。FirstService是应用程序,可以替换为项目生成的应用程序。
注:在安装过程中如果一切顺利当然是幸事,本人的安装足是用了两个下午,最后在一大牛的指导下才安装正确。究其原因,检查你使用的计算机的用户是否对你项目所在位置的盘是否赋予其相应的权利(管理员)。(深受其苦)
三、调试windows服务:
1、将创建的windows服务发布到本地系统中。
2、打开vs2010,在项目源代码中设置断点。
3、点击调试按钮,打开附件到进程对话框,选择所需要进程后点击附加按钮。
4、启动服务即可进行调试。
windows服务成功完成后,原本是想在不同的进程间建立通信,最后经过询问先驱者,本项目的需求是当数据有改变时将其变化保持到另一数据库表即可,完成目标。但是这里勾起了我学习进程间通信的好奇心。
进程间通信方式:
匿名管道:半双工通信方式,数据只能在有亲缘关系的进程间单向流动。通俗来讲:用来在父进程和子进程间传输数据,并且是在本机上使用。
命名管道:相对应匿名管道来说的,单向或双向的,用在服务端和一个或多个客户端进行通讯。一个命名管道的所有实例共享同样的管道名,但是每个实例都有各自的缓存和句柄,作为一个隔离的通道,让客户端-服务器端进行通讯,这些实例允许客户端同时使用相同名字的管道。
信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点
信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
套接字:日常工作中经常遇到,此次不做介绍。
方式有很多,目前为止只用到过套接字,在时间有限的情况下自己仅选择命名管道进行学习。
命名管道,上面也介绍了很多,它是相对于匿名管道来说的。由于是初次学习,是学习而不是为了完成项目需求,所以本着实现最基本的框架即可的原则来进行下面讲述。
命名管道有两个很重要的类:
NamedPipeClientStream:公开命名管道周围的 System.IO.Stream,该管道既支持同步读写操作,也支持异步读写操作。
NamedPipeServerStream:公开命名管道周围的 System.IO.Stream,该管道既支持同步读写操作,也支持异步读写操作。
从上面的定义也可看出client和server定义相同,因此其通信角色也可互换。同时其定义也正对应着其字面意思。有点拗口,简单来说无非就是定义有名字的管道周围的stream。多余的不说了
参考代码:
Server端:
Thread receiveDataThread = new Thread(new ThreadStart(ReceiveDataFromClient)); private static void ReceiveDataFromClient() { while (true) { try { NamedPipeServerStream pipeServer = new NamedPipeServerStream("closePipe", PipeDirection.InOut, 2); pipeServer.WaitForConnection(); StreamReader sr = new StreamReader(pipeServer); string receiveData = sr.ReadLine(); Thread.Sleep(1000); sr.Close(); Console.WriteLine("[server] receive {0} from client", receiveData); } catch (Exception ex) { throw new Exception(ex.Message); } } }
client端:
private static void SendDataToServer() { try { NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "closePipe", PipeDirection.InOut, PipeOptions.Asynchronous); pipeClient.Connect(); StreamWriter sw = new StreamWriter(pipeClient); sw.WriteLine("hello,everyone"); sw.Flush(); Thread.Sleep(1000); sw.Close(); Console.WriteLine("[client] send hello,everyone to server"); } catch (Exception ex) { throw new Exception(ex.Message); } }