最近做了一些.NET Core的程式,有在Windows下運行的 有在CentOS 下運行的,Windows下運行的還好,對Windows下還算比較熟悉了,但CentOS 下 每次都是找筆記支持命令 於是今天晚上就乾脆把以.NET Core程式已服務形式啟動的代碼封裝了下,代碼 主要是便於安裝。 我 ...
最近做了一些.NET Core的程式,有在Windows下運行的 有在CentOS 下運行的,Windows下運行的還好,對Windows下還算比較熟悉了,但CentOS 下 每次都是找筆記支持命令
於是今天晚上就乾脆把以.NET Core程式已服務形式啟動的代碼封裝了下,代碼 主要是便於安裝。
我們寫好一個程式後 然後要已服務啟動 每次都是需要輸入命令,對於我這種不記單詞的人來說太痛苦了,
當然windows環境下命令到是很簡單。
廢話不說了 先給大家看下效果
CentOS 下的運行效果是這樣的
dotnet ConsoleApp2.dll -i
進行服務的安裝
dotnet ConsoleApp2.dll -u
進行服務的卸載
安裝完成後使用 ps x 進行進程查看 是否存在。
Window 下運行效果
安裝完成,我們在到window 服務管理工具去看看
發現已經多了一個服務,說明安裝成功了
然後再來分析代碼
首先CentOS 下讓程式以服務形式啟動 我們需要做以下工作
1、寫service文件
2、systemctl 啟動service
service文件內容如下:
[Unit] Description="服務說明" [Service] Type=simple GuessMainPID=true WorkingDirectory=//項目路徑 StandardOutput=journal StandardError=journal ExecStart=/usr/bin/dotnet 項目文件dll //啟動指令 Restart=always [Install] WantedBy=multi-user.target
參考:http://www.jinbuguo.com/systemd/systemd.service.html
在使用systemctl 命令使其生效
systemctl enable *.service 使自啟動生效
systemctl start *.service 立即啟動項目服務
那麼結合代碼就好說了
var servicepath = $"/etc/systemd/system/{serviceName}.service";// 創建服務文件 System.IO.File.WriteAllText(servicepath, $"[Unit]{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"Description={serviceDescription}{Environment.NewLine}");// 服務描述 System.IO.File.AppendAllText(servicepath, $"[Service]{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"Type=simple{Environment.NewLine}");//設置進程的啟動類型, 必須設為 simple, forking, oneshot, dbus, notify, idle 之一。 System.IO.File.AppendAllText(servicepath, $"GuessMainPID=true{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"WorkingDirectory={workingDirectory}{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"StandardOutput=journal{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"StandardError=journal{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"ExecStart=/usr/bin/dotnet {System.IO.Path.GetFileName(filePath)}{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"Restart=always{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"[Install]{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"WantedBy=multi-user.target{Environment.NewLine}"); Console.WriteLine(StartProcess("/usr/bin/systemctl", $"enable {serviceName}.service")); Console.WriteLine(StartProcess("/usr/bin/systemctl", $"start {serviceName}.service")); Console.WriteLine($"Unix 下安裝服務完成,如果失敗請手動執行以下命令完成安裝:"); Console.WriteLine($"systemctl enable {serviceName}.service //使自啟動生效"); Console.WriteLine($"systemctl start {serviceName}.service //立即啟動項目服務"); Console.WriteLine($"systemctl status {serviceName}.service -l //查看服務狀態");View Code
這樣 我們就可以安裝並啟動服務了,
那麼windows下麵 其實根據簡單 ,就一個命令
sc create 服務名稱 binpath=啟動文件 就可以了
當然 需要添加一個ServiceBase的子類,這裡我搞了一個通用的
代碼如下
/// <summary> /// Windows 服務 /// </summary> class WinService : ServiceBase { private static string WinServiceName; private static Action<string[]> StartRun; public WinService() { ServiceName = WinServiceName; } /// <summary> /// 啟動服務 /// </summary> /// <param name="args"></param> protected override void OnStart(string[] args) { StartRun(args); } public static void Config(Action<string[]> startRun,string serviceName) { WinServiceName = serviceName; StartRun = startRun; } }View Code
這裡為什麼是Action<string[]> StartRun; 了 就是因為讓服務啟動後 執行實際要執行的代碼。
比如demo
class Program { [Description("這是一個測試服務")] [DisplayName("測試服務")] static void Main(string[] args) { DotNet.Install.ServiceInstall.Run("test", args, Run); } static void Run(string[] args) { HttpServer httpServer = new HttpServer(); httpServer.Start(); } }View Code
服務啟動後是執行的Run方法。
WIndows下的安裝代碼那就是
/// <summary> /// Windows 環境下運行 /// </summary> /// <param name="filePath">啟動文件</param> /// <param name="serviceName">服務名稱</param> /// <param name="displayName">顯示名稱</param> /// <param name="serviceDescription">服務說明</param> /// <param name="args"></param> /// <param name="startRun">實際運行方法</param> /// <returns></returns> static bool RunWin(string filePath, string serviceName,string displayName, string serviceDescription, string[] args, Action<string[]> startRun) { if (args.Length == 1) { var workingDirectory = System.IO.Path.GetDirectoryName(filePath); if (System.IO.File.Exists(System.IO.Path.ChangeExtension(filePath, ".exe")))//判斷是否存在exe ,如果存在則啟動exe { filePath = System.IO.Path.ChangeExtension(filePath, ".exe");//修改尾碼名為exe } else { var dotnetPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet", "dotnet.exe");//查找預設軟體安裝目錄下的dotnet.exe if (System.IO.File.Exists(dotnetPath)) { filePath = $"\\\"{dotnetPath}\\\" \\\"{filePath}\\\""; } else { dotnetPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "dotnet", "dotnet.exe");//為了防止有的人只裝了x86位的,所以查找x86下的軟體安裝目錄下的dotnet.exe if (System.IO.File.Exists(dotnetPath)) { filePath = $"\\\"{dotnetPath}\\\" \\\"{filePath}\\\""; } else { Console.WriteLine($"系統無法定位DotNet Core的安裝目錄。"); return true; } } } if (args[0].Equals("-i", StringComparison.OrdinalIgnoreCase)) { if (!AdminRestartApp(filePath, args)) { return true; } Console.WriteLine(StartProcess("sc.exe", $"create {serviceName} binpath=\"{filePath}\" start=auto DisplayName=\"{displayName}\"")); Console.WriteLine($"Windows 下安裝服務完成,如果失敗請手動執行以下命令完成安裝:"); Console.WriteLine($"sc create {serviceName} binpath=\"{filePath}\" start=auto DisplayName=\"{displayName}\" //安裝服務"); using (var service = Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{serviceName}", true)) { service.SetValue("Description", serviceDescription); } return true; } else if (args[0].Equals("-u", StringComparison.OrdinalIgnoreCase)) { if (!AdminRestartApp(filePath, args)) { return true; } Console.WriteLine(StartProcess("sc.exe", $"delete {serviceName}")); Console.WriteLine($"Windows 下卸載服務完成,如果失敗請手動執行以下命令完成卸載:"); Console.WriteLine($"sc delete {serviceName} //卸載服務"); return true; } } WinService.Config(startRun, serviceName); using (var service = new WinService()) { System.ServiceProcess.ServiceBase.Run(service); } return false; }View Code
這樣我們就完美了
在放上整個代碼:
/************************************************************** * Copyright (C) 2019 www.hnlyf.com 版權所有(盜版必究) * * 作者: 李益芬(QQ 12482335) * 創建時間: 2019/09/22 22:40:15 * 文件名: * 描述: * * 修改歷史 * 修改人: * 時間: * 修改說明: * **************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Linq; using System.ComponentModel; using Microsoft.Win32; namespace DotNet.Install { /// <summary> /// 服務安裝 /// </summary> public static class ServiceInstall { /// <summary> /// Windows 環境下運行 /// </summary> /// <param name="filePath">啟動文件</param> /// <param name="serviceName">服務名稱</param> /// <param name="displayName">顯示名稱</param> /// <param name="serviceDescription">服務說明</param> /// <param name="args"></param> /// <param name="startRun">實際運行方法</param> /// <returns></returns> static bool RunWin(string filePath, string serviceName,string displayName, string serviceDescription, string[] args, Action<string[]> startRun) { if (args.Length == 1) { var workingDirectory = System.IO.Path.GetDirectoryName(filePath); if (System.IO.File.Exists(System.IO.Path.ChangeExtension(filePath, ".exe")))//判斷是否存在exe ,如果存在則啟動exe { filePath = System.IO.Path.ChangeExtension(filePath, ".exe");//修改尾碼名為exe } else { var dotnetPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet", "dotnet.exe");//查找預設軟體安裝目錄下的dotnet.exe if (System.IO.File.Exists(dotnetPath)) { filePath = $"\\\"{dotnetPath}\\\" \\\"{filePath}\\\""; } else { dotnetPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "dotnet", "dotnet.exe");//為了防止有的人只裝了x86位的,所以查找x86下的軟體安裝目錄下的dotnet.exe if (System.IO.File.Exists(dotnetPath)) { filePath = $"\\\"{dotnetPath}\\\" \\\"{filePath}\\\""; } else { Console.WriteLine($"系統無法定位DotNet Core的安裝目錄。"); return true; } } } if (args[0].Equals("-i", StringComparison.OrdinalIgnoreCase)) { if (!AdminRestartApp(filePath, args)) { return true; } Console.WriteLine(StartProcess("sc.exe", $"create {serviceName} binpath=\"{filePath}\" start=auto DisplayName=\"{displayName}\"")); Console.WriteLine($"Windows 下安裝服務完成,如果失敗請手動執行以下命令完成安裝:"); Console.WriteLine($"sc create {serviceName} binpath=\"{filePath}\" start=auto DisplayName=\"{displayName}\" //安裝服務"); using (var service = Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{serviceName}", true)) { service.SetValue("Description", serviceDescription); } return true; } else if (args[0].Equals("-u", StringComparison.OrdinalIgnoreCase)) { if (!AdminRestartApp(filePath, args)) { return true; } Console.WriteLine(StartProcess("sc.exe", $"delete {serviceName}")); Console.WriteLine($"Windows 下卸載服務完成,如果失敗請手動執行以下命令完成卸載:"); Console.WriteLine($"sc delete {serviceName} //卸載服務"); return true; } } WinService.Config(startRun, serviceName); using (var service = new WinService()) { System.ServiceProcess.ServiceBase.Run(service); } return false; } /// <summary> /// Unix環境下運行 /// </summary> /// <param name="filePath"></param> /// <param name="serviceName"></param> /// <param name="serviceDescription"></param> /// <param name="args"></param> /// <param name="startRun"></param> /// <returns></returns> static bool RunUnix(string filePath, string serviceName, string serviceDescription, string[] args, Action<string[]> startRun) { var workingDirectory = System.IO.Path.GetDirectoryName(filePath); if (args.Length == 1) { if (args[0].Equals("-i", StringComparison.OrdinalIgnoreCase)) { var servicepath = $"/etc/systemd/system/{serviceName}.service";// 創建服務文件 System.IO.File.WriteAllText(servicepath, $"[Unit]{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"Description={serviceDescription}{Environment.NewLine}");// 服務描述 System.IO.File.AppendAllText(servicepath, $"[Service]{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"Type=simple{Environment.NewLine}");//設置進程的啟動類型, 必須設為 simple, forking, oneshot, dbus, notify, idle 之一。 System.IO.File.AppendAllText(servicepath, $"GuessMainPID=true{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"WorkingDirectory={workingDirectory}{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"StandardOutput=journal{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"StandardError=journal{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"ExecStart=/usr/bin/dotnet {System.IO.Path.GetFileName(filePath)}{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"Restart=always{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"[Install]{Environment.NewLine}"); System.IO.File.AppendAllText(servicepath, $"WantedBy=multi-user.target{Environment.NewLine}"); Console.WriteLine(StartProcess("/usr/bin/systemctl", $"enable {serviceName}.service")); Console.WriteLine(StartProcess("/usr/bin/systemctl", $"start {serviceName}.service")); Console.WriteLine($"Unix 下安裝服務完成,如果失敗請手動執行以下命令完成安裝:"); Console.WriteLine($"systemctl enable {serviceName}.service //使自啟動生效"); Console.WriteLine($"systemctl start {serviceName}.service //立即啟動項目服務"); Console.WriteLine($"systemctl status {serviceName}.service -l //查看服務狀態"); return true; } else if (args[0].Equals("-u", StringComparison.OrdinalIgnoreCase)) { var servicepath = $"/etc/systemd/system/{serviceName}.service"; Console.WriteLine(StartProcess("/usr/bin/systemctl", $"disable {serviceName}.service")); if (System.IO.File.Exists(servicepath)) { System.IO.File.Delete(servicepath); } Console.WriteLine($"Unix 下卸載服務完成,如果失敗請手動執行以下命令完成卸載"); Console.WriteLine($"systemctl disable {serviceName}.service //使自啟動失效"); Console.WriteLine($"rm -y {servicepath} //刪除服務文件"); return true; } } startRun(args); System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); return false; } /// <summary> /// 運行程式,如果有命令-i或者-u則執行安裝或卸載,否則執行<paramref name="startRun"/> /// <para>請在Main函數中調用,服務顯示名稱請在Main函數增加[DisplayName()]特性,服務說明[Description]特性。</para> /// <para>Windiows下需要依賴System.ServiceProcess.ServiceController</para> /// <para>-i 表示安裝服務</para> /// <para>-u 表示卸載服務</para> /// </summary> /// <param name="serviceName">服務名稱</param> /// <param name="args">啟動程式的參數</param> /// <param name="startRun">實際程式運行的函數</param> public static void Run(string serviceName, string[] args, Action<string[]> startRun) { bool finish = false; string serviceDescription = serviceName; string displayName = serviceName; string filePath = string.Empty; if (args.Length == 1) { StackFrame frame = new StackFrame(1); if (string.IsNullOrWhiteSpace(serviceName)) { serviceName=frame.GetMethod().DeclaringType.Assembly.GetName().Name; } var displayNames = frame.GetMethod().GetCustomAttributes(typeof(DisplayNameAttribute), true); if (displayNames.Length > 0) { displayName = (displayNames[0] as DisplayNameAttribute).DisplayName; } var descriptions = frame.GetMethod().GetCustomAttributes(typeof(DescriptionAttribute), true); if (descriptions.Length > 0) { serviceDescription = (descriptions[0] as DescriptionAttribute).Description; } filePath = frame.GetMethod().DeclaringType.Assembly.Location; } if (Environment.OSVersion.Platform == PlatformID.Win32NT) { finish = RunWin(filePath, serviceName, displayName, serviceDescription, args, startRun); } else { finish = RunUnix(filePath, serviceName, serviceDescription, args, startRun); } } static string StartProcess(string fileName, string arguments) { string output = string.Empty; using (System.Diagnostics.Process process = new System.Diagnostics.Process()) { process.StartInfo = new ProcessStartInfo { UseShellExecute = false, Arguments = arguments, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, WorkingDirectory = Environment.CurrentDirectory, FileName = fileName, }; process.Start();//啟動程式 process.WaitForExit();//等待程式執行完退出進程 output = process.StandardOutput.ReadToEnd(); process.Close(); } return output; } static bool AdminRestartApp(string filePath,string[] args) { if (!IsAdmin()) { Console.WriteLine("重新已管理員啟動" + filePath); ProcessStartInfo startInfo = new ProcessStartInfo { UseShellExecute = true, Arguments = string.Join(" ", args), WorkingDirectory = Environment.CurrentDirectory, FileName = filePath, Verb = "runas" }; try { Process.Start(startInfo); } catch (Exception ex) { Console.WriteLine($"重新已管理員啟動失敗:{ex}"); } return false; } return true; } /// <summary> /// 判斷是否是處於Administrator下允許 /// </summary> /// <returns></returns> static bool IsAdmin() { using (System.Security.Principal.WindowsIdentity wi = System.Security.Principal.WindowsIdentity.GetCurrent()) { System.Security.Principal.WindowsPrincipal wp = new System.Security.Principal.WindowsPrincipal(wi); return wp.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator); } } } }View Code
最後把dll 上轉到nuget 上面 DotNetCN.Install
源代碼放到github上面:https://github.com/hnlyf/DotNet.Install