socketserver
— A framework for network servers¶
Вихідний код: Lib/socketserver.py
Модуль socketserver
спрощує завдання написання мережевих серверів.
Існує чотири основних класи конкретних серверів:
-
class
socketserver.
TCPServer
(server_address, RequestHandlerClass, bind_and_activate=True)¶ This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server. If bind_and_activate is true, the constructor automatically attempts to invoke
server_bind()
andserver_activate()
. The other parameters are passed to theBaseServer
base class.
-
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
stream server is the address family, which is simply repeated in both Unix
server classes.
-
class
socketserver.
ForkingMixIn
¶ -
class
socketserver.
ThreadingMixIn
¶ Розгалуження та версії потоків кожного типу сервера можна створити за допомогою цих змішаних класів. Наприклад,
ThreadingUDPServer
створюється таким чином:class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
Клас mix-in стоїть на першому місці, оскільки він замінює метод, визначений у
UDPServer
. Налаштування різних атрибутів також змінює поведінку базового серверного механізму.ForkingMixIn
і згадані нижче класи Forking доступні лише на платформах POSIX, які підтримуютьfork()
.socketserver.ForkingMixIn.server_close()
waits until all child processes complete, except ifsocketserver.ForkingMixIn.block_on_close
attribute is false.socketserver.ThreadingMixIn.server_close()
waits until all non-daemon threads complete, except ifsocketserver.ThreadingMixIn.block_on_close
attribute is false. Use daemonic threads by settingThreadingMixIn.daemon_threads
toTrue
to not wait until threads complete.Змінено в версії 3.7:
socketserver.ForkingMixIn.server_close()
andsocketserver.ThreadingMixIn.server_close()
now waits until all child processes and non-daemonic threads complete. Add a newsocketserver.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
¶ Ці класи попередньо визначені за допомогою змішаних класів.
Щоб реалізувати службу, ви повинні отримати клас від 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). See
asyncore
for another way to manage this.
Серверні об’єкти¶
-
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 для виконання дій, специфічних для певної служби, наприклад дій очищення.Нове в версії 3.3.
-
shutdown
()¶ Скажіть циклу
serve_forever()
зупинитися та зачекайте, доки він зупиниться.shutdown()
потрібно викликати, покиserve_forever()
працює в іншому потоці, інакше він блокується.
-
server_close
()¶ Очистити сервер. Може бути перевизначено.
-
address_family
¶ Сімейство протоколів, до якого належить сокет сервера. Типовими прикладами є
socket.AF_INET
іsocket.AF_UNIX
.
-
RequestHandlerClass
¶ Клас обробника запитів, наданий користувачем; екземпляр цього класу створюється для кожного запиту.
-
server_address
¶ The address on which the server is listening. The format of addresses varies depending on the protocol family; see the documentation for the
socket
module for details. For Internet protocols, this is a tuple containing a string giving the address, and an integer port number:('127.0.0.1', 80)
, for example.
-
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
self.request
; the client address asself.client_address
; and the server instance asself.server
, in case it needs access to per-server information.The type of
self.request
is different for datagram or stream services. For stream services,self.request
is a socket object; for datagram services,self.request
is a pair of string and socket.
-
-
class
socketserver.
StreamRequestHandler
¶ -
class
socketserver.
DatagramRequestHandler
¶ These
BaseRequestHandler
subclasses override thesetup()
andfinish()
methods, and provideself.rfile
andself.wfile
attributes. Theself.rfile
andself.wfile
attributes can be read or written, respectively, to get the request data or return data to the client.The
rfile
attributes of both classes support theio.BufferedIOBase
readable interface, andDatagramRequestHandler.wfile
supports theio.BufferedIOBase
writable interface.Змінено в версії 3.6:
StreamRequestHandler.wfile
also supports theio.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("{} wrote:".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 sent
from the client in one sendall()
call.
Це сторона клієнта:
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()
.