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(); этот метод будет обрабатывать входящие запросы. Во-вторых, необходимо создать экземпляр одного из классов серверов, передав ему адрес сервера и класс обработчика запросов. Затем необходимо вызвать метод handle_request() или serve_forever() объекта сервера для обработки одного или нескольких запросов. Наконец, вызовите server_close() для закрытия сокета.
При наследовании от ThreadingMixIn для работы с многопоточными подключениями, необходимо явно указать, как вы хотите, чтобы ваши потоки вели себя при внезапном завершении работы. Класс ThreadingMixIn определяет атрибут daemon_threads, который указывает, следует ли серверу ждать завершения потоков. Вам следует явно установить этот флаг, если вы хотите, чтобы потоки вели себя автономно; значение по умолчанию — False, что означает, что Python не завершит работу, пока все потоки, созданные классом ThreadingMixIn, не завершат свою работу.
Классы серверов имеют одинаковые внешние методы и атрибуты, независимо от используемого сетевого протокола.
1. Примечания по созданию сервера
В диаграмме наследования есть пять классов, четыре из которых представляют синхронные серверы четырёх типов:
+------------+
| 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. Установка различных атрибутов также изменяет поведение основного механизма сервера.
-
class socketserver.ForkingTCPServer -
class socketserver.ForkingUDPServer -
class socketserver.ThreadingTCPServer -
class socketserver.ThreadingUDPServer -
Эти классы предварительно определены с использованием классов-миксинов.
Для реализации службы необходимо создать класс, унаследованный от BaseRequestHandler и переопределить его метод handle(). Затем вы можете запустить различные версии службы, объединив один из классов серверов с вашим классом обработчика запросов. Класс обработчика запросов должен быть разным для служб дейтаграмм или потоков. Это можно скрыть, используя подклассы обработчиков StreamRequestHandler или DatagramRequestHandler.
Конечно, вам всё равно нужно использовать свой разум! Например, нет смысла использовать разветвляющийся сервер, если служба содержит состояние в памяти, которое может быть изменено разными запросами, поскольку изменения в дочернем процессе никогда не достигнут начального состояния, сохраняемого в родительском процессе и передаваемого каждому дочернему процессу. В этом случае вы можете использовать сервер с потоками, но, вероятно, вам придется использовать блокировки для защиты целостности общих данных.
С другой стороны, если вы создаёте HTTP-сервер, где все данные хранятся во внешних источниках (например, в файловой системе), синхронный класс фактически сделает службу «глухой» во время обработки одного запроса — что может занять очень много времени, если клиент медленно получает все запрошенные данные. В этом случае подходящим является сервер с потоками или разветвлением.
В некоторых случаях может быть целесообразно обработать часть запроса синхронно, но завершить обработку в разветвлённом дочернем процессе в зависимости от данных запроса. Это можно реализовать, используя синхронный сервер и выполняя явное разветвление в методе обработчика запросов handle().
Другой подход к обработке нескольких одновременных запросов в среде, не поддерживающей потоки или fork() (или где это слишком дорого или неподходяще для службы) — поддерживать явную таблицу частично завершённых запросов и использовать selectors для определения запроса, над которым следует работать следующим (или обрабатывать новый входящий запрос). Это особенно важно для потоковых служб, где каждый клиент потенциально может быть подключен надолго (если потоки или дочерние процессы не могут быть использованы). См. asyncore для другого способа управления этим.
2. Объекты сервера
-
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()остановить работу и ожидать завершения.
-
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() -
Фактически обрабатывает запрос, создавая экземпляр
RequestHandlerClassи вызывая его методhandle().
-
get_request() -
Должен принять запрос от сокета и вернуть кортеж из 2 элементов, содержащий новый объект сокета для связи с клиентом и адрес клиента.
-
handle_error(request, client_address) -
Эта функция вызывается, если метод
handle()экземпляраRequestHandlerClassвызывает исключение. По умолчанию выполняется вывод трассировки стека в стандартный вывод и продолжается обработка дальнейших запросов.
-
handle_timeout() -
Эта функция вызывается, когда атрибут
timeoutбыл установлен на значение, отличное отNone, и срок ожидания истек без получения запросов. По умолчанию для серверов с разветвлением выполняется сбор состояния любых завершившихся дочерних процессов, в то время как в серверах с потоками этот метод ничего не делает.
-
process_request(request, client_address) -
Вызывает
finish_request()для создания экземпляраRequestHandlerClass. При необходимости эта функция может создать новый процесс или поток для обработки запроса; классыForkingMixInиThreadingMixInэто делают.
-
-
server_activate() -
Вызывается конструктором сервера для активации сервера. По умолчанию для TCP-сервера вызывается
listen()на сокете сервера. Может быть переопределён.
-
server_bind() -
Вызывается конструктором сервера для привязки сокета к нужному адресу. Может быть переопределён.
-
3. Объекты обработчиков запросов
-
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могут быть соответственно читаемы и записываемы, чтобы получить данные запроса или вернуть данные клиенту.
4. Примеры
4.1. Пример 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
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
# 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
4.2. Пример 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
server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
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-сервера.
4.3. Асинхронные миксины
Для создания асинхронных обработчиков используйте классы 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)
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()
server.server_close()
Вывод примера должен выглядеть примерно так:
$ 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 используется аналогично, за исключением того, что сервер будет создавать новый процесс для каждого запроса.
© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.5/library/socketserver.html