Windows服务的新建,安装,卸载,调试以及调用!
一.前言:
写这篇博文之前,我正顶着压力在尝试着调试我一无所知的Windows自建服务。历经千辛万苦,找了无数零散文档拼凑关于VisualStudio2015中怎样创建和调试服务程序!最后终于调试成功!So记录下我的学习历程,供大家借鉴,也供自己回顾复习!
二.Windows服务程序的个人理解:
你可以将它看成是个C/S模式的个人桌面APP,即跟你在网上下载的QQ,暴风,迅雷等桌面应用类似,都有安装,启动,卸载的过程;只不过方式有区别;Windows服务程序运行的方式也不同,它不会给你展示出一个直观的运行画面,也不允许有界面(说白了就是你开启了过后就在后台悄悄的运行,不打扰你做其它更重要的事;);
打开任务管理器—>"服务",或右击“我的电脑”—>"管理"—>"服务和应用程序"—>“服务”,这些就是所谓的的服务程序;
它们都是些什么鬼==,看看描述就不难理解,哦--这个是管声音的,这个是管磁盘的,这个是触摸板服务,这个是防火墙的服务;你有见到过你打开电脑后还要在桌面打开个开启触摸板的(笔记本)app,一直运行着你才能用触摸板的么!没有吧,他们都是后台自动开启的服务,让你的桌面简洁又美观(这才是服务,不打扰,默默奉献,给你提供最舒适的交互,不过话说回来,服务是会占用系统资源的,太多的服务也会影响你系统的性能,不能提供良好的用户体验!)
接下来就以一个简单的示例展示一下Windows服务程序,该服务程序的功能是:
TimeService项目:每隔5000毫秒获取一遍当前系统时间,存放于“消息队列(MessageQueue)”中;
ShowNowTime项目:每隔1000毫秒从“消息队列(MessageQueue)”中取出时间并显示;
三.正文:
1.创建服务程序;
a.服务程序要干嘛:
1.服务开启时打开计时器
2.通过计时器记录时间,每隔5000毫秒获取当前系统时间发送到“消息队列(MessageQueue)”中;
3.服务停止时关闭计时器
b.打开VisualStudio2015(或其它版本,不重要);
右键“文件”新建“项目”,或直接在起始页点“新建项目”;
c.选择框架(决定你安装服务的路径),设置服务名称,设置项目的存放路径;
d.在service1的设计视图中右键添加安装程序;
e.设置serviceProcessInstaller1的属性(account设置为loadsystem)
f.设置serviceInstaller1的属性;
g.打开service1折叠,或者在“service1[设计]中右键查看代码”,Onstart就是服务启动后要做的事儿,onstop即服务停止的时候做得事
h.在Service1.cs文件代码如下(注意添加引用,并将代码中的所有“C:\\Users\\Mrwan\\Desktop\\log.txt”改为你自己的桌面路径)
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Linq; using System.ServiceProcess; using System.Text; using System.Windows.Forms; using System.Messaging; using System.Configuration; namespace TimeService { public partial class Service1 : ServiceBase { //常量直接赋值 public const string sMessageConnectionString = @".\Private$\SendTime"; //变量读取配置文件(APP.CONFIG) string LinkAddress = ""; public Service1() { InitializeComponent(); LinkAddress =ConfigurationManager.AppSettings["path"].ToString(); } /// <summary> /// 开启服务的时候服务做得事(每隔5秒将当前时间写入桌面记事本) /// </summary> /// <param name="args"></param> protected override void OnStart(string[] args) { TimeStart.Enabled = true; TimeStart.Interval = 1000;//时间间隔5000毫秒 TimeStart.Start();//打开计时器 //获取时间并写入文件 using (System.IO.StreamWriter sw = new System.IO.StreamWriter("C:\\Users\\Mrwan\\Desktop\\log.txt", true)) { sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "计时器开启"); } } /// <summary> /// 计时器方法 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void TimeStart_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { try { // Process.Start("D:\\QQ2015\\Bin\\QQ.exe");//打开某个程序 int i = 1; using (System.IO.StreamWriter sw = new System.IO.StreamWriter("C:\\Users\\Mrwan\\Desktop\\log.txt", true)) { sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "进入方法"+i++); } SendMessage(); } catch (Exception ex) { string msg = ex.Message; using (System.IO.StreamWriter sw = new System.IO.StreamWriter("C:\\Users\\Mrwan\\Desktop\\log.txt", true)) { sw.WriteLine(msg); } } } /// <summary> /// 向窗体发送时间字符串 /// </summary> /// <param name="msg"></param> private void SendMessage() { //完整队列格式为:计算机名\private$\队列名称 (专用队列) MessageQueue mqQue = new MessageQueue(LinkAddress); mqQue.MessageReadPropertyFilter.SetAll(); System.Messaging.Message msg = new System.Messaging.Message(); //消息主体 msg.Body = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss "); //将消息加入到发送队列 msg.ResponseQueue = mqQue; // msg.AttachSenderId = true; msg.Formatter = new System.Messaging.XmlMessageFormatter(new Type[] { typeof(string) }); try { //发送 mqQue.Send(msg); using (System.IO.StreamWriter sw = new System.IO.StreamWriter("C:\\Users\\Mrwan\\Desktop\\log.txt", true)) { sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "发送成功"); } } catch (Exception ex) { throw new Exception(ex.Message.ToString()); } } /// <summary> /// 关闭服务时要做的事儿 /// </summary> protected override void OnStop() { TimeStart.Enabled = false; //向记事本中写入字符串 using (System.IO.StreamWriter sw = new System.IO.StreamWriter("C:\\Users\\Mrwan\\Desktop\\log.txt", true)) { sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "计时器停止"); } } } }
i.App.confi文件中的代码
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings > <add key="path" value=".\Private$\SendTime"/> </appSettings> </configuration>
j.选中当前项目重新生成;
k.如果报错(签名证书什么的)选中项目—>右键—>属—>签名—>去掉勾选重新生成!
2.安装服务程序;
a.找到框架路径并复制;(如果是4.0的框架则路径为:C:\Windows\Microsoft.NET\Framework\v4.0.30319);
b.找到路径为C:\Windows\System32下的cmd.exe程序,右键“以管理员权限打开”;
c.在这个小黑窗中输入 cd C:\Windows\Microsoft.NET\Framework\v4.0.30319) 回车;
d.找到项目所在文件夹bin目录下debug里面的exe程序:我的是 E:\Study\TimeService\TimeService\bin\Debug\TimeService.exe
e.将该路径跟上程序名称输入小黑窗;installutil E:\Study\TimeService\TimeService\bin\Debug\TimeService.exe回车
f.安装成功,启动服务net start TimeService(关闭:net stop TimeService,卸载:sc delete TimeService)
每次重新生成前都要关闭并卸载服务,生成后再重新安装并开启服务,;
2.调试服务程序;
a.进入VisualStudio,在需要调试的地方设置断点,右键调试—>附加到进程;
b.显示所有用户进程,找到我们创建的进程,附加(必须启动我们开发的服务才能看到该进程);
c.如果出现此提示,请选(使用其他凭据启动),vs会重新启动,然后重新附加到进程即可开始调试;
d.然后就可以在你桌面上的txt文件中找到该服务程序的操作记录了
3.调用服务程序;
本例子假设使用Winform进行获取Windows服务向消息队列中发送的时间,并将每隔一段时间就将获取到的时间弹出来;
即 TimeService服务开启——>TimeService服务每隔5秒向消息队列存入获取的时间——>
Winform窗体每隔一段时间从消息队列中获取时间并显示(鉴于MessageQueue的特性,该时间并不一定是当前系统时间)
a,消息队列的开启见http://www.cnblogs.com/wangsongwang/p/5320205.html
b,新建Winform窗体(添加Timer控件,ServiceControl控件,Lable控件);
c.ShowNowTime窗体后台代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Configuration; using System.Messaging; namespace ShowNowTime { public partial class ShowNowTime : Form { //设置为常量 // public const string sMessageConnectionString = @".\Private$\SendTime"; //设置为变量 string LinkAddress = ""; #region<关于设置为常量还是变量的说明> /// 相较于常量,变量的方式更利于程序的后期维护 /// 如果设置为Const常量,则后面当服务的消息队列路径更改时得重新更改该程序 /// 如果设置为变量,通过读取配置文件(app.config)的方式可获得消息队列路径 /// 则当服务程序指向的消息队列修改时只需更改该程序的配置文件就可以了 #endregion public ShowNowTime() { InitializeComponent(); //窗体初始化的时候,读取配置文件获得消息队列的路径 LinkAddress = ConfigurationManager.AppSettings["path"].ToString(); timer1.Start();//计时器开始 } /// <summary> /// Timer控件事件,每到指定的时间就执行该方法 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer1_Tick(object sender, EventArgs e) { //获得TimeService服务发送的时间字符串 GetMessage(); label1.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss "); } /// <summary> /// 获取方法 /// </summary> private void GetMessage() { //消息队列对象,可以获得消息队列上的各个属性值 MessageQueue mqQue = null; //根据配置文件中指定的“消息队列路径”判断是否存在该消息队列 if (MessageQueue.Exists(LinkAddress)) { mqQue = new MessageQueue(LinkAddress); } //通过消息队列的GetAllMessages方法得到所有消息 System.Messaging.Message[] messages = mqQue.GetAllMessages(); if (messages.Length > 0)//如果有内容 { foreach (System.Messaging.Message msgVal in messages) { //同步接收,直到得到一条消息为止,如果消息队列为空,会一直阻塞 System.Messaging.Message msg = mqQue.Receive(); msg.Formatter = new System.Messaging.XmlMessageFormatter(new Type[] { typeof(string) }); //消息的内容显示在窗体上 //label1.Text = msg.Body.ToString(); MessageBox.Show(msg.Body.ToString()); break; } //删除队列中包含的所有消息 //mqQue.Purge(); } } } }
d.在窗体所在的项目下添加App.config(应用程序配置文件)文件;
代码如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <appSettings > <add key="path" value=".\Private$\SendTime"/> </appSettings> </configuration>
e.重新生成整个解决方案,安装服务,启动服务,运行Winform窗体,即实现了WindowsService(TimeSerivce)的调用!