4.5.2 守护进程开发

4.5.2 守护进程开发 #

守护进程(Daemon)是在后台运行的系统服务程序,它们通常在系统启动时启动,并持续运行直到系统关闭。守护进程不与任何终端关联,独立于用户会话运行。在 Go 语言中开发守护进程需要遵循 Unix/Linux 系统的特定规范和最佳实践。

守护进程基础概念 #

守护进程的特征 #

标准的 Unix 守护进程具有以下特征:

package main

import (
    "fmt"
    "os"
    "syscall"
)

func main() {
    fmt.Println("=== 守护进程特征 ===")

    // 1. 父进程 PID 为 1 (init 进程)
    ppid := os.Getppid()
    fmt.Printf("父进程 PID: %d\n", ppid)

    // 2. 进程组 ID 和会话 ID
    pid := os.Getpid()
    pgid, _ := syscall.Getpgid(pid)
    sid, _ := syscall.Getsid(pid)

    fmt.Printf("进程 PID: %d\n", pid)
    fmt.Printf("进程组 ID: %d\n", pgid)
    fmt.Printf("会话 ID: %d\n", sid)

    // 3. 工作目录
    wd, _ := os.Getwd()
    fmt.Printf("工作目录: %s\n", wd)

    // 4. 文件权限掩码
    fmt.Printf("当前 umask: %o\n", syscall.Umask(0))
    syscall.Umask(0) // 重置 umask

    // 5. 标准输入输出
    fmt.Printf("标准输入: %v\n", os.Stdin.Name())
    fmt.Printf("标准输出: %v\n", os.Stdout.Name())
    fmt.Printf("标准错误: %v\n", os.Stderr.Name())

    fmt.Println("\n守护进程应该具有:")
    fmt.Println("- 父进程为 init (PID 1)")
    fmt.Println("- 成为会话领导者")
    fmt.Println("- 工作目录为根目录 /")
    fmt.Println("- umask 设置为 0")
    fmt.Println("- 关闭所有文件描述符")
    fmt.Println("- 重定向标准输入输出到 /dev/null")
}

守护进程创建步骤 #

创建守护进程需要遵循标准的步骤:

package main

import (
    "fmt"
    "log"
    "os"
    "syscall"
)

// 守护进程创建步骤演示
func demonstrateDaemonSteps() {
    fmt.Println("=== 守护进程创建步骤 ===")

    fmt.Println("步骤 1: fork() 创建子进程,父进程退出")
    fmt.Println("  - 确保子进程不是进程组领导者")
    fmt.Println("  - 允许后续创建新会话")

    fmt.Println("\n步骤 2: setsid() 创建新会话")
    fmt.Println("  - 成为新会话的领导者")
    fmt.Println("  - 成为新进程组的领导者")
    fmt.Println("  - 脱离控制终端")

    fmt.Println("\n步骤 3: 再次 fork() 并退出父进程")
    fmt.Println("  - 确保进程不能重新获得控制终端")
    fmt.Println("  - 只有会话领导者才能获得控制终端")

    fmt.Println("\n步骤 4: 改变工作目录到根目录")
    fmt.Println("  - 避免阻止文件系统卸载")
    fmt.Println("  - 确保工作目录始终存在")

    fmt.Println("\n步骤 5: 重置文件权限掩码")
    fmt.Println("  - 设置 umask 为 0")
    fmt.Println("  - 确保创建文件时权限正确")

    fmt.Println("\n步骤 6: 关闭所有文件描述符")
    fmt.Println("  - 关闭从父进程继承的文件描述符")
    fmt.Println("  - 释放系统资源")

    fmt.Println("\n步骤 7: 重定向标准输入输出")
    fmt.Println("  - 重定向到 /dev/null")
    fmt.Println("  - 避免意外的输入输出操作")
}

func main() {
    demonstrateDaemonSteps()
}

基础守护进程实现 #

简单守护进程 #

让我们实现一个基础的守护进程:

package main

import (
    "fmt"
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"
)

// Daemon 结构体
type Daemon struct {
    name    string
    pidFile string
    logFile string
}

// NewDaemon 创建新的守护进程实例
func NewDaemon(name, pidFile, logFile string) *Daemon {
    return &Daemon{
        name:    name,
        pidFile: pidFile,
        logFile: logFile,
    }
}

