0

0

用 Go 编写 Windows 服务

PHPz

PHPz

发布时间:2024-08-28 09:06:33

|

993人浏览过

|

来源于dev.to

转载

用 go 编写 windows 服务

目录

  • 简介
  • windows“服务”到底是什么?
  • 为什么选择 golang
  • 用 go 编写 windows 服务
  • 安装并启动服务
  • 结论
  • 完整代码

介绍

开发者们大家好,我已经有一段时间没有写一些 windows 风格的东西了。所以,今天我想指导大家如何用 go 编写 windows 服务应用程序。是的,你没有听错,就是go语言。在本教程博客中,我们将介绍有关 windows 服务应用程序的一些基本内容,在后面的部分中,我将指导您完成一个简单的代码演练,我们为 windows 服务编写代码,将一些信息记录到文件中。话不多说,让我们开始吧...!

windows“服务”到底是什么?

windows 服务应用程序又名 windows 服务是在后台运行的小型应用程序。与普通的 windows 应用程序不同,它们没有 gui 或任何形式的用户界面。这些服务应用程序在计算机启动时开始运行。无论它在哪个用户帐户中运行,它都会运行。它的生命周期(启动、停止、暂停、继续等)由名为服务控制管理器 (scm) 的程序控制。

因此,从这里我们可以理解,我们应该以这样的方式编写我们的 windows service,以便 scm 应该与我们的 windows service 交互并管理它的生命周期。

为什么选择 golang?

您可以考虑使用 go 来编写 windows 服务的几个因素。

并发性

go 的并发模型允许更快且资源高效的处理。 go 的 goroutine 允许我们编写可以执行多任务处理而不会出现任何阻塞或死锁的应用程序。

简单

