2009-03-03 17 views
7

Lo so, lo sto chiedendo un sacco di domande ... ma come un nuovo sviluppatore Delphi continuo a cadere tutte queste domande :)Indy Write Buffer/comunicazione TCP efficiente

Questo si occupa di comunicazione TCP utilizzando indy 10. Per rendere efficiente la comunicazione, codifico una richiesta di operazione client come un singolo byte (nella maggior parte degli scenari seguito da altri byte di dati, ovviamente, ma in questo caso solo un singolo byte). Il problema è che

var Bytes : TBytes; 
... 
SetLength (Bytes, 1); 
Bytes [0] := OpCode; 
FConnection.IOHandler.Write (Bytes, 1); 
ErrorCode := Connection.IOHandler.ReadByte; 

non invia quel byte immediatamente (almeno il gestore di esecuzione server non viene richiamato). Se cambio "1" in "9", ad esempio, tutto funziona correttamente. Ho supposto che Indy buffer i byte in uscita e provato a disattivare il buffer di scrittura con

FConnection.IOHandler.WriteBufferClose; 

ma non ha aiutato. Come posso inviare un singolo byte e assicurarmi che sia inviato immediatamente? E - aggiungo un'altra piccola domanda qui - qual è il modo migliore per inviare un intero usando indy? Purtroppo non riesco a trovare funzione come WriteInteger nel IOHandler di TIdTCPServer ... e

WriteLn (IntToStr (SomeIntVal)) 

non sembra molto efficiente per me. Fa la differenza se uso più comandi di scrittura in una riga o raggruppo le cose in un array di byte e me lo invii una volta?

Grazie per eventuali risposte!

MODIFICA: Ho aggiunto un suggerimento che sto usando Indy 10 poiché sembrano esserci importanti cambiamenti riguardanti le procedure di lettura e scrittura.

+0

Nota che l'invio di 1 byte non è più efficiente di inviare più byte. Probabilmente dovresti leggere su TCP/IP, dimensione del pacchetto, sovraccarico del trasferimento e tutto. C'è un motivo per cui la maggior parte dei vecchi protocolli Internet utilizza il testo, anche se la dimensione dei dati è maggiore rispetto ai dati binari. – mghie

+0

Questo comando è un'eccezione. La maggior parte dei comandi aggiunge più byte per i parametri di comando. Pensavo che non fosse male mettere le cose il più vicino possibile. O sbaglio qui? – jpfollenius

+0

Fintanto che il tuo data frame completo si adatta a un pacchetto TCP, non dovrebbe fare molta differenza. I protocolli basati su testo OTOH sono di grande aiuto quando si tratta di eseguire il debug di materiale. – mghie

risposta

6

Il buffering di scrittura è disabilitato per impostazione predefinita. È possibile controllare il buffer di scrittura per verificare se è attivo nel codice testando la proprietà fConnection.IOHandler.WriteBufferingActive.

Per quanto riguarda il modo migliore per inviare un intero ... "dipende" dal protocollo e dagli obiettivi generali. In particolare, utilizzare FConnection.IOHandler.Write() poiché esistono metodi sovraccarichi per scrivere praticamente su qualsiasi tipo di dati, incluso un numero intero.

Tratto da IdIOHandler:

// Optimal Extra Methods 
// 
// These methods are based on the core methods. While they can be 
// overridden, they are so simple that it is rare a more optimal method can 
// be implemented. Because of this they are not overrideable. 
// 
// 
// Write Methods 
// 
// Only the ones that have a hope of being better optimized in descendants 
// have been marked virtual 
procedure Write(const AOut: string; const AEncoding: TIdEncoding = enDefault); overload; virtual; 
procedure WriteLn(const AEncoding: TIdEncoding = enDefault); overload; 
procedure WriteLn(const AOut: string; const AEncoding: TIdEncoding = enDefault); overload; virtual; 
procedure WriteLnRFC(const AOut: string = ''; const AEncoding: TIdEncoding = enDefault); virtual; 
procedure Write(AValue: TStrings; AWriteLinesCount: Boolean = False; const AEncoding: TIdEncoding = enDefault); overload; virtual; 
procedure Write(AValue: Byte); overload; 
procedure Write(AValue: Char; const AEncoding: TIdEncoding = enDefault); overload; 
procedure Write(AValue: LongWord; AConvert: Boolean = True); overload; 
procedure Write(AValue: LongInt; AConvert: Boolean = True); overload; 
procedure Write(AValue: SmallInt; AConvert: Boolean = True); overload; 
procedure Write(AValue: Int64; AConvert: Boolean = True); overload; 
procedure Write(AStream: TStream; ASize: Int64 = 0; AWriteByteCount: Boolean = False); overload; virtual; 

Un'altra domanda che avevo era "Fa una differenza se io uso più comandi di scrittura in una riga o imballare le cose insieme in un array di byte e inviare che una volta?" Per la maggior parte dei casi, sì, fa la differenza. Per server altamente stressati, dovrai essere più coinvolto nel modo in cui i byte vengono inviati avanti e indietro, ma a questo livello devi astrarre le tue mandate in una classe di tipi di protocollo separata che costruisce i dati da inviare e li invia in un scoppiano e hanno un protocollo ricevente che riceve un gruppo di dati e lo elabora come unità completa invece di rompere le cose fino all'invio/ricezione di un intero, carattere, matrice di byte, ecc.

