Bonjour les développeurs, Cela fait un moment que je n'ai pas écrit quelque chose de type Windows. Donc, aujourd’hui, je veux vous guider sur la façon d’écrire une application de service Windows dans Go. Oui, vous avez bien entendu, c'est parti pour le langage. Dans ce blog de didacticiel, nous aborderons quelques éléments de base sur les applications de service Windows et, plus tard, je vous guiderai à travers une simple procédure de code dans laquelle nous écrivons du code pour un service Windows qui enregistre certaines informations dans un fichier. Sans plus attendre, commençons...!
Une application de service Windows, également appelée services Windows, est de petites applications qui s'exécutent en arrière-plan. Contrairement aux applications Windows normales, elles n'ont pas d'interface graphique ni aucune forme d'interface utilisateur. Ces applications de service commencent à s'exécuter au démarrage de l'ordinateur. Il s'exécute quel que soit le compte utilisateur dans lequel il s'exécute. Son cycle de vie (démarrage, arrêt, pause, poursuite, etc.) est contrôlé par un programme appelé Service Control Manager (SCM).
Donc, à partir de là, nous pouvons comprendre que nous devons écrire notre service Windows de telle manière que le SCM doive interagir avec notre service Windows et gérer son cycle de vie.
Il existe plusieurs facteurs pour lesquels vous pouvez envisager Go pour écrire des services Windows.
Le modèle de concurrence de Go permet un traitement plus rapide et économe en ressources. Les goroutines de Go nous permettent d'écrire des applications capables d'effectuer plusieurs tâches sans aucun blocage ni blocage.
Traditionnellement, les services Windows sont écrits en C++ ou en C (parfois C#), ce qui entraîne non seulement un code complexe, mais également une mauvaise DX (Developer Experience). L'implémentation des services Windows par Go est simple et chaque ligne de code a du sens.
Vous vous demandez peut-être : "Pourquoi ne pas utiliser un langage encore plus simple comme Python ?". La raison est due à la nature interprétée de Python. Go compile dans un fichier binaire unique lié statiquement, ce qui est essentiel au fonctionnement efficace d'un service Windows. Les binaires Go ne nécessitent aucun runtime/interpréteur. Le code Go peut également être compilé de manière croisée.
Bien qu'il s'agisse d'un langage Garbage Collected, Go fournit un support solide pour interagir avec des éléments de bas niveau. Nous pouvons facilement invoquer les API win32 et les appels système généraux en déplacement.
Très bien, assez d’informations. Codons...
Cette procédure pas à pas de code suppose que vous ayez une connaissance de base de la syntaxe Go. Sinon, A Tour of Go serait un bon endroit pour l'apprendre.
PS C:\> go mod init cosmic/my_service
PS C:\> go get golang.org/x/sys
Remarque : ce package contient également la prise en charge du langage Go au niveau du système d'exploitation pour les systèmes d'exploitation basés sur UNIX comme Mac OS et Linux.
Créez un fichier main.go. Le fichier main.go contient la fonction principale, qui sert de point d'entrée pour notre application/service Go.
Pour créer une instance de service, nous devons écrire quelque chose appelé Service Context, qui implémente l'interface Handler de golang.org/x/sys/windows/svc.
Donc, la définition de l'interface ressemble à ceci
type Handler interface { Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) }
La fonction Exécuter sera appelée par le code du package au démarrage du service, et le service se fermera une fois l'exécution terminée.
Nous lisons les demandes de changement de service provenant du canal de réception uniquement r et agissons en conséquence. Nous devons également maintenir notre service à jour en envoyant des signaux aux canaux d'envoi uniquement. Nous pouvons transmettre des arguments facultatifs au paramètre args.
En sortant, nous pouvons revenir avec le exitCode étant 0 en cas d'exécution réussie. Nous pouvons également utiliser svcSpecificEC pour cela.
type myService struct{}
func (m *myService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { // to be filled }
Créez une constante, avec les signaux que notre service peut accepter du 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) }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!