Delphi: Clientdataset: EDatabaseError:使用Synapse丢失数据包。

时间:2021-03-08 17:35:45

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.

我正在从客户端向服务器发送一个字符串,他应该返回给我什么。这一次它是一个由ClientDataSet创建的流。不幸的是,接收(或发送)现在不工作。

Note: I am using Synapse with blocking sockets. The server is multithreaded.

注意:我正在使用带有阻塞套接字的Synapse。服务器是多线程的。

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.

现在,每当我启动服务器并单击客户机上的connect按钮时,它应该将数据集传输到客户机,而TDBGrid应该会出现。

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).

不幸的是,这并没有发生。相反,我得到标题中所述的错误:缺少数据提供程序或数据包装。但是数据提供者被设置为DataSetProvider1(对象检查器)。

How can I make it work that the client TDBGrid is filled with data?

如何让客户端TDBGrid充满数据?

1 个解决方案

#1


4  

You are creating a new instance inside the ClientDataSet1 variable, but none of the other components on your form will reference to that.

您正在ClientDataSet1变量中创建一个新实例,但是窗体上的任何其他组件都不会引用它。

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. 在服务器上放置两个tclientdataset (ClientDataSet1和ClientDataSet2)
  3. Load the data into that ClientDataSet1 at design time using a provider and such
  4. 在设计时使用提供程序将数据加载到该ClientDataSet1中。
  5. Save that data from ClientDataSet1 to a .CDS file
  6. 将数据从ClientDataSet1保存到.CDS文件中
  7. Load the .CDS file into ClientDataSet2 at design time
  8. 在设计时将.CDS文件加载到ClientDataSet2中
  9. Send over ClientDataSet2 to your client
  10. 将ClientDataSet2发送到您的客户端

If it still reproduces, then post that .pas/.dfm somewhere. If it does not reproduce, then start cutting out portions until it reproduces.

如果它仍然复制,那么发布那个。pa /。dfm某处。如果它没有繁殖,那么就开始减少一部分,直到它繁殖。

Good luck!

好运!

--jeroen

——基珀斯

Edit: I downloaded your sources and got them working in Delphi 2009.

编辑:我下载了你的资料,让他们在Delphi 2009工作。

It contains a few issues, and Synapse library contains an issues as well.

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

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.

在我忘记之前:我所做的第一件事就是确保我启用了调试dcus并禁用了编译器选项中的优化。这些都是深入研究问题的宝贵条件。我没有向您的现有代码添加任何清理,因为我希望它尽可能接近。但是我确实运行了一个源代码格式化程序,所以至少当时的块是分开的。

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

我删除了您的数据库连接,从客户端复制了ClientDataSet1到服务器,并重构了您的SServer单元,从而使TTCPSocketDaemon和TTCPSocketThrd都可以访问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.

然后我开始运行客户端。看来LogThis(strm答复。size);输出0(0 !)所以没有接收到任何东西。

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.

这让我再次查看服务器。看起来您使用带分隔符的文本来分割传入的参数,但是在此之后,您将引用sl. names[0],而不是if sl[0]。这在2009年的Delphi中失败了,可能在其他的Delphi版本中也失败了。除此之外,您没有检查一个空的s,因此它会在每次超时后在列表索引超出界限时失败。

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.

然后我添加了一些调试代码:我想看看实际发送了什么。这导致我使用XML而不是使用S_CLIENT的二进制进行流。SaveToStream(strm dfXMLUTF8);并将缓冲区复制到本地文件中,这样我就可以监视该文件。文件是OK的:它看起来是一个ClientDataSet的有效XML表示。

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.

最后,我注意到您在服务器中使用SendBuffer,在客户机中使用RecvStream。它们不匹配:您必须选择,所以在连接的两边使用缓冲区或流!我选择的流。

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')

所以我去重新访问接收代码。因为您已经在记录strmReply了。大小,我想知道为什么您没有对它为0(0)的特殊情况做出反应,所以我添加了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:

然后我开始调试,并发现了那个strmReply。每次都是0(0)。因此,我开始深入研究Synapse代码(我使用了最近使用的Habari组件中已经包含的副本)。在那里我发现了两件事:

  1. upon sending the stream, it was encoding the length of the stream in the first 4 bytes
  2. 在发送流时,它将流的长度编码为前四个字节
  3. the encoding was done in a different byte order than the decoding
  4. 编码以不同于解码的字节顺序进行

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.

这无疑是突触上的一个bug,所以请随时向他们报告。结果是,RecvStream不是SendStream的对应物,而是RecvStreamIndy。

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.

在所有这些变化之后,它仍然不起作用。原因是ClientDataSet1.LoadFromStream(strmReply);开始从流中的当前位置读取(您可以自己检查;核心加载逻辑在TCustomClientDataSet.ReadDataPacket中)。看来你忘了添加strmReply了。寻找(0,sostart);在我添加之后,它突然生效。

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.

