Pythonでネットワークプログラミング(4)
TCP/IPプロトコルでのネットワークプログラムをPythonで書くための練習。
今回の検討項目
前回(http://d.hatena.ne.jp/Megumi221/20110409 )はwxPythonでネットワークプログラムをラップした計算プログラムを作成したが、細かい動作の仕様もできるだけ決めたうえで、書き直すことにする。動作が曖昧だと実装も曖昧になってしまい、他の課題に応用する場合に問題点が発現することが往々にしてあるため。
今回は、前回の計算プログラムをSocketServerを使って修正することにする。
- 参考サイト
計算プログラム(オリジナル)
GUIから使える、簡単な計算プログラムを書いた。コードはサーバプログラムとクライアントプログラムに分かれている。2つのプログラムは別々に実行する。クライアントプログラムを実行するとGUI画面が起動する。
プログラムの動作は以下の通り。
- サーバプログラムを実行するだけでは何も起こらない。サーバ側にはGUI画面はない。
- クライアントのGUI画面上で、「Connect」ボタンを押すと、サーバと接続される。どこから接続されたかが、サーバ側のターミナル画面に表示される。
- GUI画面上で任意の数式を入力し、「Calculate」ボタンを押すと計算結果が入力欄に表示される。
- 数式の計算は続けて何度も実行することができる。
- 「Disconnect」ボタンを押すと、サーバとの接続が切断されサーバプログラムが終了する。
- クライアントプログラムの終了は、GUI画面を閉じることで行う。
- クライアントのGUIを起動したままで、再びサーバプログラムを実行すれば「Connect」ボタンを押すことで再び計算可能となる。
これらがちゃんと動作するよう、前回のコードを少し書き直すと以下のようになる。
まず、サーバプログラム(cal_server.py)。
# --- cal_server.py --- # import socket import time HOST = 'localhost' PORT = 50007 #接続待ちから接続まで s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(1) soc, addr = s.accept() print 'Connected by', addr, time.ctime(time.time()) #データの受信、計算、送信の繰り返し while 1: data = soc.recv(1024) try: ans = str(eval(data)) soc.send(ans) except: #計算ができなくなったら終了 break soc.close()
続いてクライアントプログラム(cal_client.py)。
# --- cal_client.py --- # import wx import time import socket class Client: def __init__(self): HOST = 'localhost' PORT = 50007 self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.s.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, "Calculate") btn2 = wx.Button(p, -1, "Connect") btn3 = wx.Button(p, -1, "Disconnect") btn1.Bind(wx.EVT_BUTTON, self.Send) btn2.Bind(wx.EVT_BUTTON, self.Connect) btn3.Bind(wx.EVT_BUTTON, self.Disconnect) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.text, 0, wx.ALL, 5) sizer.Add(btn1, 0, wx.ALL, 5) sizer.Add(btn2, 0, wx.ALL, 5) sizer.Add(btn3, 0, wx.ALL, 5) p.SetSizer(sizer) self.working = 0 def Send(self, event): self.data = self.text.GetValue() def Connect(self, event): self.data = None self.answ = None if not self.working: self.working = 1 self.need_abort = 0 self.c = Client() while 1: wx.Yield() #これがないとフリーズする if self.need_abort: break if self.data != None: self.c.s.send(self.data) self.data = None self.answ = self.c.s.recv(1024) if self.answ != None: self.text.SetValue(self.answ) self.answ = None self.working = 0 def Disconnect(self, event): if self.working: #接続を切断する self.c.s.close() self.need_abort = 1 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()
前回と大きく異なるところは、終了処理のところ。前回は仕様が曖昧でエラーで終了していた。
サーバプログラムの修正
SocketServerモジュールを使って、サーバプログラムを修正する。
「Disconnect」したとき、どのようにサーバプログラムを終了するかがここでのポイント。serve_forever()で無限ループに入るが、shutdown()を呼べばループを抜けられるらしい。しかし、python2.6からのサポートになるようで、python2.5では使えないようだ。
そこで、os._exit(1)で無理矢理終了させてみた。
# --- cal_server2.py --- # import SocketServer import os 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): while 1: data = self.request.recv(1024) try: ans = str(eval(data)) self.request.send(ans) except: break # 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.serve_forever()
LinuxのPython2.5では動作確認できた。Windows(Python2.6)でサーバプログラムを実行しようとすると、以下のエラー。
Traceback (most recent call last): File "D:\share\cal_server2.py", line 36, in <module> server = SocketServer.TCPServer((host, port), MyTCPHandler) File "C:\Python26\lib\SocketServer.py", line 400, in __init__ self.server_bind() File "C:\Python26\lib\SocketServer.py", line 411, in server_bind self.socket.bind(self.server_address) File "<string>", line 1, in bind error: [Errno 10048] 通常、各ソケット アドレスに対してプロトコル、 ネットワーク アドレス、またはポートのどれか 1 つのみを使用できます。
この場合は、ポート番号がすでに使われているので、別の番号に変えて試してみたらうまくいった。