.NET跨平台实践:再谈用C#开发Linux守护进程 — 完整篇
Linux守护进程是Linux的后台服务进程,相当于Windows服务,对于为Linux开发服务程序的朋友来说,Linux守护进程相关技术是必不可少的,因为这个技术不仅仅是为了开发守护进程,还可以拓展到多进程,父子进程文件描述符共享,父子进程通讯、控制等方面,是实现Linux大型服务的基础技术之一。
去年我也曾写了一篇关于守护进程的帖子,名字叫《.NET跨平台实践:用C#开发Linux守护进程》,这篇文章的的确确实现了一个Daemon,不过,它有一个弱点,不能运行多线程!
这篇帖子的目的就是进一步完善,让我们写出一个功能完整,可以用于生产环节的基本的守护进程。
先帖代码(假设项目名是daemon):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | 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守护进程的完整技术亦属本人首发,如需转载,请注明出处和作者。
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器