.NET跨平臺實踐:再談用C#開發Linux守護程序 — 完整篇
Linux守護程序是Linux的後臺服務程序,相當於Windows服務,對於為Linux開發服務程式的朋友來說,Linux守護程序相關技術是必不可少的,因為這個技術不僅僅是為了開發守護程序,還可以拓展到多程序,父子程序檔案描述符共享,父子程序通訊、控制等方面,是實現Linux大型服務的基礎技術之一。
去年我也曾寫了一篇關於守護程序的帖子,名字叫《.NET跨平臺實踐:用C#開發Linux守護程序》,這篇文章的的確確實現了一個Daemon,不過,它有一個弱點,不能執行多執行緒!
這篇帖子的目的就是進一步完善,讓我們寫出一個功能完整,可以用於生產環節的基本的守護程序。
先帖程式碼(假設專案名是daemon):
1 using System; 2 using System.Threading; 3 using System.Timers; 4 using System.Runtime.InteropServices; 5 using System.IO; 6 using System.Text; 7 8 9 /******************************************** 10 * 一個完整的linux daemon示例,作者宇內流雲 * 11 ********************************************/ 12 13 namespace daemon 14 { 15 class Program 16 { 17 18 const string DaemonTag = "--daemon."; 19 static void Main(string[] args) 20 { 21 // 判斷是否已經進入Daemon狀態,如果是,就直接執行後臺主函式 22 if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DaemonTag)) == false) 23 { 24 Environment.SetEnvironmentVariable(DaemonTag, null); 25 DaemonMain(args); 26 return; 27 } 28 29 30 // 如果還沒有進入daemon狀態,就作daemon處理 31 ///////////////////////////////////////////////////// 32 33 int pid = fork(); 34 if (pid != 0) exit(0); 35 setsid(); 36 pid = fork(); 37 if (pid != 0) exit(0); 38 umask(0); 39 40 41 // 這兒已經進入“守護程序”工作狀態了! 42 43 // 關閉所有開啟的檔案描述符 44 int max = open("/dev/null", 0); 45 for (var i = 0; i <= max; i++) { close(i); } 46 47 48 // 設定標記,防止重複執行進入 49 Environment.SetEnvironmentVariable(DaemonTag,"yes"); 50 51 52 //為execp引數重組引數 53 var args1 = args == null ? new string[2] : new string[args.Length + 2]; 54 55 args1[0] = "MyDaemon"; 56 args1[1] = Path.Combine(Environment.CurrentDirectory, Thread.GetDomain().FriendlyName); 57 58 if (args1.Length > 2) 59 { 60 for (var i = 0; i < args.Length; i++) 61 { args1[i + 2] = args[i]; } 62 } 63 64 65 //守護狀態下重新載入和執行本程式 66 execvp("mono", args1); 67 68 } 69 70 71 /// <summary> 72 /// Daemon工作狀態的主方法 73 /// </summary> 74 /// <param name="aargs"></param> 75 static void DaemonMain(string[] aargs) 76 { 77 //啟動一個執行緒去處理一些事情 78 (new Thread(DaemonWorkFunct) { IsBackground = true }).Start(); 79 80 81 //daemon時,控制檯輸入、輸出流已經關閉 82 //請不要再用Console.Write/Read等方法 83 84 //阻止daemon程序退出 85 (new AutoResetEvent(false)).WaitOne(); 86 87 } 88 89 90 static FileStream fs; 91 static int count = 0; 92 static void DaemonWorkFunct() { 93 fs = File.Open("/tmp/daemon.txt", FileMode.OpenOrCreate); 94 var t = new System.Timers.Timer() { Interval = 1000 }; 95 t.Elapsed += OnElapsed; 96 t.Start(); 97 } 98 private static void OnElapsed(object sender, ElapsedEventArgs e) 99 { 100 var s = DateTime.Now.ToString("yyy-MM-dd HH:mm:ss") + "\n"; 101 var b = Encoding.ASCII.GetBytes(s); 102 fs.Write(b, 0, b.Length); 103 fs.Flush(); 104 105 count++; 106 if (count > 100) { 107 fs.Close(); 108 fs.Dispose(); 109 exit(0); 110 } 111 112 } 113 114 115 116 [DllImport("libc", SetLastError = true)] 117 static extern int fork(); 118 119 [DllImport("libc", SetLastError = true)] 120 static extern int setsid(); 121 122 [DllImport("libc", SetLastError = true)] 123 static extern int umask(int mask); 124 125 [DllImport("libc", SetLastError = true)] 126 static extern int open([MarshalAs(UnmanagedType.LPStr)]string pathname, int flags); 127 128 [DllImport("libc", SetLastError = true)] 129 static extern int close(int fd); 130 131 [DllImport("libc", SetLastError = true)] 132 static extern int exit(int code); 133 134 [DllImport("libc", SetLastError = true)] 135 static extern int execvp([MarshalAs(UnmanagedType.LPStr)]string file, string[] argv); 136 137 138 } 139 140 }
以上程式碼的工作過程是:判斷程式自身是否已經處於daemon(後臺服務)狀態,如果是,就直接開始具體的服務工作(開啟一個執行緒,每秒向 /tmp/daemon.txt中列印一行字元,100次後退出),如果不是daemon狀態,就進入Daemon處理,使之進入daemon工作狀態。
以上程式碼編譯後,會生成一個叫 daemon.exe 的程式,當然,這個程式是為linux開發的,不能在windows上執行。現在,我把它放到linux上面,用mono daemon.exe命令啟動它。
這時我們可以看到這個程式啟動後,控制檯上沒有任何輸出,也沒有阻塞控制檯,那麼,在哪兒能找到它呢?用 ps -ef命令看看,原來它真的已經在後臺執行起來了。
再看看這個後臺程序是否完成了它的工作:cat /tmp/daemon.txt 檢視檔案內容:
從生成的檔案內容看,這個Daemon服務程式的確按我們的設計意圖,每秒鐘向/tmp/daemon.txt列印了一行字元。
注:本文為 宇內流雲 (郵箱:[email protected])原創作品,c#開發Linux守護程序的完整技術亦屬首發,如需轉載,請註明出處和作者。