Рубрики
Без рубрики

socketserver – Создание сетевых серверов

Автор оригинала: Doug Hellmann.

Цель:

Создание сетевых серверов.

Модуль socketserver – это платформа для создания сетевых серверов. Он определяет классы для обработки синхронных сетевых запросов (обработчик запросов сервера блокируется до тех пор, пока запрос не будет завершен) через TCP, UDP, потоки Unix и дейтаграммы Unix. Он также предоставляет смешанные классы для простого преобразования серверов для использования отдельного потока или процесса для каждого запроса.

Ответственность за обработку запроса разделена между классом сервера и классом обработчика запросов. Сервер занимается проблемами связи, такими как прослушивание сокета и прием соединений, а обработчик запросов занимается проблемами «протокола», такими как интерпретация входящих данных, их обработка и отправка данных обратно клиенту. Такое разделение ответственности означает, что многие приложения могут использовать один из существующих классов серверов без каких-либо изменений и предоставлять класс обработчика запросов для его работы с настраиваемым протоколом.

Типы серверов

В socketserver определены пять различных классов серверов. BaseServer определяет API и не предназначен для создания и использования напрямую. TCPServer использует для связи сокеты TCP/IP. UDPServer использует сокеты датаграмм. UnixStreamServer и UnixDatagramServer используют сокеты домена Unix и доступны только на платформах Unix.

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

Чтобы создать сервер, передайте ему адрес для прослушивания запросов и обработчик запросов класс (не экземпляр). Формат адреса зависит от типа сервера и используемого семейства сокетов. Подробную информацию см. В документации по модулю сокета.

После создания экземпляра серверного объекта используйте для обработки запросов либо handle_request () , либо serve_forever () . Метод serve_forever () вызывает handle_request () в бесконечном цикле, но если приложению необходимо интегрировать сервер с другим циклом событий или использовать select () для мониторинга нескольких сокетов для разных серверов, он может напрямую вызывать handle_request () .

Реализация сервера

При создании сервера обычно достаточно повторно использовать один из существующих классов и предоставить собственный класс обработчика запросов. Для других случаев BaseServer включает несколько методов, которые можно переопределить в подклассе.

  • verify_request (request, client_address) : вернуть True для обработки запроса или False для его игнорирования. Например, сервер может отклонять запросы из диапазона IP-адресов или если он перегружен.
  • process_request (request, client_address) : вызывает finish_request () для фактического выполнения работы по обработке запроса. Он также может создать отдельный поток или процесс, как это делают смешанные классы.
  • finish_request (request, client_address) : создает экземпляр обработчика запросов, используя класс, переданный конструктору сервера. Вызывает handle () в обработчике запроса для обработки запроса.

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

Обработчики запросов выполняют большую часть работы по получению входящих запросов и принятию решения о том, какие действия предпринять. Обработчик отвечает за реализацию протокола поверх уровня сокетов (например, HTTP, XML-RPC или AMQP). Обработчик запросов считывает запрос из входящего канала данных, обрабатывает его и записывает ответ обратно. Доступны три метода переопределения.

  • setup () : подготавливает обработчик запроса к запросу. В StreamRequestHandler метод setup () создает файловые объекты для чтения и записи в сокет.
  • handle () : выполняет реальную работу по запросу. Разберите входящий запрос, обработайте данные и отправьте ответ.
  • finish () : очищает все, что было создано во время setup () .

Многие обработчики могут быть реализованы только с помощью метода handle () .

Пример эха

В этом примере реализуется простая пара сервер/обработчик запросов, которая принимает TCP-соединения и возвращает любые данные, отправленные клиентом. Он начинается с обработчика запросов.

socketserver_echo.py

import logging
import sys
import socketserver

logging.basicConfig(levellogging.DEBUG,
                    format'%(name)s: %(message)s',
                    )


class EchoRequestHandler(socketserver.BaseRequestHandler):

    def __init__(self, request, client_address, server):
        self.logger  logging.getLogger('EchoRequestHandler')
        self.logger.debug('__init__')
        socketserver.BaseRequestHandler.__init__(self, request,
                                                 client_address,
                                                 server)
        return

    def setup(self):
        self.logger.debug('setup')
        return socketserver.BaseRequestHandler.setup(self)

    def handle(self):
        self.logger.debug('handle')

        # Echo the back to the client
        data  self.request.recv(1024)
        self.logger.debug('recv()->"%s"', data)
        self.request.send(data)
        return

    def finish(self):
        self.logger.debug('finish')
        return socketserver.BaseRequestHandler.finish(self)

Единственный метод, который действительно необходимо реализовать, – это EchoRequestHandler.handle () , но включены версии всех методов, описанных ранее, чтобы проиллюстрировать последовательность выполненных вызовов. Класс EchoServer ничем не отличается от TCPServer , кроме журнала при вызове каждого метода.

