Pythonでネットワークプログラミング(7)

TCP/IPプロトコルでのネットワークプログラムをPythonで書くための練習。

今回の検討項目

前々回(http://d.hatena.ne.jp/Megumi221/20110414)に作成したクライアント-サーバ型の簡単な計算プログラムでは、サーバ側で長時間かかる計算を外部プロセスとして実行した。クライアント側のGUI画面(wxPythonで作成)から、計算開始を指示し、計算が終了した時点でシグナルを受け取り、計算完了の表示をGUI画面に行う。この一連の処理を行うことはできたが、サーバで計算プログラムを実行する間、クライアントのGUI画面がフリーズする問題が生じた。
クライアントプログラムの通信部分を別スレッドで実行することで、この問題を解決する。

  • 参考サイト

wxPyWiki LongRunningTasks

クライアントプログラムの修正

修正は、クライアントプログラム(ca\client3.py)だけでよい。サーバプログラムに変更はない。
修正前のコード(cal_client3.py)は以下の通り。これで実行すると、「Run」ボタンを押してから計算プログラムの実行が完了するまで、サーバからの受信待ちになり、クライアントのGUI画面が固まる。

#--- 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()

修正したコード(cal_client3mod.py)は以下の通り。

# --- cal_client3mod.py ---
#
import wx
import time
import socket
from threading import *   #threadingモジュールを使う

class Client:
    def __init__(self):
        HOST = 'localhost'
        PORT = 50007
        self.soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.soc.connect((HOST, PORT))

class WorkerThread(Thread):
    def __init__(self, notify_window, conn):
      Thread.__init__(self)
      self._notify_window = notify_window
      self.conn = conn
      self.start()   #スレッドの実行開始

    def run(self):   #この部分を別スレッドで実行
        self.data = '1'
        self.conn.soc.send(self.data)
        self.answ = self.conn.soc.recv(32)
        self._notify_window.text.SetValue('Done')
        self.conn.soc.close()

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")
        btn2 = wx.Button(p, -1, "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.worker = WorkerThread(self, self.c) #

    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()

実行方法は前々回と同じ。これで問題は解決する。
wx.Yieldを使うよりもこちらの方が確実な印象を受ける。別の場合にも、wx.Yieldで不安定だったのがthredadingにすることで解決できた。