// Daemonize 将当前进程转换为守护进程
func (d *Daemon) Daemonize() error {
    // 检查是否已经是守护进程
    if os.Getppid() == 1 {
        return nil // 已经是守护进程
    }

    // 第一次 fork
    if err := d.forkAndExit(); err != nil {
        return fmt.Errorf("第一次 fork 失败: %v", err)
    }

    // 创建新会话
    if _, err := syscall.Setsid(); err != nil {
        return fmt.Errorf("创建新会话失败: %v", err)
    }

    // 第二次 fork
    if err := d.forkAndExit(); err != nil {
        return fmt.Errorf("第二次 fork 失败: %v", err)
    }

    // 改变工作目录到根目录
    if err := os.Chdir("/"); err != nil {
        return fmt.Errorf("改变工作目录失败: %v", err)
    }

    // 重置文件权限掩码
    syscall.Umask(0)

    // 关闭文件描述符
    if err := d.closeFileDescriptors(); err != nil {
        return fmt.Errorf("关闭文件描述符失败: %v", err)
    }

    // 重定向标准输入输出
    if err := d.redirectStdio(); err != nil {
        return fmt.Errorf("重定向标准输入输出失败: %v", err)
    }

    return nil
}

// forkAndExit 执行 fork 并让父进程退出
func (d *Daemon) forkAndExit() error {
    // 在 Go 中,我们使用 os.StartProcess 来模拟 fork
    // 注意:这是一个简化的实现,实际生产环境中可能需要更复杂的处理

    // 获取当前可执行文件路径
    executable, err := os.Executable()
    if err != nil {
        return err
    }

    // 启动新进程
    process, err := os.StartProcess(executable, os.Args, &os.ProcAttr{
        Dir: "/",
        Env: os.Environ(),
        Files: []*os.File{
            nil, // stdin
            nil, // stdout
            nil, // stderr
        },
        Sys: &syscall.SysProcAttr{
            Setsid: true, // 创建新会话
        },
    })

    if err != nil {
        return err
    }

    // 父进程退出
    process.Release()
    os.Exit(0)

    return nil
}

// closeFileDescriptors 关闭所有文件描述符
func (d *Daemon) closeFileDescriptors() error {
    // 获取最大文件描述符数量
    var rlimit syscall.Rlimit
    if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
        return err
    }

    // 关闭所有文件描述符(除了 0, 1, 2)
    for fd := 3; fd < int(rlimit.Cur); fd++ {
        syscall.Close(fd)
    }

    return nil
}

// redirectStdio 重定向标准输入输出到 /dev/null
func (d *Daemon) redirectStdio() error {
    devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
    if err != nil {
        return err
    }

    // 重定向标准输入
    if err := syscall.Dup2(int(devNull.Fd()), int(os.Stdin.Fd())); err != nil {
        return err
    }

    // 重定向标准输出
    if err := syscall.Dup2(int(devNull.Fd()), int(os.Stdout.Fd())); err != nil {
        return err
    }

    // 重定向标准错误
    if err := syscall.Dup2(int(devNull.Fd()), int(os.Stderr.Fd())); err != nil {
        return err
    }

    devNull.Close()
    return nil
}

// WritePidFile 写入 PID 文件
func (d *Daemon) WritePidFile() error {
    pidFile, err := os.Create(d.pidFile)
    if err != nil {
        return err
    }
    defer pidFile.Close()

    _, err = fmt.Fprintf(pidFile, "%d\n", os.Getpid())
    return err
}

// RemovePidFile 删除 PID 文件
func (d *Daemon) RemovePidFile() error {
    return os.Remove(d.pidFile)
}

// Run 运行守护进程
func (d *Daemon) Run() error {
    // 转换为守护进程
    if err := d.Daemonize(); err != nil {
        return err
    }

    // 写入 PID 文件
    if err := d.WritePidFile(); err != nil {
        return err
    }

    // 设置日志输出
    if d.logFile != "" {
        logFile, err := os.OpenFile(d.logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
        if err != nil {
            return err
        }
        log.SetOutput(logFile)
    }

    log.Printf("守护进程 %s 启动,PID: %d", d.name, os.Getpid())

    // 设置信号处理
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

    // 主循环
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case sig := <-sigChan:
            log.Printf("接收到信号: %s", sig)
            switch sig {
            case syscall.SIGINT, syscall.SIGTERM:
                log.Println("守护进程正在关闭...")
                d.RemovePidFile()
                return nil
            case syscall.SIGHUP:
                log.Println("重新加载配置...")
                // 这里可以添加配置重载逻辑
            }

        case <-ticker.C:
            log.Printf("守护进程 %s 正在运行...", d.name)
            // 这里添加守护进程的主要工作逻辑
        }
    }
}

