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

Kod źródłowy: Lib/socketserver.py


Модуль socketserver спрощує завдання написання мережевих серверів.

Dostępność: not 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.

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

класс ThreadingUDPServer (ThreadingMixIn, UDPServer): проходить

Клас mix-in стоїть на першому місці, оскільки він замінює метод, визначений у 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, чтобы не ждать завершения потоков.

Zmienione w wersji 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

Ці класи попередньо визначені за допомогою змішаних класів.

Dodane w wersji 3.12: Были добавлены классы ForkingUnixStreamServer и ForkingUnixDatagramServer.

Щоб реалізувати службу, ви повинні отримати клас від BaseRequestHandler і перевизначити його метод handle(). Потім ви можете запустити різні версії служби, поєднавши один із класів сервера з класом обробника запитів. Клас обробника запитів має відрізнятися для датаграм або потокових служб. Це можна приховати за допомогою підкласів обробників StreamRequestHandler або DatagramRequestHandler.

Звичайно, вам все одно доведеться використовувати свою голову! Наприклад, немає сенсу використовувати сервер розгалуження, якщо служба містить стан у пам’яті, який можна змінювати різними запитами, оскільки зміни в дочірньому процесі ніколи не досягнуть початкового стану, який зберігається в батьківському процесі та передається кожному дочірньому. . У цьому випадку ви можете використовувати потоковий сервер, але вам, ймовірно, доведеться використовувати блокування для захисту цілісності спільних даних.

З іншого боку, якщо ви створюєте HTTP-сервер, де всі дані зберігаються зовні (наприклад, у файловій системі), синхронний клас, по суті, надаватиме послугу „глухою”, поки обробляється один запит, що може бути дуже довго, якщо клієнт повільно отримує всі запитувані дані. Тут підійде сервер потоків або розгалуження.

У деяких випадках може бути доречним обробити частину запиту синхронно, але завершити обробку в розгалуженому дочірньому файлі залежно від даних запиту. Це можна реалізувати за допомогою синхронного сервера та виконання явного розгалуження в методі класу обробника запитів handle().

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

Серверні об’єкти

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() для очищення дочірніх процесів-зомбі.

Zmienione w wersji 3.3: Додано виклик service_actions до методу serve_forever.

service_actions()

Це викликається в циклі serve_forever(). Цей метод може бути перевизначений підкласами або класами mixin для виконання дій, специфічних для певної служби, наприклад дій очищення.

Dodane w wersji 3.3.

shutdown()

Скажіть циклу serve_forever() зупинитися та зачекайте, доки він зупиниться. shutdown() потрібно викликати, поки serve_forever() працює в іншому потоці, інакше він блокується.

server_close()

Очистити сервер. Може бути перевизначено.

address_family

Семейство протоколов, которым принадлежит сокет сервера. Общие примеры: const: socket.af_inet,: const:` socket.af_inet6` и: const: socket.af_unix. Подкласс классов TCP или UDP -сервера в этом модуле с атрибутом класса `` address_family = af_inet6`` Установите, если вы хотите классы сервера IPv6.

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

Zmienione w wersji 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.

Zmienione w wersji 3.6: Додано підтримку протоколу context manager. Вихід із контекстного менеджера еквівалентний виклику 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.

Zmienione w wersji 3.6: wfile также поддерживает записываемый интерфейс io.BufferedIOBase.

Przykłady

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

A diferença é que a chamada readline() no segundo manipulador chamará recv() várias vezes até encontrar um caractere de nova linha, enquanto o primeiro manipulador teve que usar um laço recv() para acumular dados até uma nova linha. Se tivesse usado apenas um único recv() sem o laço, ele teria retornado apenas o que foi recebido até agora do cliente. O TCP é baseado em fluxo: os dados chegam na ordem em que foram enviados, mas não há correlação entre as chamadas send() ou sendall() do cliente e o número de chamadas recv() no servidor necessárias para recebê-los.

Це сторона клієнта:

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