Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Criar uma experiência em tempo real como o recurso de co-criação no Microsoft Word pode ser um desafio.
Por meio de suas APIs fáceis de usar, o Socket.IO provou ser uma biblioteca para comunicação em tempo real entre clientes e um servidor. No entanto, os usuários do Socket.IO frequentemente relatam dificuldade em escalar 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.
Importante
As cadeias de conexão brutas aparecem neste artigo somente para fins de demonstração.
Uma cadeia de conexão inclui as informações de autorização necessárias para que o seu aplicativo acesse o serviço Azure Web PubSub. A chave de acesso dentro da cadeia de conexão é semelhante a uma senha raiz para o serviço. Em ambientes de produção, sempre proteja suas chaves de acesso. Use o Azure Key Vault para gerenciar e girar suas chaves com segurança e proteger sua conexão com WebPubSubServiceClient
.
Evite distribuir chaves de acesso para outros usuários, fazer hard-coding com elas ou salvá-las em qualquer lugar em texto sem formatação que seja acessível a outras pessoas. Gire suas chaves se você acredita que elas podem ter sido comprometidas.
Visão geral
Este artigo mostra como criar um aplicativo que permite que um codificador transmita atividades de codificação para um público-alvo. Você cria este aplicativo usando:
- Monaco Editor, o editor de código que alimenta o Visual Studio Code.
- Expresso, uma estrutura da Web do Node.js.
- APIs que a biblioteca de Socket.IO fornece para comunicação em tempo real.
- Hospedar conexões Socket.IO que usam o Web PubSub para Socket.IO.
O aplicativo finalizado
O aplicativo concluído permite que o usuário de um editor de código compartilhe um link da Web por meio do qual as pessoas podem assistir à digitação.
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 elas podem fazer no editor:
- Um gravador, que pode digitar no editor online e o conteúdo é transmitido
- Visualizadores, que recebem conteúdo em tempo real digitado pelo gravador e não podem editar o conteúdo
Arquitetura
Item | Finalidade | Benefícios |
---|---|---|
Biblioteca Socket.IO | Fornece um mecanismo de troca de dados bidirecional e de baixa latência entre o aplicativo back-end e os clientes | APIs fáceis de usar que abrangem a maioria dos cenários de comunicação em tempo real |
Web PubSub para Socket.IO | Hospeda conexões WebSocket ou conexões persistentes baseadas em pesquisa com clientes Socket.IO | Suporte para 100.000 conexões simultâneas; arquitetura de aplicativo simplificada |
Pré-requisitos
Para seguir todas as etapas deste artigo, você precisa ter:
- Uma conta do Azure. Caso você não tenha uma assinatura do Azure, crie uma conta gratuita do Azure antes de começar.
- A CLI do Azure (versão 2.29.0 ou superior) ou oAzure Cloud Shell para gerenciar recursos do Azure.
- Familiaridade básica com as APIs do Socket.IO.
Criar um recurso Web PubSub para Socket.IO
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 ao Web PubSub para Socket.IO.
Execute os seguintes comandos. Mantenha a cadeia de conexão retornada em algum lugar, pois 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
Grave o código do lado do servidor do aplicativo
Comece a escrever o código do aplicativo trabalhando no lado do servidor.
Criar um servidor HTTP
Criar um projeto Node.js:
mkdir codestream cd codestream npm init
Instale o SDK do servidor e o Expresso:
npm install @azure/web-pubsub-socket.io npm install express
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')));
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 valorroom
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); });
Crie o Web PubSub para o servidor Socket.IO
Importe o Web PubSub para o SDK Socket.IO e defina as opções:
Cadeias de conexão brutas aparecem neste artigo somente para fins de demonstração. Em ambientes de produção, sempre proteja suas chaves de acesso. Use o Azure Key Vault para gerenciar e girar suas chaves com segurança e proteger sua conexão com
WebPubSubServiceClient
./*server.js*/ const { useAzureSocketIO } = require("@azure/web-pubsub-socket.io"); const wpsOptions = { hub: "codestream", connectionString: process.argv[2] }
Crie um Web PubSub para o servidor Socket.IO:
/*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 a lógica de negócios
Agora que você criou um servidor Socket.IO hospedado pelo Web PubSub, você 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.
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"); });
Cada cliente emite dois eventos aos quais o servidor pode responder:
joinRoom
esendToRoom
. Depois que o servidor obtém o valorroom_id
que um cliente deseja ingressar, você usasocket.join
da API do Socket.IO para ingressar o cliente de destino na sala especificada./*server.js*/ socket.on('joinRoom', async (message) => { const room_id = message["room_id"]; await socket.join(room_id); });
Depois que um cliente é ingressado, o servidor informa ao cliente sobre o resultado bem-sucedido enviando um evento
message
. Quando o cliente recebe um eventomessage
com um tipo deackJoinRoom
, o cliente pode pedir ao servidor para enviar o estado mais recente do editor./*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'}); } // ... });
Quando um cliente envia um evento
sendToRoom
para o servidor, o servidor transmite as alterações para o estado do editor de código para a sala especificada. Todos os clientes na sala agora podem receber a atualização mais recente.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 }); });
Gravar o código do lado do cliente do aplicativo
Agora que os procedimentos do lado do servidor foram concluídos, você pode trabalhar no lado do cliente.
Instalaçã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 o Web PubSub para o 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 função initialize(url)
organiza algumas operações de instalação em conjunto:
- Busca o ponto de extremidade para um serviço do Azure do servidor HTTP
- Cria uma instância do Monaco Editor
- Estabelece uma conexão persistente com o Web PubSub para Socket.IO
Cliente gravador
Conforme mencionado anteriormente, você tem duas funções de usuário no lado do cliente: gravador e visualizador. Tudo o que o gravador digita é transmitido para a tela do visualizador.
Obtenha o ponto de extremidade para o Web PubSub para Socket.IO e o valor
room_id
:/*client.js*/ let [socket, editor, room_id] = await initialize('/negotiate');
Quando o cliente gravador está conectado ao servidor, o servidor envia um evento
login
para o gravador. O gravador pode responder solicitando que o servidor ingresse em uma sala especificada. A cada 200 milissegundos, o cliente gravador envia o estado mais recente do editor para a sala. Uma função chamadaflush
organiza a lógica de envio./*client.js*/ socket.on("login", () => { updateStatus('Connected'); joinRoom(socket, `${room_id}`); setInterval(() => flush(), 200); // Update editor content // ... });
Se um gravador não fizer edições,
flush()
não fará nada e simplesmente retornará. Caso contrário, as alterações no estado do editor serã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(); }
Quando um novo cliente visualizador estiver conectado, o visualizador precisará obter o estado completo mais recente do editor. Para fazer isso, uma mensagem que contém dados
sync
é enviada ao cliente gravador. A mensagem pede ao cliente gravador para enviar 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
Assim como o cliente gravador, o cliente visualizador cria seu cliente Socket.IO por meio de
initialize()
. Quando o cliente visualizador está conectado e recebe um eventologin
do servidor, ele solicita que o servidor ingresse na sala especificada. A consultaroom_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}`); });
Quando um cliente visualizador recebe um evento
message
do servidor e o tipo de dados éackJoinRoom
, o cliente visualizador pede ao cliente 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 //... });
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; } } });
Implemente
joinRoom()
esendToRoom()
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 o aplicativo
Localizar o repositório
As seções anteriores abordaram a lógica central relacionada à sincronização do estado do editor entre os visualizadores e o gravador. 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.
Iniciar o servidor
node server.js <web-pubsub-connection-string>
Essa é a cadeia de conexão que você recebeu em uma etapa anterior.
Reproduzir com o editor de código em tempo real
Abra http://localhost:3000
em uma guia do navegador. Abra outra guia com a URL exibida na primeira página da Web.
Se você escrever código na primeira guia, deverá ver sua digitação refletida em tempo real na outra guia. O Web PubSub para Socket.IO facilita a passagem de mensagens na nuvem. O servidor express
serve apenas o arquivo index.html
estático e o ponto de extremidade /negotiate
.