class EchoServer(socketserver.TCPServer):

    def __init__(self, server_address,
                 handler_classEchoRequestHandler,
                 ):
        self.logger  logging.getLogger('EchoServer')
        self.logger.debug('__init__')
        socketserver.TCPServer.__init__(self, server_address,
                                        handler_class)
        return

    def server_activate(self):
        self.logger.debug('server_activate')
        socketserver.TCPServer.server_activate(self)
        return

    def serve_forever(self, poll_interval0.5):
        self.logger.debug('waiting for request')
        self.logger.info(
            'Handling requests, press  to quit'
        )
        socketserver.TCPServer.serve_forever(self, poll_interval)
        return

    def handle_request(self):
        self.logger.debug('handle_request')
        return socketserver.TCPServer.handle_request(self)

    def verify_request(self, request, client_address):
        self.logger.debug('verify_request(%s, %s)',
                          request, client_address)
        return socketserver.TCPServer.verify_request(
            self, request, client_address,
        )

    def process_request(self, request, client_address):
        self.logger.debug('process_request(%s, %s)',
                          request, client_address)
        return socketserver.TCPServer.process_request(
            self, request, client_address,
        )

    def server_close(self):
        self.logger.debug('server_close')
        return socketserver.TCPServer.server_close(self)

    def finish_request(self, request, client_address):
        self.logger.debug('finish_request(%s, %s)',
                          request, client_address)
        return socketserver.TCPServer.finish_request(
            self, request, client_address,
        )

    def close_request(self, request_address):
        self.logger.debug('close_request(%s)', request_address)
        return socketserver.TCPServer.close_request(
            self, request_address,
        )

    def shutdown(self):
        self.logger.debug('shutdown()')
        return socketserver.TCPServer.shutdown(self)

Последний шаг – добавить основную программу, которая настраивает сервер для работы в потоке и отправляет ему данные, чтобы проиллюстрировать, какие методы вызываются, когда данные возвращаются эхом.

if __name__  '__main__':
    import socket
    import threading

    address  ('localhost', 0)  # let the kernel assign a port
    server  EchoServer(address, EchoRequestHandler)
    ip, port  server.server_address  # what port was assigned?

    # Start the server in a thread
    t  threading.Thread(targetserver.serve_forever)
    t.setDaemon(True)  # don't hang on exit
    t.start()

    logger  logging.getLogger('client')
    logger.info('Server on %s:%s', ip, port)

    # Connect to the server
    logger.debug('creating socket')
    s  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    logger.debug('connecting to server')
    s.connect((ip, port))

    # Send the data
    message  'Hello, world'.encode()
    logger.debug('sending data: %r', message)
    len_sent  s.send(message)

    # Receive a response
    logger.debug('waiting for response')
    response  s.recv(len_sent)
    logger.debug('response from server: %r', response)

    # Clean up
    server.shutdown()
    logger.debug('closing socket')
    s.close()
    logger.debug('done')
    server.socket.close()

Запуск программы дает следующий результат.

$ python3 socketserver_echo.py

EchoServer: __init__
EchoServer: server_activate
EchoServer: waiting for request
EchoServer: Handling requests, press  to quit
client: Server on 127.0.0.1:55484
client: creating socket
client: connecting to server
client: sending data: b'Hello, world'
EchoServer: verify_request(, ('127.0.0.1', 55485))
EchoServer: process_request(, ('127.0.0.1', 55485))
EchoServer: finish_request(, ('127.0.0.1', 55485))
EchoRequestHandler: __init__
EchoRequestHandler: setup
EchoRequestHandler: handle
client: waiting for response
EchoRequestHandler: recv()->"b'Hello, world'"
EchoRequestHandler: finish
client: response from server: b'Hello, world'
EchoServer: shutdown()
EchoServer: close_request()
client: closing socket
client: done

Примечание

Используемый номер порта будет меняться каждый раз при запуске программы, поскольку ядро автоматически выделяет доступный порт. Чтобы сервер каждый раз прослушивал определенный порт, укажите этот номер в адресном кортеже вместо 0 .

Вот сокращенная версия того же сервера без вызовов регистрации. Необходимо предоставить только метод handle () в классе обработчика запросов.

socketserver_echo_simple.py

import socketserver


class EchoRequestHandler(socketserver.BaseRequestHandler):

    def handle(self):
        # Echo the back to the client
        data  self.request.recv(1024)
        self.request.send(data)
        return


