Dashboard software real-time in html senza backend pesante

di il
26 risposte

26 Risposte - Pagina 2

  • Re: Dashboard software real-time in html senza backend pesante

    Mia figlia mi ha detto che esiste una tecnica push standardizzata con l'HTTP 2.0  ma che ha molte controindicazioni e sembra che molti browser la vogliano togliere. Purtroppo una dashboard che aggiorni i suoi dati con la tecnica di polling non sarebbe l'ideale per la mia applicazione, perchè, ricordo, non devo fare un sito web ma una dashboard per un'applicazione in realtime .NET Framework.
    Cercherò informazioni sulla tecnologia websocket se questa è applicabile al mio server HTTP

    Grazie

  • Re: Dashboard software real-time in html senza backend pesante

    Websocket e' una tecnologia per processare dati in realtime (centinaia o migliaia di aggiornamenti al secondo).

    Se a te basta un aggiornamento al secondo, che immagino sia piu' che sufficiente, o al limite DUE aggiornamenti al secondo, ti basta un ‘polling’ : ogni tot il cliente chiede al server i nuovi dati, ed il CLIENT aggiorna l'interfaccia (NON il server). 

    Il server risponde in qualche manciata di MICROSECONDI, al limite qualche manciata di millisecondi, CENTINAIA DI VOLTE piu' velocemente di quanto ti serve. Quindi, nel 99% o anche 99.99% del tempo sta lì a non fare niente.

    Non fasciarti la testa su elucubrazioni problemi relativi alle ‘performance’: NON C'E' NE SONO!

  • Re: Dashboard software real-time in html senza backend pesante

    Sì, infatti il sistema del polling credo sia quello più semplice, purtroppo il ritardo di 80mSec nel processare un messaggio MIDI diventa udibile anche in una sola nota musicale e il musicista non è affatto contento. Potrei però essere più tranquillo se abbasso la priorità del thread in ascolto http. Farò delle prove in merito ….

    Riguardo alla webSocket ho trovato questo articolo in cui viene esposto un WebSocket Server in C#:
    https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_server

    Dopo l'handshake in http la connessione diventa WebSoket e sia il Client che il Server possono indifferentemente iniziare ad inviare dati.

    L'unica perplessità è che nell'articolo si dice che non tutti i browser accettino di aprire una websocket su una connessione non sicura (!https) e in tal caso occorrerebbe interporre un server Proxy. A questo punto però la cosa si fa troppo complessa….

  • Re: Dashboard software real-time in html senza backend pesante

    Inizia con la soluzione PIU' semplice!

    SE, dopo averla ‘limata’ fino ALL'OSSO, non ti soddisfa ancora, e' il momento di passare a qualcosa di meglio (e piu' complicato). 

  • Re: Dashboard software real-time in html senza backend pesante

    15/07/2024 - zorban62 ha scritto:


    Purtroppo una dashboard che aggiorni i suoi dati con la tecnica di polling non sarebbe l'ideale per la mia applicazione, perchè, ricordo, non devo fare un sito web ma una dashboard per un'applicazione in realtime .NET Framework.

    Quando nella mia prima risposta ti sconsigliavo questa strada perché il “real time” non è facilmente ottenibile con gli strumenti di cui si parlava all'inizio, hai detto che non era così fondamentale. Ora mi pare che la questione sia cambiata, o che non fosse chiara all'inizio, ovvero che la pagina debba invece essere proprio “in real time” (benché bisogna capire che cosa si intende nello specifico).

    15/07/2024 - zorban62 ha scritto:


    Sì, infatti il sistema del polling credo sia quello più semplice, purtroppo il ritardo di 80mSec nel processare un messaggio MIDI diventa udibile anche in una sola nota musicale e il musicista non è affatto contento.

    Ma questo ritardo a cosa sarebbe dovuto? Cosa ha che fare il ritardo nel processare il messaggio MIDI con l'applicazione Web?

    15/07/2024 - zorban62 ha scritto:


    Potrei però essere più tranquillo se abbasso la priorità del thread in ascolto http.

    Essendo che quel thread passa la maggior parte del suo tempo in “idle”, ossia in attesa, sono praticamente certo che questo non cambi lo stato delle cose. Ma bisogna capire bene cosa si sta cercando di fare, quali sono i comandi impartiti dalla pagina, che tipo di notifiche si devono avere, che tempi di reazione sono richiesti, come i comandi impartiti sulla pagina vanno tecnicamente a finire nel cosiddetto “engine”…

    Ci sono una marea di aspetti su cui non sono chiari i requisiti, gli obiettivi e l'implementazione, almeno per me.

  • Re: Dashboard software real-time in html senza backend pesante

    L'applicazione prevede un'interfaccia midi con un ingresso e 7 uscite. L'interfaccia in questione è 
    https://www.amazon.it/dp/B07HQLK5BP?psc=1&ref=ppx_yo2ov_dt_b_product_details
    L'engine è in ascolto sulla porta MIDI 1 e alla ricezione dei messaggi midi li processa e in base al settaggio di alcuni parametri memorizzati in oggetti in memoria (Parti), produce nelle 7 uscite altri messaggi midi. Questo processo è prioritario nel senso che non può essere interrotto per più di qualche decina di millisecondi altrimenti la produzione in ritardo della nota o l'invio dei controller continui si fa sentire.

    Al momento con la dashboard in HTTP senza polling il sistema funziona egregiamente, la dashboard visualizza lo stato dell'engine e i parametri su cui lavora, attivi in quel momento (attualmente solo se si fa il refresh della pagina). 

    Adesso proverò ad inserire un polling che richiede il refresh della pagina ogni 500 mSec ma non vorrei che per spedire la nuova pagina html al client, il server, che gira su un altro thread, sottraesse tempo al thread dell'engine. Ma forse è solo una mia preoccupazione esagerata. Forse potrei cercare di modificare la priorità dei due thread dando più tempo di CPU all'engine.

    Questo è quanto, non ho altre informazioni in più.

    Grazie.

  • Re: Dashboard software real-time in html senza backend pesante

    15/07/2024 - zorban62 ha scritto:


    Adesso proverò ad inserire un polling che richiede il refresh della pagina ogni 500 mSec

    Puoi anche usare la tecnica del “long polling”: vedi questo articolo.

    15/07/2024 - zorban62 ha scritto:


    non vorrei che per spedire la nuova pagina html al client, il server, che gira su un altro thread, sottraesse tempo al thread dell'engine

    Ma non hai una CPU multi-core? :)

  • Re: Dashboard software real-time in html senza backend pesante

    Prova a dare un'occhiata qui:

    https://github.com/glynnbird/metrics-collector-midi-microservice

    sempre che non sia troppo vecchio il progettino.

    Eventualmente prova a cercare qualche altra implementazione sempre con supporto RabbitMQ

  • Re: Dashboard software real-time in html senza backend pesante

    15/07/2024 - Alka ha scritto:


    15/07/2024 - zorban62 ha scritto:


    Adesso proverò ad inserire un polling che richiede il refresh della pagina ogni 500 mSec

    Puoi anche usare la tecnica del “long polling”: vedi questo articolo.

    Grazie di cuore, penso che questo sia quello che fa al caso mio. Il “Long Polling” mia figlia non lo conosceva. Io avevo pensato a una cosa del genere, cioè che il server non rispondesse subito alla richiesta del client ma soltanto quando resta "comodo" a lui …. non sapevo che esistesse un paradigma specifico chiamato “long polling.”

    Grazie di nuovo  !!^!!

    15/07/2024 - zorban62 ha scritto:


    non vorrei che per spedire la nuova pagina html al client, il server, che gira su un altro thread, sottraesse tempo al thread dell'engine

    Ma non hai una CPU multi-core? :)


    Mi son sempre chiesto se thread diversi girino "sempre" su core fisici diversi.
    In generale non può essere perchè se i thread sono > dei Core il SO deve per forza distribuirli.
    Ho letto che si parla allora di “core virtuali” messi a disposizione dal sistema operativo.
    C'è un modo per forzare obbligatoriamente l'esecuzione di due thread su core fisici diversi? 

  • Re: Dashboard software real-time in html senza backend pesante

    16/07/2024 - Toki ha scritto:


    RabbitMQ

    RabbitMQ usa comunque le WebSocket HTTP.
    Il problema delle WebSocket è se il browser riesce ad aprirle su connessioni non sicure (http) come quelle del server http che ho scritto in C#

    Ho trovato un'estensione Chrome web socket client test:
    https://chromewebstore.google.com/detail/simple-websocket-client/pfdhoblngboilpfeibdedpjgfnlcodoo?hl=it

    Ma l'ho potuta provare solo con https//echo.websocket.org che ha una connessione protetta.
    Rimango quindi con il dubbio. Dovrei implementare un WebSocket Server in C# e con questa estensione chrome verificare che funzioni.

    Penso che il "Long Polling" sia la soluzione migliore è più semplice dal punto di vista realizzativo.

  • Re: Dashboard software real-time in html senza backend pesante

    16/07/2024 - zorban62 ha scritto:


    Il “Long Polling” mia figlia non lo conosceva. Io avevo pensato a una cosa del genere, cioè che il server non rispondesse subito alla richiesta del client ma soltanto quando resta "comodo" a lui …. non sapevo che esistesse un paradigma specifico chiamato “long polling.”

    Diciamo che è un po' un barbatrucco, però se il numero di client che si collegano tramite le pagine non è eccessivo (perché non è ovviamente una soluzione scalabile), in genere funziona ed era una delle soluzioni impiegate ai tempi in cui non c'erano alternative per poter implementare una sorta di comunicazione “real time” che non richiedesse polling continuo e ripetuto.

    Con i WebSocket e correlati, la questione è stata poi risolta con tecnologie ad hoc, come è giusto che sia. :)

    16/07/2024 - zorban62 ha scritto:


    Mi son sempre chiesto se thread diversi girino "sempre" su core fisici diversi.

    Sempre no, ma in genere se si tratta di thread separati, la CPU e l'OS cercano di ottimizzare al meglio lo sfruttamento di tutti i core.

    16/07/2024 - zorban62 ha scritto:


    C'è un modo per forzare obbligatoriamente l'esecuzione di due thread su core fisici diversi? 

    So che è possibile impostare una “affinità” con una CPU (o core), quantomeno dal Task Manager, quindi è plausibile che vi sia anche una API in grado di farlo, ma onestamente mi pare rischioso: a meno di non avere problemi molto specifici con la gestione tradizionale e/o conoscendo perfettamente le politiche di scheduling del sistema operativo, “deviare” dal pilota automatico può essere rischioso, se non si è perfettamente in grado di gestire manualmente nel modo corretto la situazione. In caso di incertezza, meglio lasciare il controllo al sistema.

  • Re: Dashboard software real-time in html senza backend pesante

    Ho scritto server e client con il long polling ma mi sono accorto che comunque la comunicazione rimaneva unidirezionale. Nel caso del LongPolling è il server che invia dati al client ma il viceversa non può avvenire perchè il client è in attesa della risposta dal server.

    Ho quindi indagato le websocket ed ho realizzato questo websocket server in .NET Framework e un semplice frontend di test.

    A questo punto però mi sono accorto che la comunicazione era sì effettivamente bidirezionale ma non ero più in grado di caricare altre pagine html perchè il server aveva commutato sul protocollo ws:// 

    Quindi ho fatto una cosa di questo tipo che però non so se sia proprio ortodossa:

    1. Quando il client invia sulla websocket un comando (nel mio esempio ‘html’) il server chiude la websocket e riapre l'httplistner tornando in ascolto per una connessione http.
    2. Quando il client riceve l'evento websocket.onclose()  richiama una nuova pagina html (window.location.assign(window.location.origin + "/pagina2.html");

    Questo metodo funziona egregiamente ma non so se è l'approccio migliore.

    Cosa mi consigliate?

    SERVER WEBSOCKET in C# .NET framework

    using System;
    using System.IO;
    using System.Net;
    using System.Net.WebSockets;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace WebSocketNet
    {
        public class WebSocketServer : IDisposable
        {
            public static HttpListener httplistener;
            public static string url = "http://+:8400/";
            public static int pageViews = 0;
            public static int requestCount = 0;
            public static WebSocket webSocket;
    
            public WebSocketServer()
            {
                //------------------------- ASCOLTO SULLA PORTA HTTP -----------------------------------
                //
                // ---> IMPORTANTE
                //Prenotazione url per gli utenti non amministratori
                //Comando Per inserire la prenotazione:  netsh http add urlacl url=http://+:8400/ user=everyone
                //Comando per verificare la prenotazione: netsh http show urlacl http://+:8400/
                //Altrimenti ritorna errore se non si avvia in modalità amministratore
    
                // Create a Http server and start listening for incoming connections
                httplistener = new HttpListener();
                httplistener.Prefixes.Add("http://+:8400/");
                httplistener.Start();
    
                Console.WriteLine("Listening for connections");
    
                Task listenTask = HandleIncomingConnections();
            }
    
    
            public static async Task HandleIncomingConnections()
            {
                bool runServer = true;
    
                while (runServer)
                {
                    HttpListenerContext context = await httplistener.GetContextAsync();
    
                    if (context.Request.IsWebSocketRequest)
                    {
                        HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(subProtocol: null);
                        webSocket = webSocketContext.WebSocket;
    
                        Console.WriteLine("Client connected");
    
                        Task listenWs = ListenWs();
    
                        break;
                    }
                    else
                    {
                        // Peel out the requests and response objects
                        HttpListenerRequest req = context.Request;
                        HttpListenerResponse resp = context.Response;
    
                        // Print out some info about the request
                        Console.WriteLine("Request #: {0}", ++requestCount);
                        Console.WriteLine(req.Url.ToString());
                        Console.WriteLine(req.HttpMethod);
                        Console.WriteLine(req.UserHostName);
                        Console.WriteLine(req.UserAgent);
                        Console.WriteLine();
    
                        string risposta = "";
    
                        if ((req.HttpMethod == "GET"))
                        {
                            if (req.Url.Segments.Length == 1)
                            {
                                risposta = ReadFile(@".\index.html");
                                resp.ContentType = "text/html";
                            }
                            else
                            {
                                if (req.Url.Segments[1] != "favicon.ico")
                                {
                                    risposta = ReadFile(req.Url.Segments[1]);
                                    if (req.Url.Segments[1] == "main.css") resp.ContentType = "text/css";
                                    else if (req.Url.Segments[1] == "app.js") resp.ContentType = "text/js";
                                    else resp.ContentType = "text/html";
                                }
                            }
                        }
    
                        // Write the response info
                        byte[] data;
                        data = Encoding.UTF8.GetBytes(risposta);
                        resp.ContentEncoding = Encoding.UTF8;
                        resp.ContentLength64 = data.LongLength;
                        // Write out to the response stream (asynchronously), then close it
                        await resp.OutputStream.WriteAsync(data, 0, data.Length);
                        resp.Close();
                    }
                }
            }
    
            public static async Task ListenWs()
            {
                while (true)
                {
                    byte[] receiveBuffer = new byte[1024];
                    if (webSocket.State == WebSocketState.Open)
                    {
                        var receiveResult = await webSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
                        if (receiveResult.MessageType == WebSocketMessageType.Text)
                        {
                            string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, receiveResult.Count);
                            Console.WriteLine($"Received message: {receivedMessage}");
                            if (receivedMessage == "html")
                            {
                                await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                                Console.WriteLine("WebSocket closed.");
                                Task listenTask = HandleIncomingConnections();
                                break;
                            }
                        }
                        else if (receiveResult.MessageType == WebSocketMessageType.Close)
                        {
                            await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                            Console.WriteLine("WebSocket closed.");
                        }
                    }
                }
            }
    
            public void SendToClient(string text)
            {
                Task listenWs = Send(text);
            }
    
            private static async Task Send(string text)
            {
                //byte[] buffer = Encoding.UTF8.GetBytes($"Hello from server");
                byte[] buffer = Encoding.UTF8.GetBytes(text);
                await webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
            }
    
            private static string ReadFile(string path)
            {
                string stringfile;
                using (StreamReader IndexFile = new StreamReader(path))
                {
                    stringfile = IndexFile.ReadToEnd();
                }
                return stringfile;
            }
    
            public void Dispose()
            {
                httplistener.Close();
            }
        }
    }

    FrontEnd inviato al client

    <!doctype html>
    <html lang="en">
      <style>
        textarea {
          vertical-align: bottom;
        }
        #output {
          overflow: auto;
        }
        #output > p {
          overflow-wrap: break-word;
        }
        #output span {
          color: blue;
        }
        #output span.error {
          color: red;
        }
      </style>
      <body>
        <h2>WebSocket Test Pagina 0</h2>
        <textarea cols="60" rows="6"></textarea>
        <button>send</button>
        <div id="output"></div>
      </body>
      <script>
        // http://www.websocket.org/echo.html
        const button = document.querySelector("button");
        const output = document.querySelector("#output");
        const textarea = document.querySelector("textarea");
        
        const origin = window.location.origin;
        //const wsUri = "ws://127.0.0.1:8400/";
        //const wsUri = "ws://" + window.location.hostname;  //  ws://localhost
        //const wsUri = "ws://" + window.location.origin;  //  ws://http://localhost:8400
        const wsUri = "ws://" + window.location.hostname + ":" + window.location.port;
        const websocket = new WebSocket(wsUri);
        let text = textarea.value;
    
        button.addEventListener("click", onClickButton);
    
        websocket.onopen = (e) => {
          writeToScreen("CONNECTED");
          doSend("WebSocket rocks");
        };
    
        websocket.onclose = (e) => {
          writeToScreen("DISCONNECTED");
          if (text == "html"){
            //window.location.reload(); //Ricarica la stessa Pagina
            window.location.assign(origin + "/page1.html");
            //fetch('http://127.0.0.1:8400/', { mode: 'no-cors'});
          }
        };
    
        websocket.onmessage = (e) => {
          writeToScreen(`<span>RESPONSE: ${e.data}</span>`);
        };
    
        websocket.onerror = (e) => {
          writeToScreen(`<span class="error">ERROR:</span> ${e.data}`);
        };
    
        function doSend(message) {
          writeToScreen(`SENT: ${message}`);
          websocket.send(message);
        }
    
        function writeToScreen(message) {
          output.insertAdjacentHTML("afterbegin", `<p>${message}</p>`);
        }
    
        function onClickButton() {
          text = textarea.value;
    
          text && doSend(text);
          textarea.value = "";
          textarea.focus();
        }
        
      </script>
    </html>
    
Devi accedere o registrarti per scrivere nel forum
26 risposte