Peer-to-peer — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Как работает BitTorrent)
м (rollbackEdits.php mass rollback)
 
(не показаны 2 промежуточные версии 2 участников)
Строка 68: Строка 68:
 
Трекер представляет из себя HTTP/HTTPS сервис, который отвечает на HTTP GET запросы. Запрос включает информацию о файле и дополнительную статистическую информацию о торренте. Ответ на запрос содержит список пиров, участвующих в данном торренте.
 
Трекер представляет из себя HTTP/HTTPS сервис, который отвечает на HTTP GET запросы. Запрос включает информацию о файле и дополнительную статистическую информацию о торренте. Ответ на запрос содержит список пиров, участвующих в данном торренте.
  
Задача клиента --- получить от сервера информацию о пирах через HTTP соединение и далее, используя TCP соединение связываться с пирами для получения/передечи данных.
+
Задача клиента --- получить от сервера информацию о пирах через HTTP соединение и далее, используя TCP соединение, связываться с пирами для получения/передачи данных.
Рассмотрим пример последовательности действий, следуя которой можно создать примитивный BitTorrent клиент.
+
Рассмотрим пример последовательности действий, следуя которой, можно создать примитивный BitTorrent клиент.
  
Для начала, можете найти в интернете любой .torrent файл. В таком файле содержится закодированая информация о торренте. В данном случае используется Bencoding кодирование про которое можно подробнее прочитать [https://wiki.theory.org/BitTorrentSpecification#Bencoding здесь], для этого, например есть библиотека для питона bencode 3rd party library(pip install bencode).
+
Для начала, можете найти в интернете любой .torrent файл. В таком файле содержится закодированная информация о торренте. В данном случае используется Bencoding кодирование про которое можно подробнее прочитать [https://wiki.theory.org/BitTorrentSpecification#Bencoding здесь], для этого, например, есть библиотека для питона bencode 3rd party library(pip install bencode).
  
 
Потребуется расшифровать и распарсить файл. Из всего, как минимум, понадобится часть announce url и info, в последней содержатся такие поля как piece length(длина кусочка), pieces(список хешей кусочков), paths и lengths для отдельных файлов (структура для торрента с отдельным файлом и несколькими может несколько различаться).
 
Потребуется расшифровать и распарсить файл. Из всего, как минимум, понадобится часть announce url и info, в последней содержатся такие поля как piece length(длина кусочка), pieces(список хешей кусочков), paths и lengths для отдельных файлов (структура для торрента с отдельным файлом и несколькими может несколько различаться).
  
Далее, нужно сделать GET запрос серверу, используя announce url в формате ‘announce-url?param=value&param=value&…’. Подробнее узнать про кодирование url можно [https://en.wikipedia.org/wiki/Percent-encoding здесь], а так же можно воспользоваться питоновской библиотекой (pip install requests). О параметрах запроса можно узнать [https://wiki.theory.org/BitTorrentSpecification#Tracker_Request_Parameters тут].
+
Далее, нужно сделать GET запрос серверу, используя announce url в формате ‘announce-url?param=value&param=value&…’. Подробнее узнать про кодирование url можно [https://en.wikipedia.org/wiki/Percent-encoding здесь], а также можно воспользоваться питоновской библиотекой (pip install requests). О параметрах запроса можно узнать [https://wiki.theory.org/BitTorrentSpecification#Tracker_Request_Parameters тут].
Примеры параметров: 'info_hash' - хеш info части разкодированого torrent файла посчитаный SHA1 алгоритмом, ‘peer_id’ - строка из 20 байтов, подроднее про формат [https://wiki.theory.org/BitTorrentSpecification#peer_id тут].
+
Примеры параметров: 'info_hash' - хеш info части раскодированого torrent файла посчитанный SHA1 алгоритмом, ‘peer_id’ - строка из 20 байтов, подробнее про формат [https://wiki.theory.org/BitTorrentSpecification#peer_id тут].
  
 
В ответ от сервера клиент получит закодированый список пиров. Используйте Benconde раскодировщик, чтобы в части peers найти список адресов в формате ip_address:port.
 
В ответ от сервера клиент получит закодированый список пиров. Используйте Benconde раскодировщик, чтобы в части peers найти список адресов в формате ip_address:port.
Строка 85: Строка 85:
 
handshake: <pstrlen><pstr><reserved><info_hash><peer_id>
 
handshake: <pstrlen><pstr><reserved><info_hash><peer_id>
 
info_hash и peer_id уже встречались, а для текущей версии протокола 'pstrlen'=19, 'pstr'=BitTorrent protoco, 'Reserved' восьмибитовая строка.
 
info_hash и peer_id уже встречались, а для текущей версии протокола 'pstrlen'=19, 'pstr'=BitTorrent protoco, 'Reserved' восьмибитовая строка.
От пира следует ожидать сообщение в аналогичном формате, и после этого проверить соответствуют ли поля info_hash и peer_id ожидаемому, в случае несоответствия лучше сразу закрыть соединение.
+
От пира следует ожидать сообщение в аналогичном формате, и после этого проверить, соответствуют ли поля info_hash и peer_id ожидаемому, в случае несоответствия лучше сразу закрыть соединение.
  
 
Дальше существует 11 типов возможных сообщений: keep-alive, choke, unchoke, interested, not-interested, have, bitfield, request, piece, cancel, and port. Описание всех видов [https://wiki.theory.org/BitTorrentSpecification#Messages тут].
 
Дальше существует 11 типов возможных сообщений: keep-alive, choke, unchoke, interested, not-interested, have, bitfield, request, piece, cancel, and port. Описание всех видов [https://wiki.theory.org/BitTorrentSpecification#Messages тут].
  
Сообщение состоит из 4 байтов задающих длину сообщения, 1 байт задает id сообщеня(у сообщения вида Keep-alive этот бит пропускается), и хвост с дополнительной информацией.
+
Сообщение состоит из 4 байтов задающих длину сообщения, 1 байт задает id сообщения(у сообщения вида Keep-alive этот бит пропускается), и хвост с дополнительной информацией.
  
* Сообщения типа Have говорят о наличии определенного кусочка у пира, номер кусочка задается 4 байтовым хвастом.
+
* Сообщения типа Have говорят о наличии определенного кусочка у пира, номер кусочка задается 4 байтовым хвостом.
 
* Сообщение типа Bitfield может посылаться сразу после handshake и задавать множество имеющихся кусочков в виде битовой маски, размер которой(в битах) задается в первых 4 байтах сообщения.  Данный вид сообщения может говорить не о всех кусочках, после этого пир может продолжать дополнять информацию сообщениями типа Have.
 
* Сообщение типа Bitfield может посылаться сразу после handshake и задавать множество имеющихся кусочков в виде битовой маски, размер которой(в битах) задается в первых 4 байтах сообщения.  Данный вид сообщения может говорить не о всех кусочках, после этого пир может продолжать дополнять информацию сообщениями типа Have.
* Сообщения Interested/Not Interested говорят о том, что скачивающий пир хочет/не хочет получить опледеленный кусочек.
+
* Сообщения Interested/Not Interested говорят о том, что скачивающий пир хочет/не хочет получить определенный кусочек.
 
* Сообщения Choke/Unchoke говорят о том, что раздающий пир не готов/готов отдать кусочек.
 
* Сообщения Choke/Unchoke говорят о том, что раздающий пир не готов/готов отдать кусочек.
 
* Сообщение Request стоить посылать после того, как получили разрешение у пира(Unchoke) на конкретный кусочек, хвост у этого типа сообщения состоит за 4 байтов для номера кусочка, 4 байта для смещения внутри кусочка в байтах, и 4 байта для размера блоков, которыми вы хотите получать данные (обычно это 2^14).
 
* Сообщение Request стоить посылать после того, как получили разрешение у пира(Unchoke) на конкретный кусочек, хвост у этого типа сообщения состоит за 4 байтов для номера кусочка, 4 байта для смещения внутри кусочка в байтах, и 4 байта для размера блоков, которыми вы хотите получать данные (обычно это 2^14).

Текущая версия на 19:03, 4 сентября 2022

Peer-to-peer

Peer to peer.jpg

Peer-to-peer - это архитектура передачи данных, основанная на идее равноправия всех участников сети. В сети отсутствуют выделенные серверы. В отличие от традиционных сетей, каждый из участников является как клиентом, так и сервером. Основным преимуществом такой сети является практически полная независимость работоспособности сети от её размера. В то время, как построить и поддерживать сервер на сотни тысяч пользователей является непростой задачей, peer-to-peer сети отлично с этим справляются.

Идея peer-to-peer общения заключается в том, что каждый peer знает и поддерживает информацию о других участниках. Когда новый клиент подключается к сети, он может узнать у любого пира информацию о том, где и какие файлы сейчас доступны. Когда клиент начинает скачивает файл себе на компьютер, то скачанные части этот файла сразу становятся доступны для скачивания другим пользователям. Никто не даёт гарантию, что каждый сервер будет находиться длительное время в сети и давать скачивать информацию, напротив -- ситуация, когда сервер пропадает в процессе загрузки, является естественной. В данном случае будет найден новый сервер, который может продолжить передачу данных. Для поддержания списка активных peer-ов каждый сервер посылает другим серверам heartbeat. Heartbeat -- это сообщение, которое один сервер посылает другому, чтобы сказать ему, что он жив. Соответственно, если heartbeat долго не приходит, значит этот сервер нужно удалить из списка активных peer-ов. Постоянно обмениваться heartbeat-ом с большим количеством серверов трудоёмко. Поэтому у каждого сервера есть два параметра -- нижняя и верхняя граница на размер списка активных серверов. Когда это количество становится ниже нижней границы, запускается поиск новых участников. Сервер запрашивает у других серверов список активных peer-ов и добавляет некоторых из них в свой список, но при этом следит за тем, чтобы размер списка не превысил верхнюю границу.

Частично децентрализованные сети

В некоторых peer-to-peer сетях кроме равноправных node присутствуют сервера, которые выполняют административные функции, такие как поддержка базы онлайн пользователей. К частично децентрализованным сетям относятся например eDonkey, BitTorrent, Direct Connect, The Onion Router.

BitTorrent протокол

Схема работы протокола BitTorrent.

Протокол BitTorrent был разработан в 2001 году Коэном Брахмом, чтобы позволить набору узлов быстро и легко обеспечивать общий доступ к файлам.

Сам протокол состоит в следующем: есть небольшой торрент-файл, содержащий информацию о трекере, который следит за файлом, и список сегментов, из которых состоит файл: для сегмента хранится 160-битовый SHA-1 соответствующего сегмента файла (обычный размер сегмента -- 64-512 кб). Таким образом, скачав файл, можно посчитать от него хеши и проверить, все ли скачалось без ошибок. Кроме торрент-файла есть трекер: сервер, который следит за состоянием пиров -- узлов, имеющих отношение к раздаче. Пир, получив торрент-файл, подключается к трекеру и узнает список сидов -- пиров, у которых уже есть некоторые сегменты файла, готовые их отдать. Узнав адрес сида, у которого можно что-то скачать, клиент уже обращается напрямую к нему и скачивает у него файл. Таким образом, все данные идут напрямую от пира к пиру (peer-to-peer), а трекер участвует лишь в качестве координатора процесса.

Таким образом, протокол не является централизованным (нет никаких выделенных хранилищ с данными), но решает проблему "А где же мне найти, где бы скачать вот этот фрагмент файла".

Для того, чтобы не было проблем с поиском какой-то части файла, трекер сначала предлагает загружать сегменты, которые есть у наименьшего числа участников сети. Это позволяет избежать проблемы, характерной для последовательной загрузки файла, когда много участников скачали почти весь файл, а вот самого конца сейчас ни у кого нет.

Система работает хорошо, когда каждый ее участник скачал документ, а потом еще и остался на его раздаче, отдавая свой долг перед родиной. К сожалению, бывает так, что клиент скачал все, что ему нужно, и решил не оставаться на раздаче -- таких пиров называют личерами. Если в системе много личеров, то раздавать файлы некому, и скорость скачивания падает. Для решения этой проблемы трекер может применить систему поощрений: отдавать пиру очередной сегмент, лишь если его отношение "скачал/отдал" не меньше, чем какое-то конкретное число, мотивируя клиентов оставаться на раздаче, повышая свой рейтинг. К сожалению, это не всегда хорошо работает, так как бывают компьютеры, имеющие хорошую скорость скачивания и малую скорость отдачи. Таким клиентам сложно иметь хороший рейтинг, и они в итоге страдают от этой системы.

Слабым местом системы можно назвать систему трекеров: трекер должен быть всегда в сети, и быть готовым координировать большое количество пиров. Это, конечно, значительно более простая работа, чем явно пересылать каждому желающему целые файлы, но тоже достаточно ресурсоемкая. Несмотря на это, в мире сейчас немало больших торрент-трекеров, несмотря на борьбу с ними компаний, занимающихся защитой авторских прав. Торрент-трекеры действительно всегда были оплотом пиратского контента -- трекеру-то ничего не предъявишь, что он хранит нелицензионный и не предназначенный для распространения контент, а участникам сети особо ничего и не предъявишь (бывают прецеденты, но они достаточно редки).

Как написать свой BitTorrent

Трекер представляет из себя HTTP/HTTPS сервис, который отвечает на HTTP GET запросы. Запрос включает информацию о файле и дополнительную статистическую информацию о торренте. Ответ на запрос содержит список пиров, участвующих в данном торренте.

Задача клиента --- получить от сервера информацию о пирах через HTTP соединение и далее, используя TCP соединение, связываться с пирами для получения/передачи данных. Рассмотрим пример последовательности действий, следуя которой, можно создать примитивный BitTorrent клиент.

Для начала, можете найти в интернете любой .torrent файл. В таком файле содержится закодированная информация о торренте. В данном случае используется Bencoding кодирование про которое можно подробнее прочитать здесь, для этого, например, есть библиотека для питона bencode 3rd party library(pip install bencode).

Потребуется расшифровать и распарсить файл. Из всего, как минимум, понадобится часть announce url и info, в последней содержатся такие поля как piece length(длина кусочка), pieces(список хешей кусочков), paths и lengths для отдельных файлов (структура для торрента с отдельным файлом и несколькими может несколько различаться).

Далее, нужно сделать GET запрос серверу, используя announce url в формате ‘announce-url?param=value&param=value&…’. Подробнее узнать про кодирование url можно здесь, а также можно воспользоваться питоновской библиотекой (pip install requests). О параметрах запроса можно узнать тут. Примеры параметров: 'info_hash' - хеш info части раскодированого torrent файла посчитанный SHA1 алгоритмом, ‘peer_id’ - строка из 20 байтов, подробнее про формат тут.

В ответ от сервера клиент получит закодированый список пиров. Используйте Benconde раскодировщик, чтобы в части peers найти список адресов в формате ip_address:port.

Далее, можно пытаться подключаться к любому из пиров по TCP, для начала советуется поддерживать хотя бы одно соединение.

Первое сообщение должно иметь вид handshake: <pstrlen><pstr><reserved><info_hash><peer_id> info_hash и peer_id уже встречались, а для текущей версии протокола 'pstrlen'=19, 'pstr'=BitTorrent protoco, 'Reserved' восьмибитовая строка. От пира следует ожидать сообщение в аналогичном формате, и после этого проверить, соответствуют ли поля info_hash и peer_id ожидаемому, в случае несоответствия лучше сразу закрыть соединение.

Дальше существует 11 типов возможных сообщений: keep-alive, choke, unchoke, interested, not-interested, have, bitfield, request, piece, cancel, and port. Описание всех видов тут.

Сообщение состоит из 4 байтов задающих длину сообщения, 1 байт задает id сообщения(у сообщения вида Keep-alive этот бит пропускается), и хвост с дополнительной информацией.

  • Сообщения типа Have говорят о наличии определенного кусочка у пира, номер кусочка задается 4 байтовым хвостом.
  • Сообщение типа Bitfield может посылаться сразу после handshake и задавать множество имеющихся кусочков в виде битовой маски, размер которой(в битах) задается в первых 4 байтах сообщения. Данный вид сообщения может говорить не о всех кусочках, после этого пир может продолжать дополнять информацию сообщениями типа Have.
  • Сообщения Interested/Not Interested говорят о том, что скачивающий пир хочет/не хочет получить определенный кусочек.
  • Сообщения Choke/Unchoke говорят о том, что раздающий пир не готов/готов отдать кусочек.
  • Сообщение Request стоить посылать после того, как получили разрешение у пира(Unchoke) на конкретный кусочек, хвост у этого типа сообщения состоит за 4 байтов для номера кусочка, 4 байта для смещения внутри кусочка в байтах, и 4 байта для размера блоков, которыми вы хотите получать данные (обычно это 2^14).
  • Сообщение Piece является ответом на Request и содержит данные одного блока. Хвост состоит из 4 байтов на номер кусочка, 4 байтов на смещение (как в предыдущем типе) и последовательностью байтов описывающей данные блока.

Этого уже вполне хватает, чтобы обмениваться файлами с другими пирами.

Децентрализованные сети

DHT

Рассмотрим пример децентрализованной структурированной P2P сети.

Предположим, что перед нами стоит задача поддерживать DHT: Distributed Hash Table. DHT -- распределенная хеш-таблица, которая размещена на нескольких различных узлах, при чем данные о значениях хранятся на разных узлах сети. Кроме того, потребуем от системы децентрализованность: отсутствие каких-либо специально выделенных серверов, отвечающих за поддержание структуры сети.

Распределенные хеш-таблицы могут найти множество применений. Например, у вас есть какие-то данные, к которым вам нужен доступ, но их слишком много для хранения на одном сервере -- вы используете DHT. Может быть, вы хотите свою торрент-систему, где каждый участник, пока он будет находиться в сети, будет отвечать за какие-то файлы -- в этом случае вам тоже подойдет DHT.

Рассмотрим вариант, предложенный Хордом (Chord). Скажем, что ключами нашей хеш-таблицы будут, например, 160-битные числа (например, SHA-1 хеш объекта). Назначим каждому узлу 160-битное число (как вариант, случайно), и каждый узел будет отвечать за объекты, для ключей которых число в этой вершине ближайшее среди всех чисел вершин.

Для инициализации можно считать, что у нас достаточно мало узлов в сети, каждый узнает ключи других узлов, и инициализирует свою часть хеш-таблицы.

Предположим, что клиент подключился к одному из узлов сети, и хочет найти сервер, отвечающий за какой-то ключ k. Простой способ -- заставим каждый узел хранить следующий и предыдущий (по ассоциированному ключу) узлы, и тогда, если ключ находится не в зоне нашей ответственности, можно спокойно передать запрос следующей вершине. Если какой-то узел хочет выйти из сети, он говорит своим соседям, что больше работать не будет, и передает им свои части таблицы. Подключение новых узлов работает аналогично.

К сожалению, это работает медленно: пробегать для каждого запроса в среднем половину серверов -- не лучшая идея. Вместо этого можно хранить что-то вроде двоичных подъемов: не только ссылку на следующий сервер, но и ссылку на сервера, отвечающие за ключ (k+2^0)%2^160, (k+2^1)%2^160), etc. Таким образом, мы сможем дойти до нужного сервера всего за логарифм операций.

Для увеличения надежности можно хранить ссылку не только на предыдущую/следующую вершины, но на несколько (3-5) ближайших. Это нужно для того, чтобы при внезапном отключении узла, который не успел договориться с соседями, не прерывать связность сети. Кроме того, нужно периодически отсылать сообщения серверам, на которые у нас ведут быстрые ссылки: при добавлении вершины честно пересчитать все переходы на 2^n во всех узлах сети не представляется возможным, а вот отдать эту задачу самим узлам, чтобы они периодически проверяли корректность своих данных -- нормальная идея.

Таким образом, мы рассмотрели пример децентрализованной структурированной P2P сети.

Blockchain

Blockchain -- распределённая база данных, представляющая из себя цепочку блоков в каждом из которых есть список транзакций.

Bitcoin

Bitcoin - это децентрализованная цифровая валюта работающая в сети интернет. Bitcoin основан на Blockchain технологии.

Идея создания распределённой криптовалюты сопряжена с трудностями основная из них это проблема Двойного расходования (англ. Double-spending). В отличие от обычных бумажных денег, которые нельзя просто так взять и скопировать. Можно легко сделать копию электронного файла, который содержит информацию о кошельке, и попытаться потратить деньги в один момент в разных частях света. Так как система распределённая нет такого сервера у которого можно было бы запросить информацию о текущем счёте. С одной стороны это сильно усложняет принцип работы, но с другой даёт огромные преимущества связанные с распределённостью.

Схема получения хеша транзакций

Как уже говорилось информация о транзакциях разбита на блоки и хранится следующим образом. Основная информация, которую содержит каждый блок.

  • хеш всего блока
  • хеш предыдущего блока в цепи
  • свой порядковый номер в цепочке
  • список транзакций (порядка 1000)

В качестве хеш функции используется sha-256. Для хеширования списка транзакций используется древовидный хеш.


Blockchain в первую очередь распределённая база данных. Её содержимое хранится на множестве компьютеров по всему миру. Несмотря на то, что данные о всех транзакциях хранятся у всех участников в открытом виде, отельно взятому участнику нет возможности что-либо подменить в базе данных. Так как после любого изменения в блоке у него изменится хеш, а следующий блок знает значения хеша предыдущего, а значит будет очевидно, что блок подделан. Чтобы реально подделать блок нужно изменить не только его, а ещё все следующие. То есть для этого злоумышленнику нужно обладать вычислительными мощностями большими чем у всего остального мира. А это на практике не возможно.

Адрес - последовательность из 27-34 латинских букв и цифр. По сути — это всё, что нужно знать от получателя для перевода ему денег. В одном кошельке может быть сколько угодно адресов, но адреса между собой никак не связаны. Зная только адрес, можно выяснить, сколько денег было получено на него и с него отправлено, но нельзя выяснить, чей он, кто отправлял деньги и зачем.

Майнинг - процесс, который решает две основные задачи, создания новых блоков с транзакциями и эмиссию биткоинов. Майнинг нужен для существования сети Биткоин, именно майнеры создают новые блоки и записывают в них все транзакции, которые произошли с момента создания предыдущего блока. Процесс майнинга требует решения математически сложной задачи, а значит, требует вычислительных ресурсов. Чтобы люди занимались майнингом, каждый вновь найденный блок не только записывает свежие транзакции, но и даёт майнеру немного биткоинов.

Когда один из участников хочет перевести деньги другому, он рассылает информацию об этой транзакции всем участникам сети. Майнер, получив очередную транзакции проверяет, что она корректная. То есть что у отправителя достаточно денег на счёте. Так как каждый майнер хранит всю историю транзакций, это легко сделать. Если всё хорошо, то транзакция добавляется в список, иначе отвергается.

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

Распределённость и общение между серверами через peer-to-peer соединения даёт большие преимущества bitcoin.

  1. Система по своей конструкции является очень надёжной. Например, чтобы обеспечить надёжность системы WebMoney нужно построить дублирующие сервера по всему миру чтобы защитить себя от технических неисправностей и природных катастроф. Данных о всей истории транзакций bitcoin хранятся на множестве компьютеров по всему миру и не нуждаются в хранение в дата-центрах.
  2. Для перевода денег нет необходимости доверять посреднику, например, банку или интернет порталу.
  3. Нет возможности блокировать транзакции по политическим мотивам. Каждый узел сам принимает решение принять транзакцию или нет.
  4. Нет единого центра, который можно было бы взять под контроль силой и начать управлять валютой.

Недостатки данной системы.

  1. Общий размер истории стремительно растёт. На январь 2017 года он занимает более 100 Гб. Следовательно со временем всё меньше людей с может себе позволить скачать всю историю и проверять истинность транзакций.
  2. Деньги могут быть украдены с помощью вредоносного ПО.

Другие применения

В июне 2016 года Шведский земельный реестр сообщил, что организация тестирует эту технологию с тем, чтобы перевести на её основу базу данных земельных участков в Швеции

Сбербанк в 2017 году запустит документооборот на основе blockchain. Это резко сократит затраты человеческих ресурсов, позволит избежать дублирования информации, перейти к совершенно новому способу подтверждения подлинности документов.


Friend-to-Friend

Friend-to-Friend -- это разновидность peer-to-peer сетей, в которой пользователи обмениваются данными только с теми peer-ми, которым они доверяют. Для аутентификации используются цифровые подписи и пароли. В отличие от P2P пользователи не могут посмотреть список участников F2F сети, кроме своих друзей. Из достоинств таких сетей можно отметить, что F2F являются сильно более защищёнными от хакерских атак, а также в них сильно меньше личеров. Использование F2F-сетей позволяет избегать атак типа mitm (man in the middle), то есть пользователи могут без опасений обмениваться секретными данными (например, крипто-ключами) со своими друзьями.