socketserver — Фреймворк для сетевых серверов
Исходный код: Lib/socketserver.py
Модуль socketserver упрощает задачу написания сетевых серверов.
Существует четыре основных конкретных класса серверов:
-
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 -
Forking и threaded версии каждого типа сервера можно создать, используя эти миксин-классы. Например,
ThreadingUDPServerсоздаётся следующим образом:class ThreadingUDPServer(ThreadingMixIn, UDPServer): passМиксин-класс ставится первым, так как он переопределяет метод, определённый в
UDPServer. Установка различных атрибутов также изменяет поведение базового механизма сервера.ForkingMixInи упоминаемые ниже классы Forking доступны только на POSIX-платформах, поддерживающихfork().socketserver.ForkingMixIn.server_close()ожидает, пока все дочерние процессы завершатся, кроме случая, когда атрибутsocketserver.ForkingMixIn.block_on_closeимеет значение False.socketserver.ThreadingMixIn.server_close()ожидает, пока все потоки, не являющиеся демонами, завершатся, кроме случая, когда атрибутsocketserver.ThreadingMixIn.block_on_closeимеет значение False. Используйте демонические потоки, установивThreadingMixIn.daemon_threadsвTrue, чтобы не ждать завершения потоков.Изменено в версии 3.7:
socketserver.ForkingMixIn.server_close()иsocketserver.ThreadingMixIn.server_close()теперь ожидают, пока завершатся все дочерние процессы и все потоки, не являющиеся демонами. Добален новый атрибут классаsocketserver.ForkingMixIn.block_on_closeдля отключения поведения, существовавшего до версии 3.7.
-
class socketserver.ForkingTCPServer -
class socketserver.ForkingUDPServer -
class socketserver.ThreadingTCPServer -
class socketserver.ThreadingUDPServer -
Эти классы предварительно определены с использованием миксин-классов.
Для реализации сервиса необходимо унаследовать класс от BaseRequestHandler и переопределить его метод handle(). Затем вы можете запустить различные версии сервиса, комбинируя один из классов серверов с вашим классом обработчика запросов. Класс обработчика запросов должен отличаться для сервисов дейтаграмм или потоков. Это можно скрыть, используя подклассы обработчиков StreamRequestHandler или DatagramRequestHandler.
Конечно, вам всё равно нужно использовать голову! Например, нет смысла использовать forking-сервер, если сервис содержит состояние в памяти, которое может быть изменено различными запросами, так как изменения в дочернем процессе никогда не достигнут начального состояния, хранящегося в родительском процессе и переданного каждому дочернему процессу. В этом случае вы можете использовать потоковый сервер, но, вероятно, придётся использовать блокировки для защиты целостности общих данных.
С другой стороны, если вы создаёте HTTP-сервер, где все данные хранятся во внешнем источнике (например, в файловой системе), синхронный класс фактически сделает сервис «глухим» во время обработки одного запроса — что может быть очень длительным, если клиент медленно получает все запрошенные данные. В этом случае потоковый или forking-сервер будет уместен.
В некоторых случаях может быть уместно обработать часть запроса синхронно, но завершить обработку в дочернем процессе в зависимости от данных запроса. Это можно реализовать, используя синхронный сервер и выполняя явное разветвление в методе обработчика запросов handle().
Ещё один подход к обработке нескольких одновременных запросов в среде, которая не поддерживает ни потоки, ни fork() (или где они слишком дороги или неуместны для сервиса), заключается в поддержании явного таблицы частично завершенных запросов и использовании selectors для определения, над каким запросом нужно работать дальше (или нужно ли обрабатывать новый входящий запрос). Это особенно важно для потоковых сервисов, где каждый клиент потенциально может быть подключён в течение длительного времени (если потоки или подпроцессы нельзя использовать). Обратитесь к asyncore для другого способа управления этим.
Объекты сервера
-
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вызывает исключение. По умолчанию выводится трассировка стека в стандартный вывод ошибки и продолжается обработка дополнительных запросов.Изменено в версии 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) -
Должен вернуть значение Boolean; если значение равно
True, запрос будет обработан, а еслиFalse, запрос будет отклонен. Эта функция может быть переопределена для реализации контроля доступа для сервера. По умолчанию функция всегда возвращаетTrue.
Изменено в версии 3.6: Добавлена поддержка протокола менеджера контекста. Выход из менеджера контекста эквивалентен вызову
server_close().-
Объекты Обработчиков Запросов
-
class socketserver.BaseRequestHandler -
Это суперкласс всех объектов обработчиков запросов. Он определяет интерфейс, приведённый ниже. Конкретный подкласс обработчика запросов должен определить новый метод
handle(), и может переопределить любой другой метод. Новый экземпляр подкласса создаётся для каждого запроса.-
setup() -
Вызывается перед методом
handle()для выполнения любых необходимых действий инициализации. По умолчанию ничего не делает.
-
handle() -
Эта функция должна выполнить всю работу, необходимую для обработки запроса. По умолчанию ничего не делает. Для неё доступно несколько атрибутов экземпляра; запрос доступен как
self.request; адрес клиента какself.client_address; и экземпляр сервера какself.server, в случае необходимости доступа к информации, специфичной для сервера.Тип
self.requestотличается для дейтаграмных и потоковых сервисов. Для потоковых сервисовself.requestпредставляет собой объект сокета; для дейтаграмных сервисовself.requestпредставляет собой пару строки и сокета.
-
-
class socketserver.StreamRequestHandler -
class socketserver.DatagramRequestHandler -
Эти подклассы
BaseRequestHandlerпереопределяют методыsetup()иfinish(), и предоставляют атрибутыself.rfileиself.wfile. Атрибутыself.rfileиself.wfileмогут быть соответственно прочитаны и записаны для получения данных запроса или возврата данных клиенту. Атрибутыrfileподдерживают читаемый интерфейсio.BufferedIOBase, а атрибутыwfileподдерживают записываемый интерфейсio.BufferedIOBase.Изменено в версии 3.6:
StreamRequestHandler.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("{} wrote:".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() в первом обработчике вернёт то, что отправлено клиентом в одном вызове sendall().
Это клиентская сторона:
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–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/library/socketserver.html