"Arvid Haugen" <
XXXX@XXXXX.COM>writes
Quote
We can't rewrite this to TidCmdTCPServer and
TidCmdTCPClient!!!
You should not be trying to use TIdCmdTCPServer and TIdCmdTCPClient together
anyway, as they are not designed to work together. Use TIdTCPClient with
TIdCmdTCPServer, and TIdCmdTCPClient with TIdTCPServer.
Quote
On the client side we the code looks like this:
That code is wrong to begin with. Your looping is not accurate and can
potentially miss data. You are misusing the IOHandler's InputBuffer, which
you really shouldn't be relying on directly to begin with. It is meant for
Indy's internal use, and its semantics do not follow the logic you are
trying to use with it.
Quote
On the server side we have a code like this:
That code is not thread-safe. You must use the TIdSync class, or other
manual synchronization method, to safely access the TMemo. For example:
type
TMemoSync = class(TIdSync)
protected
fMemo: TMemo;
fLines: TStrings;
procedure DoSynchronize; override;
public
constructor Create(AMemo: TMemo; ALines: TStrings);
class procedure GetLines(AMemo: TMemo; ALines: TStrings);
end;
constructor TMemoSync.Create(AMemo: TMemo; ALines: TStrings);
begin
inherited Create;
fMemo := AMemo;
fLines := ALines;
end;
procedure TMemoSync.DoSynchronize;
begin
fLines.Assign(fMemo1.Lines);
end;
class procedure TMemoSync.GetLines(AMemo: TMemo; ALines: TStrings);
begin
with Create(AMemo, ALines) do
try
Synchronize;
finally
Free;
end;
end;
procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
var
LLines: TStrings;
begin
//...
LLines := TStringList.Create;
try
TMemoSync.GetLines(Memo1, LLines);
// send LLines as needed...
finally
LLines.Free;
end;
//...
end;
Quote
if trim(lline) = '' then
AContext.Connection.IOHandler.WriteLn('')
else
begin
AContext.Connection.IOHandler.Write(Memo1.lines);
end;
You are not sending any reply code back, but your client code is expecting
one. You need to do so, ie:
procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
var
LLine: String;
LLines: TStrings;
begin
LLine := Trim(AContext.Connection.IOHandler.ReadLn);
if Fetch(LLine) = '100' then
begin
LLines := TStringList.Create;
try
TMemoSync.GetLines(Memo1, LLines);
// send LLines as needed...
finally
LLines.Free;
end;
end
else
AContext.Connection.IOHandler.WriteLn('500 Unknown command');
//...
end;
As for the lines themselves, I strongly suggest that you either:
1) set the AWriteLinesCount parameter of Write(TStrings) to True, and then
use ReadStrings() on the client:
--- server ---
procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
//...
begin
// ...
AContext.Connection.IOHandler.Write('201 Data follows');
AContext.Connection.IOHandler.Write(LLines, True);
// ...
end;
--- client ---
var
LLines: TStrings;
begin
Result := IdTCPClient1.SendCmd('100 Test', [201, 202]);
if Result = 201 then
begin
LLines := TStringList.Create;
try
IdTCPClient1.IOHandler.ReadStrings(LLines);
// loop through LLines as needed...
finally
LLines.Free;
end;
end;
else
//...
end;
2) write an explicit "end of data" line after sending the rest of your
lines, and then your client can look for that. Stop looking at the
InputBuffer at all, just keep calling ReadLn() unconditionally until that
ending line is received:
--- server ---
procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
//...
begin
// ...
AContext.Connection.IOHandler.Write('201 Data follows');
AContext.Connection.IOHandler.Write(LLines);
AContext.Connection.IOHandler.Write('<endofdata>');
// ...
end;
--- client ---
var
LLine: String;
begin
Result := IdTCPClient1.SendCmd('100 Test', [201, 202]);
if Result = 201 then
begin
LLine := IdTCPClient1.IOHandler.ReadLn;
while LLine <>'<endofdata>' do
begin
// process LLine as needed...
end;
end;
else
//...
end;
You can take that a step further by using standard RFC formatting, which
uses '.' as the ending line, and escapes any leading '.' on the lines. This
way, you can use WriteRFCStrings() and Capture() instead:
--- server ---
procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
//...
begin
// ...
AContext.Connection.IOHandler.Write('201 Data follows');
AContext.Connection.IOHandler.WriteRFCStrings(LLines);
// ...
end;
--- client ---
var
LLines: TStrings;
begin
Result := IdTCPClient1.SendCmd('100 Test', [201, 202]);
if Result = 201 then
begin
LLines := TStringList.Create;
try
IdTCPClient1.IOHandler.Capture(LLines);
// loop through LLines as needed...
finally
LLines.Free;
end;
end;
else
//...
end;
3) since your client is using SendCmd() anyway, write the lines in a format
that SendCmd() can handle for you directly. The client's LastCmdResult will
then contain the lines after SendCmd() exits:
--- server ---
procedure TForm5.IdTCPServer1Execute(AContext: TIdContext);
var
LLine: String;
LLines: TStrings;
I: Integer;
begin
LLine := Trim(AContext.Connection.IOHandler.ReadLn);
if Fetch(LLine) = '100' then
begin
LLines := TStringList.Create;
try
TMemoSync.GetLines(Memo1, LLines);
if LLines.Count>0 then
begin
AContext.Connection.IOHandler.WriteLn('201-Data
follows');
if LLines.Count>1 then
begin
for I := 0 to LLines.Count-2 do
AContext.Connection.IOHandler.WriteLn('201-' +
LLines[I]);
end;
AContext.Connection.IOHandler.WriteLn('201 ' +
LLines[LLines.Count-1]);
end else
AContext.Connection.IOHandler.WriteLn('202 No data');
finally
LLines.Free;
end;
end
else
AContext.Connection.IOHandler.WriteLn('500 Unknown command');
//...
end;
--- client ---
begin
Result := IdTCPClient1.SendCmd('100 Test', [201, 202]);
if Result = 201 then
begin
// loop through IdTCPClient1.LastCmdResult.Text needed...
end;
else
//...
end;
#3 is how TIdCmdTCPServer works internally. SendCmd() is designed to be
used with the types of protocols that TIdCmdTCPServer is designed to handle
automatically for you. I know you said you don't want to use
TIdCmdTCPServer, but you really should reconsider that. #3 above can be
translated to the following simplified OnCommand event handler:
procedure TForm5.100Command(ASender: TIdCommand);
begin
TMemoSync.GetLines(Memo1, ASender.Response);
if ASender.Response.Count>0 then
ASender.Reply.SetReply(201, 'Data follows')
else
ASender.Reply.SetReply(202, 'No data');
end;
Quote
For both cases the data is there next time I try to write something
from the client.
That is because your client is not reading everything the first time around.
It is exiting its loop prematurely, leaving the rest of the data unread.
Quote
Should I use some kind of flush command to ensure that everything is
written???
No.
Gambit