func main() {
    daemon := NewDaemon("mydaemon", "/var/run/mydaemon.pid", "/var/log/mydaemon.log")

    if err := daemon.Run(); err != nil {
        log.Fatalf("守护进程运行失败: %v", err)
    }
}

改进的守护进程实现 #

由于 Go 语言的特殊性,我们需要一个更实用的守护进程实现:

package main

import (
    "context"
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
    "os/signal"
    "path/filepath"
    "strconv"
    "strings"
    "syscall"
    "time"
)

// DaemonConfig 守护进程配置
type DaemonConfig struct {
    Name        string
    Description string
    PidFile     string
    LogFile     string
    WorkDir     string
    User        string
    Group       string
}

// GoDaemon Go 守护进程实现
type GoDaemon struct {
    config   *DaemonConfig
    ctx      context.Context
    cancel   context.CancelFunc
    logger   *log.Logger
}

// NewGoDaemon 创建新的 Go 守护进程
func NewGoDaemon(config *DaemonConfig) *GoDaemon {
    ctx, cancel := context.WithCancel(context.Background())

    return &GoDaemon{
        config: config,
        ctx:    ctx,
        cancel: cancel,
        logger: log.New(os.Stdout, fmt.Sprintf("[%s] ", config.Name), log.LstdFlags),
    }
}

// IsDaemon 检查是否在守护进程模式下运行
func (gd *GoDaemon) IsDaemon() bool {
    return os.Getenv("DAEMON_MODE") == "1"
}

// Start 启动守护进程
func (gd *GoDaemon) Start() error {
    if gd.IsDaemon() {
        // 已经在守护进程模式下运行
        return gd.runDaemon()
    }

    // 检查是否已经在运行
    if gd.isRunning() {
        return fmt.Errorf("守护进程已经在运行")
    }

    // 启动守护进程
    return gd.startDaemon()
}

// startDaemon 启动守护进程
func (gd *GoDaemon) startDaemon() error {
    // 获取当前可执行文件路径
    executable, err := os.Executable()
    if err != nil {
        return err
    }

    // 准备命令
    cmd := exec.Command(executable, os.Args[1:]...)
    cmd.Env = append(os.Environ(), "DAEMON_MODE=1")
    cmd.Dir = gd.config.WorkDir

    // 设置进程属性
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Setsid: true, // 创建新会话
    }

    // 重定向输入输出
    if err := gd.setupStdio(cmd); err != nil {
        return err
    }

    // 启动进程
    if err := cmd.Start(); err != nil {
        return err
    }

    // 写入 PID 文件
    if err := gd.writePidFile(cmd.Process.Pid); err != nil {
        cmd.Process.Kill()
        return err
    }

    fmt.Printf("守护进程 %s 已启动,PID: %d\n", gd.config.Name, cmd.Process.Pid)
    return nil
}

// runDaemon 运行守护进程主逻辑
func (gd *GoDaemon) runDaemon() error {
    // 设置日志输出
    if err := gd.setupLogging(); err != nil {
        return err
    }

    gd.logger.Printf("守护进程 %s 启动,PID: %d", gd.config.Name, os.Getpid())

    // 设置信号处理
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGUSR1)

    // 启动工作协程
    go gd.worker()

    // 信号处理循环
    for {
        select {
        case sig := <-sigChan:
            gd.logger.Printf("接收到信号: %s", sig)

            switch sig {
            case syscall.SIGINT, syscall.SIGTERM:
                gd.logger.Println("守护进程正在关闭...")
                gd.cancel()
                gd.cleanup()
                return nil

            case syscall.SIGHUP:
                gd.logger.Println("重新加载配置...")
                gd.reloadConfig()

            case syscall.SIGUSR1:
                gd.logger.Println("打印状态信息...")
                gd.printStatus()
            }

        case <-gd.ctx.Done():
            gd.logger.Println("守护进程上下文已取消")
            return nil
        }
    }
}

// worker 工作协程
func (gd *GoDaemon) worker() {
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            gd.logger.Printf("守护进程 %s 正在运行...", gd.config.Name)
            gd.doWork()

        case <-gd.ctx.Done():
            gd.logger.Println("工作协程收到关闭信号")
            return
        }
    }
}

