Какое самое простое решение для прозрачного удаленного взаимодействия с Delphi? - PullRequest
3 голосов
/ 12 октября 2010

У меня есть двухуровневое приложение Delphi для Win32 с большой бизнес-логикой, реализованной в объекте God, который я хочу передать в отдельный сервис.Эта отдельная служба должна быть доступна нескольким клиентам через протокол TCP / IP в стиле telnet.

Как мне сделать переход максимально простым?

Точно, я бы хотел сохранить этопростота: я хочу определить каждую функцию только один раз.Например, если я хочу добавить в приложение функцию входа с помощью пин-кода, мне просто нужно определить функцию

 function Login(Username: string; PinCode: integer): boolean;

в каком-либо объекте на сервере, тогда я могу использовать ее с клиентов без каких-либодополнительная работа.

В худшем случае мне нужно реализовать три функции вместо одной.Во-первых, собственно тело функции на сервере, во-вторых, unmarshaller, который получает текстовую строку из сети, распаковывает ее и проверяет правильность:

 procedure HandleCommand(Cmd: string; Params: array of string);
 begin
   ...
   if SameText(Cmd, 'Login') then begin
     CheckParamCount(Params, 2);
     ServerObject.Login(
       Params[0],
       StrToInt(Params[1])
     );
   end;
 end;

В-третьих, маршаллер, который при вызове клиентом упаковываетпараметры и отправляет их на сервер:

function TServerConnection.Login(Username: string; PinCode: integer): boolean;
begin
  Result := StrToBool(ServerCall('Login '+Escape(Username)+' '+IntToStr(PinCode)));
end;

Очевидно, я этого не хочу.

Пока мне удалось избавиться от демаршаллера.Работая с Delphi RTTI, я написал общий unmarshaller, который ищет опубликованный метод по имени, проверяет параметры и вызывает его.

Так что теперь я могу просто добавить опубликованный метод к объекту сервера, и я могучтобы позвонить по телнету:

 function Login(Username: string; PinCode: integer): boolean;

 > login john_locke
 Missing parameter 2 (PinCode: integer)!

Но что мне делать с написанием маршаллера?Я не могу динамически получить список функций сервера и добавить функции к клиентскому объекту.Я, вероятно, могу сохранить какой-то динамический набор псевдофункций, но это сделало бы мой клиент безобразным:

ServerConnection.Call('Login', [Username, Password]);

Плюс, это отрицательно сказывается на безопасности типов, поскольку каждый параметр передается как вариант.Если возможно, я бы хотел сохранить безопасность типов во время компиляции.

Может быть, автогенерация кода клиента?Я, вероятно, могу написать «GetFunctionList ()» и «GetFunctionPrototype (Name: string)» на моем сервере:

> GetFunctionList
Login
Logout
IsLoggedIn

> GetFunctionPrototype Login
function Login(Username: string; PinCode: integer): boolean;

Так что каждый раз, когда мне нужно обновить клиента, я просто повторно запрашиваю все функциипрототипы с сервера и автоматически генерировать маршаллерский код для них.Но это смешало бы компиляцию с выполнением: мне нужно было сначала скомпилировать сервер, затем запустить его и запросить его функции, затем собрать клиентский маршаллер и перекомпилировать клиент только после этого.Сложно!

Другой вариант - написать универсальную функцию маршаллера и затем вызвать ее из всех функций-прототипов клиента:

procedure TServerConnection.GenericMarshaller(); assembler;
asm
  //finds the RTTI for the caller function, unwinds stack, pops out caller params,
  //packs them according to RTTI and sends to the server.
  //receives the result, pushes it to stack according to RTTI, quits
  //oh god
end;

function TServerConnection.Login(Username: string; PinCode: integer): boolean; assembler;
asm
  call GenericMarshaller
end;

Это избавляет меня от написания ручной упаковки каждый раз (меньше шансов на ошибки), но все равно требует, чтобы я вручную скопировал прототипы серверных функций в объект client.Кроме того, написание этого универсального маршаллера, вероятно, станет настоящим адом.

