Укрощение WebRTC с помощью PeerJS: создание простой веб-игры P2P

Опубликовано: 2022-03-11

WebRTC — это технология, которая обеспечивает связь между веб-браузерами в режиме реального времени. Он относительно новый, и определение API все еще считается черновиком. В сочетании с тем фактом, что WebRTC еще не поддерживается всеми основными веб-браузерами (а среди тех, которые поддерживают, некоторые из них не поддерживают все функции этой технологии), это делает относительно сложным использование WebRTC для любых критически важных приложений. . Или так вы думаете!

Подключите Four через WebRTC с помощью PeerJS: смотрите, ма, сервера нет!

Подключите Four через WebRTC с помощью PeerJS: смотрите, ма, сервера нет!
Твитнуть

С тех пор как Google впервые представил его в мае 2011 года, WebRTC используется во многих современных веб-приложениях. Являясь основной функцией многих современных веб-браузеров, веб-приложения могут беспрепятственно использовать преимущества этой технологии для улучшения взаимодействия с пользователем во многих отношениях. Приложения для потокового видео или конференц-связи, которые не требуют раздутых плагинов для браузера и могут использовать преимущества одноранговых (P2P) сетей (при этом не передавая каждый бит данных через какой-либо сервер), являются лишь частью всех удивительных вещей, которые может быть достигнуто с помощью WebRTC.

В этой статье мы рассмотрим, как можно использовать WebRTC для создания простой веб-игры P2P Connect Four. Чтобы обойти различные шероховатости и различия в реализации WebRTC, мы будем использовать замечательную библиотеку JavaScript: PeerJS.

Данные через WebRTC

Прежде чем мы начнем, важно понять, что WebRTC — это не только передача аудио- и видеопотоков. Он также обеспечивает поддержку каналов передачи данных P2P. Эти каналы бывают двух видов: надежные и ненадежные. Нетрудно догадаться, что надежные каналы передачи данных гарантируют, что сообщения будут доставлены и доставлены по порядку, а ненадежные каналы таких гарантий не дают.

Инфраструктура WebRTC — океан аббревиатур

Инфраструктура WebRTC — океан аббревиатур
Твитнуть

Более того, каналы данных WebRTC не требуют специальной настройки инфраструктуры, за исключением того, что необходимо для типичного однорангового соединения WebRTC: сигнальный сервер для координации соединения между одноранговыми узлами, сервер STUN для определения общедоступной идентификации одноранговых узлов и, при необходимости, сервер TURN. для маршрутизации сообщений между узлами, если прямое соединение между узлами невозможно установить (например, когда оба узла находятся за NAT). Если эти аббревиатуры кажутся вам знакомыми, то это потому, что WebRTC по возможности перепрофилирует существующие технологии.

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

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

Познакомьтесь с PeerJS

PeerJS берет реализацию WebRTC в вашем браузере и оборачивает вокруг нее простой, согласованный и элегантный API. Он затыкает различные дыры в реализации WebRTC более ранних браузеров. Например, в Chrome 30 и старше были доступны только ненадежные каналы передачи данных. PeerJS, если он настроен на использование надежных каналов данных, будет использовать прокладку для этих старых браузеров. Хотя это будет не так эффективно, как встроенная реализация надежных каналов, оно все равно будет работать.

С PeerJS идентификация пиров еще проще. Каждый одноранговый узел идентифицируется только с помощью идентификатора. Строка, которую одноранговый узел может выбрать сам или заставить сервер сгенерировать ее. Хотя WebRTC обещает одноранговую связь, вам все равно нужен сервер, который будет выступать в роли брокера соединений и обрабатывать сигналы. PeerJS предоставляет реализацию этого сервера брокера соединений с открытым исходным кодом PeerJS Server (написанного на Node.js) на тот случай, если вы не хотите использовать их облачную версию (которая сейчас бесплатна и имеет некоторые ограничения).

Соедините четыре выхода P2P

