Discussion:
Read/Write Stream
(too old to reply)
Eric W. Carman
2008-07-07 20:52:59 UTC
Permalink
I'm trying to communicate between an indy client and server using the
read/write stream methods and I'm missing something....

When I try this:

procedure TMyAppServer.IdTCPServer1Execute(AContext: TIdContext);
var
BLArea: String;
begin
BLArea := AContext.Connection.IOHandler.ReadLn;
AContext.Connection.IOHandler.WriteLn(BLArea);
end;

procedure TForm1.ToolButton1Click(Sender: TObject);
var
Str: String;
begin
idtcpclient1.IOHandler.WriteLn('Do Something');
str := idtcpclient1.IOHandler.ReadLn;
memo1.Lines.Add(Str);
end;

Everything works well. The message is sent/received without issue.

When I replace it with the following, the readstream in the server never
returns. I'm missing something that is probably very obvious, but I haven't
quite worked out what that is. The examples I've found on the internet
don't seem to do more than this, but something needs to tell the server that
it can grab the data and continue.

Any help would be appreciated.

Delphi 2006, Indy 10.1.5 (as supplied in D2006 install), winxp.

procedure TMyAppServer.IdTCPServer1Execute(AContext: TIdContext);
var
MemStr: TMemoryStream;
BLArea: String;
begin
MemStr := TMemoryStream.Create;
try
AContext.Connection.IOHandler.ReadStream(MemStr);
AContext.Connection.IOHandler.Write(MemStr, MemStr.Size);
finally
MemStr.Free;
end;
end;

procedure TForm1.ToolButton1Click(Sender: TObject);
var
MemStr: TMemoryStream;
Str: String;
begin
MemStr := TMemoryStream.Create;
try
Str := 'My Message';
MemStr.Write(Str[1], Length(Str));
MemStr.Position := 0;
idTCPClient1.IOHandler.Write(MemStr, MemStr.Size);
MemStr.Clear;
idTCPClient1.IOHandler.ReadStream(MemStr);
finally
MemStr.Free;
end;
memo1.lines.add('done');
end;

TIA

Eric Carman
Remy Lebeau (TeamB)
2008-07-07 22:22:27 UTC
Permalink
Post by Eric W. Carman
I'm trying to communicate between an indy client and server using
the read/write stream methods and I'm missing something....
You made a classic newbie mistake - you did not read the documentation. You
are not using the methods correctly with each other.
Post by Eric W. Carman
When I replace it with the following, the readstream in the server
never returns.
Because you are telling ReadStream() to read more data than the server
actually sends, so it gets stuck in an endless wait.
Post by Eric W. Carman
AContext.Connection.IOHandler.ReadStream(MemStr);
ReadStream() has three parameters:

procedure ReadStream(AStream: TStream; AByteCount: Int64 = -1;
AReadUntilDisconnect: Boolean = False); virtual;

You are not providing the last two parameters in your code. When AByteCount
is -1 and AReadUntilDisconnect is False, ReadStream() will assume the first
4 bytes (8 bytes if the LargeStream property is True) of the received data
is the stream size. This is clearly stated in the ReadStream()
documentation.
Post by Eric W. Carman
idTCPClient1.IOHandler.Write(MemStr, MemStr.Size);
Write() has three parameters:

procedure Write(AStream: TStream; ASize: Int64 = 0; AWriteByteCount:
Boolean = False); overload; virtual;

You are not providing the last parameter in your code. When AWriteByteCount
is False, Write() will not to send the stream size before sending the stream
data. Your ReadStream() calls are expecting it, though. So the server ends
up reading the first four characters of your string ('My M', aka $4D $79 $20
$4D) as integer 1293973837 and tries to waits for that many bytes to arrive.
But only 6 more bytes will actually arrive ('essage').

To solve the problem, you have a couple of choices:

1) set AWriteByteCount to True when calling Write():

procedure TMyAppServer.IdTCPServer1Execute(AContext: TIdContext);
var
MemStr: TMemoryStream;
begin
MemStr := TMemoryStream.Create;
try
AContext.Connection.IOHandler.ReadStream(MemStr{, -1, False});

// important, or else Write() will raise an exception because no
data is available to send
MemStr.Position := 0;
AContext.Connection.IOHandler.Write(MemStr, MemStr.Size, True);
//
// alternatively: set AByteCount to 0 instead, which will
automatically
// seek the stream back to the beginning and use the full stream
Size
//
// AContext.Connection.IOHandler.Write(MemStr, 0, True);
finally
MemStr.Free;
end;
end;