最后,解决你的问题并不难。最难的部分是找到关于RecvStreamIndy的信息,剩下的部分仅仅是对代码的清理,并对发生的事情进行精确处理:这花费了大部分时间。在这种情况下,流处理要求您仔细监视流的位置。

Regards,

问候,

--jeroen

——基珀斯

#1


4  

You are creating a new instance inside the ClientDataSet1 variable, but none of the other components on your form will reference to that.

您正在ClientDataSet1变量中创建一个新实例,但是窗体上的任何其他组件都不会引用它。

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. 在服务器上放置两个tclientdataset (ClientDataSet1和ClientDataSet2)
  3. Load the data into that ClientDataSet1 at design time using a provider and such
  4. 在设计时使用提供程序将数据加载到该ClientDataSet1中。
  5. Save that data from ClientDataSet1 to a .CDS file
  6. 将数据从ClientDataSet1保存到.CDS文件中
  7. Load the .CDS file into ClientDataSet2 at design time
  8. 在设计时将.CDS文件加载到ClientDataSet2中
  9. Send over ClientDataSet2 to your client
  10. 将ClientDataSet2发送到您的客户端

If it still reproduces, then post that .pas/.dfm somewhere. If it does not reproduce, then start cutting out portions until it reproduces.

如果它仍然复制,那么发布那个。pa /。dfm某处。如果它没有繁殖,那么就开始减少一部分,直到它繁殖。

Good luck!

好运!

--jeroen

——基珀斯

Edit: I downloaded your sources and got them working in Delphi 2009.

编辑:我下载了你的资料,让他们在Delphi 2009工作。

It contains a few issues, and Synapse library contains an issues as well.

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

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.

在我忘记之前:我所做的第一件事就是确保我启用了调试dcus并禁用了编译器选项中的优化。这些都是深入研究问题的宝贵条件。我没有向您的现有代码添加任何清理,因为我希望它尽可能接近。但是我确实运行了一个源代码格式化程序,所以至少当时的块是分开的。

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

我删除了您的数据库连接,从客户端复制了ClientDataSet1到服务器,并重构了您的SServer单元,从而使TTCPSocketDaemon和TTCPSocketThrd都可以访问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.

然后我开始运行客户端。看来LogThis(strm答复。size);输出0(0 !)所以没有接收到任何东西。

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.

这让我再次查看服务器。看起来您使用带分隔符的文本来分割传入的参数,但是在此之后,您将引用sl. names[0],而不是if sl[0]。这在2009年的Delphi中失败了,可能在其他的Delphi版本中也失败了。除此之外,您没有检查一个空的s,因此它会在每次超时后在列表索引超出界限时失败。

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.

然后我添加了一些调试代码:我想看看实际发送了什么。这导致我使用XML而不是使用S_CLIENT的二进制进行流。SaveToStream(strm dfXMLUTF8);并将缓冲区复制到本地文件中,这样我就可以监视该文件。文件是OK的:它看起来是一个ClientDataSet的有效XML表示。

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.

最后,我注意到您在服务器中使用SendBuffer,在客户机中使用RecvStream。它们不匹配:您必须选择,所以在连接的两边使用缓冲区或流!我选择的流。

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')

所以我去重新访问接收代码。因为您已经在记录strmReply了。大小,我想知道为什么您没有对它为0(0)的特殊情况做出反应,所以我添加了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:

然后我开始调试,并发现了那个strmReply。每次都是0(0)。因此,我开始深入研究Synapse代码(我使用了最近使用的Habari组件中已经包含的副本)。在那里我发现了两件事:

  1. upon sending the stream, it was encoding the length of the stream in the first 4 bytes
  2. 在发送流时,它将流的长度编码为前四个字节
  3. the encoding was done in a different byte order than the decoding
  4. 编码以不同于解码的字节顺序进行

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.

这无疑是突触上的一个bug,所以请随时向他们报告。结果是,RecvStream不是SendStream的对应物,而是RecvStreamIndy。

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.

在所有这些变化之后,它仍然不起作用。原因是ClientDataSet1.LoadFromStream(strmReply);开始从流中的当前位置读取(您可以自己检查;核心加载逻辑在TCustomClientDataSet.ReadDataPacket中)。看来你忘了添加strmReply了。寻找(0,sostart);在我添加之后,它突然生效。

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.

最后,解决你的问题并不难。最难的部分是找到关于RecvStreamIndy的信息,剩下的部分仅仅是对代码的清理,并对发生的事情进行精确处理:这花费了大部分时间。在这种情况下,流处理要求您仔细监视流的位置。

Regards,

问候,

--jeroen

——基珀斯