Windows服務應用程序是一種需要長期運行的應用程序,它對於服務器環境特別適合。它沒有用戶界面,並且也不會產生任何可視輸出。任何用戶消息都會被寫進Windows事件日志。計算機啟動時,服務會自動開始運行。它們不要用戶一定登錄才運行,它們能在包括這個系統內的任何用戶環境下運行。通過服務控制管理器,Windows服務是可控的,可以終止、暫停及當需要時啟動。
Windows 服務,以前的NT服務,都是被作為Windows NT操作系統的一部分引進來的。它們在Windows 9x及Windows Me下沒有。你需要使用NT級別的操作系統來運行Windows服務,諸如:Windows NT、Windows 2000 Professional或Windows 2000 Server。舉例而言,以Windows服務形式的產品有:Microsoft Exchange、SQL Server,還有別的如設置計算機時鍾的Windows Time服務。
(1) 編寫windows service
① 添加windows service項目
② OnStart()方法作為windows service的啟動方法,OnStop()方法作為結束方法
③ 通常在windows service中,我們都加入Timer,讓服務定時做某些事
④ 寫好服務后,在視圖頁面右鍵,為服務添加安裝程序;
⑤ serviceInstaller1“StartType”屬性設置為Automatic,serviceProcessInstaller1“Account”屬性設置為LocalSystem
例子:該服務實現,服務啟動、結束時記下日志,服務在運行時每隔一秒記下一條日志
public partial class Service1 : ServiceBase { public Service1() { InitializeComponent(); } private string _rootPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log"); private string _fileName = DateTime.Now.ToString("yyyyMMdd") + ".txt"; static readonly object padlock = new object(); protected override void OnStart(string[] args) { if (!Directory.Exists(_rootPath)) Directory.CreateDirectory(_rootPath); string path = Path.Combine(_rootPath, _fileName); using (StreamWriter sw = new StreamWriter(path)) { sw.WriteLine("Windows Services is Started at {0}!", DateTime.Now); } var smsTimer = new Timer { Interval = 1000D, Enabled = true }; smsTimer.Elapsed += smsTimer_Elapsed; smsTimer.Start(); } protected override void OnStop() { string path = Path.Combine(_rootPath, _fileName); using (StreamWriter sw = new StreamWriter(path, true)) { sw.WriteLine("Windows Services is Stopped at {0}!", DateTime.Now); } } private void smsTimer_Elapsed(object sender, ElapsedEventArgs e) { lock (padlock) { string path = Path.Combine(_rootPath, _fileName); using (StreamWriter sw = new StreamWriter(path, true)) { sw.WriteLine("Windows Services is Running at {0}!", DateTime.Now); } } } }
(2) 安裝、啟動、停止、卸載
① 微軟自帶安裝、卸載
從命令行進入服務編譯后的路徑,cd F:\study\code\01WindowsServiceDemo\bin\Release
安裝:"%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe" 01WindowsServiceDemo.exe
啟動:net start "MyTestService"
停止:net stop "MyTestService"
卸載:"%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe" /u 01WindowsServiceDemo.exe
② sc命令(批處理中有介紹《sc索引鏈接》)
安裝:
set str=%~f0
set str=%str:~0,-26%01WindowsServiceDemo.exe
sc create "MyTestService" binPath= "%str%"
sc config "MyTestService" type= own start= auto tag= no
注:set str=%str:~0,-26%01WindowsServiceDemo.exe 為獲取安裝文件執行路徑
啟動:sc start "MyTestService"
停止:sc stop "MyTestService"
卸載:sc delete "MyTestService"
(3) 調試
① 安裝啟動服務
② 設置斷點
③ 附加進程
(1) 問題引入
cs程序與bs程序數據的交互,我們采用的是上傳下載機制;bs系統維護管理數據,cs系統無論是需要從bs系統下載數據,還是對其上傳數據,首先都會生成xml文檔,xml文檔中包含了上傳下載類型、要執行的sql命令等等。cs系統以socket通信的方式把xml發送給bs系統,在bs系統中解析xml文檔,如果接到了下載的要求,就根據需要再生成xml以同樣方式發給cs系統,如果接到上傳要求,就更新數據庫。
(2)准備工作
BS系統中寫好windows服務來進行處理數據上傳下載
CS系統中寫好通信機制,CS系統的特殊性,本身就是一個進程,故不需要windows服務
(3) 代碼片段
public partial class DataExchageService : ServiceBase { public DataExchageService() { InitializeComponent(); } protected override void OnStart(string[] args) { DataExchageTimer.Interval = GetIntervalTimes(); //開啟線程進行監聽 Thread t = new Thread(AsyncListening) { IsBackground = true }; t.Start(); } protected override void OnStop() { DataExchageTimer.Enabled = false; } /// <summary> /// Timer輪詢事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void DataExchageTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { //定時解析上傳來的xml數據並保存數據庫 //...... } #region 異步方法 // 線程事件標記,初始狀態為非終止狀態 public static ManualResetEvent MrDone = new ManualResetEvent(false); /// <summary> /// 監聽方法 /// </summary> public void AsyncListening() { //建立Socket通信套接字 Socket listenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { //通信端口 IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(SystemConfig.Ip), int.Parse(SystemConfig.Port)); listenerSocket.Bind(endPoint); listenerSocket.Listen(5000); //輪詢 while (true) { //置為非終止狀態 MrDone.Reset(); //Socket開啟異步線程進行接收請求 listenerSocket.BeginAccept(AcceptCallback, listenerSocket); //阻塞主監聽線程 MrDone.WaitOne(); } } catch (Exception) { throw; } } /// <summary> /// 異步接收請求回調函數 /// </summary> /// <param name="result"></param> public void AcceptCallback(IAsyncResult result) { //復位主監聽線程,讓其不再阻塞 MrDone.Set(); //得到接收到Socket Socket handler = (Socket)result.AsyncState; //完成接收請求 Socket acceptSocket = handler.EndAccept(result); //創建狀態對象(目的是為了封裝多個參數到一個對象中) DataExchangeState state = new DataExchangeState() { WorkSocket = acceptSocket, Ms = new MemoryStream() }; //異步線程接收數據 acceptSocket.BeginReceive(state.Buffer, 0, DataExchangeState.BufferSize, 0, ReceiveCallback, state); } /// <summary> /// 異步接收請求回調函數 /// </summary> /// <param name="result"></param> public void ReceiveCallback(IAsyncResult result) { //異步接收狀態對象 DataExchangeState state = (DataExchangeState) result.AsyncState; Socket handler = state.WorkSocket; //完成接收數據大小 int byteSizeRec = handler.EndReceive(result); if (byteSizeRec > 0) { state.Ms.Write(state.Buffer, 0, byteSizeRec); state.ReceiveSize += byteSizeRec; //根據請求得到返回數據,此處代碼省略 string sendMsg = ""; state.Ms.Close(); state.Ms.Dispose(); AsyncSend(handler, sendMsg); } } /// <summary> /// 發送數據方法 /// </summary> /// <param name="handler">socket對象</param> /// <param name="data">數據</param> private void AsyncSend(Socket handler, string data) { //壓縮數據 byte[] byteData = DataExchangeUtility.ZipString(data); //發送包頭 byte[] sendSize = new byte[4]; sendSize = BitConverter.GetBytes(byteData.Length); handler.Send(sendSize); //異步調用發送方法 handler.BeginSend(byteData, 0, byteData.Length, 0, SendCallback, handler); } /// <summary> /// 發送數據回調方法 /// </summary> /// <param name="result">異步操作狀態</param> private void SendCallback(IAsyncResult result) { try { //從狀態對象得到socket Socket handler = (Socket)result.AsyncState; handler.EndSend(result); //關閉socket連接 handler.Shutdown(SocketShutdown.Both); handler.Close(); } catch (Exception ex) { LogHelper.WriteLog(string.Format("log{0}", "SendCallback" + ex.Message)); } } #endregion #region 通用方法 /// <summary> /// 獲得Timer的Interval /// </summary> /// <returns></returns> private int GetIntervalTimes() { int intervalTimes = 10000; switch (SystemConfig.TimesType) { case "1": intervalTimes = int.Parse(SystemConfig.Times) * SystemConfig.Hour; break; case "2": intervalTimes = int.Parse(SystemConfig.Times) * SystemConfig.Minute; break; case "3": intervalTimes = int.Parse(SystemConfig.Times) * SystemConfig.Second; break; case "4": intervalTimes = int.Parse(SystemConfig.Times) * SystemConfig.Milliseconds; break; } return intervalTimes; } #endregion }
批處理文件(Batch File,簡稱 BAT文件)是一種在DOS 下最常用的可執行文件。它具有靈活的操縱性,可適應各種復雜的計算機操作。所謂的批處理,就是按規定的順序自動執行若干個指定的DOS命令或程序。即是把原來一個一個執行的命令匯總起來,成批的執行,而程序文件可以移植到其它電腦中運行,因此可以大大節省命令反復輸入的繁瑣。同時批處理文件還有一些編程的特點,可以通過擴展參數來靈活的控制程序的執行,所以在日常工作中非常實用。
-->常用批處理命令
(1) rem 與 ::
它們都用來注釋,rem能回顯;而::即時用echo on也不能進行回顯,因為:后跟非數字字母的字符,命令解釋行都認為他不是有效命令。
(2) pause
暫停系統命令,一般顯示:按任意鍵繼續…;
(3) goto與 :
: XX來設置標識位,用goto XX進行跳轉到剛才用:標識的XX位置
(4) title
設置cmd窗口標題
(5) color
color [attr],attr屬性值
0 = 黑色 8 = 灰色
1 = 藍色 9 = 淡藍色
2 = 綠色 A = 淡綠色
3 = 湖藍色 B = 淡淺綠色
4 = 紅色 C = 淡紅色
5 = 紫色 D = 淡紫色
6 = 黃色 E = 淡黃色
7 = 白色 F = 亮白色
(6) call
CALL命令可以在批處理執行過程中調用另一個批處理,當另一個批處理執行完后,再繼續執行原來的批處理
在批處理編程中,可以根據一定條件生成命令字符串,用call可以執行該字符串,見例子:
call [drive:][path]filename [batch-parameters]
(7) shift
shift [/n]:更改批處理文件中可替換參數的位置。n 介於零和八之間。
例如:shift /2 會將 %3 移位到 %2,將 %4 移位到 %3,等等;並且不影響 %0 和 %1。
(8) Setlocal Enabledelayedexpansion
開啟變量延遲,通常在“復合語句”(凡是()里的所有命令)中才會用到;
cmd在處理“復合語句”的時候,如果“復合語句”中用到了變量,會把變量的值當作復合語句之前變量的值來引用。如果在此之前變量沒有被賦值,就把它當成空值。
(9) set
設置變量,注:在為變量賦值時,等號一定要緊隨其后 ex: set isServicesStart=%1%
(10) echo
① 打開回顯或關閉回顯:echo [{on|off}]
② 顯示當前echo設置狀態:echo
③ 輸出提示信息:echo 信息內容
Echo 刪除引號: %~1
Echo 擴充到路徑: %~f1
Echo 擴充到一個驅動器號: %~d1
Echo 擴充到一個路徑: %~p1
Echo 擴充到一個文件名: %~n1
Echo 擴充到一個文件擴展名: %~x1
Echo 擴充的路徑指含有短名: %~s1
Echo 擴充到文件屬性: %~a1
Echo 擴充到文件的日期/時間: %~t1
Echo 擴充到文件的大小: %~z1
Echo 擴展到驅動器號和路徑:%~dp1
Echo 擴展到文件名和擴展名:%~nx1
Echo 擴展到類似 DIR 的輸出行:%~ftza1
Echo.:換空行
-->組合
%~dp1 - 只將 %1 擴展到驅動器號和路徑
%~nx1 - 只將 %1 擴展到文件名和擴展名
-->常用Dos命令
(1)rmdir、rd
刪除非空目錄:rmdir [/s] [/q] [drive:]path
刪除空目錄:rd [/s] [/q] [drive:]path
/s 除目錄本身外,還將刪除指定目錄下的所有 文件。用於刪除目錄樹。
/q 安靜模式,帶 /s 刪除目錄樹時不要求確認
(2) md
創建文件夾:md
(3) xcopy
XCOPY是COPY的擴展,可以把指定的目錄連文件和目錄結構一並拷貝,但不能拷貝隱藏文件和系統文件;使用時源盤符、源目標路徑名、源文件名至少指定一個;選用/s時對源目錄下及其子目錄下的所有文件進行拷貝。除非指定/e參數,否則/S不會拷貝空目錄,若不指定/S參數,則XCOPY只拷貝源目錄本身的文件,而不涉及其下的子目錄;
(4) ren
鍵入ren(空格)舊文件名(空格)新文件名
(5) del
DEL [/P] [/F] [/S] [/Q] [/A[[:]attributes]] names
names 指定一個或數個文件或目錄列表。通配符可被用來 刪除多個文件。如果指定了一個目錄,目錄中的所 有文件都會被刪除。
/P 刪除每一個文件之前提示確認。
/F 強制刪除只讀文件。
/S 從所有子目錄刪除指定文件。
/Q 安靜模式。刪除全局通配符時,不要求確認。
/A 根據屬性選擇要刪除的文件。
attributes R 只讀文件 S 系統文件 :H 隱藏文件 A 存檔文件
(6) cacls
語法:cacls FileName [/t] [/e] [/c] [/g User:permission] [/r User [...]] [/p User:permission [...]] [/d User [...]]
參數介紹
FileName:必需。顯示指定文件的 ACL。/t:更改當前目錄和所有子目錄中指定文件的 ACL。/e:編輯 ACL,而不是替換它。/c:忽略錯誤,繼續修改 ACL。/g User:permission:將訪問權限授予指定用戶。(/r user:取消指定用戶的訪問權限。/p User:permission:替代指定用戶的訪問權限。
permission參數:n 無、r 閱讀順序、w 寫入、c 更改(寫入)、F 完全控制
-->sqlcmd命令
sqlcmd -S 服務器名 -U 用戶名 -P 密碼 -d 數據庫 -i 腳本文件路徑
-Q sql執行命令
功能:
① SC可以 檢索和設置有關服務的控制信息。可以使用 SC.exe 來測試和調試服務程序。
② 可以設置存儲在注冊表中的服務屬性,以控制如何在啟動時啟動服務應用程序,以及如何將其作為后台程序運行。即更改服務的啟動狀態。
③ SC 命令還可以用來刪除系統中的無用的服務。(除非對自己電腦中的軟硬件所需的服務比較清楚,否則不建議刪除任何系統服務,尤其是基礎服務)
④ SC命令 的參數可以配置指定的服務,檢索當前服務的狀態,也可以停止和啟動服務(功能上類似NET STOP/START命令,但SC速度更快且能停止更多的服務)。
⑤ 可以創建批處理文件來調用不同的 SC 命令,以自動啟動或關閉服務序列。
語法:《相關參考》
SC [Servername] command Servicename [Optionname= Optionvalue]
query-----------查詢服務的狀態,或枚舉服務類型的狀態。
queryex---------查詢服務的擴展狀態,或枚舉服務類型的狀態。
start-----------啟動服務。
pause-----------發送 PAUSE 控制請求到服務。
interrogate-----發送 INTERROGATE 控制請求到服務。
continue--------發送 CONTINUE 控制請求到服務。
stop------------發送 STOP 請求到服務。
config----------(永久地)更改服務的配置。
description-----更改服務的描述。
failure---------更改服務失敗時所進行的操作。
qc--------------查詢服務的配置信息。
qdescription----查詢服務的描述。
qfailure--------查詢失敗服務所進行的操作。
delete----------(從注冊表)刪除服務。
create----------創建服務(將其添加到注冊表)。
control---------發送控制到服務。
sdshow----------顯示服務的安全描述符。
sdset-----------設置服務的安全描述符。
GetDisplayName--獲取服務的 DisplayName。
GetKeyName------獲取服務的 ServiceKeyName。
EnumDepend------枚舉服務的依存關
-->常用特殊符號
(1)@ 命令行回顯屏蔽符
@echo off 達到所有命令均不回顯的要求
(2)% 批處理變量引導符
引用變量用%var%,調用程序外部參數用%1至%9等等
%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %*為命令行傳遞給批處理的參數
(1) 問題的引入
(2) 准備工作
① 獲得通過持久化集成生成release版本的項目文件
② 擁有修改config的exe處理程序(本文中並未設計,根據自己項目的需要寫一個處理程序即可),因為release版本的config配置文件的參數與真實部署環境是有差別的,通過運行這個程序就可達到將release的config文件修改為真實環境的配置。這個程序可隨release版本一起發布出來
③ winform制作的批處理參數生成工具
④ 數據庫的執行腳本,這些腳本可以跟隨release版本一起發布出來
(3) 批處理上陣
① 批處理參數生成工具,讓系統部署人員,在頁面中配置好要部署的數據庫信息,以及發布的服務端口,網站部署路徑等等,最終生成批處理的參數列表,它們以空格分隔
ex:installTool.cmd F:\worktest\Project 192.168.1.18 1433 sa sa DBName "192.168.1.28@50001@192.168.1.18@10009@192.168.1.18@8021@192.168.1.18@4099@192.168.1.18@10008@127.0.0.1@10020" true true false
第1個參數“installTool.cmd”:要執行的批處理文件
第2個參數“F:\worktest\Project ”:IIS部署路徑
第3個參數 “192.168.1.18”:服務器數據庫IP地址
第4個參數 “1433 ”:數據庫端口號
第5、6個參數 “sa”:數據庫登陸用戶名和密碼
第7個參數“DBName”:數據庫名稱
第8個參數:各種服務端口信息
第9-11個參數:批處理控制信息,包括是否啟動服務,是否初始化數據庫等等
② 批處理代碼,具體解釋見注釋
:: 開啟變量延遲 @echo on setlocal enabledelayedexpansion echo 開始進行配置... :: -----------------------------------------通過參數設置變量開始------------------------------------------------ :: 通過第1個參數(%n%參數n默認從0開始)獲得當前批處理文件所在路徑(%~dp0將第一個參數擴展到驅動器號和路徑) set filePath=%~dp0 :: 通過第2個參數獲得網站部署根目錄 set rootPath=%1% :: 通過第3、4、5、6、7個參數獲得數據庫相關信息(包括數據庫IP,數據庫端口,數據庫登陸用戶名,密碼,數據庫名稱) set serverDbIp=%2% set serverDbPort=%3% set dbUser=%4% set dbPwd=%5% set dbName=%6% :: 通過第8個參數,獲得服務相關配置的字符串 set serviceConfig=%7% :: 通過第9個參數,獲得是否自動啟動服務 set isStartService=%8% :: 通過第10個參數,獲得是否執行數據庫腳本 set isExecuteDbScripts=%9% :: shift /n,如果傳入參數過多,可以在用參數給變量賦值后,使用shift使參數向前移動 shift /1 :: 通過第11個參數,獲得是否顯示執行過程 set isShowProcess=%9% :: -----------------------------------------通過參數設置變量結束------------------------------------------------ :: -----------------------------------------文件相關操作開始---------------------------------------------------- :: 創建配置中指定的部署路徑文件夾 rmdir /s /q %FilePath% md %FilePath% :: 拷貝release版本文件到配置中指定的部署路徑 xcopy %str%*.* %FilePath% /E /R /Y :: 修改自動生成的文件夾名稱 ren %FilePath%\PlatWebService_deploy PlatWebService ren %FilePath%\WebSite_deploy WebSite :: 把指定IP、服務等相關參數傳入,執行替換config文件的程序 %FilePath%\ReplaceXmlValues.exe %FilePath% "%ServiceIp%@%ServicePort%@%DBUser%@%DBPwd%@%DBName%@%ServicePath%" :: -----------------------------------------文件相關操作結束---------------------------------------------------- :: -----------------------------------------windows服務啟動開始------------------------------------------------- if "%isStartService%"=="false" goto BeginExecuteDbScripts @echo Windows服務開始啟動 :: 停止並刪除之前的服務 @sc stop "DataExchangeService" @sc delete "DataExchangeService" @sc stop "QueueService" @sc delete "QueueService" :: 調用 寫好的批處理來安裝啟動相應的服務 call %FilePath%\DataExchange\Installer\installDataExchangeService.bat call %FilePath%\WebSite\Queue\Installer\installQueueService.bat :: -----------------------------------------windows服務啟動結束------------------------------------------------- :: -----------------------------------------數據庫腳本執行開始-------------------------------------------------- :BeginExecuteDbScripts if "%isExecuteDbScripts%"=="false" goto End @echo 數據庫腳本開始執行 :: 設置腳本所在路徑 set scriptPath=%filePath%Scripts\ :: 創建數據庫(按照默認方式創建),也可按照下方進行對數據庫創建的詳細參數 :: create database bbsDB on(name='bbsDB_data',filename='D:\project\bbsDB_data.mdf',size=10,filegrowth=20%) log on(name='bbsDB_log',filename='D:\project\bbsDB_log.ldf',size=3,maxsize=20,filegrowth=10%) sqlcmd -S %serverDbIp%,%serverDbPort% -U %dbUser% -P %dbPwd% -Q "if DB_ID('%dbName%') is not null drop database %dbName% create database %dbName%" :: 開始執行數據庫腳本 sqlcmd -S %serverDbIp%,%serverDbPort% -U %dbUser% -P %dbPwd% -d %DBName% -i %scriptPath%DDL.sql sqlcmd -S %serverDbIp%,%serverDbPort% -U %dbUser% -P %dbPwd% -d %DBName% -i %scriptPath%insert-ModelDictionary.sql sqlcmd -S %serverDbIp%,%serverDbPort% -U %dbUser% -P %dbPwd% -d %DBName% -i %scriptPath%insert-baseData.sql sqlcmd -S %serverDbIp%,%serverDbPort% -U %dbUser% -P %dbPwd% -d %DBName% -i %scriptPath%insert-exchange-appParams.sql sqlcmd -S %serverDbIp%,%serverDbPort% -U %dbUser% -P %dbPwd% -d %DBName% -i %scriptPath%insert-exchange-appParams.sql :: -----------------------------------------數據庫腳本執行結束-------------------------------------------------- :End pause
注:各位園友,如果你在系統部署方面還有什么好方法,不妨討論一下,大家共同學習;如果覺得本文對你有些幫助的話,就幫我右下角推薦一下!