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

socketserver.md

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

1> **Источник:** https://python-all.ru/3.2/library/socketserver.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 20.19. [`socketserver`](https://python-all.ru/3.2/library/socketserver.html#module-socketserver) – Фреймворк для сетевых серверов89**Исходный код:** [Lib/socketserver.py](https://python-all.ru/src/3.2/Lib/socketserver.py)1011---1213Модуль [`socketserver`](https://python-all.ru/3.2/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()` объекта сервера для обработки одного или нескольких запросов.2021При наследовании от `ThreadingMixIn` для поведения с потоками следует явно указать, как должны вести себя потоки при внезапном завершении. Класс `ThreadingMixIn` определяет атрибут *daemon\_threads*, который указывает, должен ли сервер ждать завершения потоков. Флаг нужно установить явно, если требуется, чтобы потоки работали автономно; по умолчанию [`False`](https://python-all.ru/3.2/library/constants.html#False), то есть Python не завершится, пока не завершатся все потоки, созданные `ThreadingMixIn`.2223Классы серверов имеют одни и те же внешние методы и атрибуты, независимо от того, какой сетевой протокол они используют.2425## 20.19.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()` (или если эти механизмы слишком дороги или не подходят для данного сервиса), заключается в ведении явной таблицы частично обработанных запросов и использовании [`select()`](https://python-all.ru/3.2/library/select.html#module-select) для определения, какой запрос обрабатывать следующим (или стоит ли принять новый входящий запрос). Особенно это важно для потоковых сервисов, где каждый клиент может быть подключён в течение длительного времени (если потоки или подпроцессы использовать нельзя). См. [`asyncore`](https://python-all.ru/3.2/library/asyncore.html#module-asyncore) как ещё один способ управления этим.6465## 20.19.2. Объекты сервера6667#### `class socketserver.BaseServer`6869Это суперкласс всех объектов серверов в модуле. Он определяет интерфейс, приведённый ниже, но не реализует большинство методов – это делается в подклассах.7071#### `BaseServer.fileno()`7273Возвращает целочисленный файловый дескриптор для сокета, на котором сервер прослушивает соединения. Эта функция чаще всего передаётся в [`select.select()`](https://python-all.ru/3.2/library/select.html#select.select), чтобы обеспечить мониторинг нескольких серверов в одном процессе.7475#### `BaseServer.handle_request()`7677Обрабатывает один запрос. Эта функция вызывает следующие методы по порядку: [`get_request()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.get_request), [`verify_request()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.verify_request) и [`process_request()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.process_request). Если предоставленный пользователем метод `handle()` класса-обработчика вызывает исключение, будет вызван метод [`handle_error()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.handle_error) сервера. Если в течение `self.timeout` секунд не получено ни одного запроса, будет вызван [`handle_timeout()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.handle_timeout), и [`handle_request()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.handle_request) вернёт управление.7879#### `BaseServer.serve_forever(poll_interval=0.5)`8081Обрабатывает запросы до тех пор, пока не поступит явный запрос [`shutdown()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.shutdown). Опрашивает запрос на завершение каждые *poll\_interval* секунд. Игнорирует `self.timeout`. Если нужно выполнять периодические задачи, выполняйте их в другом потоке.8283#### `BaseServer.shutdown()`8485Даёт команду циклу [`serve_forever()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.serve_forever) остановиться и ожидает его завершения.8687#### `BaseServer.address_family`8889Семейство протоколов, к которому принадлежит сокет сервера. Типичные примеры: [`socket.AF_INET`](https://python-all.ru/3.2/library/socket.html#socket.AF_INET) и [`socket.AF_UNIX`](https://python-all.ru/3.2/library/socket.html#socket.AF_UNIX).9091#### `BaseServer.RequestHandlerClass`9293Класс обработчика запросов, предоставленный пользователем; экземпляр этого класса создаётся для каждого запроса.9495#### `BaseServer.server_address`9697Адрес, на котором сервер прослушивает соединения. Формат адресов зависит от семейства протоколов; подробнее см. документацию модуля socket. Для интернет-протоколов это кортеж, содержащий строку с адресом и целочисленный номер порта: `('127.0.0.1', 80)`, например.9899#### `BaseServer.socket`100101Объект сокета, через который сервер будет принимать входящие запросы.102103Классы серверов поддерживают следующие переменные класса:104105#### `BaseServer.allow_reuse_address`106107Определяет, разрешает ли сервер повторное использование адреса. По умолчанию [`False`](https://python-all.ru/3.2/library/constants.html#False), и может быть установлен в подклассах для изменения политики.108109#### `BaseServer.request_queue_size`110111Размер очереди запросов. Если обработка одного запроса занимает много времени, все запросы, поступающие во время занятости сервера, помещаются в очередь – до [`request_queue_size`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.request_queue_size) запросов. Когда очередь заполнена, последующие запросы от клиентов получают ошибку «Connection denied». Обычно значение по умолчанию равно 5, но его можно переопределить в подклассах.112113#### `BaseServer.socket_type`114115Тип сокета, используемого сервером; [`socket.SOCK_STREAM`](https://python-all.ru/3.2/library/socket.html#socket.SOCK_STREAM) и [`socket.SOCK_DGRAM`](https://python-all.ru/3.2/library/socket.html#socket.SOCK_DGRAM) – два распространённых значения.116117#### `BaseServer.timeout`118119Длительность тайм-аута в секундах или [`None`](https://python-all.ru/3.2/library/constants.html#None), если тайм-аут не нужен. Если [`handle_request()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.handle_request) не получает входящих запросов в течение тайм-аута, вызывается метод [`handle_timeout()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.handle_timeout).120121Существует несколько методов сервера, которые могут быть переопределены подклассами базовых классов сервера, например `TCPServer`; эти методы не предназначены для внешних пользователей объекта сервера.122123#### `BaseServer.finish_request()`124125Непосредственно обрабатывает запрос, создавая экземпляр [`RequestHandlerClass`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.RequestHandlerClass) и вызывая его метод `handle()`.126127#### `BaseServer.get_request()`128129Должен принять запрос из сокета и вернуть кортеж из двух элементов, содержащий *новый* объект сокета для взаимодействия с клиентом и адрес клиента.130131#### `BaseServer.handle_error(request, client_address)`132133Эта функция вызывается, если метод `handle()` класса [`RequestHandlerClass`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.RequestHandlerClass) вызывает исключение. По умолчанию трассировка выводится в стандартный вывод, и обработка последующих запросов продолжается.134135#### `BaseServer.handle_timeout()`136137Эта функция вызывается, когда атрибут [`timeout`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.timeout) установлен в значение, отличное от [`None`](https://python-all.ru/3.2/library/constants.html#None), и по истечении тайм-аута не было получено ни одного запроса. В серверах с порождением процессов (forking) по умолчанию собирается статус завершившихся дочерних процессов, а в серверах с потоками этот метод ничего не делает.138139#### `BaseServer.process_request(request, client_address)`140141Вызывает [`finish_request()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.finish_request) для создания экземпляра [`RequestHandlerClass`](https://python-all.ru/3.2/library/socketserver.html#socketserver.BaseServer.RequestHandlerClass). При необходимости эта функция может создать новый процесс или поток для обработки запроса; классы `ForkingMixIn` и `ThreadingMixIn` делают это.142143#### `BaseServer.server_activate()`144145Вызывается конструктором сервера для его активации. По умолчанию просто вызывает `listen()` на сокете сервера. Может быть переопределён.146147#### `BaseServer.server_bind()`148149Вызывается конструктором сервера для привязки сокета к нужному адресу. Можно переопределить.150151#### `BaseServer.verify_request(request, client_address)`152153Должен возвращать логическое значение; если [`True`](https://python-all.ru/3.2/library/constants.html#True), запрос будет обработан, а если [`False`](https://python-all.ru/3.2/library/constants.html#False), запрос будет отклонён. Эту функцию можно переопределить для реализации контроля доступа к серверу. Реализация по умолчанию всегда возвращает [`True`](https://python-all.ru/3.2/library/constants.html#True).154155## 20.19.3. Объекты RequestHandler156157Класс обработчика запросов должен определять новый метод `handle()` и может переопределять любые из следующих методов. Для каждого запроса создаётся новый экземпляр.158159#### `RequestHandler.finish()`160161Вызывается после метода [`handle()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.RequestHandler.handle) для выполнения необходимых действий по очистке. Реализация по умолчанию ничего не делает. Если [`setup()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.RequestHandler.setup) вызывает исключение, эта функция не будет вызвана.162163#### `RequestHandler.handle()`164165Эта функция должна выполнять всю работу по обслуживанию запроса. Реализация по умолчанию ничего не делает. Для неё доступны несколько атрибутов экземпляра: запрос доступен как `self.request`; адрес клиента как `self.client_address`; и экземпляр сервера как `self.server`, на случай, если потребуется доступ к информации, специфичной для сервера.166167Тип `self.request` различается для датаграммных и потоковых служб. Для потоковых служб `self.request` – это объект сокета; для датаграммных – `self.request` представляет собой пару из строки и сокета. Однако это можно скрыть, используя подклассы обработчика запросов `StreamRequestHandler` или `DatagramRequestHandler`, которые переопределяют методы [`setup()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.RequestHandler.setup) и [`finish()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.RequestHandler.finish) и предоставляют атрибуты `self.rfile` и `self.wfile`. `self.rfile` и `self.wfile` можно читать или записывать соответственно для получения данных запроса или возврата данных клиенту.168169#### `RequestHandler.setup()`170171Вызывается перед методом [`handle()`](https://python-all.ru/3.2/library/socketserver.html#socketserver.RequestHandler.handle) для выполнения необходимых действий по инициализации. Реализация по умолчанию ничего не делает.172173## 20.19.4. Примеры174175### 20.19.4.1. `socketserver.TCPServer` Пример176177Это серверная сторона:178179```python180import socketserver181182class MyTCPHandler(socketserver.BaseRequestHandler):183    """184    Класс RequestHandler для нашего сервера.185186    Он создаётся один раз на каждое подключение к серверу и должен187    переопределять метод handle() для реализации взаимодействия с188    клиентом.189    """190191    def handle(self):192        # self.request – это TCP-сокет, подключённый к клиенту193        self.data = self.request.recv(1024).strip()194        print("{} wrote:".format(self.client_address[0]))195        print(self.data)196        # просто отправляет обратно те же данные, но в верхнем регистре197        self.request.sendall(self.data.upper())198199if __name__ == "__main__":200    HOST, PORT = "localhost", 9999201202    # Создаём сервер, привязываясь к localhost на порту 9999203    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)204205    # Запускаем сервер; он будет работать, пока вы206    # не прервёте программу с помощью Ctrl-C207    server.serve_forever()208```209210Альтернативный класс обработчика запросов, который использует потоки (объекты, подобные файлам, упрощающие взаимодействие за счет предоставления стандартного файлового интерфейса):211212```python213class MyTCPHandler(socketserver.StreamRequestHandler):214215    def handle(self):216        # self.rfile – это файлоподобный объект, созданный обработчиком;217        # теперь можно использовать, например, readline() вместо прямых вызовов recv()218        self.data = self.rfile.readline().strip()219        print("{} wrote:".format(self.client_address[0]))220        print(self.data)221        # Аналогично, self.wfile – это файлоподобный объект, используемый для записи ответа222        # клиенту223        self.wfile.write(self.data.upper())224```225226Разница в том, что вызов `readline()` во втором обработчике вызывает `recv()` несколько раз, пока не встретит символ новой строки, тогда как одиночный вызов `recv()` в первом обработчике просто возвращает то, что было отправлено от клиента за один вызов `sendall()`.227228Это клиентская сторона:229230```python231import socket232import sys233234HOST, PORT = "localhost", 9999235data = " ".join(sys.argv[1:])236237# Создать сокет (SOCK_STREAM означает TCP-сокет)238sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)239240try:241    # Подключиться к серверу и отправить данные242    sock.connect((HOST, PORT))243    sock.sendall(bytes(data + "\n", "utf-8"))244245    # Получить данные от сервера и завершить работу246    received = str(sock.recv(1024), "utf-8")247finally:248    sock.close()249250print("Sent:     {}".format(data))251print("Received: {}".format(received))252```253254Результат работы примера должен выглядеть примерно так:255256Сервер:257258```python259$ python TCPServer.py260127.0.0.1 wrote:261b'hello world with TCP'262127.0.0.1 wrote:263b'python is nice'264```265266Клиент:267268```python269$ python TCPClient.py hello world with TCP270Sent:     hello world with TCP271Received: HELLO WORLD WITH TCP272$ python TCPClient.py python is nice273Sent:     python is nice274Received: PYTHON IS NICE275```276277### 20.19.4.2. `socketserver.UDPServer` Пример278279Это серверная сторона:280281```python282import socketserver283284class MyUDPHandler(socketserver.BaseRequestHandler):285    """286    Этот класс работает аналогично классу-обработчику TCP, за исключением того, что287    self.request состоит из пары данных и клиентского сокета, и поскольку288    соединение отсутствует, адрес клиента должен быть указан явно289    при отправке данных обратно через sendto().290    """291292    def handle(self):293        data = self.request[0].strip()294        socket = self.request[1]295        print("{} wrote:".format(self.client_address[0]))296        print(data)297        socket.sendto(data.upper(), self.client_address)298299if __name__ == "__main__":300    HOST, PORT = "localhost", 9999301    server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)302    server.serve_forever()303```304305Это клиентская сторона:306307```python308import socket309import sys310311HOST, PORT = "localhost", 9999312data = " ".join(sys.argv[1:])313314# SOCK_DGRAM – это тип сокета для UDP-сокетов315sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)316317# Как можно видеть, вызов connect() отсутствует; в UDP нет соединений.318# Вместо этого данные отправляются напрямую получателю через sendto().319sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))320received = str(sock.recv(1024), "utf-8")321322print("Sent:     {}".format(data))323print("Received: {}".format(received))324```325326Результат работы примера должен выглядеть точно так же, как для примера TCP-сервера.327328### 20.19.4.3. Асинхронные примеси329330Для создания асинхронных обработчиков используйте классы `ThreadingMixIn` и `ForkingMixIn`.331332Пример для класса `ThreadingMixIn`:333334```python335import socket336import threading337import socketserver338339class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):340341    def handle(self):342        data = str(self.request.recv(1024), 'ascii')343        cur_thread = threading.current_thread()344        response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')345        self.request.sendall(response)346347class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):348    pass349350def client(ip, port, message):351    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)352    sock.connect((ip, port))353    try:354        sock.sendall(bytes(message, 'ascii'))355        response = str(sock.recv(1024), 'ascii')356        print("Received: {}".format(response))357    finally:358        sock.close()359360if __name__ == "__main__":361    # Порт 0 означает выбор произвольного неиспользуемого порта362    HOST, PORT = "localhost", 0363364    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)365    ip, port = server.server_address366367    # Запустить поток для сервера – этот поток затем запустит один368    # дополнительный поток на каждый запрос369    server_thread = threading.Thread(target=server.serve_forever)370    # Завершить поток сервера при завершении главного потока371    server_thread.daemon = True372    server_thread.start()373    print("Server loop running in thread:", server_thread.name)374375    client(ip, port, "Hello World 1")376    client(ip, port, "Hello World 2")377    client(ip, port, "Hello World 3")378379    server.shutdown()380```381382Результат работы примера должен выглядеть примерно так:383384```python385$ python ThreadedTCPServer.py386Server loop running in thread: Thread-1387Received: Thread-2: Hello World 1388Received: Thread-3: Hello World 2389Received: Thread-4: Hello World 3390```391392Класс `ForkingMixIn` используется так же, за исключением того, что сервер будет создавать новый процесс для каждого запроса.393