開發者們大家好,我已經有一段時間沒有寫一些 Windows 風格的東西了。所以,今天我想指導大家如何用 Go 寫 Windows 服務應用程式。是的,你沒有聽錯,就是go語言。在本教學部落格中,我們將介紹有關Windows 服務應用程式的一些基本內容,在後面的部分中,我將指導您完成一個簡單的程式碼演練,我們為Windows 服務編寫程式碼,將一些資訊記錄到文件中。話不多說,讓我們開始吧...!
Windows 服務應用程式又稱為 Windows 服務是在背景執行的微型應用程式。與普通的 Windows 應用程式不同,它們沒有 GUI 或任何形式的使用者介面。這些服務應用程式在電腦啟動時開始運作。無論它在哪個用戶帳戶中運行,它都會運行。它的生命週期(啟動、停止、暫停、繼續等)由名為服務控制管理員 (SCM) 的程式控制。
因此,從這裡我們可以理解,我們應該以這樣的方式編寫我們的 Windows 服務,即 SCM 應該與我們的 Windows 服務互動並管理它的生命週期。
您可以考慮使用 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) }
執行函數將在服務啟動時由套件代碼調用,一旦執行完成,服務將退出。
我們從僅接收通道 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
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 a mainloop for 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,
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
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.") } } }
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 ?)
PS C:\> go build -ldflags "-s -w"
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 powershell as Administrator,
PS C:\> sc.exe create MyService <path to your service_app.exe>
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.
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 ❤️.
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中文網其他相關文章!