Python select with timeout

Вот мы добрались и до обработки нескольких клиентов на одном сервере.

с помощью модуля select. Сегодня наши клиенты будут общаться в общем чате, как в настоящем Телеграме.

Потоки ввода/вывода

Чтобы постичь основы highload в Python используют три базовых подхода:

  1. Отдельный поток на каждого клиента . Почти совсем не хайлоад, т.к. этот вариант потребляет достаточно много системных ресурсов, поэтому, при увеличении нагрузки на сервер, его использование будет опасным решением
  2. Неблокирующие сокеты . В Питоне с третей версии для реализации таких советов предусмотрен метод setblocking(), в который передается параметр, равный 0.
  3. Применение системных вызовов select() и poll() из модуля select — самое интересное. Системный вызов select() поддерживается всеми программными платформами, подразумевающими сетевое взаимодействие

Модуль select

Системный вызов select() можно использовать для опроса — или мультиплексирования — обработки нескольких потоков ввода-вывода, не используя потоки управления или дочерние процессы. В системах UNIX эти вызовы можно применять для работы с сокетами, каналами и многими другими типами файлов. В Windows — только для работы с сокетами.

select(r, w, e [, timeout])

В первых трех аргументах передаются списки с целочисленными дескрипторами файлов или с объектами, обладающими методом fileno() , который возвращает дескриптор файла

r — список объектов, которые передают информацию серверу (эти клиенты передают информацию на сервер)

w — список объектов, которые считывают информацию от сервера, но ничего ему не передают и не запрашивают у него (эти клиенты только принимают данные, то, что передает им сервер)

e — исключения

timeout — Таймаут необходим, чтобы проверять сокет на наличие подключений новых клиентов, на наличие данных

Системный вызов select() возвращает кортеж списков с объектами, находящимися в требуемом состоянии

Как это работает?

Массив дескрипторов клиентских сокетов можно передавать в качестве аргумента функции select, а она в свою очередь предоставляет список сокетов, которые:

  • Готовы принять новые данные;
  • Имеют новые данные для чтения;
  • Содержат ошибки выполнения.

Конструкция select — это даже не функция, а системный вызов. Она заложена не в сам Python, а в операционную систему. Таким образом, традиционный подход с простым перебором заменяется на более эффективный, с особым оптимизированным алгоритмом.

Пример использования

Яркой демонстрацией модуля select является отправка/прием сообщений между сервером и несколькими клиентами. Для этого мы сначала создадим служебный файл, запускающий несколько «клиентов» с использованием модуля subprocess. Пусть это будет service.py

from subprocess import Popen, CREATE_NEW_CONSOLE
import os

process_list = [] #сюда будут попадать все клиентские процессы

while True:
user = input(«Запустить 10 клиентов (start) / Закрыть клиентов (close) / Выйти (quit) «)

if user == ‘quit’: #если пользователь ввел quit, то останавливаем цикл
break
elif user == ‘start’: #если пользователь ввел start, то запускаем процессы в консоли
for _ in range(10):
process_list.append(Popen(‘python client.py’,
creationflags=CREATE_NEW_CONSOLE)) #CREATE_NEW_CONSOLE — открываем каждый процесс в новой консоли

print(‘ Запущено 10 клиентов’)
elif user == ‘close’: #если пользователь ввел close, то дропаем процессы
for process in process_list:
process.kill()
process_list.clear() #очищаем список

Далее мы напишем серверную часть — server.py [code]

import select
from socket import socket, AF_INET, SOCK_STREAM

def clients_read(r_clients, clientlist): # чтение клиентских запросов

responses = <> # Словарь ответов сервера вида

#for sock in clients:
for sock in r_clients:
try:
data = sock.recv(1024).decode(‘utf-8’)
responses[sock] = data #записываем ключ в словаре responses в виде данных
except:
print(‘Клиент <> <> отключился’.format(sock.fileno(), sock.getpeername()))
clientlist.remove(sock)

def clients_write(requests, w_clients, all_clients): # ответы клиентам на их запросы

for sock in w_clients:
if sock in requests:
try:
# Подготовить и отправить ответ сервера
response = requests[sock].encode(‘utf-8’)
# Ответ сделаем чуть непохожим на оригинал
sock.send(response.lower())
except: # Сокет недоступен, клиент отключился
print(‘Клиент <> <> отключился’.format(sock.fileno(), sock.getpeername()))
sock.close()
all_clients.remove(sock)

def mainserver():
address = (‘localhost’, 8888)
clients = []

s = socket(AF_INET, SOCK_STREAM)
s.bind(address)
s.listen(5)
s.settimeout(0.2) # Таймаут для операций с сокетом
while True:
try:
conn, addr = s.accept() # Проверка подключений
except OSError as e:
pass # timeout вышел
else:
print(«Получен запрос на соединение от %s» % str(addr))
clients.append(conn)
finally:
# Проверить наличие событий ввода-вывода
wait = 10
r = [] w = [] try:
r, w, e = select.select(clients, clients, [], wait)
except:
pass # Ничего не делать, если какой-то клиент отключился

requests = clients_read(r, clients) # Принимаем запросы клиентов
if requests: #если есть запрос
clients_write(requests, w, clients) # то формируем ответ и отправляем его

print(‘Server is RUN’)
mainserver()

Касаемо функций — clients_read() обходит список советов на чтение и формируется словарь запросов в формате, а clients_write() обходит сокеты на запись. Запросы обрабатываются на основе присланных клиентских данных, обрабатываются и отсылаются сервером на основании словаря запросов requests . Основная функция сервера — mainserver() . Она открывает сокет на прослушивание с соответствующим таймаутом для операций ввода/вывода. Проверка подключений осуществляется функцией accept() и добавляет новое подключение в список clients , изначально пустой. Далее функцией select мы читаем данные из клиентских сокетов и записываем данные в них (для подключений которые попали в список clients). Сама функция select() возвращает кортеж из трех списков: файловые дескрипторы на чтение, на запись и имеющие исключение. На очереди — клиентская часть ( client.py )

# Клиентская часть (client.py)
from socket import *
#from select import select
import sys

PLACE = (‘localhost’, 8888) #куда подключаемся

def client():
with socket(AF_INET, SOCK_STREAM) as sock: # Создать сокет TCP # конструкция with (менеджер контекста) означает, что сокет будет автоматически закрыт
sock.connect(PLACE) # Коненктимся с сервером
while True:
message = input(‘Ваше сообщение: ‘)
sock.send(message.encode(‘utf-8’)) # отправляем, кодировав в байты
receive = sock.recv(1024).decode(‘utf-8’) # принимаем, декодировав в юнион
print(‘Ответ:’, receive)

if __name__ == ‘__main__’:
client()

В результате, при запуске служебного файла service.py получим 10 разных клиентов в 10 окнах консоли, обрабатываемыми одним сервером с помощью модуля select

Источник

Читайте также:  Php изменить файл txt
Оцените статью