> **Источник:** https://python-all.ru/3.5/library/asyncio-protocol.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# 18.5.4. Транспорты и протоколы (API на основе колбэков)

## 18.5.4.1. Транспорты

Транспорты – это классы, предоставляемые [`asyncio`](https://python-all.ru/3.5/library/asyncio.html#module-asyncio) для абстракции различных видов каналов связи. Обычно транспорт не создаётся вручную; вместо этого вызывается метод [`AbstractEventLoop`](https://python-all.ru/3.5/library/asyncio-eventloop.html#asyncio.AbstractEventLoop), который создаёт транспорт и пытается инициировать базовый канал связи, вызывая колбэк при успехе.

После установки канала связи транспорт всегда связан с экземпляром [протокола](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio-protocol). Затем протокол может вызывать методы транспорта для различных целей.

[`asyncio`](https://python-all.ru/3.5/library/asyncio.html#module-asyncio) в настоящее время реализует транспорты для TCP, UDP, SSL и каналов подпроцессов. Доступные методы транспорта зависят от его типа.

Классы транспортов [не потокобезопасны](https://python-all.ru/3.5/library/asyncio-dev.html#asyncio-multithreading).

### 18.5.4.1.1. BaseTransport

#### `class asyncio.BaseTransport`

Базовый класс для транспортов.

#### `close()`

Закрывает транспорт. Если у транспорта есть буфер для исходящих данных, буферизированные данные будут асинхронно сброшены. Новые данные приниматься не будут. После сброса всех буферизированных данных будет вызван метод `connection_lost()` протокола с аргументом [`None`](https://python-all.ru/3.5/library/constants.html#None).

#### `is_closing()`

Возвращает `True`, если транспорт закрывается или уже закрыт.

Новое в версии 3.5.1.

#### `get_extra_info(name, default=None)`

Возвращает дополнительную информацию о транспорте. *name* – это строка, представляющая часть информации, специфичной для транспорта, которую требуется получить; *default* – значение, возвращаемое, если информация отсутствует.

Этот метод позволяет реализациям транспорта легко предоставлять информацию, специфичную для канала.

- сокет:

  - `'peername'`: удаленный адрес, к которому подключен сокет, результат [`socket.socket.getpeername()`](https://python-all.ru/3.5/library/socket.html#socket.socket.getpeername) (`None` при ошибке)
  - `'socket'`: экземпляр [`socket.socket`](https://python-all.ru/3.5/library/socket.html#socket.socket)
  - `'sockname'`: собственный адрес сокета, результат [`socket.socket.getsockname()`](https://python-all.ru/3.5/library/socket.html#socket.socket.getsockname)
- SSL-сокет:

  - `'compression'`: алгоритм сжатия, используемый в виде строки, или `None`, если соединение не сжато; результат [`ssl.SSLSocket.compression()`](https://python-all.ru/3.5/library/ssl.html#ssl.SSLSocket.compression)
  - `'cipher'`: кортеж из трех значений, содержащий название используемого шифра, версию протокола SSL, определяющую его использование, и количество используемых секретных битов; результат [`ssl.SSLSocket.cipher()`](https://python-all.ru/3.5/library/ssl.html#ssl.SSLSocket.cipher)
  - `'peercert'`: сертификат однорангового узла; результат [`ssl.SSLSocket.getpeercert()`](https://python-all.ru/3.5/library/ssl.html#ssl.SSLSocket.getpeercert)
  - `'sslcontext'`: экземпляр [`ssl.SSLContext`](https://python-all.ru/3.5/library/ssl.html#ssl.SSLContext)
  - `'ssl_object'`: экземпляр [`ssl.SSLObject`](https://python-all.ru/3.5/library/ssl.html#ssl.SSLObject) или [`ssl.SSLSocket`](https://python-all.ru/3.5/library/ssl.html#ssl.SSLSocket)
- канал:

  - `'pipe'`: объект канала
- подпроцесс:

  - `'subprocess'`: экземпляр [`subprocess.Popen`](https://python-all.ru/3.5/library/subprocess.html#subprocess.Popen)

#### `set_protocol(protocol)`

Устанавливает новый протокол. Смену протокола следует выполнять только в случае, если оба протокола документально поддерживают переключение.

Новое в версии 3.5.3.

#### `get_protocol()`

Возвращает текущий протокол.

Новое в версии 3.5.3.

Изменено в версии 3.5.1: информация `'ssl_object'` добавлена к SSL-сокетам.

### 18.5.4.1.2. ReadTransport

#### `class asyncio.ReadTransport`

Интерфейс для транспортов, доступных только для чтения.

#### `pause_reading()`

Приостанавливает приём данных на транспорте. Данные не будут передаваться методу `data_received()` протокола, пока не будет вызван [`resume_reading()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.ReadTransport.resume_reading).

#### `resume_reading()`

Возобновляет приём данных. Метод `data_received()` протокола будет вызван снова, если появились данные для чтения.

### 18.5.4.1.3. WriteTransport

#### `class asyncio.WriteTransport`

Интерфейс для транспортов, доступных только для записи.

#### `abort()`

Закрывает транспорт немедленно, не дожидаясь ожидающих операций до завершения. Буферизованные данные будут потеряны. Новые данные приниматься не будут. Метод `connection_lost()` протокола в конечном итоге будет вызван с аргументом [`None`](https://python-all.ru/3.5/library/constants.html#None).

#### `can_write_eof()`

Возвращает [`True`](https://python-all.ru/3.5/library/constants.html#True), если транспорт поддерживает [`write_eof()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.WriteTransport.write_eof), и [`False`](https://python-all.ru/3.5/library/constants.html#False) в противном случае.

#### `get_write_buffer_size()`

Возвращает текущий размер выходного буфера, используемого транспортом.

#### `get_write_buffer_limits()`

Возвращает пределы *high* и *low* для управления потоком записи. Возвращает кортеж `(low, high)`, где *low* и *high* – положительные числа байтов.

Используйте [`set_write_buffer_limits()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.WriteTransport.set_write_buffer_limits) для установки границ.

Новое в версии 3.4.2.

#### `set_write_buffer_limits(high=None, low=None)`

Устанавливает *high*- и *low*-пределы для управления потоком записи.

Эти два значения (измеряются в байтах) определяют, когда вызываются методы `pause_writing()` и `resume_writing()` протокола. Если заданы, нижний предел должен быть меньше или равен верхнему. Ни *high*, ни *low* не могут быть отрицательными.

`pause_writing()` вызывается, когда размер буфера становится больше или равен значению *high*. Если запись была приостановлена, `resume_writing()` вызывается, когда размер буфера становится меньше или равен значению *low*.

Значения по умолчанию зависят от реализации. Если задан только верхний предел, нижний предел по умолчанию равен значению, зависящему от реализации, которое меньше или равно верхнему пределу. Установка *high* в ноль принудительно устанавливает *low* в ноль и приводит к вызову `pause_writing()` всякий раз, когда буфер становится непустым. Установка *low* в ноль приводит к вызову `resume_writing()` только когда буфер становится пустым. Использование нуля для любого из пределов в целом неоптимально, так как снижает возможности для одновременного выполнения ввода-вывода и вычислений.

Используйте [`get_write_buffer_limits()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.WriteTransport.get_write_buffer_limits) для получения пределов.

#### `write(data)`

Записывает несколько байт *данных* в транспорт.

Этот метод не блокирует выполнение; он буферизует данные и обеспечивает их асинхронную отправку.

#### `writelines(list_of_data)`

Записывает список (или любой итерабельный объект) байтов данных в транспорт. Это функционально эквивалентно вызову [`write()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.WriteTransport.write) для каждого элемента, возвращаемого итератором, но может быть реализовано более эффективно.

#### `write_eof()`

Закрывает конец записи транспорта после сброса буферизованных данных. Данные всё ещё могут приниматься.

Этот метод может вызывать [`NotImplementedError`](https://python-all.ru/3.5/library/exceptions.html#NotImplementedError), если транспорт (например, SSL) не поддерживает полузакрытие.

### 18.5.4.1.4. DatagramTransport

#### `DatagramTransport.sendto(data, addr=None)`

Отправляет байты *data* удалённому узлу, заданному *addr* (зависящий от транспорта целевой адрес). Если *addr* равно [`None`](https://python-all.ru/3.5/library/constants.html#None), данные отправляются на целевой адрес, заданный при создании транспорта.

Этот метод не блокирует выполнение; он буферизует данные и обеспечивает их асинхронную отправку.

#### `DatagramTransport.abort()`

Закрывает транспорт немедленно, не дожидаясь ожидающих операций до завершения. Буферизованные данные будут потеряны. Новые данные приниматься не будут. Метод `connection_lost()` протокола в конечном итоге будет вызван с аргументом [`None`](https://python-all.ru/3.5/library/constants.html#None).

### 18.5.4.1.5. BaseSubprocessTransport

#### `class asyncio.BaseSubprocessTransport`

#### `get_pid()`

Возвращает идентификатор процесса подпроцесса в виде целого числа.

#### `get_pipe_transport(fd)`

Возвращает транспорт для канала связи, соответствующего целочисленному файловому дескриптору *fd*:

- `0`: читаемый потоковый транспорт стандартного ввода (*stdin*) или [`None`](https://python-all.ru/3.5/library/constants.html#None), если подпроцесс был создан без `stdin=PIPE`
- `1`: записываемый потоковый транспорт стандартного вывода (*stdout*) или [`None`](https://python-all.ru/3.5/library/constants.html#None), если подпроцесс был создан без `stdout=PIPE`
- `2`: записываемый потоковый транспорт стандартного вывода ошибок (*stderr*) или [`None`](https://python-all.ru/3.5/library/constants.html#None), если подпроцесс был создан без `stderr=PIPE`
- другой *fd*: [`None`](https://python-all.ru/3.5/library/constants.html#None)

#### `get_returncode()`

Возвращает код возврата подпроцесса в виде целого числа или [`None`](https://python-all.ru/3.5/library/constants.html#None), если он ещё не завершился, аналогично атрибуту [`subprocess.Popen.returncode`](https://python-all.ru/3.5/library/subprocess.html#subprocess.Popen.returncode).

#### `kill()`

Завершает подпроцесс, как в [`subprocess.Popen.kill()`](https://python-all.ru/3.5/library/subprocess.html#subprocess.Popen.kill).

В системах POSIX функция отправляет подпроцессу сигнал SIGKILL. В Windows этот метод является псевдонимом для [`terminate()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.BaseSubprocessTransport.terminate).

#### `send_signal(signal)`

Отправляет подпроцессу номер *signal*, как в [`subprocess.Popen.send_signal()`](https://python-all.ru/3.5/library/subprocess.html#subprocess.Popen.send_signal).

#### `terminate()`

Запрашивает остановку подпроцесса, как в [`subprocess.Popen.terminate()`](https://python-all.ru/3.5/library/subprocess.html#subprocess.Popen.terminate). Этот метод является псевдонимом метода [`close()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.BaseSubprocessTransport.close).

На системах POSIX этот метод отправляет сигнал SIGTERM подпроцессу. В Windows вызывается функция Windows API TerminateProcess() для остановки подпроцесса.

#### `close()`

Запрашивает остановку подпроцесса вызовом метода [`terminate()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.BaseSubprocessTransport.terminate), если подпроцесс ещё не завершился, и закрывает транспорты всех каналов (*stdin*, *stdout* и *stderr*).

## 18.5.4.2. Протоколы

[`asyncio`](https://python-all.ru/3.5/library/asyncio.html#module-asyncio) предоставляет базовые классы, которые можно наследовать для реализации своих сетевых протоколов. Эти классы используются вместе с [транспортами](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio-transport) (см. ниже): протокол разбирает входящие данные и запрашивает запись исходящих данных, а транспорт отвечает за фактический ввод-вывод и буферизацию.

При наследовании класса протокола рекомендуется переопределять определённые методы. Эти методы являются колбэками: они будут вызваны транспортом при определённых событиях (например, при получении некоторых данных); не следует вызывать их самостоятельно, если только вы не реализуете транспорт.

> **Примечание**
>
> Все колбэки имеют реализации по умолчанию, которые являются пустыми. Поэтому нужно реализовывать только те колбэки, которые соответствуют интересующим событиям.

### 18.5.4.2.1. Классы протоколов

#### `class asyncio.Protocol`

Базовый класс для реализации потоковых протоколов (для использования, например, с транспортами TCP и SSL).

#### `class asyncio.DatagramProtocol`

Базовый класс для реализации дейтаграммных протоколов (для использования, например, с транспортами UDP).

#### `class asyncio.SubprocessProtocol`

Базовый класс для реализации протоколов, взаимодействующих с дочерними процессами (через набор однонаправленных каналов).

### 18.5.4.2.2. Колбэки соединения

Эти колбэки могут вызываться для экземпляров [`Protocol`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.Protocol), [`DatagramProtocol`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.DatagramProtocol) и [`SubprocessProtocol`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.SubprocessProtocol):

#### `BaseProtocol.connection_made(transport)`

Вызывается при установлении соединения.

Аргумент *транспорт* представляет соединение. Если он понадобится, его следует сохранить (например, как атрибут).

#### `BaseProtocol.connection_lost(exc)`

Вызывается при потере или закрытии соединения.

The argument is either an exception object or [`None`](https://python-all.ru/3.5/library/constants.html#None). The latter means a regular EOF is received, or the connection was aborted or closed by this side of the connection.

[`connection_made()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.BaseProtocol.connection_made) и [`connection_lost()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.BaseProtocol.connection_lost) вызываются ровно один раз за каждое успешное соединение. Все остальные колбэки вызываются между этими двумя методами, что упрощает управление ресурсами в реализации протокола.

Следующие колбэки могут вызываться только для экземпляров [`SubprocessProtocol`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.SubprocessProtocol):

#### `SubprocessProtocol.pipe_data_received(fd, data)`

Вызывается, когда дочерний процесс записывает данные в свой канал stdout или stderr. *fd* – это целочисленный файловый дескриптор канала. *data* – это непустой объект bytes, содержащий данные.

#### `SubprocessProtocol.pipe_connection_lost(fd, exc)`

Вызывается, когда один из каналов, связывающих с дочерним процессом, закрывается. *fd* – это целочисленный файловый дескриптор закрытого канала.

#### `SubprocessProtocol.process_exited()`

Вызывается, когда дочерний процесс завершился.

### 18.5.4.2.3. Потоковые протоколы

Следующие колбэки вызываются для экземпляров [`Protocol`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.Protocol):

#### `Protocol.data_received(data)`

Вызывается при получении данных. *data* – это непустой объект bytes, содержащий входящие данные.

> **Примечание**
>
> Буферизация, фрагментация или сборка данных зависит от транспорта. В общем случае не следует полагаться на конкретную семантику, а делать разбор достаточно общим и гибким. Однако данные всегда принимаются в правильном порядке.

#### `Protocol.eof_received()`

Вызывается, когда удалённая сторона сигнализирует, что больше не будет отправлять данные (например, вызовом `write_eof()`, если другая сторона также использует asyncio).

Этот метод может вернуть ложное значение (включая `None`), и в этом случае транспорт закроется сам. Если же метод возвращает истинное значение, закрытие транспорта остаётся на усмотрение протокола. Поскольку реализация по умолчанию возвращает `None`, соединение неявно закрывается.

> **Примечание**
>
> Некоторые транспорты, такие как SSL, не поддерживают полузакрытые соединения; в этом случае возврат истинного значения из метода не предотвращает закрытие соединения.

`data_received()` может вызываться произвольное количество раз в течение соединения. Однако `eof_received()` вызывается не более одного раза, и после его вызова `data_received()` уже не будет вызван.

Автомат состояний:

> start -\>
>
> [`connection_made()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.BaseProtocol.connection_made)
>
> \[-\>
>
> [`data_received()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.Protocol.data_received)
>
> \*\] \[-\>
>
> [`eof_received()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.Protocol.eof_received)
>
> ?\] -\>
>
> [`connection_lost()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.BaseProtocol.connection_lost)
>
> -\> end

### 18.5.4.2.4. Протоколы дейтаграмм

Следующие колбэки вызываются для экземпляров [`DatagramProtocol`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.DatagramProtocol).

#### `DatagramProtocol.datagram_received(data, addr)`

Вызывается при получении дейтаграммы. *data* – это байтовый объект, содержащий входящие данные. *addr* – адрес узла, отправляющего данные; точный формат зависит от транспорта.

#### `DatagramProtocol.error_received(exc)`

Вызывается, когда предыдущая операция отправки или получения вызывает исключение [`OSError`](https://python-all.ru/3.5/library/exceptions.html#OSError). *exc* – экземпляр [`OSError`](https://python-all.ru/3.5/library/exceptions.html#OSError).

Этот метод вызывается в редких случаях, когда транспорт (например, UDP) обнаруживает, что дейтаграмма не может быть доставлена получателю. Однако во многих случаях недоставленные дейтаграммы просто молча отбрасываются.

### 18.5.4.2.5. Колбэки управления потоком

Эти колбэки могут вызываться для экземпляров [`Protocol`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.Protocol), [`DatagramProtocol`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.DatagramProtocol) и [`SubprocessProtocol`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.SubprocessProtocol):

#### `BaseProtocol.pause_writing()`

Вызывается, когда буфер транспорта превышает верхнюю границу (high-water mark).

#### `BaseProtocol.resume_writing()`

Вызывается, когда буфер транспорта опускается ниже нижней границы (low-water mark).

Вызовы `pause_writing()` и `resume_writing()` парные – `pause_writing()` вызывается один раз, когда буфер строго превышает верхнюю границу (даже если последующие записи ещё больше увеличивают размер буфера), и затем `resume_writing()` вызывается один раз, когда размер буфера достигает нижней границы.

> **Примечание**
>
> Если размер буфера равен верхней границе, `pause_writing()` не вызывается – он должен быть строго выше. И наоборот, `resume_writing()` вызывается, когда размер буфера меньше или равен нижней границе. Эти граничные условия важны, чтобы всё работало ожидаемым образом, когда одна из границ равна нулю.

> **Примечание**
>
> В системах BSD (OS X, FreeBSD и т.д.) управление потоком не поддерживается для [`DatagramProtocol`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.DatagramProtocol), так как ошибки отправки из-за записи слишком большого количества пакетов трудно обнаружить. Сокет всегда кажется «готовым», и лишние пакеты отбрасываются; исключение [`OSError`](https://python-all.ru/3.5/library/exceptions.html#OSError) с errno, установленным в [`errno.ENOBUFS`](https://python-all.ru/3.5/library/errno.html#errno.ENOBUFS), может быть возбуждено или нет; если оно возбуждено, оно будет сообщено в [`DatagramProtocol.error_received()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.DatagramProtocol.error_received), но в остальном игнорируется.

### 18.5.4.2.6. Корутины и протоколы

Корутины могут быть запланированы в методе протокола с помощью [`ensure_future()`](https://python-all.ru/3.5/library/asyncio-task.html#asyncio.ensure_future), но порядок выполнения не гарантируется. Протоколы не знают о корутинах, созданных в их методах, и поэтому не будут ждать их.

Для надёжного порядка выполнения используйте [потоковые объекты](https://python-all.ru/3.5/library/asyncio-stream.html#asyncio-streams) в корутине с `yield from`. Например, корутину [`StreamWriter.drain()`](https://python-all.ru/3.5/library/asyncio-stream.html#asyncio.StreamWriter.drain) можно использовать для ожидания, пока буфер записи не будет сброшен.

## 18.5.4.3. Примеры протоколов

### 18.5.4.3.1. Протокол TCP-клиента echo

TCP-клиент эхо, использующий метод [`AbstractEventLoop.create_connection()`](https://python-all.ru/3.5/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.create_connection), отправляет данные и ждёт, пока соединение не будет закрыто:

```python
import asyncio

class EchoClientProtocol(asyncio.Protocol):
    def __init__(self, message, loop):
        self.message = message
        self.loop = loop

    def connection_made(self, transport):
        transport.write(self.message.encode())
        print('Data sent: {!r}'.format(self.message))

    def data_received(self, data):
        print('Data received: {!r}'.format(data.decode()))

    def connection_lost(self, exc):
        print('The server closed the connection')
        print('Stop the event loop')
        self.loop.stop()

loop = asyncio.get_event_loop()
message = 'Hello World!'
coro = loop.create_connection(lambda: EchoClientProtocol(message, loop),
                              '127.0.0.1', 8888)
loop.run_until_complete(coro)
loop.run_forever()
loop.close()
```

Цикл событий запускается дважды. Метод [`run_until_complete()`](https://python-all.ru/3.5/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.run_until_complete) предпочтительнее в этом коротком примере, чтобы вызвать исключение, если сервер не прослушивается, вместо того чтобы писать короткую корутину для обработки исключения и остановки работающего цикла. При выходе [`run_until_complete()`](https://python-all.ru/3.5/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.run_until_complete) цикл больше не работает, поэтому нет необходимости останавливать цикл в случае ошибки.

> **См. также**
>
> Пример [TCP-эхо-клиента с использованием потоков данных](https://python-all.ru/3.5/library/asyncio-stream.html#asyncio-tcp-echo-client-streams) использует функцию [`asyncio.open_connection()`](https://python-all.ru/3.5/library/asyncio-stream.html#asyncio.open_connection).

### 18.5.4.3.2. Протокол TCP-сервера echo

TCP-эхо-сервер с использованием метода [`AbstractEventLoop.create_server()`](https://python-all.ru/3.5/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.create_server), отправляет обратно полученные данные и закрывает соединение:

```python
import asyncio

class EchoServerClientProtocol(asyncio.Protocol):
    def connection_made(self, transport):
        peername = transport.get_extra_info('peername')
        print('Connection from {}'.format(peername))
        self.transport = transport

    def data_received(self, data):
        message = data.decode()
        print('Data received: {!r}'.format(message))

        print('Send: {!r}'.format(message))
        self.transport.write(data)

        print('Close the client socket')
        self.transport.close()

loop = asyncio.get_event_loop()
# Каждое клиентское соединение создаёт новый экземпляр протокола.
coro = loop.create_server(EchoServerClientProtocol, '127.0.0.1', 8888)
server = loop.run_until_complete(coro)

# Обслуживать запросы до нажатия Ctrl+C
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

# Закрыть сервер
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
```

`Transport.close()` можно вызвать сразу после [`WriteTransport.write()`](https://python-all.ru/3.5/library/asyncio-protocol.html#asyncio.WriteTransport.write), даже если данные еще не отправлены на сокет: оба метода асинхронны. `yield from` не нужен, поскольку эти методы транспорта не являются корутинами.

> **См. также**
>
> Пример [TCP-эхо-сервера с использованием потоков данных](https://python-all.ru/3.5/library/asyncio-stream.html#asyncio-tcp-echo-server-streams) использует функцию [`asyncio.start_server()`](https://python-all.ru/3.5/library/asyncio-stream.html#asyncio.start_server).

### 18.5.4.3.3. Протокол UDP-эхо-клиента

UDP-эхо-клиент с использованием метода [`AbstractEventLoop.create_datagram_endpoint()`](https://python-all.ru/3.5/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.create_datagram_endpoint) отправляет данные и закрывает транспорт при получении ответа:

```python
import asyncio

class EchoClientProtocol:
    def __init__(self, message, loop):
        self.message = message
        self.loop = loop
        self.transport = None

    def connection_made(self, transport):
        self.transport = transport
        print('Send:', self.message)
        self.transport.sendto(self.message.encode())

    def datagram_received(self, data, addr):
        print("Received:", data.decode())

        print("Close the socket")
        self.transport.close()

    def error_received(self, exc):
        print('Error received:', exc)

    def connection_lost(self, exc):
        print("Socket closed, stop the event loop")
        loop = asyncio.get_event_loop()
        loop.stop()

loop = asyncio.get_event_loop()
message = "Hello World!"
connect = loop.create_datagram_endpoint(
    lambda: EchoClientProtocol(message, loop),
    remote_addr=('127.0.0.1', 9999))
transport, protocol = loop.run_until_complete(connect)
loop.run_forever()
transport.close()
loop.close()
```

### 18.5.4.3.4. Протокол UDP-эхо-сервера

UDP-эхо-сервер с использованием метода [`AbstractEventLoop.create_datagram_endpoint()`](https://python-all.ru/3.5/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.create_datagram_endpoint) отправляет обратно полученные данные:

```python
import asyncio

class EchoServerProtocol:
    def connection_made(self, transport):
        self.transport = transport

    def datagram_received(self, data, addr):
        message = data.decode()
        print('Received %r from %s' % (message, addr))
        print('Send %r to %s' % (message, addr))
        self.transport.sendto(data, addr)

loop = asyncio.get_event_loop()
print("Starting UDP server")
# Будет создан один экземпляр протокола для обслуживания всех клиентских запросов.
listen = loop.create_datagram_endpoint(
    EchoServerProtocol, local_addr=('127.0.0.1', 9999))
transport, protocol = loop.run_until_complete(listen)

try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

transport.close()
loop.close()
```

### 18.5.4.3.5. Регистрация открытого сокета для ожидания данных с помощью протокола

Ожидать, пока сокет получит данные, используя метод [`AbstractEventLoop.create_connection()`](https://python-all.ru/3.5/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.create_connection) с протоколом, а затем закрыть цикл событий

```python
import asyncio
try:
    from socket import socketpair
except ImportError:
    from asyncio.windows_utils import socketpair

# Создаём пару соединённых сокетов
rsock, wsock = socketpair()
loop = asyncio.get_event_loop()

class MyProtocol(asyncio.Protocol):
    transport = None

    def connection_made(self, transport):
        self.transport = transport

    def data_received(self, data):
        print("Received:", data.decode())

        # Готово: закрыть транспорт (он вызовет connection_lost()).
        self.transport.close()

    def connection_lost(self, exc):
        # Сокет закрыт, остановить цикл событий.
        loop.stop()

# Зарегистрировать сокет для ожидания данных.
connect_coro = loop.create_connection(MyProtocol, sock=rsock)
transport, protocol = loop.run_until_complete(connect_coro)

# Симулировать приём данных из сети
loop.call_soon(wsock.send, 'abc'.encode())

# Запустить цикл событий
loop.run_forever()

# Готово, закрыть сокеты и цикл событий.
rsock.close()
wsock.close()
loop.close()
```

> **См. также**
>
> Пример [наблюдения за файловым дескриптором на предмет событий чтения](https://python-all.ru/3.5/library/asyncio-eventloop.html#asyncio-watch-read-event) использует низкоуровневый метод [`AbstractEventLoop.add_reader()`](https://python-all.ru/3.5/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.add_reader) для регистрации файлового дескриптора сокета.
>
> В примере [регистрации открытого сокета для ожидания данных с помощью потоков](https://python-all.ru/3.5/library/asyncio-stream.html#asyncio-register-socket-streams) используются высокоуровневые потоки , созданные функцией [`open_connection()`](https://python-all.ru/3.5/library/asyncio-stream.html#asyncio.open_connection) в корутине.
