用 Go 編寫 Windows 服務

PHPz
發布: 2024-08-28 06:40:10
原創
884 人瀏覽過

Writing a Windows Service in Go

目錄

  • 簡介
  • 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 文件,
雷雷
  • 現在我們需要安裝 golang.org/x/sys 套件。該套件為Windows作業系統相關應用程式提供go語言支援。
雷雷

注意:此軟體包還包含基於 UNIX 的作業系統(如 Mac OS 和 Linux)的作業系統級 go 語言支援。

  • 建立一個main.go檔案。 main.go 檔案包含 main 函數,它充當我們的 Go 應用程式/服務的入口點。

  • 為了建立服務實例,我們需要寫一個名為Service Context的東西,它實作了 golang.org/x/sys/windows/svc 的 Handler 介面。

所以,介面定義看起來像這樣

雷雷

Execute 函數會在服務啟動時被套件程式碼調用,一旦 Execute 完成,服務就會退出。

我們從僅接收通道 r 讀取服務變更請求並採取相應行動。我們還應該透過向僅發送通道發送信號來更新我們的服務。我們可以將可選參數傳遞給 args 參數。

退出時,我們可以回傳 exitCode 為 0 的成功執行。我們也可以使用 svcSpecificEC 來實現這一點。

  • 現在,建立一個名為 myService 的類型,它將充當我們的服務上下文。
雷雷
  • 建立類型 myService 後,將上述的 Execute 作為方法加入其中,使其實作 Handler 介面。
雷雷
  • 現在我們已經成功實作了Handler接口,現在我們可以開始編寫實際的邏輯了。

建立一個常數,其中包含我們的服務可以從 SCM 接受的訊號。

const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
登入後複製

Our main goal is the log some data every 30 seconds. So we need to define a thread safe timer for that.

tick := time.Tick(30 * time.Second)
登入後複製

So, we have done all the initialization stuffs. It's time to send START signal to the SCM. we're going to do exactly that,

status <- svc.Status{State: svc.StartPending} status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
登入後複製

Now we're going to write a loop which acts as amainloopfor our application. Handling events in loop makes our application never ending and we can break the loop only when the SCM sends STOP or SHUTDOWN signal.

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) } } }
登入後複製

Here we used a select statement to receive signals from channels. In first case, we handle the Timer's tick signal. This case receives signal every 30 seconds, as we declared before. We log a string "Tick Handled...!" in this case.

Secondly, we handle the signals from SCM via the receive-only r channel. So, we assign the value of the signal from r to a variable c and using a switch statement, we can handle all the lifecycle event/signals of our service. We can see about each lifecycle below,

  1. svc.Interrogate - Signal requested by SCM on a timely fashion to check the current status of the service.
  2. svc.Stop and svc.Shutdown - Signal sent by SCM when our service needs to be stopped or Shut Down.
  3. svc.Pause - Signal sent by SCM to pause the service execution without shutting it down.
  4. svc.Continue - Signal sent by SCM to resume the paused execution state of the service.

So, when on receiving either svc.Stop or svc.Shutdown signal, we break the loop. It is to be noted that we need to send STOP signal to the SCM to let the SCM know that our service is stopping.

status <- svc.Status{State: svc.StopPending} return false, 1
登入後複製
  • Now we write a function called runService where we enable our service to run either in Debug mode or in Service Control Mode.

Note: It's super hard to debug Windows Service Applications when running on Service Control Mode. That's why we are writing an additional Debug mode.

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.") } } }
登入後複製
  • Finally we can call the runService function in our main function.
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 }
登入後複製

Note: We are logging the logs to a log file. In advanced scenarios, we log our logs to Windows Event Logger. (phew, that sounds like a tongue twister ?)

  • Now run go build to create a binary '.exe'. Optionally, we can optimize and reduce the binary file size by using the following command,
PS C:\> go build -ldflags "-s -w"
登入後複製

Installing and Starting the Service

For installing, deleting, starting and stopping our service, we use an inbuilt tool called sc.exe

To install our service, run the following command in powershellas Administrator,

PS C:\> sc.exe create MyService 
登入後複製

To start our service, run the following command,

PS C:\> sc.exe start MyService
登入後複製

To delete our service, run the following command,

PS C:\> sc.exe delete MyService
登入後複製

You can explore more commands, just type sc.exe without any arguments to see the available commands.

Conclusion

As we can see, implementing Windows Services in go is straightforward and requires minimal implementation. You can write your own windows services which acts as a web server and more. Thanks for reading and don't forget to drop a ❤️.

Complete Code

Here is the complete code for your reference.

// 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中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!