Теперь, когда у нас есть источник уверенности в работе с WebRTC, то есть PeerJS, давайте начнем с создания простого приложения Node.js/Express.

 npm init npm install express --save npm install jade --save npm install peer --save

Мы будем использовать это только для размещения сервера PeerJS и обслуживания страницы и внешних ресурсов. Нам нужно будет обслуживать только одну страницу, и она будет содержать два раздела: простое главное меню и сетку Connect Four 7 на 6.

PeerJS-сервер

Разместить собственный сервер PeerJS очень просто. В официальном репозитории на GitHub даже есть кнопка для развертывания экземпляра PeerJS Server в Heroku одним щелчком мыши.

В нашем случае мы просто хотим создать экземпляр ExpressPeerServer в нашем приложении Node.js и обслуживать его в «/peerjs»:

 var express = require('express') var app = express() // … Configure Express, and register necessary route handlers srv = app.listen(process.env.PORT) app.use('/peerjs', require('peer').ExpressPeerServer(srv, { debug: true }))

Клиент PeerJS

Когда PeerJS Server запущен и работает, мы переходим к клиентской стороне. Как обсуждалось ранее, PeerJS идентифицирует одноранговые узлы с уникальными идентификаторами. Эти идентификаторы могут быть сгенерированы PeerServer автоматически для каждого однорангового узла, или мы можем выбрать один для каждого однорангового узла при создании экземпляров одноранговых объектов.

 var peer = new Peer(id, options)

Здесь id можно вообще не указывать, если мы хотим, чтобы сервер сгенерировал его для нас. В нашем случае это то, что мы хотим сделать. PeerServer гарантирует уникальность выдаваемых им идентификаторов. Второй аргумент, options , обычно представляет собой объект, содержащий ключ (ключ API, если вы используете облачный PeerServer, или host , port , path и т. д., если вы сами размещаете PeerServer).

 var peer = new Peer({ host: location.hostname, port: location.port || (location.protocol === 'https:' ? 443 : 80), path: '/peerjs' })

Чтобы установить соединение между двумя узлами PeerJS, один из узлов должен знать идентификатор другого узла. Для простоты в нашей реализации Connect Four через WebRTC мы потребуем, чтобы игрок, запускающий игру, делился своим идентификатором пира со своим противником. Зная идентификатор узла назначения, нам потребуется простой вызов peer.connect(destId) :

 var conn = peer.connect(destId)

И объект Peer , и объект DataConnection , возвращаемый функцией peer.connect(destId) , генерируют некоторые действительно полезные события, которые стоит послушать. Для целей этого руководства нас особенно интересуют событие «данные» объекта DataConnection и события «ошибка» обоих объектов.

Чтобы отправить данные на другой конец соединения, просто вызовите conn.send(data) :

 conn.send('hello')

Хотя это немного излишне для наших нужд, PeerJS передает данные между одноранговыми узлами после их кодирования в формате BinaryPack. Это позволяет одноранговым узлам обмениваться строками, числами, массивами, объектами и даже большими двоичными объектами.

