Содержание страницы
socketserver – фреймворк для сетевых серверов¶socketserver – A framework for network servers
Исходный код: Lib/socketserver.py
Модуль socketserver упрощает создание сетевых серверов.
Доступность: не WASI.
Этот модуль не работает или недоступен на WebAssembly. Подробнее см. платформы WebAssembly.
Существует четыре базовых конкретных класса серверов:
- class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
Этот класс использует сетевой протокол TCP, который обеспечивает непрерывные потоки данных между клиентом и сервером. Если bind_and_activate равен true, конструктор автоматически пытается вызвать
server_bind()иserver_activate(). Остальные параметры передаются базовому классуBaseServer.Изменено в версии 3.15: Размер очереди по умолчанию теперь равен
socket.SOMAXCONNдляsocketserver.TCPServer.
- class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
Использует дейтаграммы – отдельные пакеты информации, которые могут приходить не по порядку или теряться при передаче. Параметры те же, что и у
TCPServer.
- class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
- class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
Эти реже используемые классы похожи на классы TCP и UDP, но используют сокеты домена Unix; они недоступны на платформах, отличных от Unix. Параметры те же, что и у
TCPServer.
Эти четыре класса обрабатывают запросы синхронно: каждый запрос должен быть
завершён до того, как начнётся следующий. Это не подходит, если каждый
запрос требует много времени на выполнение из-за вычислительной сложности
или большого объёма возвращаемых данных, которые клиент обрабатывает медленно.
Решение – создавать отдельный процесс или поток для обработки каждого запроса;
классы-примеси ForkingMixIn и ThreadingMixIn
позволяют реализовать асинхронное поведение.
Создание сервера состоит из нескольких этапов. Сначала необходимо создать
класс-обработчик запросов, унаследовав его от BaseRequestHandler и
переопределив метод handle();
этот метод будет обрабатывать входящие запросы. Затем нужно создать экземпляр
одного из классов сервера, передав ему адрес сервера и класс-обработчик.
Рекомендуется использовать сервер в операторе with. После этого
вызовите метод handle_request() или
serve_forever() объекта сервера для обработки одного или нескольких
запросов. Наконец, вызовите server_close()
для закрытия сокета (если только не использовался оператор with).
При наследовании от ThreadingMixIn для поддержки многопоточности
следует явно указать, как потоки должны вести себя при резком завершении
работы. Класс ThreadingMixIn определяет атрибут
daemon_threads, который указывает, должен ли сервер ожидать
завершения потоков. Установите этот флаг явно, если хотите, чтобы потоки
работали автономно; значение по умолчанию – False, то есть
Python не завершится, пока все потоки, созданные ThreadingMixIn, не завершатся.
Классы серверов имеют одни и те же внешние методы и атрибуты, независимо от того, какой сетевой протокол они используют.
Замечания по созданию серверов¶Server Creation Notes
В иерархии наследования пять классов, четыре из которых представляют синхронные серверы четырёх типов:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
Обратите внимание, что UnixDatagramServer наследуется от UDPServer, а не от
UnixStreamServer – единственное различие между IP-сервером и
сервером Unix заключается в семействе адресов.
- class socketserver.ForkingMixIn¶
- class socketserver.ThreadingMixIn¶
Версии с порождением процессов и потоками для каждого типа сервера можно создать с помощью этих классов-примесей. Например,
ThreadingUDPServerсоздаётся следующим образом:class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
Класс-примесь указывается первым, поскольку он переопределяет метод, определённый в
UDPServer. Установка различных атрибутов также изменяет поведение базового механизма сервера.ForkingMixInи классы Forking, упомянутые ниже, доступны только на POSIX-платформах, поддерживающихfork().- block_on_close¶
ForkingMixIn.server_closeожидает завершения всех дочерних процессов, за исключением случая, когда атрибутblock_on_closeравенFalse.ThreadingMixIn.server_closeожидает завершения всех не-демонических потоков, за исключением случая, когда атрибутblock_on_closeравенFalse.
- max_children¶
Задаёт максимальное количество дочерних процессов, одновременно обрабатывающих запросы для
ForkingMixIn. Если лимит достигнут, новые запросы ожидают завершения одного из дочерних процессов.
- daemon_threads¶
Для
ThreadingMixInиспользуются демонические потоки, если установитьThreadingMixIn.daemon_threadsвTrue– чтобы не ожидать завершения потоков.
Изменено в версии 3.7:
ForkingMixIn.server_closeиThreadingMixIn.server_closeтеперь ожидают завершения всех дочерних процессов и не-демонических потоков. Добавлен новый атрибут классаForkingMixIn.block_on_closeдля сохранения поведения, предшествующего версии 3.7.
- class socketserver.ForkingTCPServer¶
- class socketserver.ForkingUDPServer¶
- class socketserver.ThreadingTCPServer¶
- class socketserver.ThreadingUDPServer¶
- class socketserver.ForkingUnixStreamServer¶
- class socketserver.ForkingUnixDatagramServer¶
- class socketserver.ThreadingUnixStreamServer¶
- class socketserver.ThreadingUnixDatagramServer¶
Эти классы предварительно определены с использованием примесных классов.
Добавлено в версии 3.12: Классы ForkingUnixStreamServer и ForkingUnixDatagramServer
были добавлены.
Чтобы реализовать службу, необходимо создать класс, наследующий от BaseRequestHandler,
и переопределить его метод handle().
Затем можно запускать различные версии службы, комбинируя один из классов сервера с классом обработчика запросов.
Класс обработчика запросов должен различаться для служб датаграмм и потоков.
Эту сложность можно скрыть, используя подклассы обработчиков StreamRequestHandler или DatagramRequestHandler.
Конечно, нужно подходить с умом. Например, нет смысла использовать сервер с порождением процессов (forking), если служба хранит состояние в памяти, которое может изменяться разными запросами, потому что изменения в дочернем процессе никогда не попадут в исходное состояние, хранящееся в родительском процессе и передаваемое каждому дочернему. В этом случае можно использовать потоковый сервер (threading), но, скорее всего, придётся использовать блокировки для защиты целостности общих данных.
С другой стороны, если создаётся HTTP-сервер, где все данные хранятся внешне (например, в файловой системе), синхронный класс фактически сделает службу «глухой» на время обработки одного запроса – что может занять очень много времени, если клиент медленно получает все запрошенные данные. Здесь подойдёт потоковый сервер или сервер с порождением процессов.
В некоторых случаях может быть уместно обработать часть запроса синхронно, но завершить обработку в порождённом дочернем процессе в зависимости от данных запроса. Этого можно добиться, используя синхронный сервер и выполняя явный fork (порождение процесса) в методе handle() класса обработчика запросов.
Другой подход к обработке нескольких одновременных запросов в среде, не поддерживающей ни потоки, ни fork() (или если они слишком дороги или неподходящи для службы) – поддерживать явную таблицу частично выполненных запросов и использовать selectors для определения, какой запрос обрабатывать следующим (или нужно ли обрабатывать новый входящий запрос). Это особенно важно для потоковых служб, где каждый клиент может потенциально оставаться подключённым долгое время (если потоки или подпроцессы не могут использоваться).
Объекты сервера¶Server Objects
- class socketserver.BaseServer(server_address, RequestHandlerClass)¶
Это суперкласс для всех объектов Server в модуле. Он определяет интерфейс, приведенный ниже, но не реализует большинство методов – это делается в подклассах. Два параметра хранятся в соответствующих атрибутах
server_addressиRequestHandlerClass.- fileno()¶
Возвращает целочисленный файловый дескриптор сокета, на котором сервер прослушивает соединения. Эта функция чаще всего передается в
selectors, чтобы обеспечить мониторинг нескольких серверов в одном процессе.
- handle_request()¶
Обрабатывает один запрос. Эта функция вызывает следующие методы по порядку:
get_request(),verify_request()иprocess_request(). Если предоставленный пользователем методhandle()класса-обработчика вызывает исключение, вызывается методhandle_error()сервера. Если в течениеtimeoutсекунд не поступило ни одного запроса, будет вызванhandle_timeout(), иhandle_request()завершится.
- serve_forever(poll_interval=0.5)¶
Обрабатывает запросы до явного запроса
shutdown(). Опрашивает завершение каждые poll_interval секунд. Игнорирует атрибутtimeout. Также вызываетservice_actions(), который может использоваться подклассом или миксином для предоставления действий, специфичных для данной службы. Например, классForkingMixInиспользуетservice_actions()для очистки процессов-зомби.Изменено в версии 3.3: Добавлен вызов
service_actionsв методserve_forever.
- service_actions()¶
Вызывается в цикле
serve_forever(). Этот метод может быть переопределен подклассами или классами-миксинами для выполнения действий, специфичных для данной службы, например, действий по очистке.Добавлено в версии 3.3.
- shutdown()¶
Сообщает циклу
serve_forever()остановиться и ждет завершения.shutdown()должен вызываться, покаserve_forever()выполняется в другом потоке, иначе возникнет взаимоблокировка.
- server_close()¶
Выполняет очистку сервера. Может быть переопределен.
- address_family¶
Семейство протоколов, к которому принадлежит сокет сервера. Типичные примеры:
socket.AF_INET,socket.AF_INET6иsocket.AF_UNIX. Наследуйте классы TCP- или UDP-сервера из этого модуля, установив атрибут классаaddress_family = AF_INET6, если нужны классы сервера для IPv6.
- RequestHandlerClass¶
Класс обработчика запросов, предоставленный пользователем; экземпляр этого класса создаётся для каждого запроса.
- server_address¶
Адрес, на котором сервер принимает соединения. Формат адресов зависит от семейства протоколов; подробнее см. документацию модуля
socket. Для интернет-протоколов это кортеж, содержащий строку с адресом и целочисленный номер порта: например,('127.0.0.1', 80).
- socket¶
Объект сокета, через который сервер будет принимать входящие запросы.
Классы серверов поддерживают следующие переменные класса:
- allow_reuse_address¶
Определяет, разрешает ли сервер повторное использование адреса. По умолчанию
False; значение можно изменить в подклассах, чтобы задать другую политику.
- request_queue_size¶
Размер очереди запросов. Если обработка одного запроса занимает много времени, запросы, поступающие, пока сервер занят, помещаются в очередь – до
request_queue_sizeзапросов. Когда очередь заполняется, дальнейшие запросы от клиентов получают ошибку «Connection denied». Обычно значение по умолчанию – 5, но его можно переопределить в подклассах.
- socket_type¶
Тип сокета, используемый сервером; распространённые значения:
socket.SOCK_STREAMиsocket.SOCK_DGRAM.
- timeout¶
Длительность тайм-аута в секундах или
None, если тайм-аут не нужен. Еслиhandle_request()не получает входящих запросов в течение периода тайм-аута, вызывается методhandle_timeout().
Существуют различные методы сервера, которые можно переопределить в подклассах базовых классов серверов, например
TCPServer; эти методы не предназначены для внешних пользователей объекта сервера.- finish_request(request, client_address)¶
Непосредственно обрабатывает запрос, создавая экземпляр
RequestHandlerClassи вызывая его методhandle().
- get_request()¶
Должен принять запрос из сокета и вернуть кортеж из двух элементов, содержащий новый объект сокета для взаимодействия с клиентом и адрес клиента.
- handle_error(request, client_address)¶
Эта функция вызывается, если метод
handle()экземпляраRequestHandlerClassвозбуждает исключение. По умолчанию трассировка выводится в стандартный поток ошибок, после чего обработка следующих запросов продолжается.Изменено в версии 3.6: Теперь вызывается только для исключений, производных от класса
Exception.
- handle_timeout()¶
Эта функция вызывается, когда атрибут
timeoutустановлен в значение, отличное отNone, и период тайм-аута истёк без получения запросов. По умолчанию для серверов с порождением процессов (forking) собирается статус завершившихся дочерних процессов; в многопоточных серверах этот метод ничего не делает.
- process_request(request, client_address)¶
Вызывает
finish_request()для создания экземпляраRequestHandlerClass. При необходимости эта функция может создать новый процесс или поток для обработки запроса; так поступают классыForkingMixInиThreadingMixIn.
- server_activate()¶
Вызывается конструктором сервера для активации сервера. По умолчанию для TCP-сервера просто вызывает
listen()на сокете сервера. Можно переопределить.
- server_bind()¶
Вызывается конструктором сервера для привязки сокета к нужному адресу. Можно переопределить.
- verify_request(request, client_address)¶
Должна возвращать логическое значение; если значение равно
True, запрос будет обработан, а еслиFalse, запрос будет отклонен. Эта функция может быть переопределена для реализации контроля доступа к серверу. Реализация по умолчанию всегда возвращаетTrue.
Изменено в версии 3.6: Добавлена поддержка протокола контекстного менеджера. Выход из контекстного менеджера эквивалентен вызову
server_close().
Объекты обработчика запросов¶Request Handler Objects
- class socketserver.BaseRequestHandler¶
Это суперкласс всех объектов-обработчиков запросов. Он определяет интерфейс, приведенный ниже. Конкретный подкласс обработчика запросов должен определить новый метод
handle()и может переопределять любые другие методы. Новый экземпляр подкласса создается для каждого запроса.- setup()¶
Вызывается перед методом
handle()для выполнения необходимых действий по инициализации. Реализация по умолчанию ничего не делает.
- handle()¶
Эта функция должна выполнять всю работу, необходимую для обслуживания запроса. Реализация по умолчанию ничего не делает. Ей доступны несколько атрибутов экземпляра: запрос доступен как
request; адрес клиента какclient_address; а экземпляр сервера какserver, на случай, если потребуется доступ к информации, специфичной для сервера.Тип
requestразличается для служб дейтаграмм и потоков. Для потоковых службrequest– это объект сокета; для служб дейтаграммrequest– это пара из строки и сокета.
- finish()¶
Вызывается после метода
handle()для выполнения необходимых действий по очистке. Реализация по умолчанию ничего не делает. Еслиsetup()вызывает исключение, эта функция не будет вызвана.
- request¶
Новый объект
socket.socket, используемый для взаимодействия с клиентом.
- client_address¶
Адрес клиента, возвращаемый
BaseServer.get_request().
- server¶
Объект
BaseServer, используемый для обработки запроса.
- class socketserver.StreamRequestHandler¶
- class socketserver.DatagramRequestHandler¶
Эти подклассы
BaseRequestHandlerпереопределяют методыsetup()иfinish()и предоставляют атрибутыrfileиwfile.- rfile¶
Файловый объект, из которого считывается запрос. Поддерживает интерфейс
io.BufferedIOBasereadable.
- wfile¶
Файловый объект, в который записывается ответ. Поддерживает интерфейс
io.BufferedIOBasewritable.
Изменено в версии 3.6:
wfileтакже поддерживает интерфейсio.BufferedIOBasewritable.
Примеры¶Examples
socketserver.TCPServer Пример¶socketserver.TCPServer Example
Это серверная сторона:
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
Класс обработчика запросов для нашего сервера.
Он создаётся один раз на каждое подключение к серверу и должен
переопределять метод handle() для реализации взаимодействия с
клиентом.
"""
def handle(self):
# self.request – это TCP-сокет, подключённый к клиенту
pieces = [b'']
total = 0
while b'\n' not in pieces[-1] and total < 10_000:
pieces.append(self.request.recv(2000))
total += len(pieces[-1])
self.data = b''.join(pieces)
print(f"Received from {self.client_address[0]}:")
print(self.data.decode("utf-8"))
# просто отправляет обратно те же данные, но в верхнем регистре
self.request.sendall(self.data.upper())
# после возврата сокет будет закрыт.
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Создаём сервер, привязываясь к localhost на порту 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Запускаем сервер; он будет работать, пока вы
# не прервёте программу с помощью Ctrl-C
server.serve_forever()
Альтернативный класс обработчика запросов, который использует потоки (объекты, подобные файлам, упрощающие взаимодействие за счет предоставления стандартного файлового интерфейса):
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile – это файлоподобный объект, созданный обработчиком.
# Теперь можно использовать, например, readline() вместо прямых вызовов recv().
# Мы ограничиваемся 10 000 байт, чтобы избежать злоупотреблений со стороны отправителя.
self.data = self.rfile.readline(10000).rstrip()
print(f"{self.client_address[0]} wrote:")
print(self.data.decode("utf-8"))
# Аналогично, self.wfile – это файлоподобный объект, используемый для записи ответа
# клиенту
self.wfile.write(self.data.upper())
Разница в том, что вызов readline() во втором обработчике вызывает recv() несколько раз, пока не встретит символ новой строки, тогда как первый обработчик должен был использовать цикл recv() для накопления данных до символа новой строки. Если бы он использовал один recv() без цикла, он бы просто вернул то, что было получено от клиента на данный момент. TCP основан на потоках: данные поступают в том порядке, в котором были отправлены, но нет корреляции между вызовами клиента send() или sendall() и количеством вызовов recv() на сервере, необходимых для их получения.
Это клиентская сторона:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# Создать сокет (SOCK_STREAM означает TCP-сокет)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Подключиться к серверу и отправить данные
sock.connect((HOST, PORT))
sock.sendall(bytes(data, "utf-8"))
sock.sendall(b"\n")
# Получить данные от сервера и завершить работу
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", received)
Результат работы примера должен выглядеть примерно так:
Сервер:
$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'
Клиент:
$ python TCPClient.py hello world with TCP
Sent: hello world with TCP
Received: HELLO WORLD WITH TCP
$ python TCPClient.py python is nice
Sent: python is nice
Received: PYTHON IS NICE
socketserver.UDPServer Пример¶socketserver.UDPServer Example
Это серверная сторона:
import socketserver
class MyUDPHandler(socketserver.BaseRequestHandler):
"""
Этот класс работает аналогично классу-обработчику TCP, за исключением того, что
self.request состоит из пары данных и клиентского сокета, и поскольку
соединение отсутствует, адрес клиента должен быть указан явно
при отправке данных обратно через sendto().
"""
def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print(f"{self.client_address[0]} wrote:")
print(data)
socket.sendto(data.upper(), self.client_address)
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
server.serve_forever()
Это клиентская сторона:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# SOCK_DGRAM – это тип сокета для UDP-сокетов
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Как можно видеть, вызов connect() отсутствует; в UDP нет соединений.
# Вместо этого данные отправляются напрямую получателю через sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", received)
Результат работы примера должен выглядеть точно так же, как для примера TCP-сервера.
Асинхронные Mixins¶Asynchronous Mixins
Для построения асинхронных обработчиков используйте классы ThreadingMixIn и
ForkingMixIn.
Пример для класса ThreadingMixIn:
import socket
import threading
import socketserver
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
data = str(self.request.recv(1024), 'ascii')
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response)
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
def client(ip, port, message):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((ip, port))
sock.sendall(bytes(message, 'ascii'))
response = str(sock.recv(1024), 'ascii')
print("Received: {}".format(response))
if __name__ == "__main__":
# Порт 0 означает выбор произвольного неиспользуемого порта
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# Запустить поток для сервера – этот поток затем запустит один
# дополнительный поток на каждый запрос
server_thread = threading.Thread(target=server.serve_forever)
# Завершить поток сервера при завершении главного потока
server_thread.daemon = True
server_thread.start()
print("Server loop running in thread:", server_thread.name)
client(ip, port, "Hello World 1")
client(ip, port, "Hello World 2")
client(ip, port, "Hello World 3")
server.shutdown()
Результат работы примера должен выглядеть примерно так:
$ python ThreadedTCPServer.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3
Класс ForkingMixIn используется аналогично, за исключением того, что сервер
будет создавать новый процесс для каждого запроса.
Доступен только на платформах POSIX, поддерживающих fork().