Connect with us

Обучение

Заметки Python #22: Модуль select. Один сервер — много клиентов

1 241

 

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

с помощью модуля 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

 

[code]

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() #очищаем список

[/code]

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

# серверная часть (server.py)

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)

return responses

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()

[/code]

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


[code]

# Клиентская часть (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()

[/code]

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

Нажмите что бы оставить комментарий

Ответить

Ваш адрес email не будет опубликован.

Лучшие сервисы стриминга музыки в 2019 году

Сервисы

Телевидение Wink Ростелеком: Samsung LG, Sony, Phillips, Android TV

Ростелеком

Ноутбуки Asus не видят жесткий диск. Автоматический вход в BIOS при старте

Гаджеты

LG WEB OS: приложения, обновления, настройка, проблемы со звуком

Гаджеты

Advertisement Яндекс.Метрика

Digital2.ru - Тренды, Профессии IT, WEB- разработка, Вакансии, Автоматизация, Цифровая экономика
Свободное копирование и распространение материалов с сайта Digital2.ru
разрешено только с указанием активной ссылки на Digital2 как на источник.
Copyright 2018 - 2020 © All rights reserved

OPGIO.COM

Connect