有關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>