Skip to content

Commit

Permalink
Update archives.
Browse files Browse the repository at this point in the history
  • Loading branch information
poneding committed Oct 16, 2024
1 parent e8d19b6 commit 4a5f62c
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 71 deletions.
4 changes: 2 additions & 2 deletions content/.obsidian/appearance.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"accentColor": "",
"theme": "system",
"monospaceFontFamily": "Menlo,Monaco,Ubuntu Mono",
"interfaceFontFamily": "Menlo,Monaco,Ubuntu",
"monospaceFontFamily": "Menlo",
"interfaceFontFamily": "Menlo",
"baseFontSize": 16,
"cssTheme": "",
"enabledCssSnippets": [
Expand Down
213 changes: 144 additions & 69 deletions content/go/go-daemon.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,121 +4,196 @@

在 Go 中编写后台运行的程序(Daemon)的常见步骤涉及到以下几个方面:

1. **创建守护进程**
## 创建守护进程

守护进程是一个在后台运行、不受用户交互影响的程序。你可以通过调用 syscall 包中的一些系统调用让 Go 程序变为后台运行的守护进程。
守护进程是一个在后台运行、不受用户交互影响的程序。你可以通过调用 `syscall` 包中的一些系统调用让 Go 程序变为后台运行的守护进程。

示例代码:

*fork.go*

```go
package main

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

func init() {
if slices.Contains(os.Args, "-d") {
// Fork 出子进程并获取其 PID
newArgs := make([]string, 0)
for _, arg := range os.Args {
if arg == "-d" {
continue
}
newArgs = append(newArgs, arg)
}

pid, err := syscall.ForkExec(os.Args[0], newArgs, &syscall.ProcAttr{
Files: []uintptr{uintptr(syscall.Stdin), uintptr(syscall.Stdout), uintptr(syscall.Stderr)},
})
if err != nil {
fmt.Println("fork failed:", err)
return
}

// 将子进程 PID 保存到文件
file, err := os.Create("child_pid.txt")
if err != nil {
fmt.Println("create file failed:", err)
return
}
defer file.Close()
file.WriteString(fmt.Sprintf("pid: %d", pid))

os.Exit(0) // 父进程退出
}
}
```

*main.go*

```go
package main

import (
    "fmt"
    "os"
    "syscall"
"fmt"
"io/fs"
"log"
"os"
"time"
)

func main() {
    // Fork出一个子进程_
    if syscall.ForkExec(os.Args[0], os.Args, &syscall.ProcAttr{}) != nil {
        os.Exit(0) // 父进程退出,子进程继续_
    }

    // 继续子进程的逻辑_
    fmt.Println("程序已经后台运行")
    // 主逻辑_
    for {
        // 在这里执行你的后台任务_
    }
// 业务逻辑:每隔一秒向文件写入新内容
for {
time.Sleep(1 * time.Second)
file, err := os.OpenFile("./hello", os.O_CREATE|os.O_WRONLY|os.O_APPEND, fs.FileMode(0644))
if err != nil {
log.Println("open file failed:", err)
return
}

_, err = file.WriteString(fmt.Sprintf("[%s] Hello.\n", time.Now().Format(time.RFC3339)))
if err != nil {
log.Println("write file failed:", err)
}
file.Close()
}
}
```

2. **日志和输出重定向**
## 使用 `nohup` 命令运行

后台程序通常不会直接输出到终端,而是将输出重定向到日志文件或 `/dev/null`
如果不想修改代码,可以直接在启动时使用 Linux 提供的 `nohup` 命令运行 Go 程序,使其不依赖终端,并自动重定向输出:

示例:
代码示例:

*main.go*


```go
package main

import (
    "os"
    "os/exec"
    "syscall"
"fmt"
"io/fs"
"log"
"os"
"time"
)

func main() {
    // 设置日志文件或将输出重定向_
    file, err := os.OpenFile("/tmp/daemon.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // 设置子进程的文件描述符_
    attr := &syscall.ProcAttr{
        Files: []uintptr{file.Fd(), file.Fd(), file.Fd()},
    }

    if _, err := syscall.ForkExec(os.Args[0], os.Args, attr); err != nil {
        panic(err)
    }
    os.Exit(0) // 退出父进程,子进程继续后台运行_
// 业务逻辑:每隔一秒向文件写入新内容
for {
time.Sleep(1 * time.Second)
file, err := os.OpenFile("./hello", os.O_CREATE|os.O_WRONLY|os.O_APPEND, fs.FileMode(0644))
if err != nil {
log.Println("open file failed:", err)
return
}

_, err = file.WriteString(fmt.Sprintf("[%s] Hello.\n", time.Now().Format(time.RFC3339)))
if err != nil {
log.Println("write file failed:", err)
}
file.Close()
}
}
```

3. 使用 `nohup` 命令运行:

如果不想修改代码,可以直接在启动时使用 Linux 提供的 `nohup` 命令运行 Go 程序,使其不依赖终端,并自动重定向输出:
运行:

```bash
nohup ./your_go_program > output.log 2>&1 &
go build -o main main.go
nohup ./main > output.log 2>&1 &
```

4. 使用第三方库(如 daemon 包)
## 使用第三方库(如 go-daemon 包)

如果觉得自己管理后台运行逻辑比较麻烦,可以使用一些库,如 `github.com/sevlyar/go-daemon`,简化守护进程的实现。

示例:
代码示例:

*main.go*

```go
package main

import (
    "fmt"
    "github.com/sevlyar/go-daemon"
"fmt"
"io/fs"
"log"
"os"
"time"

"github.com/sevlyar/go-daemon"
)

func main() {
    cntxt := &daemon.Context{
        PidFileName: "sample.pid",
        PidFilePerm: 0644,
        LogFileName: "sample.log",
        LogFilePerm: 0640,
        WorkDir:     "./",
        Umask:       027,
    }

    d, err := cntxt.Reborn()
    if err != nil {
        fmt.Println("无法启动后台进程:", err)
        return
    }

    if d != nil {
        return
    }
    defer cntxt.Release()

    fmt.Println("后台进程运行中")
    // 执行你的后台任务_
cntxt := &daemon.Context{
PidFileName: "sample.pid",
PidFilePerm: 0644,
LogFileName: "sample.log",
LogFilePerm: 0640,
WorkDir: "./",
Umask: 027,
}

d, err := cntxt.Reborn()
if err != nil {
fmt.Println("无法启动后台进程:", err)
return
}

if d != nil {
return
}
defer cntxt.Release()

// 业务逻辑:每隔一秒向文件写入新内容
for {
time.Sleep(1 * time.Second)
file, err := os.OpenFile("./hello", os.O_CREATE|os.O_WRONLY|os.O_APPEND, fs.FileMode(0644))
if err != nil {
log.Println("open file failed:", err)
return
}

_, err = file.WriteString(fmt.Sprintf("[%s] Hello.\n", time.Now().Format(time.RFC3339)))
if err != nil {
log.Println("write file failed:", err)
}
file.Close()
}
}
```

你可以根据你的需求选择合适的方法让 Go 程序后台运行。如果你有更具体的需求,欢迎提供更多细节。

---
[« Golang 不同平台架构编译](go-cross-complie.md)

Expand Down

0 comments on commit 4a5f62c

Please sign in to comment.