socketserver — Фреймворк для сетевых серверов
Исходный код: 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.
-
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, не завершатся.
Классы серверов имеют одинаковые внешние методы и атрибуты, независимо от того, какой сетевой протокол они используют.
END_OF_DOCUMENT_MARKERПримечания по созданию сервера
В диаграмме наследования присутствуют пять классов, четыре из которых представляют синхронные серверы четырёх типов:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
Обратите внимание, что UnixDatagramServer наследуется от UDPServer, а не от UnixStreamServer — единственное различие между IP- и Unix-серверами заключается в семействе адресов.
-
class socketserver.ForkingMixIn -
class socketserver.ThreadingMixIn -
Версии серверов с форking и потоковой обработкой могут быть созданы с использованием этих классов-миксов. Например,
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.
-
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.
Конечно, вам всё равно нужно использовать голову! Например, нет смысла использовать сервер с форking, если служба содержит состояние в памяти, которое может быть изменено различными запросами, поскольку изменения в дочернем процессе никогда не достигнут начального состояния, хранящегося в родительском процессе и передаваемого каждому дочернему процессу. В этом случае вы можете использовать сервер с потоковой обработкой, но, вероятно, вам нужно будет использовать блокировки для защиты целостности общих данных.
С другой стороны, если вы создаёте HTTP-сервер, где все данные хранятся внешне (например, в файловой системе), синхронный класс по сути сделает службу «глухой», пока обрабатывается один запрос — что может длиться очень долго, если клиент медленно получает все запрошенные данные. В этом случае подходит сервер с потоковой или форking обработкой.
В некоторых случаях может быть целесообразно обрабатывать часть запроса синхронно, но завершать обработку в виртуальном дочернем процессе в зависимости от данных запроса. Это можно реализовать, используя синхронный сервер и выполнив явное разветвление в методе обработчика запросов handle().
Ещё один подход к обработке нескольких одновременных запросов в среде, не поддерживающей ни потоки, ни fork() (или где они слишком дороги или неподходящи для службы) — поддерживать явную таблицу частично завершенных запросов и использовать selectors для определения следующего запроса для обработки (или для обработки нового входящего запроса). Это особенно важно для потоковых служб, где каждый клиент потенциально может быть подключен в течение длительного времени (если потоки или подпроцессы нельзя использовать).
Объекты сервера
-
class socketserver.BaseServer(server_address, RequestHandlerClass) -
Это суперкласс всех объектов сервера в модуле. Он определяет интерфейс, приведённый ниже, но не реализует большинство методов, что делается в подклассах. Два параметра хранятся в соответствующих атрибутах
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_UNIX.
-
RequestHandlerClass -
Класс пользовательского обработчика запросов; экземпляр этого класса создаётся для каждого запроса.
-
server_address -
Адрес, на котором сервер прослушивает подключения. Формат адресов зависит от семейства протоколов; см. документацию модуля
socketдля получения подробностей. Для интернет-протоколов это кортеж, содержащий строку, задающую адрес, и целое число номера порта:('127.0.0.1', 80), например.
-
socket -
Объект сокета, на котором сервер будет прослушивать входящие запросы.
Классы серверов поддерживают следующие переменные класса:
-
allow_reuse_address -
Разрешает ли сервер повторное использование адреса. По умолчанию —
False, и может быть изменён в подклассах.
-
request_queue_size -
Размер очереди запросов. Если обработка одного запроса занимает много времени, любые запросы, поступающие во время работы сервера, помещаются в очередь до
request_queue_sizeзапросов. При заполнении очереди дальнейшие запросы от клиентов получат ошибку «Отказ в подключении». Значение по умолчанию обычно 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() -
Должен принять запрос от сокета и вернуть кортеж из 2-х элементов, содержащий новый объект сокета для связи с клиентом и адрес клиента.
-
handle_error(request, client_address) -
Эта функция вызывается, если метод
handle()экземпляраRequestHandlerClassвызывает исключение. Действие по умолчанию — вывести traceback в стандартный вывод ошибки и продолжить обработку дальнейших запросов.Изменено в версии 3.6: Теперь вызывается только для исключений, производных от класса
Exception.
-
-
handle_timeout() -
Эта функция вызывается, когда атрибут
timeoutбыл установлен на значение, отличное отNone, и истек таймаут, при этом запросы не поступали. По умолчанию для серверов с форкингом выполняется сбор состояния любых завершенных дочерних процессов, а для серверов с многопоточностью этот метод ничего не делает.
-
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().-
Объекты обработчиков запросов
-
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.BufferedIOBase.
-
wfile -
Объект файла, в который записывается ответ. Поддерживает записываемый интерфейс
io.BufferedIOBase
Изменено в версии 3.6:
wfileтакже поддерживает записываемый интерфейсio.BufferedIOBase. -
Примеры
socketserver.TCPServer Пример
Это серверная сторона:
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The request handler class for our server.
It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""
def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print("Received from {}:".format(self.client_address[0]))
print(self.data)
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Create the server, binding to localhost on port 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()
Альтернативный класс обработчика запросов, использующий потоки (объекты, подобные файлам, которые упрощают общение, предоставляя стандартный интерфейс файлов):
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile is a file-like object created by the handler;
# we can now use e.g. readline() instead of raw recv() calls
self.data = self.rfile.readline().strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# Likewise, self.wfile is a file-like object used to write back
# to the client
self.wfile.write(self.data.upper())
Разница заключается в том, что вызов readline() во втором обработчике будет вызван recv() несколько раз, пока не встретится символ новой строки, в то время как единственный вызов recv() в первом обработчике вернёт только полученную до этого информацию от клиента в результате вызова sendall() (обычно всё, но это не гарантируется протоколом TCP).
Это клиентская сторона:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data + "\n", "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(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 Пример
Это серверная сторона:
import socketserver
class MyUDPHandler(socketserver.BaseRequestHandler):
"""
This class works similar to the TCP handler class, except that
self.request consists of a pair of data and client socket, and since
there is no connection the client address must be given explicitly
when sending data back via sendto().
"""
def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print("{} wrote:".format(self.client_address[0]))
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 is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
Вывод примера должен выглядеть точно так же, как для примера сервера TCP.
Асинхронные миксины
Для создания асинхронных обработчиков используйте классы 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__":
# Port 0 means to select an arbitrary unused port
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# Start a thread with the server -- that thread will then start one
# more thread for each request
server_thread = threading.Thread(target=server.serve_forever)
# Exit the server thread when the main thread terminates
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().
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/library/socketserver.html