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 mix-in могут быть используемый для поддержки асинхронного поведения.

Создание сервера требует нескольких шагов. Во-первых, необходимо создать класс обработчик запроса путем подкласса класса 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

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

Классы ForkingMixIn и Forking, упомянутые ниже, доступны только на платформах POSIX, поддерживающих fork().

socketserver.ForkingMixIn.server_close() ожидает завершения всех дочерних процессов, за исключением случаев, когда socketserver.ForkingMixIn.block_on_close атрибут имеет значение false.

socketserver.ThreadingMixIn.server_close() ждет до всего недемона полный потоки, кроме того, если socketserver.ThreadingMixIn.block_on_close атрибут ложный. Используйте демонические потоки, установив для 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-сервера, на котором все данные хранятся извне (для сущность, в файловой системе), синхронный класс по существу предоставит «глухой» сервис, в то время как один запрос обрабатывается - который может быть в течение очень долгого времени, если клиент не спешит получать все данные, это просило. Здесь подходит сервер многопоточности или вилки.

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

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

Объекты Server

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(). Этот метод может быть переопределен классами подклассы или mixin для выполнения действий, специфичных для данной службы, таких как действия очистки.

Добавлено в версии 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-кортеж, содержащий объект new сокет, который будет используемый для связи с клиентом, и адрес клиента.

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

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

    Он создается один раз для каждого подключения к серверу и должен переопределять
    метод handle() для реализации связи с клиентом.
    """

    def handle(self):
        # self.request - это TCP - сокет, подключенный к клиенту
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # просто отправьте обратно те же данные, но в верхнем регистре
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Создать серверный биндинг localhost на порту 9999
    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        # Активировать сервер; это будет продолжаться до тех пор, пока вы не прервете
        # программу с помощью Ctrl-C
        server.serve_forever()

Альтернативный класс обработчик запросов, использующий потоки (файловые объекты, упрощающие обмен данными, обеспечивая стандартный файловый интерфейс):

class MyTCPHandler(socketserver.StreamRequestHandler):

    def handle(self):
        # self.rfile - файлообразный объект, созданный обработчик; теперь мы можем
        # использовать, например, readline() вместо необработанных вызовов recv()
        self.data = self.rfile.readline().strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # Аналогично, self.wfile является файловым объектом, используемый для обратной
        # записи клиенту
        self.wfile.write(self.data.upper())

Различие - то, что требование readline() во втором обработчик назовет recv() многократно, пока это не столкнется с newline символ, в то время как единственное требование recv() в первом обработчик будет просто возвращает, что послали от клиента в одном требовании sendall().

Это клиентская сторона:

import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# Создать сокет (SOCK_STREAM означает TCP сокет)
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"))

    # Получение данных с сервера и завершение работы
    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):
    """
    Класс работает аналогично классу TCP обработчика, за исключением того, что
    self.request состоит из пары данных и клиентских сокет, и поскольку соединение
    отсутствует, адрес клиента должен быть задан явно при отправке данных обратно
    через 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 - тип сокета, используемый для сокеты UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Как видно, вызов connect() отсутствует; UDP не имеет соединений. Вместо этого
# данные отправляются непосредственно получателю через 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- сервера.

Асинхронные Mixins

Для создания асинхронных обработчики используйте классы 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__":
    # Порт 0 означает выбор произвольного неиспользуемого порта
    HOST, PORT = "localhost", 0

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    with server:
        ip, port = server.server_address

        # Начать поток с сервера, далее поток запустит еще один поток
        # для каждого запроса
        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().