socketserver.md
1> **Источник:** https://python-all.ru/3.1/library/socketserver.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 20.19. `socketserver` – Фреймворк для сетевых серверов89Модуль `socketserver` упрощает задачу написания сетевых серверов.1011Существует четыре базовых класса серверов: `TCPServer` использует протокол TCP, который обеспечивает непрерывные потоки данных между клиентом и сервером. `UDPServer` использует дейтаграммы – дискретные пакеты информации, которые могут прийти не по порядку или потеряться при передаче. Реже используемые классы `UnixStreamServer` и `UnixDatagramServer` похожи, но используют сокеты домена Unix; они недоступны на платформах, отличных от Unix. Дополнительные сведения о сетевом программировании можно найти в таких книгах, как UNIX Network Programming У. Ричарда Стивенса или Win32 Network Programming Ральфа Дэвиса.1213Эти четыре класса обрабатывают запросы *синхронно*; каждый запрос должен быть завершен до того, как может начаться следующий. Это не подходит, если каждый запрос требует много времени на выполнение из-за вычислительной сложности или из-за возврата большого объема данных, которые клиент обрабатывает медленно. Решение – создавать отдельный процесс или поток для обработки каждого запроса; миксин-классы `ForkingMixIn` и `ThreadingMixIn` можно использовать для поддержки асинхронного поведения.1415Создание сервера требует нескольких шагов. Сначала необходимо создать класс-обработчик запросов, унаследовав его от класса `BaseRequestHandler` и переопределив его метод `handle()`; этот метод будет обрабатывать входящие запросы. Затем нужно создать экземпляр одного из классов серверов, передав ему адрес сервера и класс-обработчик запросов. В конце вызовите метод `handle_request()` или `serve_forever()` объекта сервера для обработки одного или нескольких запросов.1617При наследовании от `ThreadingMixIn` для поведения с потоками необходимо явно указать, как потоки должны вести себя при аварийном завершении. Класс `ThreadingMixIn` определяет атрибут *daemon\_threads*, который указывает, должен ли сервер ждать завершения потоков. Следует явно установить этот флаг, если требуется, чтобы потоки работали автономно; по умолчанию [`False`](https://python-all.ru/3.1/library/constants.html#False), то есть Python не завершится, пока все потоки, созданные `ThreadingMixIn`, не завершатся.1819Классы серверов имеют одни и те же внешние методы и атрибуты, независимо от того, какой сетевой протокол они используют.2021## 20.19.1. Заметки по созданию сервера2223В иерархии наследования пять классов, четыре из которых представляют синхронные серверы четырёх типов:2425```python26+------------+27| BaseServer |28+------------+29 |30 v31+-----------+ +------------------+32| TCPServer |------->| UnixStreamServer |33+-----------+ +------------------+34 |35 v36+-----------+ +--------------------+37| UDPServer |------->| UnixDatagramServer |38+-----------+ +--------------------+39```4041Обратите внимание, что `UnixDatagramServer` наследует от `UDPServer`, а не от `UnixStreamServer` – единственное различие между IP- и Unix-стрим-сервером заключается в семействе адресов, которое просто повторяется в обоих Unix-классах серверов.4243Версии с порождением процессов и потоками для каждого типа сервера можно создать с помощью миксин-классов `ForkingMixIn` и `ThreadingMixIn`. Например, класс потокового UDP-сервера создаётся следующим образом:4445```python46class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass47```4849Класс-примесь должен идти первым, так как он переопределяет метод, определённый в `UDPServer`. Настройка различных переменных-членов также изменяет поведение базового серверного механизма.5051Для реализации службы необходимо создать класс, унаследованный от `BaseRequestHandler`, и переопределить его метод `handle()`. Затем можно запускать различные версии службы, комбинируя один из классов сервера с классом-обработчиком запросов. Класс-обработчик запросов должен различаться для дейтаграммных и потоковых служб. Это можно скрыть, используя подклассы обработчиков `StreamRequestHandler` или `DatagramRequestHandler`.5253Конечно, нужно подходить с умом. Например, нет смысла использовать сервер с порождением процессов (forking), если служба хранит состояние в памяти, которое может изменяться разными запросами, потому что изменения в дочернем процессе никогда не попадут в исходное состояние, хранящееся в родительском процессе и передаваемое каждому дочернему. В этом случае можно использовать потоковый сервер (threading), но, скорее всего, придётся использовать блокировки для защиты целостности общих данных.5455С другой стороны, если создаётся HTTP-сервер, где все данные хранятся внешне (например, в файловой системе), синхронный класс фактически сделает службу «глухой» на время обработки одного запроса – что может занять очень много времени, если клиент медленно получает все запрошенные данные. Здесь подойдёт потоковый сервер или сервер с порождением процессов.5657В некоторых случаях может быть целесообразно обработать часть запроса синхронно, но завершить обработку в порождённом дочернем процессе в зависимости от данных запроса. Это можно реализовать, используя синхронный сервер и выполняя явный вызов fork в методе `handle()` класса-обработчика запросов.5859Другой подход к обработке нескольких одновременных запросов в среде, не поддерживающей ни потоки, ни `fork()` (или где они слишком дороги или неприемлемы для данного сервиса), заключается в ведении явной таблицы частично завершённых запросов и использовании `select()` для определения, какой запрос обрабатывать следующим (или следует ли обрабатывать новый входящий запрос). Это особенно важно для потоковых сервисов, где каждый клиент может быть подключен в течение длительного времени (если невозможно использовать потоки или подпроцессы). См. [`asyncore`](https://python-all.ru/3.1/library/asyncore.html#module-asyncore) для другого способа управления этим.6061## 20.19.2. Объекты сервера6263#### `class socketserver.BaseServer`6465Это суперкласс всех объектов серверов в модуле. Он определяет интерфейс, приведённый ниже, но не реализует большинство методов – это делается в подклассах.6667#### `BaseServer.fileno()`6869Возвращает целочисленный файловый дескриптор для сокета, на котором сервер прослушивает соединения. Эта функция чаще всего передаётся в7071[`select.select()`](https://python-all.ru/3.1/library/select.html#select.select)7273, чтобы обеспечить мониторинг нескольких серверов в одном процессе.7475#### `BaseServer.handle_request()`7677Обрабатывает один запрос. Эта функция вызывает следующие методы по порядку:7879[`get_request()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.get_request)8081,8283[`verify_request()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.verify_request)8485и8687[`process_request()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.process_request)8889. Если предоставленный пользователем метод9091`handle()`9293класса-обработчика вызывает исключение, будет вызван метод9495[`handle_error()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.handle_error)9697сервера. Если в течение9899`self.timeout`100101секунд не получено ни одного запроса, будет вызван102103[`handle_timeout()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.handle_timeout)104105, и106107[`handle_request()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.handle_request)108109вернёт управление.110111#### `BaseServer.serve_forever(poll_interval=0.5)`112113Обрабатывает запросы до тех пор, пока не поступит явный запрос114115[`shutdown()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.shutdown)116117. Опрашивает на предмет завершения каждые118119*poll\_interval*120121секунд.122123#### `BaseServer.shutdown()`124125Сообщает циклу126127[`serve_forever()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.serve_forever)128129о необходимости остановиться и ожидает его завершения.130131#### `BaseServer.address_family`132133Семейство протоколов, к которому принадлежит сокет сервера. Типичные примеры:134135[`socket.AF_INET`](https://python-all.ru/3.1/library/socket.html#socket.AF_INET)136137и138139[`socket.AF_UNIX`](https://python-all.ru/3.1/library/socket.html#socket.AF_UNIX)140141.142143#### `BaseServer.RequestHandlerClass`144145Класс обработчика запросов, предоставленный пользователем; экземпляр этого класса создаётся для каждого запроса.146147#### `BaseServer.server_address`148149Адрес, на котором сервер прослушивает соединения. Формат адресов зависит от семейства протоколов; подробнее см. документацию модуля socket. Для интернет-протоколов это кортеж, содержащий строку с адресом и целочисленный номер порта:150151`('127.0.0.1', 80)`152153, например.154155#### `BaseServer.socket`156157Объект сокета, через который сервер будет принимать входящие запросы.158159Классы серверов поддерживают следующие переменные класса:160161#### `BaseServer.allow_reuse_address`162163Определяет, разрешает ли сервер повторное использование адреса. По умолчанию164165[`False`](https://python-all.ru/3.1/library/constants.html#False)166167, но может быть установлено в подклассах для изменения политики.168169#### `BaseServer.request_queue_size`170171Размер очереди запросов. Если обработка одного запроса занимает много времени, все запросы, поступающие во время занятости сервера, помещаются в очередь – до172173[`request_queue_size`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.request_queue_size)174175запросов. Когда очередь заполнена, последующие запросы от клиентов получают ошибку «Connection denied». Обычно значение по умолчанию равно 5, но его можно переопределить в подклассах.176177#### `BaseServer.socket_type`178179Тип сокета, используемого сервером;180181[`socket.SOCK_STREAM`](https://python-all.ru/3.1/library/socket.html#socket.SOCK_STREAM)182183и184185[`socket.SOCK_DGRAM`](https://python-all.ru/3.1/library/socket.html#socket.SOCK_DGRAM)186187– два распространённых значения.188189#### `BaseServer.timeout`190191Длительность тайм-аута в секундах или192193[`None`](https://python-all.ru/3.1/library/constants.html#None)194195, если тайм-аут не нужен. Если196197[`handle_request()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.handle_request)198199не получает входящих запросов в течение тайм-аута, вызывается метод200201[`handle_timeout()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.handle_timeout)202203.204205Существует несколько методов сервера, которые могут быть переопределены подклассами базовых классов сервера, например `TCPServer`; эти методы не предназначены для внешних пользователей объекта сервера.206207#### `BaseServer.finish_request()`208209Непосредственно обрабатывает запрос, создавая экземпляр210211[`RequestHandlerClass`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.RequestHandlerClass)212213и вызывая его метод214215`handle()`216217.218219#### `BaseServer.get_request()`220221Должен принять запрос из сокета и вернуть кортеж из двух элементов, содержащий222223*новый*224225объект сокета для взаимодействия с клиентом и адрес клиента.226227#### `BaseServer.handle_error(request, client_address)`228229Эта функция вызывается, если метод230231`handle()`232233класса234235[`RequestHandlerClass`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.RequestHandlerClass)236237вызывает исключение. По умолчанию трассировка выводится в стандартный вывод, и обработка последующих запросов продолжается.238239#### `BaseServer.handle_timeout()`240241Эта функция вызывается, когда атрибут242243[`timeout`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.timeout)244245установлен в значение, отличное от246247[`None`](https://python-all.ru/3.1/library/constants.html#None)248249, и по истечении тайм-аута не было получено ни одного запроса. В серверах с порождением процессов (forking) по умолчанию собирается статус завершившихся дочерних процессов, а в серверах с потоками этот метод ничего не делает.250251#### `BaseServer.process_request(request, client_address)`252253Вызывает254255[`finish_request()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.finish_request)256257для создания экземпляра258259[`RequestHandlerClass`](https://python-all.ru/3.1/library/socketserver.html#socketserver.BaseServer.RequestHandlerClass)260261. При необходимости эта функция может создать новый процесс или поток для обработки запроса; классы262263`ForkingMixIn`264265и266267`ThreadingMixIn`268269делают это.270271#### `BaseServer.server_activate()`272273Вызывается конструктором сервера для его активации. Поведение по умолчанию просто вызывает274275`listen()`276277для сокета сервера. Может быть переопределено.278279#### `BaseServer.server_bind()`280281Вызывается конструктором сервера для привязки сокета к нужному адресу. Можно переопределить.282283#### `BaseServer.verify_request(request, client_address)`284285Должна возвращать логическое значение; если значение равно286287[`True`](https://python-all.ru/3.1/library/constants.html#True)288289, запрос будет обработан, а если290291[`False`](https://python-all.ru/3.1/library/constants.html#False)292293, запрос будет отклонён. Эту функцию можно переопределить для реализации контроля доступа к серверу. Реализация по умолчанию всегда возвращает294295[`True`](https://python-all.ru/3.1/library/constants.html#True)296297.298299## 20.19.3. Объекты RequestHandler300301Класс обработчика запросов должен определять новый метод `handle()` и может переопределять любые из следующих методов. Для каждого запроса создаётся новый экземпляр.302303#### `RequestHandler.finish()`304305Вызывается после метода306307[`handle()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.RequestHandler.handle)308309для выполнения необходимых действий по очистке. Реализация по умолчанию ничего не делает. Если310311[`setup()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.RequestHandler.setup)312313или314315[`handle()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.RequestHandler.handle)316317вызывают исключение, эта функция не будет вызвана.318319#### `RequestHandler.handle()`320321Эта функция должна выполнять всю работу по обслуживанию запроса. Реализация по умолчанию ничего не делает. Для неё доступны несколько атрибутов экземпляра: запрос доступен как `self.request`; адрес клиента как `self.client_address`; и экземпляр сервера как `self.server`, на случай, если потребуется доступ к информации, специфичной для сервера.322323Тип `self.request` различается для датаграммных и потоковых служб. Для потоковых служб `self.request` – это объект сокета; для датаграммных – `self.request` представляет собой пару из строки и сокета. Однако это можно скрыть, используя подклассы обработчика запросов `StreamRequestHandler` или `DatagramRequestHandler`, которые переопределяют методы [`setup()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.RequestHandler.setup) и [`finish()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.RequestHandler.finish) и предоставляют атрибуты `self.rfile` и `self.wfile`. `self.rfile` и `self.wfile` можно читать или записывать соответственно для получения данных запроса или возврата данных клиенту.324325#### `RequestHandler.setup()`326327Вызывается перед методом328329[`handle()`](https://python-all.ru/3.1/library/socketserver.html#socketserver.RequestHandler.handle)330331для выполнения необходимых действий по инициализации. Реализация по умолчанию ничего не делает.332333## 20.19.4. Примеры334335### 20.19.4.1. `socketserver.TCPServer` Пример336337Это серверная сторона:338339```python340import socketserver341342class MyTCPHandler(socketserver.BaseRequestHandler):343 """344 Класс RequestHandler для нашего сервера.345346 Он создаётся один раз на каждое подключение к серверу и должен347 переопределять метод handle() для реализации взаимодействия с348 клиентом.349 """350351 def handle(self):352 # self.request – это TCP-сокет, подключённый к клиенту353 self.data = self.request.recv(1024).strip()354 print("%s wrote:" % self.client_address[0])355 print(self.data)356 # просто отправляет обратно те же данные, но в верхнем регистре357 self.request.send(self.data.upper())358359if __name__ == "__main__":360 HOST, PORT = "localhost", 9999361362 # Создаём сервер, привязываясь к localhost на порту 9999363 server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)364365 # Запускаем сервер; он будет работать, пока вы366 # не прервёте программу с помощью Ctrl-C367 server.serve_forever()368```369370Альтернативный класс обработчика запросов, который использует потоки (объекты, подобные файлам, упрощающие взаимодействие за счет предоставления стандартного файлового интерфейса):371372```python373class MyTCPHandler(socketserver.StreamRequestHandler):374375 def handle(self):376 # self.rfile – это файлоподобный объект, созданный обработчиком;377 # теперь можно использовать, например, readline() вместо прямых вызовов recv()378 self.data = self.rfile.readline().strip()379 print("%s wrote:" % self.client_address[0])380 print(self.data)381 # Аналогично, self.wfile – это файлоподобный объект, используемый для записи ответа382 # клиенту383 self.wfile.write(self.data.upper())384```385386Разница в том, что вызов `readline()` во втором обработчике будет вызывать `recv()` несколько раз, пока не встретит символ новой строки, в то время как единственный вызов `recv()` в первом обработчике просто вернёт то, что было отправлено клиентом в одном вызове `send()`.387388Это клиентская сторона:389390```python391import socket392import sys393394HOST, PORT = "localhost", 9999395data = " ".join(sys.argv[1:])396397# Создать сокет (SOCK_STREAM означает TCP-сокет)398sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)399400# Подключиться к серверу и отправить данные401sock.connect((HOST, PORT))402sock.send(bytes(data + "\n","utf8"))403404# Получить данные от сервера и завершить работу405received = sock.recv(1024)406sock.close()407408print("Sent: %s" % data)409print("Received: %s" % received)410```411412Результат работы примера должен выглядеть примерно так:413414Сервер:415416```python417$ python TCPServer.py418127.0.0.1 wrote:419b'hello world with TCP'420127.0.0.1 wrote:421b'python is nice'422```423424Клиент:425426```python427$ python TCPClient.py hello world with TCP428Sent: hello world with TCP429Received: b'HELLO WORLD WITH TCP'430$ python TCPClient.py python is nice431Sent: python is nice432Received: b'PYTHON IS NICE'433```434435### 20.19.4.2. `socketserver.UDPServer` Пример436437Это серверная сторона:438439```python440import socketserver441442class MyUDPHandler(socketserver.BaseRequestHandler):443 """444 Этот класс работает аналогично классу-обработчику TCP, за исключением того, что445 self.request состоит из пары данных и клиентского сокета, и поскольку446 соединение отсутствует, адрес клиента должен быть указан явно447 при отправке данных обратно через sendto().448 """449450 def handle(self):451 data = self.request[0].strip()452 socket = self.request[1]453 print("%s wrote:" % self.client_address[0])454 print(data)455 socket.sendto(data.upper(), self.client_address)456457if __name__ == "__main__":458 HOST, PORT = "localhost", 9999459 server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)460 server.serve_forever()461```462463Это клиентская сторона:464465```python466import socket467import sys468469HOST, PORT = "localhost", 9999470data = " ".join(sys.argv[1:])471472# SOCK_DGRAM – это тип сокета для UDP-сокетов473sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)474475# Как можно видеть, вызов connect() отсутствует; в UDP нет соединений.476# Вместо этого данные отправляются напрямую получателю через sendto().477sock.sendto(bytes(data + "\n","utf8"), (HOST, PORT))478received = sock.recv(1024)479480print("Sent: %s" % data)481print("Received: %s" % received)482```483484Результат работы примера должен выглядеть точно так же, как для примера TCP-сервера.485486### 20.19.4.3. Асинхронные примеси487488Для создания асинхронных обработчиков используйте классы `ThreadingMixIn` и `ForkingMixIn`.489490Пример для класса `ThreadingMixIn`:491492```python493import socket494import threading495import socketserver496497class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):498499 def handle(self):500 data = self.request.recv(1024)501 cur_thread = threading.current_thread()502 response = bytes("%s: %s" % (cur_thread.getName(), data),'ascii')503 self.request.send(response)504505class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):506 pass507508def client(ip, port, message):509 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)510 sock.connect((ip, port))511 sock.send(message)512 response = sock.recv(1024)513 print("Received: %s" % response)514 sock.close()515516if __name__ == "__main__":517 # Порт 0 означает выбор произвольного неиспользуемого порта518 HOST, PORT = "localhost", 0519520 server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)521 ip, port = server.server_address522523 # Запустить поток для сервера – этот поток затем запустит один524 # дополнительный поток на каждый запрос525 server_thread = threading.Thread(target=server.serve_forever)526 # Завершить поток сервера при завершении главного потока527 server_thread.setDaemon(True)528 server_thread.start()529 print("Server loop running in thread:", server_thread.name)530531 client(ip, port, b"Hello World 1")532 client(ip, port, b"Hello World 2")533 client(ip, port, b"Hello World 3")534535 server.shutdown()536```537538Результат работы примера должен выглядеть примерно так:539540```python541$ python ThreadedTCPServer.py542Server loop running in thread: Thread-1543Received: b"Thread-2: b'Hello World 1'"544Received: b"Thread-3: b'Hello World 2'"545Received: b"Thread-4: b'Hello World 3'"546```547548Класс `ForkingMixIn` используется так же, за исключением того, что сервер будет создавать новый процесс для каждого запроса.549