socketserver — Фреймворк для мережевих серверів

Вихідний код: Lib/socketserver.py


Модуль socketserver спрощує завдання написання мережевих серверів.

Availability: not Emscripten, not WASI.

This module does not work or is not available on WebAssembly platforms wasm32-emscripten and wasm32-wasi. See WebAssembly platforms for more information.

Існує чотири основних класи конкретних серверів:

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.

Ці чотири класи обробляють запити synchronously; кожен запит має бути виконано перед початком наступного запиту. Це не підходить, якщо виконання кожного запиту займає багато часу, тому що він вимагає багато обчислень або тому, що він повертає багато даних, які клієнт повільно обробляє. Рішення полягає у створенні окремого процесу або потоку для обробки кожного запиту; змішані класи 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 |
+-----------+        +--------------------+

Note that UnixDatagramServer derives from UDPServer, not from UnixStreamServer — the only difference between an IP and a Unix server is the address family.

class socketserver.ForkingMixIn
class socketserver.ThreadingMixIn

Розгалуження та версії потоків кожного типу сервера можна створити за допомогою цих змішаних класів. Наприклад, ThreadingUDPServer створюється таким чином:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

Клас mix-in стоїть на першому місці, оскільки він замінює метод, визначений у UDPServer. Налаштування різних атрибутів також змінює поведінку базового серверного механізму.

ForkingMixIn і згадані нижче класи Forking доступні лише на платформах POSIX, які підтримують fork().

block_on_close

ForkingMixIn.server_close waits until all child processes complete, except if block_on_close attribute is False.

ThreadingMixIn.server_close waits until all non-daemon threads complete, except if block_on_close attribute is False.

daemon_threads

For ThreadingMixIn use daemonic threads by setting ThreadingMixIn.daemon_threads to True to not wait until threads complete.

Змінено в версії 3.7: ForkingMixIn.server_close and ThreadingMixIn.server_close now waits until all child processes and non-daemonic threads complete. Add a new ForkingMixIn.block_on_close class attribute to opt-in for the pre-3.7 behaviour.

class socketserver.ForkingTCPServer
class socketserver.ForkingUDPServer
class socketserver.ThreadingTCPServer
class socketserver.ThreadingUDPServer
class socketserver.ForkingUnixStreamServer
class socketserver.ForkingUnixDatagramServer
class socketserver.ThreadingUnixStreamServer
class socketserver.ThreadingUnixDatagramServer

Ці класи попередньо визначені за допомогою змішаних класів.

Added in version 3.12: The ForkingUnixStreamServer and ForkingUnixDatagramServer classes were added.

Щоб реалізувати службу, ви повинні отримати клас від BaseRequestHandler і перевизначити його метод handle(). Потім ви можете запустити різні версії служби, поєднавши один із класів сервера з класом обробника запитів. Клас обробника запитів має відрізнятися для датаграм або потокових служб. Це можна приховати за допомогою підкласів обробників StreamRequestHandler або DatagramRequestHandler.

Звичайно, вам все одно доведеться використовувати свою голову! Наприклад, немає сенсу використовувати сервер розгалуження, якщо служба містить стан у пам’яті, який можна змінювати різними запитами, оскільки зміни в дочірньому процесі ніколи не досягнуть початкового стану, який зберігається в батьківському процесі та передається кожному дочірньому. . У цьому випадку ви можете використовувати потоковий сервер, але вам, ймовірно, доведеться використовувати блокування для захисту цілісності спільних даних.

З іншого боку, якщо ви створюєте HTTP-сервер, де всі дані зберігаються зовні (наприклад, у файловій системі), синхронний клас, по суті, надаватиме послугу «глухою», поки обробляється один запит, що може бути дуже довго, якщо клієнт повільно отримує всі запитувані дані. Тут підійде сервер потоків або розгалуження.

У деяких випадках може бути доречним обробити частину запиту синхронно, але завершити обробку в розгалуженому дочірньому файлі залежно від даних запиту. Це можна реалізувати за допомогою синхронного сервера та виконання явного розгалуження в методі класу обробника запитів handle().

Another approach to handling multiple simultaneous requests in an environment that supports neither threads nor fork() (or where these are too expensive or inappropriate for the service) is to maintain an explicit table of partially finished requests and to use selectors to decide which request to work on next (or whether to handle a new incoming request). This is particularly important for stream services where each client can potentially be connected for a long time (if threads or subprocesses cannot be used).

Серверні об’єкти

class socketserver.BaseServer(server_address, RequestHandlerClass)

Це суперклас усіх об’єктів сервера в модулі. Він визначає інтерфейс, наведений нижче, але не реалізує більшість методів, що робиться в підкласах. Два параметри зберігаються у відповідних атрибутах 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: Додано виклик service_actions до методу serve_forever.

service_actions()

Це викликається в циклі serve_forever(). Цей метод може бути перевизначений підкласами або класами mixin для виконання дій, специфічних для певної служби, наприклад дій очищення.

Added in version 3.3.

shutdown()

Скажіть циклу serve_forever() зупинитися та зачекайте, доки він зупиниться. shutdown() потрібно викликати, поки serve_forever() працює в іншому потоці, інакше він блокується.

server_close()

Очистити сервер. Може бути перевизначено.

address_family

Сімейство протоколів, до якого належить сокет сервера. Типовими прикладами є socket.AF_INET і socket.AF_UNIX.

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()

Потрібно прийняти запит від сокета та повернути 2-кортеж, що містить новий об’єкт сокета, який буде використовуватися для зв’язку з клієнтом, і адресу клієнта.

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: Додано підтримку протоколу context manager. Вихід із контекстного менеджера еквівалентний виклику server_close().

Об’єкти обробки запитів

class socketserver.BaseRequestHandler

Це суперклас усіх об’єктів обробки запитів. Він визначає інтерфейс, наведений нижче. Конкретний підклас обробника запитів повинен визначати новий метод handle() і може перевизначати будь-які інші методи. Для кожного запиту створюється новий екземпляр підкласу.

setup()

Викликається перед методом handle() для виконання будь-яких необхідних дій ініціалізації. Стандартна реалізація нічого не робить.

handle()

This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as request; the client address as client_address; and the server instance as server, in case it needs access to per-server information.

The type of request is different for datagram or stream services. For stream services, request is a socket object; for datagram services, request is a pair of string and socket.

finish()

Викликається після методу handle() для виконання будь-яких необхідних дій очищення. Стандартна реалізація нічого не робить. Якщо setup() викликає виняток, ця функція не буде викликана.

request

The new socket.socket object to be used to communicate with the client.

client_address

Client address returned by BaseServer.get_request().

server

BaseServer object used for handling the request.

class socketserver.StreamRequestHandler
class socketserver.DatagramRequestHandler

These BaseRequestHandler subclasses override the setup() and finish() methods, and provide rfile and wfile attributes.

rfile

A file object from which receives the request is read. Support the io.BufferedIOBase readable interface.

wfile

A file object to which the reply is written. Support the io.BufferedIOBase writable interface

Змінено в версії 3.6: wfile also supports the io.BufferedIOBase writable interface.

Приклади

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
        self.data = self.request.recv(1024).strip()
        print("Received from {}:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

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
        self.data = self.rfile.readline().strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # Likewise, self.wfile is a file-like object used to write back
        # to the client
        self.wfile.write(self.data.upper())

The difference is that the readline() call in the second handler will call recv() multiple times until it encounters a newline character, while the single recv() call in the first handler will just return what has been received so far from the client’s sendall() call (typically all of it, but this is not guaranteed by the TCP protocol).

Це сторона клієнта:

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 + "\n", "utf-8"))

    # Receive data from the server and shut down
    received = str(sock.recv(1024), "utf-8")

print("Sent:     {}".format(data))
print("Received: {}".format(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("{} wrote:".format(self.client_address[0]))
        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:     {}".format(data))
print("Received: {}".format(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().