Документация Python неофициальный перевод
Содержание страницы

HOWTO по файловому дескриптору таймераtimer file descriptor HOWTO

Версия:

1.13

В этом HOWTO рассматривается поддержка в Python файлового дескриптора таймера Linux.

ПримерыExamples

В следующем примере показано, как использовать файловый дескриптор таймера для выполнения функции два раза в секунду:

# Практические скрипты должны использовать неблокирующий таймер,
# здесь для простоты мы используем блокирующий таймер.
import os, time

# Создаём файловый дескриптор таймера
fd = os.timerfd_create(time.CLOCK_REALTIME)

# Запускаем таймер через 1 секунду с интервалом в полсекунды.
os.timerfd_settime(fd, initial=1, interval=0.5)

try:
    # Обрабатываем события таймера четыре раза.
    for _ in range(4):
        # read() будет блокироваться, пока таймер не сработает.
        _ = os.read(fd, 8)
        print("Timer expired")
finally:
    # Не забудьте закрыть файловый дескриптор таймера!
    os.close(fd)

Чтобы избежать потери точности, вызванной типом float, файловые дескрипторы таймера позволяют задавать начальное время срабатывания и интервал в целых наносекундах с помощью вариантов функций _ns.

Этот пример показывает, как epoll() можно использовать с файловыми дескрипторами таймера для ожидания, пока файловый дескриптор не будет готов к чтению:

import os, time, select, socket, sys

# Создаём объект epoll.
ep = select.epoll()

# В этом примере используется адрес обратной петли для отправки команды "stop" на сервер.
#
# $ telnet 127.0.0.1 1234
# Пытаемся подключиться к 127.0.0.1...
# Подключено к 127.0.0.1.
# Экранирующий символ – '^]'.
# стоп
# Соединение закрыто внешним хостом.
#
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("127.0.0.1", 1234))
sock.setblocking(False)
sock.listen(1)
ep.register(sock, select.EPOLLIN)

# Создавать файловые дескрипторы таймера в неблокирующем режиме.
num = 3
fds = []
for _ in range(num):
    fd = os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
    fds.append(fd)
    # Зарегистрировать файловый дескриптор таймера для событий чтения
    ep.register(fd, select.EPOLLIN)

