Создание приложения потоковой передачи кода в режиме реального времени с помощью Socket.IO и его размещение в Azure

Создание функции совместного создания в Microsoft Word в режиме реального времени может быть сложной задачей.

С помощью простых в использовании API Socket.IO оказался библиотекой для обмена данными между клиентами и сервером в режиме реального времени. Однако Socket.IO пользователи часто сообщают о трудностях при масштабировании подключений Socket.IO. С помощью Web PubSub для Socket.IO разработчики больше не должны беспокоиться об управлении постоянными подключениями.

Обзор

В этой статье показано, как создать приложение, позволяющее кодировщику передавать действия по программированию аудитории. Вы создаете это приложение с помощью:

  • Редактор Монако, редактор кода, который управляет Visual Studio Code.
  • Express, веб-платформа Node.js.
  • API, которые библиотека Socket.IO предоставляет для обмена данными в режиме реального времени.
  • Узлы Socket.IO подключения, использующие Web PubSub для Socket.IO.

Готовое приложение

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

Screenshot of the finished code-streaming app.

Для поддержания фокуса процедур и дайджестируемых в течение около 15 минут в этой статье определяются две роли пользователей и то, что они могут сделать в редакторе:

  • Модуль записи, который может ввести в онлайн-редакторе и содержимое передается в поток
  • Зрители, получающие содержимое в режиме реального времени, типизированные средством записи, и не могут редактировать содержимое.

Архитектура

Позиция Назначение Льготы
библиотека Socket.IO Предоставляет механизм обмена данными с низкой задержкой и двунаправленным обменом данными между серверным приложением и клиентами Простые в использовании API, охватывающие большинство сценариев обмена данными в режиме реального времени
Web PubSub для Socket.IO Узлы WebSocket или постоянные подключения на основе опроса с клиентами Socket.IO Поддержка 100 000 одновременных подключений; упрощенная архитектура приложения

Diagram that shows how the Web PubSub for Socket.IO service connects clients with a server.

Необходимые компоненты

Чтобы выполнить все действия, описанные в этой статье, вам потребуется:

Создание веб-pubSub для ресурса Socket.IO

Используйте Azure CLI для создания ресурса:

az webpubsub create -n <resource-name> \
                    -l <resource-location> \
                    -g <resource-group> \
                    --kind SocketIO \
                    --sku Free_F1

Получение строка подключения

Строка подключения позволяет подключаться к Web PubSub для Socket.IO.

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

az webpubsub key show -n <resource-name> \ 
                      -g <resource-group> \ 
                      --query primaryKey \
                      -o tsv

Написание кода на стороне сервера приложения

Начните писать код приложения, работая на стороне сервера.

Создание HTTP-сервера

  1. Создайте проект Node.js:

    mkdir codestream
    cd codestream
    npm init
    
  2. Установите серверный пакет SDK и Express:

    npm install @azure/web-pubsub-socket.io
    npm install express
    
  3. Импорт обязательных пакетов и создание HTTP-сервера для обслуживания статических файлов:

    /*server.js*/
    
    // Import required packages
    const express = require('express');
    const path = require('path');
    
    // Create an HTTP server based on Express
    const app = express();
    const server = require('http').createServer(app);
    
    app.use(express.static(path.join(__dirname, 'public')));
    
  4. Определение вызываемой /negotiateконечной точки. Клиент записи сначала попадает в эту конечную точку. Эта конечная точка возвращает HTTP-ответ. Ответ содержит конечную точку, которую клиент должен использовать для установления постоянного подключения. Он также возвращает room значение, которому назначен клиент.

    /*server.js*/
    app.get('/negotiate', async (req, res) => {
        res.json({
            url: endpoint
            room_id: Math.random().toString(36).slice(2, 7),
        });
    });
    
    // Make the Socket.IO server listen on port 3000
    io.httpServer.listen(3000, () => {
        console.log('Visit http://localhost:%d', 3000);
    });
    

