Windows下的通用进程守护程序(持续更新中),高仿supervisor。

Posted webmote

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Windows下的通用进程守护程序(持续更新中),高仿supervisor。相关的知识,希望对你有一定的参考价值。

  • 📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!
  • 📢本文作者:由webmote 原创
  • 📢作者格言:无尽的折腾后,终于又回到了起点,工控,我来了 !

Window下的通用守护进程

是的,有这个需求。

曾经,我也觉得没必要,然而,现实很残酷。

比如开发了.net core的web程序,明明可以很好的部署在IIS下,但领导偏不,他就想启动为不可见的Console程序。如果是一个console程序,那么还可以借助exe转service的技术,把它转换成windows service服务,好像也没啥大不了,怕就怕竟然又多个。

多个console启动在服务端虽然有些不好看,忍忍是不是就没事了? 不,如果程序写的不好崩溃了咋办?这…容我思考下,感觉好像需要写个守护程序…

哎,部署在Linux下不行吗?使用supervisor不行吗?好像也可以,好像又不行。

好吧,那就准备操练一把,写个通用的守护进程程序吧。

守护配置

目标,守护多个进程。
士兵,windows 服务。
军粮:守护配置。

用户可以灵活配置守护,如同Supervisor配置一样。配置内容如下:

[program:WebTest]
# 项目根目录
directory=C:\\Users\\source\\repos\\Framework\\WebTest\\WebTest\\bin\\Debug\\net6.0
# 启动执行命令
command=C:\\Users\\source\\repos\\Framework\\WebTest\\WebTest\\bin\\Debug\\net6.0\\WebTest.exe
stderr_logfile=d:\\test_stderr.log
stdout_logfile=d:\\test_stdout.log
arguments=
env=

有了这个配置,你就可以轻松配置被守护的程序,执行目录,参数以及环境变量了。当然也可以重定向输出日志和错误日志。

下载

当前版本实现了基本的守护功能,程序自动启动为 guard service服务,自我测试,运行良好。
下载1.0文件在 此
下载1.1文件在 此
使用了dotnet的一体化打包模式,不需要安装.net6运行环境。因此包稍微大了点。

dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true

具体实现逻辑

首先选用了TopShelf类库包,这样就减轻了开发windows service的复杂度和安装命令解析的复杂度。
因此,exe执行文件可以带一系列参数进行服务的安装和卸载工作。

 service install
        Installs the service into the service control manager

    service install -username:joe -password:bob --autostart
        Installs the service using the specified username/password and
        configures the service to start automatically at machine startup

    service uninstall
        Uninstalls the service

    service install -instance:001
        Installs the service, appending the instance name to the service name
        so that the service can be installed multiple times. You may need to
        tweak the log4net.config to make this play nicely with the log files.

我写了个直接运行的批处理文件,解包后可以看到是RunAsService.bat,内容如下:

call WbtGuardService.exe install --autostart 
echo start guard service...
call WbtGuardService.exe start

预留了开关web服务的配置(包含端口号),可以修改appsettings.json文件的配置


  "Kestrel": 
    "Endpoints": 
      "Http": "Url": "http://*:8088"     
    
  ,
  "EnableWeb": "true", 
  "UserName": "admin",
  "Password": "admin",
  "CheckInterval": 20000,
  "Logging": 
    "LogLevel": 
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    
  ,
  "AllowedHosts": "*"


内部核心的代码,无非是包装了process的调用方法。代码如下:

private Process StartProcess()
        
            _logger.Info($"开始程序 this.config.Name...");
            var bDir = !string.IsNullOrEmpty(this.config.Directory);
            Process p = Process.GetProcessesByName(config.Name)?.FirstOrDefault();
            if (p == null)
            
                var startInfo =  new ProcessStartInfo
                
                    FileName = this.config.Command,
                    UseShellExecute = false,
                    RedirectStandardOutput = stdoutStream != null,
                    RedirectStandardError = stderrorStream != null,
                    WorkingDirectory = bDir ? this.config.Directory : AppDomain.CurrentDomain.BaseDirectory,
                    Arguments = this.config.Arguments,
                    CreateNoWindow = true,                   
                    //StandardOutputEncoding = Encoding.UTF8,
                    //StandardErrorEncoding = Encoding.UTF8,
                ;
                foreach (var (key, value) in this.config.GetEnvironmentVariables())
                
                    if (value is not null)
                    
                        startInfo.Environment[key] = value;
                    
                    else
                    
                        // Null value means we should remove the variable
                        // https://github.com/Tyrrrz/CliWrap/issues/109
                        // https://github.com/dotnet/runtime/issues/34446
                        startInfo.Environment.Remove(key);
                    
                
                Console.WriteLine($"start config.Name  ....");
                p = new Process
                
                    StartInfo =startInfo,
                ;
                
                p.ErrorDataReceived += P_ErrorDataReceived;
                p.OutputDataReceived += P_OutputDataReceived;

                try
                
                    if (!p.Start())
                    
                       
                    

                    if (stdoutStream != null) p.BeginOutputReadLine();
                    if (stderrorStream != null) p.BeginErrorReadLine();
                    _logger.Info($"程序 this.config.Name 启动成功.");
                
                catch (Win32Exception ex)
                
                    
                
            

            return p;
        

增加管理页面

为了方便手工管理守护程序的启动、停止、重启、清理和查看日志等,我增加了管理页面。

通过SignalR和守护服务进行通讯,利用Channel进行命令的转发,感觉有种奇妙的感觉。

利用Channel功能实现了队列命令。

public class MessageQueueService : IDisposable

    private Channel<Message> _channelR2S;

    public ChannelReader<Message> Reader => _channelR2S.Reader;
    public MessageQueueService()
    
        var options = new BoundedChannelOptions(10)
        
            AllowSynchronousContinuations = true,   
        ;
        _channelR2S = Channel.CreateBounded<Message>(options) ;
    

    /// <summary>
    /// 从 hub 发送命令,不需要等待完成
    /// </summary>
    /// <param name="msg"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    public ValueTask SendCommandNoReturn(Message msg,CancellationToken token)
    
        return _channelR2S.Writer.WriteAsync(msg, token);
    
    /// <summary>
    /// 从hub发送命令
    /// </summary>
    /// <param name="msg"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    public async ValueTask SendCommand(Message msg, CancellationToken token)
    
        await _channelR2S.Writer.WriteAsync(msg, token);        

        
    

    public void Dispose()
    
        _channelR2S.Writer.Complete();
    

增加了SignalR通讯

为了实现前端和后端的即时通讯,我利用SignalR技术,实现了两者间的通讯。

public async Task ClearLogs(string processName)
    
        CancellationTokenSource timeoutSource = new CancellationTokenSource(_timeout);
        CancellationTokenSource waitSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, Context.ConnectionAborted);
        await service.SendCommand(new Message  
            ClientId = Context.ConnectionId,
            Command = "ClearLogs",
            Content = processName,
            ProcessName = processName,
        , waitSource.Token);

        
    

利用队列服务,向进程管理服务发送命令,让其管理进程的启动、停止和重启工作。

欢迎评论

如果你想用,有场景用,欢迎评论,我已经放在github上开源了。

看看这个场景是真需求,还是假需求。

反正我不信真的有人用…

好吧,有产生了一个轮子。

以上是关于Windows下的通用进程守护程序(持续更新中),高仿supervisor。的主要内容,如果未能解决你的问题,请参考以下文章

Windows下的通用进程守护程序(持续更新中),高仿supervisor。

Windows下的通用进程守护程序(持续更新中),高仿supervisor。

windows下bat批处理实现守护进程(有日志)

Supervisor-守护进程工具

[C#]通用守护进程服务

.NET Core 中的跨平台后台服务(想想 windows 服务/unix 守护进程)?