Spec-Zone.ru › Python 2.7

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

Примечание

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

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

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

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

1. Примечания по созданию сервера

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

+------------+
| 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 и упоминаемые ниже форкинговые классы доступны только на платформах POSIX, поддерживающих fork().

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

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

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

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

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

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

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

2. Объекты сервера

class SocketServer.BaseServer(server_address, RequestHandlerClass)
END_OF_DOCUMENT_MARKER

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

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. Объекты обработчиков запросов

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

4. Примеры

4.1. Пример 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
    server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)

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

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

import socket
import sys

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

# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    # Connect to server and send data
    sock.connect((HOST, PORT))
    sock.sendall(data + "\n")

    # Receive data from the server and shut down
    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

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

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

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

4.3. Асинхронные миксины

Для создания асинхронных обработчиков используйте классы 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__":
    # Port 0 means to select an arbitrary unused port
    HOST, PORT = "localhost", 0

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    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()
    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().

© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/2.7/library/socketserver.html

Spec-Zone.ru

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