2009-03-03 8 views
7

Je sais, je pose beaucoup de questions ... mais comme un nouveau développeur de delphi je continue à tomber toutes ces questions :)Indy Ecrire Buffering/communication efficace TCP

Celui-ci traite de la communication TCP en utilisant indy 10. Pour rendre la communication efficace, je code une requête d'opération client sous la forme d'un seul octet (dans la plupart des scénarios suivis par d'autres octets de données bien sûr, mais dans ce cas un seul octet). Le problème est que

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

n'envoie pas cet octet immédiatement (au moins le gestionnaire d'exécution des serveurs n'est pas appelé). Si je change le '1' en '9' par exemple tout fonctionne bien. Je suppose que Indy tamponne les octets sortants et essayé de désactiver tampon d'écriture avec

FConnection.IOHandler.WriteBufferClose; 

mais il n'a pas aidé. Comment puis-je envoyer un seul octet et m'assurer qu'il est envoyé immédiatement? Et - j'ajoute une autre petite question ici - quelle est la meilleure façon d'envoyer un entier en utilisant indy? Malheureusement, je ne peux pas trouver la fonction comme WriteInteger dans le IOHandler de TIdTCPServer ... et

WriteLn (IntToStr (SomeIntVal)) 

me semble pas très efficace. Est-ce que cela fait une différence si j'utilise plusieurs commandes d'écriture dans une rangée ou si je rassemble des choses dans un tableau d'octets et que j'envoie une seule fois?

Merci pour toutes les réponses!

EDIT: J'ai ajouté une indication que j'utilise Indy 10 car il semble y avoir des changements majeurs concernant les procédures de lecture et d'écriture.

+0

Notez que l'envoi de 1 octet n'est pas plus efficace que d'envoyer plus d'octets. Vous devriez probablement lire sur TCP/IP, la taille des paquets, les frais généraux de transfert et tout. Il y a une raison pour laquelle la plupart des anciens protocoles Internet utilisent du texte, même si la taille des données est plus grande qu'avec les données binaires. – mghie

+0

Cette commande est une exception. La plupart des commandes ajoutent plus d'octets pour les paramètres de la commande. Je pensais que ça ne pouvait pas être mauvais d'emballer les choses le plus près possible. Ou ai-je tort ici? – jpfollenius

+0

Tant que votre trame de données complète correspond à un paquet TCP, cela ne devrait pas faire beaucoup de différence. Les protocoles basés sur le texte OTOH sont d'une grande aide quand il s'agit de déboguer des choses. – mghie

Répondre

6

La mise en mémoire tampon d'écriture est désactivée par défaut. Vous pouvez vérifier la mise en mémoire tampon en écriture pour voir si elle est active dans votre code en testant la propriété fConnection.IOHandler.WriteBufferingActive.

En ce qui concerne la meilleure façon d'envoyer un entier ... 'cela dépend' de votre protocole et de vos objectifs généraux. Plus précisément, utilisez FConnection.IOHandler.Write() car il existe des méthodes surchargées pour écrire à peu près n'importe quel type de données, y compris un entier.

Taken 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; 

Une autre question que vous aviez été « Est-il une différence si j'utilise plusieurs commandes d'écriture dans une ligne ou emballer les choses ensemble dans un tableau d'octets et envoyer qu'une fois? » Pour la majorité des cas, oui, cela fait une différence. Pour les serveurs fortement sollicités, vous devrez vous impliquer davantage dans la façon dont les octets sont envoyés, mais à ce niveau, vous devez extraire vos envois dans une classe de type de protocole séparée qui construit les données à envoyer et les envoie dans un éclater et avoir un protocole de réception qui reçoit un tas de données et le traite comme une unité complète au lieu de briser l'envoi/réception d'un entier, caractère, tableau d'octets, etc

Comme un exemple rapide très approximatif:

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; 

envoyer ensuite via:

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

De l'autre côté vous pouvez recevoir les données via Indy puis

myCommand.Deserialize(ReceivedData); 
+0

+1 bonne réponse. Merci! – jpfollenius

+0

Si vous utilisez la mémoire tampon d'écriture intégrée d'Indy, vous n'avez pas besoin de sérialiser les données manuellement. Et vous ne devriez certainement PAS utiliser de chaînes pour la sérialisation, surtout quand vous travaillez avec Indy 10 et ses nouvelles capacités Unicode. Si vous voulez sérialiser manuellement, utilisez plutôt TIdBytes. –

+0

Unicode dans la communication pour moi, et beaucoup de gens aux États-Unis, est moins utile. En outre, je préfère utiliser des chaînes pour la sérialisation que TidBytes. Je n'ai jamais fini par utiliser Indy 10 pour quelque chose d'important - Indy9 si Indy du tout. –

0

Semble que vous devez vider votre tampon. Essayez ceci:

TIdTCPConnection.FlushWriteBuffer; 

Si vous ne voulez pas un tampon d'écriture, utilisez ceci:

TIdTCPConnection.CancelWriteBuffer; 

Selon l'aide, cette première appelle ClearWriteBuffer, pour effacer la mémoire tampon puis appelle CloseWriteBuffer.

La meilleure façon d'envoyer un entier (en utilisant Indy 10) utilise TIdIOHandler.Write (selon Indy 10 aide Ecrire une surcharge pour gérer différents types de données, y compris Entiers)

+0

J'ai essayé de vider le tampon, mais cela ne change rien. Il ne devrait pas y avoir de tampon d'écriture du tout, car l'appel à WriteBufferClose ne dépend pas du tampon d'écriture (comme WriteBufferCancel) – jpfollenius

+0

... et de votre modification avec WriteInteger: Je pense que vous utilisez Indy 9. Mon IOHandler ne contient pas une telle méthode. – jpfollenius

+0

Essayez d'utiliser les méthodes d'écriture TIdTCPConnection au lieu de IOHandler directement, peut-être que cela fait une différence. Je suppose que vous appelez FlushWriteBuffer après avoir écrit les données et avant de lire la réponse. –

4

Je ne suis pas au courant Indy, mais vous voudrez peut-être regarder autour de son API pour une option TCP_NODELAY (vous pouvez grep l'arbre source Indy pour quelque chose comme ça - insensible à la casse pour « retard » devrait le faire.)

Edit: Rob Kennedy fait remarquer que la propriété dont je parlais est TIdIOHandlerSocket.UseNagle - que ks!

Le problème est inhérent à la nature de TCP. TCP garantit la livraison des données dans le même ordre que celui qui a été émis, mais et non garantit les limites des messages. En d'autres termes, le système d'exploitation de la source, de la cible et des routeurs en cours de route sont libres de fusionner les paquets de la connexion ou de les fragmenter à volonté. Vous devez regarder une transmission TCP comme un flux, pas comme une série de paquets individuels.Ainsi vous devrez implémenter un mécanisme par lequel vous délimitez les messages individuels (par un octet magique, par exemple, que vous devez échapper si cela peut également se produire dans vos données de message), ou vous pouvez envoyer la longueur du message suivant d'abord, puis le message réel. J'ai toujours utilisé UDP couplé avec un schéma ACK/retransmission naïf quand j'avais besoin d'envoyer des messages où la frontière du message était importante, comme c'est votre cas. Je pourrais vouloir en tenir compte. UDP est beaucoup mieux adapté pour les messages de commande.

+0

Vous parlez de Nagle, n'est-ce pas? Cela est contrôlé dans Indy avec la propriété TIdIOHandlerSocket.UseNagle. –

+0

Yup, Nagle - merci, Rob. J'ai modifié le poste en conséquence. –