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

socketserver.md

408 строк · 32.7 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.4/library/socketserver.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 21.21. [`socketserver`](https://python-all.ru/3.4/library/socketserver.html#module-socketserver) – Фреймворк для сетевых серверов89**Исходный код:** [Lib/socketserver.py](https://python-all.ru/src/3.4/Lib/socketserver.py)1011---1213Модуль [`socketserver`](https://python-all.ru/3.4/library/socketserver.html#module-socketserver) упрощает задачу написания сетевых серверов.1415Существует четыре базовых класса серверов: `TCPServer` использует протокол TCP, который обеспечивает непрерывные потоки данных между клиентом и сервером. `UDPServer` использует дейтаграммы – дискретные пакеты информации, которые могут прийти не по порядку или потеряться при передаче. Реже используемые классы `UnixStreamServer` и `UnixDatagramServer` похожи, но используют сокеты домена Unix; они недоступны на платформах, отличных от Unix. Дополнительные сведения о сетевом программировании можно найти в таких книгах, как UNIX Network Programming У. Ричарда Стивенса или Win32 Network Programming Ральфа Дэвиса.1617Эти четыре класса обрабатывают запросы *синхронно*; каждый запрос должен быть завершен до того, как может начаться следующий. Это не подходит, если каждый запрос требует много времени на выполнение из-за вычислительной сложности или из-за возврата большого объема данных, которые клиент обрабатывает медленно. Решение – создавать отдельный процесс или поток для обработки каждого запроса; миксин-классы `ForkingMixIn` и `ThreadingMixIn` можно использовать для поддержки асинхронного поведения.1819Создание сервера состоит из нескольких шагов. Сначала нужно создать класс-обработчик запросов, унаследовав его от класса `BaseRequestHandler` и переопределив его метод `handle()`; этот метод будет обрабатывать входящие запросы. Затем необходимо создать экземпляр одного из классов сервера, передав ему адрес сервера и класс-обработчик запросов. После этого вызовите метод `handle_request()` или `serve_forever()` объекта сервера, чтобы обработать один или несколько запросов. Наконец, вызовите [`server_close()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.server_close), чтобы закрыть сокет.2021При наследовании от `ThreadingMixIn` для поведения с потоками следует явно указать, как должны вести себя потоки при внезапном завершении. Класс `ThreadingMixIn` определяет атрибут *daemon\_threads*, который указывает, должен ли сервер ждать завершения потоков. Флаг нужно установить явно, если требуется, чтобы потоки работали автономно; по умолчанию [`False`](https://python-all.ru/3.4/library/constants.html#False), то есть Python не завершится, пока не завершатся все потоки, созданные `ThreadingMixIn`.2223Классы серверов имеют одни и те же внешние методы и атрибуты, независимо от того, какой сетевой протокол они используют.2425## 21.21.1. Примечания по созданию сервера2627В иерархии наследования пять классов, четыре из которых представляют синхронные серверы четырёх типов:2829```python30+------------+31| BaseServer |32+------------+33      |34      v35+-----------+        +------------------+36| TCPServer |------->| UnixStreamServer |37+-----------+        +------------------+38      |39      v40+-----------+        +--------------------+41| UDPServer |------->| UnixDatagramServer |42+-----------+        +--------------------+43```4445Обратите внимание, что `UnixDatagramServer` наследует от `UDPServer`, а не от `UnixStreamServer` – единственное различие между IP- и Unix-стрим-сервером заключается в семействе адресов, которое просто повторяется в обоих Unix-классах серверов.4647Версии с порождением процессов и потоками для каждого типа сервера можно создать с помощью миксин-классов `ForkingMixIn` и `ThreadingMixIn`. Например, класс потокового UDP-сервера создаётся следующим образом:4849```python50class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass51```5253Миксин-класс должен стоять первым, поскольку он переопределяет метод, определённый в `UDPServer`. Установка различных атрибутов также изменяет поведение базового механизма сервера.5455Для реализации службы необходимо создать класс, унаследованный от `BaseRequestHandler`, и переопределить его метод `handle()`. Затем можно запускать различные версии службы, комбинируя один из классов сервера с классом-обработчиком запросов. Класс-обработчик запросов должен различаться для дейтаграммных и потоковых служб. Это можно скрыть, используя подклассы обработчиков `StreamRequestHandler` или `DatagramRequestHandler`.5657Конечно, нужно подходить с умом. Например, нет смысла использовать сервер с порождением процессов (forking), если служба хранит состояние в памяти, которое может изменяться разными запросами, потому что изменения в дочернем процессе никогда не попадут в исходное состояние, хранящееся в родительском процессе и передаваемое каждому дочернему. В этом случае можно использовать потоковый сервер (threading), но, скорее всего, придётся использовать блокировки для защиты целостности общих данных.5859С другой стороны, если создаётся HTTP-сервер, где все данные хранятся внешне (например, в файловой системе), синхронный класс фактически сделает службу «глухой» на время обработки одного запроса – что может занять очень много времени, если клиент медленно получает все запрошенные данные. Здесь подойдёт потоковый сервер или сервер с порождением процессов.6061В некоторых случаях может быть целесообразно обработать часть запроса синхронно, но завершить обработку в порождённом дочернем процессе в зависимости от данных запроса. Это можно реализовать, используя синхронный сервер и выполняя явный вызов fork в методе `handle()` класса-обработчика запросов.6263Другой подход к обработке нескольких одновременных запросов в среде, не поддерживающей ни потоки, ни [`fork()`](https://python-all.ru/3.4/library/os.html#os.fork) (или где они слишком дороги или неподходящи для службы), заключается в ведении явной таблицы частично завершённых запросов и использовании [`select()`](https://python-all.ru/3.4/library/select.html#select.select) для принятия решения, над каким запросом работать следующим (или обрабатывать ли новый входящий запрос). Это особенно важно для потоковых служб, где каждый клиент может быть подключён длительное время (если нельзя использовать потоки или подпроцессы). См. [`asyncore`](https://python-all.ru/3.4/library/asyncore.html#module-asyncore) для другого способа управления этим.6465## 21.21.2. Объекты сервера6667#### `class socketserver.BaseServer`6869Это суперкласс всех объектов серверов в модуле. Он определяет интерфейс, приведённый ниже, но не реализует большинство методов – это делается в подклассах.7071#### `BaseServer.fileno()`7273Возвращает целочисленный файловый дескриптор для сокета, на котором сервер прослушивает соединения. Эта функция чаще всего передаётся в [`select.select()`](https://python-all.ru/3.4/library/select.html#select.select), чтобы обеспечить мониторинг нескольких серверов в одном процессе.7475#### `BaseServer.handle_request()`7677Обрабатывает один запрос. Эта функция вызывает следующие методы по порядку: [`get_request()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.get_request), [`verify_request()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.verify_request) и [`process_request()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.process_request). Если предоставленный пользователем метод `handle()` класса-обработчика вызывает исключение, будет вызван метод [`handle_error()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.handle_error) сервера. Если в течение `self.timeout` секунд не получено ни одного запроса, будет вызван [`handle_timeout()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.handle_timeout), и [`handle_request()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.handle_request) вернёт управление.7879#### `BaseServer.serve_forever(poll_interval=0.5)`8081Обрабатывает запросы до тех пор, пока не поступит явный запрос на [`shutdown()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.shutdown). Проверяет необходимость завершения каждые *poll\_interval* секунд. Игнорирует `self.timeout`. Также вызывает [`service_actions()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.service_actions), который может использоваться подклассом или миксином для предоставления действий, специфичных для данной службы. Например, класс `ForkingMixIn` использует [`service_actions()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.service_actions) для очистки зомби-процессов дочерних процессов.8283Изменено в версии 3.3: В метод `serve_forever` добавлен вызов `service_actions`.8485#### `BaseServer.service_actions()`8687Вызывается в цикле [`serve_forever()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.serve_forever). Этот метод может быть переопределён подклассами или миксин-классами для выполнения действий, специфичных для данной службы, например, очистки.8889Новое в версии 3.3.9091#### `BaseServer.shutdown()`9293Даёт команду циклу [`serve_forever()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.serve_forever) остановиться и ожидает его завершения.9495#### `BaseServer.server_close()`9697Выполняет очистку сервера. Может быть переопределен.9899Новое в версии 2.6.100101#### `BaseServer.address_family`102103Семейство протоколов, к которому принадлежит сокет сервера. Типичные примеры: [`socket.AF_INET`](https://python-all.ru/3.4/library/socket.html#socket.AF_INET) и [`socket.AF_UNIX`](https://python-all.ru/3.4/library/socket.html#socket.AF_UNIX).104105#### `BaseServer.RequestHandlerClass`106107Класс обработчика запросов, предоставленный пользователем; экземпляр этого класса создаётся для каждого запроса.108109#### `BaseServer.server_address`110111Адрес, на котором сервер прослушивает соединения. Формат адресов зависит от семейства протоколов; подробнее см. документацию модуля socket. Для интернет-протоколов это кортеж, содержащий строку с адресом и целочисленный номер порта: `('127.0.0.1', 80)`, например.112113#### `BaseServer.socket`114115Объект сокета, через который сервер будет принимать входящие запросы.116117Классы серверов поддерживают следующие переменные класса:118119#### `BaseServer.allow_reuse_address`120121Определяет, разрешает ли сервер повторное использование адреса. По умолчанию [`False`](https://python-all.ru/3.4/library/constants.html#False), и может быть установлен в подклассах для изменения политики.122123#### `BaseServer.request_queue_size`124125Размер очереди запросов. Если обработка одного запроса занимает много времени, все запросы, поступающие во время занятости сервера, помещаются в очередь – до [`request_queue_size`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.request_queue_size) запросов. Когда очередь заполнена, последующие запросы от клиентов получают ошибку «Connection denied». Обычно значение по умолчанию равно 5, но его можно переопределить в подклассах.126127#### `BaseServer.socket_type`128129Тип сокета, используемого сервером; [`socket.SOCK_STREAM`](https://python-all.ru/3.4/library/socket.html#socket.SOCK_STREAM) и [`socket.SOCK_DGRAM`](https://python-all.ru/3.4/library/socket.html#socket.SOCK_DGRAM) – два распространённых значения.130131#### `BaseServer.timeout`132133Длительность тайм-аута в секундах или [`None`](https://python-all.ru/3.4/library/constants.html#None), если тайм-аут не нужен. Если [`handle_request()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.handle_request) не получает входящих запросов в течение тайм-аута, вызывается метод [`handle_timeout()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.handle_timeout).134135Существует несколько методов сервера, которые могут быть переопределены подклассами базовых классов сервера, например `TCPServer`; эти методы не предназначены для внешних пользователей объекта сервера.136137#### `BaseServer.finish_request()`138139Непосредственно обрабатывает запрос, создавая экземпляр [`RequestHandlerClass`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.RequestHandlerClass) и вызывая его метод `handle()`.140141#### `BaseServer.get_request()`142143Должен принять запрос из сокета и вернуть кортеж из двух элементов, содержащий *новый* объект сокета для взаимодействия с клиентом и адрес клиента.144145#### `BaseServer.handle_error(request, client_address)`146147Эта функция вызывается, если метод `handle()` класса [`RequestHandlerClass`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.RequestHandlerClass) вызывает исключение. По умолчанию трассировка выводится в стандартный вывод, и обработка последующих запросов продолжается.148149#### `BaseServer.handle_timeout()`150151Эта функция вызывается, когда атрибут [`timeout`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.timeout) установлен в значение, отличное от [`None`](https://python-all.ru/3.4/library/constants.html#None), и по истечении тайм-аута не было получено ни одного запроса. В серверах с порождением процессов (forking) по умолчанию собирается статус завершившихся дочерних процессов, а в серверах с потоками этот метод ничего не делает.152153#### `BaseServer.process_request(request, client_address)`154155Вызывает [`finish_request()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.finish_request) для создания экземпляра [`RequestHandlerClass`](https://python-all.ru/3.4/library/socketserver.html#socketserver.BaseServer.RequestHandlerClass). При необходимости эта функция может создать новый процесс или поток для обработки запроса; классы `ForkingMixIn` и `ThreadingMixIn` делают это.156157#### `BaseServer.server_activate()`158159Вызывается конструктором сервера для его активации. По умолчанию просто вызывает `listen()` на сокете сервера. Может быть переопределён.160161#### `BaseServer.server_bind()`162163Вызывается конструктором сервера для привязки сокета к нужному адресу. Можно переопределить.164165#### `BaseServer.verify_request(request, client_address)`166167Должен возвращать логическое значение; если [`True`](https://python-all.ru/3.4/library/constants.html#True), запрос будет обработан, а если [`False`](https://python-all.ru/3.4/library/constants.html#False), запрос будет отклонён. Эту функцию можно переопределить для реализации контроля доступа к серверу. Реализация по умолчанию всегда возвращает [`True`](https://python-all.ru/3.4/library/constants.html#True).168169## 21.21.3. Объекты RequestHandler170171Класс обработчика запросов должен определять новый метод `handle()` и может переопределять любые из следующих методов. Для каждого запроса создаётся новый экземпляр.172173#### `RequestHandler.finish()`174175Вызывается после метода [`handle()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.RequestHandler.handle) для выполнения необходимых действий по очистке. Реализация по умолчанию ничего не делает. Если [`setup()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.RequestHandler.setup) вызывает исключение, эта функция не будет вызвана.176177#### `RequestHandler.handle()`178179Эта функция должна выполнять всю работу по обслуживанию запроса. Реализация по умолчанию ничего не делает. Для неё доступны несколько атрибутов экземпляра: запрос доступен как `self.request`; адрес клиента как `self.client_address`; и экземпляр сервера как `self.server`, на случай, если потребуется доступ к информации, специфичной для сервера.180181Тип `self.request` различается для датаграммных и потоковых служб. Для потоковых служб `self.request` – это объект сокета; для датаграммных – `self.request` представляет собой пару из строки и сокета. Однако это можно скрыть, используя подклассы обработчика запросов `StreamRequestHandler` или `DatagramRequestHandler`, которые переопределяют методы [`setup()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.RequestHandler.setup) и [`finish()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.RequestHandler.finish) и предоставляют атрибуты `self.rfile` и `self.wfile`. `self.rfile` и `self.wfile` можно читать или записывать соответственно для получения данных запроса или возврата данных клиенту.182183#### `RequestHandler.setup()`184185Вызывается перед методом [`handle()`](https://python-all.ru/3.4/library/socketserver.html#socketserver.RequestHandler.handle) для выполнения необходимых действий по инициализации. Реализация по умолчанию ничего не делает.186187## 21.21.4. Примеры188189### 21.21.4.1. `socketserver.TCPServer` Пример190191Это серверная сторона:192193```python194import socketserver195196class MyTCPHandler(socketserver.BaseRequestHandler):197    """198    Класс RequestHandler для нашего сервера.199200    Он создаётся один раз на каждое подключение к серверу и должен201    переопределять метод handle() для реализации взаимодействия с202    клиентом.203    """204205    def handle(self):206        # self.request – это TCP-сокет, подключённый к клиенту207        self.data = self.request.recv(1024).strip()208        print("{} wrote:".format(self.client_address[0]))209        print(self.data)210        # просто отправляет обратно те же данные, но в верхнем регистре211        self.request.sendall(self.data.upper())212213if __name__ == "__main__":214    HOST, PORT = "localhost", 9999215216    # Создаём сервер, привязываясь к localhost на порту 9999217    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)218219    # Запускаем сервер; он будет работать, пока вы220    # не прервёте программу с помощью Ctrl-C221    server.serve_forever()222```223224Альтернативный класс обработчика запросов, который использует потоки (объекты, подобные файлам, упрощающие взаимодействие за счет предоставления стандартного файлового интерфейса):225226```python227class MyTCPHandler(socketserver.StreamRequestHandler):228229    def handle(self):230        # self.rfile – это файлоподобный объект, созданный обработчиком;231        # теперь можно использовать, например, readline() вместо прямых вызовов recv()232        self.data = self.rfile.readline().strip()233        print("{} wrote:".format(self.client_address[0]))234        print(self.data)235        # Аналогично, self.wfile – это файлоподобный объект, используемый для записи ответа236        # клиенту237        self.wfile.write(self.data.upper())238```239240Разница в том, что вызов `readline()` во втором обработчике вызывает `recv()` несколько раз, пока не встретит символ новой строки, тогда как одиночный вызов `recv()` в первом обработчике просто возвращает то, что было отправлено от клиента за один вызов `sendall()`.241242Это клиентская сторона:243244```python245import socket246import sys247248HOST, PORT = "localhost", 9999249data = " ".join(sys.argv[1:])250251# Создать сокет (SOCK_STREAM означает TCP-сокет)252sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)253254try:255    # Подключиться к серверу и отправить данные256    sock.connect((HOST, PORT))257    sock.sendall(bytes(data + "\n", "utf-8"))258259    # Получить данные от сервера и завершить работу260    received = str(sock.recv(1024), "utf-8")261finally:262    sock.close()263264print("Sent:     {}".format(data))265print("Received: {}".format(received))266```267268Результат работы примера должен выглядеть примерно так:269270Сервер:271272```python273$ python TCPServer.py274127.0.0.1 wrote:275b'hello world with TCP'276127.0.0.1 wrote:277b'python is nice'278```279280Клиент:281282```python283$ python TCPClient.py hello world with TCP284Sent:     hello world with TCP285Received: HELLO WORLD WITH TCP286$ python TCPClient.py python is nice287Sent:     python is nice288Received: PYTHON IS NICE289```290291### 21.21.4.2. `socketserver.UDPServer` Пример292293Это серверная сторона:294295```python296import socketserver297298class MyUDPHandler(socketserver.BaseRequestHandler):299    """300    Этот класс работает аналогично классу-обработчику TCP, за исключением того, что301    self.request состоит из пары данных и клиентского сокета, и поскольку302    соединение отсутствует, адрес клиента должен быть указан явно303    при отправке данных обратно через sendto().304    """305306    def handle(self):307        data = self.request[0].strip()308        socket = self.request[1]309        print("{} wrote:".format(self.client_address[0]))310        print(data)311        socket.sendto(data.upper(), self.client_address)312313if __name__ == "__main__":314    HOST, PORT = "localhost", 9999315    server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)316    server.serve_forever()317```318319Это клиентская сторона:320321```python322import socket323import sys324325HOST, PORT = "localhost", 9999326data = " ".join(sys.argv[1:])327328# SOCK_DGRAM – это тип сокета для UDP-сокетов329sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)330331# Как можно видеть, вызов connect() отсутствует; в UDP нет соединений.332# Вместо этого данные отправляются напрямую получателю через sendto().333sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))334received = str(sock.recv(1024), "utf-8")335336print("Sent:     {}".format(data))337print("Received: {}".format(received))338```339340Результат работы примера должен выглядеть точно так же, как для примера TCP-сервера.341342### 21.21.4.3. Асинхронные миксины343344Для создания асинхронных обработчиков используйте классы `ThreadingMixIn` и `ForkingMixIn`.345346Пример для класса `ThreadingMixIn`:347348```python349import socket350import threading351import socketserver352353class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):354355    def handle(self):356        data = str(self.request.recv(1024), 'ascii')357        cur_thread = threading.current_thread()358        response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')359        self.request.sendall(response)360361class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):362    pass363364def client(ip, port, message):365    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)366    sock.connect((ip, port))367    try:368        sock.sendall(bytes(message, 'ascii'))369        response = str(sock.recv(1024), 'ascii')370        print("Received: {}".format(response))371    finally:372        sock.close()373374if __name__ == "__main__":375    # Порт 0 означает выбор произвольного неиспользуемого порта376    HOST, PORT = "localhost", 0377378    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)379    ip, port = server.server_address380381    # Запустить поток для сервера – этот поток затем запустит один382    # дополнительный поток на каждый запрос383    server_thread = threading.Thread(target=server.serve_forever)384    # Завершить поток сервера при завершении главного потока385    server_thread.daemon = True386    server_thread.start()387    print("Server loop running in thread:", server_thread.name)388389    client(ip, port, "Hello World 1")390    client(ip, port, "Hello World 2")391    client(ip, port, "Hello World 3")392393    server.shutdown()394    server.server_close()395```396397Результат работы примера должен выглядеть примерно так:398399```python400$ python ThreadedTCPServer.py401Server loop running in thread: Thread-1402Received: Thread-2: Hello World 1403Received: Thread-3: Hello World 2404Received: Thread-4: Hello World 3405```406407Класс `ForkingMixIn` используется так же, за исключением того, что сервер будет создавать новый процесс для каждого запроса.408