if __name__  '__main__':
    import socket
    import threading

    address  ('localhost', 0)  # let the kernel assign a port
    server  socketserver.TCPServer(address, EchoRequestHandler)
    ip, port  server.server_address  # what port was assigned?

    t  threading.Thread(targetserver.serve_forever)
    t.setDaemon(True)  # don't hang on exit
    t.start()

    # Connect to the server
    s  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip, port))

    # Send the data
    message  'Hello, world'.encode()
    print('Sending : {!r}'.format(message))
    len_sent  s.send(message)

    # Receive a response
    response  s.recv(len_sent)
    print('Received: {!r}'.format(response))

    # Clean up
    server.shutdown()
    s.close()
    server.socket.close()

В этом случае никакого специального класса сервера не требуется, поскольку TCPServer обрабатывает все требования сервера.

$ python3 socketserver_echo_simple.py

Sending : b'Hello, world'
Received: b'Hello, world'

Заправка и разветвление

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

Для потоков используйте ThreadingMixIn .

socketserver_threaded.py

import threading
import socketserver


class ThreadedEchoRequestHandler(
        socketserver.BaseRequestHandler,
):

    def handle(self):
        # Echo the back to the client
        data  self.request.recv(1024)
        cur_thread  threading.currentThread()
        response  b'%s: %s' % (cur_thread.getName().encode(),
                                data)
        self.request.send(response)
        return


class ThreadedEchoServer(socketserver.ThreadingMixIn,
                         socketserver.TCPServer,
                         ):
    pass


if __name__  '__main__':
    import socket

    address  ('localhost', 0)  # let the kernel assign a port
    server  ThreadedEchoServer(address,
                                ThreadedEchoRequestHandler)
    ip, port  server.server_address  # what port was assigned?

    t  threading.Thread(targetserver.serve_forever)
    t.setDaemon(True)  # don't hang on exit
    t.start()
    print('Server loop running in thread:', t.getName())

    # Connect to the server
    s  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip, port))

    # Send the data
    message  b'Hello, world'
    print('Sending : {!r}'.format(message))
    len_sent  s.send(message)

    # Receive a response
    response  s.recv(1024)
    print('Received: {!r}'.format(response))

    # Clean up
    server.shutdown()
    s.close()
    server.socket.close()

Ответ от этого многопоточного сервера включает идентификатор потока, в котором обрабатывается запрос.

$ python3 socketserver_threaded.py

Server loop running in thread: Thread-1
Sending : b'Hello, world'
Received: b'Thread-2: Hello, world'

Для отдельных процессов используйте ForkingMixIn .

socketserver_forking.py

import os
import socketserver


class ForkingEchoRequestHandler(socketserver.BaseRequestHandler):

    def handle(self):
        # Echo the back to the client
        data  self.request.recv(1024)
        cur_pid  os.getpid()
        response  b'%d: %s' % (cur_pid, data)
        self.request.send(response)
        return


class ForkingEchoServer(socketserver.ForkingMixIn,
                        socketserver.TCPServer,
                        ):
    pass


if __name__  '__main__':
    import socket
    import threading

    address  ('localhost', 0)  # let the kernel assign a port
    server  ForkingEchoServer(address,
                               ForkingEchoRequestHandler)
    ip, port  server.server_address  # what port was assigned?

    t  threading.Thread(targetserver.serve_forever)
    t.setDaemon(True)  # don't hang on exit
    t.start()
    print('Server loop running in process:', os.getpid())

    # Connect to the server
    s  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip, port))

    # Send the data
    message  'Hello, world'.encode()
    print('Sending : {!r}'.format(message))
    len_sent  s.send(message)

    # Receive a response
    response  s.recv(1024)
    print('Received: {!r}'.format(response))

    # Clean up
    server.shutdown()
    s.close()
    server.socket.close()

В этом случае идентификатор дочернего процесса включается в ответ сервера:

$ python3 socketserver_forking.py

Server loop running in process: 22599
Sending : b'Hello, world'
Received: b'22600: Hello, world'

Смотрите также

  • стандартная библиотечная документация для сервера сокетов
  • socket – низкоуровневое сетевое взаимодействие
  • select – Низкоуровневые инструменты асинхронного ввода-вывода
  • asyncio – инструменты асинхронного ввода-вывода, цикла событий и параллелизма
  • SimpleXMLRPCServer – сервер XML-RPC, созданный с использованием socketserver .
  • Сетевое программирование Unix, Том 1: Сетевой API сокетов, 3/E У. Ричард Стивенс, Билл Феннер и Эндрю М. Рудофф. Опубликовано Addison-Wesley Professional, 2004. ISBN-10: 0131411551
  • Основы сетевого программирования Python, 3/E Авторы Брэндон Родс и Джон Герцен. Опубликовано Apress, 2014. ISBN-10: 1430258543