Автор оригинала: 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, pressto 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, pressto 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