socketserver
— Фреймворк для сетевых серверов¶
Источник: Lib/socketserver.py
Модуль socketserver
упрощает задачу написания сетевых серверов.
Availability: не 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
.
Эти четыре класса обрабатывают запросы synchronously; каждый запрос должен быть завершен, прежде чем будет запущен следующий. Это не подходит, если каждый запрос выполняется долго, потому что требует много вычислений, или потому что возвращает много данных, которые клиент обрабатывает медленно. Решением является создание отдельного процесса или потока для обработки каждого запроса; для поддержки асинхронного поведения можно использовать смешанные классы 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
Класс mix-in стоит на первом месте, поскольку он переопределяет метод, определенный в
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¶
Эти классы предварительно определяются с помощью смешанных классов.
Added in version 3.12: Были добавлены классы ForkingUnixStreamServer
и ForkingUnixDatagramServer
.
Чтобы реализовать сервис, вы должны вывести класс из BaseRequestHandler
и переопределить его метод handle()
. Затем вы можете запускать различные версии сервиса, комбинируя один из классов сервера с классом обработчика запросов. Класс обработчика запросов должен быть разным для служб дейтаграмм и потоков. Это можно скрыть, используя подклассы обработчика StreamRequestHandler
или DatagramRequestHandler
.
Разумеется, вам все равно нужно использовать голову! Например, не имеет смысла использовать forking-сервер, если служба содержит в памяти состояние, которое может быть изменено различными запросами, поскольку изменения в дочернем процессе никогда не достигнут начального состояния, хранящегося в родительском процессе и передаваемого каждому дочернему. В этом случае можно использовать потоковый сервер, но для защиты целостности общих данных вам, скорее всего, придется использовать блокировки.
С другой стороны, если вы создаете HTTP-сервер, где все данные хранятся во внешней среде (например, в файловой системе), синхронный класс, по сути, сделает сервис «глухим» на время обработки одного запроса - а это может быть очень долго, если клиент медленно получает все запрошенные данные. Здесь уместен потоковый или вилочный сервер.
В некоторых случаях целесообразно обрабатывать часть запроса синхронно, а завершать обработку в дочерней вилке в зависимости от данных запроса. Это можно реализовать, используя синхронный сервер и выполнив явную вилку в методе handle()
класса обработчика запросов.
Другой подход к обработке нескольких одновременных запросов в среде, которая не поддерживает ни потоки, ни fork()
(или если они слишком дороги или не подходят для сервиса), заключается в ведении явной таблицы частично завершенных запросов и использовании selectors
для принятия решения о том, над каким запросом работать дальше (или обрабатывать ли новый входящий запрос). Это особенно важно для потоковых сервисов, где каждый клиент потенциально может быть подключен в течение длительного времени (если нельзя использовать потоки или подпроцессы).
Объекты сервера¶
- 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()
. Этот метод может быть переопределен подклассами или классами-миксинами для выполнения действий, специфичных для данного сервиса, например, действий по очистке.Added in version 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
запросов. Как только очередь будет заполнена, при дальнейших запросах клиентов будет выдаваться ошибка «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()¶
Должен принять запрос от сокета и вернуть 2-кортеж, содержащий новый объект сокета, который будет использоваться для связи с клиентом, и адрес клиента.
- 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: Добавлена поддержка протокола context manager. Выход из менеджера контекста эквивалентен вызову
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
readable.
- 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()
.