From the client I am sending a string to the server what he should send me back. This time its a stream created by a ClientDataSet. Unfortunately receiving (or sending??) does not work at the moment.
Note: I am using Synapse with blocking sockets. The server is multithreaded.
The Server:
procedure TTCPSocketThrd.Execute;
var s: String;
strm: TMemoryStream;
ADO_QUERY: TADOQuery;
DS_PROV: TDataSetProvider;
DS_CLIENT: TClientDataSet;
begin
CoInitialize(nil);
Sock := TTCPBlockSocket.Create;
try
Sock.Socket := CSock;
Sock.GetSins;
with Sock do
begin
repeat
if terminated then break;
//if within 60s no data is input, close connection.
s := RecvTerminated(60000,'|');
if s = 'getBENUds' then
begin
//ini ADO_QUERY
ADO_QUERY := TADOQuery.Create(Form1);
ADO_QUERY.ConnectionString := 'private :)';
ADO_QUERY.SQL.Clear;
ADO_QUERY.SQL.Add('sql_query');
ADO_QUERY.Open;
//ini DS_PROV
DS_PROV := TDataSetProvider.Create(ADO_QUERY);
DS_PROV.DataSet := ADO_QUERY;
//ini DS_CLIENT
DS_CLIENT := TClientDataSet.Create(ADO_QUERY);
DS_CLIENT.ProviderName := 'DS_PROV';
DS_CLIENT.SetProvider(DS_PROV);
//DSCLIENTDATASET bauen
strm := TMemoryStream.Create;;
DS_CLIENT.Open;
DS_CLIENT.SaveToStream(strm);
SendStream(strm);
end;
The Client:
procedure TForm1.btnConnectClick(Sender: TObject);
begin
CSocket := TTCPBlockSocket.Create;
strmReply := TMemoryStream.Create;
ClientDataSet1 := TClientDataSet.Create(Form1);
try
CSocket.Connect('ip', 'port');
if CSocket.LastError = 0 then
begin
//Sending to the server I want data
CSocket.SendString('getBENUds|');
if CSocket.LastError = 0 then
begin
//Waiting for data
//Receiving the Stream in strmReply, 5000ms timeout
CSocket.RecvStream(strmReply, 5000);
//Loading the data into ClientDataSet
ClientDataSet1.LoadFromStream(strmReply);
ClientDataSet1.Open;
end;
end;
except
on E:Exception do
ShowMessage(E.Message);
end;
CSocket.Free;
end;
Now, whenever I start the server and click the connect button on the client, it SHOULD transfer the DataSet to the client and the TDBGrid SHOULD come to life.
Unfortunately this does not happen. Instead I get the error as stated in the title: Missing data provider or datapackage. But the data provider is set to DataSetProvider1 (in object inspector).
How can I make it work that the client TDBGrid is filled with data?
You are creating a new instance inside the ClientDataSet1 variable, but none of the other components on your form will reference to that.
That's not the cause of the "Missing data provider or datapackage" error message: in order to find that out you should post a reproducible case.
The easiest way to get that reproducible case going would be to:
If it still reproduces, then post that .pas/.dfm somewhere. If it does not reproduce, then start cutting out portions until it reproduces.
Good luck!
--jeroen
Edit: I downloaded your sources and got them working in Delphi 2009.
It contains a few issues, and Synapse library contains an issues as well.
The majority of your issues come down to being not precise. I already had a vague feeling for that, based on your source code formatting, the mix of naming conventions you were using for your stuff, and the lack of freeing allocated objects.
Before I forget: the very first thing I did was making sure that I enabled use debug dcus and disable optimizations in the compiler options. Those are invaluable settings for digging deeper into problems. I didn't add any cleanup to your existing code, as I wanted it to be as close as possible. But I did run a source code formatter so that at least the then blocks were on separate lines.
Lets start with the sending code.
I got rid of your database connection, copied the ClientDataSet1 from the client to the server, and refactored your SServer unit to that both TTCPSocketDaemon and TTCPSocketThrd now have access to FClientDataSet: TClientDataSet
That got me a kind of reproducible case that I asked for before.
Then I started running the client. There it appeared that LogThis(strmReply.Size); was outputting 0 (zero!), so it was not receiving anything.
That made me look into the server again. It seems you were splitting the incoming arguments using Delimited text, but after that you were refering to sl.Names[0] in stead of if sl[0]. That fails in Delphi 2009, and probably in other Delphi versions as well. In addition to that, you were not checking for an empty s, so it would fail with an List index out of bounds after each time out.
Then I added some debugging code: I wanted to see what was actually being sent. This caused me to stream using XML in stead of binary using S_CLIENT.SaveToStream(strm, dfXMLUTF8); and to copy the buffer to a file locally, so I could watch that file. The file was OK: it appeared to be a valid XML representation of a ClientDataSet.
Finally, I noticed that you were using SendBuffer in the server, and RecvStream in the client. Those do not match: you have to choose, so either use buffers or streams at both sides of the connection! I chose for streams.
So the sending code becomes this:
procedure TTCPSocketThrd.Execute;
var
s: string;
strm: TMemoryStream;
ADO_QUERY: TADOQuery;
DS_PROV: TDataSetProvider;
DS_CLIENT: TClientDataSet;
sl: TStringList;
adoconnstr: string;
CDSStream: TFileStream;
begin
CoInitialize(nil);
Sock := TTCPBlockSocket.Create;
adoconnstr := 'POST YOUR CONNECTION STRING HERE, IF TOO LONG adoconnstr := adoconnstr + ''nwestring''';
try
Sock.Socket := CSock;
sl := TStringList.Create;
Sock.GetSins;
with Sock do
begin
repeat
if terminated then
break;
s := RecvTerminated(60000, '|');
if s = '' then
Exit;
//Den Text mit Leerzeichen splitten zur besseren Verarbeitung
sl.Delimiter := ' ';
sl.DelimitedText := s;
LogThis(sl.Names[0]);
if sl[0] = 'getBENUds' then // jpl: not sl.Names[0] !!!!
begin
// //ini ADO_QUERY
// ADO_QUERY := TADOQuery.Create(Form1);
// ADO_QUERY.ConnectionString := adoconnstr;
// ADO_QUERY.SQL.Clear;
// ADO_QUERY.SQL.Add('SELECT * FROM BENU');
// ADO_QUERY.Open;
// LogThis('ADO_QUERY fertig');
// //ini DS_PROV
// DS_PROV := TDataSetProvider.Create(ADO_QUERY);
// DS_PROV.DataSet := ADO_QUERY;
// LogThis('DS_DSPROV fertig');
// //ini DS_CLIENT
// DS_CLIENT := TClientDataSet.Create(ADO_QUERY);
// DS_CLIENT.ProviderName := 'DS_PROV';
// DS_CLIENT.SetProvider(DS_PROV);
DS_CLIENT := FClientDataSet;
LogThis('DS_CLIENT fertig');
//DSCLIENTDATASET bauen
strm := TMemoryStream.Create;
DS_CLIENT.Open;
//DS_CLIENT.Open;
DS_CLIENT.SaveToStream(strm, dfXMLUTF8); // jpl: easier debugging than binary
strm.Seek(0, soBeginning);
SendStream(strm); // jpl: not SendBuffer(strm.Memory, strm.Size); !!!
strm.Seek(0, soBeginning);
CDSStream := TFileStream.Create('Server.cds', fmCreate);
CDSStream.CopyFrom(strm, strm.Size);
CDSStream.Free();
LogThis('gesendet');
end
else if sl[0] = 'validuser' then // jpl: not sl.Names[0] !!!!
begin
//do stuff
end;
if lastError <> 0 then
break;
until false;
Form1.Memo1.Lines.Add('down');
end;
finally
Sock.Free;
end;
end;
So I went to revisit the receiving code. Since you were already logging strmReply.Size, I wondered why you didn't react on the special case where it was 0 (zero), so I added it: LogThis('empty stream received')
Then I started debugging, and found out that strmReply.Size was still 0 (zero) every time. So I started digging into the Synapse code (I took the copy I already had that is included in the Habari components) I have been using lately. There I found out two things:
This is no doubt a bug in Synapse, so feel free to report it to them. The result is that RecvStream is not the counterpart of SendStream, but RecvStreamIndy is.
After all these changes, it still didn't work. The reason is that ClientDataSet1.LoadFromStream(strmReply); is starting to read from the current position in the stream (you can check that out yourself; the core loading logic is in TCustomClientDataSet.ReadDataPacket). So it seems you forgot adding strmReply.Seek(0, soBeginning);, and after I added that, it suddenly worked.
So the receiving code is this:
procedure TForm1.btnConnectClick(Sender: TObject);
var
CDSStream: TFileStream;
begin
CSocket := TTCPBlockSocket.Create;
strmReply := TMemoryStream.Create;
ClientDataSet1 := TClientDataSet.Create(Form1);
try
// CSocket.Connect('10.100.105.174', '12345');
CSocket.Connect('127.0.0.1', '12345');
if CSocket.LastError = 0 then
begin
LogThis('verbunden');
CSocket.SendString('getBENUds|');
LogThis('''getBENUds|'' gesendet');
if CSocket.LastError = 0 then
begin
CSocket.RecvStreamIndy(strmReply, 50000); // jpl: this fails, because SendStream defaults to Indy: CSocket.RecvStream(strmReply, 50000);
LogThis(strmReply.Size);
if strmReply.Size = 0 then
LogThis('empty stream received')
else
begin
strmReply.Seek(0, soBeginning);
CDSStream := TFileStream.Create('Client.cds', fmCreate);
CDSStream.CopyFrom(strmReply, strmReply.Size);
CDSStream.Free();
strmReply.Seek(0, soBeginning); // jpl: always make sure that the stream position is right!
ClientDataSet1.LoadFromStream(strmReply);
ClientDataSet1.Open;
end;
end;
end;
except
on E: Exception do
ShowMessage(E.Message);
end;
CSocket.Free;
end;
In the end it was not difficult to solve your issues. The hardest part was finding out about RecvStreamIndy, the rest was a mere cleanup of your code and being precise in what happens when: that took most of the time. In this case, stream handling requires you to watch your stream positions carefully.
Regards,
--jeroen