Документация Python неофициальный перевод
Содержание страницы

20.17. SocketServer – Фреймворк для сетевых серверовSocketServer – A framework for network servers

Примечание

Модуль SocketServer в Python 3 переименован в socketserver. Инструмент 2to3 при преобразовании исходного кода в Python 3 автоматически адаптирует импорты.

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


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

Существует четыре базовых конкретных класса серверов:

class SocketServer.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)

Использует протокол TCP (Internet), который обеспечивает непрерывные потоки данных между клиентом и сервером. Если 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(); этот метод будет обрабатывать входящие запросы. Во-вторых, нужно создать экземпляр одного из серверных классов, передав ему адрес сервера и класс-обработчик запросов. Затем вызовите метод handle_request() или serve_forever() объекта сервера для обработки одного или нескольких запросов. Наконец, вызовите server_close(), чтобы закрыть сокет.

При наследовании от ThreadingMixIn для многопоточного поведения соединений следует явно указать, как потоки должны вести себя при внезапном завершении. Класс ThreadingMixIn определяет атрибут daemon_threads, который указывает, должен ли сервер ожидать завершения потоков. Если требуется, чтобы потоки работали автономно, этот флаг нужно установить явно; по умолчанию False, то есть Python не завершится, пока не завершатся все потоки, созданные ThreadingMixIn.

Классы серверов имеют одни и те же внешние методы и атрибуты, независимо от того, какой сетевой протокол они используют.

20.17.1. Заметки о создании сервераServer Creation Notes

В иерархии наследования пять классов, четыре из которых представляют синхронные серверы четырёх типов:

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

class SocketServer.ForkingTCPServer
class SocketServer.ForkingUDPServer
class SocketServer.ThreadingTCPServer
class SocketServer.ThreadingUDPServer

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

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

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

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

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

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

20.17.2. Объекты сервераServer Objects

class SocketServer.BaseServer(server_address, RequestHandlerClass)

Это суперкласс для всех объектов Server в модуле. Он определяет интерфейс, приведенный ниже, но не реализует большинство методов – это делается в подклассах. Два параметра хранятся в соответствующих атрибутах server_address и RequestHandlerClass.

fileno()

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

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. Если нужно выполнять периодические задачи, делайте это в другом потоке.

shutdown()

Дайте команду serve_forever() циклу остановиться и ждать завершения.

Новое в версии 2.6.

server_close()

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

Новое в версии 2.6.

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 запросов. Когда очередь заполняется, дальнейшие запросы от клиентов получают ошибку «Connection denied». Обычно значение по умолчанию – 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 вызывает исключение. Действие по умолчанию – вывод трассировки в стандартный вывод и продолжение обработки последующих запросов.

handle_timeout()

Эта функция вызывается, когда атрибут timeout установлен в значение, отличное от None, и период тайм-аута истёк без получения запросов. По умолчанию для серверов с порождением процессов (forking) собирается статус завершившихся дочерних процессов; в многопоточных серверах этот метод ничего не делает.

process_request(request, client_address)

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

server_activate()

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

server_bind()

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

verify_request(request, client_address)

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

20.17.3. Объекты обработчиков запросовRequest Handler Objects

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

20.17.4. ПримерыExamples

20.17.4.1. SocketServer.TCPServer ПримерSocketServer.TCPServer Example

Это серверная сторона:

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
    server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)

    # Запускаем сервер; он будет работать, пока вы
    # не прервёте программу с помощью 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() несколько раз, пока не встретит символ новой строки, в то время как одиночный вызов recv() в первом обработчике просто вернет то, что было отправлено клиентом за один вызов sendall().

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

import socket
import sys

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

# Создать сокет (SOCK_STREAM означает TCP-сокет)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    # Подключиться к серверу и отправить данные
    sock.connect((HOST, PORT))
    sock.sendall(data + "\n")

    # Получить данные от сервера и завершить работу
    received = sock.recv(1024)
finally:
    sock.close()

print "Sent:     {}".format(data)
print "Received: {}".format(received)

Результат работы примера должен выглядеть примерно так:

Сервер:

$ python TCPServer.py
127.0.0.1 wrote:
hello world with TCP
127.0.0.1 wrote:
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

20.17.4.2. SocketServer.UDPServer ПримерSocketServer.UDPServer Example

Это серверная сторона:

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
    server = SocketServer.UDPServer((HOST, PORT), MyUDPHandler)
    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(data + "\n", (HOST, PORT))
received = sock.recv(1024)

print "Sent:     {}".format(data)
print "Received: {}".format(received)

Результат работы примера должен выглядеть точно так же, как для примера TCP-сервера.

20.17.4.3. Асинхронные примесиAsynchronous Mixins

Для построения асинхронных обработчиков используйте классы ThreadingMixIn и ForkingMixIn.

Пример для класса ThreadingMixIn:

import socket
import threading
import SocketServer

class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):

    def handle(self):
        data = self.request.recv(1024)
        cur_thread = threading.current_thread()
        response = "{}: {}".format(cur_thread.name, data)
        self.request.sendall(response)

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

def client(ip, port, message):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((ip, port))
    try:
        sock.sendall(message)
        response = sock.recv(1024)
        print "Received: {}".format(response)
    finally:
        sock.close()

if __name__ == "__main__":
    # Порт 0 означает выбор произвольного неиспользуемого порта
    HOST, PORT = "localhost", 0

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

    # Запустить поток для сервера – этот поток затем запустит один
    # дополнительный поток на каждый запрос
    server_thread = threading.Thread(target=server.serve_forever)
    # Завершить поток сервера при завершении главного потока
    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()
    server.server_close()

Результат работы примера должен выглядеть примерно так:

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