procedure TForm1.ToolButton1Click(Sender: TObject);
var
MemStr: TMemoryStream;
Str: String;
begin
MemStr := TMemoryStream.Create;
try
Str := 'My Message';
MemStr.Write(Str[1], Length(Str));
MemStr.Position := 0;
IdTCPClient1.IOHandler.Write(MemStr, MemStr.Size, True);
// or:
// IdTCPClient1.IOHandler.Write(MemStr, 0, True);
MemStr.Clear;
IdTCPClient1.IOHandler.ReadStream(MemStr, {-1, False});
finally
MemStr.Free;
end;
Memo1.Lines.Add('done');
end;


2) read/write the stream size separately yourself anyway you want (integers,
strings, members of records, whatever), and then tell Write/ReadStream() to
ignore the implicit stream size handling altogether:

procedure TMyAppServer.IdTCPServer1Execute(AContext: TIdContext);
var
Size: Int64;
MemStr: TMemoryStream;
begin
MemStr := TMemoryStream.Create;
try
// receive the size anyway you wish...
Size := AContext.Connection.IOHandler.ReadInt64;

AContext.Connection.IOHandler.ReadStream(MemStr, Size{, False});
MemStr.Position := 0;
Size := MemStr.Size;

// send the size anyway you wish...
AContext.Connection.IOHandler.Write(Size);
AContext.Connection.IOHandler.Write(MemStr, Size, False);
finally
MemStr.Free;
end;
end;

procedure TForm1.ToolButton1Click(Sender: TObject);
var
Size: Int64;
MemStr: TMemoryStream;
Str: String;
begin
MemStr := TMemoryStream.Create;
try
Str := 'My Message';
MemStr.Write(Str[1], Length(Str));
MemStr.Position := 0;
Size := MemStr.Size;

// send the size anyway you wish...
IdTCPClient1.IOHandler.Write(Size);
IdTCPClient1.IOHandler.Write(MemStr, Size, False);

MemStr.Clear;

// receive the size anyway you wish...
Size := IdTCPClient1.IOHandler.ReadInt64;
IdTCPClient1.IOHandler.ReadStream(MemStr, Size{, False});
finally
MemStr.Free;
end;
Memo1.Lines.Add('done');
end;


Gambit
Eric W. Carman
2008-07-08 14:09:39 UTC
Permalink
Post by Remy Lebeau (TeamB)
Post by Eric W. Carman
I'm trying to communicate between an indy client and server using
the read/write stream methods and I'm missing something....
You made a classic newbie mistake - you did not read the documentation.
You are not using the methods correctly with each other.
Actually, I have read tons of documentation and examples. Unfortunately, it
is apparent that I've not read them effectively. For some reason I got it
into my head that specifying the size during the write would cause the size
to be written as the prefix to the stream - completely missing the
significance of the boolean in the third parameter. I even studied the
readstream code in the indy source, further confirming that the size was
read at the beginning and left wondering why it still wasn't working.

Did I think to go to the write code? Of course not, since I had gotten it
into my head that the write is certainly writing the size since I bothered
to specify it. Folly at its best.

Thank you for the quick response.

Best Regards,
Eric
Remy Lebeau (TeamB)
2008-07-08 20:16:00 UTC
Permalink
Post by Eric W. Carman
Actually, I have read tons of documentation and examples.
Did you read Indy's official documentation, though?

TIdIOHandler.ReadStream Method
http://www.indyproject.org/docsite/html/***@TIdStream@***@Boolean.html

"AByteCount indicates the number of bytes of data to read using the
IOHandler, and to store in the stream instance in AStream.

When AByteCount contains the value cSizeUnknown, the amount of data to
read from the IOHandler is determined either by reading the byte count from
the IOHandler or by reading data until the connection is closed by the peer.

When AReadUntilDisconnect contains False, the byte count is read from
the IOHandler. The byte count will be an Integer data type when LargeStream
contains False, or an Int64 value when LargeStream contains True. This
allows the Indy library to maintain compatibility with older VCL versions
where stream sizes and positions are not returned as Int64 data types. The
byte count (from the argument or read from the IOHandler) is used to
pre-allocate the stream size.

When AReadUntilDisconnect contains True, ReadStream expects the
connection for the IOHandler to be closed when no more data is available for
the method."


TIdIOHandler.Write Method (TIdStream, Int64, Boolean)
http://www.indyproject.org/docsite/html/***@TIdStream@***@Boolean.html

"AWriteByteCount indicates if the byte count for the operation is
written to the IOHandler. When AWriteByteCount contains True, the value in
ASize is written as the initial step in the write operation. ASize will be
written as an Int64 data type when LargeStream contains True. ASize will be
written as an Integer data type when LargeStream contains False. The default
value for AWriteByteCount is False."
Post by Eric W. Carman
For some reason I got it into my head that specifying the size
during the write would cause the size to be written as the prefix
to the stream
Only if the AWriteByteCount parameter is set to True, which you were not
doing.


Gambit

Loading...