起因
本文主要是通过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 守护进程,查看是否写入系统日志文件中