socketserver
— Платформа для сетевых серверов¶
Исходный код: Lib/socketserver.py
Модуль socketserver
упрощает задачу написания сетевых серверов.
Availability: это не Emscripten, это был не я.
Этот модуль не работает или недоступен на платформах WebAssembly wasm32-emscripten
и wasm32-wasi
. Дополнительную информацию смотрите в разделе Платформы веб-сборки.
Существует четыре основных класса конкретных серверов:
- 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()
; этот метод будет обрабатывать входящие запросы. Во-вторых, вы должны создать экземпляр одного из классов server, передав ему адрес сервера и класс обработчика запросов. Рекомендуется использовать server в инструкции with
. Затем вызовите метод handle_request()
или serve_forever()
объекта server для обработки одного или нескольких запросов. Наконец, вызовите 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
и классы разветвления, упомянутые ниже, доступны только на платформах 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
class, чтобы настроить поведение до версии 3.7.
- class socketserver.ForkingTCPServer¶
- class socketserver.ForkingUDPServer¶
- class socketserver.ThreadingTCPServer¶
- class socketserver.ThreadingUDPServer¶
Эти классы предварительно определены с помощью встроенных классов.
Чтобы реализовать службу, вы должны вывести класс из BaseRequestHandler
и переопределить его метод handle()
. Затем вы можете запускать различные версии службы, объединив один из классов сервера с вашим классом обработчика запросов. Класс обработчика запросов должен отличаться для служб дейтаграмм или потоковых сервисов. Это можно скрыть, используя подклассы обработчиков StreamRequestHandler
или DatagramRequestHandler
.
Конечно, вам все равно придется шевелить мозгами! Например, нет смысла использовать сервер разветвления, если служба содержит состояние в памяти, которое может быть изменено различными запросами, поскольку изменения в дочернем процессе никогда не достигнут исходного состояния, сохраненного в родительском процессе и передаваемого каждому дочернему процессу. В этом случае вы можете использовать потоковый сервер, но вам, вероятно, придется использовать блокировки для защиты целостности общих данных.
С другой стороны, если вы создаете HTTP-сервер, где все данные хранятся извне (например, в файловой системе), синхронный класс, по сути, сделает службу «глухой» во время обработки одного запроса, что может занять очень много времени, если клиент работает медленно. получите все запрошенные им данные. Здесь уместен потоковый или разветвляющий сервер.
В некоторых случаях может оказаться целесообразным обрабатывать часть запроса синхронно, но завершать обработку в дочернем ответвлении в зависимости от данных запроса. Это может быть реализовано с помощью синхронного сервера и выполнения явного fork в методе класса обработчика запросов handle()
.
Другой подход к обработке нескольких одновременных запросов в среде, которая не поддерживает ни потоки, ни fork()
(или когда они слишком дороги или не подходят для сервиса), заключается в ведении явной таблицы частично завершенных запросов и использовании selectors
для принятия решения о том, какой запрос следует выполнять. работайте над следующим (или над обработкой нового входящего запроса). Это особенно важно для потоковых сервисов, где каждый клиент потенциально может быть подключен в течение длительного времени (если потоки или подпроцессы не могут быть использованы). Смотрите asyncore
для другого способа управления этим.
Серверные объекты¶
- class socketserver.BaseServer(server_address, RequestHandlerClass)¶
Это суперкласс всех серверных объектов в модуле. Он определяет интерфейс, приведенный ниже, но не реализует большинство методов, что делается в подклассах. Эти два параметра хранятся в соответствующих атрибутах
server_address
иRequestHandlerClass
.- fileno()¶
Возвращает целочисленный файловый дескриптор для сокета, который прослушивает сервер. Эта функция чаще всего передается в
selectors
, чтобы разрешить мониторинг нескольких серверов в рамках одного процесса.
- handle_request()¶
Обработать один запрос. Эта функция вызывает следующие методы по порядку:
get_request()
,verify_request()
, иprocess_request()
. Если предоставленный пользователем методhandle()
класса handler вызывает исключение, будет вызван метод сервера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
; эти методы бесполезны для внешних пользователей объекта server.- finish_request(request, client_address)¶
Фактически обрабатывает запрос, создавая экземпляр
RequestHandlerClass
и вызывая его методhandle()
.
- get_request()¶
Должен принять запрос от сокета и вернуть 2-й кортеж, содержащий объект new socket, который будет использоваться для связи с клиентом, и адрес клиента.
- 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: Добавлена поддержка протокола 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
читаемый интерфейс.
- 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()
.