Come un esempio rapido molto approssimativo:

TmyCommand = class(TmyProtocol) 
private 
    fCommand:Integer; 
    fParameter:String; 
    fDestinationID:String; 
    fSourceID:String; 
    fWhatever:Integer; 
public 
    property Command:Integer read fCommand write fCommand; 
    ... 

    function Serialize; 
    procedure Deserialize(Packet:String); 
end; 

function TmyCommand.Serialize:String; 
begin 
    //you'll need to delimit these to break them apart on the other side 
    result := AddItem(Command) + 
      AddItem(Parameter) + 
      AddItem(DestinationID) + 
      AddItem(SourceID) + 
      AddItem(Whatever); 
end; 
procedure TMyCommand.Deserialize(Packet:String); 
begin 
    Command := StrToInt(StripOutItem(Packet)); 
    Parameter := StripOutItem(Packet); 
    DesintationID := StripOutItem(Packet); 
    SourceID := StripOutItem(Packet); 
    Whatever := StrToInt(StripOutItem(Packet)); 
end; 

Poi invia questa via:

FConnection.IOHandler.Write(myCommand.Serialize()); 

D'altra parte è possibile ricevere i dati tramite Indy e quindi

myCommand.Deserialize(ReceivedData); 
+0

+1 ottima risposta. Grazie! – jpfollenius

+0

Se si utilizza il buffer di scrittura incorporato di Indy, non è necessario serializzare manualmente i dati. E NON dovresti assolutamente usare stringhe per la serializzazione, comunque, specialmente quando lavori con Indy 10 e le sue nuove funzionalità Unicode. Se vuoi serializzare manaully, usa invece TIdBytes. –

+0

Unicode in comunicazione per me e un bel po 'di persone negli Stati Uniti, è meno che utile. Inoltre, preferisco usare le stringhe per la serializzazione di TidBytes. Non ho mai finito per usare Indy 10 per qualcosa di importante - Indy9 se Indy a tutti. –

0

Sembra che tu debba svuotare il buffer. Prova questo:

TIdTCPConnection.FlushWriteBuffer; 

Se non si desidera un buffer di scrittura, utilizzare questo:

TIdTCPConnection.CancelWriteBuffer; 

Secondo l'aiuto, questa prima chiama ClearWriteBuffer, per cancellare il buffer e quindi chiama CloseWriteBuffer.

Il modo migliore per inviare un intero (utilizzando Indy 10) sta usando TIdIOHandler.Write (secondo Indy 10 aiutare a scrivere è sovraccarico per gestire diversi tipi di dati, tra cui interi)

+0

Ho provato a svuotare il buffer, ma non cambia nulla. Non dovrebbe esserci alcun buffer di scrittura, perché chiamare WriteBufferClose fa in modo che l'aiuto liberasse il buffer di scrittura (come WriteBufferCancel) – jpfollenius

+0

... e riguardo la modifica con WriteInteger: Penso che tu stia usando Indy 9. Il mio IOHandler non contiene un tale metodo – jpfollenius

+0

Provate a usare direttamente i metodi di scrittura TIdTCPConnection invece di IOHandler, forse questo fa la differenza. Presumo che chiami FlushWriteBuffer dopo aver scritto i dati e prima di leggere la risposta. –

4

io non sono a conoscenza Indy, ma si potrebbe desiderare di guardarsi intorno sue API per un'opzione TCP_NODELAY (si potrebbe desiderare di grep l'albero dei sorgenti Indy per una cosa del genere - case insensitive per "ritardo" dovrebbe farlo.)

Edit: Rob Kennedy ha sottolineato che la proprietà a cui mi riferivo è TIdIOHandlerSocket.UseNagle - rispetto ks!

Il problema è inerente alla natura del TCP. TCP garantisce la consegna dei dati nello stesso ordine in cui è stata emessa, ma lo è non garantire i limiti del messaggio. In altre parole, il sistema operativo della sorgente, dell'obiettivo e di tutti i router lungo la strada sono liberi di riunire i pacchetti dalla connessione o frammentarli a piacimento. È necessario guardare una trasmissione TCP come un flusso, non come una serie di singoli pacchetti.Quindi dovrai implementare un meccanismo tramite il quale o delimitare i singoli messaggi (ad esempio, da un byte magico, che è necessario sfuggire se può verificarsi anche nei dati del messaggio), oppure è possibile inviare la lunghezza del seguente messaggio prima, poi il messaggio reale.

Ho sempre usato UDP accoppiato con un ingenuo schema ACK/ritrasmissione quando avevo bisogno di inviare messaggi in cui il confine del messaggio era importante, come nel caso specifico. Potrebbe voler tener conto di ciò. UDP è molto più adatto per i messaggi di comando.

+0

Stai parlando di Nagle, giusto? Questo è controllato in Indy con la proprietà TIdIOHandlerSocket.UseNagle. –

+0

Yup, Nagle: grazie, Rob. Ho modificato il post di conseguenza. –