echo server implementation for python
DESCRIPTION
PythonによるEchoサーバのサンプル実装 〜eventletを活用したノンブロッキングI/O勉強メモ〜TRANSCRIPT
PythonによるEchoサーバのサンプル実装
@ttsubo2013.9.23
<勉強メモ>
1
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
#!/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
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
#!/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
ユーザからの処理をブロッキングI/Oで処理する場合には、マルチスレッドでアプリケーションを作成する必要があり、スレッド間の同期の問題などが発生するので、細心の注意が必要になる。
ノンブロッキングI/Oで処理する場合には、1つのスレッドで複数のコネクションを扱うことが可能となる。なお、1つのスレッドで複数のユーザからの処理を制御するスケジューリング処理は、OSではなくアプリケーション側で実施しなければならない。
ブロッキングI/O
ノンブロッキングI/O
今回、Eventletに着目してみた。6
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
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
#!/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
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
#!/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
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
#!/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
ノンブロッリングI/OをEventlet活用してEchoサーバを実装してみた。感覚的には、シングルスレッド的に実装できるところが、Good !!
あと、ネイティブスレッドと異なり、グリーンスレッドは、ユーザ空間で動作するので、グリーンスレッド切り替え処理をプログラマ側で制御できるのが利点らしい。
まだまだ、API活用事例が少ないため、英語ドキュメントの読解力が必要みたい。
14