Ошибка Delphi 10 Seattle Datasnap: сбой операции. Соединение было закрыто.

Я создал автономный TCP/IP-сервер Datasnap с помощью мастера. Я выбрал примеры методов (echostring и reversestring). Я сохранил сервер и запустил его. Затем я создал клиентское приложение и, используя 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;

Теперь возникает проблема. При первом нажатии на кнопку открывается соединение и вызывается метод. При втором нажатии на кнопку соединение закрывается. При третьем щелчке возникает исключение «Операция не удалась. Соединение было закрыто» с кодом 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;

Это вроде как решает проблему, поскольку экземпляр FServerMethods1Client ClientModule1 сбрасывается, поэтому код создания запускается снова, как и при первом запуске.

Единственная другая проблема сейчас (я использую Eurekalog) создает утечку памяти.

Что я делаю неправильно? Как правильно повторно подключаться/отключаться от сервера Datasnap без перезапуска приложения?


person nolaspeaker    schedule 15.04.2016    source источник


Ответы (2)


Причина первой ошибки заключается в том, что код, который связывает прокси-сервер на стороне клиента (который позволяет вызывать методы сервера), привязан к локальному SQL-соединению. Обратите внимание на вызов для создания прокси-класса:

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

Базовое соединение DBExpress передается по ссылке, и прокси-класс использует это соединение для вызова сервера. Вы закрыли и снова открыли соединение, но базовое соединение DBExpress, которое использовалось ServerMethodsClient1, было уничтожено. Таким образом, вы получаете исключение «Соединение было закрыто». Соединение, которое использовалось ServerMethodsClient1, было закрыто. Вы должны воссоздать ServerMethodsClient1, как вы сделали во втором примере.

Я не могу ответить на ваш второй вопрос, так как я считаю, что он специфичен для ARC. Для приложения VCL DataSnap я бы вызвал ServerMethodsClient1.Free, а не устанавливал его равным нулю. Основываясь на моем очень, очень ограниченном понимании реализации ARC в Delphi (все это из групп новостей), я считаю, что вам следует вызывать ServerMethodsClient1.DisposeOf, поскольку класс происходит от TComponent.

Но я не уверен в этом. Я уверен, что здесь найдется кто-то, кто понимает ARC и правильное решение для уничтожения объекта, а не утечки памяти.

person Jon Robertson    schedule 16.04.2016
comment
Спасибо. То, что вы сказали, имеет смысл... до определенного момента. На самом деле я написал законченные приложения для создания снимков данных VCL, которые используются каждый день, и я никогда не сталкивался с этой проблемой. Почему нет? Потому что я использовал компоненты времени разработки (TDSProviderConnection и TSqlServerMethod) для получения данных с этого сервера. Обновления выполняются через соответствующий TClientDataset.Applyupdattes. Я не вызываю никаких серверных методов, и в этой реализации пользователи подключаются и отключаются весь день, не закрывая приложение. Более... - person nolaspeaker; 17.04.2016

В моей реализации FMX для Android я вызываю серверные методы только для того, чтобы что-то сделать. (т.е. я не использую компоненты данных 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 действительно был бы полезен?

person nolaspeaker    schedule 17.04.2016
comment
Это работает, хотя было бы более эффективно оставить экземпляр клиентского прокси-сервера выделенным, пока соединение открыто, а не создавать и уничтожать новый экземпляр каждый раз. Выделение/управление памятью дорого, особенно по сравнению с альтернативой (не делать это повторно). - person Jon Robertson; 06.05.2016
comment
Меня смущает, что вы ответили на свой вопрос и отметили его как ответ, когда предоставленная вами информация действительно не является ответом на исходный вопрос. - person Jon Robertson; 06.05.2016
comment
Вы, конечно, можете дать полный ответ. А пока достаточно моего собственного ответа, не так ли? - person nolaspeaker; 07.05.2016
comment
Я не знаю, чем ваш новый ответ отличается от моего ответа, кроме того, что вы предоставили пример кода. Я нашел время, чтобы дать подробный ответ, который, по-видимому, также устранил вашу утечку памяти. Ваш ответ - это просто реализация моего ответа, но вы не отдали мне должное за ответ на вопрос. Ничего особенного, но я не чувствую, что вы цените то, что я потратил время, чтобы ответить на ваш вопрос. - person Jon Robertson; 09.05.2016
comment
Вы можете быть уверены, что я оценил это, потому что я проголосовал за него. Однако это не полный ответ, который я дал сам после исследования. Если вы действительно хотите, чтобы я пометил ваш ответ как таковой, покажите, как сохранить соединение DBX при закрытии соединения TSqlConnection. - person nolaspeaker; 09.05.2016
comment
Вы не можете. TSqlConnection — это соединение dbExpress с базой данных. Но TSqlConnection — это более высокая абстракция соединения. DBXConnection — это фактическое соединение с базой данных. Закрытие TSqlConnection закроет DBXConnection. Вы не можете закрыть соединение с базой данных и одновременно оставить это соединение с базой данных открытым. Есть только одно соединение. Либо закрой, либо нет. Вы не можете сделать и то, и другое. - person Jon Robertson; 20.05.2016