用 Go 编写 Windows 服务
来源:dev.to
时间:2024-08-29 20:46:03 368浏览 收藏
从现在开始,努力学习吧!本文《用 Go 编写 Windows 服务》主要讲解了等等相关知识点,我会在golang学习网中持续更新相关的系列文章,欢迎大家关注并积极留言建议。下面就先一起来看一下本篇正文内容吧,希望能帮到你!
目录
- 简介
- 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 完成,服务就会退出。
我们从仅接收通道 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 语句,我们可以处理服务的所有生命周期事件/信号。我们可以在下面看到每个生命周期,
- svc.interrogate - scm 及时请求信号来检查服务的当前状态。
- svc.stop 和 svc.shutdown - 当我们的服务需要停止或关闭时,scm 发送信号。
- svc.pause - scm 发送的信号以暂停服务执行而不关闭它。
- 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) }
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于Golang的相关知识,也可关注golang学习网公众号。
-
505 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
197 收藏
-
338 收藏
-
370 收藏
-
380 收藏
-
338 收藏
-
370 收藏
-
268 收藏
-
103 收藏
-
424 收藏
-
186 收藏
-
380 收藏
-
403 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习