I have Server And Client App.
From Server side there are 2 buttons.
1st button "Display Data On Server".
2nd button "Send Data to Client".
In a Server side i'm using FDQuery1, SringGrid1, TetheringManager1 and TetheringAppProfile1.
From Client Side only 1 button "Connect".
In a Client Side I'm using StringGrid1, TetheringManager1 and TetheringAppProfile1
So First Client Connecting to the Server then Server Side sending data to client.
Server "Send Data to Client" button
Code:
procedure TForm1.Button2Click(Sender: TObject);
var rec:integer;
begin
FDQuery1.SQL.Text := 'SELECT * FROM names';
FDQuery1.Open;
rec := FDQuery1.RecordCount;
FDQuery1.First;
if rec>0 then
begin
while not FDQuery1.Eof do
begin
TetheringAppProfile1.Resources.FindByName('Vefa').Value:=FDQuery1.FieldByName('Name').AsString;
FDQuery1.Next;
end;
end;
Client Side Receive
Code:
procedure TForm2.TetheringAppProfile1Resources1ResourceReceived(
const Sender: TObject; const AResource: TRemoteResource);
var i:integer;
begin
for i := 0 to TetheringAppProfile1.Resources.Count do
StringGrid1.Cells[1,i]:=AResource.Value.AsString;
end;
But When I send data from Server to Client I see like this:
You can collect the names into a TStringList and then send that either as a string, using the TStringList.Text property, or as a stream.
To send as a string (and assuming a resource name = NameList), the SendDataClick event handler could look like this:
procedure TServerForm.btnSendDataClick(Sender: TObject);
var
sl: TStringList;
begin
sl := TStringList.Create;
try
sl.Add(StringGrid1.Cells[1, 1]);
sl.Add(StringGrid1.Cells[1, 2]);
sl.Add(StringGrid1.Cells[1, 3]);
TetheringAppProfile1.Resources.FindByName('NameList').Value := sl.Text;
finally
sl.Free;
end;
end;
I simply copied the names from the grid, you could do it directly from the db records.
And the client OnResourceReceived:
procedure TClientForm.TetheringAppProfile1Resources2ResourceReceived(
const Sender: TObject; const AResource: TRemoteResource);
var
sl: TStringList;
i: integer;
begin
sl := TStringList.Create;
try
sl.Text := AResource.Value.AsString;
for i := 0 to sl.Count-1 do
StringGrid1.Cells[1, i+1] := sl[i];
finally
sl.Free;
end;
end;
I suggest you also read about passing streams in Malcolm Groves blog
Related
I've 2 Apps Let's call Server And Client.
I'm using Delphi-xe8. App ->Multi-Device Application
In Both Side using: App tethering[tManager,tAProfile], SQLite Database.
In Server SQLite Database I've 6 images. I would like to View that images In Client Side.
In Client Side I've 6 [TImage].
When I Click Button 'Get Image List' I'm getting 6 images with the same view.
I would like 6 images view differently.->[Get From Server Database]
Client "Get Image List" button Code:
procedure TForm1.GetImgLstClick(Sender: TObject);
begin
tAProfile.SendString(tManager.RemoteProfiles.First,'GetImages','');
end;
Server Received Code:
procedure TForm2.tAProfileResourceReceived(const Sender: TObject;
const AResource: TRemoteResource);
var
MS1:TMemorystream;
begin
if AResource.Hint='GetImages' then
begin
MS1:=TMemorystream.Create;
rQuery.Close;
rQuery.SQL.Clear;
rQuery.SQL.Add('select image from users');
rQuery.Open;
while not rQuery.Eof do
begin
tblobField(rQuery.FieldByName('image')).SaveToStream(MS1);
Image1.Bitmap:=nil;
rQuery.Next;
end;
tAProfile.SendStream(tManager.RemoteProfiles.First,'SendImages',MS1);
end;
end;
Client Received Code:
procedure TForm1.tAProfileResourceReceived(const Sender: TObject;
const AResource: TRemoteResource);
var
MS:TMemoryStream;
begin
if AResource.Hint='SendImages' then
begin
Image1.Bitmap.LoadFromStream(AResource.Value.AsStream);
Image2.Bitmap.LoadFromStream(AResource.Value.AsStream);
Image3.Bitmap.LoadFromStream(AResource.Value.AsStream);
Image4.Bitmap.LoadFromStream(AResource.Value.AsStream);
Image5.Bitmap.LoadFromStream(AResource.Value.AsStream);
Image6.Bitmap.LoadFromStream(AResource.Value.AsStream);
end;
end;
Update: I gather from your most recent comment that you want to send your
images one-by-one.
A problem is that a Delphi dataset's TGraphicField supports a number of formats
which may be of variable size, so if you just write them to the server's outbound
stream, there is no way for the client to know, when reading the stream, where the
data of one image ends and the next one begins. A simple solution to that is to have
the server write the size of the image to the stream before it writes the image's
data to the stream, and get the client's code to read the image size so that it
knows how much of what follows is the image's data.
I'm going back to the answer I posted to your other q (Delphi: How to Get All Images From Server Database by using App tethering?), which uses TClientDataSets,
but adapting it so that it sends only the images (and their sizes) in the stream. The
code is still quite simple and should be no different in principle than using FireDAC datasets and a Sqlite data table:
Server
procedure TApp1Form.SendImageStream;
var
StreamToSend,
ImageStream : TMemoryStream;
StreamedImageSize : Integer;
begin
StreamToSend := TMemoryStream.Create;
ImageStream := TMemoryStream.Create;
try
CDS1.DisableControls;
CDS1.First;
while not CDS1.Eof do begin
ImageStream.Clear;
CDS1Graphic.SaveToStream(ImageStream);
ImageStream.Position := 0;
StreamedImageSize := ImageStream.Size;
StreamToSend.Write(StreamedImageSize, SizeOf(Integer));
StreamToSend.CopyFrom(ImageStream, StreamedImageSize);
CDS1.Next;
end;
StreamToSend.Position := 0;
TetheringAppProfile1.Resources.FindByName('BioLife').Value := StreamToSend;
finally
CDS1.EnableControls;
ImageStream.Free;
end;
end;
Client
// Note: In the client, CDS1 has only two fields, one named ID which is an
// ftAutoInc field, and Graphic, which is a TGraphicField
procedure TApp2Form.TetheringAppProfile1Resources0ResourceReceived(const Sender:
TObject; const AResource: TRemoteResource);
var
ReceivedStream : TStream;
ImageStream : TMemoryStream;
ImageSize : Integer;
begin
AResource.Value.AsStream.Position := 0;
ReceivedStream := AResource.Value.AsStream;
ImageStream := TMemoryStream.Create;
try
if CDS1.Active then
CDS1.EmptyDataSet // discard existing data
else
CDS1.CreateDataSet;
CDS1.DisableControls;
while ReceivedStream.Position < ReceivedStream.Size - 1 do begin
ImageStream.Clear;
ReceivedStream.ReadBuffer(ImageSize, SizeOf(Integer));
ImageStream.CopyFrom(ReceivedStream, ImageSize);
CDS1.Insert;
TGraphicField(CDS1.FieldByName('Graphic')).LoadFromStream(ImageStream);
CDS1.Post;
end;
CDS1.First;
finally
ImageStream.Free;
CDS1.EnableControls;
end;
end;
Original answer follows
I have already shown you a very simple way to move images between server and client app using TClientDataSets in my answer to your q Delphi: How to Get All Images From Server Database by using App tethering?. I assumed you knew enough about Delphi programming to be able to get the data from your Sqlite db into a TCientDataSet but perhaps not.
Below is the code for the server + client of my other answer, adapted to use FireDAC components instead of TClientDataSets. Again, it uses the server dataset's SaveToStream method to save its data to the stream from the server and LoadFromStream on the client side.
Notice that there are only two lines of code in the client app.
FDApp1 code:
type
TApp1Form = class(TForm)
TetheringManager1: TTetheringManager;
TetheringAppProfile1: TTetheringAppProfile;
DBImage1: TDBImage;
btnConnect: TButton;
Label1: TLabel;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
btnSendStream: TButton;
FDConnection1: TFDConnection;
FDQuery1: TFDQuery;
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
FDStanStorageBinLink1: TFDStanStorageBinLink;
procedure btnConnectClick(Sender: TObject);
procedure btnSendStreamClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure TetheringManager1PairedToRemote(const Sender: TObject; const
AManagerInfo: TTetheringManagerInfo);
private
procedure DataSetToStream;
end;
[...]
procedure TApp1Form.btnConnectClick(Sender: TObject);
begin
TetheringManager1.AutoConnect;
end;
procedure TApp1Form.btnSendStreamClick(Sender: TObject);
begin
DataSetToStream;
end;
procedure TApp1Form.FormCreate(Sender: TObject);
begin
Caption := Format('App1 : %s', [TetheringManager1.Identifier]);
FDQuery1.LoadFromFile('D:\D10\Samples\Data\BioLife.FDS');
end;
procedure TApp1Form.TetheringManager1PairedToRemote(const Sender: TObject; const
AManagerInfo: TTetheringManagerInfo);
begin
Label1.Caption := Format('Connected : %s %s',
[AManagerInfo.ManagerIdentifier,
AManagerInfo.ManagerName]);
end;
procedure TApp1Form.DataSetToStream;
var
Stream : TMemoryStream;
begin
Stream := TMemoryStream.Create;
FDQuery1.SaveToStream(Stream);
Stream.Position := 0;
TetheringAppProfile1.Resources.FindByName('BioLife').Value := Stream;
end;
FDApp2 code:
type
TApp2Form = class(TForm)
TetheringManager1: TTetheringManager;
TetheringAppProfile1: TTetheringAppProfile;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
DBImage1: TDBImage;
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
FDMemTable1: TFDMemTable;
FDStanStorageBinLink1: TFDStanStorageBinLink;
procedure TetheringAppProfile1Resources0ResourceReceived(const Sender: TObject;
const AResource: TRemoteResource);
public
end;
[...]
procedure TApp2Form.TetheringAppProfile1Resources0ResourceReceived(const Sender:
TObject; const AResource: TRemoteResource);
begin
AResource.Value.AsStream.Position := 0;
FDMemTable1.LoadFromStream(AResource.Value.AsStream);
end;
Of course, on the client side, if for some reason you want the images (but not the other server data) copied into another dataset, you can do that by a row-by-row copy, similar to the code in your qs.
can anyone guide me please what i am doing wrong here or is there any better method to perform this task?
its a chat application using TIdUDPServer i got clients info from server now i want to send text to the client selected from the TListBox.
i have two lists one is visual TListBox in which username is stored only. and the other defined as
FClient: TList which contains a record sent from the server (both are updated OnUDPRead event).
Mylibarary is defined as follows.
tcommand=(cmd_login,cmd_user,cmd_add,cmd_data,cmd_remove,cmd_logout);
TPacket = record
command_id:tcommand;
username :string;
ip :string;
i_ip :string;
port :word;
msgpacket :string;
end;
ppacket=^tpacket;
tudp_protocol=class
buffer:tidbytes;
packet:ppacket;
//index:word;
procedure Login;
procedure Add;
procedure Msg;
procedure Logout;
procedure ExtractLogin;
procedure ExtractAddRemoveUser;
procedure ExtractMessage;
procedure ExtractLogout;
procedure ExtractUser;
constructor Create ( var ARecord: PPacket );
constructor Extract (var ARecord: PPacket; var ABuffer : TBytes);
end;
Below code is triggered on send click .
procedure TForm2.SendClick(Sender: TObject);
var
player,peer:tudp_protocol;
p,packet:ppacket;
selected,i,index:integer;
peerip:string;
peerport:word;
name:string;
begin
new(p);
new(packet);
selected:=0;
p.command_id:=cmd_data;
p.msgpacket:=edit1.Text;
memo1.Lines.Add(p.msgpacket);
player:=tudp_protocol.Create(p);//encoding data here from string to byte array
if ListBox1.ItemIndex = -1 then
begin
ShowMessage('Username not selected');
Exit;
end
else
begin
selected := ListBox1.ItemIndex;
name := ListBox1.Items[selected];
// comparing the listbox username with fclient list username
for index := 0 to FClient.Count - 1 do
begin
packet := FClient.Items[index];
if (CompareStr(packet.username, name) = 0) then
begin
peerip := packet.ip; //getting external ip of matched name
peerport := packet.port; // external port
client.SendBuffer(peerip, peerport, player.buffer);// encoded data sent
end;
end;
end;
end;
procedure TForm2.clientUDPRead(AThread: TIdUDPListenerThread;
AData: array of Byte; ABinding: TIdSocketHandle);
var
buffer:tidbytes;
protocol:tudp_protocol;
packet:ppacket;
begin
new(packet);
setlength(buffer,length(adata));
move(adata[0],buffer[0],length(adata));
protocol:=tudp_protocol.Extract(packet,buffer);
listbox1.AddItem(packet.username,nil);
fclient.Add(packet);
memo1.Lines.Add(packet.msgpacket);
end;
//------------------------------------------------------------------------------
procedure TForm2.FormCreate(Sender: TObject);
begin
fclient:=tlist.Create;
fclient.Capacity:=10;
end;
I am new to Delphi and trying to convert vb.net apps to learn. The issue I am having is reading from a TCP/IP host. Currently I can connect via telnet to the device, send a command, and the device will send data non-stop until all data is sent. This could be simply two characters followed by CR/LF, or it could be several rows of varing length data. Each row is end is CR/LF. Prior to writing code, we were able to telnet via Hyperterminal to the device. Send a command, and, with the capture text enabled save to a text file.
Below is the code I have so far. I have not coded for saving to text file (one step at a time). The data is pipe delimited. I have no control on the format or operatation of the device aside from sending commands and receiving data. It works most of the time however there are times when not all of the data (65 records for testing) are received. I will greatly appreciate guidence and feel free to comment on my code, good or bad.
function Parse(Char, S: string; Count: Integer): string;
var
I: Integer;
T: string;
begin
if S[Length(S)] <> Char then
S := S + Char;
for I := 1 to Count do
begin
T := Copy(S, 0, Pos(Char, S) - 1);
S := Copy(S, Pos(Char, S) + 1, Length(S));
end;
Result := T;
end;
procedure TForm2.btnEXITClick(Sender: TObject);
begin
if idTcpClient1.connected then
begin
idTcpClient1.IOHandler.InputBuffer.clear;
idTcpClient1.Disconnect;
end;
Close;
end;
procedure TForm2.btnSendDataClick(Sender: TObject);
var
mTXDataString : String;
RXString : String;
begin
IdTCPClient1.Host := IPAddress.Text;
IdTCPClient1.Port := StrToInt(IPPort.Text);
mTXDataString := mTXData.Text + #13#10;
IdTCPClient1.Connect;
If IdTCPClient1.Connected then
begin
IdTCPClient1.IOHandler.Write(mTXDataString);
mTXDataString := mTXData.Lines.Text;
if MTXDataString.Contains('SCHEMA') then
begin
mRXData.Lines.Add(IdTCPClient1.IOHandler.ReadLn);
while not (IdTCPClient1.IOHandler.InputBufferIsEmpty) do
begin
RXString := IdTCPClient1.IOHandler.ReadLn;
If (RXString <> '') and (RXString <> '??') then
begin
//Add received data to RXmemo
mRXData.Lines.Add(RXString);
//Determine number of records to received based on schema data
lblRecords.Caption := Parse(',', RXString, 2);
end;
end; //while not
end // if
else
if mTXDataString.Contains('DATA') then
begin
mRXData.Lines.Add(IdTCPClient1.IOHandler.ReadLn);
while not (IdTCPClient1.IOHandler.InputBufferIsEmpty) do
begin
RXString := IdTCPClient1.IOHandler.ReadLn;
If (RXString <> '') and (RXString <> '??') then
begin
mRXData.Lines.Add(RXString);
end; // if
end; //while not
end; // if Schema or not
end; // if Connected
IdTCPClient1.Disconnect;
end; //Procedure
HyperTerminal and Telnet apps display whatever data they receive, in real-time. TIdTCPClient is not a real-time component. You control when and how it reads. If you are expecting data to arrive asynchronously, especially if you don't know how many rows are going to be received, then you need to perform the reading in a timer or worker thread, eg:
procedure TForm2.TimerElapsed(Sender: TObject);
var
S: String;
begin
if IdTCPClient1.IOHandler = nil then Exit;
if IdTCPClient1.IOHandler.InputBufferIsEmpty then
begin
IdTCPClient1.IOHandler.CheckForDataOnSource(50);
if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
end;
S := IdTCPClient1.IOHandler.ReadLn;
// use S as needed ...
end;
Or:
type
TMyThread = class(TThread)
protected
fClient: TIdTCPClient;
procedure Execute; override;
public
constructor Create(aClient: TIdTCPClient);
end;
constructor TMyThread.Create(aClient: TIdTCPClient);
begin
inherited Create(False);
fClient := aClient;
end;
procedure TMyThread.Execute;
var
S: String;
begin
while not Terminated do
begin
S := fClient.IOHandler.ReadLn;
// use S as needed ...
end;
end;
Or, if the server supports the actual Telnet protocol, have a look at using Indy's TIdTelnet component instead.
I have small problem (I wish it's small) with disconnecting server - I mean - only in the moment when I want to disconnect it from server application (server.active=false).
Here is my simple code:
type
PClient = ^TClient;
type
TClient = record
Name: string;
AContext: TIdContext;
end;
clients: TThreadList;
SERVER: TIdTCPServer;
procedure TX.SERVERConnect(AContext: TIdContext);
var
NewClient: PClient;
s:string;
begin
s := AContext.Connection.socket.ReadLn();
GetMem(NewClient, SizeOf(TClient));
NewClient.name:=s;
NewClient.AContext := AContext;
AContext.data := TObject(NewClient);
try
clients.LockList.Add(NewClient);
finally
clients.UnlockList;
end;
AContext.Connection.socket.writeln('E:');//answer to client - "all right"
End;
procedure TX.SERVERDisconnect(AContext: TIdContext);
var
AClient: PClient;
begin
AClient := PClient(AContext.data);
try
clients.LockList.Remove(AClient);
finally
clients.UnlockList;
end;
FreeMem(AClient);
AContext.data := nil;
end;
It have to works only for sending data to clients therefore I read only one data line in onconnect procedure - it contains login name.
Procedure for sending data in my code looks like (is it good?):
var
procedure TX.send(what: string; where: string);
i, ile: integer;
s: string;
Aclient: PClient;
list: tlist;
begin
list:= SERVER.Contexts.LockList;
try
for i := 0 to list.Count - 1 do
with TIdContext(list[i]) do
begin
AClient := PClient(data);
if where = ActClient^.name then
Connection.IOHandler.writeln(what);
end;
finally
SERVER.Contexts.UnlockList;
end;
end;
It looks it works good - I mean. But when I want to disable server by SERVER.active:=false application freezes? I tried to free clients etc. but it dosen't work in my bad code.
Could Somebody help me and give me advice how to stop server for this code?
Artik
I need help in understanding how to transfer a record through Indy TCP Server/Client. I have 2 programs, in I put client and in another server.
On client on a button I put connect : Client is TIdTCPClient
Client.Connect();
And at server side I am adding a line to memo that client is connected , on ServerConnect event
Protocol.Lines.Add(TimeToStr(Time)+' connected ');
To send data from client I have a record, which I want to send :
Tmyrecord = record
IPStr: string[15];
end;
And I have a send button there :
procedure Tform1.ButtonSendClick(Sender: TObject);
var
MIRec: Tmyrecord;
msRecInfo: TMemoryStream;
begin
MIRec.IPStr := '172.0.0.1';
msRecInfo := TMemoryStream.Create;
msRecInfo.Write(MIRec, SizeOf(MIRec));
msRecInfo.Position := 0;
Client.IOHandler.Write(msRecInfo);
end;
At server side onexecute I have the following code , I have same tmyrecord declared at server side too :
procedure TServerFrmMain.ServerExecute(AContext: TIdContext);
var
MIRec: Tmyrecord;
msRecInfo: TMemoryStream;
begin
if AContext.Connection.Connected then
begin
AContext.Connection.IOHandler.CheckForDataOnSource(10);
if not AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
msRecInfo:= TMemoryStream.Create;
AContext.Connection.IOHandler.ReadStream(msRecInfo);
msRecInfo.Read(MIRec, sizeOf(msRecInfo));
ShowMessage(MIRec.IPStr);
end;
end;
end
I dont know why it is not working, why I cant show IP adress which I wrote from client side.
I want to read a record (msRecInfo) on server side which I am sending from client side. I want to access my record elements, in this case I want to read IPSTR element of my record. When I press send button from a client side, application hangs, server part.
Thanks a lot in advance
You are making a classic newbie mistake - you are expecting the default behaviors of the TIdIOHandler.Write(TStream) and TIdIOHandler.ReadStream() methods to match each other, but they actually do not.
The default parameter values of TIdIOHandler.ReadStream() tell it to expect an Integer or Int64 (depending on the value of the TIdIOHandler.LargeStream property) to preceed the stream data to specify the length of the data.
However, the default parameter values of TIdIOHandler.Write(TStream) do not tell it to send any such Integer/Int64 value. Thus, your use of TIdIOHandler.ReadStream() reads the first few bytes of the record and interprets them as an Integer/Int64 (which is 926036233 given the string value you are sending), and then waits for that many bytes to arrive, which never will so TIdIOHandler.ReadStream() does not exit (unless you set the TIdIOHandler.ReadTimeout property to a non-infinite value).
There are also some other minor bugs/typos in your code that uses the TMemoryStream objects outside of Indy.
Try this instead:
procedure Tform1.ButtonSendClick(Sender: TObject);
var
MIRec: Tmyrecord;
msRecInfo: TMemoryStream;
begin
MIRec.IPStr := '172.0.0.1';
msRecInfo := TMemoryStream.Create;
try
msRecInfo.Write(MIRec, SizeOf(MIRec));
// writes the stream size then writes the stream data
Client.IOHandler.Write(msRecInfo, 0, True);
finally
msRecInfo.Free;
end;
end;
procedure TServerFrmMain.ServerExecute(AContext: TIdContext);
var
MIRec: Tmyrecord;
msRecInfo: TMemoryStream;
begin
msRecInfo := TMemoryStream.Create;
try
// reads the stream size then reads the stream data
AContext.Connection.IOHandler.ReadStream(msRecInfo, -1, False);
msRecInfo.Position := 0;
msRecInfo.Read(MIRec, SizeOf(MIRec));
...
finally
msRecInfo.Free;
end;
end;
Or this:
procedure Tform1.ButtonSendClick(Sender: TObject);
var
MIRec: Tmyrecord;
msRecInfo: TMemoryStream;
begin
MIRec.IPStr := '172.0.0.1';
msRecInfo := TMemoryStream.Create;
try
msRecInfo.Write(MIRec, SizeOf(MIRec));
// does not write the stream size, just the stream data
Client.IOHandler.Write(msRecInfo, 0, False);
finally
msRecInfo.Free;
end;
end;
procedure TServerFrmMain.ServerExecute(AContext: TIdContext);
var
MIRec: Tmyrecord;
msRecInfo: TMemoryStream;
begin
msRecInfo := TMemoryStream.Create;
try
// does not read the stream size, just the stream data
AContext.Connection.IOHandler.ReadStream(msRecInfo, SizeOf(MIRec), False);
msRecInfo.Position := 0;
msRecInfo.Read(MIRec, SizeOf(MIRec));
...
finally
msRecInfo.Free;
end;
end;
Alternatively, you can send the record using TIdBytes instead of TStream:
procedure Tform1.ButtonSendClick(Sender: TObject);
var
MIRec: Tmyrecord;
Buffer: TIdBytes;
begin
MIRec.IPStr := '172.0.0.1';
Buffer := RawToBytes(MIRec, SizeOf(MIRec));
Client.IOHandler.Write(Buffer);
end;
procedure TServerFrmMain.ServerExecute(AContext: TIdContext);
var
MIRec: Tmyrecord;
Buffer: TIdBytes;
begin
AContext.Connection.IOHandler.ReadBytes(Buffer, SizeOf(MIRec));
BytesToRaw(Buffer, MIRec, SizeOf(MIRec));
...
end;