Tworzenie aplikacji przesyłania strumieniowego kodu w czasie rzeczywistym przy użyciu Socket.IO i hostowanie jej na platformie Azure
Tworzenie środowiska w czasie rzeczywistym, takiego jak funkcja współtworzenia w programie Microsoft Word , może być trudne.
Dzięki łatwym w użyciu interfejsom API Socket.IO okazała się biblioteką komunikacji w czasie rzeczywistym między klientami a serwerem. Jednak Socket.IO użytkownicy często zgłaszają trudności ze skalowaniem połączeń Socket.IO. Dzięki usłudze Web PubSub dla Socket.IO deweloperzy nie muszą już martwić się o zarządzanie połączeniami trwałymi.
Omówienie
W tym artykule pokazano, jak utworzyć aplikację, która umożliwia programowi coder przesyłanie strumieniowe działań kodowania do odbiorców. Ta aplikacja jest kompilowany przy użyciu:
- Edytor Monaco, edytor kodu, który obsługuje program Visual Studio Code.
- Express, platforma internetowa Node.js.
- Interfejsy API, które biblioteka Socket.IO zapewnia do komunikacji w czasie rzeczywistym.
- Hostowanie połączeń Socket.IO korzystających z usługi Web PubSub na potrzeby Socket.IO.
Zakończona aplikacja
Gotowa aplikacja umożliwia użytkownikowi edytora kodu udostępnianie linku internetowego, za pomocą którego użytkownicy mogą obserwować wpisywanie.
Aby procedury były skoncentrowane i szyfrowane w ciągu około 15 minut, w tym artykule zdefiniowano dwie role użytkownika i czynności, które mogą wykonywać w edytorze:
- Składnik zapisywania, który może wpisać w edytorze online i zawartość jest przesyłana strumieniowo
- Osoby przeglądające, które otrzymują zawartość w czasie rzeczywistym wpisanym przez składnik zapisywania i nie mogą edytować zawartości
Architektura
Produkt | Przeznaczenie | Świadczenia |
---|---|---|
biblioteka Socket.IO | Zapewnia mechanizm wymiany danych dwukierunkowych o małych opóźnieniach między aplikacją zaplecza a klientami | Łatwe w użyciu interfejsy API, które obejmują większość scenariuszy komunikacji w czasie rzeczywistym |
Web PubSub for Socket.IO | Hostuje połączenia trwałe protokołu WebSocket lub oparte na sondach z klientami Socket.IO | Obsługa 100 000 połączeń współbieżnych; uproszczona architektura aplikacji |
Wymagania wstępne
Aby wykonać wszystkie kroki opisane w tym artykule, potrzebne są następujące elementy:
- Konto platformy Azure. Jeśli nie masz subskrypcji platformy Azure, przed rozpoczęciem utwórz bezpłatne konto platformy Azure.
- Interfejs wiersza polecenia platformy Azure (wersja 2.29.0 lub nowsza) lub usługa Azure Cloud Shell do zarządzania zasobami platformy Azure.
- Podstawowa znajomość interfejsów API Socket.IO.
Tworzenie usługi Web PubSub dla zasobu Socket.IO
Użyj interfejsu wiersza polecenia platformy Azure, aby utworzyć zasób:
az webpubsub create -n <resource-name> \
-l <resource-location> \
-g <resource-group> \
--kind SocketIO \
--sku Free_F1
Pobieranie parametry połączenia
Parametry połączenia umożliwia nawiązywanie połączenia z usługą Web PubSub na potrzeby Socket.IO.
Uruchom następujące polecenia. Zachowaj zwróconą parametry połączenia gdzieś, ponieważ będzie ona potrzebna po uruchomieniu aplikacji w dalszej części tego artykułu.
az webpubsub key show -n <resource-name> \
-g <resource-group> \
--query primaryKey \
-o tsv
Pisanie kodu po stronie serwera aplikacji
Zacznij pisać kod aplikacji, pracując po stronie serwera.
Tworzenie serwera HTTP
Utwórz projekt Node.js:
mkdir codestream cd codestream npm init
Zainstaluj zestaw SDK serwera i platformę Express:
npm install @azure/web-pubsub-socket.io npm install express
Zaimportuj wymagane pakiety i utwórz serwer HTTP do obsługi plików statycznych:
/*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')));
Zdefiniuj punkt końcowy o nazwie
/negotiate
. Klient modułu zapisywania najpierw osiąga ten punkt końcowy. Ten punkt końcowy zwraca odpowiedź HTTP. Odpowiedź zawiera punkt końcowy, którego klient powinien użyć do nawiązania trwałego połączenia. Zwraca również wartość przypisanąroom
przez klienta./*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); });
Tworzenie usługi Web PubSub dla serwera Socket.IO
Zaimportuj usługę Web PubSub dla zestawu SDK Socket.IO i zdefiniuj opcje:
/*server.js*/ const { useAzureSocketIO } = require("@azure/web-pubsub-socket.io"); const wpsOptions = { hub: "codestream", connectionString: process.argv[2] }
Utwórz web pubSub dla serwera Socket.IO:
/*server.js*/ const io = require("socket.io")(); useAzureSocketIO(io, wpsOptions);
Te dwa kroki różnią się nieco od sposobu tworzenia serwera Socket.IO zgodnie z opisem w tej dokumentacji Socket.IO. W tych dwóch krokach kod po stronie serwera może odciążyć zarządzanie trwałymi połączeniami z usługą platformy Azure. Za pomocą usługi platformy Azure serwer aplikacji działa tylko jako lekki serwer HTTP.
Implementowanie logiki biznesowej
Po utworzeniu serwera Socket.IO hostowanego przez usługę Web PubSub można zdefiniować sposób komunikowania się klientów i serwerów przy użyciu interfejsów API Socket.IO. Ten proces jest nazywany implementowanie logiki biznesowej.
Po nawiązaniu połączenia klient aplikacji informuje klienta, że jest zalogowany, wysyłając zdarzenie niestandardowe o nazwie
login
./*server.js*/ io.on('connection', socket => { socket.emit("login"); });
Każdy klient emituje dwa zdarzenia, na które serwer może reagować:
joinRoom
isendToRoom
. Gdy serwer pobierzeroom_id
wartość, którą klient chce dołączyć, użyj interfejsusocket.join
API Socket.IO, aby dołączyć klienta docelowego do określonego pokoju./*server.js*/ socket.on('joinRoom', async (message) => { const room_id = message["room_id"]; await socket.join(room_id); });
Po dołączeniu klienta serwer informuje klienta o pomyślnym wyniku, wysyłając
message
zdarzenie. Gdy klient odbieramessage
zdarzenie o typieackJoinRoom
, klient może poprosić serwer o wysłanie najnowszego stanu edytora./*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'}); } // ... });
Gdy klient wysyła
sendToRoom
zdarzenie na serwer, serwer emituje zmiany stanu edytora kodu do określonego pokoju. Wszyscy klienci w pokoju mogą teraz otrzymać najnowszą aktualizację.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 }); });
Pisanie kodu po stronie klienta aplikacji
Po zakończeniu procedur po stronie serwera można pracować po stronie klienta.
Konfiguracja początkowa
Aby komunikować się z serwerem, należy utworzyć klienta Socket.IO. Pytanie brzmi, z którym serwerem klient powinien nawiązać trwałe połączenie. Ponieważ używasz usługi Web PubSub dla Socket.IO, serwer jest usługą platformy Azure. Pamiętaj, że zdefiniowano /negotiate route do obsługi klientów punktu końcowego do web PubSub dla 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];
}
Funkcja initialize(url)
organizuje kilka operacji konfiguracji razem:
- Pobiera punkt końcowy do usługi platformy Azure z serwera HTTP
- Tworzy wystąpienie edytora Monaco
- Ustanawia trwałe połączenie z usługą Web PubSub dla Socket.IO
Klient modułu zapisywania
Jak wspomniano wcześniej, po stronie klienta masz dwie role użytkownika: składnik zapisywania i przeglądarki. Wszystkie typy składników zapisywania są przesyłane strumieniowo do ekranu osoby przeglądanej.
Pobierz punkt końcowy do usługi Web PubSub dla Socket.IO i
room_id
wartości:/*client.js*/ let [socket, editor, room_id] = await initialize('/negotiate');
Gdy klient składnika zapisywania jest połączony z serwerem, serwer wysyła
login
zdarzenie do składnika zapisywania. Składnik zapisywania może odpowiedzieć, prosząc serwer o przyłączenie się do określonego pokoju. Co 200 milisekund klient zapisywania wysyła najnowszy stan edytora do pokoju. Funkcja o nazwieflush
organizuje logikę wysyłania./*client.js*/ socket.on("login", () => { updateStatus('Connected'); joinRoom(socket, `${room_id}`); setInterval(() => flush(), 200); // Update editor content // ... });
Jeśli składnik zapisywania nie wprowadza żadnych zmian,
flush()
nic nie robi i po prostu zwraca. W przeciwnym razie zmiany stanu edytora są wysyłane do pokoju./*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(); }
Po nawiązaniu połączenia nowego klienta przeglądarki przeglądarka musi uzyskać najnowszy pełny stan edytora. Aby to osiągnąć, komunikat zawierający
sync
dane jest wysyłany do klienta modułu zapisywania. Komunikat prosi klienta modułu zapisywania o wysłanie pełnego stanu edytora./*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, }); } });
Klient przeglądarki
Podobnie jak klient zapisywania, klient przeglądarki tworzy klienta Socket.IO za pośrednictwem programu
initialize()
. Gdy klient przeglądarki jest połączony i odbieralogin
zdarzenie z serwera, prosi serwer o przyłączenie się do określonego pokoju. Zapytanieroom_id
określa pomieszczenie./*client.js*/ let [socket, editor] = await initialize(`/register?room_id=${room_id}`) socket.on("login", () => { updateStatus('Connected'); joinRoom(socket, `${room_id}`); });
Gdy klient przeglądarki odbiera
message
zdarzenie z serwera, a typ danych toackJoinRoom
, klient przeglądarki prosi klienta modułu zapisywania w pokoju o wysłanie pełnego stanu edytora./*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 //... });
Jeśli typ danych to
editorMessage
, klient przeglądarki aktualizuje edytor zgodnie z rzeczywistą zawartością./*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; } } });
Implementowanie
joinRoom()
interfejsów API Socket.IO isendToRoom()
przy użyciu nich:/*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 }); }
Uruchamianie aplikacji
Znajdź repozytorium
W poprzednich sekcjach opisano podstawową logikę związaną z synchronizowaniem stanu edytora między przeglądarkami a modułem zapisywania. Pełny kod można znaleźć w repozytorium przykładów.
Klonowanie repozytorium
Możesz sklonować repozytorium i uruchomić npm install
polecenie , aby zainstalować zależności projektu.
Uruchamianie serwera
node server.js <web-pubsub-connection-string>
Jest to parametry połączenia otrzymana we wcześniejszym kroku.
Odtwarzanie za pomocą edytora kodu w czasie rzeczywistym
Otwórz na karcie przeglądarki. Otwórz http://localhost:3000
inną kartę z adresem URL wyświetlanym na pierwszej stronie internetowej.
Jeśli napiszesz kod na pierwszej karcie, na drugiej karcie powinno być widoczne wpisywanie odzwierciedlone w czasie rzeczywistym. Usługa Web PubSub dla Socket.IO ułatwia przekazywanie komunikatów w chmurze. Serwer express
obsługuje tylko plik statyczny index.html
i /negotiate
punkt końcowy.