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-поточными серверами заключается в семействе адресов, которое просто повторяется в обоих классах Unix-серверов.
-
class socketserver.ForkingMixIn -
class socketserver.ThreadingMixIn -
Серверы с поддержкой форкинга и потоков для каждого типа можно создать, используя эти миксин-классы. Например,
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.
Конечно, вам всё ещё нужно использовать голову! Например, нет смысла использовать сервер с форкингом, если сервис содержит состояние в памяти, которое может быть изменено различными запросами, так как изменения в дочернем процессе никогда не достигнут начального состояния, хранящегося в родительском процессе и передаваемого каждому дочернему.
В этом случае вы можете использовать сервер с потоками, но, вероятно, вам придётся использовать блокировки для защиты целостности общих данных.
С другой стороны, если вы строите 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() -
Должен принять запрос от сокета и вернуть кортеж из 2 элементов, содержащий новый объект сокета для связи с клиентом и адрес клиента.
-
handle_error(request, client_address) -
Эта функция вызывается, если метод
handle()экземпляраRequestHandlerClassвызывает исключение. По умолчанию выводится трассировка стека в стандартный поток ошибок и продолжается обработка других запросов.Изменено в версии 3.6: Теперь вызывается только для исключений, унаследованных от класса
Exception.
-
handle_timeout() -
Эта функция вызывается, когда атрибут
timeoutзадан значением, отличным отNone, и период ожидания прошёл без получения запросов. По умолчанию для серверов с разветвлением (forking) собирается информация о статусе завершённых дочерних процессов, а в серверах с многопоточностью (threading) этот метод ничего не делает.
-
-
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() -
Эта функция должна выполнить всю работу, необходимую для обработки запроса. По умолчанию ничего не делает. Доступны несколько атрибутов экземпляра; запрос доступен как
self.request; адрес клиента какself.client_address; и экземпляр сервера какself.server, если ему нужен доступ к информации по серверу.Тип
self.requestотличается для запросов по UDP или TCP. Для TCP-сервисовself.request— объект сокета; для UDP-сервисовself.request— пара строки и сокета.
-
-
class socketserver.StreamRequestHandler -
class socketserver.DatagramRequestHandler -
Эти подклассы
BaseRequestHandlerпереопределяют методыsetup()иfinish()и предоставляют атрибутыself.rfileиself.wfile. Атрибутыself.rfileиself.wfileможно читать и записывать соответственно для получения данных запроса или возврата данных клиенту.Атрибуты
rfileобоих классов поддерживают читаемый интерфейсio.BufferedIOBase, аDatagramRequestHandler.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–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.7/library/socketserver.html