Используйте mORMot Framework для отправки сообщения между сервером и клиентами

В mORMot Framework ( http://www.synopse.info/) добавлена ​​поддержка WebSockets, на упаковке также есть демонстрационная версия о WebSockets (пример 31). В этом примере клиент отправляет сообщение на сервер, и этот ответ с новым сообщением клиенту. Я хотел бы использовать эту библиотеку, чтобы сделать это:

  1. Клиент отправляет сообщение на сервер и отправляет также IP-адрес (без ожидания сообщения от сервера.);
  2. Сервер может отправить сообщение одному клиенту по IP-адресу;

Примечание: IP-адрес используется только для идентификации клиента. Я могу использовать также уникальное имя.

Что-то вроде LAN-чата между клиентами и сервером. Я не понимаю, как редактировать образец n. 31, чтобы сделать это. Этот образец основан на интерфейсе.

1 ответ

Решение

Не нужно хранить IP или какой-либо параметр реализации низкого уровня (кстати, IP не сможет идентифицировать соединение уникальным образом: несколько клиентов могут использовать один и тот же IP).

В платформе mORMot асинхронные обратные вызовы реализуются через параметры интерфейса. На стороне сервера каждый экземпляр этого параметра на самом деле был бы "поддельным" экземпляром класса, связанным с входным соединением, способным вызывать клиент обратно через его методы интерфейса.

Это довольно простой способ реализации обратных вызовов - на самом деле это хороший способ реализации обратных вызовов SOLID на стороне сервера, а инфраструктура mORMot позволяет публиковать этот механизм клиент-серверным способом, используя WebSockets.

Итак, вы сначала определяете интерфейс обратного вызова и интерфейс службы:

  IChatCallback = interface(IInvokable)
    ['{EA7EFE51-3EBA-4047-A356-253374518D1D}']
    procedure BlaBla(const pseudo, msg: string);
  end;

  IChatService = interface(IInvokable)
    ['{C92DCBEA-C680-40BD-8D9C-3E6F2ED9C9CF}']
    procedure Join(const pseudo: string; const callback: IChatCallback);
    procedure BlaBla(const pseudo,msg: string);
    procedure CallbackReleased(const callback: IInvokable);
  end;

Затем на стороне сервера каждый вызов IChatService.Join() подписался бы на внутренний список соединений:

  TChatService = class(TInterfacedObject,IChatService)
  protected
    fConnected: array of IChatCallback;
  public
    procedure Join(const pseudo: string; const callback: IChatCallback);
    procedure BlaBla(const pseudo,msg: string);
    procedure CallbackReleased(const callback: IInvokable);
  end;

procedure TChatService.Join(const pseudo: string;
  const callback: IChatCallback);
begin
  InterfaceArrayAdd(fConnected,callback);
end;

Затем удаленный вызов IChatService.BlaBla() метод должен быть передан всем подключенным клиентам, просто вызвав IChatCallback.BlaBla() метод:

procedure TChatService.BlaBla(const pseudo,msg: string);
var i: integer;
begin
  for i := 0 to high(fConnected) do
    fConnected[i].BlaBla(pseudo,msg);
end;

Обратите внимание, что все вызовы цикла IChatCallback.BlaBla() будет выполняться через WebSockets асинхронным и неблокирующим образом, так что даже в случае большого количества клиентов IChatService.BlaBla() метод не будет блокировать В случае большого количества сообщений платформа может даже собирать сообщения push-уведомлений в одно сообщение, чтобы сократить использование ресурсов.

Следующий метод будет вызываться сервером, когда клиентский экземпляр обратного вызова освобождается (либо явно, либо если соединение разорвано), поэтому его можно использовать для отмены подписки на уведомление:

procedure TChatService.CallbackReleased(const callback: IInvokable);
begin
  InterfaceArrayDelete(fConnected,callback);
end;

На стороне сервера вы определяете службу как таковую:

  Server.ServiceDefine(TChatService,[IChatService],sicShared).
    SetOptions([],[optExecLockedPerInterface]);

Здесь optExecLockedPerInterface опция была установлена, чтобы все вызовы методов были сделаны потокобезопасными, чтобы параллельный доступ к внутреннему fConnected[] список будет в безопасности.

На стороне клиента вы реализуете IChatCallback интерфейс обратного вызова:

type
  TChatCallback = class(TInterfacedCallback,IChatCallback)
  protected
    procedure BlaBla(const pseudo, msg: string);
  end;

procedure TChatCallback.BlaBla(const pseudo, msg: string);
begin
  writeln(#13'@',pseudo,' ',msg);
end;

Затем вы подписываетесь на свой удаленный сервис как таковой:

var Service: IChatService;
    callback: IChatCallback;
...
    Client.ServiceDefine([IChatService],sicShared);
    if not Client.Services.Resolve(IChatService,Service) then
      raise EServiceException.Create('Service IChatService unavailable');
...
      callback := TChatCallback.Create(Client,IChatCallback);
      Service.Join(pseudo,callback);
...
    try
      repeat
        TextColor(ccLightGray);
        readln(msg);
        if msg='' then
          break;
        Service.BlaBla(pseudo,msg);
      until false;
    finally
      callback := nil;
      Service := nil; // release the service local instance BEFORE Client.Free
    end;

Если вы сравните с существующими клиент-серверными SOA-решениями (в Delphi, Java, C# или даже в Go или других средах), этот механизм обратного вызова на основе интерфейса звучит довольно уникально и с ним легко работать. Конечно, это работает от Delphi 6 до XE7, а также под Linux благодаря FPC или CrossKylix.

Я загрузил полный пример исходного кода в наш репозиторий, как на стороне сервера, так и на стороне клиента.

Другие вопросы по тегам