开发者们大家好,我已经有一段时间没有写一些 windows 风格的东西了。所以,今天我想指导大家如何用 go 编写 windows 服务应用程序。是的,你没有听错,就是go语言。在本教程博客中,我们将介绍有关 windows 服务应用程序的一些基本内容,在后面的部分中,我将指导您完成一个简单的代码演练,我们为 windows 服务编写代码,将一些信息记录到文件中。话不多说,让我们开始吧...!
windows 服务应用程序又名 windows 服务是在后台运行的小型应用程序。与普通的 windows 应用程序不同,它们没有 gui 或任何形式的用户界面。这些服务应用程序在计算机启动时开始运行。无论它在哪个用户帐户中运行,它都会运行。它的生命周期(启动、停止、暂停、继续等)由名为服务控制管理器 (scm) 的程序控制。
因此,从这里我们可以理解,我们应该以这样的方式编写我们的 windows service,以便 scm 应该与我们的 windows service 交互并管理它的生命周期。
您可以考虑使用 go 来编写 windows 服务的几个因素。
go 的并发模型允许更快且资源高效的处理。 go 的 goroutine 允许我们编写可以执行多任务处理而不会出现任何阻塞或死锁的应用程序。
传统上,windows 服务是使用 c++ 或 c(有时是 c#)编写的,这不仅导致代码复杂,而且 dx(开发人员体验)很差。 go 对 windows 服务的实现非常简单,每一行代码都有意义.
你可能会问,“为什么不使用像python这样更简单的语言呢?”。原因是python 的解释性质。 go 编译为静态链接的单个文件二进制文件,这对于 windows 服务高效运行至关重要。 go 二进制文件不需要任何运行时/解释器。 go代码也可以交叉编译。
虽然 go 是一种垃圾收集语言,但它为与低级元素交互提供了坚实的支持。我们可以在go中轻松调用win32 api和通用系统调用。
好吧,信息足够了。让我们编码...
此代码演练假设您具有 go 语法的基本知识。如果没有,a tour of go 将是一个学习 go 的好地方。
ps c:\> go mod init cosmic/my_service
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 完成,服务就会退出。
我们从仅接收通道 r 读取服务变更请求并采取相应行动。我们还应该通过向仅发送通道发送信号来更新我们的服务。我们可以将可选参数传递给 args 参数。
退出时,我们可以返回 exitcode 为 0 的成功执行。我们还可以使用 svcspecificec 来实现这一点。
type myservice struct{}
func (m *myservice) execute(args []string, r <-chan svc.changerequest, status chan<- svc.status) (bool, uint32) { // to be filled }
创建一个常量,其中包含我们的服务可以从 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 语句,我们可以处理服务的所有生命周期事件/信号。我们可以在下面看到每个生命周期,
因此,当收到 svc.stop 或 svc.shutdown 信号时,我们会中断循环。需要注意的是,我们需要向scm发送stop信号,让scm知道我们的服务正在停止。
status <- svc.status{state: svc.stoppending} return false, 1
注意:在服务控制模式下运行时,调试 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.") } } }
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 事件记录器。 (唷,这听起来像绕口令?)
ps c:\> go build -ldflags "-s -w"
为了安装、删除、启动和停止我们的服务,我们使用一个名为 sc.exe 的内置工具
要安装我们的服务,请以管理员身份在 powershell 中运行以下命令,
ps c:\> sc.exe create myservice <path to your service_app.exe>
ps c:\> sc.exe start myservice
ps c:\> sc.exe delete myservice
结论
完整代码
// 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) }
以上就是用 Go 编写 Windows 服务的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号