# Запустить таймер с помощью os.timerfd_settime_ns() в наносекундах.
# Таймер 1 срабатывает каждые 0.25 секунд, таймер 2 каждые 0.5 секунд и т.д.
for i, fd in enumerate(fds, start=1):
    one_sec_in_nsec = 10**9
    i = i * one_sec_in_nsec
    os.timerfd_settime_ns(fd, initial=i//4, interval=i//4)

timeout = 3
try:
    conn = None
    is_active = True
    while is_active:
        # Ожидать истечения таймера в течение 3 секунд.
        # epoll.poll() возвращает список пар (fd, событие).
        # fd – это файловый дескриптор.
        # sock и conn[=возвращаемое значение socket.accept()] – это объекты сокетов, а не файловые дескрипторы.
        # Поэтому используйте sock.fileno() и conn.fileno() для получения файловых дескрипторов.
        events = ep.poll(timeout)

        # Если несколько файловых дескрипторов таймера готовы к чтению одновременно,
        # epoll.poll() возвращает список пар (fd, событие).
        #
        # В данном примере настроек:
        #    1-й таймер срабатывает каждые 0,25 секунды через 0,25 секунды. (0,25; 0,5; 0,75; 1,0; ...)
        #    2-й таймер – каждые 0,5 секунды через 0,5 секунды. (0,5; 1,0; 1,5; 2,0; ...)
        #    3-й таймер – каждые 0,75 секунды через 0,75 секунды. (0,75; 1,5; 2,25; 3,0; ...)
        #
        #    Через 0,25 секунды срабатывает только 1-й таймер.
        #    Через 0,5 секунды 1-й и 2-й таймеры срабатывают одновременно.
        #    Через 0,75 секунды 1-й и 3-й таймеры срабатывают одновременно.
        #    Через 1,5 секунды 1-й, 2-й и 3-й таймеры срабатывают одновременно.
        #
        # Если файловый дескриптор таймера был сигнализирован более одного раза с момента
        # последнего вызова os.read(), os.read() возвращает количество сигналов
        # в виде целого числа в порядке хоста (байты).
        print(f"Signaled events={events}")
        for fd, event in events:
            if event & select.EPOLLIN:
                if fd == sock.fileno():
                    # Проверить, есть ли запрос на подключение.
                    print(f"Accepting connection {fd}")
                    conn, addr = sock.accept()
                    conn.setblocking(False)
                    print(f"Accepted connection {conn} from {addr}")
                    ep.register(conn, select.EPOLLIN)
                elif conn and fd == conn.fileno():
                    # Проверить, есть ли данные для чтения.
                    print(f"Reading data {fd}")
                    data = conn.recv(1024)
                    if data:
                        # Для безопасности следует перехватывать исключение UnicodeDecodeError.
                        cmd = data.decode()
                        if cmd.startswith("stop"):
                            print(f"Stopping server")
                            is_active = False
                        else:
                            print(f"Unknown command: {cmd}")
                    else:
                        # Данных больше нет, закрыть соединение.
                        print(f"Closing connection {fd}")
                        ep.unregister(conn)
                        conn.close()
                        conn = None
                elif fd in fds:
                    print(f"Reading timer {fd}")
                    count = int.from_bytes(os.read(fd, 8), byteorder=sys.byteorder)
                    print(f"Timer {fds.index(fd) + 1} expired {count} times")
                else:
                    print(f"Unknown file descriptor {fd}")
finally:
    for fd in fds:
        ep.unregister(fd)
        os.close(fd)
    ep.close()

Этот пример показывает, как select() можно использовать с файловыми дескрипторами таймера для ожидания, пока файловый дескриптор не будет готов к чтению:

import os, time, select, socket, sys

# В этом примере используется адрес обратной петли для отправки команды "stop" на сервер.
#
# $ telnet 127.0.0.1 1234
# Пытаемся подключиться к 127.0.0.1...
# Подключено к 127.0.0.1.
# Экранирующий символ – '^]'.
# стоп
# Соединение закрыто внешним хостом.
#
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("127.0.0.1", 1234))
sock.setblocking(False)
sock.listen(1)

# Создавать файловые дескрипторы таймера в неблокирующем режиме.
num = 3
fds = [os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
       for _ in range(num)]
select_fds = fds + [sock]

# Запускать таймеры с помощью os.timerfd_settime() в секундах.
# Таймер 1 срабатывает каждые 0.25 секунд, таймер 2 каждые 0.5 секунд и т.д.
for i, fd in enumerate(fds, start=1):
   os.timerfd_settime(fd, initial=i/4, interval=i/4)

timeout = 3
try:
    conn = None
    is_active = True
    while is_active:
       # Ожидать истечения таймера в течение 3 секунд.
       # select.select() возвращает список файловых дескрипторов или объектов.
       rfd, wfd, xfd = select.select(select_fds, select_fds, select_fds, timeout)
       for fd in rfd:
           if fd == sock:
               # Проверить, есть ли запрос на подключение.
               print(f"Accepting connection {fd}")
               conn, addr = sock.accept()
               conn.setblocking(False)
               print(f"Accepted connection {conn} from {addr}")
               select_fds.append(conn)
           elif conn and fd == conn:
               # Проверить, есть ли данные для чтения.
               print(f"Reading data {fd}")
               data = conn.recv(1024)
               if data:
                   # Для безопасности следует перехватывать исключение UnicodeDecodeError.
                   cmd = data.decode()
                   if cmd.startswith("stop"):
                       print(f"Stopping server")
                       is_active = False
                   else:
                       print(f"Unknown command: {cmd}")
               else:
                   # Данных больше нет, закрыть соединение.
                   print(f"Closing connection {fd}")
                   select_fds.remove(conn)
                   conn.close()
                   conn = None
           elif fd in fds:
               print(f"Reading timer {fd}")
               count = int.from_bytes(os.read(fd, 8), byteorder=sys.byteorder)
               print(f"Timer {fds.index(fd) + 1} expired {count} times")
           else:
               print(f"Unknown file descriptor {fd}")
finally:
    for fd in fds:
       os.close(fd)
    sock.close()
    sock = None