Spec-Zone.ru › Python 3.12

socketserver — Фреймворк для сетевых серверов

Исходный код: Lib/socketserver.py

Модуль socketserver упрощает задачу написания сетевых серверов.

Доступность: не Emscripten, не WASI.

Этот модуль не работает или недоступен на платформах WebAssembly wasm32-emscripten и wasm32-wasi. Дополнительную информацию см. в платформах 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.

Эти четыре класса обрабатывают запросы синхронно; каждый запрос должен быть завершен, прежде чем можно будет начать следующий запрос. Это не подходит, если каждый запрос требует длительного времени для завершения, из-за большого объёма вычислений или возвращает большой объём данных, который клиент медленно обрабатывает. Решением является создание отдельного процесса или потока для обработки каждого запроса; миксины 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

Можно создать форking- и threading-версии каждого типа сервера с использованием этих смешанных классов. Например, ThreadingUDPServer создаётся следующим образом:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

Смешанный класс идёт первым, поскольку он переопределяет метод, определённый в UDPServer. Установка различных атрибутов также изменяет поведение базового механизма сервера.

ForkingMixIn и перечисленные ниже форking-классы доступны только на платформах 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

Эти классы определены с использованием смешанных классов.

Добавлен в версии 3.12: Классы ForkingUnixStreamServer и ForkingUnixDatagramServer были добавлены.

Для реализации сервиса необходимо создать класс, унаследованный от BaseRequestHandler, и переопределить метод handle(). Затем можно запустить различные версии сервиса, объединив один из классов серверов с классом обработчика запросов. Класс обработчика запросов должен быть другим для запросов datagram или stream. Это можно скрыть, используя подклассы обработчиков StreamRequestHandler или DatagramRequestHandler.

Конечно, вам всё ещё нужно использовать разум. Например, не имеет смысла использовать форking-сервер, если сервис содержит состояние в памяти, которое может изменяться различными запросами, поскольку изменения в дочернем процессе никогда не достигнут начального состояния, хранящегося в родительском процессе и переданного каждому дочернему.

В этом случае можно использовать threading-сервер, но, вероятно, потребуется использовать блокировки для защиты целостности общих данных.

С другой стороны, если вы создаёте HTTP-сервер, где все данные хранятся внешне (например, в файловой системе), синхронный класс в значительной степени сделает сервис «глухим», в то время как обрабатывается один запрос — что может быть очень долго, если клиент медленно получает все запрошенные данные. В этом случае подходит threading- или forking-сервер.

В некоторых случаях может быть уместно обработать часть запроса синхронно, а завершить обработку в дочернем процессе с помощью fork, в зависимости от данных запроса. Это можно реализовать, используя синхронный сервер и выполняя явный fork в методе обработчика запросов handle().

Другой подход к обработке нескольких одновременных запросов в среде, где не поддерживаются ни потоки, ни fork() (или где они слишком дороги или неподходящие для сервиса), — это поддерживать явную таблицу частично завершённых запросов и использовать selectors для принятия решения о следующем запросе для обработки (или о том, стоит ли обрабатывать новый входящий запрос). Это особенно важно для stream-сервисов, где каждый клиент может потенциально оставаться подключённым в течение длительного времени (если потоки или подпроцессы не могут быть использованы).

Объекты сервера

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, и истекло время ожидания, но запросы не были получены. По умолчанию для серверов с форкингом (fork) собирается состояние завершённых дочерних процессов, а для серверов с потоками (threads) этот метод ничего не делает.

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()

Эта функция должна выполнить всю необходимую работу для обработки запроса. По умолчанию ничего не делает. Несколько атрибутов экземпляра доступны для неё; запрос доступен как 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().

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/socketserver.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API