Создание веб-pubSub для сервера Socket.IO

  1. Импортируйте web PubSub для пакета SDK для Socket.IO и определите параметры:

    /*server.js*/
    const { useAzureSocketIO } = require("@azure/web-pubsub-socket.io");
    
    const wpsOptions = {
        hub: "codestream",
        connectionString: process.argv[2]
    }
    
  2. Создайте web PubSub для сервера Socket.IO:

    /*server.js*/
    
    const io = require("socket.io")();
    useAzureSocketIO(io, wpsOptions);
    

Два шага немного отличаются от того, как обычно создается сервер Socket.IO, как описано в этой Socket.IO документации. С помощью этих двух шагов код на стороне сервера может выгрузить постоянные подключения к службе Azure. С помощью службы Azure сервер приложений выступает только в качестве упрощенного HTTP-сервера.

Реализация бизнес-логики

Теперь, когда вы создали сервер Socket.IO, размещенный в Web PubSub, вы можете определить, как клиенты и сервер взаимодействуют с помощью API Socket.IO. Этот процесс называется реализацией бизнес-логики.

  1. После подключения клиента сервер приложений сообщает клиенту, что он вошел в систему, отправив пользовательское событие с именем login.

    /*server.js*/
    io.on('connection', socket => {
        socket.emit("login");
    });
    
  2. Каждый клиент выдает два события, на которые сервер может реагировать: joinRoom и sendToRoom. После того как сервер получает room_id значение, которое клиент хочет присоединить, вы используете socket.join API Socket.IO для присоединения целевого клиента к указанной комнате.

    /*server.js*/
    socket.on('joinRoom', async (message) => {
        const room_id = message["room_id"];
        await socket.join(room_id);
    });
    
  3. После присоединения клиента сервер сообщает клиенту о успешном результате, отправив message событие. Когда клиент получает message событие с типом ackJoinRoom, клиент может попросить сервера отправить последнее состояние редактора.

    /*server.js*/
    socket.on('joinRoom', async (message) => {
        // ...
        socket.emit("message", {
            type: "ackJoinRoom", 
            success: true 
        })
    });
    
    /*client.js*/
    socket.on("message", (message) => {
        let data = message;
        if (data.type === 'ackJoinRoom' && data.success) {
            sendToRoom(socket, `${room_id}-control`, { data: 'sync'});
        }
        // ... 
    });
    
  4. Когда клиент отправляет sendToRoom событие на сервер, сервер передает изменения в состояние редактора кода в указанную комнату. Теперь все клиенты в комнате могут получать последнее обновление.

    socket.on('sendToRoom', (message) => {
        const room_id = message["room_id"]
        const data = message["data"]
    
        socket.broadcast.to(room_id).emit("message", {
            type: "editorMessage",
            data: data
        });
    });
    

Написание клиентского кода приложения

Теперь, когда серверные процедуры завершены, вы можете работать на стороне клиента.

Начальная настройка

Необходимо создать клиент Socket.IO для взаимодействия с сервером. Вопрос заключается в том, с каким сервером клиент должен установить постоянное подключение. Так как вы используете Web PubSub для Socket.IO, сервер — это служба Azure. Помните, что вы определили маршрут /negotiate для обслуживания клиентов конечной точки в Web PubSub для Socket.IO.

/*client.js*/

async function initialize(url) {
    let data = await fetch(url).json()

    updateStreamId(data.room_id);

    let editor = createEditor(...); // Create an editor component

    var socket = io(data.url, {
        path: "/clients/socketio/hubs/codestream",
    });

    return [socket, editor, data.room_id];
}

Функция initialize(url) упорядочивает несколько операций установки вместе:

  • Извлекает конечную точку в службу Azure с HTTP-сервера.
  • Создание экземпляра редактора Монако
  • Устанавливает постоянное подключение к Web PubSub для Socket.IO

Клиент записи

Как упоминание ранее, у вас есть две роли пользователей на стороне клиента: средство записи и средства просмотра. Все, что типы записи передаются на экран средства просмотра.

  1. Получите конечную точку в Web PubSub для Socket.IO и room_id значения:

    /*client.js*/
    
    let [socket, editor, room_id] = await initialize('/negotiate');
    
  2. Когда клиент записи подключен к серверу, сервер отправляет login событие записи. Средство записи может отвечать, запрашивая сервер присоединиться к указанной комнате. Каждые 200 миллисекунда клиент записи отправляет последнее состояние редактора в комнату. Функция с именем flush упорядочивает логику отправки.

    /*client.js*/
    
    socket.on("login", () => {
        updateStatus('Connected');
        joinRoom(socket, `${room_id}`);
        setInterval(() => flush(), 200);
        // Update editor content
        // ...
    });
    
  3. Если модуль записи не вносит никаких изменений, flush() ничего не делает и просто возвращается. В противном случае изменения состояния редактора отправляются в комнату.

    /*client.js*/
    
    function flush() {
        // No changes from editor need to be flushed
        if (changes.length === 0) return;
    
        // Broadcast the changes made to editor content
        sendToRoom(socket, room_id, {
            type: 'delta',
            changes: changes
            version: version++,
        });
    
        changes = [];
        content = editor.getValue();
    }
    
  4. При подключении нового клиента средства просмотра средство просмотра должно получить последнее полное состояние редактора. Для этого сообщение, содержащее sync данные, отправляется клиенту записи. Сообщение просит клиента записи отправить полное состояние редактора.

    /*client.js*/
    
    socket.on("message", (message) => {
        let data = message.data;
        if (data.data === 'sync') {
            // Broadcast the full content of the editor to the room
            sendToRoom(socket, room_id, {
                type: 'full',
                content: content
                version: version,
            });
        }
    });
    

Клиент средства просмотра

  1. Как и клиент записи, клиент средства просмотра создает свой Socket.IO клиент через initialize(). Когда клиент средства просмотра подключен и получает login событие от сервера, он просит сервер присоединиться к указанной комнате. Запрос room_id указывает комнату.

    /*client.js*/
    
    let [socket, editor] = await initialize(`/register?room_id=${room_id}`)
    socket.on("login", () => {
        updateStatus('Connected');
        joinRoom(socket, `${room_id}`);
    });
    
  2. Когда клиент средства просмотра получает message событие от сервера и тип ackJoinRoomданных, клиент средства просмотра запрашивает клиент записи в комнате, чтобы отправить полное состояние редактора.

    /*client.js*/
    
    socket.on("message", (message) => {
        let data = message;
        // Ensures the viewer client is connected
        if (data.type === 'ackJoinRoom' && data.success) { 
            sendToRoom(socket, `${id}`, { data: 'sync'});
        } 
        else //...
    });
    
  3. Если тип данных задан editorMessage, клиент средства просмотра обновляет редактор в соответствии с фактическим содержимым.

    /*client.js*/
    
    socket.on("message", (message) => {
        ...
        else 
            if (data.type === 'editorMessage') {
            switch (data.data.type) {
                case 'delta':
                    // ... Let editor component update its status
                    break;
                case 'full':
                    // ... Let editor component update its status
                    break;
            }
        }
    });
    
  4. Реализуйте joinRoom() и sendToRoom() используя API Socket.IO:

    /*client.js*/
    
    function joinRoom(socket, room_id) {
        socket.emit("joinRoom", {
            room_id: room_id,
        });
    }
    
    function sendToRoom(socket, room_id, data) {
        socket.emit("sendToRoom", {
            room_id: room_id,
            data: data
        });
    }
    

Выполнение приложения

Поиск репозитория

В предыдущих разделах описана основная логика, связанная с синхронизацией состояния редактора между средствами просмотра и средством записи. Полный код можно найти в репозитории примеров.

Клонирование репозитория

Вы можете клонировать репозиторий и запустить npm install для установки зависимостей проекта.

Запуск сервера

node server.js <web-pubsub-connection-string>

Это строка подключения, полученные на предыдущем шаге.

Воспроизведение с редактором кода в режиме реального времени

Откройте вкладку браузера. Откройте http://localhost:3000 другую вкладку с URL-адресом, отображаемым на первой веб-странице.

При написании кода на первой вкладке на другой вкладке вы увидите, что ввод в режиме реального времени отражается на другой вкладке. Web PubSub для Socket.IO упрощает передачу сообщений в облаке. Сервер express обслуживает только статический index.html файл и конечную точку /negotiate .