一周一話題之三(Windows服務、批處理項目實戰)


一、 Windows服務

1. windows service介紹

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服務。

回到導航

2. 使用步驟

(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) 調試

① 安裝啟動服務

② 設置斷點

③ 附加進程

回到導航

3. 項目實例--數據上傳下載服務

(1) 問題引入

cs程序與bs程序數據的交互,我們采用的是上傳下載機制;bs系統維護管理數據,cs系統無論是需要從bs系統下載數據,還是對其上傳數據,首先都會生成xml文檔,xml文檔中包含了上傳下載類型、要執行的sql命令等等。cs系統以socket通信的方式把xml發送給bs系統,在bs系統中解析xml文檔,如果接到了下載的要求,就根據需要再生成xml以同樣方式發給cs系統,如果接到上傳要求,就更新數據庫。

image

(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

        
    }

  

二、 批處理運用

1. 批處理介紹

批處理文件(Batch File,簡稱 BAT文件)是一種在DOS 下最常用的可執行文件。它具有靈活的操縱性,可適應各種復雜的計算機操作。所謂的批處理,就是按規定的順序自動執行若干個指定的DOS命令或程序。即是把原來一個一個執行的命令匯總起來,成批的執行,而程序文件可以移植到其它電腦中運行,因此可以大大節省命令反復輸入的繁瑣。同時批處理文件還有一些編程的特點,可以通過擴展參數來靈活的控制程序的執行,所以在日常工作中非常實用。

回到導航

2. 基本語法

-->常用批處理命令

(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)rmdirrd

刪除非空目錄: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只拷貝源目錄本身的文件,而不涉及其下的子目錄;

《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可以 檢索和設置有關服務的控制信息。可以使用 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  %*為命令行傳遞給批處理的參數

回到導航

3. 項目實例--項目部署

(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

  注:各位園友,如果你在系統部署方面還有什么好方法,不妨討論一下,大家共同學習;如果覺得本文對你有些幫助的話,就幫我右下角推薦一下!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM