2016-04-15

Pythonでselect, poll, epoll, kqueue試してみた

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

このエントリーをはてなブックマークに追加