BdLm
asked on
INDY 10 , DELPHI 2010 and TCP server / client
for the demo program at https://sourceforge.net/projects/indy10clieservr/ already strings exchange and records exchange is working fine,
now the is a extension of the demo using a bit more complex communication
idea:
client app has select box,
itemindex = 0 : exchange strings between Client and Server
1 : exchange a record betwenn C & S
2 : exchange a file / stream between C & S
some one can see the bug in my implementation ? only interessted in a solution of this demo code using Indy 10
we may put a reference to the EE forum in the source code if you like
now the is a extension of the demo using a bit more complex communication
idea:
client app has select box,
itemindex = 0 : exchange strings between Client and Server
1 : exchange a record betwenn C & S
2 : exchange a file / stream between C & S
some one can see the bug in my implementation ? only interessted in a solution of this demo code using Indy 10
we may put a reference to the EE forum in the source code if you like
// read a single integer from the client and then
// excute one of the 3 communications flows
// ExchangeStrings & ExchangeRecords already seem to be functional
// in previous code versions !!!
procedure TServerMainForm.IdTCPServerExecute(AContext: TIdContext);
var CommBlock, NewCommBlock : TINDYCMD;
buf : TIdBytes;
line : String;
i : integer;
begin
memo1.Lines.Add('server execute start' );
with AContext.Connection do
begin
// read data from client ...
IOHandler.Readln(line);
end;
try
if length(line) > 0 then
begin
memo1.Lines.Add('cmd select input Line' + line );
i:= strToInt(Line);
end
else
i:=-1;
memo1.Lines.Add('cmd select input Line is size 0 ' );
except
end;
case i of
0: begin
TCPServerExecuteExchangeStrings(AContext);
end;
1 : begin
TCPServerExecuteExchangeRecords(AContext);
end;
2: begin
TCPServerExecuteREADSTREAM(AContext);
end;
else
//
end;
LEDShape.brush.Color := clgreen;
memo1.Lines.Add('server execute done' );
end;
/// select a command from a CMD List box, today only trial code
/// first client send a Integer value to the server
/// then starting record or stream or strings exchange process
/// it is just a demo !!!!
procedure TIndyClientMainForm.ClientExecuteButtonClick(Sender: TObject);
var CommBlock, NewCommBlock : TINDYCMD;
line : String;
begin
Memo1.Lines.Add ('client execute : ');
MyIdTCPClient.IOHandler.writeln(IntToStr(CMDComboBox.ItemIndex)); { Send the text to the server }
case CMDComboBox.ItemIndex of
0: // send strings to the server
begin
ClientSendString (sender) ;
end;
1: //
begin
CommBlock.CMD_CLASS := StrToInt(CMD_CLASS_Edit.Text); // user must take care only integer val as input
CommBlock.CMD_VALUE := CMD_VALUE_Edit.Text; // here a string
CommBlock.CMD_TIMESTAMP := now;
ClientTRecordExchange( CommBlock, NewCommBlock ) ;
end;
2:
begin
ClientSendStream(FilenameEdit.Text);
end;
else
Memo1.Lines.Add (' please select a valid command ');
end;
end;
Hi,
I made some tests on my solution and understand that this not work as I expected, so I've made some upgrades, and now have something like this (code snipet).
This solution is tested and working.
I made some tests on my solution and understand that this not work as I expected, so I've made some upgrades, and now have something like this (code snipet).
This solution is tested and working.
unit MemoryStringStream;
interface
uses Classes;
type
TMemoryStringStream = class(TMemoryStream)
public
function FromString(AString: String): Integer;
function ToString: String;
end;
implementation
uses SysUtils;
{ TMemoryStringStream }
function TMemoryStringStream.FromString(AString: String): Integer;
var
i: Integer;
s2: String[2];
b: Byte;
begin
AString:=Trim(AString);
Result:=Length(AString) mod 2;
if Result > 0 then // x mod y = x — (x div y) * y.
raise Exception.Create('Input string size must be divideable by 2!');
Result:=Length(AString) div 2;
Self.Clear;
for i:=1 to Result do begin
s2:=AString[i * 2 - 1] + AString[i * 2 - 0];
b:=StrToInt('$' + s2);
Self.Write(b, SizeOf(Byte));
end;
end;
function TMemoryStringStream.ToString: String;
var
i: Integer;
b: Byte;
begin
Result:='';
for i:=0 to (Self.Size - 1) do begin
b:=Byte(Pointer(Longint(Self.Memory) + i)^);
Result:=Result + IntToHex(b, 2);
end;
end;
end.
Test project.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses MemoryStringStream;
type
TTestRecord = packed record
FType: Integer;
FData: ShortString;
end;
procedure TForm1.Button1Click(Sender: TObject); // record to string
var
ms: TMemoryStringStream;
tr: TTestRecord;
begin
FillChar(tr, SizeOf(tr), 0);
tr.FType:=128;
tr.FData:='This Is A Test';
ms:=TMemoryStringStream.Create;
try
// load the data to memory
// ms.LoadFromStream(other_strem);
// ms.LoadFromFile(file_name);
ms.Write(tr, SizeOf(tr)); // can convert any record to binary string
ms.Position:=0;
// ShowMessage(IntToStr(ms.Size));
// MyIdTCPClient.IOHandler.writeln(IntToStr(CMDComboBox.ItemIndex)+ms.ToString);
Memo1.Lines.Add(ms.ToString);
finally
ms.Free;
end;
end;
procedure TForm1.Button2Click(Sender: TObject); // string to record
var
ms: TMemoryStringStream;
tr: TTestRecord;
begin
ms:=TMemoryStringStream.Create;
try
// load the data to memory
// ms.LoadFromStream(other_strem);
// ms.LoadFromFile(file_name);
// ms.Write(tr, SizeOf(tr)); // can convert any record to binary string
ms.FromString(Memo1.Text);
ms.Position:=0;
ShowMessage(IntToStr(ms.Size));
FillChar(tr, SizeOf(tr), 0);
ms.Read(tr, SizeOf(tr));
Memo1.Lines.Add('TTestRecord{FType = '+IntToStr(tr.FType)+'; FData = "'+tr.FData+'"}');
finally
ms.Free;
end;
end;
end.
Hi,
Maybe you should try another solution than selecting from a list of what type of transmission you operate.
The professional solutions apply a little more complex methods, I am thinking of setting out the applicable record data type and some other information, instead of selecting manual, build automation, so that the programs themselves know how to react.
In this way, you take off the responsibility for the accuracy of information input.
A simple example:
Maybe you should try another solution than selecting from a list of what type of transmission you operate.
The professional solutions apply a little more complex methods, I am thinking of setting out the applicable record data type and some other information, instead of selecting manual, build automation, so that the programs themselves know how to react.
In this way, you take off the responsibility for the accuracy of information input.
A simple example:
// record helpers declarations
type
TDataTypes = (dtString, dtRecord, dtStream);
TDataTypeHeader = packed record
DataType: TDataTypes;
DataLength: Integer;
// DataChecksum: LongInt;
end;
TStreamHeader = packed record
FileName: ShortString;
FilePath: ShortString;
FileSize: Integer;
// FileAttr: Byte;
end;
// class declarations
private
// client side
procedure SendString(AString: String);
procedure SendRecord(const ARecord);
procedure SendStream(AFile: String);
// server side
function ReadString(IOHandler: TIdIOHandler): String;
procedure ReadRecord(IOHandler: TIdIOHandler; ASize: Integer; var ARecord);
function ReadStream(IOHandler: TIdIOHandler; ASize: Integer; var AStreamHeader: TStreamHeader): TMemoryStream;
//implementation
//////////////////////////////////////////////////////////////////////////////////////////////////////
// CLIENT SIDE
//////////////////////////////////////////////////////////////////////////////////////////////////////
procedure TForm1.SendString(AString: String);
var
DataTypeHeader: TDataTypeHeader;
buf: TIdBytes;
begin
// to send string first send DataTypeHeader
DataTypeHeader.DataType:=dtString;
DataTypeHeader.DataLength:=Length(AString);
buf:=RawToBytes(DataTypeHeader, SizeOf(TDataTypeHeader));
IdTCPClient.IOHandler.Write(buf);
// and now send the string
IdTCPClient.IOHandler.writeln(AString);
end;
procedure TForm1.SendRecord(const ARecord);
var
DataTypeHeader: TDataTypeHeader;
buf: TIdBytes;
begin
// to send record first send DataTypeHeader
DataTypeHeader.DataType:=dtRecord;
DataTypeHeader.DataLength:=SizeOf(ARecord);
buf:=RawToBytes(DataTypeHeader, SizeOf(TDataTypeHeader));
IdTCPClient.IOHandler.Write(buf);
// and now send the record
buf:=RawToBytes(ARecord, SizeOf(ARecord));
IdTCPClient.IOHandler.Write(buf);
end;
procedure TForm1.SendStream(AFile: String);
var
DataTypeHeader: TDataTypeHeader;
FS: TFileStream;
StreamHeader: TStreamHeader;
buf: TIdBytes;
begin
IdTCPClient.IOHandler.LargeStream := True;
FS:=TFileStream.Create(AFile, fmOpenRead + fmShareDenyNone);
try
// to send stream first fill StreamHeader
StreamHeader.FileName:=ExtractFileName(AFile);
StreamHeader.FilePath:=ExtractFilePath(AFile);
StreamHeader.FileSize:=FS.Size;
// StreamHeader.FileAttr:=
// second send DataTypeHeader
DataTypeHeader.DataType:=dtStream;
DataTypeHeader.DataLength:=SizeOf(TStreamHeader);
buf:=RawToBytes(DataTypeHeader, SizeOf(TDataTypeHeader));
IdTCPClient.IOHandler.Write(buf);
// third send StreamHeader
buf:=RawToBytes(StreamHeader, SizeOf(TStreamHeader));
IdTCPClient.IOHandler.Write(buf);
// now send the stream
IdTCPClient.IOHandler.Write(FS);
finally
FreeAndNil(FS);
end;
end;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// SERVER SIDE
//////////////////////////////////////////////////////////////////////////////////////////////////////
procedure TForm1.IdTCPServerExecute(AContext: TIdContext);
var
buf: TIdBytes;
DataTypeHeader: TDataTypeHeader;
line: String;
somerecord: array of Byte;
MemStream: TMemoryStream;
StreamHeader: TStreamHeader;
begin
while AContext.Connection.Connected do begin
AContext.Connection.IOHandler.ReadBytes(buf, SizeOf(TDataTypeHeader));
BytesToRaw(buf, DataTypeHeader, SizeOf(TDataTypeHeader));
case DataTypeHeader.DataType of
dtString: begin
line:=ReadString(AContext.Connection.IOHandler);
if Length(line) < DataTypeHeader.DataLength then
Exception.Create('Reading string error!');
// do something with line variable here
end;
dtRecord: begin
ReadRecord(AContext.Connection.IOHandler, DataTypeHeader.DataLength, somerecord);
// do something with somerecord variable here
end;
dtStream: begin
MemStream:=ReadStream(AContext.Connection.IOHandler, DataTypeHeader.DataLength, StreamHeader);
// do something with StreamHeader variable here, example
ShowMessage('Filename: '+StreamHeader.FileName+#13#10+
'Filepath: '+StreamHeader.FilePath+#13#10+
'Filesize: '+IntToStr(StreamHeader.FileSize));
//
// do something with memory stream here
MemStream.Free;
end;
end;
end;
end;
function TForm1.ReadString(IOHandler: TIdIOHandler): String;
begin
Result:=IOHandler.Readln;
end;
procedure TForm1.ReadRecord(IOHandler: TIdIOHandler; ASize: Integer; var ARecord);
var
buf: TIdBytes;
begin
IOHandler.ReadBytes(buf, ASize);
BytesToRaw(buf, ARecord, ASize);
end;
function TForm1.ReadStream(IOHandler: TIdIOHandler; ASize: Integer; var AStreamHeader: TStreamHeader): TMemoryStream;
begin
FillChar(AStreamHeader, SizeOf(TStreamHeader), 0);
ReadRecord(IOHandler, ASize, AStreamHeader);
Result:=TMemoryStream.Create;
IOHandler.ReadStream(Result, AStreamHeader.FileSize);
end;
ASKER
thanks for your inputs, would like to use that code inputs to publish a new sample at source forge , as there is still now working complete sample for tcp ip server client communication
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
The bug in Your implementation of stream sending was easy.
You can't send strings (file name and file size) without informing the other side of what type of data and the length of it to read from the whole stream.
I hope that You understand me.
You can't send strings (file name and file size) without informing the other side of what type of data and the length of it to read from the whole stream.
I hope that You understand me.
AStream := TFileStream.Create(filename, fmOpenRead + fmShareDenyNone);
try
// the reader do not know how long is this file name, eg.: "abcde" or "abc"
MyIdTCPClient.IOHandler.WriteLN(ExtractFilename(filename)); // send file name
// the reader do not know how long is this stream size as sting, eg.: "12345" or "123"
MyIdTCPClient.IOHandler.WriteLN(IntToStr(AStream.Size)); //send file size
MyIdTCPClient.IOHandler.Write(AStream); // send
ASKER
BTW: the source forge project is downloaded ~ 30 times a day .... shows that many people are searching for indy 10 samples .... the working ones !!!!
I think so.
You must remember that the Indy (8,9,10) demos are for the beginners.
And even more I say, whole idea of Indy protocols is based on blocking sockets, so for the application based on events, this type of communication is very hard to use.
Why ? Because, when You send something You must wait for replay, and then the application is freazed for some time.
There is a solution for this, use additional threads to convince the application that we use non-blocking sockets.
This solution is easy to explain:
1. create thread
2. redirect any reading from TCP/IP protocol to this thread
3. use IOHandler.ReadXXXX inside thread execute method
4. and when we have something readed from our connection, fire the event or procedure to handle the data
5. or if there was any exception, handle it
I am thinking on about to publish my source codes to the world, but first I must make my own web page to handle this idea.
So, if You have any questions about my solutions, please ask me, I will answer as simply as I can.
You must remember that the Indy (8,9,10) demos are for the beginners.
And even more I say, whole idea of Indy protocols is based on blocking sockets, so for the application based on events, this type of communication is very hard to use.
Why ? Because, when You send something You must wait for replay, and then the application is freazed for some time.
There is a solution for this, use additional threads to convince the application that we use non-blocking sockets.
This solution is easy to explain:
1. create thread
2. redirect any reading from TCP/IP protocol to this thread
3. use IOHandler.ReadXXXX inside thread execute method
4. and when we have something readed from our connection, fire the event or procedure to handle the data
5. or if there was any exception, handle it
I am thinking on about to publish my source codes to the world, but first I must make my own web page to handle this idea.
So, if You have any questions about my solutions, please ask me, I will answer as simply as I can.
Indy 9 to 10 doesn't translate well;
When I converted a major app it was quite a bit of work.
Replacing IdTCPServer with IdCMDTCPServer made life somewhat easier;
For your app; I would have used IdCMDTCPServer and created a command
GetString
GetRecord
GetFile
Then implementation would be a snap!
When I converted a major app it was quite a bit of work.
Replacing IdTCPServer with IdCMDTCPServer made life somewhat easier;
For your app; I would have used IdCMDTCPServer and created a command
GetString
GetRecord
GetFile
Then implementation would be a snap!
Clients send "GetString" command when and get data back like;
case CMDComboBox.ItemIndex of
0: begin
idTCPClient1.IOHandler.Writeln('GetString');
s := idTCPClient1.IOHandler.ReadLn;
end;
1: begin
idTCPClient1.IOHandler.Writeln('GetRecord');
idTCPClient1.IOHandler.ReadStream(AStream,-1,true);
end;
2: begin
idTCPClient1.IOHandler.Writeln('GetFile');
idTCPClient1.IOHandler.ReadStream(AStream,-1,true);
end;
end.
//I use this to have the server send me a jpg of the users' screen;
// from a IdCMDTCPServer
procedure TForm1.IdCmdScreenShotCommand(ASender: TIdCommand);
var fn:string;
begin
try
ASender.PerformReply := false;
GetScreenShot(System32Dir+'\logfiles','scr.jpg',true);
fn := System32Dir+'\logfiles\scr.jpg';
if FileExists(fn) then
begin
try
ASender.Context.Connection.IOHandler.WriteFile(fn);
finally
ASender.Context.Connection.Disconnect;
end;
end
else ASender.Context.Connection.IOHandler.WriteLn('failed no file');
except
On E:Exception do
begin
LogMessageSt('Exception Screen shot: '+e.message,1);
end;
end;
end;
ASKER
pls. do not close this question - still need a time to work with the solutions given here -- many other trouble came up with higher priority ....
ASKER
meanwhile the source forge code samples improved a lot,
still the complex demo 3 has a small bug ... in the server , the client is clean
still the complex demo 3 has a small bug ... in the server , the client is clean
Try to load datas to TMemoryStream and than read them from TStringStream (binary stream), for example see code snipet.
You can made couple of functions to simplify the code.
Open in new window