views:

2386

answers:

3

I know, I'm asking a lot of questions...but as a new delphi developer I keep falling over all these questions :)

This one deals with TCP communication using indy 10. To make communication efficient, I code a client operation request as a single byte (in most scenarios followed by other data bytes of course, but in this case only one single byte). Problem is that

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

does not send that byte immediately (at least the servers execute handler is not invoked). If I change the '1' to a '9' for example everything works fine. I assumed that Indy buffers the outgoing bytes and tried to disable write buffering with

FConnection.IOHandler.WriteBufferClose;

but it did not help. How can I send a single byte and make sure that it is immediatly sent? And - I add another little question here - what is the best way to send an integer using indy? Unfortunately I can't find function like WriteInteger in the IOHandler of TIdTCPServer...and

WriteLn (IntToStr (SomeIntVal))

seems not very efficient to me. Does it make a difference whether I use multiple write commands in a row or pack things together in a byte array and send that once?

Thanks for any answers!

EDIT: I added a hint that I'm using Indy 10 since there seem to be major changes concerning the read and write procedures.

A: 

Sounds like you have to flush your buffer. Try this:

TIdTCPConnection.FlushWriteBuffer;

If you don't want a write buffer, use this:

TIdTCPConnection.CancelWriteBuffer;

According to the help, this first calls ClearWriteBuffer, to clear the buffer and then calls CloseWriteBuffer.

The best way to send an integer (using Indy 10) is using TIdIOHandler.Write (according to Indy 10 help Write is overloaded to handle different kinds of data, including Integers)

The_Fox
I tried to flush the buffer, but it doesn't change a thing. There should be no write buffer at all, because calling WriteBufferClose does according to the help free the write buffer (as does WriteBufferCancel)
Smasher
...and concerning your edit with WriteInteger: I think you are using Indy 9. My IOHandler does not contain such a method.
Smasher
Try using the TIdTCPConnection write methods instead of the IOHandler directly, maybe that makes a difference. I presume you call FlushWriteBuffer after Writing the data and before reading the response.
The_Fox
That's because I'm not using the IOHandler, but the Connection class (FConnection in your case), and yes, I'm looking at the Indy 9 help.
The_Fox
Your presumption is correct ;) In Indy 10 there are no write methods in the TCP connection besides WriteHeader and WriteRFCStrings.
Smasher
+1  A: 

I'm not familiar with Indy, but you might want to look around its API for a TCP_NODELAY option (you might want to grep the Indy source tree for something like that - case insensitive for "delay" should do it.)

Edit: Rob Kennedy pointed out that the property I was referring to is TIdIOHandlerSocket.UseNagle - thanks!

The problem is inherent in the nature of TCP. TCP does guarantee data delivery in the same order as it was emitted but does not guarantee message boundaries. In other words, the operating system of the source, of the target, and any routers along the way are free to coalesce packets from the connection or to fragment them at will. You must look at a TCP transmission as a stream, not as a series of individual packets. Thus you will have to implement a mechanism by which you either delimit the individual messages (by a magic byte, for example, which you must escape if it can also occur in your message data), or you could send the length of the following message first, then the actual message.

I've always used UDP coupled with a naive ACK/retransmission scheme when I needed to send messages where the message boundary was important, such as is your case. Might want to take that into account. UDP is much better suited for command messages.

Mihai Limbășan
You're talking about Nagle, right? That's controlled in Indy with the TIdIOHandlerSocket.UseNagle property.
Rob Kennedy
Yup, Nagle - thank you, Rob. I've edited the post accordingly.
Mihai Limbășan
+2  A: 

Write buffering is disabled by default. You can check write buffering to see if it's active in your code by testing the fConnection.IOHandler.WriteBufferingActive property.

As far as the best way to send an integer... 'it depends' on your protocol and overall goals. Specifically, use FConnection.IOHandler.Write() as there are overloaded methods to write just about any type of data, including an integer.

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

Another question you had was "Does it make a difference whether I use multiple write commands in a row or pack things together in a byte array and send that once?" For the majority of cases, yes it makes a difference. For highly stressed servers you are going to have to get more involved in how bytes are sent back and forth, but at this level you should abstract out your sends into a separate protocol type class that builds the data to be sent and sends it in a burst and have a receiving protocol that receives a bunch of data and processes it as a complete unit instead of breaking things down to sending/receiving an integer, character, byte array, etc..

As a very rough quick example:

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;

Then send this via:

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

On the other side you can receive the data via Indy and then

  myCommand.Deserialize(ReceivedData);
Darian Miller
+1 great answer. thanks!
Smasher
If you use Indy's built-in write buffering, then you do not need to serialize the data manually. And you should certainly NOT use strings for serialization, anyway, especially when working with Indy 10 and its new Unicode capabilities. If you want to serialize manaully, then use TIdBytes instead.
Remy Lebeau - TeamB
Unicode in communication for me, and quite a lot of people in the U.S., is less than useful. Besides, I'd rather use strings for serialization than TidBytes. I've never ended up using Indy 10 for anything important - Indy9 if Indy at all.
Darian Miller