Тогда есть возможность использовать RPC, но мне это не нравится, потому что мне нужно переопределить все функции в IDL,И редактор Delphi IDL отстой.А для интерфейсов OLE Delphi принудительно генерирует «безопасные» функции, которые тоже отстой.И нет никакого способа реализовать функции автоматического обнаружения-разъединения и автоматического восстановления-соединения, кроме проверки вызова функции EVERY SINGLE в классе RPC:

function TServerWrapper.Login(Username: string; PinCode: integer): boolean;
begin
  try
    RealObject.Login(Username, Pincode);
  except
    on E: EOleException do
      if IndicatesDisconnect(E) then
        Disconnect;
      Reconnect;
      RealObject.Login(Username, Pincode);
  end;
end;

Мы вернулись к нескольким функциям вместо одной.

Итак, что бы вы предложили, ребята?Какие другие варианты я пропускаю?Существуют ли общие шаблоны или готовые решения для удаленного взаимодействия в Delphi?

Ответы [ 4 ]

7 голосов
/ 13 октября 2010

Лично я не думаю, что вы должны разрабатывать распределенную систему, основанную на прозрачном удаленном взаимодействии.RPC на уровне процедур - просто неправильный уровень детализации для надежного и эффективного взаимодействия клиент-сервер;делая его терпимым, вы заставите вас писать плохо определенные процедуры, которые выполняют целую кучу вещей и принимают массу параметров, просто чтобы не допустить попадания в производительность и надежность сетевых циклических обходов (=> много мелких вызовов) API.

Думайте больше с точки зрения сообщений.Подумайте о создании очереди сообщений на клиенте для передачи на ваш сервер, возможно, с обратными вызовами, связанными с каждым сообщением для обработки возвращаемых значений, если таковые имеются (анонимные методы отлично подходят для обратных вызовов!).Затем отправляйте все сообщения на сервер за один раз, получайте и анализируйте результаты, вызывая обратные вызовы и т. Д. По мере необходимости для каждого обрабатываемого сообщения.

Если ваш сервер каким-то образом инкапсулирует все изменения состояния при обработкесоставной запрос (очередь сообщений от клиента) в транзакцию, он работает еще лучше: вы получаете хороший способ обработки ошибок, возникающих в n-м сообщении, - просто отбрасываете всю работу, выполненную на сервере, и действуете так, как будтоклиентский запрос никогда не поступал. Обычно это также упрощает понимание клиентом состояния сервера.

В прошлом я создавал системы, ориентированные на эти принципы, и они работают.Делая сеть прозрачной, притворяясь, что физическая структура системы не существует, она вызывает боль в долгосрочной перспективе.

5 голосов
/ 12 октября 2010

Вы обязательно должны использовать DataSnap как минимум в Delphi 2010, предпочтительно в Delphi XE.То, что вы описали, в основном и есть DataSnap.Это очень мощная система RPC, которая позволяет создавать серверы, которые могут обслуживать любой тип или класс Delphi для клиента.Как только вы установили сервер, клиент может создать прокси-код для вызова сервера, как если бы он был встроенной частью вашего приложения.Нет IDL, нет COM, ничего, кроме чистого кода Delphi.Сервер может даже создать комбинацию REST / JSON для легкого использования с не-Delphi клинами.

DataSnap - это именно то, что вы ищете - чистый, аккуратный, мощный и простой.

4 голосов
/ 12 октября 2010

Вы должны посмотреть на одну из платформ, такую ​​как RemObjects SDK.Вы используете их инструмент для определения интерфейса, и он делает всю работу за вас.Затем вы просто реализуете функции, а в клиенте просто вызываете их.Я преобразовал одно приложение в приложение клиент / сервер, взяв компонент бизнес-логики и сделав его интерфейсом.Не нужно накатывать собственный протокол.

2 голосов
/ 12 октября 2010

Вы могли бы написать программу, которая использовала бы RTTI для создания необходимых вам единиц, а не для ручного кодирования всего.В прошлом я делал это с системой ORM, которая содержала тысячи таблиц ... было быстрее и намного проще написать генератор кода, который выделяет классы и модули, необходимые для работы с системой, из схемы базы данных.

Другое преимущество этого подхода заключается в том, что его проще тестировать, поскольку его можно масштабировать с предсказуемым поведением.

...