传统上,windows 服务是使用 c++ 或 c(有时是 c#)编写的,这不仅导致代码复杂,而且 dx(开发人员体验)很差。 go 对 windows 服务的实现非常简单,每一行代码都有意义.

静态二进制文件

你可能会问,“为什么不使用像python这样更简单的语言呢?”。原因是python 的解释性质。 go 编译为静态链接的单个文件二进制文件,这对于 windows 服务高效运行至关重要。 go 二进制文件不需要任何运行时/解释器。 go代码也可以交叉编译。

低级访问

虽然 go 是一种垃圾收集语言,但它为与低级元素交互提供了坚实的支持。我们可以在go中轻松调用win32 api和通用系统调用。

好吧,信息足够了。让我们编码...

用 go 编写 windows 服务

此代码演练假设您具有 go 语法的基本知识。如果没有,a tour of go 将是一个学习 go 的好地方。

  • 首先,让我们为我们的项目命名。我将地雷命名为 cosmic/my_service。创建一个 go.mod 文件,
ps c:\> go mod init cosmic/my_service
  • 现在我们需要安装 golang.org/x/sys 包。该包为windows操作系统相关应用程序提供go语言支持。
ps c:\> go get golang.org/x/sys
注意:此软件包还包含对基于 unix 的操作系统(如 mac os 和 linux)的操作系统级 go 语言支持。
  • 创建一个main.go文件。 main.go 文件包含 main 函数,它充当我们的 go 应用程序/服务的入口点。

  • 为了创建服务实例,我们需要编写一个名为service context的东西,它实现了 golang.org/x/sys/windows/svc 的 handler 接口。

所以,接口定义看起来像这样

type handler interface {
    execute(args []string, r <-chan changerequest, s chan<- status) (svcspecificec bool, exitcode uint32)
}

execute 函数会在服务启动时被包代码调用,一旦 execute 完成,服务就会退出。

墨鱼aigc
墨鱼aigc

一款超好用的Ai写作工具,为用户提供一键生成营销广告、原创文案、写作辅助等文字生成服务。

下载

我们从仅接收通道 r 读取服务变更请求并采取相应行动。我们还应该通过向仅发送通道发送信号来更新我们的服务。我们可以将可选参数传递给 args 参数。

退出时,我们可以返回 exitcode 为 0 的成功执行。我们还可以使用 svcspecificec 来实现这一点。

  • 现在,创建一个名为 myservice 的类型,它将充当我们的服务上下文。
type myservice struct{}
  • 创建类型 myservice 后,将上面提到的 execute 作为方法添加到其中,使其实现 handler 接口。
func (m *myservice) execute(args []string, r <-chan svc.changerequest, status chan<- svc.status) (bool, uint32) {
    // to be filled
}
  • 现在我们已经成功实现了handler接口,现在我们可以开始编写实际的逻辑了。

创建一个常量,其中包含我们的服务可以从 scm 接受的信号。

const cmdsaccepted = svc.acceptstop | svc.acceptshutdown | svc.acceptpauseandcontinue

我们的主要目标是每 30 秒记录一些数据。所以我们需要为此定义一个线程安全的计时器。

tick := time.tick(30 * time.second)

所以,我们已经完成了所有的初始化工作。是时候向 scm 发送 start 信号了。我们就是这么做的,

status <- svc.status{state: svc.startpending}
status <- svc.status{state: svc.running, accepts: cmdsaccepted}

现在我们要编写一个循环,充当我们应用程序的主循环。在循环中处理事件使我们的应用程序永远不会结束,只有当 scm 发送 stop 或 shutdown 信号时我们才能打破循环。

loop:
    for {
        select {
        case <-tick:
            log.print("tick handled...!")
        case c := <-r:
            switch c.cmd {
            case svc.interrogate:
                status <- c.currentstatus
            case svc.stop, svc.shutdown:
                log.print("shutting service...!")
                break loop
            case svc.pause:
                status <- svc.status{state: svc.paused, accepts: cmdsaccepted}
            case svc.continue:
                status <- svc.status{state: svc.running, accepts: cmdsaccepted}
            default:
                log.printf("unexpected service control request #%d", c)
            }
        }
    }

这里我们使用了 select 语句来接收来自通道的信号。在第一种情况下,我们处理计时器的滴答信号。正如我们之前声明的,此案例每 30 秒接收一次信号。我们记录一个字符串“tick handled...!”在这种情况下.

其次,我们通过仅接收的 r 通道处理来自 scm 的信号。因此,我们将 r 中的信号值分配给变量 c 并使用 switch 语句,我们可以处理服务的所有生命周期事件/信号。我们可以在下面看到每个生命周期,

  1. svc.interrogate - scm 及时请求信号来检查服务的当前状态。
  2. svc.stop 和 svc.shutdown - 当我们的服务需要停止或关闭时,scm 发送信号。
  3. svc.pause - scm 发送的信号以暂停服务执行而不关闭它。
  4. svc.continue - scm 发送的信号以恢复服务的暂停执行状态。

因此,当收到 svc.stop 或 svc.shutdown 信号时,我们会中断循环。需要注意的是,我们需要向scm发送stop信号,让scm知道我们的服务正在停止。

status <- svc.status{state: svc.stoppending}
return false, 1
  • 现在我们编写一个名为 runservice 的函数,使我们的服务能够在调试模式或服务控制模式下运行。

注意:在服务控制模式下运行时,调试 windows 服务应用程序非常困难。这就是我们编写额外的调试模式的原因。

func runservice(name string, isdebug bool) {
    if isdebug {
        err := debug.run(name, &myservice{})
        if err != nil {
            log.fatalln("error running service in debug mode.")
        }
    } else {
        err := svc.run(name, &myservice{})
        if err != nil {
            log.fatalln("error running service in debug mode.")
        }
    }
}
  • 最后我们可以在main函数中调用runservice函数了。
func main() {

    f, err := os.openfile("debug.log", os.o_rdwr|os.o_create|os.o_append, 0666)
    if err != nil {
        log.fatalln(fmt.errorf("error opening file: %v", err))
    }
    defer f.close()

    log.setoutput(f)
    runservice("myservice", false) //change to true to run in debug mode
}

注意:我们将日志记录到日志文件中。在高级场景中,我们将日志记录到 windows 事件记录器。 (唷,这听起来像绕口令?)

  • 现在运行 go build 创建一个二进制“.exe”。或者,我们可以使用以下命令优化和减小二进制文件大小,
ps c:\> go build -ldflags "-s -w"

安装并启动服务

为了安装、删除、启动和停止我们的服务,我们使用一个名为 sc.exe 的内置工具

要安装我们的服务,请以管理员身份在 powershell 中运行以下命令

ps c:\> sc.exe create myservice 

要启动我们的服务,请运行以下命令,


ps c:\> sc.exe start myservice

要删除我们的服务,请运行以下命令,


ps c:\> sc.exe delete myservice

您可以探索更多命令,只需键入不带任何参数的 sc.exe 即可查看可用命令。

结论

正如我们所见,在 go 中实现 windows 服务非常简单,并且需要最少的实现。您可以编写自己的 windows 服务,充当 web 服务器等。感谢您的阅读,别忘了留下❤️.

完整代码

这是完整的代码供大家参考。


// file: main.go

package main

import (
    "fmt"
    "golang.org/x/sys/windows/svc"
    "golang.org/x/sys/windows/svc/debug"
    "log"
    "os"
    "time"
)

type myService struct{}

func (m *myService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) {

    const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
    tick := time.Tick(5 * time.Second)

    status <- svc.Status{State: svc.StartPending}

    status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}

loop:
    for {
        select {
        case <-tick:
            log.Print("Tick Handled...!")
        case c := <-r:
            switch c.Cmd {
            case svc.Interrogate:
                status <- c.CurrentStatus
            case svc.Stop, svc.Shutdown:
                log.Print("Shutting service...!")
                break loop
            case svc.Pause:
                status <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
            case svc.Continue:
                status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
            default:
                log.Printf("Unexpected service control request #%d", c)
            }
        }
    }

    status <- svc.Status{State: svc.StopPending}
    return false, 1
}

func runService(name string, isDebug bool) {
    if isDebug {
        err := debug.Run(name, &myService{})
        if err != nil {
            log.Fatalln("Error running service in debug mode.")
        }
    } else {
        err := svc.Run(name, &myService{})
        if err != nil {
            log.Fatalln("Error running service in debug mode.")
        }
    }
}

func main() {

    f, err := os.OpenFile("E:/awesomeProject/debug.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil {
        log.Fatalln(fmt.Errorf("error opening file: %v", err))
    }
    defer f.Close()

    log.SetOutput(f)
    runService("myservice", false)
}

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

182

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

229

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

343

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

210

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

396

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

240

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

194

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

458

2025.06.17

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

7

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 8.1万人学习

Git 教程
Git 教程

共21课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号