Delphi:客户端Dataset:EdatabaseError:使用Synapse缺少数据包装
-
16-09-2019 - |
题
从客户端,我将字符串发送到服务器,他应该把我发送回去。这次是由客户端创建的流。不幸的是,目前接收(或发送??)不起作用。
注意:我正在使用Synapse与阻塞插座。服务器是多线程。
服务器:
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;
客户端:
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;
现在,每当我启动服务器并单击客户端上的“连接”按钮时,它应该将数据集传输到客户端,并且TDBGRID应该栩栩如生。
不幸的是,这没有发生。取而代之的是,我得到标题中所述的错误:缺少数据提供商或数据包。但是数据提供商设置为DataSetProvider1(在对象检查器中)。
如何使客户端TDBGRID充满数据的工作?
解决方案
您正在clientdataset1变量内创建一个新实例,但是表单上的其他组件都不会参考。
这不是“丢失数据提供商或DataPackage”错误消息的原因:为了发现您应该发布可重复的情况。
获得可再现案例的最简单方法是:
- 将两个TCLIENTDATASET放在服务器上(ClientDataSet1和ClientDataSet2)
- 使用提供商等在设计时间将数据加载到该客户端DataSet1中
- 将该数据从客户clientdataset1保存到.cds文件
- 在设计时间将.cds文件加载到clientdataset2中
- 将客户端DataSet2发送给您的客户端
如果仍然复制,请在某个地方发布.pas/.dfm。如果不复制,请开始切割部分,直到重现为止。
祝你好运!
- 杰罗恩
编辑:我下载了您的资源,并让他们在Delphi 2009中工作。
它包含一些问题,突触库也包含一个问题。
您的大多数问题都不精确。根据您的源代码格式,您为您使用的命名约定的组合以及缺乏释放分配的对象,我已经对此感到模糊的感觉。
在我忘记之前:我做的第一件事就是确保我启用了 使用DEBUG DCUS 并禁用 优化 在编译器选项中。这些是无价的设置,可深入研究问题。我没有在您的现有代码中添加任何清理,因为我希望它尽可能接近。但是我确实运行了一个源代码格式器,所以至少 然后阻止 在单独的线上。
让我们从发送代码开始。
我摆脱了您的数据库连接,复制了 ClientDataSet1 来自 客户 到 服务器, 并重构 SSERVER 这两个单位 ttcpsocketdaemon 和 ttcpsocketthrd 现在可以访问 fclientDataset:tclientDataset
这给了我以前要求的一种可再现的案例。
然后我开始运行客户端。似乎在那里 logthis(strmreply.size); 输出0(零!),因此没有收到任何东西。
这使我再次查看服务器。看来您是在使用划界文本分配传入的论点,但是在此之后 sl.names [0 代替 如果SL [0. 。在2009年的Delphi中,这也可能失败,也可能在其他Delphi版本中。除此之外,您没有检查空的 s, ,所以它会失败 列表索引越界 每次出去之后。
然后我添加了一些调试代码:我想看看实际发送的内容。这导致我使用XML代替二进制流媒体 s_client.savetostream(strm,dfxmlutf8); 并将缓冲区复制到本地文件,因此我可以观看该文件。该文件还可以:它似乎是客户端DataSet的有效XML表示。
最后,我注意到您正在使用 sendbuffer 在服务器中,以及 recvstream 在客户中。这些不匹配:您必须选择,因此要么在连接的两侧使用缓冲区或流!我选择了溪流。
因此,发送代码成为以下:
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;
因此,我去重新审视接收代码。既然您已经登录了 strmreply.size, ,我想知道为什么您没有在0(零)的特殊情况下做出反应,所以我添加了: logthis(“接收到空的流”)
然后我开始调试,发现 strmreply.size 每次仍然是0(零)。因此,我开始挖掘突触代码(我采用了已经包含的副本 哈巴里 组件)我最近一直在使用。在那里我发现了两件事:
- 发送流后,它正在编码前4个字节中的流的长度
- 编码以与解码不同的字节顺序完成
毫无疑问,这是突触的错误,因此请随时向他们报告。结果是 recvstream 不是 sendstream, , 但 recvstreamindy 是。
在所有这些更改之后,它仍然无效。原因是 clientdataset1.loadfromstream(strmreply); 开始从流中的当前位置读取(您可以自己检查一下;核心加载逻辑在 tcustomclientdataset.readdatapacket)。所以看来您忘了添加 strmreply.seek(0,sobeginning);, ,在我添加之后,它突然起作用。
因此,接收代码是:
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;
最后,解决您的问题并不难。最难的部分是发现 recvstreamindy, ,其余的仅仅是对您的代码的清理,并且在大多数情况下发生的情况下会进行精确。在这种情况下,流处理需要您仔细观察流的位置。
问候,
- 杰罗恩