Delphi:Clientdataset:EDatabaseError:使用 Synapse 缺少数据包

发布于 2024-08-02 16:39:44 字数 2580 浏览 8 评论 0原文

我从客户端向服务器发送一个字符串,他应该将其发回给我。这次它是由 ClientDataSet 创建的流。不幸的是,目前无法接收(或发送?)。

注意:我使用带有阻塞功能的 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 充满数据?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

思慕 2024-08-09 16:39:44

您正在 ClientDataSet1 变量内创建一个新实例,但表单上的其他组件都不会引用该实例。

这不是“缺少数据提供程序或数据包”错误消息的原因:为了找出答案,您应该发布一个可重现的案例。

获得可重现案例的最简单方法是:

  1. 在服务器上放置两个 TClientDataSet(ClientDataSet1 和 ClientDataSet2)
  2. 在设计时使用提供程序将数据加载到 ClientDataSet1 中,并将
  3. 数据从 ClientDataSet1 保存到 .CDS 文件
  4. 加载在设计时将 .CDS 文件放入 ClientDataSet2
  5. 将 ClientDataSet2 发送到您的客户端

如果仍然可以重现,则将 .pas/.dfm 发布到某处。
如果它不繁殖,则开始切割部分,直到它繁殖为止。

祝你好运!

--jeroen

编辑:我下载了您的源代码并让它们在 Delphi 2009 中工作。

它包含一些问题,Synapse 库也包含一个问题。

你的大部分问题都归结为不精确。
根据您的源代码格式、您用于内容的命名约定的组合以及缺乏释放分配的对象,我已经对此有一种模糊的感觉。

在我忘记之前:我做的第一件事是确保在编译器选项中启用了使用调试 dcus 并禁用优化。这些对于深入挖掘问题来说是非常宝贵的设置。
我没有向现有代码添加任何清理,因为我希望它尽可能接近。
但我确实运行了源代码格式化程序,以便至少 then 块 位于单独的行上。

让我们从发送代码开始。

我摆脱了你的数据库连接,将ClientDataSet1客户端复制到服务器,并重构了你的SServer TTCPSocketDaemonTTCPSocketThrd 现在都可以访问 FClientDataSet: TClientDataSet

这给了我一种我之前要求的可重现的情况。

然后我开始运行客户端。
看起来 LogThis(strmReply.Size); 正在输出 0(零!),因此它没有收到任何内容。

这让我再次查看服务器。
您似乎使用分隔文本拆分传入参数,但之后您引用的是 sl.Names[0] 而不是 if sl[0]。这在 Delphi 2009 中失败了,在其他 Delphi 版本中也可能失败。
除此之外,您没有检查空的s,因此每次超时后都会失败,并出现列表索引越界

然后我添加了一些调试代码:我想看看实际发送的内容。
这导致我使用 XML 而不是使用 S_CLIENT.SaveToStream(strm, dfXMLUTF8); 进行二进制流式传输,并将缓冲区复制到本地文件,以便我可以观看该文件。
该文件没问题:它似乎是 ClientDataSet 的有效 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('empty stream returned ')

然后我开始调试,发现 strmReply.Size 每次仍然是 0(零)。
所以我开始深入研究 Synapse 代码(我拿了我已经拥有的副本,它包含在 Habari组件)我最近一直在使用。
在那里我发现了两件事:

  1. 在发送流时,它在前 4 个字节中对流的长度进行编码,
  2. 编码是以与解码不同的字节顺序完成的,

这无疑是 Synapse 中的一个错误,所以请随意向他们报告。
结果是 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,剩下的只是清理代码并精确地了解何时发生的情况:这花费了大部分时间。
在这种情况下,流处理需要您仔细观察流位置。

问候,

--杰罗恩

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:

  1. Put two TClientDataSets on your server (ClientDataSet1 and ClientDataSet2)
  2. Load the data into that ClientDataSet1 at design time using a provider and such
  3. Save that data from ClientDataSet1 to a .CDS file
  4. Load the .CDS file into ClientDataSet2 at design time
  5. Send over ClientDataSet2 to your client

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:

  1. upon sending the stream, it was encoding the length of the stream in the first 4 bytes
  2. the encoding was done in a different byte order than the decoding

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文