pythonでのWindowsサービスの書き方
初めに
Pythonのスクリプトを書いていて、常駐化させたい時があります。
*nix系だとpython-daemon 1.5.5 : Python Package Indexを使って実現できるようですが、
Windowsのサービスは探してもあまり情報が見当たらないので腹を据えて調べてみました。
なんだかんだでwin32APIの知識が必要になって割と骨だったのですが、
ある程度動くものができたので、公開します。
[注意] 私はwin32APIの知識が足りません。おかしなところがあればご指摘をお願いします。
参考
- python でWindowsサービス作成
基礎知識
参考によるとpywin32モジュールのwin32serviceutilを使って実現できるようです。
これからお見せする例ではWindowsのイベントハンドル周りの知識が必要ですが、
身構える必要はありません。
サービスを作る分には以下のwin32APIをおさえておけばokです。
- CreateEvent() : イベントハンドルを作成(一般的には非シグナル[フラグオフ状態]で作成)
- SetEvent() : イベントハンドルをシグナル化[フラグオン状態に変更]
- WaitForSingleObject() : イベントをハンドルがシグナル化するまで待つ
なにもしないWindowsサービス
初めに何もしないWindowsサービスを作成してみます。
サービスの開始/停止のみ可能です。
# coding: utf-8 import win32service import win32serviceutil import win32event # win32serviceutil.ServiceFrameworkを継承する class SmallestPythonService(win32serviceutil.ServiceFramework): """ 管理ツール => サービス画面への表示内容 """ # サービス名 _svc_name_ = "SmallestPythonService" # 表示名(サービス画面にはこれを表示) _svc_display_name_ = "Display Service" # サービスの説明 _svc_description_='do nothing speical' def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) # イベントハンドルを非シグナルで作成。 self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) // サービスを停止するときに呼ばれるメソッド def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) # イベントハンドルをシグナル化 win32event.SetEvent(self.hWaitStop) // サービスを開始するときに呼ばれるメソッド def SvcDoRun(self): # SvcStopメソッド内でイベントハンドルがシグナルするまで待機 win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) # 待機関数がWAIT_OBJECT_0などを返した場合非シグナル状態に。 if __name__=='__main__': # コマンドラインから登録する win32serviceutil.HandleCommandLine(SmallestPythonService)
これをserver.pyとして保存し、以下のように実行してサービスとして登録します。
サービスの登録
> python server.py install
実行後はサービス画面から開始/停止などの設定が可能です。
ちなみに削除するときは以下のようにします。
> python server.py remove
他にも機能はいろいろあります
Usage: 'service.py [options] install|update|remove|start [...]|stop|restart [...]|debug [...]' Options for 'install' and 'update' commands only: --username domain\username : The Username the service is to run under --password password : The password for the username --startup [manual|auto|disabled] : How the service starts, default = manual --interactive : Allow the service to interact with the desktop. --perfmonini file: .ini file to use for registering performance monitor data --perfmondll file: .dll file to use when querying the service for performance data, default = perfmondata.dll Options for 'start' and 'stop' commands only: --wait seconds: Wait for the service to actually start or stop. If you specify --wait with the 'stop' option, the service and all dependent services will be stopped, each waiting the specified period.
サービスからのファイル出力
なにもしないのもさみしいので、ファイルへ出力するサービスを書いてみます。
SvcStopメソッド内でイベントハンドルがシグナルするまで待機していたのを、タイムアウト時間を設定し、
処理を行うように変更します。
# coding: utf-8 import win32service import win32serviceutil import win32event import datetime class SmallestPythonService(win32serviceutil.ServiceFramework): _svc_name_ = "SmallestPythonService" _svc_display_name_ = "Display Service" _svc_description_='do nothing speical' # シグナルまでの待ち用タイムアウト時間(今回は10秒 = 10,000ミリ秒) _timeout_Milliseconds = 10000 def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) win32event.SetEvent(self.hWaitStop) def SvcDoRun(self): while 1: # イベントのシグナル化を10秒待つ ret = win32event.WaitForSingleObject( self.hWaitStop, self._timeout_Milliseconds ) # サービス停止(イベントがシグナル化)ならば、処理を中止 if ret == win32event.WAIT_OBJECT_0: break self.DoRun() # 実際のサービスの処理 def DoRun(self): with open("c:\\temp\\test.txt", "a") as f: f.write("[Test] %s \n" % (datetime.datetime.now())) if __name__=='__main__': win32serviceutil.HandleCommandLine(SmallestPythonService)
で、やっと本題
定期的にatndからデータを取ってきて結果をGrowl on Windowsに出力するスクリプトをサービスとして常駐化してみます。
Pythonから Growl on Windowsに投げるのはGrowl for Linuxを使ってみた | hexacosa.netを参考にしました。
# coding: utf-8 import gntp from gntp.notifier import GrowlNotifier import urllib2 import json import win32service import win32serviceutil import win32event import datetime class SmallestPythonService(win32serviceutil.ServiceFramework): _svc_name_ = "SmallestPythonService" _svc_display_name_ = "Display Service" _svc_description_='do nothing speical' # 30分ごとに実行 _timeout_Milliseconds = 1000 * 30 def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) # growlの設定 self.notifier = GrowlNotifier("python script", ["TEST"], hostname="127.0.0.1") #self.notifier.register() # 一回だけ実行 def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) win32event.SetEvent(self.hWaitStop) def SvcDoRun(self): while 1: ret = win32event.WaitForSingleObject( self.hWaitStop, self._timeout_Milliseconds ) if ret == win32event.WAIT_OBJECT_0: break self.DoRun() def DoRun(self): URL = 'http://api.atnd.org/events/?event_id=13124&format=json' r = urllib2.urlopen(URL) jj = json.load(r) info = jj["events"][0] t = info["title"] a = info["accepted"] w = info["waiting"] l = info["limit"] self.notifier.notify("TEST", "atnd info", "%s \n %s/%s (wait:%s)" % (t,a, l, w)) if __name__=='__main__': win32serviceutil.HandleCommandLine(SmallestPythonService)
お疲れ様でした。
注意
今回はpywin32をインストーラーで入れたためか、buildoutでつくった環境のeggファイルが読み込み
できませんでした。サービス化するにはeasy_installするしかないのかも。