Gestione di più tipi di pacchetti in Java 8

0

Ho un'implementazione di server di gioco basata su Netty che gestisce circa 40 pacchetti distinti con la propria struttura di serializzazione, per brevità mi riferirò a loro come FooPacket , BarPacket , ... Questi tipi di pacchetti sono stati assegnati numerici consecutivi "tipi di pacchetto", che sono byte senza segno. Ad esempio, FooPacket è di tipo 0x01, BarPacket è di tipo 0x02, ecc ...

Tutti questi pacchetti estendono una classe astratta, MyGameNetworkPacket .

Il mio attuale approccio con Netty è quello di utilizzare un livello di decodifica iniziale per convertire un flusso TCP in una serie di oggetti pacchetto non parsed, che sono definiti come costituiti da un tipo di pacchetto, lunghezza e buffer contenente il contenuto del pacchetto effettivo. (non viene eseguito alcun riempimento di byte). Successivamente, trasmetto questo pacchetto lungo la pipeline a uno dei pochi "processori di deserializzazione", ognuno dei quali gestisce un insieme di tipi di pacchetti che sono semanticamente correlati tra loro (ad esempio uno di questi processori potrebbe gestire i messaggi di stato della connessione, mentre un altro potrebbe gestire pacchetti relativi ad un certo aspetto del gioco stesso). Come per il design dell'uso di MessageToMessageDecoder di Netty, il mio decodificatore è implementato come:

protected void decode (ChannelHandlerContext ctx, UnparsedPacket msg, List<Object> out)
        throws Exception {
    switch (msg.command) {
        case FooPacket.COMMAND_ID:
            FooPacket fooPacket = new FooPacket(msg.frame);
            out.add(fooPacket);
            break;
        case BarPacket.COMMAND_ID:
            BarPacket barPacket = new BarPacket(msg.frame);
            out.add(barPacket);
            break;
    }

}

Una volta ottenuta una struttura decisa, posso facilmente creare questo con un generatore di codice.

Inoltre, sovrascrivo public boolean acceptInboundMessage(Object) per specificare se un pacchetto unparsed debba essere gestito da questo decoder.

Questo sta ottenendo un po 'di quello che sembra essere un odore di codice per me, quindi stavo cercando soluzioni alternative, come ad esempio:

  • Avere un array le cui (chiavi integer) corrispondono ai valori di un lambda che accetta un RawPacket e restituisce il pacchetto analizzato. Questo mi sembra un modo un po 'strano per usare un array e richiede che mantengo manualmente la definizione di quell'array in modo che il suo mapping index-match corrisponda esattamente al tipo di pacchetto effettivo alla mappatura della struttura dei pacchetti già in uso.
  • Avere una struttura della mappa che fa la stessa cosa dell'array. Simili insidie, ma un po 'più mantenibili.

Ci sono altri modi nuovi di approcciare a questo che non ho ancora pensato?

    
posta hexafraction 29.08.2014 - 22:57
fonte

2 risposte

1

Idealmente, dovresti sovrascrivere il metodo di decodifica per ogni tipo di pacchetto.

Ma il metodo di decodifica non fa parte della sottoclasse specifica. Questo è il motivo per cui lo switch, o una mappa che restituisce la fabbrica specifica del pacchetto, è giustificata. Odora perché è necessario ripetere lo stesso codice più e più volte e il metodo ha dipendenze su tutte e 40 le sottoclassi. Ma se il codice viene generato, non vedo un problema lì.

Se desideri maggiore indipendenza ed evita di ripetere lo stesso codice per ciascuna sottoclasse, puoi mappare il tuo intero alla classe specifica del pacchetto, creare un'istanza dalla classe e chiedere all'istanza di inizializzarsi dal frame.

Avresti qualcosa di simile:

static Class<? extends MyGameNetworkPacket>[] packetClasses;
static {
    packetClasses = new Class[50];
    packetClasses[1] = FooPacket.class;
    packetClasses[2] = BarPacket.class;
    packetClasses[5] = GimmeFivePacket.class;
}

E nel metodo di decodifica:

Class<? extends MyGameNetworkPacket> packetClass = packetClasses[msg.command];
if( packetClass != null ){
    MyGameNetworkPacket pkt;
    try {
        pkt = packetClass.newInstance();
    } catch( InstantiationException | IllegalAccessException e ){
        throw new RuntimeException("Failed to create instance.", e);
    }
    pkt.fillFrom(msg.frame);
    out.add(pkt);
} else {
    throw new RuntimeException("Invalid command ID: " + msg.command + ".");
}

Non sembra molto elegante a causa del try / catch per errori anche se non possono accadere se le classi sono state scritte correttamente. Ma riduce la dipendenza dalle sottoclassi dal riferimento alla loro classe. È anche possibile inizializzare la matrice da un file di configurazione esterno che contiene il nome della classe per ciascun ID di comando.

    
risposta data 30.08.2014 - 17:27
fonte
4

Quello che hai descritto, in un modo o nell'altro, è esattamente come lo farebbero chiunque e non vedo alcun problema con questo metodo.

Nota a margine: non è necessario utilizzare le funzioni lambda nella mappa / matrice. Un metodo di interfaccia semplice sarebbe più pulito.

    
risposta data 29.08.2014 - 23:25
fonte

Leggi altre domande sui tag