2

我从客户端向服务器发送了一个字符串,他应该将其发回给我。这次它是由 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;

现在,每当我启动服务器并单击客户端上的连接按钮时,它应该将 DataSet 传输到客户端,并且 TDBGrid 应该变得活跃起来。

不幸的是,这不会发生。相反,我收到标题中所述的错误:缺少数据提供者或数据包。但是数据提供者设置为 DataSetProvider1(在对象检查器中)。

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

4

1 回答 1

4

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

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

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

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

如果它仍然重现,则将 .pas/.dfm 发布到某处。如果它不复制,则开始剪切部分直到它复制。

祝你好运!

——杰伦

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

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

您的大多数问题归结为不精确。我已经对此有一种模糊的感觉,基于你的源代码格式、你为你的东西使用的命名约定的混合,以及缺乏释放分配的对象。

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

让我们从发送代码开始。

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

这给了我一种我之前要求的可重复的案例。

然后我开始运行客户端。那里似乎LogThis(strmReply.Size); 输出 0(零!),所以它没有收到任何东西。

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

然后我添加了一些调试代码:我想看看实际发送了什么。这导致我使用 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 received')

然后我开始调试,发现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,剩下的只是清理你的代码并准确地了解发生的情况:这花费了大部分时间。在这种情况下,流处理需要您仔细观察流位置。

问候,

——杰伦

于 2009-08-14T10:46:58.263 回答