Pythonでネットワークプログラミング(5)
TCP/IPプロトコルでのネットワークプログラムをPythonで書くための練習。
今回の検討項目
前回(http://d.hatena.ne.jp/Megumi221/20110412 )までに、クライアント-サーバ型の簡単な計算プログラムを作成した。計算プログラムでは、サーバプログラムは、送られてきた数式をevalして値を送信するだけだったが、今回はもう少しサーバ側の処理が重い場合の実装を考えてみる。
具体的には、サーバプログラムで数十分かかる計算を実行することにする。計算プログラムはあらかじめ別に作成しておき、その実行モジュールを外部プロセスとしてPythonプログラムから起動する。
外部プロセスの起動方法としては以下を参考に。
- 参考サイト
外部プロセス実行プログラム
前回の計算プログラムをベースに、今回の実装を行う。
クライアントプログラムでは、wxPythonで作成されたGUIが起動する。サーバとのコネクションを確立して、サーバプログラムに計算プログラムの実行指示を送信する。サーバプログラムから実行完了の返答があるまでしばらく待つ。実行が完了すると、「Done」と表示される。
クライアントプログラムは以下(cal_client3.py)の通り。
# --- cal_client3.py --- # import wx import time import socket class Client: def __init__(self): HOST = 'localhost' #localhostでテスト実行 PORT = 50007 self.soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.soc.connect((HOST, PORT)) #サーバに接続 class MainFrame(wx.Frame): def __init__(self, parent, id): wx.Frame.__init__(self, parent, id, title="Client", size=(300, 200)) p = wx.Panel(self) self.text = wx.TextCtrl(p, -1, "", size=(200, 30)) btn1 = wx.Button(p, -1, "Run") #「Run」ボタン btn2 = wx.Button(p, -1, "Connect") #「Connect」ボタン btn1.Bind(wx.EVT_BUTTON, self.Run) btn2.Bind(wx.EVT_BUTTON, self.Connect) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.text, 0, wx.ALL, 5) sizer.Add(btn2, 0, wx.ALL, 5) sizer.Add(btn1, 0, wx.ALL, 5) p.SetSizer(sizer) def Run(self, event): self.data = '1' self.c.soc.send(self.data) #'1'をサーバへ送信 self.answ = self.c.soc.recv(32) #サーバから受信(0は正常終了) if self.answ == '0': self.text.SetValue('Done') #正常終了の場合「Done」を表示 self.c.soc.close() #接続を切る def Connect(self, event): self.c = Client() class MainApp(wx.App): def OnInit(self): self.frame = MainFrame(None, -1) self.frame.Show(True) self.SetTopWindow(self.frame) return True if __name__ == '__main__': app = MainApp(0) app.MainLoop()
サーバプログラムでは、クライアントから'1'を受信したら、あらかじめ用意しておいたプログラムa.outを外部プロセスとして実行する。
サーバプログラムは以下(cal_server3.py)の通り。
# --- cal_server3.py --- # import SocketServer import socket import os import subprocess class MyTCPHandler(SocketServer.BaseRequestHandler): def __init__(self, request, client_address, server): #いらない SocketServer.BaseRequestHandler.__init__(self, request, client_address, server) return def setup(self): print "Connected by", self.client_address return SocketServer.BaseRequestHandler.setup(self) def handle(self): data = self.request.recv(1) if data: fd = open('log', 'w') #a.outが標準出力に書きだす内容を保存するファイル args = ['a.out'] subproc_args = { 'stdin': subprocess.PIPE, 'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT, 'close_fds' : True } #WIndowsではclose_fdsは使えない p = subprocess.Popen(args, **subproc_args) stdout = p.stdout while 1: line = stdout.readline() #標準出力に書き出される内容の読み込み if not line: break fd.write(line) #ファイルに保存しておく ret = p.wait() #プロセスが終了したときの返り値(正常終了なら0) fd.close() self.request.send(str(ret)) self.request.close() self.server.socket.close() os._exit(1) #サーバプログラムの終了 return def finish(self): #いらない return SocketServer.BaseRequestHandler.finish(self) if __name__ == '__main__': host, port = '', 50007 server = SocketServer.TCPServer((host, port), MyTCPHandler) server.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #ソケットのオプション server.serve_forever()
プログラムの実行
実行は以下のように行う。
- ターミナル画面を二つ開いて、一方でcal_server3.pyを実行すると、サーバプログラムが走る。
- もう一つのターミナル画面で、クライアントのGUIを起動する。
- GUI画面で「Connect」ボタンを押し、サーバと接続する。
- 続けて「Run」ボタンを押すとサーバ側でa.outが実行される。
- 数分後に、a.outが終了してGUI画面に「Done」と表示され実行が完了したことが分かる。
▲クライアントプログラムのGUI画面
▲「Run」ボタンを押した後の状態。サーバからの受信を待つ間、「Run」ボタンが押されたままなのだが...。計算完了までこのまま。再び押せる状態に戻ってほしい。そうするためにここはthreadで実行するべきか。
▲サーバ側の計算が完了したときの画面。「Done」と表示されている。