socketserver — Фреймворк для сетевых серверов
Исходный код: Lib/socketserver.py
Модуль socketserver упрощает создание сетевых серверов.
Доступность: недоступен в Emscripten и WASI.
Этот модуль не работает или недоступен на платформах WebAssembly wasm32-emscripten и wasm32-wasi. Дополнительные сведения см. в разделе Платформы 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.
Классы серверов имеют одинаковые внешние методы и атрибуты независимо от используемого сетевого протокола.
Примечания по созданию серверов
В диаграмме наследования показаны пять классов, четыре из которых представляют синхронные серверы четырёх типов:
+------------+
| 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.
-
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 -
Эти классы предварительно определены с использованием классов-примесей.
Для реализации службы нужно создать класс, унаследованный от BaseRequestHandler, и переопределить его метод handle(). Затем можно запускать различные варианты службы, сочетая один из классов серверов со своим классом обработчика запросов. Для служб на основе датаграмм и потоков нужны разные классы обработчиков запросов. Это различие можно скрыть, используя подклассы обработчиков StreamRequestHandler или DatagramRequestHandler.
Разумеется, не забывайте думать! Например, использовать сервер с разветвлением процессов бессмысленно, если служба хранит в памяти состояние, которое могут изменять разные запросы: изменения в дочернем процессе никогда не попадут в исходное состояние, хранящееся в родительском процессе и передаваемое каждому дочернему процессу. В этом случае можно использовать сервер с потоками, но, вероятно, придётся применять блокировки, чтобы защитить целостность общих данных.
С другой стороны, если вы создаёте HTTP-сервер, все данные которого хранятся во внешнем источнике (например, в файловой системе), синхронный класс фактически сделает службу «недоступной» на время обработки запроса. Это может занять очень много времени, если клиент медленно получает запрошенные данные. В этом случае подойдёт сервер с потоками или разветвлением процессов.
В некоторых случаях уместно обработать часть запроса синхронно, а завершить обработку в дочернем процессе, созданном разветвлением, в зависимости от данных запроса. Это можно реализовать, используя синхронный сервер и явно создавая дочерний процесс в методе handle() класса обработчика запросов.
Ещё один способ обрабатывать несколько одновременных запросов в среде, которая не поддерживает ни потоки, ни fork() (либо где они слишком затратны или не подходят для службы), — вести явную таблицу частично обработанных запросов и использовать selectors, чтобы выбирать следующий запрос для обработки (или решать, следует ли принять новый входящий запрос). Это особенно важно для потоковых служб, где каждый клиент потенциально может оставаться подключённым длительное время (если нельзя использовать потоки или подпроцессы). Другой способ управления этим описан в asyncore.
Объекты сервера
-
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_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() -
Должен принять запрос от сокета и вернуть кортеж из двух элементов: нового объекта сокета для связи с клиентом и адреса клиента.
-
handle_error(request, client_address) -
Эта функция вызывается, если метод
handle()экземпляраRequestHandlerClassвызывает исключение. По умолчанию трассировка выводится в стандартный поток ошибок, после чего обработка следующих запросов продолжается.Изменено в версии 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.11/library/socketserver.html