Посмотрим, как все это можно воплотить в программный код. Сценарий в примере 12.9 реализует еще один эхо-сервер, который может обрабатывать несколько клиентов, не запуская новые процессы или потоки.
Пример 12.9. PP4E\Internet\Sockets\select-server.py
Сервер: обслуживает параллельно несколько клиентов с помощью select. Использует модуль select для мультиплексирования в группе сокетов: главных сокетов, принимающих от клиентов новые запросы на соединение, и входных сокетов, связанных с клиентами, запрос на соединение от которых был удовлетворен; вызов select может принимать необязательный 4-й аргумент — 0 означает "опрашивать", число n.m означает "ждать n.m секунд", отсутствие аргумента означает "ждать готовности к обработке любого сокета".
import sys, time
from select import select
from socket import socket, AF_INET, SOCK_STREAM
def now(): return time.ctime(time.time())
myHost = » # компьютер-сервер, » означает локальный хост
myPort = 50007 # использовать незарезервированный номер порта
if len(sys.argv) == 3: # хост/порт можно указать в командной строке
myHost, myPort = sys.argv[1:]
numPortSocks = 2 # количество портов для подключения клиентов
# создать главные сокеты для приема новых запросов на соединение от клиентов mainsocks, readsocks, writesocks = [], [], []
for i in range(numPortSocks):
portsock = socket(AF_INET, SOCK_STREAM) # создать объект сокета TCP portsock.bind((myHost, myPort)) # связать с номером порта сервера portsock.listen(5) # не более 5 ожидающих запросов
mainsocks.append(portsock) # добавить в главный список
# для идентификации
readsocks.append(portsock) # добавить в список источников select
myPort += 1 # привязка выполняется к смежным портам
# цикл событий: слушать и мультиплексировать, пока процесс не завершится print(‘select—server loop starting‘)
while True:
#print(readsocks)
readables, writeables, exceptions = select(readsocks, writesocks, []) for sockobj in readables:
if sockobj in mainsocks: # для готовых входных сокетов
# сокет порта: принять соединение от нового клиента newsock, address = sockobj.accept() # accept не должен
# блокировать
print(‘Connect:’, address, id(newsock)) # newsock — новый сокет readsocks.append(newsock) # добавить в список select, ждать else:
# сокет клиента: читать следующую строку
data = sockobj.recv(1024) # recv не должен блокировать
print(‘\tgot’, data, ‘on’, id(sockobj)) if not data: # если закрыто клиентом
sockobj.close() # закрыть и удалить из списка
readsocks.remove(sockobj) # иначе повторно будет
else: # обслуживаться вызовом select
# может блокировать: в действительности для операции записи # тоже следовало бы использовать вызов select reply = ‘Echo=>%s at %s‘ % (data, now()) sockobj.send(reply.encode())
Основу этого сценария составляет большой цикл событий while, в котором вызывается функция select, чтобы определить, какие сокеты готовы к обработке (в том числе главные сокеты, к которым могут подключаться клиенты, и открытые соединения с клиентами). Затем все готовые сокеты перебираются в цикле, при этом для главных сокетов выполняется прием соединений, а для клиентских сокетов, готовых к вводу, производится чтение или эхо-вывод. Методы accept и recv, используемые здесь, гарантированно не будут блокировать процесс сервера после возврата из select. Благодаря этому сервер может быстро вернуться в начало цикла и обработать вновь поступившие клиентские запросы на соединение и входные данные, отправленные уже подключенными клиентами. В итоге все новые запросы и клиенты обслуживаются псев- допараллельным образом.
Чтобы этот процесс мог действовать, сервер добавляет все сокеты, подключенные к клиентам, в список readables, передаваемый функции select, и просто ждет, когда какой-либо сокет появится в списке, который возвращается этой функцией. Для иллюстрации мы задали больше одного порта, на которых этот сервер слушает клиентов, — в наших примерах это порты 50007 и 50008. Так как главные сокеты портов также опрашиваются функцией select, запросы на соединение по любому порту могут быть приняты без блокирования уже подключившихся клиентов или новых запросов на соединение, появляющихся на другом порту. Вызов select возвращает сокеты из списка readables, которые готовы к обработке. Это могут быть и главные сокеты портов, и сокеты, соединенные с обслуживаемыми в данный момент клиентами.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011