Чтобы получать входящие данные, просто прослушайте событие data на conn :

 conn.on('data', function(data) { // data === 'hello' })

И это почти все, что нам нужно!

Логика игры

Первому игроку, который начинает игру, показывается его идентификатор пира, сгенерированный PeerJS, которым он может поделиться со своим противником. Как только противник присоединяется к игре, используя идентификатор пира первого игрока, первый игрок может сделать ход.

Connect Four, игра с простыми правилами и механикой, имеет только один тип хода: каждый игрок, в свою очередь, должен выбрать столбец и бросить в него диск. Это означает, что все, что партнеру нужно сообщить, — это номер столбца, в который текущий игрок решил вставить свой диск. Мы будем передавать эту информацию в виде массива с двумя элементами: строкой «ход» и числом — 0-. на основе индекса столбца слева.

Каждый раз, когда игрок нажимает на столбец:

 if(!turn) { // it is not the current player's turn return } var i // i = chosen column index if(grid[i].length == 6) { // the column doesn't have any more space available return } // track player's move locally grid[i].push(peerId) // end current player's turn turn = false conn.send(['move', i])

После отправки этих данных хода противнику мы локально обновляем состояние игры. Это включает в себя определение того, выиграл ли текущий игрок или игра закончилась вничью.

На принимающей стороне этих данных о перемещении:

 if(turn) { // ignore incoming move data when it is the current player's turn return } var i = data[1] if(grid[i].length == 6) { // ignore incoming move data when it is invalid return } // track opponent's move locally grid[i].push(opponent.peerId) // activate current player's turn turn = true

И естественно, после этого мы локально обновляем состояние игры, определяем, выиграл ли соперник или игра закончилась вничью.

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

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

Соединение всего

Чтобы объединить все это, мы создаем простую страницу с двумя разделами. При загрузке страницы отображается раздел, содержащий главное меню, раздел, содержащий игровую сетку, остается скрытым.

 section#menu div.animated.bounceIn div h1 Connect Four br div.no-support() div.alert.alert-warning p Unfortunately, your web browser does not <a href="http://iswebrtcreadyyet.com">support WebRTC</a> div a.btn.btn-primary.btn-lg(href='#start') Start | &nbsp; a.btn.btn-default.btn-lg(href='#join') Join section#game() div div h1 Connect Four br table.table.grid tbody for i in [0, 1, 2, 3, 4, 5] tr for j in [0, 1, 2, 3, 4, 5, 6] td div.slot br div.alert.alert-info p

Придание этим элементам DOM красивого вида выходит за рамки этого руководства. Поэтому мы прибегнем к нашему надежному компаньону Bootstrap и немного стилизуем его.

Когда первый игрок нажимает кнопку «Старт», открывается игровая сетка вместе с идентификатором игрока. Затем игрок может поделиться этим идентификатором пира со своим противником.

Начните новую игру и поделитесь своим идентификатором пира, сгенерированным PeerJS.

Второй игрок может щелкнуть, а затем нажать кнопку «Присоединиться», ввести идентификатор пира первого игрока и начать игру.

Используйте идентификатор пира вашего оппонента, чтобы присоединиться к игре

Пробуем

Вы можете попробовать этот пример приложения по адресу https://arteegee.herokuapp.com.

Или вы можете клонировать репозиторий с GitHub, установить зависимости NPM и попробовать его локально:

 git clone https://github.com/hjr265/arteegee.git cd arteegee npm install PORT=5000 npm start

Когда сервер запущен, вы можете указать в своем веб-браузере адрес http://localhost:5000, запустить игру с одной вкладки и присоединиться к ней с другой вкладки (или даже из другого веб-браузера с поддержкой WebRTC), используя идентификатор узла.

Вы можете открыть консоль своего веб-браузера, чтобы увидеть некоторую отладочную информацию, так как в этом примере приложения клиент PeerJS был настроен для ведения подробного журнала.

Но это не работает для меня!

Есть две основные причины, по которым эта игра может не работать на вашем компьютере.

Возможно, вы используете веб-браузер, который еще не поддерживает необходимые API-интерфейсы WebRTC. Если это так, вы можете попробовать другой браузер — тот, который поддерживает WebRTC и каналы данных.

Если вы используете современный веб-браузер с поддержкой WebRTC, то есть вероятность, что вы находитесь за какой-то сетевой инфраструктурой, в которую WebRTC не может проникнуть. В идеале эту проблему можно легко решить с помощью сервера TURN, но, поскольку в примере приложения он не используется, он не будет работать, когда и вы, и ваш оппонент находитесь за NAT.

Заключение

WebRTC — это новая технология, и ее реализация еще далека от зрелости. Это часто вызывает некоторые уникальные проблемы для разработчиков. Однако с появлением таких библиотек, как PeerJS, которые аккуратно абстрагируются от грубых необработанных API, технология уже становится вполне доступной.

Я надеюсь, что это краткое руководство по созданию игры на основе PeerJS поможет вам начать работу с WebRTC и создать несколько замечательных одноранговых веб-приложений в реальном времени.