• .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") + "
    ";
    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打印了一行字符。

  • 相关阅读:
    打造一个有感觉的vim(四)
    Sql Server中不常用的表运算符之UNPIVOT
    Sql Server中不常用的表运算符之PIVOT
    Sql Server中不常用的表运算符之APPLY(2)
    Sql Server中不常用的表运算符之APPLY(1)
    Sql Server隔离级别(2)
    Sql Server隔离级别(1)
    修改maven仓库位置
    Cannot change version of project facet Dynamic web的解决方法
    为 HTML 添加新元素
  • 原文地址:https://www.cnblogs.com/microtiger/p/7927612.html
Copyright © 2020-2023  润新知