Содержание страницы
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