Programmazione della rete asincrona mediante l'estensione reattiva


25

Dopo aver fatto un po 'di programmazione (più o meno) "di basso livello" asincrona socketanni fa (in modo EAP) basato su eventi e recentemente spostato "su" in un TcpListener(Modello di programmazione asincrono (APM) ) e poi nel tentativo di passare a async/await(Task-based Asynchronous Pattern (TAP) ), l'ho praticamente avuto con il continuare a dovermi preoccupare di tutto questo "impianto idraulico di basso livello". Quindi stavo pensando; perché non provare RX( estensioni reattive ) dal momento che potrebbe adattarsi meglio al mio dominio problematico.

Un sacco di codice che scrivo deve fare con molti client che si connettono tramite Tcp alla mia applicazione, che quindi avvia una comunicazione bidirezionale (asincrona). Il client o il server può decidere in qualsiasi momento che un messaggio deve essere inviato e farlo, quindi questa non è la tua request/responseconfigurazione classica ma più di una "linea" in tempo reale, bidirezionale, aperta a entrambe le parti per inviare quello che vogliono , ogni volta che vogliono. (Se qualcuno ha un nome decente per descriverlo, sarei felice di ascoltarlo!).

Il "protocollo" differisce per applicazione (e non è realmente rilevante per la mia domanda). Tuttavia, ho una domanda iniziale:

  1. Dato che è in esecuzione un solo "server", ma deve tenere traccia di molte (di solito migliaia) di connessioni (ad es. Client) ognuna con (per mancanza di una migliore descrizione) la propria "macchina a stati" per tenere traccia delle proprie stati ecc., quale approccio preferiresti? EAP / TAP / APM? RX sarebbe anche considerata un'opzione? Se no, perché?

Quindi, ho bisogno di lavorare su Async poiché a) non è un protocollo di richiesta / risposta, quindi non posso avere un thread / client in una chiamata di blocco "in attesa di un messaggio" o di blocco "invio di un messaggio" (tuttavia, se l'invio è bloccando per quel client solo io potrei conviverci) e b) ho bisogno di gestire molte connessioni simultanee. Non vedo alcun modo di farlo (in modo affidabile) usando il blocco delle chiamate.

La maggior parte delle mie applicazioni sono correlate a VoiP; che si tratti di messaggi SIP da SIP cient o di messaggi PBX (correlati) da applicazioni come FreeSwitch / OpenSIPS ecc. ma è possibile, nella sua forma più semplice, provare a immaginare un server "chat" che prova a gestire molti client "chat". La maggior parte dei protocolli sono basati su testo (ASCII).

Quindi, dopo aver implementato molte diverse permutazioni delle tecniche sopra menzionate, vorrei semplificare il mio lavoro creando un oggetto che posso semplicemente istanziare, dirlo su quale IPEndpointascoltare e farmelo dire ogni volta che succede qualcosa di interessante (che di solito usa gli eventi per, quindi alcuni EAP sono di solito mescolati con le altre due tecniche). La classe non dovrebbe preoccuparsi di cercare di "comprendere" il protocollo; dovrebbe semplicemente gestire le stringhe in entrata / in uscita. E così, tenendo d'occhio RX sperando che (alla fine) potesse semplificare il lavoro, ho creato un nuovo "violino" da zero:

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Reactive.Linq;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        var f = new FiddleServer(new IPEndPoint(IPAddress.Any, 8084));
        f.Start();
        Console.ReadKey();
        f.Stop();
        Console.ReadKey();
    }
}

public class FiddleServer
{
    private TcpListener _listener;
    private ConcurrentDictionary<ulong, FiddleClient> _clients;
    private ulong _currentid = 0;

    public IPEndPoint LocalEP { get; private set; }

    public FiddleServer(IPEndPoint localEP)
    {
        this.LocalEP = localEP;
        _clients = new ConcurrentDictionary<ulong, FiddleClient>();
    }

    public void Start()
    {
        _listener = new TcpListener(this.LocalEP);
        _listener.Start();
        Observable.While(() => true, Observable.FromAsync(_listener.AcceptTcpClientAsync)).Subscribe(
            //OnNext
            tcpclient =>
            {
                //Create new FSClient with unique ID
                var fsclient = new FiddleClient(_currentid++, tcpclient);
                //Keep track of clients
                _clients.TryAdd(fsclient.ClientId, fsclient);
                //Initialize connection
                fsclient.Send("connect\n\n");

                Console.WriteLine("Client {0} accepted", fsclient.ClientId);
            },
            //OnError
            ex =>
            {

            },
            //OnComplete
            () =>
            {
                Console.WriteLine("Client connection initialized");
                //Accept new connections
                _listener.AcceptTcpClientAsync();
            }
        );
        Console.WriteLine("Started");
    }

    public void Stop()
    {
        _listener.Stop();
        Console.WriteLine("Stopped");
    }

    public void Send(ulong clientid, string rawmessage)
    {
        FiddleClient fsclient;
        if (_clients.TryGetValue(clientid, out fsclient))
        {
            fsclient.Send(rawmessage);
        }
    }
}

public class FiddleClient
{
    private TcpClient _tcpclient;

    public ulong ClientId { get; private set; }

    public FiddleClient(ulong id, TcpClient tcpclient)
    {
        this.ClientId = id;
        _tcpclient = tcpclient;
    }

