Einen Windows-Dienst in Go schreiben

PHPz
Freigeben: 2024-08-28 06:40:10
Original
884 Leute haben es durchsucht

Writing a Windows Service in Go

Inhaltsverzeichnis

  • Einführung
  • Was genau ist ein Windows-„Dienst“?
  • Warum Golang?
  • Einen Windows-Dienst in Go schreiben
  • Installieren und Starten des Dienstes
  • Fazit
  • Vollständiger Code

Einführung

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...!

Was genau ist ein Windows-„Dienst“?

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.

Warum Golang?

Es gibt mehrere Faktoren, die Sie zum Schreiben von Windows-Diensten in Betracht ziehen könnten.

Parallelität

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.

Einfachheit

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.

aufrufen

Okay, genug Informationen. Lasst uns programmieren...

Einen Windows-Dienst in Go schreiben

Diese Code-Anleitung setzt voraus, dass Sie über Grundkenntnisse der Go-Syntax verfügen. Wenn nicht, wäre A Tour of Go ein guter Ort, um es zu lernen.

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
    Nach dem Login kopieren
  • Jetzt müssen wir das Paket golang.org/x/sys installieren. Dieses Paket bietet Go-Sprachunterstützung für Windows-bezogene Anwendungen.
      PS C:\> go get golang.org/x/sys
      Nach dem Login kopieren
    • Hinweis: Dieses Paket enthält auch OS-Level-Go-Sprachunterstützung für UNIX-basierte Betriebssysteme wie Mac OS und Linux.

      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.
      • Um eine Dienstinstanz zu erstellen, müssen wir etwas namens
      • Service Context
      • schreiben, das die Handler-Schnittstelle von golang.org/x/sys/windows/svc.

        implementiert

        Die Schnittstellendefinition sieht also ungefähr so aus
      type Handler interface { Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) }
      Nach dem Login kopieren

      Die Execute-Funktion wird vom Paketcode beim Start des Dienstes aufgerufen und der Dienst wird beendet, sobald die Ausführung abgeschlossen ist.

      Wir lesen Serviceänderungsanfragen vom Nur-Empfangskanal r und handeln entsprechend. Wir sollten unseren Dienst auch mit dem Senden von Signalen an reine Sendekanäle auf dem neuesten Stand halten. Wir können optionale Argumente an den Parameter args übergeben.

      Beim Beenden können wir bei erfolgreicher Ausführung mit dem ExitCode 0 zurückkehren. Dafür können wir auch svcSpecificEC verwenden.

      Erstellen Sie nun einen Typ namens „myService“, der als unser Servicekontext fungiert.

        type myService struct{}
        Nach dem Login kopieren
      • Nachdem Sie den Typ myService erstellt haben, fügen Sie ihm die oben erwähnte Execute-Methode hinzu, damit er die Handler-Schnittstelle implementiert.
        func (m *myService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { // to be filled }
        Nach dem Login kopieren
      • Nachdem wir die Handler-Schnittstelle erfolgreich implementiert haben, können wir nun mit dem Schreiben der eigentlichen Logik beginnen.
      • Erstellen Sie eine Konstante mit den Signalen, die unser Service von SCM akzeptieren kann.
      const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
      Nach dem Login kopieren

      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)
      Nach dem Login kopieren

      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}
      Nach dem Login kopieren

      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) } } }
      Nach dem Login kopieren

      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
      Nach dem Login kopieren
      • 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.") } } }
      Nach dem Login kopieren
      • 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 }
      Nach dem Login kopieren

      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"
      Nach dem Login kopieren

      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 
      Nach dem Login kopieren

      To start our service, run the following command,

      PS C:\> sc.exe start MyService
      Nach dem Login kopieren

      To delete our service, run the following command,

      PS C:\> sc.exe delete MyService
      Nach dem Login kopieren

      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) }
      Nach dem Login kopieren

      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!

      Quelle:dev.to
      Erklärung dieser Website
      Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
      Neueste Downloads
      Mehr>
      Web-Effekte
      Quellcode der Website
      Website-Materialien
      Frontend-Vorlage
      Über uns Haftungsausschluss Sitemap
      Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!