原文轉自:http://dreambuild.blog.sohu.com/132659489.html
有關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>