socketserver — Фреймворк для сетевых серверов
Исходный код: Lib/socketserver.py
Модуль socketserver упрощает задачу написания сетевых серверов.
Доступность: недоступен в WASI.
Этот модуль не работает или недоступен в WebAssembly. Дополнительную информацию см. в разделе Платформы WebAssembly.
Существует четыре базовых конкретных класса серверов:
-
class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True) -
Использует интернет-протокол TCP, обеспечивающий непрерывные потоки данных между клиентом и сервером. Если значение bind_and_activate равно true, конструктор автоматически пытается вызвать
server_bind()иserver_activate(). Остальные параметры передаются базовому классуBaseServer.
-
class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True) -
Использует датаграммы — отдельные пакеты информации, которые могут прибыть не по порядку или потеряться при передаче. Параметры те же, что и у
TCPServer.
-
class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True) -
class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True) -
Эти менее часто используемые классы похожи на классы TCP и UDP, но используют доменные сокеты Unix; они недоступны на платформах, отличных от Unix. Параметры те же, что и у
TCPServer.
Эти четыре класса обрабатывают запросы синхронно; каждый запрос должен быть обработан до начала обработки следующего. Это не подходит, если обработка каждого запроса занимает много времени из-за необходимости больших вычислений или возврата большого объёма данных, которые клиент медленно обрабатывает. Решение — создать отдельный процесс или поток для обработки каждого запроса; для поддержки асинхронного поведения можно использовать классы-миксины ForkingMixIn и ThreadingMixIn.
Создание сервера состоит из нескольких шагов. Сначала нужно создать класс обработчика запросов, унаследовав его от класса BaseRequestHandler и переопределив его метод handle(); этот метод будет обрабатывать входящие запросы. Затем нужно создать экземпляр одного из классов серверов, передав ему адрес сервера и класс обработчика запросов. Рекомендуется использовать сервер в инструкции with. После этого вызовите метод handle_request() или serve_forever() объекта сервера для обработки одного или нескольких запросов. Наконец, вызовите server_close(), чтобы закрыть сокет (если только вы не использовали инструкцию with).
При наследовании от ThreadingMixIn для реализации обработки соединений в потоках следует явно указать, как потоки должны вести себя при неожиданном завершении работы. Класс ThreadingMixIn определяет атрибут daemon_threads, указывающий, должен ли сервер ожидать завершения потоков. Если вы хотите, чтобы потоки работали автономно, следует явно установить этот флаг; по умолчанию он равен False, то есть Python не завершит работу, пока не завершатся все потоки, созданные классом ThreadingMixIn.
Классы серверов имеют одинаковые внешние методы и атрибуты независимо от используемого сетевого протокола.
Примечания по созданию серверов
В диаграмме наследования представлены пять классов, четыре из которых соответствуют синхронным серверам четырёх типов:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
Обратите внимание: UnixDatagramServer наследуется от UDPServer, а не от UnixStreamServer — единственное различие между IP-сервером и сервером Unix заключается в семействе адресов.
-
class socketserver.ForkingMixIn -
class socketserver.ThreadingMixIn -
С помощью этих классов-миксинов можно создавать серверы каждого типа с использованием процессов или потоков. Например,
ThreadingUDPServerсоздаётся следующим образом:class ThreadingUDPServer(ThreadingMixIn, UDPServer): passКласс-миксин указывается первым, поскольку он переопределяет метод, определённый в
UDPServer. Установка различных атрибутов также меняет поведение базового механизма сервера.ForkingMixInи упомянутые ниже классы Forking доступны только на платформах POSIX, поддерживающихfork().-
block_on_close -
ForkingMixIn.server_closeожидает завершения всех дочерних процессов, если атрибутblock_on_closeне равенFalse.ThreadingMixIn.server_closeожидает завершения всех недемонических потоков, если атрибутblock_on_closeне равенFalse.
-
daemon_threads -
Чтобы использовать демонические потоки в
ThreadingMixInи не ожидать завершения потоков, установитеThreadingMixIn.daemon_threadsвTrue.
Изменено в версии 3.7: Теперь
ForkingMixIn.server_closeиThreadingMixIn.server_closeожидают завершения всех дочерних процессов и недемонических потоков. Добавлен новый атрибут классаForkingMixIn.block_on_close, позволяющий включить поведение, существовавшее до версии 3.7. -
-
class socketserver.ForkingTCPServer -
class socketserver.ForkingUDPServer -
class socketserver.ThreadingTCPServer -
class socketserver.ThreadingUDPServer -
class socketserver.ForkingUnixStreamServer -
class socketserver.ForkingUnixDatagramServer -
class socketserver.ThreadingUnixStreamServer -
class socketserver.ThreadingUnixDatagramServer -
Эти классы предварительно определены с использованием классов-миксинов.
Добавлено в версии 3.12: Добавлены классы ForkingUnixStreamServer и ForkingUnixDatagramServer.
Для реализации службы необходимо создать класс, унаследованный от BaseRequestHandler, и переопределить его метод handle(). Затем можно запускать различные варианты службы, комбинируя один из классов серверов с классом обработчика запросов. Для служб, использующих датаграммы или потоки, необходимы разные классы обработчиков запросов. Это различие можно скрыть, используя подклассы обработчиков StreamRequestHandler или DatagramRequestHandler.
Разумеется, нужно руководствоваться здравым смыслом! Например, нет смысла использовать сервер с разветвлением процессов, если служба хранит состояние в памяти, которое может изменяться разными запросами: изменения в дочернем процессе никогда не повлияют на исходное состояние, сохранённое в родительском процессе и переданное каждому дочернему процессу. В этом случае можно использовать сервер с потоками, но для защиты целостности общих данных, скорее всего, придётся использовать блокировки.
С другой стороны, если вы создаёте HTTP-сервер, где все данные хранятся извне (например, в файловой системе), синхронный класс фактически сделает службу «глухой» на время обработки одного запроса — а это может занять очень много времени, если клиент медленно получает все запрошенные данные. В этом случае подойдёт сервер с потоками или с разветвлением процессов.
В некоторых случаях может быть целесообразно синхронно обработать часть запроса, а затем завершить обработку в порождённом дочернем процессе в зависимости от данных запроса. Это можно реализовать с помощью синхронного сервера и явного вызова fork в методе handle() класса обработчика запросов.
Ещё один способ обрабатывать несколько одновременных запросов в среде, не поддерживающей ни потоки, ни fork() (или когда они слишком затратны либо не подходят для службы), — вести явную таблицу частично обработанных запросов и использовать selectors, чтобы определить, какой запрос обрабатывать следующим (или следует ли обрабатывать новый входящий запрос). Это особенно важно для потоковых служб, где соединение с каждым клиентом потенциально может сохраняться долгое время (если нельзя использовать потоки или подпроцессы).
Объекты сервера
-
class socketserver.BaseServer(server_address, RequestHandlerClass) -
Это суперкласс всех объектов Server в модуле. Он определяет приведённый ниже интерфейс, но не реализует большинство методов — их реализация выполняется в подклассах. Два параметра сохраняются в соответствующих атрибутах
server_addressиRequestHandlerClass.-
fileno() -
Возвращает целочисленный файловый дескриптор сокета, на котором сервер ожидает соединения. Эта функция чаще всего передаётся в
selectors, чтобы можно было отслеживать несколько серверов в одном процессе.
-
handle_request() -
Обрабатывает один запрос. Эта функция последовательно вызывает следующие методы:
get_request(),verify_request()иprocess_request(). Если предоставленный пользователем методhandle()класса обработчика вызывает исключение, будет вызван метод сервераhandle_error(). Если запрос не получен в течениеtimeoutсекунд, будет вызван методhandle_timeout(), аhandle_request()вернёт управление.
-
serve_forever(poll_interval=0.5) -
Обрабатывает запросы до явного вызова
shutdown(). Проверяет наличие запроса на завершение каждые poll_interval секунд. Игнорирует атрибутtimeout. Также вызываетservice_actions(), который подкласс или миксин может использовать для выполнения действий, специфичных для конкретной службы. Например, классForkingMixInиспользуетservice_actions()для очистки процессов-зомби.Изменено в версии 3.3: В метод
serve_foreverдобавлен вызовservice_actions.
-
service_actions() -
Вызывается в цикле
serve_forever(). Этот метод можно переопределить в подклассах или классах-миксина для выполнения действий, специфичных для конкретной службы, например очистки.Добавлено в версии 3.3.
-
shutdown() -
Указывает циклу
serve_forever()остановиться и ожидает его остановки. Методshutdown()необходимо вызвать, покаserve_forever()выполняется в другом потоке, иначе возникнет взаимная блокировка.
-
server_close() -
Очищает ресурсы сервера. Может быть переопределён.
-
address_family -
Семейство протоколов, к которому относится сокет сервера. Распространённые примеры:
socket.AF_INET,socket.AF_INET6иsocket.AF_UNIX. Чтобы создать классы IPv6-серверов, унаследуйте классы TCP- или UDP-серверов из этого модуля и задайте атрибут классаaddress_family = AF_INET6.
-
RequestHandlerClass -
Предоставленный пользователем класс обработчика запросов; для каждого запроса создаётся экземпляр этого класса.
-
server_address -
Адрес, на котором сервер ожидает соединения. Формат адресов различается в зависимости от семейства протоколов; подробности см. в документации модуля
socket. Для интернет-протоколов это кортеж, содержащий строку с адресом и целочисленный номер порта: например,('127.0.0.1', 80).
-
socket -
Сокет, на котором сервер будет ожидать входящие запросы.
Классы серверов поддерживают следующие переменные класса:
-
allow_reuse_address -
Определяет, разрешено ли серверу повторно использовать адрес. По умолчанию имеет значение
False; это значение можно изменить в подклассах, чтобы задать другую политику.
-
request_queue_size -
Размер очереди запросов. Если обработка одного запроса занимает много времени, запросы, поступающие во время занятости сервера, помещаются в очередь — не более
request_queue_sizeзапросов. Когда очередь заполнится, клиенты, отправившие новые запросы, получат ошибку «Соединение отклонено». Обычно значение по умолчанию равно 5, но его можно переопределить в подклассах.
-
socket_type -
Тип сокета, используемого сервером; распространённые значения —
socket.SOCK_STREAMиsocket.SOCK_DGRAM.
-
timeout -
Длительность ожидания в секундах или
None, если время ожидания не требуется. Еслиhandle_request()не получает входящих запросов в течение периода ожидания, вызывается методhandle_timeout().
Существует несколько методов сервера, которые можно переопределить в подклассах базовых классов серверов, таких как
TCPServer; эти методы не предназначены для пользователей объекта сервера.-
finish_request(request, client_address) -
Фактически обрабатывает запрос, создавая экземпляр
RequestHandlerClassи вызывая его методhandle().
-
get_request() -
Должен принять запрос от сокета и вернуть кортеж из двух элементов: нового объекта сокета для обмена данными с клиентом и адреса клиента.
-
handle_error(request, client_address) -
Эта функция вызывается, если метод
handle()экземпляраRequestHandlerClassвызывает исключение. По умолчанию трассировка выводится в стандартный поток ошибок, после чего обработка следующих запросов продолжается.Изменено в версии 3.6: Теперь метод вызывается только для исключений, унаследованных от класса
Exception.
-
handle_timeout() -
Эта функция вызывается, если атрибут
timeoutустановлен в значение, отличное отNone, и период ожидания истёк, а запросы не поступили. Для серверов с разветвлением процессов действие по умолчанию — собрать сведения о завершившихся дочерних процессах; на серверах с потоками этот метод ничего не делает.
-
process_request(request, client_address) -
Вызывает
finish_request(), чтобы создать экземплярRequestHandlerClass. При необходимости эта функция может создать новый процесс или поток для обработки запроса; это делают классыForkingMixInиThreadingMixIn.
-
server_activate() -
Вызывается конструктором сервера для его активации. Для TCP-сервера по умолчанию вызывает
listen()для сокета сервера. Метод можно переопределить.
-
server_bind() -
Вызывается конструктором сервера для привязки сокета к нужному адресу. Метод можно переопределить.
-
verify_request(request, client_address) -
Должен возвращать логическое значение: если возвращается
True, запрос будет обработан, а еслиFalse, запрос будет отклонён. Эту функцию можно переопределить для реализации контроля доступа к серверу. Реализация по умолчанию всегда возвращаетTrue.
Изменено в версии 3.6: Добавлена поддержка протокола менеджера контекста. Выход из менеджера контекста эквивалентен вызову
server_close(). -
Объекты обработчиков запросов
-
class socketserver.BaseRequestHandler -
Это суперкласс всех объектов обработчиков запросов. Он определяет приведённый ниже интерфейс. Конкретный подкласс обработчика запросов должен определить новый метод
handle()и может переопределить любой из остальных методов. Для каждого запроса создаётся новый экземпляр подкласса.-
setup() -
Вызывается перед методом
handle()для выполнения необходимых действий по инициализации. Реализация по умолчанию ничего не делает.
-
handle() -
Эта функция должна выполнять всю работу, необходимую для обработки запроса. Реализация по умолчанию ничего не делает. Ей доступны несколько атрибутов экземпляра: запрос доступен через
request, адрес клиента — черезclient_address, а экземпляр сервера — черезserver, если потребуется получить доступ к информации, относящейся к серверу.Тип
requestразличается для служб дейтаграмм и потоковых служб. Для потоковых службrequest— это объект сокета; для служб дейтаграммrequest— это пара из строки и сокета.
-
finish() -
Вызывается после метода
handle()для выполнения необходимых действий по очистке. Реализация по умолчанию ничего не делает. Еслиsetup()вызывает исключение, эта функция вызвана не будет.
-
request -
Новый объект
socket.socket, используемый для обмена данными с клиентом.
-
client_address -
Адрес клиента, возвращённый методом
BaseServer.get_request().
-
server -
Объект
BaseServer, используемый для обработки запроса.
-
-
class socketserver.StreamRequestHandler -
class socketserver.DatagramRequestHandler -
Эти подклассы
BaseRequestHandlerпереопределяют методыsetup()иfinish(), а также предоставляют атрибутыrfileиwfile.-
rfile -
Файловый объект, из которого считывается запрос. Поддерживает интерфейс чтения
io.BufferedIOBase.
-
wfile -
Файловый объект, в который записывается ответ. Поддерживает интерфейс записи
io.BufferedIOBase.
Изменено в версии 3.6:
wfileтакже поддерживает интерфейс записиio.BufferedIOBase. -
Примеры
Пример socketserver.TCPServer
Код сервера:
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The request handler class for our server.
It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""
def handle(self):
# self.request is the TCP socket connected to the client
pieces = [b'']
total = 0
while b'\n' not in pieces[-1] and total < 10_000:
pieces.append(self.request.recv(2000))
total += len(pieces[-1])
self.data = b''.join(pieces)
print(f"Received from {self.client_address[0]}:")
print(self.data.decode("utf-8"))
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
# after we return, the socket will be closed.
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Create the server, binding to localhost on port 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()
Альтернативный класс обработчика запросов, использующий потоки (файлоподобные объекты, упрощающие обмен данными благодаря стандартному файловому интерфейсу):
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile is a file-like object created by the handler.
# We can now use e.g. readline() instead of raw recv() calls.
# We limit ourselves to 10000 bytes to avoid abuse by the sender.
self.data = self.rfile.readline(10000).rstrip()
print(f"{self.client_address[0]} wrote:")
print(self.data.decode("utf-8"))
# Likewise, self.wfile is a file-like object used to write back
# to the client
self.wfile.write(self.data.upper())
Разница в том, что вызов readline() во втором обработчике будет вызывать recv() несколько раз, пока не встретит символ новой строки, тогда как первому обработчику пришлось использовать цикл recv() для накопления данных до появления символа новой строки. Если бы он использовал только один вызов recv() без цикла, то вернул бы лишь то, что успело поступить от клиента. TCP основан на потоковой передаче данных: данные поступают в том порядке, в котором были отправлены, но между вызовами send() или sendall() на стороне клиента и количеством вызовов recv() на сервере, необходимых для их получения, нет взаимосвязи.
Код клиента:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data, "utf-8"))
sock.sendall(b"\n")
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", received)
Результат выполнения примера должен выглядеть примерно так:
Сервер:
$ python TCPServer.py 127.0.0.1 wrote: b'hello world with TCP' 127.0.0.1 wrote: b'python is nice'
Клиент:
$ python TCPClient.py hello world with TCP Sent: hello world with TCP Received: HELLO WORLD WITH TCP $ python TCPClient.py python is nice Sent: python is nice Received: PYTHON IS NICE
Пример socketserver.UDPServer
Код сервера:
import socketserver
class MyUDPHandler(socketserver.BaseRequestHandler):
"""
This class works similar to the TCP handler class, except that
self.request consists of a pair of data and client socket, and since
there is no connection the client address must be given explicitly
when sending data back via sendto().
"""
def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print(f"{self.client_address[0]} wrote:")
print(data)
socket.sendto(data.upper(), self.client_address)
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
server.serve_forever()
Код клиента:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# SOCK_DGRAM is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", received)
Результат выполнения примера должен в точности совпадать с результатом примера TCP-сервера.
Асинхронные примеси
Для создания асинхронных обработчиков используйте классы ThreadingMixIn и ForkingMixIn.
Пример использования класса ThreadingMixIn:
import socket
import threading
import socketserver
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
data = str(self.request.recv(1024), 'ascii')
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response)
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
def client(ip, port, message):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((ip, port))
sock.sendall(bytes(message, 'ascii'))
response = str(sock.recv(1024), 'ascii')
print("Received: {}".format(response))
if __name__ == "__main__":
# Port 0 means to select an arbitrary unused port
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# Start a thread with the server -- that thread will then start one
# more thread for each request
server_thread = threading.Thread(target=server.serve_forever)
# Exit the server thread when the main thread terminates
server_thread.daemon = True
server_thread.start()
print("Server loop running in thread:", server_thread.name)
client(ip, port, "Hello World 1")
client(ip, port, "Hello World 2")
client(ip, port, "Hello World 3")
server.shutdown()
Результат выполнения примера должен выглядеть примерно так:
$ python ThreadedTCPServer.py Server loop running in thread: Thread-1 Received: Thread-2: Hello World 1 Received: Thread-3: Hello World 2 Received: Thread-4: Hello World 3
Класс ForkingMixIn используется аналогичным образом, за исключением того, что сервер запускает новый процесс для каждого запроса. Доступен только на платформах POSIX, поддерживающих fork().
© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/library/socketserver.html