猫型エンジニアのブログ

プログラム/ネットワーク系の技術関連をまとめたページです 

TCPServerの仕組み

 具体的にサーバを動作させる場合は、BaseServerを継承したサーバを用います。ここではTCPServerを使ってみます。

TCPServerの中身

 ここを参照すると、以下の例のようにBaseServerでインタフェースのみであった部分を継承して、tcp用のソケットプログラミングの実装がなされています。

 def server_activate(self):
        """Called by constructor to activate the server.
        May be overridden.
        """
        self.socket.listen(self.request_queue_size)

 具体的にどのメソッドが何をやっているかは以下の通りです。なおソケットの対してのread/writeに関する操作は、ユーザ定義のハンドラーで実装します。

  • bind -> server_bind
  • listen -> server_activate
  • accept ->get_request
  • close -> shutdown_request

TCPServerの使い方

 以下がサンプルコードです。クライアントが入力したデータをそのままサーバ上に表示するだけです。

import SocketServer

class MyTCPHandler(SocketServer.BaseRequestHandler):

    def handle(self):
        self.data = self.request.recv(1024).strip()
        print "%s wrote:" % self.client_address[0]
        print self.data

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)

    server.serve_forever()

 クライアント側はtelnetでサーバに接続します。以前はクライアント側のコードも書いていましたが、実はtelnetでも通信できます(当然か)。

# telnet localhost 9999
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
test
TESTConnection closed by foreign host.

 サーバ側で確かに動作しているのを確認できました。なぜかリモートホストからだと失敗します(笑)。

# python tcpserver.py 
127.0.0.1 wrote:
test

StreamRequestHandlerの中身

 今回は用いませんでしたが、ハンドラーのクラスとして便利なStreamRequestHandlerがあります。StreamRequestHandlerは、以下のようにBaseRequestHandlerにrwileおよびwfileを実装したクラスです(コメントは削除して実装のみ掲載してます)。

class StreamRequestHandler(BaseRequestHandler):

    rbufsize = -1
    wbufsize = 0

    timeout = None

    disable_nagle_algorithm = False    
 
  def setup(self):
        self.connection = self.request
        if self.timeout is not None:
            self.connection.settimeout(self.timeout)
        if self.disable_nagle_algorithm:
            self.connection.setsockopt(socket.IPPROTO_TCP,
                                       socket.TCP_NODELAY, True)
        self.rfile = self.connection.makefile('rb', self.rbufsize)
        self.wfile = self.connection.makefile('wb', self.wbufsize)

    def finish(self):
        if not self.wfile.closed:
            try:
                self.wfile.flush()
            except socket.error:
                # An final socket error may have occurred here, such as
                # the local error ECONNABORTED.
                pass
        self.wfile.close()
        self.rfile.close()

 実際に読み書きするパケットのデータ部分は、ソケットオブジェクトmakefileメソッドを呼び出してファイルオブジェクトを用いて処理しているところに注目です。ファイルオブジェクトとしてプロトコルを扱える場合はとても便利です。クライアントにレスポンスを返すのはfinish関数中のflushメソッドを利用しています。