让你的c#程序成为守护进程
起因
本文主要是通过P/Invoke的方式调用系统API,让c#程序成为守护进程.使用pstree查看进程间的关系

具体看实现代码
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 */
/// <summary>
/// 设置为守护进程程序
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// 真正要执行的实现代码
/// 由于在SetDaemon函数中,将标准的输入输出文件及错误文件都关闭了,所以在Run函数中,是不能直接使用Console.WriteLine,可以使用:
/// 1.将日志信息通过syslog输出到系统日志中
/// 2.将日志信息写入到当前程序的日志(可使用nlog等日志组件)
/// </summary>
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();
}
}
}
编译和运行
#mcs 是mono的编译器
mcs MyDaemon.cs
#通过mono运行c#程序
mono MyDaemon.exe
查看进程关系和日志
#pstree 查看进程树
pstree

#查看系统日志
cat /var/log/messages

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

支持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的可以参考这篇:
生成单文件及裁剪之后的文件大小:
生成单文件及裁剪的工程文件配置:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<!--发布时,生成单文件-->
<PublishSingleFile>true</PublishSingleFile>
<!--发布时,进行裁剪不需要的dll-->
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>
将文件上传到Linux服务器上:
#因为文件是在Windows生成的,在Linux上没法运行,要给文件可执行权限
chmod +x Daemon
#继续使用pstree查看Linux进程树
pstree
具体有没有运行成功,还是查看一下系统日志有没有写入成功.
由于Ubuntu 20.04系统日志文件名换为:syslog
#ubuntu 20.04查看系统日志
cat /var/log/syslog
秋风
2017-01-09