failed to make peer to peer connection parsec что делать
Failed to make peer to peer connection parsec что делать
Low latency, peer-to-peer, interactive game streaming.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
The Parsec SDK allows your application to make interactive, low-latency peer-to-peer connections for the purpose of game streaming. The SDK handles the low level internals of peer-to-peer connectivity, networking, and hardware accelerated video/audio processing. It is lightweight, consisting of a single header file and a shared object
The ParsecHost portion of the SDK allows a host to accept incoming client connections (guests). There are two modes of operation: game mode and desktop mode.
Additionally, game mode can be used to make any game entirely headless from the host’s point of view. The game window, rendering swap chain, and audio playback can be removed, leaving the game to accept input from and invisibly output data to Parsec connected guests.
Desktop mode shares the host’s entire desktop (or any fullscreen application) and adds additional permissions/approval to the connection process.
The ParsecClient portion of the SDK provides everything necessary to make a connection to a host, send gamepad/mouse/keyboard input, and receive video/audio output from the host. The ParsecClientRender family of functions allow the client application to efficiently render the incoming frames with OpenGL, D3D9, D3D11, or Metal.
Game Mode Hosting with Unity
The Parsec SDK provides the ParsecUnity module for easy integration in Unity games. See examples/unity for details.
Windows, macOS, Linux Clients
The client example demonstrates the most complete implementation of the client SDK. It uses the cross platform libmatoya library to handle window creation, rendering, audio playback, and input handling. Windows, macOS, and x86-64 Linux are supported.
iOS and Android Clients
The ios and android examples demonstrate audio playback, rendering, and basic input capabilities of the client SDK. The Android example provides a simple JNI bindings package to expose a subset of the C API in Java.
Desktop Mode Hosting
The host example is a succinct implementation of desktop hosting functionality. Desktop hosting is currently only available on Windows.
The Parsec Web SDK provides a fully wrapped Parsec client interface only requiring a and element for initialization. The performance is limited compared to the native SDK clients. See examples/web and sdk/web for details.
Documentation is procedurally generated from the parsec.h header file.
Obtaining a sessionID and peerID
The SDK requires a sessionID and peerID obtained through the Parsec API to identify users and make secure connections. There are three methods of obtaining a sessionID :
Sharing Private Hosts
Using Runtime Linking (DSO)
If PARSEC_DSO is defined before including parsec.h, the header will export a struct containing function pointers for use with runtime linking. There is a convenience header which makes DSO easy to use called parsec-dso.h in the sdk directory. The client and host examples use DSO. There are a few differences when using the parsec-dso.h header instead of parsec.h:
Bindings are maintained by third party developers (thank you!). If you’re interested in another language and would like to create a binding, please submit an issue or contact us via sdk@parsecgaming.com.
Language | Maintainer | Repo |
---|---|---|
Java | Richard Smith | electronstudio/parsec-java |
For questions, suggestions, concerns, and support, visit the Parsec SDK Server on Discord.
Отправка сообщений peer-to-peer при помощи PeerJS
Приветствую вас, уважаемые читатели. В предыдущей статье я рассказал, как сделать простую звонилку в браузере при помощи PeerJS. А сегодня планирую рассмотреть, как обмениваться сообщениями между двумя пользователями напрямую без задержек.
Кому это интересно? Если Вы разрабатываете онлайн игру, в которой необходим быстрый обмен данными между игроками, тогда прямой обмен сообщениями это пожалуй то, что вам нужно.
Разметка и инициализация
Я покажу, как работает технология, на примере простого чата между двумя пользователями, а также расскажу как адаптировать код для обмена игровыми данными.
Начнем с первичной разметки и инициализации объекта peer
В заголовке (head) мы подключаем PeerJS. Какую роль играют элементы с индексами myid и otherPeerId смотрите в статье о звонке
Массив messList будет хранить ленту сообщений. Функция addMess будет добавлять элементы в этот массив и выводить его содержимое в контейнер переписки.
Далее идет инициализация объекта peer, которая также описана в прошлой статье.
Теперь немного о соединениях. Чтобы установить соединение необходимо, чтоб один участник, зная peerID другого, начал соединение с ним, а второй — получил это соединение.
Установка соединения
Событие ‘connection’ для объекта peer происходит при входящем соединении. А функция connect объекта peer устанавливает такое соединение. В обоих случаях будем сохранять объект соединение в переменную conn. Поскольку дальнейшие действия с соединением для текущего учебного примера будут идентичны (хотя в боевом проекте разница может присутствовать), я вынес в отдельную функцию initConn.
Здесь вешаем 2 обработчика: на открытие и на закрытие соединения. В обработчике на открытие соединения довешываем обработчик на прием данных, который будет добавлять в контейнер диалога прилетевшее сообщение.
Остается только реализовать функцию, которая будет отправлять сообщение по нажатию кнопки Отправить, которая:
Адаптация к пересылке игровых данных
Что необходимо сделать, чтоб посылать таким же методом не обычный текст, а данные, которыми нужно обмениваться в процессе игр? На самом деле ничего особенного. В JS есть методы JSON.stringify и JSON.parse которые преобразуют объект в строку и обратно. Просто заверните ваши данные объект, преобразуйте объект в строку (JSON.stringify) перед отправкой и превратите полученные данные в объект (JSON.parse) при получении
Обычно для пересылки игровых объектов и текстовых сообщений не нужны большие объемы данных. Но если вы собираетесь переслать содержимое целого контейнера на странице (куча HTML кода) имейте в виду, что большое соединение может не дойти в неизменном виде.
Из личного опыта скажу: не стоит пересылать таким способом сообщения больше 10 КБ (
10 000 символов). Лучше такое сообщение записать во временный файл и послать партнеру команду на чтение кода из этого файла (думаю смысл вы уловили).
На этом можно было бы остановиться, если бы не…
Обрыв соединения
Да, такое происходит. Виной тому бывает нестабильный интернет. Бывало ли так, что вы уже почти выиграли, но обрывается соединение и вы теряете весь свой прогресс? Чтобы такого избежать, давайте допишем код, который будет поднимать упавшее соединение. Будем для этого обрабатывать событие ‘close’. Это событие возникает если:
Здесь мы с задержкой в 2 секунды после обрыва соединения просто пытаемся установить новое.
partnerPeer у объекта conn присутствует только у установившего в первый раз соединение партнера, а значит только одна из 2-х сторон соединения начнет его восстанавливать при обрыве.
WebRTC: Делаем peer to peer игру на javascript
Недавно мне довелось поработать над прототипом видеочата. Это был отличный повод поближе познакомиться с концепциями WebRTC и опробовать их на практике. Как правило, когда говорят про WebRTC, подразумевают организацию аудио- и видеосвязи, но эта технология может применяться и для других интересных вещей. Я решил попробовать сделать peer-to-peer игру и поделиться опытом ее создания. Видео того что получилось и подробности реализации под катом.
Движок для игры
Как-то давным-давно мне попалась на глаза демка игры с симпатичной пиксельарт графикой. Игра была сделана на JavaScript-движке Impact. Про него даже как-то упоминали на Хабре.
Движок платный, я купил его еще пару лет назад, но так ничего дельного на нем и не сделал, и вот наконец-то он мне пригодился. Надо сказать, что сам по себе процесс создания игры на нем — очень увлекательное занятие, и для людей вроде меня, которые хотят быстро и недорого ощутить себя крутыми «игроделами», это то, что нужно. Определившись с технологией связи и игровым движком, можно перейти к реализации. Лично я начал с игровых комнат.
Игровые комнаты
Каким образом игрок может попасть в игру и как пригласить в нее своих друзей? Многие онлайн-игры используют так называемые комнаты или каналы, чтобы игроки могли играть друг с другом. Для этого понадобится сервер, который позволит создавать эти самые комнаты и добавлять/удалять пользователей. Схема его работы довольно простая: когда пользователь запускает игру, а в нашем случае — открывает окно браузера с адресом игры, то происходит следующее:
Протокол взаимодействия
Формат и смысл сообщений, передаваемых между игроками, сильно зависит от того, что вообще будет происходить в игре. В нашем случае это простенький 2D-шутер, где игроки бегают и стреляют друг в друга. Поэтому в первую очередь нужно знать о месте расположения других игроков на карте:
Получая такое сообщение можно понять, где находится игрок, но нельзя понять, как он в данный момент выглядит. Поэтому для полноты картины сюда можно добавить информацию о том, какая в данный момент у игрока включена анимация, в каком она кадре и в какую сторону он смотрит:
Отлично! Какие еще сообщения понадобятся? В зависимости от того, что вы планируете делать в игре, у вас получится свой набор, а у меня получилось примерно следующее:
Типизированные поля в сообщениях
Как вы могли заметить, каждое из полей в сообщениях имеет свой тип данных, например, int16 — для полей, представляющих координаты. Давайте сразу в этом разберемся, заодно я немного расскажу про WebRTC API. Дело в том, что для передачи данных между пирами используется объект типа RTCDataChannel, который, в свою очередь, умеет работать с данными типа USVString, BLOB, ArrayBuffer или ArrayBufferView. Как раз для того, чтобы использовать ArrayBufferView, и нужно четко понимать, какого формата будут данные.
Итак, описав все сообщения, мы готовы продолжить и перейти непосредственно к организации взаимодействия между пирами. Здесь я постараюсь описать матчасть настолько кратко, насколько смогу. Вообще, пытаться рассказать про WebRTC во всех деталях — долгое и сложное занятие, тем более что в открытом доступе есть книга Ильи Григорика, которая является просто кладезем информации на эту и другие темы, касающиеся сетевого взаимодействия. Моя же цель, как я уже сказал, — дать краткое описание основных механизмов WebRTC, с изучения которых придется начать каждому.
Установка соединения
Что нужно для того, чтобы пользователи А и Б смогли установить peer-to-peer соединение между собой? Ну, как минимум каждый из пользователей должен знать адрес и порт, по которому его оппонент слушает и может получить входящие данные. Но как А и Б сообщат друг другу эту информацию, если связь еще не установлена? Для передачи этой информации нужен сервер. В терминологии WebRTC он называется signalling-сервер. И так как уже реализован свой сервер для игровых комнат, его же можно использовать и в качестве signalling-сервера.
Также кроме адресов и портов, А и Б должны договориться о параметрах устанавливаемой сессии.Например об использовании тех или иных кодеков и их параметров в случае аудио- и видео связи. Формат данных, описывающих всевозможные свойства соединения, называется SDP — Session Description Protocol. Более подробно с ним можно познакомиться на webrtchacks.com. Итак, исходя из вышесказанного, порядок обмена данными через signalling следующий:
Определение адреса и проверка доступности
Когда каждый из пользователей доступен по публичному IP-адресу или оба находятся в рамках одной подсети — все просто. Тогда каждый из них может запросить свой IP у операционной системы и отправить его через signalling своему оппоненту. Но что делать, если пользователь недоступен напрямую, а находится за NAT, и у него два адреса: один локальный, внутри подсети (192.168.1.1), второй — адрес самого NAT (50.76.44.114)? В этом случае ему каким-то образом нужно определить свой публичный адрес и порт.
Идея решения довольно проста: нужен публично доступный сервер, который, получив запрос от нас, отправит в ответ наш публичный адрес и порт.
Такие сервера называются STUN ( Session Traversal Utilities for NAT ). Существуют готовые решения, например, coturn, который можно развернуть в качестве своего STUN-сервера. Но можно поступить еще проще и воспользоваться уже развернутыми и доступными серверами, например от Google.
Таким образом, каждый может получить свой адрес и послать его своему оппоненту. Но этого недостаточно, ведь после получения адреса от оппонента нужно еще проверить, можем ли мы достучаться до него по этому адресу?
К счастью, задачу взаимодействия со STUN и задачу проверки доступности берет на себя ICE (Interactive Connectivity Establishment) фреймворк, встроенный в браузер. Все, что нам нужно, — обрабатывать события этого фреймворка. Итак, приступим к реализации…
Создание соединения
Поначалу может показаться, что процесс установки соединения достаточно сложный. Но, к счастью, вся сложность скрыта всего лишь за одним интерфейсом RTCPeerConnection, и на практике все проще, чем может показаться на первый взгляд. Полный код для класса, реализующего peer-to-peer соединение, можно посмотреть тут, дальше я поясню его.
Как я уже сказал, установка, мониторинг и закрытие соединения, а также работа с SDP и ICE кандидатами — все это делается через RTCPeerConnection. Более подробную информацию о конфигурации можно посмотреть, например, тут. Нам же в качестве конфигурации понадобится только адрес STUN-сервера от Google, о котором я говорил выше.
RTCPeerConnection предоставляет набор колбеков для различных событий жизненного цикла соединения, из которого нам понадобятся:
Отправка запроса на соединение
В списке действий для соединения первыми двумя пунктами были запрос на установку соединения и подтверждение этого запроса. Мы немного упростим процесс и будем считать, что если пользователь знает адрес игровой комнаты, то кто-то дал ему ссылку, поэтому запрос на установку связи не требуется, можно сразу переходить к обмену данными сессии и адресами.
Определение параметров сессии
Для получения параметров сессии в RTCPeerConnection существуют методы createOffer — для вызова на инициирующей стороне, и createAnswer — на отвечающей. Результатом работы этих методов являются данные в формате SDP, которые необходимо отправить через signalling оппоненту. RTCPeerConnection хранит как локальное описание сессии, так и удаленное, полученное через signalling от оппонента. Для установки этих полей есть методы setLocalDescription и setRemoteDescription. Итак, допустим клиент А инициирует соединение, тогда порядок действий следующий:
1. Клиент А создает SDP-offer, устанавливает локальное описание сессии в своем RTCPeerConnection, после чего отправляет его клиенту Б:
2. Клиент Б получает offer от клиента А и устанавливает удаленное описание сессии. После чего создает SDP-answer, устанавливает его в качестве локального описания сессии и отправляет клиенту А:
4. После того как клиент А получает SDP-answer от клиента Б, он также устанавливает его в качестве удаленного описания сессии. В результате каждый из клиентов установил локальное описание сессии и удаленное, полученное от своего оппонента:
Сбор ICE-кандидатов
Каждый раз когда ICE-агент клиента А находит новую пару IP+port, которую можно использовать для связи, у RTCPeerConnection срабатывает событие icecandidate. Данные кандидата выглядят следующим образом:
Вот что можно понять, глядя на эти данные:
Эти данные нужно передать через signalling клиенту Б, чтобы он добавил их в свой RTCPeerConnection. Точно так же поступает и клиент Б, когда обнаруживает свои пары IP+port:
Создание канала данных
Ну и, пожалуй, последнее, на чем стоит остановиться, это RTCDataChannel. Этот интерфейс предоставляет нам API, с помощью которого можно передавать произвольные данные, а также настраивать свойства доставки данных:
И наконец, после успешного открытия канала данных между игроками можно начинать передачу игровых сообщений между ними.
Больше игроков
Мы рассмотрели, как установить связь между двумя игроками, и этого, в принципе, достаточно, чтобы играть один на один. А если мы хотим, чтобы в одной комнате могли играть несколько игроков? Что тогда изменится? На самом деле — ничего, просто для каждой пары игроков должно быть свое соединение. Т.е. если вы играете в комнате еще с 3 игроками, у вас должно быть открыто 3 peer-to-peer соединения с каждым из них. Полный код класса, отвечающего за взаимодействие со всеми оппонентами по комнате, можно посмотреть тут.
Итак, signalling-сервер c комнатами готов, формат сообщений и способ их доставки обсудили, как теперь на основе этого сделать так, чтобы игроки видели друг друга?
Синхронизация местоположения
Идея синхронизации довольно простая: нужно один раз в какой-то промежуток времени отправлять оппонентам свои координаты, тогда они на основе этих данных могут достоверно отражать твое местоположение.
Как часто нужно отправлять синхронизационные сообщения? В идеале оппонент должен видеть обновления так же часто, как и сам игрок, т.е. если игра работает с фреймрейтом 30-60 кадров в секунду, то и сообщения тоже должны отправляться с той же частотой. Но это довольно наивное решение, и многое в конечном итоге зависит от динамичности самой игры. Например, стоит ли так часто отправлять координаты, если они меняются раз в десять-двадцать секунд? Наверное, в таком случае это излишне. В моем случае анимация и положение игроков меняется довольно часто, поэтому я решил пойти простым путем и отправлять сообщения с координатами на каждый фрейм.
Отправка синхронизационного сообщения:
Получение синхронизационного сообщения:
К сожалению, то, что получилось работает без каких либо задержек только до тех пор, пока не начнешь играть с кем-нибудь настоящим, кто сидит за другим компьютером и не в одной с тобой сети. Потому что тогда это начинает работать примерно так:
Дело в том, что для плавности картинки необходимо доставлять сообщения с неизменной частотой — той же, с которой они отправлялись. Достичь этого в реальных условиях практически невозможно, из-за этого промежутки времени между приходящими сообщениями постоянно меняются, создавая такой неприятный глазу эффект. Победить его можно используя экстраполяцию координат.
Экстраполяция координат
Для начала надо более подробно разобраться, как задержки сообщений влияют на качество картинки, которую видит игрок. Для плавного движения необходимо, чтобы сообщения приходили с равным интервалом, близким к частоте обновления кадров в игре:
На практике же получается нечто иное. Интервалы между сообщениями распределены неравномерно, что приводит скачкообразной анимации и изменению координат:
При взгляде на вторую схему становится понятно, что происходит в момент повышенной задержки сообщения: игрок сначала видит замирание, а потом резкий скачок. Это и производит неприятный эффект.
Движение было бы гораздо более плавным, если бы в моменты задержек координаты игрока менялись пропорционально, пусть и не всегда достоверно точно:
И действительно, если проанализировать движение игроков, то можно понять, что резкой смены направления движения обычно не происходит, а это значит, что не получив в какой-то момент очередного сообщения с координатами, мы можем предположить их, исходя, например, из его скорости в предыдущем кадре. Для этого нужно либо вычислять эту скорость на принимающей стороне, либо просто отправлять ее вместе с координатами. Я, как обычно, выбрал самый простой способ и отправляю ее вместе с координатами. И теперь, если в определенном кадре не было сообщения с обновлением координат, то они вычисляются из скорости игрока в предыдущем кадре:
А вот как это выглядит после применения экстраполяции:
Конечно, этот метод обладает кучей недостатков, и на совсем медленных соединениях может получиться, например, так:
Но реализация экстраполяции выходит далеко за рамки этой статьи, поэтому предлагаю остановиться на том, что есть.
Другие игровые действия
Что получилось в итоге
Код (за исключением исходников самого ImpactJS) и инструкции по запуску можно посмотреть на гитхабе.
Рискну оставить тут эту ссылку, где можно попробовать поиграть. Не знаю, что там случится с моим single-core дроплетом, но будь что будет =)
Напоследок
Если вы дочитали до конца — спасибо! Значит, мой труд не пропал даром и вы нашли для себя что-то интересное. Вопросы, замечания и предложения оставляйте, пожалуйста, в комментариях.
Александр Гутников, frontend разработчик, Badoo.
Peer-to-peer
Содержание
Peer-to-peer [ править ]
Частично децентрализованные сети [ править ]
В некоторых peer-to-peer сетях кроме равноправных node присутствуют сервера, которые выполняют административные функции, такие как поддержка базы онлайн пользователей. К частично децентрализованным сетям относятся например eDonkey, BitTorrent, Direct Connect, The Onion Router.
BitTorrent протокол [ править ]
Протокол BitTorrent был разработан в 2001 году Коэном Брахмом, чтобы позволить набору узлов быстро и легко обеспечивать общий доступ к файлам.
Таким образом, протокол не является централизованным (нет никаких выделенных хранилищ с данными), но решает проблему «А где же мне найти, где бы скачать вот этот фрагмент файла».
Для того, чтобы не было проблем с поиском какой-то части файла, трекер сначала предлагает загружать сегменты, которые есть у наименьшего числа участников сети. Это позволяет избежать проблемы, характерной для последовательной загрузки файла, когда много участников скачали почти весь файл, а вот самого конца сейчас ни у кого нет.
Как написать свой BitTorrent [ править ]
Трекер представляет из себя HTTP/HTTPS сервис, который отвечает на HTTP GET запросы. Запрос включает информацию о файле и дополнительную статистическую информацию о торренте. Ответ на запрос содержит список пиров, участвующих в данном торренте.
Потребуется расшифровать и распарсить файл. Из всего, как минимум, понадобится часть announce url и info, в последней содержатся такие поля как piece length(длина кусочка), pieces(список хешей кусочков), paths и lengths для отдельных файлов (структура для торрента с отдельным файлом и несколькими может несколько различаться).
В ответ от сервера клиент получит закодированый список пиров. Используйте Benconde раскодировщик, чтобы в части peers найти список адресов в формате ip_address:port.
Далее, можно пытаться подключаться к любому из пиров по TCP, для начала советуется поддерживать хотя бы одно соединение.
Первое сообщение должно иметь вид handshake:
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 этот бит пропускается), и хвост с дополнительной информацией.
Этого уже вполне хватает, чтобы обмениваться файлами с другими пирами.
Децентрализованные сети [ править ]
DHT [ править ]
Рассмотрим пример децентрализованной структурированной P2P сети.
Рассмотрим вариант, предложенный Хордом (Chord). Скажем, что ключами нашей хеш-таблицы будут, например, 160-битные числа (например, SHA-1 хеш объекта). Назначим каждому узлу 160-битное число (как вариант, случайно), и каждый узел будет отвечать за объекты, для ключей которых число в этой вершине ближайшее среди всех чисел вершин.
Для инициализации можно считать, что у нас достаточно мало узлов в сети, каждый узнает ключи других узлов, и инициализирует свою часть хеш-таблицы.
Таким образом, мы рассмотрели пример децентрализованной структурированной P2P сети.
Blockchain [ править ]
Bitcoin [ править ]
Идея создания распределённой криптовалюты сопряжена с трудностями основная из них это проблема Двойного расходования (англ. Double-spending). В отличие от обычных бумажных денег, которые нельзя просто так взять и скопировать. Можно легко сделать копию электронного файла, который содержит информацию о кошельке, и попытаться потратить деньги в один момент в разных частях света. Так как система распределённая нет такого сервера у которого можно было бы запросить информацию о текущем счёте. С одной стороны это сильно усложняет принцип работы, но с другой даёт огромные преимущества связанные с распределённостью.
Как уже говорилось информация о транзакциях разбита на блоки и хранится следующим образом. Основная информация, которую содержит каждый блок.
В качестве хеш функции используется sha-256. Для хеширования списка транзакций используется древовидный хеш.
Blockchain в первую очередь распределённая база данных. Её содержимое хранится на множестве компьютеров по всему миру. Несмотря на то, что данные о всех транзакциях хранятся у всех участников в открытом виде, отельно взятому участнику нет возможности что-либо подменить в базе данных. Так как после любого изменения в блоке у него изменится хеш, а следующий блок знает значения хеша предыдущего, а значит будет очевидно, что блок подделан. Чтобы реально подделать блок нужно изменить не только его, а ещё все следующие. То есть для этого злоумышленнику нужно обладать вычислительными мощностями большими чем у всего остального мира. А это на практике не возможно.
Когда один из участников хочет перевести деньги другому, он рассылает информацию об этой транзакции всем участникам сети. Майнер, получив очередную транзакции проверяет, что она корректная. То есть что у отправителя достаточно денег на счёте. Так как каждый майнер хранит всю историю транзакций, это легко сделать. Если всё хорошо, то транзакция добавляется в список, иначе отвергается.
Сложность математической задачи для создания блока постоянно регулируется и поддерживается так, чтобы новый блок создавался в среднем раз в 10 минут.
Распределённость и общение между серверами через peer-to-peer соединения даёт большие преимущества bitcoin.
Недостатки данной системы.
Другие применения [ править ]
В июне 2016 года Шведский земельный реестр сообщил, что организация тестирует эту технологию с тем, чтобы перевести на её основу базу данных земельных участков в Швеции
Сбербанк в 2017 году запустит документооборот на основе blockchain. Это резко сократит затраты человеческих ресурсов, позволит избежать дублирования информации, перейти к совершенно новому способу подтверждения подлинности документов.