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

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

今回の検討項目

前回(http://d.hatena.ne.jp/Megumi221/20110414 )までに、外部プロセスとして計算プログラムを実行するクライアント-サーバ型のプログラムを作成した。そのとき、サーバプログラムで数十分かかる計算を実行し、計算が終了した時点でクライアント側にシグナルを送信した。
今回は、サーバプログラムが実行する計算プログラムが実行途中で標準出力に吐き出すログを、クライアントプログラムのGUI上に計算実行と並行して表示してみる。つまり、サーバプログラムからログのデータを、逐次クライアントプログラムへ送信する。

プログラムの実装

前回のクラインアント-サーバプログラム(cal_server3.py, cal_client3.py)をベースにしたプログラムとして作成する。計算の過程を標準出力へ書きだす計算モジュールa.outを事前に作成し用意しておく。そのa.outをサーバプログラムが実行する。
プログラムの内容は前回とあまり変わっていないので、変更点を中心にコメントを入れている。クライアントプログラム(cal_client4.py)は以下の通り。

# --- cal_client4.py ---
#
import wx
import time
import socket

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 MainFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, title="Client",
                          size=(650, 300))
        p = wx.Panel(self)
        self.text  = wx.TextCtrl(p, -1, "", size=(600, 200),
                     style=wx.TE_MULTILINE|wx.VSCROLL|wx.TE_READONLY)
        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.data = '1'
        self.c.soc.send(self.data)
        while 1:
            wx.Yield()
            self.answ = self.c.soc.recv(128)  #出力結果を受信
            if self.answ != '':
                self.text.AppendText(self.answ) #TextCtrl部へ表示する
            if self.answ == '0':  #計算終了の場合
                break
        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_server4.py)は以下の通り。

# --- cal_server4.py ---
#
import os
import subprocess
import socket
import SocketServer

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')
            args = ['a.out']
            subproc_args = { 'stdin': subprocess.PIPE,
                             'stdout': subprocess.PIPE,
                             'stderr': subprocess.STDOUT,
                             'close_fds' : True }  #Windowsで実行する場合はこれを削除
            p = subprocess.Popen(args, **subproc_args)
            stdout = p.stdout
            while 1:
                line = stdout.readline()  #a.out実行時の出力を読みとり
                if not line:
                    break      #出力がなくなったら終了
                fd.write(line)    #ファイルにも出力しておく
                fd.flush()
                self.request.send(line) #クライアントへ送信
            ret = p.wait()
            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_server4.pyを実行するとサーバプログラムが走る。
  • もう一つのターミナル画面で、cal_client4.pyを実行しGUIを起動する。
  • GUI画面で「Connect」ボタンを押し、サーバと接続する。
  • 続けてGUI画面の「Run」ボタンを押すとサーバ側でa.outが実行される。
  • a.outから標準出力への出力内容が、逐次GUI画面に表示される。
  • 数分後に、a.outが終了して、サーバプログラムが終了する。


▲クライアント側の起動画面。Windowsで実行した。

▲サーバ側での計算プログラム終了時の画面。プログラムからの出力をTextCtrlに表示している(画面には意図的にぼかしを入れている)。
外部プログラムa.outを実行中は、前回同様、フリーズする傾向がある。これをなんとかするのが次の課題。