В этом разделе мы настроим связь между интерфейсом и сервером с помощью веб-сокетов.
Настройка серверной части
Впервые за долгое время мы вносим изменения в server/index.js
. Существуют различные пакеты веб-сокетов для Node. Для нашего относительно простого варианта использования мы просто используем ws
. Импортируйте это в верхней части файла:
const {WebSocketServer} = require('ws');
Внизу файла давайте добавим код для проверки концепции. Это запустит сервер веб-сокетов и покажет, когда соединение будет выполнено с внешнего интерфейса:
const wss = new WebSocketServer({ port: 8080 }); wss.on('connection', ws => { console.log('A connection!'); ws.on('message', data => { console.log('Data from frontend:', data.toString()); wss.clients.forEach(client => client.send(data)); }); });
Настройка внешнего интерфейса
Мы обернем объект WebSocket по умолчанию в класс, чтобы наш код был более организованным. Добавьте это в script.js:
class Socket { constructor() { this.socket = new WebSocket('ws://localhost:8080'); this.socket.onopen = e => this.sendDraw(); this.onmessage = msg => this.handleMessage(msg); } sendDraw() { this.socket.send('this works'); } async handleMessage(msg) { console.log(await msg.data.text()); } }
Затем в index.html добавьте следующее:
const socket = new Socket();
Этот код запускает сокетное соединение и отправляет тестовое сообщение на сервер. Важно, чтобы сервер работал с новым кодом, прежде чем обновлять страницу в браузере, чтобы это работало. После обновления страницы внешний интерфейс отправляет сообщение сокета, которое должно выглядеть примерно так в консоли, где у вас запущен сервер Node:
Затем серверная часть отправляет тестовое сообщение обратно каждому подключенному в данный момент клиенту. Поэтому, если у нас открыто несколько копий внешнего интерфейса на разных вкладках, каждая из них получит сообщение «это работает» при открытии новой вкладки. Если все работает правильно, вы должны увидеть «это работает» в консоли разработчика для используемого вами браузера.
Идентификация себя
Теперь, когда мы можем отправлять сообщения сокетов на сервер и с сервера, мы можем начать совместную часть этого проекта. Для совместной работы требуется пара настроек:
- Концепция «комнат», которая представляет холст, над которым работают вместе несколько человек.
- Идентификаторы для каждого человека в комнате
Для этого мы добавляем класс Room
на фронтэнд:
class Room { constructor() { this.getRoom(); this.id = Math.round(Math.random() * 10000).toString(); } getRoom() { const {pathname} = location; if (pathname !== '/') { this.room = pathname.split('/')[1]; } else { this.room = Math.round(Math.random() * 10000).toString(); history.pushState({}, '', '/' + this.room); } } }
Метод getName
выполняет здесь тяжелую работу. Он проверяет URL на наличие имени комнаты. Например, в http://localhost:3000/foo
имя комнаты — foo
. Вы можете сделать имя комнаты любым, если оно соответствует правилам структуры URL. Если он не находит название комнаты в URL-адресе, он генерирует его случайным образом. В данном случае это число от 0 до 10 000.
Конструктор также генерирует случайный идентификатор для текущей вкладки. Это также случайное число от 0 до 10 000.
Создайте экземпляр объекта Room
в файле index.html. Поместите его перед всем остальным Javascript в файле:
const room = new Room();
Теперь, когда мы перезагружаем http://localhost:3000
, мы видим число, прикрепленное к концу URL-адреса. Но есть проблема. Если мы сейчас обновим страницу или введем имя комнаты вручную, мы получим ошибку 404. Это проблема в нашем коде Node. Ни одно из правил маршрутизации не знает, как с этим справиться, поэтому нам нужно его добавить. Добавьте это в функцию обслуживания script.js:
app.get('/:name?', (req, res) => { res.sendFile(path.join(__dirname + '/../client/index.html')); });
Затем перезапустите сервер Node, и теперь он должен работать правильно. Это правило ищет URL-адреса, в которых за http://localhost:3000
следуют буквенно-цифровые символы.
Отправка события розыгрыша
Мы закончим эту статью, просто написав код для отправки события рисования на все открытые вкладки. Что должно произойти, так это следующее: когда пользователь щелкает точку на холсте, объект Canvas
должен отправить информацию о щелчке на сервер, используя соответствующие данные из объектов Socket
и Room
. Чтобы это произошло, сначала нам нужно немного реорганизовать код.
В файле index.html передайте объекты сокета и комнаты в качестве аргументов объекту холста:
const canvas = new Canvas(socket, room);
И сохраните их как свойства в классе Canvas
:
class Canvas { constructor(socket, room) { ... this.socket = socket; this.room = room; } ... }
В Socket
мы обновляем метод sendDraw
. Мы передаем ему необходимую информацию, чтобы другие клиенты знали, кто отправляет рисунок, и как нарисовать его самостоятельно:
sendDraw(room, mode, color, startPoint, endPoint) { this.socket.send(JSON.stringify({ type: 'draw', roomId: room.id, roomName: room.room, mode, color, startPoint, endPoint })); }
Этот метод вызывается в Canvas.handleDraw
. Однако обратите внимание, что его нужно вызывать в операторе else
, где this.pointMode
означает «конец». Таким образом, сообщение сокета отправляется только при рисовании фигуры. Его также необходимо вызвать до того, как начальная и конечная точки будут установлены обратно в нуль. В противном случае другие клиенты не будут знать, где нарисовать фигуру.
handleDraw(e) { ... if (...) { ... } else { ... this.socket.sendDraw(this.room, this.mode, this.activeColor, this.startPoint, this.endPoint); this.startPoint = null; this.endPoint = null; } }
Измените строку, говорящую
this.socket.onopen = e => this.sendDraw();
Быть console.log(e);
или вообще удалить, если не считаете это полезным.
Собираем вместе
Чтобы протестировать изменения, откройте две вкладки с одинаковым URL-адресом, скажем, http://localhost:3000/1234
. Откройте консоль разработчика для обоих и начните рисовать фигуры на каждой вкладке. То, что вы должны увидеть в консоли на любой из вкладок, — это объекты JSON, описывающие то, что только что было нарисовано.
Вот как выглядит код на данный момент:
В следующем разделе мы посмотрим, как на самом деле рисовать фигуры, полученные с других вкладок, а также очищать холст на каждой вкладке.