.NET跨平台实践:再谈用C#开发Linux守护进程 — 完整篇

Linux守护进程是Linux的后台服务进程,相当于Windows服务,对于为Linux开发服务程序的朋友来说,Linux守护进程相关技术是必不可少的,因为这个技术不仅仅是为了开发守护进程,还可以拓展到多进程,父子进程文件描述符共享,父子进程通讯、控制等方面,是实现Linux大型服务的基础技术之一。

去年我也曾写了一篇关于守护进程的帖子,名字叫《.NET跨平台实践:用C#开发Linux守护进程》,这篇文章的的确确实现了一个Daemon,不过,它有一个弱点,不能运行多线程!

这篇帖子的目的就是进一步完善,让我们写出一个功能完整,可以用于生产环节的基本的守护进程。

先帖代码(假设项目名是daemon):

using System;
using System.Threading;
using System.Timers;
using System.Runtime.InteropServices;
using System.IO;
using System.Text;
 
  
/********************************************
 * 一个完整的linux daemon示例,作者宇内流云 *
 ********************************************/
  
namespace daemon
{
    class Program
    {

        const string DaemonTag = "--daemon.";
        static void Main(string[] args)
        {
            // 判断是否已经进入Daemon状态,如果是,就直接执行后台主函数
            if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DaemonTag)) == false)
            {
                Environment.SetEnvironmentVariable(DaemonTag, null);
                DaemonMain(args);
                return;
            }


            // 如果还没有进入daemon状态,就作daemon处理
            /////////////////////////////////////////////////////

            int pid = fork();
            if (pid != 0) exit(0);
            setsid();
            pid = fork();
            if (pid != 0) exit(0);
            umask(0);


            // 这儿已经进入“守护进程”工作状态了!

            // 关闭所有打开的文件描述符
            int nul_fd = open("/dev/null", 0);
            for (var i = 0; i <= nul_fd; i++) {
                   if(i<3)
                       dup2(nul_fd, i);
                   else
                       close(i); 
            }
  
  
             // 设置标记,防止重复运行进入(.net core应使用libc的setenv函数进行设置)
             Environment.SetEnvironmentVariable(DaemonTag,"yes");
 
 
             //为execp参数重组参数
             var args1 = args == null ? new string[2] : new string[args.Length + 2];
 
             args1[0] = "MyDaemon";
             args1[1] = Path.Combine(Environment.CurrentDirectory, Thread.GetDomain().FriendlyName);
 
            if (args1.Length > 2)
            {
                for (var i = 0; i < args.Length; i++)
                { args1[i + 2] = args[i]; }
            }
 
            //守护状态下重新加载和运行本程序
            execvp("mono", args1);
         }
  
        /// <summary>
        /// 守护进程的主方法
        /// </summary>
        /// <param name="aargs"></param>
        static void DaemonMain(string[] aargs)
        {
            // 启动一个线程去做正经事情。本示例程序是定时向文件写入数据
            (new Thread(DaemonWorkFunct) { IsBackground = true }).Start();
 
             // 进入守护状态后,控制台输入、输出流已经关闭
             // 所以,不要再用Console.Write/Read等方法
 
             // 阻止守护进程退出
             (new AutoResetEvent(false)).WaitOne();
 
         }
 
 
         static FileStream fs;
         static int count = 0;
         static void DaemonWorkFunct() {
             fs = File.Open("/tmp/daemon.txt", FileMode.OpenOrCreate);
             var t = new System.Timers.Timer() { Interval = 1000 };
             t.Elapsed += OnElapsed;
             t.Start();
         }
         private static void OnElapsed(object sender, ElapsedEventArgs e)
         {
            var s = DateTime.Now.ToString("yyy-MM-dd HH:mm:ss") + "\n";
            var b = Encoding.ASCII.GetBytes(s);
            fs.Write(b, 0, b.Length);
            fs.Flush();

            count++;
            if (count > 100) {
                fs.Close();
                fs.Dispose();
                exit(0);
            }

        }


 
        [DllImport("libc", SetLastError = true)]
        static extern int fork();
 
        [DllImport("libc", SetLastError = true)]
        static extern int setsid();
 
        [DllImport("libc", SetLastError = true)]
        static extern int umask(int mask);
 
        [DllImport("libc", SetLastError = true)]
        static extern int open([MarshalAs(UnmanagedType.LPStr)]string pathname, int flags);
 
        [DllImport("libc", SetLastError = true)]
        static extern int close(int fd);
 
        [DllImport("libc", SetLastError = true)]
        static extern int exit(int code);

        [DllImport("libc", SetLastError = true)]
        static extern int execvp([MarshalAs(UnmanagedType.LPStr)]string file, string[] argv);

        [DllImport("libc", SetLastError = true)]
        static extern int dup2(int oldfd, int newfd);
 
    }
 
}

以上代码的工作过程是:判断程序自身是否已经处于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守护进程的完整技术亦属本人首发,如需转载,请注明出处和作者

 

posted @ 2017-04-25 11:40  宇内流云  阅读(9600)  评论(32编辑  收藏  举报