// doWork 执行实际工作
func (gd *GoDaemon) doWork() {
    // 这里添加守护进程的主要工作逻辑
    // 例如:处理队列、监控系统、数据同步等

    gd.logger.Println("执行守护进程工作...")

    // 模拟工作
    time.Sleep(1 * time.Second)
}

// setupStdio 设置标准输入输出
func (gd *GoDaemon) setupStdio(cmd *exec.Cmd) error {
    // 打开 /dev/null
    devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
    if err != nil {
        return err
    }

    cmd.Stdin = devNull
    cmd.Stdout = devNull
    cmd.Stderr = devNull

    return nil
}

// setupLogging 设置日志
func (gd *GoDaemon) setupLogging() error {
    if gd.config.LogFile == "" {
        return nil
    }

    // 确保日志目录存在
    logDir := filepath.Dir(gd.config.LogFile)
    if err := os.MkdirAll(logDir, 0755); err != nil {
        return err
    }

    // 打开日志文件
    logFile, err := os.OpenFile(gd.config.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        return err
    }

    // 设置日志输出
    gd.logger.SetOutput(io.MultiWriter(logFile, os.Stdout))

    return nil
}

// writePidFile 写入 PID 文件
func (gd *GoDaemon) writePidFile(pid int) error {
    if gd.config.PidFile == "" {
        return nil
    }

    // 确保 PID 文件目录存在
    pidDir := filepath.Dir(gd.config.PidFile)
    if err := os.MkdirAll(pidDir, 0755); err != nil {
        return err
    }

    // 写入 PID 文件
    return os.WriteFile(gd.config.PidFile, []byte(strconv.Itoa(pid)), 0644)
}

// removePidFile 删除 PID 文件
func (gd *GoDaemon) removePidFile() error {
    if gd.config.PidFile == "" {
        return nil
    }

    return os.Remove(gd.config.PidFile)
}

// isRunning 检查守护进程是否正在运行
func (gd *GoDaemon) isRunning() bool {
    if gd.config.PidFile == "" {
        return false
    }

    // 读取 PID 文件
    pidData, err := os.ReadFile(gd.config.PidFile)
    if err != nil {
        return false
    }

    // 解析 PID
    pidStr := strings.TrimSpace(string(pidData))
    pid, err := strconv.Atoi(pidStr)
    if err != nil {
        return false
    }

    // 检查进程是否存在
    process, err := os.FindProcess(pid)
    if err != nil {
        return false
    }

    // 发送信号 0 检查进程是否存活
    err = process.Signal(syscall.Signal(0))
    return err == nil
}

// Stop 停止守护进程
func (gd *GoDaemon) Stop() error {
    if gd.config.PidFile == "" {
        return fmt.Errorf("未配置 PID 文件")
    }

    // 读取 PID 文件
    pidData, err := os.ReadFile(gd.config.PidFile)
    if err != nil {
        return fmt.Errorf("读取 PID 文件失败: %v", err)
    }

    // 解析 PID
    pidStr := strings.TrimSpace(string(pidData))
    pid, err := strconv.Atoi(pidStr)
    if err != nil {
        return fmt.Errorf("解析 PID 失败: %v", err)
    }

    // 查找进程
    process, err := os.FindProcess(pid)
    if err != nil {
        return fmt.Errorf("查找进程失败: %v", err)
    }

    // 发送 SIGTERM 信号
    if err := process.Signal(syscall.SIGTERM); err != nil {
        return fmt.Errorf("发送终止信号失败: %v", err)
    }

    // 等待进程退出
    for i := 0; i < 30; i++ {
        if !gd.isRunning() {
            fmt.Printf("守护进程 %s 已停止\n", gd.config.Name)
            return nil
        }
        time.Sleep(1 * time.Second)
    }

    // 强制终止
    if err := process.Signal(syscall.SIGKILL); err != nil {
        return fmt.Errorf("强制终止失败: %v", err)
    }

    fmt.Printf("守护进程 %s 已强制停止\n", gd.config.Name)
    return nil
}

// Status 检查守护进程状态
func (gd *GoDaemon) Status() {
    if gd.isRunning() {
        fmt.Printf("守护进程 %s 正在运行\n", gd.config.Name)
    } else {
        fmt.Printf("守护进程 %s 未运行\n", gd.config.Name)
    }
}

