開発者の皆さん、久しぶりに Windows っぽいものを書きました。そこで、今日は Go で Windows サービス アプリケーションを作成する方法について説明したいと思います。はい、お聞きのとおり、それは Go 言語です。このチュートリアル ブログでは、Windows サービス アプリケーションに関するいくつかの基本事項を説明し、後半では、情報をファイルに記録する Windows サービスのコードを記述する簡単なコード ウォークスルーを説明します。早速、始めましょう...!
Windows サービス アプリケーション (別名 Windows サービス) は、バックグラウンドで実行される小さなアプリケーションです。通常の Windows アプリケーションとは異なり、GUI やユーザー インターフェイスはありません。これらのサービス アプリケーションは、コンピュータの起動時に実行を開始します。どのユーザー アカウントで実行されているかに関係なく実行されます。そのライフサイクル (開始、停止、一時停止、続行など) は、サービス コントロール マネージャー (SCM) と呼ばれるプログラムによって制御されます。
このことから、SCM が Windows サービスと対話し、そのライフサイクルを管理するような方法で Windows サービスを作成する必要があることがわかります。
Windows サービスの作成に Go を検討する要因はいくつかあります。
Go の同時実行モデルにより、より高速でリソース効率の高い処理が可能になります。 Go のゴルーチンを使用すると、ブロックやデッドロックを発生させずにマルチタスクを実行できるアプリケーションを作成できます。
従来、Windows サービスは C++ または C (場合によっては C#) を使用して記述されており、コードが複雑になるだけでなく、DX (開発者エクスペリエンス) も低下します。 Go の Windows サービスの実装は簡単で、コードの各行は意味があります。
「Python のようなもっと単純な言語を使用したらどうだろうか?」と疑問に思われるかもしれません。その理由は、Python の解釈される性質によるものです。 Go は、Windows サービスが効率的に機能するために不可欠な、静的にリンクされた単一ファイル バイナリにコンパイルします。 Go バイナリにはランタイムやインタープリターは必要ありません。 Go コードはクロスコンパイルすることもできます。
Go はガベージ コレクション言語ではありますが、低レベルの要素と対話するための確実なサポートを提供します。 go では win32 API と一般的なシステムコールを簡単に呼び出すことができます。
はい、情報は十分です。コードを書いてみましょう...
このコードのチュートリアルは、Go 構文に関する基本的な知識があることを前提としています。そうでない場合は、A Tour of Go がそれを学ぶのに最適な場所です。
PS C:\> go mod init cosmic/my_service
PS C:\> go get golang.org/x/sys
注: このパッケージには、Mac OS や Linux などの UNIX ベースの OS に対する OS レベルの Go 言語サポートも含まれています。
main.go ファイルを作成します。 main.go ファイルには、Go アプリケーション/サービスのエントリポイントとして機能する main 関数が含まれています。
サービス インスタンスを作成するには、サービス コンテキスト と呼ばれるものを記述する必要があります。これは golang.org/x/sys/windows/svc のハンドラー インターフェイスを実装します。
インターフェイス定義は次のようになります
type Handler interface { Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) }
実行関数はサービスの開始時にパッケージ コードによって呼び出され、実行が完了するとサービスは終了します。
受信専用チャネル r からサービス変更リクエストを読み取り、それに応じて動作します。また、送信専用チャネルに信号を送信してサービスを更新し続ける必要もあります。オプションの引数を args パラメータに渡すことができます。
終了時に、実行が成功すると exitCode が 0 になって戻ることができます。そのために svcSpecificEC を使用することもできます。
type myService struct{}
func (m *myService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { // to be filled }
サービスが 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) }
以上がGo で Windows サービスを作成するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。