让你的C#程序成为守护进程

起因

本文主要是通过P/Invoke的方式调用系统API,让c#程序成为守护进程.

使用pstree查看进程间的关系

通过pstree查看进程间的关系

C#调用系统API实现守护进程

using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace linuxapp
{
    class MyDaemon
    {
        [DllImport("libc", SetLastError = true)]
        private static extern int close(int fd);

        [DllImport("libc", SetLastError = true)]
        private static extern int fork();

        [DllImport("libc", SetLastError = true)]
        private static extern int exit(int status);

        [DllImport("libc", SetLastError = true)]
        private static extern int setsid();

        [DllImport("libc", SetLastError = true)]
        private static extern int chdir(string path);

        [DllImport("libc", SetLastError = true)]
        private static extern int umask(int cmask);

        [DllImport("libc", SetLastError = true)]
        private static extern int syslog(int priority, string message);

        //将syslog.h文件中, log宏定义改为常量
        const int LOG_EMERG = 0;	/* system is unusable */
        const int LOG_ALERT = 1;	/* action must be taken immediately */
        const int LOG_CRIT = 2;	        /* critical conditions */
        const int LOG_ERR = 3;	        /* error conditions */
        const int LOG_WARNING = 4;	/* warning conditions */
        const int LOG_NOTICE = 5;	/* normal but significant condition */
        const int LOG_INFO = 6;	        /* informational */
        const int LOG_DEBUG = 7;	/* debug-level messages */

        /// 
        /// 设置为守护进程程序
        /// 
        /// 
        static int SetDaemon()
        {
            int pid = fork();  //fork创建当前进程的副本
            if (pid < 0 return -1 if pid> 0)
            {
                exit(0);       //让父进程退出,让子进程成为孤儿进程,由系统进程领养,可以通过pstree查看
            }
            else
            {
                setsid();      //设置一个新的会话
                chdir("/");    //设置当前程序的工作目录为根目录
                umask(0);      //设置umask为0
                close(0);      //关闭标准输入文件STDIN_FILENO
                close(1);      //关闭标准输出文件STDOUT_FILENO
                close(2);      //关闭标准错误输出文件STDERR_FILENO
            }
            return 0;
        }

        /// 
        /// 真正要执行的实现代码
        /// 由于在SetDaemon函数中,将标准的输入输出文件及错误文件都关闭了,所以在Run函数中,是不能直接使用Console.WriteLine,可以使用:
        /// 1.将日志信息通过syslog输出到系统日志中
        /// 2.将日志信息写入到当前程序的日志(可使用nlog等日志组件)
        /// 
        static void Run()
        {
            //这里只是模拟
            while (true)
            {
                Thread.Sleep(30 * 1000);     //休眠30秒,执行一次
                syslog(LOG_INFO, "MyDaemon(csharp) is running");  //记录日志,查看cat /var/log/messages
            }
        }

        static void Main(string[] args)
        {
            int result = SetDaemon();
            if (result == -1)
            {
                return;
            }
            Run();
        }
    }
}

编译和运行(使用Mono)

#mcs 是mono的编译器
mcs MyDaemon.cs   

#通过mono运行c#程序
mono MyDaemon.exe

查看进程关系和日志

#pstree 查看进程树
pstree

查看mono运行的c#程序是不是守护进程

#查看系统日志(应该是Centos,后面换为Ubuntu Server)
cat /var/log/messages

C#守护进程,输出的日志

#如何关闭守护进程
#1.先查找mono的进程id
ps -u root(当前用户) | grep mono

#2,根据进程id,关闭进程
kill -9 18197

查找mono所运行的c#守护进程Id

支持dotnet core及dotnet 5/6(2021-08-10)

在2017年,.Net Core还不够火,即使在2021年,还是有一部分项目还没有迁移到.Net Core上,更别说迁移到.Net 5,其实上边守护进程的代码,不做任何改变就可以在.Net 5/6上运行.

尤其到.Net 5/6上,使用更方便,因为可以发布单文件.不用安装.Net 运行时/也不用安装Mono.还有一种就是NativeAOT这个暂不支持交叉编译.如果需要NativeAOT的可以参考这篇:

//在.Net 6 生成Native库,让C/C++调用 
//https://www.qiufengblog.com/articles/dotnet-6-natvie-invoke.html

生成单文件及裁剪之后的文件大小:

dotnet 单文件发布,裁剪后的文件大小

生成单文件及裁剪的工程文件配置:


  Exe
  net6.0
  
    true
  
  true 
  link
  false

将文件上传到Linux服务器上:

#因为文件是在Windows生成的,在Linux上没法运行,要给文件可执行权限
chmod +x Daemon

#继续使用pstree查看Linux进程树
pstree

运行.Net 6 守护进程,看到进程变为Systemd的子进程

具体有没有运行成功,还是查看一下系统日志有没有写入成功.

由于Ubuntu 20.04系统日志文件名换为:syslog

#ubuntu 20.04查看系统日志
cat /var/log/syslog

运行.Net 6 守护进程,查看是否写入系统日志文件中

原文链接:,转发请注明来源!