golang 守護進程(daemon)實例(二)——加載任意進程


前期實現

  1. -daemon功能:為任意 Go 程序創建守護進程,使 Go 業務進程脫離終端運行;
  2. -forever功能:創建監控重啟進程,使 Go 業務進程被殺死后能夠重啟;
  3. 不影響業務進程邏輯;
  4. 實現 Linux 端運行。

見上一篇文章golang 守護進程(daemon)實例——后台運行,重啟進程

更進一步思考

我們可以不局限於 Go 代碼層面的守護進程,而是實現對任意進程的守護進程創建,從而對所有 Linux 環境的程序都可以進行加載。

需求

  1. -prog功能:將任意待執行的程序以參數形式傳入;
  2. 不影響業務進程邏輯,也就是把原先的 DoSomething 部分修改為啟動新進程;
  3. 跨平台,實現 Linux 端 / Windows 端執行。

實現

關鍵點

  1. 為實現兼容不同操作系統,對操作系統類型進行了判斷

注意:在 Windows 環境下,雖然 daemonforever 兩項功能都可以使用,但 Windows 沒有所謂的 daemon 托管機制,所以 daemon 功能只是把業務進程變成了后台運行,cmd 掛掉程序依舊會退出。

switch runtime.GOOS { 
	case "darwin": 
		cmd = exec.Command(os.Getenv("SHELL"), "-c", args[0])
		break
	case "linux": 
		cmd = exec.Command(os.Getenv("SHELL"), "-c", args[0])
		break
	case "windows": 
		cmd = exec.Command("cmd", "/C", args[0])
		break
	default:
		os.Exit(0)
		break
}
  1. 在啟動業務進程時,要記得進行 cmd.Wait(),否則業務進程尚未結束父進程就掛掉了,業務進程就無條件變成后台進程了
cmd = SubProcess([]string{*program}, true)
cmd.Wait()
  1. 這個點存在一個Bug,但不影響程序使用。在完成 forever 功能時,當業務進程第一次被殺死后,for循環中重啟業務進程,但 -prog 參數被傳遞了2次,百思不得其解,希望有大神能幫忙解決一下
for {
	fmt.Printf("[*] Forever running in PID: %d PPID: %d\n", os.Getpid(), os.Getppid())
	cmd = SubProcess(StripSlice(args, "-"+FOREVER), false)
	cmd.Wait()
}

go-daemon-loader.go

直接上代碼

package main

import (
	"os"
	"os/exec"
	"fmt"
	"flag"
	"runtime"
)

const (
	PROGRAM = "prog"
	DAEMON = "daemon"
	FOREVER = "forever"
)

func StripSlice(slice []string, element string) []string {
	for i := 0; i < len(slice); {
		if slice[i] == element && i != len(slice)-1 {
			slice = append(slice[:i], slice[i+1:]...)
			break
		} else if slice[i] == element && i == len(slice)-1 {
			slice = slice[:i]
			break
		} else {
			i++
		}
	}
	return slice
}

func SubProcess(args []string, shell bool) *exec.Cmd {
	var cmd *exec.Cmd
	if shell { 
		switch runtime.GOOS { 
		case "darwin": 
			cmd = exec.Command(os.Getenv("SHELL"), "-c", args[0])
			break
		case "linux": 
			cmd = exec.Command(os.Getenv("SHELL"), "-c", args[0])
			break
		case "windows": 
			cmd = exec.Command("cmd", "/C", args[0])
			break
		default:
			os.Exit(1)
			break
		}
	} else {
		cmd = exec.Command(args[0], args[1:]...)
	}
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Start()
	if err != nil {
		fmt.Fprintf(os.Stderr, "[-] Error: %s\n", err)
	}
	return cmd
}

func main(){
	var cmd *exec.Cmd
	program := flag.String(PROGRAM, "", "run program")
	daemon := flag.Bool(DAEMON, false, "run in daemon")
	forever := flag.Bool(FOREVER, false, "run forever")
	flag.Parse()
	fmt.Printf("[*] PID: %d PPID: %d ARG: %s PROG:\"%s\"\n", os.Getpid(), os.Getppid(), os.Args, *program)
	if *program == "" {
		flag.Usage()
		os.Exit(1)
	}
	if *daemon {
		fmt.Printf("[*] Daemon running in PID: %d PPID: %d\n", os.Getpid(), os.Getppid())
		SubProcess(StripSlice(os.Args, "-"+DAEMON), false)
		os.Exit(0)
	} else if *forever {
		args := os.Args
		for {
			fmt.Printf("[*] Forever running in PID: %d PPID: %d\n", os.Getpid(), os.Getppid())
			cmd = SubProcess(StripSlice(args, "-"+FOREVER), false)
			cmd.Wait()
		}
		os.Exit(0)
	} else {
		fmt.Printf("[*] Service running in PID: %d PPID: %d\n", os.Getpid(), os.Getppid())
		cmd = SubProcess([]string{*program}, true)
		cmd.Wait()
	}
}

使用

編譯

  • Linux 編譯(同 MacOS 編譯,未測試)
go build -ldflags "-s -w" ./go-daemon-loader.go
  • Windows 編譯
go build -ldflags "-s -w" .\go-daemon-loader.go

運行

  • Linux
./go-daemon-loader -prog "ping 127.0.0.1" -daemon -forever
  • Windows
.\go-daemon-loader.exe -prog "ping 127.0.0.1 -t" -daemon -forever


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM