Share via


Crie um aplicativo de streaming de código em tempo real usando o Socket.IO e hospede-o no Azure

Criar uma experiência em tempo real, como o recurso de cocriação no Microsoft Word , pode ser um desafio.

Através de suas APIs fáceis de usar, Socket.IO provou ser uma biblioteca para comunicação em tempo real entre clientes e um servidor. No entanto, Socket.IO usuários geralmente relatam dificuldade em dimensionar as conexões do Socket.IO. Com o Web PubSub para Socket.IO, os desenvolvedores não precisam mais se preocupar com o gerenciamento de conexões persistentes.

Descrição geral

Este artigo mostra como criar um aplicativo que permite que um codificador transmita atividades de codificação para um público. Você constrói este aplicativo usando:

  • Monaco Editor, o editor de código que alimenta o Visual Studio Code.
  • Express, um framework web .js nó.
  • APIs que a biblioteca Socket.IO fornece para comunicação em tempo real.
  • Host Socket.IO conexões que usam Web PubSub para Socket.IO.

A aplicação concluída

O aplicativo concluído permite que o usuário de um editor de código compartilhe um link da web através do qual as pessoas podem assistir à digitação.

Screenshot of the finished code-streaming app.

Para manter os procedimentos focados e digeríveis em cerca de 15 minutos, este artigo define duas funções de usuário e o que eles podem fazer no editor:

  • Um escritor, que pode digitar no editor on-line e o conteúdo é transmitido
  • Visualizadores, que recebem conteúdo digitado em tempo real pelo escritor e não podem editar o conteúdo

Arquitetura

Item Objetivo Benefícios
Socket.IO biblioteca Fornece um mecanismo de troca de dados bidirecional de baixa latência entre o aplicativo back-end e os clientes APIs fáceis de usar que cobrem a maioria dos cenários de comunicação em tempo real
Web PubSub para Socket.IO Hospeda conexões persistentes baseadas em sondagem ou WebSocket com clientes Socket.IO Suporte para 100.000 conexões simultâneas; Arquitetura de aplicativo simplificada

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

Pré-requisitos

Para seguir todas as etapas deste artigo, você precisa:

  • Uma conta do Azure. Se você não tiver uma assinatura do Azure, crie uma conta gratuita do Azure antes de começar.
  • A CLI do Azure (versão 2.29.0 ou posterior) ou o Azure Cloud Shell para gerenciar recursos do Azure.
  • Familiaridade básica com as APIs do Socket.IO.

Criar um Web PubSub para Socket.IO recurso

Use a CLI do Azure para criar o recurso:

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

Obter uma cadeia de conexão

Uma cadeia de conexão permite que você se conecte com o Web PubSub por Socket.IO.

Execute os seguintes comandos. Mantenha a cadeia de conexão retornada em algum lugar, porque você precisará dela quando executar o aplicativo mais adiante neste artigo.

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

Escreva o código do lado do servidor do aplicativo

Comece a escrever o código do seu aplicativo trabalhando no lado do servidor.

Criar um servidor HTTP

  1. Crie um projeto .js nó:

    mkdir codestream
    cd codestream
    npm init
    
  2. Instale o SDK do servidor e o Express:

    npm install @azure/web-pubsub-socket.io
    npm install express
    
  3. Importe os pacotes necessários e crie um servidor HTTP para servir arquivos estáticos:

    /*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. Defina um ponto de extremidade chamado /negotiate. Um cliente gravador atinge esse ponto de extremidade primeiro. Esse ponto de extremidade retorna uma resposta HTTP. A resposta contém um ponto de extremidade que o cliente deve usar para estabelecer uma conexão persistente. Ele também retorna um room valor ao qual o cliente está atribuído.

    /*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);
    });
    

Criar o Web PubSub para Socket.IO servidor

  1. Importe o Web PubSub para Socket.IO SDK e defina opções:

    /*server.js*/
    const { useAzureSocketIO } = require("@azure/web-pubsub-socket.io");
    
    const wpsOptions = {
        hub: "codestream",
        connectionString: process.argv[2]
    }
    
  2. Crie um Web PubSub para Socket.IO servidor:

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

As duas etapas são ligeiramente diferentes de como você normalmente criaria um servidor Socket.IO, conforme descrito nesta documentação Socket.IO. Com essas duas etapas, seu código do lado do servidor pode descarregar o gerenciamento de conexões persistentes para um serviço do Azure. Com a ajuda de um serviço do Azure, seu servidor de aplicativos atua apenas como um servidor HTTP leve.

Implementar lógica de negócios

Agora que você criou um servidor de Socket.IO hospedado pelo Web PubSub, pode definir como os clientes e o servidor se comunicam usando as APIs do Socket.IO. Esse processo é chamado de implementação da lógica de negócios.

  1. Depois que um cliente é conectado, o servidor de aplicativos informa ao cliente que ele está conectado enviando um evento personalizado chamado login.

    /*server.js*/
    io.on('connection', socket => {
        socket.emit("login");
    });
    
  2. Cada cliente emite dois eventos aos quais o servidor pode responder: joinRoom e sendToRoom. Depois que o servidor obtém o valor que um cliente deseja ingressar, você usa socket.join a API do Socket.IO para unir o room_id cliente de destino à sala especificada.

    /*server.js*/
    socket.on('joinRoom', async (message) => {
        const room_id = message["room_id"];
        await socket.join(room_id);
    });
    
  3. Depois que um cliente é ingressado, o servidor informa o cliente do resultado bem-sucedido enviando um message evento. Quando o cliente recebe um evento com um message tipo de , o cliente pode pedir ao servidor para enviar o estado do ackJoinRoomeditor mais recente.

    /*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. Quando um cliente envia um sendToRoom evento para o servidor, o servidor transmite as alterações no estado do editor de código para a sala especificada. Todos os clientes na sala já podem receber a última atualização.

    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
        });
    });
    

Escreva o código do lado do cliente do aplicativo

Agora que os procedimentos do lado do servidor estão concluídos, você pode trabalhar no lado do cliente.

Configuração inicial

Você precisa criar um cliente Socket.IO para se comunicar com o servidor. A questão é com qual servidor o cliente deve estabelecer uma conexão persistente. Como você está usando o Web PubSub para Socket.IO, o servidor é um serviço do Azure. Lembre-se de que você definiu uma rota /negotiate para servir aos clientes um ponto de extremidade para Web PubSub para 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];
}

A initialize(url) função organiza algumas operações de configuração em conjunto:

  • Obtém o ponto de extremidade para um serviço do Azure a partir do seu servidor HTTP
  • Cria uma instância do Editor de Mônaco
  • Estabelece uma conexão persistente com o Web PubSub para Socket.IO

Cliente Writer

Como mencionado anteriormente, você tem duas funções de usuário no lado do cliente: escritor e visualizador. Tudo o que o escritor digita é transmitido para a tela do espectador.

  1. Obtenha o ponto de extremidade para Web PubSub para Socket.IO e o room_id valor:

    /*client.js*/
    
    let [socket, editor, room_id] = await initialize('/negotiate');
    
  2. Quando o cliente gravador está conectado com o servidor, o servidor envia um login evento para o gravador. O gravador pode responder pedindo ao servidor para se juntar a uma sala especificada. A cada 200 milissegundos, o cliente de gravação envia o estado mais recente do editor para a sala. Uma função chamada flush organiza a lógica de envio.

    /*client.js*/
    
    socket.on("login", () => {
        updateStatus('Connected');
        joinRoom(socket, `${room_id}`);
        setInterval(() => flush(), 200);
        // Update editor content
        // ...
    });
    
  3. Se um escritor não faz nenhuma edição, flush() não faz nada e simplesmente retorna. Caso contrário, as alterações no estado do editor são enviadas para a sala.

    /*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. Quando um novo cliente visualizador é conectado, o visualizador precisa obter o estado completo mais recente do editor. Para conseguir isso, uma mensagem que contém sync dados é enviada para o cliente gravador. A mensagem solicita que o cliente gravador envie o estado completo do editor.

    /*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,
            });
        }
    });
    

Cliente visualizador

  1. Como o cliente de gravação, o cliente visualizador cria seu cliente Socket.IO através do initialize(). Quando o cliente visualizador está conectado e recebe um login evento do servidor, ele solicita que o servidor se junte à sala especificada. A consulta room_id especifica a sala.

    /*client.js*/
    
    let [socket, editor] = await initialize(`/register?room_id=${room_id}`)
    socket.on("login", () => {
        updateStatus('Connected');
        joinRoom(socket, `${room_id}`);
    });
    
  2. Quando um cliente visualizador recebe um message evento do servidor e o tipo de dados é ackJoinRoom, o cliente visualizador solicita ao cliente do gravador na sala para enviar o estado completo do editor.

    /*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. Se o tipo de dados for editorMessage, o cliente visualizador atualiza o editor de acordo com seu conteúdo real.

    /*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. Implemente joinRoom() e sendToRoom() usando as APIs do 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
        });
    }
    

Executar a aplicação

Localize o repositório

As seções anteriores abordaram a lógica central relacionada à sincronização do estado do editor entre os espectadores e o escritor. Você pode encontrar o código completo no repositório de exemplos.

Clonar o repositório

Você pode clonar o repositório e executar npm install para instalar dependências do projeto.

Inicie o servidor

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

Esta é a cadeia de conexão que você recebeu em uma etapa anterior.

Jogue com o editor de código em tempo real

Abra em uma guia do navegador. Abra http://localhost:3000 outro separador com o URL apresentado na primeira página Web.

Se escrever código no primeiro separador, deverá ver a sua escrita refletida em tempo real no outro separador. Web PubSub para Socket.IO facilita a passagem de mensagens na nuvem. Seu express servidor serve apenas o arquivo estático index.html e o ponto de /negotiate extremidade.