// reloadConfig 重新加载配置
func (gd *GoDaemon) reloadConfig() {
    gd.logger.Println("重新加载配置...")
    // 这里添加配置重载逻辑
}

// printStatus 打印状态信息
func (gd *GoDaemon) printStatus() {
    gd.logger.Printf("=== %s 状态信息 ===", gd.config.Name)
    gd.logger.Printf("PID: %d", os.Getpid())
    gd.logger.Printf("运行时间: %s", time.Since(startTime))
    gd.logger.Printf("工作目录: %s", gd.config.WorkDir)
    gd.logger.Printf("========================")
}

// cleanup 清理资源
func (gd *GoDaemon) cleanup() {
    gd.logger.Println("清理资源...")
    gd.removePidFile()
}

var startTime = time.Now()

func main() {
    config := &DaemonConfig{
        Name:        "go-daemon",
        Description: "Go 守护进程示例",
        PidFile:     "/tmp/go-daemon.pid",
        LogFile:     "/tmp/go-daemon.log",
        WorkDir:     "/",
    }

    daemon := NewGoDaemon(config)

    if len(os.Args) < 2 {
        fmt.Printf("用法: %s {start|stop|status|restart}\n", os.Args[0])
        os.Exit(1)
    }

    switch os.Args[1] {
    case "start":
        if err := daemon.Start(); err != nil {
            fmt.Printf("启动失败: %v\n", err)
            os.Exit(1)
        }

    case "stop":
        if err := daemon.Stop(); err != nil {
            fmt.Printf("停止失败: %v\n", err)
            os.Exit(1)
        }

    case "status":
        daemon.Status()

    case "restart":
        daemon.Stop()
        time.Sleep(2 * time.Second)
        if err := daemon.Start(); err != nil {
            fmt.Printf("重启失败: %v\n", err)
            os.Exit(1)
        }

    default:
        fmt.Printf("未知命令: %s\n", os.Args[1])
        fmt.Printf("用法: %s {start|stop|status|restart}\n", os.Args[0])
        os.Exit(1)
    }
}

PID 文件管理 #

PID 文件操作 #

PID 文件是守护进程管理的重要组成部分:

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "strconv"
    "strings"
    "syscall"
)

// PidFileManager PID 文件管理器
type PidFileManager struct {
    pidFile string
}

// NewPidFileManager 创建 PID 文件管理器
func NewPidFileManager(pidFile string) *PidFileManager {
    return &PidFileManager{pidFile: pidFile}
}

// Write 写入 PID 文件
func (pfm *PidFileManager) Write(pid int) error {
    // 确保目录存在
    dir := filepath.Dir(pfm.pidFile)
    if err := os.MkdirAll(dir, 0755); err != nil {
        return fmt.Errorf("创建目录失败: %v", err)
    }

    // 检查文件是否已存在
    if pfm.Exists() {
        if pfm.IsRunning() {
            return fmt.Errorf("进程已在运行")
        }
        // 删除过期的 PID 文件
        pfm.Remove()
    }

    // 写入 PID
    content := fmt.Sprintf("%d\n", pid)
    if err := os.WriteFile(pfm.pidFile, []byte(content), 0644); err != nil {
        return fmt.Errorf("写入 PID 文件失败: %v", err)
    }

    return nil
}

// Read 读取 PID 文件
func (pfm *PidFileManager) Read() (int, error) {
    data, err := os.ReadFile(pfm.pidFile)
    if err != nil {
        return 0, fmt.Errorf("读取 PID 文件失败: %v", err)
    }

    pidStr := strings.TrimSpace(string(data))
    pid, err := strconv.Atoi(pidStr)
    if err != nil {
        return 0, fmt.Errorf("解析 PID 失败: %v", err)
    }

    return pid, nil
}

// Exists 检查 PID 文件是否存在
func (pfm *PidFileManager) Exists() bool {
    _, err := os.Stat(pfm.pidFile)
    return err == nil
}

// IsRunning 检查 PID 文件中的进程是否在运行
func (pfm *PidFileManager) IsRunning() bool {
    pid, err := pfm.Read()
    if err != nil {
        return false
    }

    return pfm.IsProcessRunning(pid)
}

