echo server implementation for python

14
Pythonによる Echoサーバのサンプル実装 @ttsubo 2013.9.23 <勉強メモ> 1

Upload: toshiki-tsuboi

Post on 11-Nov-2014

1.150 views

Category:

Technology


3 download

DESCRIPTION

PythonによるEchoサーバのサンプル実装 〜eventletを活用したノンブロッキングI/O勉強メモ〜

TRANSCRIPT

Page 1: Echo server implementation for Python

PythonによるEchoサーバのサンプル実装

@ttsubo2013.9.23

<勉強メモ>

1

Page 2: Echo server implementation for Python

1. ブロッキングI/OなEchoサーバ

aaaaaa

Echoサーバ

クライアント1

$ telnet 127.0.0.1 4000Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.aaa[Mon Sep 23 14:37:28 2013] aaaaaaaa[Mon Sep 23 14:37:31 2013] aaaaaaa[Mon Sep 23 14:37:32 2013] aa

クライアント2

aaa $ telnet 127.0.0.1 4000Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.aaa

シングルスレッド

待ち処理となる

シングルスレッドでEchoサーバを実装すると、複数クライアントからのアクセスに対応できなかった

$ python tcp_server.py waiting for connection......connected from: ('127.0.0.1', 52201)

2

Page 3: Echo server implementation for Python

#!/usr/bin/env python

from socket import *from time import ctime

HOST = '127.0.0.1' # localhostPORT = 4000 # choose a random port numberBUFSIZ = 1024 # set buffer size to 1KADDR = (HOST, PORT)

def Server(tcpCliSock): while True: data = tcpCliSock.recv(BUFSIZ) if not data: break tcpCliSock.send('[%s] %s' % (ctime(), data))

tcpCliSock.close()

def start_tcp_server(): tcpSerSock = socket(AF_INET, SOCK_STREAM) tcpSerSock.bind(ADDR) tcpSerSock.listen(5) print 'waiting for connection...' while True: tcpCliSock, addr = tcpSerSock.accept() print '...connected from:', addr Server(tcpCliSock)

if __name__ == '__main__': start_tcp_server()

3

Page 4: Echo server implementation for Python

2. ブロッキングI/OなEchoサーバ

aaaaaa

Echoサーバ

クライアント1

$ telnet 127.0.0.1 4000Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.aaa[Mon Sep 23 15:09:10 2013] aaaaaaaa[Mon Sep 23 15:09:14 2013] aaaaaaa[Mon Sep 23 15:12:07 2013] aa

クライアント2

aaa $ telnet 127.0.0.1 4000Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.aaa[Mon Sep 23 15:09:38 2013] aaa

マルチスレッドでEchoサーバを実装すると、複数クライアントからのアクセスに対応できた

$ python tcp_server_threading.py waiting for connection......connected from: ('127.0.0.1', 52363)...connected from: ('127.0.0.1', 52370)

aaa

マルチスレッド

4

Page 5: Echo server implementation for Python

#!/usr/bin/env python

import threadingfrom socket import *from time import ctime

HOST = '127.0.0.1' # localhostPORT = 4000 # choose a random port numberBUFSIZ = 1024 # set buffer size to 1KADDR = (HOST, PORT)

def Server(tcpCliSock): while True: data = tcpCliSock.recv(BUFSIZ) if not data: break tcpCliSock.send('[%s] %s' % (ctime(), data))

tcpCliSock.close()

def start_tcp_server(): tcpSerSock = socket(AF_INET, SOCK_STREAM) tcpSerSock.bind(ADDR) tcpSerSock.listen(5) print 'waiting for connection...' while True: tcpCliSock, addr = tcpSerSock.accept() print '...connected from:', addr threading.Thread(target=Server, args=(tcpCliSock,)).start()

if __name__ == '__main__': start_tcp_server()

5

Page 6: Echo server implementation for Python

ユーザからの処理をブロッキングI/Oで処理する場合には、マルチスレッドでアプリケーションを作成する必要があり、スレッド間の同期の問題などが発生するので、細心の注意が必要になる。

ノンブロッキングI/Oで処理する場合には、1つのスレッドで複数のコネクションを扱うことが可能となる。なお、1つのスレッドで複数のユーザからの処理を制御するスケジューリング処理は、OSではなくアプリケーション側で実施しなければならない。

ブロッキングI/O

ノンブロッキングI/O

今回、Eventletに着目してみた。6

Page 7: Echo server implementation for Python

eventletとは

Eventlet is built around the concept of green threads (i.e. coroutines, we use the terms interchangeably) that are launched to do network-related work. Green threads differ from normal threads in two main ways:

• Green threads are so cheap they are nearly free. You do not have to conserve green threads like you would normal threads. In general, there will be at least one green thread per network connection.

• Green threads cooperatively yield to each other instead of preemptively being scheduled. The major advantage from this behavior is that shared data structures don’t need locks, because only if a yield is explicitly called can another green thread have access to the data structure. It is also possible to inspect primitives such as queues to see if they have any pending data.

http://eventlet.net/doc/basic_usage.html

7

Page 8: Echo server implementation for Python

3. ノンブロッキングI/OなEchoサーバ

aaaaaa

Echoサーバ

クライアント1

