Hello Devs, Sudah lama saya tidak menulis sesuatu windows-ish. Jadi, hari ini saya ingin membimbing anda tentang cara menulis Aplikasi Perkhidmatan Windows dalam Go. Ya, anda mendengarnya dengan betul, itu bahasa pergi. Dalam blog tutorial ini, kami akan membincangkan beberapa perkara asas tentang Aplikasi Perkhidmatan Windows dan pada bahagian kemudiannya, saya akan membimbing anda melalui laluan kod mudah di mana kami menulis kod untuk perkhidmatan Windows yang log beberapa maklumat ke fail. Tanpa berlengah lagi, Jom mulakan...!
Aplikasi Perkhidmatan Windows a.k.a. Perkhidmatan Windows ialah aplikasi kecil yang berjalan di latar belakang. Tidak seperti aplikasi windows biasa, mereka tidak mempunyai GUI atau sebarang bentuk antara muka pengguna. Aplikasi perkhidmatan ini mula dijalankan apabila komputer but. Ia berjalan tanpa mengira akaun pengguna yang mana ia dijalankan. Kitaran hayat (mula, berhenti, jeda, teruskan dll.) dikawal oleh program yang dipanggil Pengurus Kawalan Perkhidmatan (SCM).
Jadi, daripada ini, kami dapat memahami bahawa kami harus menulis Perkhidmatan Windows kami sedemikian rupa sehingga SCM harus berinteraksi dengan Perkhidmatan Windows kami dan mengurus kitaran hayatnya.
Terdapat beberapa faktor yang anda boleh pertimbangkan Go untuk menulis Perkhidmatan Windows.
Model konkurensi Go membolehkan pemprosesan yang lebih pantas dan cekap sumber. Go's goroutines membolehkan kami menulis aplikasi yang boleh melakukan pelbagai tugas tanpa sebarang sekatan atau kebuntuan.
Secara tradisinya, Perkhidmatan Windows ditulis menggunakan sama ada C++ atau C (kadangkala C#) yang bukan sahaja menghasilkan kod yang kompleks, tetapi juga DX (Pengalaman Pembangun) yang lemah. Pelaksanaan Perkhidmatan Windows Go adalah mudah dan setiap baris kod masuk akal.
Anda mungkin bertanya, "Mengapa tidak menggunakan bahasa yang lebih mudah seperti python?". Alasannya adalah kerana sifat Python yang ditafsirkan. Go menyusun ke binari fail tunggal yang dipautkan secara statik yang penting untuk Perkhidmatan Windows berfungsi dengan cekap. Perduaan Go tidak memerlukan sebarang masa jalan / penterjemah. Kod Go juga boleh disusun silang.
Walaupun sebagai bahasa yang Dikumpul Sampah, Go menyediakan sokongan padu untuk berinteraksi dengan elemen tahap rendah. Kami boleh menggunakan API win32 dan syscall am dengan mudah.
Baiklah, maklumat yang mencukupi. Jom kod...
Laluan kod ini mengandaikan bahawa anda mempunyai pengetahuan asas tentang sintaks Go. Jika tidak, A Tour of Go adalah tempat yang bagus untuk mempelajarinya.
PS C:\> go mod init cosmic/my_service
PS C:\> go get golang.org/x/sys
Nota: Pakej ini juga mengandungi sokongan bahasa tahap OS untuk OS berasaskan UNIX seperti Mac OS dan Linux.
Buat fail main.go. Fail main.go mengandungi fungsi utama, yang bertindak sebagai titik masuk untuk aplikasi/perkhidmatan Go kami.
Untuk mencipta contoh perkhidmatan, kita perlu menulis sesuatu yang dipanggil Konteks Perkhidmatan, yang melaksanakan antara muka Pengendali daripada golang.org/x/sys/windows/svc.
Jadi, definisi antara muka kelihatan seperti ini
type Handler interface { Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) }
Fungsi Laksanakan akan dipanggil oleh kod pakej pada permulaan perkhidmatan dan perkhidmatan akan keluar sebaik sahaja Laksana selesai.
Kami membaca permintaan pertukaran perkhidmatan daripada saluran r terima sahaja dan bertindak sewajarnya. Kami juga harus memastikan perkhidmatan kami dikemas kini dengan menghantar isyarat ke saluran hantar sahaja. Kita boleh menghantar argumen pilihan kepada parameter args.
Apabila keluar, kita boleh kembali dengan exitCode adalah 0 pada pelaksanaan yang berjaya. Kami juga boleh menggunakan svcSpecificEC untuk itu.
type myService struct{}
func (m *myService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { // to be filled }
Buat pemalar, dengan isyarat yang perkhidmatan kami boleh terima daripada 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) }
Atas ialah kandungan terperinci Menulis Perkhidmatan Windows dalam Go. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!