2009-03-03 33 views
7

Lo sé, lo estoy haciendo muchas preguntas ... sino como un nuevo desarrollador de Delphi que siguen cayendo sobre todas estas preguntas :)Indy Escribir búfer/comunicación TCP eficiente

acuerdos este uno con el uso de la comunicación TCP Indy 10. Para que la comunicación sea eficiente, codigo una solicitud de operación del cliente como un solo byte (en la mayoría de los escenarios seguidos de otros bytes de datos, por supuesto, pero en este caso solo un byte). El problema es que

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

no envía ese byte inmediatamente (al menos los servidores ejecutan el controlador no se invoca). Si cambio el '1' a '9', por ejemplo, todo funciona bien. Supuse que Indy amortigua los bytes salientes y trató de desactivar el almacenamiento en búfer de escritura con

FConnection.IOHandler.WriteBufferClose; 

pero no sirvió de nada. ¿Cómo puedo enviar un solo byte y asegurarme de que se envía inmediatamente? Y, añado otra pequeña pregunta aquí, ¿cuál es la mejor manera de enviar un entero usando indy? Por desgracia no puedo encontrar la función como WriteInteger en el IOHandler de TIdTCPServer ... y

WriteLn (IntToStr (SomeIntVal)) 

no parece muy eficaz para mí. ¿Hace una diferencia si uso múltiples comandos de escritura en una fila o empaqueto cosas en una matriz de bytes y lo envío una vez?

¡Gracias por cualquier respuesta!

EDIT: Agregué una pista de que estoy usando Indy 10 ya que parece que hay cambios importantes con respecto a los procedimientos de lectura y escritura.

+0

Tenga en cuenta que el envío de 1 byte no es más eficiente que el envío de más bytes. Probablemente deberías leer en TCP/IP, tamaño de paquete, sobrecarga de transferencia y todo. Existe una razón por la cual la mayoría de los antiguos protocolos de Internet usan texto, aunque el tamaño de los datos es mayor que con los datos binarios. – mghie

+0

Este comando es una excepción. La mayoría de los comandos agregan más bytes para los parámetros del comando. Pensé que no podía ser malo empacar las cosas lo más juntas posible. ¿O estoy equivocado aquí? – jpfollenius

+0

Siempre que su marco de datos completo se ajuste a un paquete TCP, no debería hacer mucha diferencia. Los protocolos basados ​​en texto OTOH son una gran ayuda cuando se trata de depurar cosas. – mghie

Respuesta

6

El almacenamiento en búfer de escritura está deshabilitado de forma predeterminada. Puede comprobar el almacenamiento en búfer de escritura para ver si está activo en su código probando la propiedad fConnection.IOHandler.WriteBufferingActive.

En cuanto a la mejor manera de enviar un número entero ... 'depende' de su protocolo y objetivos generales. Específicamente, use FConnection.IOHandler.Write() ya que hay métodos sobrecargados para escribir casi cualquier tipo de datos, incluido un número entero.

Tomado de 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; 

Otra pregunta que tenía era "¿Hay alguna diferencia si utilizo varios comandos de escritura en una fila o empacar cosas juntos en una matriz de bytes y enviar que una vez?" Para la mayoría de los casos, sí, hace la diferencia. Para servidores altamente estresados, vas a tener que involucrarte más en la manera en que se envían los bytes, pero en este nivel debes abstraer tus envíos a una clase de tipo de protocolo separado que construye los datos que se enviarán y los envía en un reventar y tener un protocolo de recepción que recibe un montón de datos y lo procesa como una unidad completa en lugar de dividir las cosas para enviar/recibir un entero, carácter, matriz de bytes, etc.

Como un rápido ejemplo muy aproximado:

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; 

A continuación, enviar esto a través de:

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

Por otro lado se puede recibir los datos a través de Indy y luego

myCommand.Deserialize(ReceivedData); 
+0

+1 gran respuesta. ¡Gracias! – jpfollenius

+0

Si usa el buffer de escritura incorporado en Indy, entonces no necesita serializar los datos manualmente. Y ciertamente NO deberías usar cadenas para la serialización, especialmente cuando trabajas con Indy 10 y sus nuevas capacidades Unicode. Si desea serializar manaully, entonces use TIdBytes en su lugar. –

+0

Unicode en la comunicación para mí, y un montón de gente en los Estados Unidos, es menos que útil. Además, prefiero usar cadenas para la serialización que TidBytes. Nunca terminé usando Indy 10 para nada importante: Indy9 si Indy en lo absoluto. –

0

Parece que tiene que enjuagar el búfer. Prueba esto:

TIdTCPConnection.FlushWriteBuffer; 

Si no desea que un buffer de escritura, utilice esto:

TIdTCPConnection.CancelWriteBuffer; 

De acuerdo con la ayuda, esta primera llama ClearWriteBuffer, para borrar el búfer y luego llama CloseWriteBuffer.

La mejor manera de enviar un entero (usando Indy 10) está utilizando TIdIOHandler.Write (según la Indy 10 Escribir ayuda está sobrecargado para manejar diferentes tipos de datos, incluyendo enteros)

+0

Intenté eliminar el buffer, pero no cambia nada. No debería haber ningún buffer de escritura, porque llamar a WriteBufferClose de acuerdo con la ayuda libera el buffer de escritura (como lo hace WriteBufferCancel) – jpfollenius

+0

... y con respecto a tu edición con WriteInteger: creo que estás usando Indy 9. My IOHandler no contiene tal método. – jpfollenius

+0

Trate de usar los métodos de escritura TIdTCPConnection en lugar del IOHandler directamente, tal vez eso haga la diferencia. Supongo que llamas a FlushWriteBuffer después de escribir los datos y antes de leer la respuesta. –

4

No estoy familiarizado con Indy, pero es posible que desee mirar alrededor de su API para una opción TCP_NODELAY (es posible que desee grep el árbol de fuentes de Indy para algo así - entre mayúsculas y minúsculas para el "retraso" debería hacerlo.)

Editar: Rob Kennedy señaló que la propiedad a la que me refería es TIdIOHandlerSocket.UseNagle - que ¡Kansas!

El problema es inherente a la naturaleza de TCP. TCP garantiza la entrega de datos en el mismo orden en que se emitió, pero no garantiza los límites del mensaje. En otras palabras, el sistema operativo de la fuente, del objetivo y de cualquier enrutador a lo largo del camino puede fusionar paquetes de la conexión o fragmentarlos a voluntad. Debe mirar una transmisión TCP como una secuencia, , no como, como una serie de paquetes individuales.Por lo tanto, deberá implementar un mecanismo mediante el cual delimite los mensajes individuales (por un byte mágico, por ejemplo, que debe escapar si también puede aparecer en los datos de su mensaje), o puede enviar la longitud del siguiente mensaje primero, luego el mensaje real.

Siempre he usado UDP junto con un esquema de ACK/retransmisión ingenuo cuando necesitaba enviar mensajes donde el límite del mensaje era importante, como es su caso. Puede que quieras tomar eso en cuenta. UDP es mucho más adecuado para mensajes de comando.

+0

Estás hablando de Nagle, ¿verdad? Eso está controlado en Indy con la propiedad TIdIOHandlerSocket.UseNagle. –

+0

Sí, Nagle - gracias, Rob. He editado la publicación en consecuencia. –

Cuestiones relacionadas