Hallo Entwickler, es ist schon eine Weile her, seit ich etwas Windows-artiges geschrieben habe. Deshalb möchte ich euch heute zeigen, wie man eine Windows-Dienstanwendung in Go schreibt. Ja, Sie haben es richtig gehört, es geht um Sprache. In diesem Tutorial-Blog behandeln wir einige grundlegende Dinge zu Windows-Dienstanwendungen und im späteren Teil führe ich Sie durch einen einfachen Code-Rundgang, bei dem wir Code für einen Windows-Dienst schreiben, der einige Informationen in einer Datei protokolliert. Ohne weitere Umschweife: Fangen wir an...!
Eine Windows-Dienstanwendung, auch bekannt als Windows-Dienste, sind kleine Anwendungen, die im Hintergrund ausgeführt werden. Im Gegensatz zu normalen Windows-Anwendungen verfügen sie weder über eine grafische Benutzeroberfläche noch über irgendeine Benutzeroberfläche. Diese Dienstanwendungen werden ausgeführt, wenn der Computer hochfährt. Es läuft unabhängig davon, unter welchem Benutzerkonto es läuft. Sein Lebenszyklus (Start, Stopp, Pause, Fortfahren usw.) wird von einem Programm namens Service Control Manager (SCM) gesteuert.
Daraus können wir also verstehen, dass wir unseren Windows-Dienst so schreiben sollten, dass der SCM mit unserem Windows-Dienst interagieren und dessen Lebenszyklus verwalten sollte.
Es gibt mehrere Faktoren, die Sie zum Schreiben von Windows-Diensten in Betracht ziehen könnten.
Das Parallelitätsmodell von Go ermöglicht eine schnellere und ressourceneffiziente Verarbeitung. Mit den Goroutinen von Go können wir Anwendungen schreiben, die Multitasking ohne Blockierung oder Deadlocks ausführen können.
Traditionell werden Windows-Dienste entweder mit C++ oder C (manchmal C#) geschrieben, was nicht nur zu einem komplexen Code, sondern auch zu einer schlechten DX (Developer Experience) führt. Gos Implementierung der Windows-Dienste ist unkompliziert und jede Codezeile macht SinnStatische Binärdateien
Sie fragen sich vielleicht: „Warum nicht eine noch einfachere Sprache wie Python verwenden?“ Der Grund liegt in der interpretierten Natur von Python. Go kompiliert zu einer statisch verknüpften Einzeldatei-Binärdatei, die für die effiziente Funktion eines Windows-Dienstes unerlässlich ist. Go-Binärdateien erfordern keine Laufzeit/Interpreter. Go-Code kann auch kreuzkompiliert werden. Zugang auf niedriger Ebene Obwohl es sich um eine Garbage-Collected-Sprache handelt, bietet Go solide Unterstützung für die Interaktion mit Low-Level-Elementen. Wir können problemlos Win32-APIs und allgemeine Systemaufrufe in go.Als Erstes benennen wir unser Projekt. Ich werde meine als cosmic/my_service bezeichnen. Erstellen Sie eine go.mod-Datei.
PS C:\> go mod init cosmic/my_service
PS C:\> go get golang.org/x/sys
Erstellen Sie eine main.go-Datei. Die Datei main.go enthält die Hauptfunktion, die als Einstiegspunkt für unsere Go-Anwendung/unseren Go-Dienst dient.
implementiert
Die Schnittstellendefinition sieht also ungefähr so austype Handler interface { Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) }
Die Execute-Funktion wird vom Paketcode beim Start des Dienstes aufgerufen und der Dienst wird beendet, sobald die Ausführung abgeschlossen ist.
Erstellen Sie nun einen Typ namens „myService“, der als unser Servicekontext fungiert.
type myService struct{}
func (m *myService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { // to be filled }
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,
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 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.
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) }
Das obige ist der detaillierte Inhalt vonEinen Windows-Dienst in Go schreiben. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!