Pythonでネットワークプログラミング(2)
TCP/IPプロトコルでのネットワークプログラミングをPythonで実装することが目標。
今回の検討項目
前回(http://d.hatena.ne.jp/Megumi221/20110310)は、簡単なチャットプログラムを実装してみたが、ここではネットワークプログラムにGUIをラップする方法を検討する。
というのも、実際にコードを書いてみると、GUIのループ(wxPythonではapp.MainLoop()のこと)とネットワークプログラムのwhileループ(データの送受信をするところ)の両立が単純ではない。コードの中にwhileループがあると、GUIが立ち上がらない、もしくは立ちあがってもwhileループに入るとフリーズすることが分かる。
これを避けるためには、通信部分をThreadで実行するか、wx.Yieldを使ってフリーズするのを回避する。
- 参考サイト
wxPyWiki LongRunningTasks
how to make python socket server work with the app.MainLoop() in wxPython?
計算プログラムの実装
GUIから使える、簡単な計算プログラムを書いてみた。クライアントプロセスから数式を送信し、サーバプロセスで数式の計算を実行し結果をクライアントへ送信する。プログラムの内容としては、
- 通信にはコネクション型を用いる。
- ユーザはクライアントプログラムだけを操作する。
- クライアントのGUI画面はwxPythonで実装する。
- クライアント画面上で、サーバプログラムへの接続、数式の入力、結果の表示が行われる。
- サーバプログラムにはGUI画面はない。
- ユーザはサーバプログラムを最初に起動するだけで、以後操作を行わない。
サーバプログラムは以下の通り。
# --- 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) #1つの接続要求を待つ soc, addr = s.accept() #要求がくるまでブロックする print 'Connected by', addr print 'Time :', time.ctime(time.time()) #接続完了 while 1: data = soc.recv(1024) #1024バイトまでのデータを受け取る # if data == "quit": # break ans = str(eval(data)) #dataは数式なので計算を実行する soc.send(ans) #結果を送信する soc.close()
クライアントプログラムは以下の通り。
# --- 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, "End") 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: time.sleep(0.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()
プログラムの実行方法
テストなので、localhostで実行してみる。
- ターミナル画面を2つ開く。
- 一方の画面(サーバ側画面)で、python cal_server.pyを実行する。この時点では、画面には何も表示されない。
- もう一方の画面(クライアント側画面)で、python cal_client.pyを実行する。するとGUI画面が起動する。
- 画面上の「Connect」ボタンを押すと、サーバとの接続が完了する。サーバ側画面には、接続日時がprintされる。
- GUI画面上の入力欄に数式を入力する。この例では、「1+2+3+5」とした。
- 画面上の「Calculate」ボタンを押すと、計算結果である「11」が表示される。
- 別の計算を行うときには、入力欄に数式を再び入力して、「Calculate」ボタンを再び押す。
- 「End」ボタンでサーバとの接続が終了する。
ちゃんと動くが、現状だと、サーバプログラムの終了がうまくできていない。