// IsProcessRunning 检查指定 PID 的进程是否在运行
func (pfm *PidFileManager) IsProcessRunning(pid int) bool {
    process, err := os.FindProcess(pid)
    if err != nil {
        return false
    }

    // 发送信号 0 检查进程是否存活
    err = process.Signal(syscall.Signal(0))
    return err == nil
}

// Remove 删除 PID 文件
func (pfm *PidFileManager) Remove() error {
    if !pfm.Exists() {
        return nil
    }

    if err := os.Remove(pfm.pidFile); err != nil {
        return fmt.Errorf("删除 PID 文件失败: %v", err)
    }

    return nil
}

// GetPid 获取当前 PID 文件中的 PID
func (pfm *PidFileManager) GetPid() (int, error) {
    if !pfm.Exists() {
        return 0, fmt.Errorf("PID 文件不存在")
    }

    return pfm.Read()
}

// Lock 锁定 PID 文件(防止多实例运行)
func (pfm *PidFileManager) Lock() (*os.File, error) {
    file, err := os.OpenFile(pfm.pidFile, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
    if err != nil {
        if os.IsExist(err) {
            return nil, fmt.Errorf("PID 文件已存在,可能有其他实例正在运行")
        }
        return nil, fmt.Errorf("创建 PID 文件失败: %v", err)
    }

    // 写入当前进程 PID
    if _, err := fmt.Fprintf(file, "%d\n", os.Getpid()); err != nil {
        file.Close()
        os.Remove(pfm.pidFile)
        return nil, fmt.Errorf("写入 PID 失败: %v", err)
    }

    return file, nil
}

// 示例用法
func demonstratePidFileManager() {
    pidFile := "/tmp/demo-daemon.pid"
    pfm := NewPidFileManager(pidFile)

    fmt.Println("=== PID 文件管理示例 ===")

    // 检查是否存在
    fmt.Printf("PID 文件存在: %v\n", pfm.Exists())

    // 写入当前进程 PID
    currentPid := os.Getpid()
    fmt.Printf("当前进程 PID: %d\n", currentPid)

    if err := pfm.Write(currentPid); err != nil {
        fmt.Printf("写入 PID 文件失败: %v\n", err)
        return
    }

    fmt.Println("PID 文件写入成功")

    // 读取 PID
    if pid, err := pfm.Read(); err != nil {
        fmt.Printf("读取 PID 文件失败: %v\n", err)
    } else {
        fmt.Printf("从 PID 文件读取的 PID: %d\n", pid)
    }

    // 检查进程是否在运行
    fmt.Printf("进程是否在运行: %v\n", pfm.IsRunning())

    // 清理
    if err := pfm.Remove(); err != nil {
        fmt.Printf("删除 PID 文件失败: %v\n", err)
    } else {
        fmt.Println("PID 文件已删除")
    }
}

func main() {
    demonstratePidFileManager()
}

守护进程最佳实践 #

完整的守护进程框架 #

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
    "os/signal"
    "path/filepath"
    "sync"
    "syscall"
    "time"
)

// DaemonFramework 守护进程框架
type DaemonFramework struct {
    config     *DaemonConfig
    logger     *log.Logger
    pidManager *PidFileManager
    ctx        context.Context
    cancel     context.CancelFunc
    wg         sync.WaitGroup
    services   []Service
}

// Service 服务接口
type Service interface {
    Start(ctx context.Context) error
    Stop() error
    Name() string
}

// DaemonConfig 守护进程配置
type DaemonConfig struct {
    Name        string `json:"name"`
    Description string `json:"description"`
    PidFile     string `json:"pid_file"`
    LogFile     string `json:"log_file"`
    WorkDir     string `json:"work_dir"`
    User        string `json:"user"`
    Group       string `json:"group"`
    LogLevel    string `json:"log_level"`
    MaxLogSize  int64  `json:"max_log_size"`
}

// NewDaemonFramework 创建守护进程框架
func NewDaemonFramework(configFile string) (*DaemonFramework, error) {
    // 加载配置
    config, err := loadConfig(configFile)
    if err != nil {
        return nil, err
    }

    ctx, cancel := context.WithCancel(context.Background())

    return &DaemonFramework{
        config:     config,
        pidManager: NewPidFileManager(config.PidFile),
        ctx:        ctx,
        cancel:     cancel,
        services:   make([]Service, 0),
    }, nil
}

