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

С помощью этих классов примесей можно создавать версии серверов каждого типа, использующие разветвление процессов или потоки. Например, класс ThreadingUDPServer создаётся следующим образом:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

Класс примеси указывается первым, поскольку он переопределяет метод, определённый в 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

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

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

Чтобы реализовать службу, необходимо создать класс-наследник BaseRequestHandler и переопределить его метод handle(). Затем можно запускать различные варианты службы, комбинируя один из классов серверов с собственным классом обработчика запросов. Для служб, использующих датаграммы и потоки, нужны разные классы обработчиков запросов. Это различие можно скрыть, используя подклассы обработчиков StreamRequestHandler или DatagramRequestHandler.

Разумеется, не забывайте руководствоваться здравым смыслом! Например, нет смысла использовать сервер с разветвлением процессов, если служба хранит в памяти состояние, которое могут изменять разные запросы: изменения в дочернем процессе никогда не попадут в исходное состояние, хранящееся в родительском процессе и передаваемое каждому дочернему процессу. В этом случае можно использовать сервер с потоками, но, вероятно, придётся применять блокировки, чтобы защитить целостность общих данных.

С другой стороны, если вы создаёте 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(). Подклассы или классы примесей могут переопределить этот метод для выполнения действий, относящихся к конкретной службе, например действий по очистке.

Добавлено в версии 3.3.

shutdown()

Указывает циклу serve_forever() остановиться и ожидает его остановки. Метод shutdown() необходимо вызывать, пока serve_forever() выполняется в другом потоке; в противном случае возникнет взаимная блокировка.

server_close()

Выполняет очистку сервера. Может быть переопределён.

address_family

Семейство протоколов, к которому относится сокет сервера. Распространённые примеры: socket.AF_INET, socket.AF_INET6 и socket.AF_UNIX. Чтобы создать классы серверов IPv6, унаследуйте классы TCP-сервера или UDP-сервера из этого модуля и задайте для атрибута класса address_family = AF_INET6 соответствующее значение.

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

Должен принимать запрос от сокета и возвращать кортеж из двух элементов: нового объекта сокета для связи с клиентом и адреса клиента.

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: Добавлена поддержка протокола менеджера контекста. Выход из менеджера контекста эквивалентен вызову 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
        pieces = [b'']
        total = 0
        while b'\n' not in pieces[-1] and total < 10_000:
            pieces.append(self.request.recv(2000))
            total += len(pieces[-1])
        self.data = b''.join(pieces)
        print(f"Received from {self.client_address[0]}:")
        print(self.data.decode("utf-8"))
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())
        # after we return, the socket will be closed.

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.
        # We limit ourselves to 10000 bytes to avoid abuse by the sender.
        self.data = self.rfile.readline(10000).rstrip()
        print(f"{self.client_address[0]} wrote:")
        print(self.data.decode("utf-8"))
        # Likewise, self.wfile is a file-like object used to write back
        # to the client
        self.wfile.write(self.data.upper())

Разница в том, что вызов readline() во втором обработчике будет вызывать recv() несколько раз, пока не встретит символ новой строки, тогда как первый обработчик должен был использовать цикл recv(), чтобы накапливать данные до появления символа новой строки. Если бы он использовал только один вызов recv() без цикла, то вернул бы лишь те данные, которые на тот момент получил от клиента. TCP основан на потоковой передаче: данные поступают в том же порядке, в котором были отправлены, но между вызовами send() или sendall() на клиенте и количеством вызовов recv(), необходимых серверу для получения данных, нет никакой взаимосвязи.

Клиентская часть:

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, "utf-8"))
    sock.sendall(b"\n")

    # Receive data from the server and shut down
    received = str(sock.recv(1024), "utf-8")

print("Sent:    ", data)
print("Received:", 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(f"{self.client_address[0]} wrote:")
        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:    ", data)
print("Received:", 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