Spec-Zone.ru › Python 3.8

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

END_OF_DOCUMENT_MARKER
process_request(request, client_address)

Вызывает finish_request() для создания экземпляра RequestHandlerClass. При необходимости эта функция может создать новый процесс или поток для обработки запроса; классы ForkingMixIn и ThreadingMixIn делают это.

server_activate()

Вызывается конструктором сервера для активации сервера. По умолчанию для TCP-сервера вызывается listen() для сокета сервера. Может быть переопределён.

server_bind()

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

verify_request(request, client_address)

Должно вернуть значение Boolean; если значение равно True, запрос будет обработан, а если False, запрос будет отклонен. Эту функцию можно переопределить для реализации контроля доступа к серверу. По умолчанию функция всегда возвращает True.

Изменено в версии 3.6: Добавлена поддержка протокола менеджера контекста. Выход из менеджера контекста эквивалентен вызову server_close().

Объекты обработчиков запросов

class socketserver.BaseRequestHandler

Это базовый класс всех объектов обработчиков запросов. Он определяет интерфейс, представленный ниже. Конкретный подкласс обработчика запросов должен определить новый метод handle() и может переопределить любые другие методы. Для каждого запроса создаётся новый экземпляр подкласса.

setup()

Вызывается перед методом handle() для выполнения любых необходимых действий инициализации. По умолчанию ничего не делает.

handle()

Эта функция должна выполнить всю работу, необходимую для обработки запроса. По умолчанию ничего не делает. Для неё доступны несколько атрибутов экземпляра; запрос доступен как self.request; адрес клиента как self.client_address; и экземпляр сервера как self.server, если ему нужен доступ к информации о сервере.

Тип self.request отличается для дейтаграмных или потоковых сервисов. Для потоковых сервисов self.request — это объект сокета; для дейтаграмных сервисов self.request — это пара строки и сокета.

finish()

Вызывается после метода handle() для выполнения любых необходимых действий по очистке. По умолчанию ничего не делает. Если в setup() возникло исключение, эта функция не будет вызвана.

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–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/library/socketserver.html

Spec-Zone.ru

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