I/O多重化のシステムコールのselect, poll, epoll, kqueueをPythonで試してみましたー。Pythonは2.7系を利用しました。
クライアント側の接続テスト
サーバ側はUnix Domain Socketで待ち受けるサンプルを作ったので、クライアント側はtelnetかsocatで接続します。テストはこんな感じで↓
$ echo "hoge" | socat stdin ./hoge1.sock
select
こんな感じ#!/usr/bin/env python
# -*- coding: utf-8 -*-
import select
import socket
import os
PATH = '/path/to/file/'
SOCKET_FILE_TEMPLATE = 'hoge{0}.sock'
SOCKET_SIZE = 3
sockets = []
for number in range(SOCKET_SIZE):
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.bind(PATH + SOCKET_FILE_TEMPLATE.format((number + 1)))
s.listen(0)
sockets.append(s)
try:
readfds = list(sockets) # hard copy from sockets to read file descriptor list
while True:
rready, wready, xready = select.select(readfds, [], [])
for s in rready:
if s in sockets:
conn, addr = s.accept()
readfds.append(conn)
else:
data = s.recv(1024)
if len(data) == 0:
readfds.remove(s)
s.close()
break
print(data)
finally:
for s in sockets:
s.close()
for number in range(SOCKET_SIZE):
os.remove(PATH + SOCKET_FILE_TEMPLATE.format((number + 1)))
poll, epoll
Mac OS Xだと対応していなかったので、Ubuntuで試しましたー。イベントマスクはかなり適当。#!/usr/bin/env python
# -*- coding: utf-8 -*-
import select
import socket
import os
PATH = '/path/to/file/'
SOCKET_FILE_TEMPLATE = 'hoge{0}.sock'
SOCKET_SIZE = 3
READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR
READ_WRITE = READ_ONLY | select.POLLOUT
sockets = []
fd_to_socket = {}
poller = select.poll()
for number in range(SOCKET_SIZE):
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.bind(PATH + SOCKET_FILE_TEMPLATE.format((number + 1)))
s.listen(0)
sockets.append(s)
fd_to_socket[s.fileno()] = s
poller.register(s, READ_ONLY)
try:
while True:
events = poller.poll()
for fd, event in events:
s = fd_to_socket[fd]
if s in sockets:
conn, addr = s.accept()
fd_to_socket[conn.fileno()] = conn
poller.register(conn, READ_ONLY)
else:
data = s.recv(1024)
if len(data) == 0:
poller.unregister(s)
s.close()
break
print(data)
finally:
for s in sockets:
s.close()
for number in range(SOCKET_SIZE):
os.remove(PATH + SOCKET_FILE_TEMPLATE.format((number + 1)))
poll, epollから返されるのがファイルディスクリプタそのものなので、それをソケットなりファイルオブジェクトなりに変換しないといけないです。
epollの場合は上記コードをselect.poll()をselect.epoll()にして、イベントマスクの定数を変えればOK。
kqueue
Ubuntuだと対応していなかったのでMac OS Xで試しましたー#!/usr/bin/env python
# -*- coding: utf-8 -*-
import select
import socket
import os
PATH = '/path/to/file/'
SOCKET_FILE_TEMPLATE = 'hoge{0}.sock'
SOCKET_SIZE = 3
kevents_socket = []
accept_socket_fds = []
fd_to_socket = {}
kq = select.kqueue()
for number in range(SOCKET_SIZE):
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.bind(PATH + SOCKET_FILE_TEMPLATE.format((number + 1)))
s.listen(0)
fd_to_socket[s.fileno()] = s
accept_socket_fds.append(s.fileno())
kevents_socket.append(select.kevent(
s,
filter=select.KQ_FILTER_READ,
flags=select.KQ_EV_ADD | select.KQ_EV_ENABLE))
kevents = list(kevents_socket)
try:
while True:
revents = kq.control(kevents, 1, None)
for event in revents:
if event.filter & select.KQ_FILTER_READ:
s = fd_to_socket[event.ident]
if event.ident in accept_socket_fds:
conn, addr = s.accept()
fd_to_socket[conn.fileno()] = conn
kevents.append(select.kevent(
conn,
filter=select.KQ_FILTER_READ,
flags=select.KQ_EV_ADD | select.KQ_EV_ENABLE))
else:
data = s.recv(1024)
if len(data) == 0:
s.close()
for kevent in kevents:
if kevent.ident == event.ident:
kevents.remove(kevent)
break
print(data)
finally:
for s in kevents_socket:
s = fd_to_socket[event.ident]
s.close()
for number in range(SOCKET_SIZE):
os.remove(PATH + SOCKET_FILE_TEMPLATE.format((number + 1)))
参考URL
- 16.1. select — I/O 処理の完了を待機する — Python 2.7.x ドキュメント
- select – Wait for I/O Efficiently - Python Module of the Week
- How To Use Linux epoll with Python
- So, how can I use kqueue/kevent on BSD/Mac using Python?
- I/Oを多重化するためのシステムコール(select, poll, epoll, kqueue) - $shibayu36->blog;
- ノンブロッキングI/Oと非同期I/Oの違いを理解する – PAYFORWARD
- Linux Programming、epollの話 - mixi engineer blog