// loadConfig 加载配置文件
func loadConfig(configFile string) (*DaemonConfig, error) {
    data, err := os.ReadFile(configFile)
    if err != nil {
        return nil, fmt.Errorf("读取配置文件失败: %v", err)
    }

    var config DaemonConfig
    if err := json.Unmarshal(data, &config); err != nil {
        return nil, fmt.Errorf("解析配置文件失败: %v", err)
    }

    // 设置默认值
    if config.WorkDir == "" {
        config.WorkDir = "/"
    }
    if config.LogLevel == "" {
        config.LogLevel = "INFO"
    }

    return &config, nil
}

// RegisterService 注册服务
func (df *DaemonFramework) RegisterService(service Service) {
    df.services = append(df.services, service)
}

// Start 启动守护进程
func (df *DaemonFramework) Start() error {
    if df.isDaemonMode() {
        return df.runDaemon()
    }

    // 检查是否已经在运行
    if df.pidManager.IsRunning() {
        return fmt.Errorf("守护进程已经在运行")
    }

    return df.startDaemon()
}

// isDaemonMode 检查是否在守护进程模式
func (df *DaemonFramework) isDaemonMode() bool {
    return os.Getenv("DAEMON_MODE") == "1"
}

// startDaemon 启动守护进程
func (df *DaemonFramework) startDaemon() error {
    executable, err := os.Executable()
    if err != nil {
        return err
    }

    cmd := exec.Command(executable, os.Args[1:]...)
    cmd.Env = append(os.Environ(), "DAEMON_MODE=1")
    cmd.Dir = df.config.WorkDir
    cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}

    // 重定向输入输出
    devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
    if err != nil {
        return err
    }
    defer devNull.Close()

    cmd.Stdin = devNull
    cmd.Stdout = devNull
    cmd.Stderr = devNull

    if err := cmd.Start(); err != nil {
        return err
    }

    // 写入 PID 文件
    if err := df.pidManager.Write(cmd.Process.Pid); err != nil {
        cmd.Process.Kill()
        return err
    }

    fmt.Printf("守护进程 %s 已启动,PID: %d\n", df.config.Name, cmd.Process.Pid)
    return nil
}

// runDaemon 运行守护进程
func (df *DaemonFramework) runDaemon() error {
    // 设置日志
    if err := df.setupLogging(); err != nil {
        return err
    }

    df.logger.Printf("守护进程 %s 启动,PID: %d", df.config.Name, os.Getpid())

    // 写入 PID 文件
    if err := df.pidManager.Write(os.Getpid()); err != nil {
        return err
    }

    // 设置信号处理
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

    // 启动所有服务
    if err := df.startServices(); err != nil {
        return err
    }

    // 等待信号
    sig := <-sigChan
    df.logger.Printf("接收到信号: %s", sig)

    switch sig {
    case syscall.SIGINT, syscall.SIGTERM:
        df.logger.Println("开始优雅关闭...")
        df.shutdown()
    case syscall.SIGHUP:
        df.logger.Println("重新加载配置...")
        df.reload()
    }

    return nil
}

