【问题标题】:Delphi 10 Seattle Datasnap error: "Operation failed. Connection was closed."Delphi 10 Seattle Datasnap 错误:“操作失败。连接已关闭。”
【发布时间】:2016-04-15 10:31:31
【问题描述】:

我使用向导创建了一个独立的 Datasnap TCP/IP 服务器。我选择了示例方法(回声串和反向串)。我保存了服务器并运行它。然后我创建了一个客户端应用程序,并使用 file-new-other,将 ClientModule 与 ClientClasses 单元一起添加到该客户端项目中。在主窗体上。我添加了一个按钮。在按钮的 onclick 事件处理程序上,我添加了以下代码:

procedure TForm1.Button1Click(Sender: TObject);
begin
  if ClientModule1.SQLConnection1.Connected then
  begin
    Button1.Text := 'Open';
    ClientModule1.SQLConnection1.Close;
  end
  else
  begin
    Button1.Text := 'Close';
    // ClientModule1.SQLConnection1.Open;
    ClientModule1.ServerMethods1Client.ReverseString('myteststring');
  end;
end;

这里的目的是模拟客户端定期登录和注销服务器而不是保持连接的情况。这对于部署到移动设备的应用尤其重要。

您可以看到我注释掉了 Connection.Open,因为第一次调用 ServerMethods1client 会打开连接。生成的代码如下所示:

function TClientModule1.GetServerMethods1Client: TServerMethods1Client;
begin
  if FServerMethods1Client = nil then
  begin
    SQLConnection1.Open;
    FServerMethods1Client := TServerMethods1Client.Create(SQLConnection1.DBXConnection, FInstanceOwner);
  end;
  Result := FServerMethods1Client;
end;

现在问题出现了。第一次单击按钮时,连接打开,并调用该方法。在第二次单击该按钮时,连接将关闭。 在第 3 次单击时,使用 TDBXCommand 代码引发异常“操作失败。连接已关闭”。

作为一种解决方法,我尝试了这个:

procedure TForm1.Button1Click(Sender: TObject);
begin
  if ClientModule1.SQLConnection1.Connected then
  begin
    Button1.Text := 'Open';
    ClientModule1.SQLConnection1.Close;
    ClientModule1.ServerMethods1Client := nil;
  end
  else
  begin
    Button1.Text := 'Close';
    // ClientModule1.SQLConnection1.Open;
    ClientModule1.ServerMethods1Client.ReverseString('myteststring');
  end;
end;

这确实解决了问题,因为 ClientModule1 的 FServerMethods1Client 实例已重置,因此创建代码再次运行,就像第一次运行时一样。

现在唯一的另一个问题是(我正在使用 Eurekalog)它会造成内存泄漏。

我做错了什么?在不重新启动应用程序的情况下重复连接/断开 Datasnap 服务器的正确方法是什么?

【问题讨论】:

    标签: delphi datasnap delphi-10-seattle


    【解决方案1】:

    第一个错误的原因是绑定客户端代理(允许调用服务器方法)的代码绑定到本地 SQL 连接。注意创建代理类的调用:

    FServerMethods1Client := TServerMethods1Client.Create(SQLConnection1.DBXConnection, ...)
    

    底层 DBExpress 连接通过引用传递,代理类使用该连接调用服务器。您关闭并重新打开了连接,但 ServerMethodsClient1 使用的底层 DBExpress 连接已被破坏。因此,您会收到“连接已关闭”异常。 ServerMethodsClient1 使用的连接已关闭。您必须像在第二个示例中那样重新创建 ServerMethodsClient1。

    我无法回答您的第二个问题,因为我认为它是针对 ARC 的。对于 VCL DataSnap 应用程序,我会调用 ServerMethodsClient1.Free 而不是将其设置为 nil。基于我对 Delphi 的 ARC 实现(全部来自新闻组)的非常非常有限的理解,我相信您应该调用 ServerMethodsClient1.DisposeOf,因为该类源自 TComponent

    但我不确定。我相信有人会跳到这里,了解 ARC 以及销毁对象而不是内存泄漏的正确解决方案。

    【讨论】:

    • 谢谢。你说的很有道理。。在一定程度上。我实际上已经编写了完整的 VCL datasnap 应用程序,这些应用程序每天都在使用,我从来没有遇到过这个问题。为什么不?因为我使用设计时组件(TDSProviderConnection 和 TSqlServerMethod)从该服务器获取数据。更新是通过相应的 TClientDataset.Applyupdattes 完成的。我不调用任何服务器方法,并且在那个实现中,用户整天连接和断开连接而不关闭应用程序。更多...
    【解决方案2】:

    在我的 Android FMX 实现中,我只调用 servermethods 来完成任务。 (即我不使用Datasnap 数据组件)。 Datasnap 架构有太多不受控制的数据传输开销,无法在移动设备上实际考虑其他任何事情......为了绕过它(并且没有内存泄漏),我现在在需要时创建 TServermethods1Client 的本地实例,并且在上下文中释放它们:

    function TClientModule1.PostTheLog: Boolean;
    var
      Server: TServerMethods1Client;
    begin
    
      Server := TServerMethods1Client.Create(ClientModule1.SQLConnection1.DBXConnection);
      try
          UserID := Server.GetUserID; 
          ...
      finally
          Server.Free;
      end;
    end;
    

    现在 ClientModule1.SQLConnection1 可以随意连接和断开连接(最好在任何调用服务器方法之前连接,然后断开连接),不会出现其他问题。

    这引出了一个问题:可公开访问的 ServerMethods1Client 在哪个理想世界中真正有用?

    【讨论】:

    • 这行得通,尽管只要连接打开就保持分配客户端代理实例会更有效,而不是每次都创建和销毁一个新实例。内存分配/管理成本很高,尤其是与替代方案相比(不重复执行)。
    • 我很困惑您回答了自己的问题并将其标记为答案,而您提供的信息确实不是原始问题的答案。
    • 当然欢迎您提供完整的答案。同时,我自己的答案就足够了,不是吗?
    • 除了您提供的代码示例之外,我不知道您的“新”答案与我的答案有什么不同。我花时间给出了详细的答案,这显然也解决了你的内存泄漏问题。您的回答只是我回答的实施,但您没有给我回答这个问题的功劳。没什么大不了的,但我不觉得你感谢我花时间回答你的问题。
    • 你可以肯定我很感激它,因为我给了它一个赞成票。但是,它并不构成完整的答案,这是我在研究后自己提供的。如果您真的希望我这样标记您的答案,请展示如何在 TSqlConnection 关闭时保留 DBXconnection。
    猜你喜欢
    • 2022-08-14
    • 2017-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-08
    • 2017-12-26
    • 2015-09-27
    • 2016-04-05
    相关资源
    最近更新 更多