    public void Send(string rawmessage)
    {
        Console.WriteLine("Sending {0}", rawmessage);
        var data = Encoding.ASCII.GetBytes(rawmessage);
        _tcpclient.GetStream().WriteAsync(data, 0, data.Length);    //Write vs WriteAsync?
    }
}

Sono consapevole che, in questo "violino", ci sono alcuni dettagli specifici di implementazione; in questo caso sto lavorando con FreeSwitch ESL quindi il "connect\n\n"violino dovrebbe essere rimosso, quando si esegue il refactoring su un approccio più generico.

Sono anche consapevole del fatto che devo refactificare i metodi anonimi in metodi di istanza privata nella classe Server; Non sono sicuro di quale convenzione (ad esempio " OnSomething" per esempio) usare per i loro nomi di metodo?

Questa è la mia base / punto di partenza / fondazione (che necessita di alcune "modifiche"). Ho alcune domande a riguardo:

  1. Vedi la domanda precedente "1"
  2. Sono sulla buona strada? O le mie decisioni "progettuali" sono ingiuste?
  3. Dal punto di vista della concorrenza: questo farebbe fronte a migliaia di clienti (analizzando / gestendo i messaggi reali a parte)
  4. Sulle eccezioni: non sono sicuro di come sollevare le eccezioni all'interno dei client "fino" al server ("RX-saggio"); quale sarebbe un buon modo?
  5. Ora posso ottenere qualsiasi client connesso dalla mia classe di server (usando esso ClientId), supponendo che io esponga i client in un modo o nell'altro e chiamare direttamente i metodi su di essi. Posso anche chiamare metodi tramite la classe Server (ad esempio, il Send(clientId, rawmessage)metodo (mentre quest'ultimo approccio sarebbe un metodo di "convenienza" per portare rapidamente un messaggio dall'altra parte).
  6. Non sono sicuro di dove (e come) andare da qui:
    • a) ho bisogno di gestire i messaggi in arrivo; come lo impostarei? Posso ottenere lo stream ofcourse, ma dove gestirò il recupero dei byte ricevuti? Penso di aver bisogno di una sorta di "ObservableStream" -qualcosa a cui posso abbonarmi? Lo metterei nel FiddleCliento FiddleServer?
    • b) Supponendo di voler evitare l'uso dell'evento fino a quando queste FiddleClient/ FiddleServerclassi non sono implementate in modo più specifico per personalizzare la gestione del protocollo specifico dell'applicazione, ecc. utilizzando classi FooClient/ più specifiche FooServer: come potrei passare dall'ottenere i dati nelle classi "Fiddle" sottostanti alle loro controparti più specifiche?

Articoli / collegamenti che ho già letto / scremato / usato come riferimento:


Dai un'occhiata alla libreria ReactiveSocket esistente
Flagbug

2
Non sto cercando librerie o collegamenti (anche se per riferimento sono apprezzati) ma per input / consigli / aiuto sulle mie domande e sulla configurazione generale. Voglio imparare a migliorare il mio codice ed essere in grado di decidere meglio in quale direzione prendere questo, valutare pro e contro, ecc. Non fare riferimento a una libreria, rilasciarla e andare avanti. Voglio imparare da questa esperienza e acquisire maggiore esperienza con la programmazione Rx / rete.
RobIII,

Certo, ma poiché la libreria è open source puoi vedere come è stata implementata lì
Flagbug

1
Certo, ma guardare il codice sorgente non spiega perché sono state / sono prese alcune decisioni di progettazione. E poiché sono relativamente nuovo su Rx in combinazione con la programmazione in rete, non ho abbastanza esperienza per dire se questa libreria è utile, se il design ha senso, se sono state prese le giuste decisioni e anche se è giusto per me.
RobIII,

Penso che sarà bene ripensare al server come membro attivo di una stretta di mano, quindi quel server avvia la connessione invece di ascoltare le connessioni. Ad esempio, qui: codeproject.com/Articles/20250/Reverse-Connection-Shell

Risposte:


1

... Vorrei semplificare il mio lavoro creando un oggetto che posso semplicemente istanziare, dirlo su quale IPEndpoint ascoltare e farmelo dire ogni volta che succede qualcosa di interessante ...

Dopo aver letto quella frase ho pensato subito agli "attori". Gli attori sono molto simili agli oggetti, tranne per il fatto che hanno un solo input in cui viene passato il messaggio (anziché chiamare direttamente i metodi dell'oggetto) e funzionano in modo asincrono. In un esempio molto semplificato ... dovresti creare l'attore e inviargli un messaggio con IPEndpoint e l'indirizzo dell'attore a cui inviare il risultato. Si spegne e funziona in background. Ne risulti solo quando accade "qualcosa di interessante". È possibile creare un'istanza di tutti gli attori necessari per gestire il carico.

Non ho familiarità con le librerie di attori in .Net anche se so che ce ne sono. Conosco la libreria TPL Dataflow (ci sarà una sezione che la copre nel mio libro http://DataflowBook.com ) e dovrebbe essere facile implementare un modello di attore semplice con quella libreria.


Sembra interessante. Preparerò un progetto giocattolo per vedere se è adatto a me. Grazie per il suggerimento
RobIII,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.