// setupLogging 设置日志
func (df *DaemonFramework) setupLogging() error {
    var writers []io.Writer

    // 添加标准输出(仅在非守护进程模式下)
    if !df.isDaemonMode() {
        writers = append(writers, os.Stdout)
    }

    // 添加日志文件
    if df.config.LogFile != "" {
        logDir := filepath.Dir(df.config.LogFile)
        if err := os.MkdirAll(logDir, 0755); err != nil {
            return err
        }

        logFile, err := os.OpenFile(df.config.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
        if err != nil {
            return err
        }

        writers = append(writers, logFile)
    }

    if len(writers) == 0 {
        writers = append(writers, os.Stdout)
    }

    df.logger = log.New(io.MultiWriter(writers...), fmt.Sprintf("[%s] ", df.config.Name), log.LstdFlags)
    return nil
}

// startServices 启动所有服务
func (df *DaemonFramework) startServices() error {
    for _, service := range df.services {
        df.logger.Printf("启动服务: %s", service.Name())

        df.wg.Add(1)
        go func(svc Service) {
            defer df.wg.Done()
            if err := svc.Start(df.ctx); err != nil {
                df.logger.Printf("服务 %s 启动失败: %v", svc.Name(), err)
            }
        }(service)
    }

    return nil
}

// shutdown 关闭守护进程
func (df *DaemonFramework) shutdown() {
    df.logger.Println("开始关闭所有服务...")

    // 取消上下文
    df.cancel()

    // 停止所有服务
    for _, service := range df.services {
        df.logger.Printf("停止服务: %s", service.Name())
        if err := service.Stop(); err != nil {
            df.logger.Printf("停止服务 %s 失败: %v", service.Name(), err)
        }
    }

    // 等待所有服务停止
    done := make(chan struct{})
    go func() {
        df.wg.Wait()
        close(done)
    }()

    select {
    case <-done:
        df.logger.Println("所有服务已停止")
    case <-time.After(30 * time.Second):
        df.logger.Println("等待服务停止超时")
    }

    // 清理 PID 文件
    df.pidManager.Remove()
    df.logger.Println("守护进程已关闭")
}

// reload 重新加载配置
func (df *DaemonFramework) reload() {
    df.logger.Println("重新加载配置...")
    // 这里可以添加配置重载逻辑
}

// Stop 停止守护进程
func (df *DaemonFramework) Stop() error {
    if !df.pidManager.IsRunning() {
        return fmt.Errorf("守护进程未运行")
    }

    pid, err := df.pidManager.GetPid()
    if err != nil {
        return err
    }

    process, err := os.FindProcess(pid)
    if err != nil {
        return err
    }

    // 发送 SIGTERM 信号
    if err := process.Signal(syscall.SIGTERM); err != nil {
        return err
    }

    // 等待进程退出
    for i := 0; i < 30; i++ {
        if !df.pidManager.IsRunning() {
            fmt.Printf("守护进程 %s 已停止\n", df.config.Name)
            return nil
        }
        time.Sleep(1 * time.Second)
    }

    // 强制终止
    process.Signal(syscall.SIGKILL)
    fmt.Printf("守护进程 %s 已强制停止\n", df.config.Name)
    return nil
}

// Status 检查状态
func (df *DaemonFramework) Status() {
    if df.pidManager.IsRunning() {
        pid, _ := df.pidManager.GetPid()
        fmt.Printf("守护进程 %s 正在运行 (PID: %d)\n", df.config.Name, pid)
    } else {
        fmt.Printf("守护进程 %s 未运行\n", df.config.Name)
    }
}

// 示例服务实现
type ExampleService struct {
    name string
}

func NewExampleService(name string) *ExampleService {
    return &ExampleService{name: name}
}

func (es *ExampleService) Name() string {
    return es.name
}

func (es *ExampleService) Start(ctx context.Context) error {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            log.Printf("服务 %s 正在工作...", es.name)
        case <-ctx.Done():
            log.Printf("服务 %s 收到停止信号", es.name)
            return nil
        }
    }
}

func (es *ExampleService) Stop() error {
    log.Printf("服务 %s 正在停止...", es.name)
    return nil
}

func main() {
    if len(os.Args) < 3 {
        fmt.Printf("用法: %s <config-file> {start|stop|status|restart}\n", os.Args[0])
        os.Exit(1)
    }

    configFile := os.Args[1]
    command := os.Args[2]

    daemon, err := NewDaemonFramework(configFile)
    if err != nil {
        fmt.Printf("创建守护进程失败: %v\n", err)
        os.Exit(1)
    }

    // 注册服务
    daemon.RegisterService(NewExampleService("worker1"))
    daemon.RegisterService(NewExampleService("worker2"))

    switch command {
    case "start":
        if err := daemon.Start(); err != nil {
            fmt.Printf("启动失败: %v\n", err)
            os.Exit(1)
        }
    case "stop":
        if err := daemon.Stop(); err != nil {
            fmt.Printf("停止失败: %v\n", err)
            os.Exit(1)
        }
    case "status":
        daemon.Status()
    case "restart":
        daemon.Stop()
        time.Sleep(2 * time.Second)
        if err := daemon.Start(); err != nil {
            fmt.Printf("重启失败: %v\n", err)
            os.Exit(1)
        }
    default:
        fmt.Printf("未知命令: %s\n", command)
        os.Exit(1)
    }
}

小结 #

守护进程开发是系统编程的重要技能。通过本节学习,我们掌握了:

  1. 守护进程基础:了解了守护进程的特征和创建步骤
  2. Go 实现方式:学会了在 Go 中实现守护进程的方法
  3. PID 文件管理:掌握了进程管理的最佳实践
  4. 完整框架:构建了可复用的守护进程框架

这些技术为开发稳定可靠的系统服务奠定了基础,是构建企业级应用程序的重要技能。