asyncio-protocol.md
1> **Источник:** https://python-all.ru/3.4/library/asyncio-protocol.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 18.5.4. Транспорты и протоколы (API на основе колбэков)89## 18.5.4.1. Транспорты1011Транспорты – это классы, предоставляемые [`asyncio`](https://python-all.ru/3.4/library/asyncio.html#module-asyncio) для абстрагирования различных видов каналов связи. Обычно не требуется создавать транспорт самостоятельно; вместо этого вызывается метод [`BaseEventLoop`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop), который создаёт транспорт и пытается инициировать базовый канал связи, вызывая колбэк при успешном завершении.1213После установки канала связи транспорт всегда связан с экземпляром [*протокола*](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio-protocol). Затем протокол может вызывать методы транспорта для различных целей.1415[`asyncio`](https://python-all.ru/3.4/library/asyncio.html#module-asyncio) в настоящее время реализует транспорты для TCP, UDP, SSL и каналов подпроцессов. Доступные методы транспорта зависят от его типа.1617Классы транспортов [*не потокобезопасны*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-multithreading).1819### 18.5.4.1.1. BaseTransport2021#### `class asyncio.BaseTransport`2223Базовый класс для транспортов.2425#### `close(self)`2627Закрывает транспорт. Если у транспорта есть буфер для исходящих данных, буферизованные данные будут асинхронно сброшены. Новые данные приниматься не будут. После того как все буферизованные данные будут сброшены, будет вызван метод `connection_lost()` протокола с [`None`](https://python-all.ru/3.4/library/constants.html#None) в качестве аргумента.2829#### `is_closing(self)`3031Возвращает `True`, если транспорт закрывается или закрыт.3233Новое в версии 3.4.4.3435#### `get_extra_info(name, default=None)`3637Возвращает дополнительную информацию о транспорте. *name* – это строка, представляющая часть информации, специфичной для транспорта, которую требуется получить; *default* – значение, возвращаемое, если информация отсутствует.3839Этот метод позволяет реализациям транспорта легко предоставлять информацию, специфичную для канала.4041- сокет:4243 - `'peername'`: удалённый адрес, к которому подключён сокет, результат [`socket.socket.getpeername()`](https://python-all.ru/3.4/library/socket.html#socket.socket.getpeername) (`None` при ошибке)44 - `'socket'`: экземпляр [`socket.socket`](https://python-all.ru/3.4/library/socket.html#socket.socket)45 - `'sockname'`: собственный адрес сокета, результат [`socket.socket.getsockname()`](https://python-all.ru/3.4/library/socket.html#socket.socket.getsockname)46- SSL-сокет:4748 - `'compression'`: строка с названием используемого алгоритма сжатия или `None`, если соединение не сжато; результат [`ssl.SSLSocket.compression()`](https://python-all.ru/3.4/library/ssl.html#ssl.SSLSocket.compression)49 - `'cipher'`: кортеж из трёх значений, содержащий название используемого шифра, версию протокола SSL, в которой он определён, и количество используемых секретных бит; результат [`ssl.SSLSocket.cipher()`](https://python-all.ru/3.4/library/ssl.html#ssl.SSLSocket.cipher)50 - `'peercert'`: сертификат однорангового узла; результат [`ssl.SSLSocket.getpeercert()`](https://python-all.ru/3.4/library/ssl.html#ssl.SSLSocket.getpeercert)51 - `'sslcontext'`: экземпляр [`ssl.SSLContext`](https://python-all.ru/3.4/library/ssl.html#ssl.SSLContext)52 - `'ssl_object'`: экземпляр `ssl.SSLObject` или [`ssl.SSLSocket`](https://python-all.ru/3.4/library/ssl.html#ssl.SSLSocket)53- канал:5455 - `'pipe'`: объект канала (pipe)56- подпроцесс:5758 - `'подпроцесс'`: экземпляр [`subprocess.Popen`](https://python-all.ru/3.4/library/subprocess.html#subprocess.Popen)5960Изменено в версии 3.4.4: `'ssl_object'` информация была добавлена для SSL-сокетов.6162### 18.5.4.1.2. ReadTransport6364#### `class asyncio.ReadTransport`6566Интерфейс для транспортов, доступных только для чтения.6768#### `pause_reading()`6970Приостанавливает приём данных на транспорте. Данные не будут передаваться методу `data_received()` протокола до тех пор, пока не будет вызван [`resume_reading()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.ReadTransport.resume_reading).7172#### `resume_reading()`7374Возобновляет приём данных. Метод `data_received()` протокола снова будет вызываться, если появятся данные для чтения.7576### 18.5.4.1.3. WriteTransport7778#### `class asyncio.WriteTransport`7980Интерфейс для транспортов, доступных только для записи.8182#### `abort()`8384Закрывает транспорт немедленно, не дожидаясь завершения ожидающих операций. Буферизованные данные будут потеряны. Новые данные приниматься не будут. Метод `connection_lost()` протокола в конечном итоге будет вызван с [`None`](https://python-all.ru/3.4/library/constants.html#None) в качестве аргумента.8586#### `can_write_eof()`8788Возвращает [`True`](https://python-all.ru/3.4/library/constants.html#True), если транспорт поддерживает [`write_eof()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.WriteTransport.write_eof), [`False`](https://python-all.ru/3.4/library/constants.html#False) в противном случае.8990#### `get_write_buffer_size()`9192Возвращает текущий размер выходного буфера, используемого транспортом.9394#### `get_write_buffer_limits()`9596Получает верхний (*high*) и нижний (*low*) пороги для управления потоком записи. Возвращает кортеж `(low, high)`, где *low* и *high* – положительные числа байт.9798Для установки порогов используйте [`set_write_buffer_limits()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.WriteTransport.set_write_buffer_limits).99100Новое в версии 3.4.2.101102#### `set_write_buffer_limits(high=None, low=None)`103104Устанавливает *high*- и *low*-пределы для управления потоком записи.105106Эти два значения определяют, когда вызываются методы протокола `pause_writing()` и `resume_writing()`. Если заданы, нижний порог должен быть меньше или равен верхнему порогу. Ни *high*, ни *low* не могут быть отрицательными.107108Значения по умолчанию зависят от реализации. Если задан только верхний порог, нижний порог по умолчанию принимает значение, зависящее от реализации, которое меньше или равно верхнему порогу. Установка *high* в ноль также устанавливает *low* в ноль и приводит к вызову `pause_writing()` всякий раз, когда буфер становится непустым. Установка *low* в ноль приводит к вызову `resume_writing()` только после того, как буфер опустеет. Использование нуля для любого из порогов обычно неоптимально, так как это снижает возможности для параллельного выполнения ввода-вывода и вычислений.109110Для получения порогов используйте [`get_write_buffer_limits()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.WriteTransport.get_write_buffer_limits).111112#### `write(data)`113114Записывает несколько байт *данных* в транспорт.115116Этот метод не блокирует выполнение; он буферизует данные и обеспечивает их асинхронную отправку.117118#### `writelines(list_of_data)`119120Записывает список (или любой итерируемый объект) байтов данных в транспорт. Функционально это эквивалентно вызову [`write()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.WriteTransport.write) для каждого элемента, возвращаемого итерируемым объектом, но может быть реализовано более эффективно.121122#### `write_eof()`123124Закрывает конец записи транспорта после сброса буферизованных данных. Данные всё ещё могут приниматься.125126Этот метод может возбуждать [`NotImplementedError`](https://python-all.ru/3.4/library/exceptions.html#NotImplementedError), если транспорт (например, SSL) не поддерживает полузакрытие.127128### 18.5.4.1.4. DatagramTransport129130#### `DatagramTransport.sendto(data, addr=None)`131132Отправляет байты *data* удалённому узлу, заданному *addr* (целевой адрес, зависящий от транспорта). Если *addr* равно [`None`](https://python-all.ru/3.4/library/constants.html#None), данные отправляются на целевой адрес, указанный при создании транспорта.133134Этот метод не блокирует выполнение; он буферизует данные и обеспечивает их асинхронную отправку.135136#### `DatagramTransport.abort()`137138Немедленно закрывает транспорт, не дожидаясь завершения ожидающих операций. Буферизованные данные будут потеряны. Новые данные поступать не будут. Метод протокола `connection_lost()` в конечном итоге будет вызван с аргументом [`None`](https://python-all.ru/3.4/library/constants.html#None).139140### 18.5.4.1.5. BaseSubprocessTransport141142#### `class asyncio.BaseSubprocessTransport`143144#### `get_pid()`145146Возвращает идентификатор процесса подпроцесса в виде целого числа.147148#### `get_pipe_transport(fd)`149150Возвращает транспорт для канала связи, соответствующего целочисленному файловому дескриптору *fd*:151152- `0`: потоковый транспорт для чтения из стандартного ввода (*stdin*), или [`None`](https://python-all.ru/3.4/library/constants.html#None), если подпроцесс не был создан с `stdin=PIPE`153- `1`: потоковый транспорт для записи в стандартный вывод (*stdout*), или [`None`](https://python-all.ru/3.4/library/constants.html#None), если подпроцесс не был создан с `stdout=PIPE`154- `2`: потоковый транспорт для записи в стандартный вывод ошибок (*stderr*), или [`None`](https://python-all.ru/3.4/library/constants.html#None), если подпроцесс не был создан с `stderr=PIPE`155- другие *fd*: [`None`](https://python-all.ru/3.4/library/constants.html#None)156157#### `get_returncode()`158159Возвращает код возврата подпроцесса в виде целого числа или [`None`](https://python-all.ru/3.4/library/constants.html#None), если он ещё не завершился, аналогично атрибуту [`subprocess.Popen.returncode`](https://python-all.ru/3.4/library/subprocess.html#subprocess.Popen.returncode).160161#### `kill(self)`162163Завершает подпроцесс, как в [`subprocess.Popen.kill()`](https://python-all.ru/3.4/library/subprocess.html#subprocess.Popen.kill).164165На системах POSIX функция отправляет SIGKILL подпроцессу. В Windows этот метод является псевдонимом для [`terminate()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.BaseSubprocessTransport.terminate).166167#### `send_signal(signal)`168169Отправляет номер *signal* подпроцессу, как в [`subprocess.Popen.send_signal()`](https://python-all.ru/3.4/library/subprocess.html#subprocess.Popen.send_signal).170171#### `terminate()`172173Просит подпроцесс остановиться, как в [`subprocess.Popen.terminate()`](https://python-all.ru/3.4/library/subprocess.html#subprocess.Popen.terminate). Этот метод является псевдонимом метода [`close()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.BaseSubprocessTransport.close).174175На системах POSIX этот метод отправляет сигнал SIGTERM подпроцессу. В Windows вызывается функция Windows API TerminateProcess() для остановки подпроцесса.176177#### `close()`178179Просит подпроцесс остановиться, вызывая метод [`terminate()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.BaseSubprocessTransport.terminate), если подпроцесс ещё не завершился, и закрывает транспорты всех каналов (*stdin*, *stdout* и *stderr*).180181## 18.5.4.2. Протоколы182183[`asyncio`](https://python-all.ru/3.4/library/asyncio.html#module-asyncio) предоставляет базовые классы, от которых можно наследоваться для реализации собственных сетевых протоколов. Эти классы используются совместно с [*транспортами*](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio-transport) (см. ниже): протокол разбирает входящие данные и запрашивает запись исходящих, в то время как транспорт отвечает за фактический ввод-вывод и буферизацию.184185При наследовании класса протокола рекомендуется переопределять определённые методы. Эти методы являются колбэками: они будут вызваны транспортом при определённых событиях (например, при получении некоторых данных); не следует вызывать их самостоятельно, если только вы не реализуете транспорт.186187> **Примечание**188>189> Все колбэки имеют реализации по умолчанию, которые являются пустыми. Поэтому нужно реализовывать только те колбэки, которые соответствуют интересующим событиям.190191### 18.5.4.2.1. Классы протоколов192193#### `class asyncio.Protocol`194195Базовый класс для реализации потоковых протоколов (для использования, например, с транспортами TCP и SSL).196197#### `class asyncio.DatagramProtocol`198199Базовый класс для реализации дейтаграммных протоколов (для использования, например, с транспортами UDP).200201#### `class asyncio.SubprocessProtocol`202203Базовый класс для реализации протоколов, взаимодействующих с дочерними процессами (через набор однонаправленных каналов).204205### 18.5.4.2.2. Колбэки соединения206207Эти колбэки могут вызываться на экземплярах [`Protocol`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.Protocol), [`DatagramProtocol`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.DatagramProtocol) и [`SubprocessProtocol`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.SubprocessProtocol):208209#### `BaseProtocol.connection_made(transport)`210211Вызывается при установлении соединения.212213Аргумент *транспорт* представляет соединение. Если он понадобится, его следует сохранить (например, как атрибут).214215#### `BaseProtocol.connection_lost(exc)`216217Вызывается при потере или закрытии соединения.218219Аргументом является либо объект исключения, либо [`None`](https://python-all.ru/3.4/library/constants.html#None). Последнее означает, что получен обычный EOF, или соединение было прервано или закрыто этой стороной соединения.220221[`connection_made()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.BaseProtocol.connection_made) и [`connection_lost()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.BaseProtocol.connection_lost) вызываются ровно один раз для каждого успешного соединения. Все остальные колбэки будут вызываться между этими двумя методами, что упрощает управление ресурсами в реализации вашего протокола.222223Следующие колбэки могут вызываться только на экземплярах [`SubprocessProtocol`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.SubprocessProtocol):224225#### `SubprocessProtocol.pipe_data_received(fd, data)`226227Вызывается, когда дочерний процесс записывает данные в свой канал stdout или stderr. *fd* – это целочисленный файловый дескриптор канала. *data* – это непустой объект bytes, содержащий данные.228229#### `SubprocessProtocol.pipe_connection_lost(fd, exc)`230231Вызывается, когда один из каналов, связывающих с дочерним процессом, закрывается. *fd* – это целочисленный файловый дескриптор закрытого канала.232233#### `SubprocessProtocol.process_exited()`234235Вызывается, когда дочерний процесс завершился.236237### 18.5.4.2.3. Потоковые протоколы238239Следующие колбэки вызываются на экземплярах [`Protocol`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.Protocol):240241#### `Protocol.data_received(data)`242243Вызывается при получении данных. *data* – это непустой объект bytes, содержащий входящие данные.244245> **Примечание**246>247> Буферизация, фрагментация или сборка данных зависит от транспорта. В общем случае не следует полагаться на конкретную семантику, а делать разбор достаточно общим и гибким. Однако данные всегда принимаются в правильном порядке.248249#### `Protocol.eof_received()`250251Вызывается, когда другая сторона сигнализирует, что больше не будет отправлять данные (например, вызовом `write_eof()`, если другая сторона тоже использует asyncio).252253Этот метод может вернуть ложное значение (включая None), и в этом случае транспорт закроется сам. И наоборот, если метод возвращает истинное значение, закрытие транспорта ложится на протокол. Поскольку реализация по умолчанию возвращает None, она неявно закрывает соединение.254255> **Примечание**256>257> Некоторые транспорты, такие как SSL, не поддерживают полузакрытые соединения; в этом случае возврат истинного значения из метода не предотвращает закрытие соединения.258259`data_received()` может вызываться произвольное количество раз во время соединения. Однако `eof_received()` вызывается не более одного раза и, если был вызван, `data_received()` после него вызываться не будет.260261Автомат состояний:262263> start -\>264>265> [`connection_made()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.BaseProtocol.connection_made)266>267> \[-\>268>269> [`data_received()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.Protocol.data_received)270>271> \*\] \[-\>272>273> [`eof_received()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.Protocol.eof_received)274>275> ?\] -\>276>277> [`connection_lost()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.BaseProtocol.connection_lost)278>279> -\> end280281### 18.5.4.2.4. Протоколы дейтаграмм282283Следующие колбэки вызываются на экземплярах [`DatagramProtocol`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.DatagramProtocol).284285#### `DatagramProtocol.datagram_received(data, addr)`286287Вызывается при получении дейтаграммы. *data* – это байтовый объект, содержащий входящие данные. *addr* – адрес узла, отправляющего данные; точный формат зависит от транспорта.288289#### `DatagramProtocol.error_received(exc)`290291Вызывается, когда предыдущая операция отправки или получения вызывает [`OSError`](https://python-all.ru/3.4/library/exceptions.html#OSError). *exc* – это экземпляр [`OSError`](https://python-all.ru/3.4/library/exceptions.html#OSError).292293Этот метод вызывается в редких случаях, когда транспорт (например, UDP) обнаруживает, что дейтаграмма не может быть доставлена получателю. Однако во многих случаях недоставленные дейтаграммы просто молча отбрасываются.294295### 18.5.4.2.5. Колбэки управления потоком296297Эти колбэки могут вызываться на экземплярах [`Protocol`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.Protocol), [`DatagramProtocol`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.DatagramProtocol) и [`SubprocessProtocol`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.SubprocessProtocol):298299#### `BaseProtocol.pause_writing()`300301Вызывается, когда буфер транспорта превышает верхнюю границу (high-water mark).302303#### `BaseProtocol.resume_writing()`304305Вызывается, когда буфер транспорта опускается ниже нижней границы (low-water mark).306307Вызовы `pause_writing()` и `resume_writing()` парны – `pause_writing()` вызывается один раз, когда буфер строго превышает верхнюю границу (high-water mark) (даже если последующие записи ещё больше увеличат размер буфера), и в итоге `resume_writing()` вызывается один раз, когда размер буфера достигает нижней границы (low-water mark).308309> **Примечание**310>311> Если размер буфера равен верхней границе (high-water mark), `pause_writing()` не вызывается – он должен строго превышать её. Наоборот, `resume_writing()` вызывается, когда размер буфера равен или меньше нижней границы (low-water mark). Эти конечные условия важны, чтобы всё работало как ожидается, когда любая из границ равна нулю.312313> **Примечание**314>315> В системах BSD (OS X, FreeBSD и др.) управление потоком (flow control) не поддерживается для [`DatagramProtocol`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.DatagramProtocol), потому что ошибки отправки, вызванные записью слишком большого количества пакетов, сложно обнаружить. Сокет всегда выглядит «готовым», и лишние пакеты отбрасываются; [`OSError`](https://python-all.ru/3.4/library/exceptions.html#OSError) с errno, установленным в [`errno.ENOBUFS`](https://python-all.ru/3.4/library/errno.html#errno.ENOBUFS), может быть выброшено, а может и нет; если оно выбрасывается, оно будет передано в [`DatagramProtocol.error_received()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.DatagramProtocol.error_received), но в остальном игнорируется.316317### 18.5.4.2.6. Корутины и протоколы318319Корутины можно планировать в методе протокола с помощью [`async()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.async), но гарантии относительно порядка выполнения нет. Протоколы не знают о корутинах, созданных в методах протокола, и поэтому не будут их ждать.320321Чтобы получить надёжный порядок выполнения, используйте [*объекты потоков данных*](https://python-all.ru/3.4/library/asyncio-stream.html#asyncio-streams) в корутине с `yield from`. Например, корутина [`StreamWriter.drain()`](https://python-all.ru/3.4/library/asyncio-stream.html#asyncio.StreamWriter.drain) может использоваться для ожидания очистки буфера записи.322323## 18.5.4.3. Примеры протоколов324325### 18.5.4.3.1. Протокол TCP-клиента echo326327TCP-эхо-клиент с использованием метода [`BaseEventLoop.create_connection()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.create_connection), отправляет данные и ждёт, пока соединение не будет закрыто:328329```python330import asyncio331332class EchoClientProtocol(asyncio.Protocol):333 def __init__(self, message, loop):334 self.message = message335 self.loop = loop336337 def connection_made(self, transport):338 transport.write(self.message.encode())339 print('Data sent: {!r}'.format(self.message))340341 def data_received(self, data):342 print('Data received: {!r}'.format(data.decode()))343344 def connection_lost(self, exc):345 print('The server closed the connection')346 print('Stop the event loop')347 self.loop.stop()348349loop = asyncio.get_event_loop()350message = 'Hello World!'351coro = loop.create_connection(lambda: EchoClientProtocol(message, loop),352 '127.0.0.1', 8888)353loop.run_until_complete(coro)354loop.run_forever()355loop.close()356```357358Цикл событий запускается дважды. В этом коротком примере предпочтительнее использовать метод [`run_until_complete()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.run_until_complete), чтобы вызвать исключение, если сервер не слушает, вместо того чтобы писать короткую корутину для обработки исключения и остановки работающего цикла. При выходе из [`run_until_complete()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.run_until_complete) цикл уже не работает, поэтому нет необходимости останавливать цикл в случае ошибки.359360> **См. также**361>362> Пример [*TCP-эхо-клиента с использованием потоков данных (streams)*](https://python-all.ru/3.4/library/asyncio-stream.html#asyncio-tcp-echo-client-streams) использует функцию [`asyncio.open_connection()`](https://python-all.ru/3.4/library/asyncio-stream.html#asyncio.open_connection).363364### 18.5.4.3.2. Протокол TCP-сервера echo365366TCP-эхо-сервер с использованием метода [`BaseEventLoop.create_server()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.create_server), отправляет обратно полученные данные и закрывает соединение:367368```python369import asyncio370371class EchoServerClientProtocol(asyncio.Protocol):372 def connection_made(self, transport):373 peername = transport.get_extra_info('peername')374 print('Connection from {}'.format(peername))375 self.transport = transport376377 def data_received(self, data):378 message = data.decode()379 print('Data received: {!r}'.format(message))380381 print('Send: {!r}'.format(message))382 self.transport.write(data)383384 print('Close the client socket')385 self.transport.close()386387loop = asyncio.get_event_loop()388# Каждое клиентское соединение создаёт новый экземпляр протокола.389coro = loop.create_server(EchoServerClientProtocol, '127.0.0.1', 8888)390server = loop.run_until_complete(coro)391392# Обслуживать запросы до нажатия Ctrl+C393print('Serving on {}'.format(server.sockets[0].getsockname()))394try:395 loop.run_forever()396except KeyboardInterrupt:397 pass398399# Закрыть сервер400server.close()401loop.run_until_complete(server.wait_closed())402loop.close()403```404405`Transport.close()` можно вызвать сразу после [`WriteTransport.write()`](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio.WriteTransport.write), даже если данные ещё не отправлены в сокет: оба метода асинхронны. `yield from` не требуется, потому что эти методы транспорта не являются корутинами.406407> **См. также**408>409> Пример [*TCP-эхо-сервера с использованием потоков данных (streams)*](https://python-all.ru/3.4/library/asyncio-stream.html#asyncio-tcp-echo-server-streams) использует функцию [`asyncio.start_server()`](https://python-all.ru/3.4/library/asyncio-stream.html#asyncio.start_server).410411### 18.5.4.3.3. Протокол UDP-эхо-клиента412413UDP-эхо-клиент с использованием метода [`BaseEventLoop.create_datagram_endpoint()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.create_datagram_endpoint), отправляет данные и закрывает транспорт после получения ответа:414415```python416import asyncio417418class EchoClientProtocol:419 def __init__(self, message, loop):420 self.message = message421 self.loop = loop422 self.transport = None423424 def connection_made(self, transport):425 self.transport = transport426 print('Send:', self.message)427 self.transport.sendto(self.message.encode())428429 def datagram_received(self, data, addr):430 print("Received:", data.decode())431432 print("Close the socket")433 self.transport.close()434435 def error_received(self, exc):436 print('Error received:', exc)437438 def connection_lost(self, exc):439 print("Socket closed, stop the event loop")440 loop = asyncio.get_event_loop()441 loop.stop()442443loop = asyncio.get_event_loop()444message = "Hello World!"445connect = loop.create_datagram_endpoint(446 lambda: EchoClientProtocol(message, loop),447 remote_addr=('127.0.0.1', 9999))448transport, protocol = loop.run_until_complete(connect)449loop.run_forever()450transport.close()451loop.close()452```453454### 18.5.4.3.4. Протокол UDP-эхо-сервера455456UDP-эхо-сервер с использованием метода [`BaseEventLoop.create_datagram_endpoint()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.create_datagram_endpoint) отправляет обратно полученные данные:457458```python459import asyncio460461class EchoServerProtocol:462 def connection_made(self, transport):463 self.transport = transport464465 def datagram_received(self, data, addr):466 message = data.decode()467 print('Received %r from %s' % (message, addr))468 print('Send %r to %s' % (message, addr))469 self.transport.sendto(data, addr)470471loop = asyncio.get_event_loop()472print("Starting UDP server")473# Будет создан один экземпляр протокола для обслуживания всех клиентских запросов.474listen = loop.create_datagram_endpoint(475 EchoServerProtocol, local_addr=('127.0.0.1', 9999))476transport, protocol = loop.run_until_complete(listen)477478try:479 loop.run_forever()480except KeyboardInterrupt:481 pass482483transport.close()484loop.close()485```486487### 18.5.4.3.5. Регистрация открытого сокета для ожидания данных с помощью протокола488489Ожидание получения данных сокетом с помощью метода [`BaseEventLoop.create_connection()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.create_connection) с протоколом, а затем закрытие цикла событий490491```python492import asyncio493try:494 from socket import socketpair495except ImportError:496 from asyncio.windows_utils import socketpair497498# Создаём пару соединённых сокетов499rsock, wsock = socketpair()500loop = asyncio.get_event_loop()501502class MyProtocol(asyncio.Protocol):503 transport = None504505 def connection_made(self, transport):506 self.transport = transport507508 def data_received(self, data):509 print("Received:", data.decode())510511 # Готово: закрыть транспорт (он вызовет connection_lost()).512 self.transport.close()513514 def connection_lost(self, exc):515 # Сокет закрыт, остановить цикл событий.516 loop.stop()517518# Зарегистрировать сокет для ожидания данных.519connect_coro = loop.create_connection(MyProtocol, sock=rsock)520transport, protocol = loop.run_until_complete(connect_coro)521522# Симулировать приём данных из сети523loop.call_soon(wsock.send, 'abc'.encode())524525# Запустить цикл событий526loop.run_forever()527528# Готово, закрыть сокеты и цикл событий.529rsock.close()530wsock.close()531loop.close()532```533534> **См. также**535>536> Пример [*наблюдения за файловым дескриптором для событий чтения*](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio-watch-read-event) использует низкоуровневый метод [`BaseEventLoop.add_reader()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.add_reader) для регистрации файлового дескриптора сокета.537>538> Пример [*регистрации открытого сокета для ожидания данных с помощью потоков данных (streams)*](https://python-all.ru/3.4/library/asyncio-stream.html#asyncio-register-socket-streams) использует высокоуровневые потоки данных, созданные функцией [`open_connection()`](https://python-all.ru/3.4/library/asyncio-stream.html#asyncio.open_connection) в корутине.539