.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打印了一行字符。

注:本文為 宇內流雲 (郵箱:j66x@163.com)原創作品,c#開發Linux守護進程的完整技術亦屬首發,如需轉載,請注明出處和作者

 


免責聲明!

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



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