$ telnet 127.0.0.1 4000Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.aaa[Mon Sep 23 15:55:56 2013] aaaaaaaa[Mon Sep 23 15:55:59 2013] aaaaaaa[Mon Sep 23 15:56:13 2013] aa

クライアント2

aaa $ telnet 127.0.0.1 4000Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.aaa[Mon Sep 23 15:56:10 2013] aaa

シングルスレッド

まず、Eventletのmonkey_patchで、対応してみた。

$ python tcp_server_threading_monkeypatch.py waiting for connection......connected from: ('127.0.0.1', 52584)...connected from: ('127.0.0.1', 52585)

aaa

8

Page 9: Echo server implementation for Python

#!/usr/bin/env python

import eventlet; eventlet.monkey_patch()import threadingfrom socket import *from time import ctime

HOST = '127.0.0.1' # localhostPORT = 4000 # choose a random port numberBUFSIZ = 1024 # set buffer size to 1KADDR = (HOST, PORT)

def Server(tcpCliSock): while True: data = tcpCliSock.recv(BUFSIZ) if not data: break tcpCliSock.send('[%s] %s' % (ctime(), data))

tcpCliSock.close()

def start_tcp_server(): tcpSerSock = socket(AF_INET, SOCK_STREAM) tcpSerSock.bind(ADDR) tcpSerSock.listen(5) print 'waiting for connection...' while True: tcpCliSock, addr = tcpSerSock.accept() print '...connected from:', addr threading.Thread(target=Server, args=(tcpCliSock,)).start()

if __name__ == '__main__': start_tcp_server()

ブロッキングI/Oでのマルチスレッドプログラミングと、ほぼ同一のまま、ノンブロッキングI/O処理が実現可能

9

Page 10: Echo server implementation for Python

4. ノンブロッキングI/OなEchoサーバ

aaaaaa

Echoサーバ

クライアント1

$ telnet 127.0.0.1 4000Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.aaa[Mon Sep 23 15:45:12 2013] aaaaaaaa[Mon Sep 23 15:45:15 2013] aaaaaaa[Mon Sep 23 15:45:38 2013] aa

クライアント2

aaa $ telnet 127.0.0.1 4000Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.aaa[Mon Sep 23 15:45:26 2013] aaa

シングルスレッド

$ python tcp_server_eventlet.py waiting for connection......connected from: ('127.0.0.1', 52524)...connected from: ('127.0.0.1', 52525)

aaa

次に、Eventletの各種APIを活用して、対応してみた。

10

Page 11: Echo server implementation for Python

#!/usr/bin/env python

import eventletfrom time import ctime

HOST = '127.0.0.1' # localhostPORT = 4000 # choose a random port numberBUFSIZ = 1024 # set buffer size to 1KADDR = (HOST, PORT)

def handler(tcpCliSock, ADDR): print '...connected from:', ADDR while True: data = tcpCliSock.recv(BUFSIZ) if not data: break tcpCliSock.send('[%s] %s' % (ctime(), data))

tcpCliSock.close()

def start_tcp_server(): print 'waiting for connection...' server = eventlet.listen(ADDR) eventlet.serve(server, handler)

if __name__ == '__main__': start_tcp_server()

11

Page 12: Echo server implementation for Python

5. ノンブロッキングI/OなEchoサーバ

aaaaaa

Echoサーバ

クライアント1

$ telnet 127.0.0.1 4000Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.aaa[Mon Sep 23 16:14:38 2013] aaaaaaaa[Mon Sep 23 16:14:40 2013] aaaaaaa[Mon Sep 23 16:14:57 2013] aa

クライアント2

aaa $ telnet 127.0.0.1 4000Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.aaa[Mon Sep 23 16:14:53 2013] aaa

シングルスレッド

$ python tcp_server_eventlet_queue.py waiting for connection......connected from: ('127.0.0.1', 52622)...connected from: ('127.0.0.1', 52623)

aaa

最後に、Eventletのqueue活用して、対応してみた。

queue

queue

put

get

put

get

12

Page 13: Echo server implementation for Python

#!/usr/bin/env python

import eventletfrom time import ctime

HOST = '127.0.0.1' # localhostPORT = 4000 # choose a random port numberBUFSIZ = 1024 # set buffer size to 1KADDR = (HOST, PORT)

def _recv_loop(queue, sock): while True: data = sock.recv(4096) if len(data) == 0: break queue.put(data)

def _send_loop(queue, sock): while True: data = queue.get() if not data: break sock.sendall('[%s] %s' % (ctime(), data))

def serve(queue, sock): eventlet.spawn(_send_loop, queue, sock) _recv_loop(queue, sock)

def start_tcp_server(): print 'waiting for connection...' server = eventlet.listen(ADDR) while True: sock, addr = server.accept() print '...connected from:', addr tx_queue = eventlet.queue.Queue() eventlet.spawn(serve, tx_queue, sock)

if __name__ == '__main__': start_tcp_server()

13

Page 14: Echo server implementation for Python

ノンブロッリングI/OをEventlet活用してEchoサーバを実装してみた。感覚的には、シングルスレッド的に実装できるところが、Good !!

あと、ネイティブスレッドと異なり、グリーンスレッドは、ユーザ空間で動作するので、グリーンスレッド切り替え処理をプログラマ側で制御できるのが利点らしい。

まだまだ、API活用事例が少ないため、英語ドキュメントの読解力が必要みたい。

14