——搜狐博客 左贤清 有关windows service的详细介绍,不在本文叙述。如果想了解,自己到网上去搜一搜,关键词就是“windows service”,搜索结果保证不会让你失望,呵呵!本文的预期读者即为对windows service有一定了解,但又没有编写过windows service程序的人。 首先咱不急于介绍如何如何编码,第一步干吗第二步干吗。在你需要动手之前,你要想清楚,我为什么要这么做,有没有其它更方便、简单、快捷的解决办法。如果我必须这么做,有什么好处等等这些。因为我自己就遇到过很多种类似的情况,高高兴兴地把东西做出来了,结果发现要么就是太复杂,要么就是客户不爽,要么就发现原来还有更简单的办法,总之就是一句:我做的东西,就项目管理者的角度而言,是没有用的,我做这个东西的时间白费了,延缓了项目的进度。当然,如果从个人自身水平和经验来说,那又是完全另外一种评论和结果了。 现在我假设你已经想得非常清楚了,狠下心来决定采用windows service来解决你在项目中遇到的问题了。你想知道如何创建一个windows service了。那么我接着介绍。为了便于理解,以下部分将分为几个部分分别讲述。 一、windows service示例使用的业务环境 之所以先介绍windows service示例使用的业务环境,是因为如果有这些介绍,后面理解起来更加容易。我在本文中所举的windows service示例,源于我们实际工作中一个web项目的需要,该项目是一个在线考试系统,其中有这么一个取舍,在线考试的时候,因为考生数量较多,为避免交卷时将答题信息一股脑儿往数据库里插入出错,采取了这样一种办法:先让所有的考生交卷,交卷的时候并不往数据库里插入数据,而是将考生答题情况生成一个xml文件,在考生交卷后上传到服务器固定的目录下,然后由程序去解析xml文件,抽取数据,插入到数据库。为了减轻服务器的压力,这些上传到服务器上的xml文件解析工作不能在考生考试时候进行,这样做是尽量减少考生考试时出错的几率。那么,如何对这些上传到服务器上的xml文件来解析,什么时间来解析,是必须要考虑的。如果不考虑这些,可以在考生交卷将包含答题信息的xml文件上传到服务器之后,由web系统直接执行一段代码,对固定目录下提交的xml文件扫描,读取数据并插入数据库。这也不失为一个好的办法。但如果想要在服务器压力较小的情况下再来对这些xml文件进行解析,参考网上的评论,据说有三个较好的办法:一是采用数据库的作业;二是采用windows的计划任务;三是采用windows service。不管怎么样,我最后决定采用windows service了。 二、windows service示例实现的功能 本文中的windows service示例,要实现代功能简单明确:1,定时扫描服务器固定目录下的xml格式文件;2,定时扫描固定目录下的xml文件,如果时间在晚上20到23点之间,提取xml文件中的数据,往本地数据库kaoshi中的表t_datiqk中插入数据;3,如果数据提取并插入成功,固定目录下的xml文件删除。 三、用c#在vs2005中创建windows service的步骤 前面介绍了很多本文中的windows service示例的情况,估计各位看官已经非常不耐烦了。那么好,现在进入真刀真枪的代码编写和操作步骤阶段。感谢微软,感谢vs2005,让我创建windows service如此的容易、快捷。接下来介绍在vs2005中用c#创建windows servcie的步骤。 1、创建windows service工程项目 打开vs2005,点击File-New-Project(偶用的是TeamSuit版VisioStudio),选择Windows Service。如下图。 在工程名称输入框中,输入GradeService(这个是windows service的名字,你爱怎么取随你,俺管不着),然后在下面选择项目的保存路径,点击OK即可,在解决方案浏览器中可以看到,vs2005已经自动为你生成了一些必要的文件,如下图 。将vs2005切换到属性浏览页面,Service1.cs会有以下属性: Autolog 是否自动写入系统的日志文件 CanHandlePowerEvent 服务时候接受电源事件 CanPauseAndContinue 服务是否接受暂停或继续运行的请求 CanShutdown 服务是否在运行它的计算机关闭时收到通知,以便能够调用 OnShutDown 过程 CanStop 服务是否接受停止运行的请求 ServiceName 服务名 系统默认产生的文件名让我很不爽,我决定要改掉它。于是我右键单击“Service1.cs”,改为“GradeService”,如下图。 vs2005弹出一个窗口,如图,点击“是”。 2、完善windows service功能,增加业务代码 因为要记录日志,所以从工具栏的“Components”拖一个EventLog过来。另外,因为要定时执行,所以还需要一个计时器。从网上可以知道,有三种计时器。一种是System.Timers.Timer;一种是System.Threading.Timer;一种是System.Windows.Forms.Timer。大家都说用System.Timers.Timer好,没办法,那就用System.Timers.Timer吧。不过这个在设计页面上是看不到的,只有在GradeService.Designer.cs里面才可以看到它的声明。双击GradeService.cs[Design]页面,进入到代码页面。里面有两个空的函数,一个是OnStart,一个是OnStop。顾名思义,OnStart函数是服务启动时执行,OnStop函数在服务停止时执行。 下面是GradeService.cs的具体代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.ServiceProcess; using System.Text; using System.IO; using System.Web; using System.Data.SqlClient; using System.Configuration;
namespace GradeService { public partial class GradeService : ServiceBase { public GradeService() { InitializeComponent(); // 如果系统时间查看器中没有“Xml文件解析”这样的类别,就添加一个 if (!System.Diagnostics.EventLog.Exists("Xml文件解析")) { System.Diagnostics.EventLog.CreateEventSource("Xml文件解析服务", "Xml文件解析"); } // 设置日志的名字 this.eventLog.Log = "Xml文件解析"; // 设置日志的来源 this.eventLog.Source = "Xml文件解析服务"; } /// <summary> /// 服务启动 /// </summary> /// <param name="args"></param> protected override void OnStart(string[] args) { // TODO: Add code here to start your service. if (this.timer == null) { eventLog.WriteEntry("xml文件解析服务启动"); timer = new System.Timers.Timer(); // 每隔5分钟执行 this.timer.Interval = 5 * 60 * 1000; // 设置timer可以激发Elapsed事件 this.timer.Enabled = true; // 开始 this.timer.Start(); this.timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed); } } /// <summary> /// 时间间隔到达后执行的代码 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { // 停止计时 this.timer.Stop(); // 取系统当前时间 DateTime now = DateTime.Now; // 判断当前时间是否在20到23点之间,如果是则执行业务代码,否则不执行 if (now.Hour <= 23 && now.Hour >= 20) { // 获取App.config文件中xml文件保存路径 string _basePath = System.Configuration.ConfigurationSettings.AppSettings["FilePath"]; // 开始扫描文件 this.ScanFile(_basePath); } // 开始计时 this.timer.Start(); } /// <summary> /// 服务停止 /// </summary> protected override void OnStop() { // TODO: Add code here to perform any tear-down necessary to stop your service. if (this.timer != null) { this.timer.Enabled = false; this.timer.Stop(); this.timer.Dispose(); eventLog.WriteEntry("xml文件解析服务停止"); } } /// <summary> /// 解析文件并将数据插入数据库 /// </summary> /// <param name="fileName">文件全路径</param> /// <param name="name">文件名</param> private void InsertData(string fileName, string name) { // 读取xml文件内容 DataSet ds = new DataSet(); ds.ReadXml(fileName); // 循环数据集并插入内容,采用事务 string msg = "解析文件" + name + "成功!"; // 获取连接数据库字符串 string sqlConnString = System.Configuration.ConfigurationSettings.AppSettings ["SqlConnString"]; if (sqlConnString == null) { eventLog.WriteEntry("在App.config文件中没有找到连接数据库的字符串,解析文件中止!"); return; } // 建立数据库连接 SqlConnection conn = new SqlConnection(sqlConnString); try { conn.Open(); } catch(Exception e) { // 如果连接失败,记录原因 eventLog.WriteEntry(e.Message); return; } // 开始事务 SqlTransaction tran = conn.BeginTransaction(); SqlCommand comm = new SqlCommand(); comm.Transaction = tran; comm.Connection = conn; try { // 从xml文件名中获取准考证和试卷编号 string temp = name.Substring(0, name.Length - 4); string[] tempXmlName = temp.Split('-'); // 准考证号码 string c_zhunkaozhm = tempXmlName[0].ToString(); // 试卷编号 string c_shijuanbh = tempXmlName[3].ToString(); // Sql语句 string strSqlInsert = "INSERT INTO t_datiqk (c_zhunkaozhm,c_shijuanbh,c_timubh,c_timulx,c_fenzhi,c_geshilx,c_wentims,c_daan,c_huidada,c_datisj,c_zhe ngque) VALUES (@c_zhunkaozhm,@c_shijuanbh,@c_timubh,@c_timulx,@c_fenzhi,@c_geshilx,@c_wentims,@c_daan,@c_huidada,@c_da tisj,@c_zhengque)"; // Sql参数 SqlParameter param; // 循环xml文件中抽取出来的数据 for (int i = 0; i < ds.Tables[0].Rows.Count; i++) { comm.Parameters.Clear(); comm.CommandText = strSqlInsert;// Sql语句 param = new SqlParameter("@c_zhunkaozhm", SqlDbType.VarChar);// 准考证号 param.Value = c_zhunkaozhm; comm.Parameters.Add(param); param = new SqlParameter("@c_shijuanbh", SqlDbType.VarChar);// 试卷编号 param.Value = c_shijuanbh; comm.Parameters.Add(param); param = new SqlParameter("@c_timubh", SqlDbType.VarChar);// 题目编号 param.Value = ds.Tables[0].Rows[i]["c_bianhao"].ToString(); comm.Parameters.Add(param); param = new SqlParameter("@c_timulx", SqlDbType.Int);// 题目类型 param.Value = Convert.ToInt32(ds.Tables[0].Rows[i]["c_timulx"]); comm.Parameters.Add(param); param = new SqlParameter("@c_fenzhi", SqlDbType.Float);// 分值 param.Value = Convert.ToSingle(ds.Tables[0].Rows[i]["c_fenzhi"]); comm.Parameters.Add(param); param = new SqlParameter("@c_geshilx", SqlDbType.Int);// 格式类型 param.Value = Convert.ToInt32(ds.Tables[0].Rows[i]["c_geshilx"]); comm.Parameters.Add(param); param = new SqlParameter("@c_wentims", SqlDbType.VarChar);// 问题描述 param.Value = ds.Tables[0].Rows[i]["c_wentims"].ToString(); comm.Parameters.Add(param); param = new SqlParameter("@c_daan", SqlDbType.Int);// 正确答案 param.Value = Convert.ToInt32(ds.Tables[0].Rows[i]["c_zhengqueda"]); comm.Parameters.Add(param); param = new SqlParameter("@c_huidada", SqlDbType.Int);// 考生回答答案 param.Value = Convert.ToInt32(ds.Tables[0].Rows[i]["c_daan"]); comm.Parameters.Add(param); param = new SqlParameter("@c_datisj", SqlDbType.DateTime);// 答题时间 param.Value = Convert.ToDateTime(ds.Tables[0].Rows[i]["c_datisj"]); comm.Parameters.Add(param); param = new SqlParameter("@c_zhengque", SqlDbType.Int);// 是否正确 param.Value = Convert.ToInt32(ds.Tables[0].Rows[i]["c_zhengque"]); comm.Parameters.Add(param); comm.ExecuteNonQuery();// 执行更新 } tran.Commit(); // 扫描成功后删除xml文件 if (File.Exists(fileName)) { File.Delete(fileName); } } catch (Exception ex) { // 会滚sql操作 tran.Rollback(); // 获取异常信息 msg = ex.Message; } finally { conn.Close(); } // 将操作信息或异常信息写入日志(日志可以在系统的事件查看器中看到) eventLog.WriteEntry(msg); } /// <summary> /// 循环扫描具体路径下的文件 /// </summary> /// <param name="filePath"></param> private void ScanFile(string filePath) { // 创建DirectoryInfo实例 DirectoryInfo dirInfo = new DirectoryInfo(filePath); // 得到源目录的文件列表 FileInfo[] files = dirInfo.GetFiles(); // 循环解析文件 for (int i = 0; i < files.Length; i++) { // 判断文件的后缀是否为xml string postFix = files[i].Extension; // 获取全路径 string fileNme = files[i].FullName; // 获取文件名 string name = files[i].Name; if (postFix == ".xml") { this.InsertData(fileNme, name); } } } } } 3、将安装程序添加到服务应用程序 想要把windows service安装起来,不是双击GradeService.exe就可以的,它和普通的可执行文件安装办法不一样。 首先,我们要给windows service添加Installer。右键点击设计视图,选择Add Installer,VS将会为我们添加ProjectInstaller.cs,并在ProjectInstaller中添加组件serviceInstaller1和serviceProcessInstaller1,现在我们来修改他们的属性来控制Service的安装和启动选项。在ProjectInstaller得设计视图中选中serviceProcessInstaller1,将它得Account属性选为LocalSystem,这样以这个账号服务启动。如果你希望系统启动时自动启动服务得话,将serviceInstaller1的StartType的属性选为Automatic,如果手动启动的话,选为manaul。 其次,要安装service,我们要用到IntallUtil.exe这个程序,这个程序位于C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727.点击开始菜单,选择“运行”,在运行对话框中输入cmd,进入到命令行窗口,输入cd :\WINDOWS\Microsoft.NET\Framework\v2.0.50727,进入到这个目录,然后输入installutil F:\project\考试系统\项目代码\Service\GradeService\GradeService\bin\Debug\GradeService.exe, installutil后边的内容就是我们的工程生成的可执行程序的路径,可以根据你自己的实际情况进行修改。 如果你给ServiceInstaller1的StartType设为Automatic的话,安装完服务,服务已经运行起来了,如果StartType是Manual的话,你需要手动启动。现在我们进入“服务”,要打开“服务”,请单击“开始”,指向“设置”,然后单击“控制面板”。依次单击“性能和维护”、“管理工具”,然后双击“服务”。在里边你应该能够看到我们制作的Service GradeService。在这里边,我们可以启动,关闭服务,还可以设置服务的启动类型。然后,我们看看服务有没有正确的写入日志,我们需要进入到事件查看器,要打开“事件查看器”,请单击“开始”,指向“设置”,然后单击“控制面板”。单击“性能和维护”,单击“管理工具”,然后双击“事件查看器”。如下图所示,我们的消息已经成功的写到了系统日志里了,下图。 四、在使用windows service过程中发现的小问题 在使用windows service中,因为有一些变量,不想写死,想保存在配置文件中,于是给示例service加了一个配置文件,叫App.config。其中定义了一些key,比如<add key="SqlConnString" value="Data Source=(local);Database=kaoshi;User ID=sa;Password=780910;"/>。但是发现,如果服务已经安装好后,去修改App.config中的key值,对服务并没有影响,windows servcie仍然按照App.config文件修改前的key值运行。不知道其他兄弟姐妹有没有遇到这种情况。 五、在asp.net中如何控制自己创建的windows service(网上流传,尚未验证是否属实) windows service是可以在asp.net中进行控制的,你可以在asp.net构建的web项目中对服务器的windows service进行控制。但是要有两个前提。 1、在web项目解决方案资源管理器中添加引用System.ServiceProcess.dll; 然后在.cs中 using System.ServiceProcess; 然后在事件中写代码: ServiceController sc=new ServiceController("GradeServiceContrller");// 建立服务控制类对象 if(sc.Status==ServiceControllerStatus.Stopped) { sc.Start();// 开始服务 } 2、在web.config中模拟一个管理员用户。 如admin(属于administrator组.) 如下所示: <configuration> <system.web> <identity impersonate="true" userName="admin" password="admin" /> </system.web> </configuration>
|