Maison > développement back-end > Golang > le corps du texte

Écrire un service Windows en Go

PHPz
Libérer: 2024-08-28 06:40:10
original
1105 Les gens l'ont consulté

Writing a Windows Service in Go

Table des matières

  • Présentation
  • Qu'est-ce qu'un « service » Windows exactement ?
  • Pourquoi Golang ?
  • Écrire un service Windows dans Go
  • Installation et démarrage du service
  • Conclusion
  • Code complet

Introduction

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

Qu'est-ce qu'un « service » Windows exactement ?

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.

Pourquoi Golang?

Il existe plusieurs facteurs pour lesquels vous pouvez envisager Go pour écrire des services Windows.

Concurrence

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.

Simplicité

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.

Binaires statiques

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.

Accès de bas niveau

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

Écrire un service Windows dans Go

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.

  • Tout d’abord, nommons notre projet. Je nommerai les mines comme cosmic/my_service. Créez un fichier go.mod,
PS C:\> go mod init cosmic/my_service
Copier après la connexion
  • Nous devons maintenant installer le package golang.org/x/sys. Ce package fournit la prise en charge du langage Go pour les applications liées au système d'exploitation Windows.
PS C:\> go get golang.org/x/sys
Copier après la connexion

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)
}
Copier après la connexion

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.

  • Maintenant, créez un type nommé myService qui fera office de contexte de service.
type myService struct{}
Copier après la connexion
  • Après avoir créé le type myService, ajoutez-y Execute mentionné ci-dessus comme méthode afin qu'il implémente l'interface Handler.
func (m *myService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) {
    // to be filled
}
Copier après la connexion
  • Maintenant que nous avons implémenté avec succès l'interface Handler, nous pouvons maintenant commencer à écrire la logique réelle.

Créez une constante, avec les signaux que notre service peut accepter du SCM.

const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
Copier après la connexion

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)
Copier après la connexion

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}
Copier après la connexion

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)
            }
        }
    }
Copier après la connexion

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
Copier après la connexion
  • 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.")
        }
    }
}
Copier après la connexion
  • 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
}
Copier après la connexion

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"
Copier après la connexion

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 powershell as Administrator,

PS C:\> sc.exe create MyService <path to your service_app.exe>
Copier après la connexion

To start our service, run the following command,

PS C:\> sc.exe start MyService
Copier après la connexion

To delete our service, run the following command,

PS C:\> sc.exe delete MyService
Copier après la connexion

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)
}
Copier après la connexion

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!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!