• 0
gonzales

обновление визуальных компонентов

Вопросы

Доброго времени суток!

Интересует вопрос, больше по технологии, чем конкретной реализации. 

Задача такая, на форме есть много компонентов (это динамический массив, условно при разных условиях отображаются разные компоненты), отображающих информацию, хранящуюся на сервере. Соответственно для отображения изменений есть таймер, который раз в секунду кидает на сервер запрос, для каких компонентов нужна информация. Получая и распарсивая ответ меняю информацию на визуальных контролах. Нет ли технологии, чтобы сервер сам отдавал мне информацию, когда у него изменяется тот или иной компонент? И еще примечание, клиентов у сервера может быть несколько, на всех естественно должна отображаться актуальная информация.

 

Заранее спасибо, буду благодарен за любые идеи

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

44 ответа на этот вопрос

  • 0

Да, забыл, клиенты должны работать на Андроиде и iOS, поэтому FMX конечно же.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

WebSockets обеспечивает обратную связь "сервер-клиент", в отличие от обычного http, где идет клиент-сервер.

Увы, насколько я знаю - это доступно только на Indy (буду очень рад, если ошибаюсь), а (далее всё очень субъективно) с индейцами у меня как-то не сложилось...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Ну, во-первых Вы не указали что за сервер используется - HTTP или какой-то иной. Если сервер самописный ТСР, то можно на клиентской стороне открыть сокет на прослушку для сообщений от сервера и по приходу пакета от сервера менять отображение визуальных компонентов.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
2 часа назад, kami сказал:

WebSockets обеспечивает обратную связь "сервер-клиент", в отличие от обычного http, где идет клиент-сервер.

Увы, насколько я знаю - это доступно только на Indy (буду очень рад, если ошибаюсь), а (далее всё очень субъективно) с индейцами у меня как-то не сложилось...

Да, ковыряю WebSockets, но пока без особых успехов.

1 час назад, IVGSoft сказал:

Ну, во-первых Вы не указали что за сервер используется - HTTP или какой-то иной. Если сервер самописный ТСР, то можно на клиентской стороне открыть сокет на прослушку для сообщений от сервера и по приходу пакета от сервера менять отображение визуальных компонентов.

Сервер TCP, крутится на контроллере. А не подскажите подробнее, что значит открыть сокет на прослушку? На стороне клиента использую idTCPClient. Вы имеете ввиду создавать TCPServer и слушать порт?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
16 минут назад, gonzales сказал:

создавать TCPServer и слушать порт

Не надо TCPServerов на клиентах. Ничего хорошего из этого не выйдет, как минимум потому что белые IP есть у 1% пользователей сотовой связи, к остальным будет не подключиться извне.

TCP соединение позволяет обеспечить двусторонний обмен. Любой из корреспондентов (и клиент и сервер) в любой момент времени вправе отправить в соединение данные. На стороне TCP сервера для этого нужно отправить данные в конкретное соединение с конкретным клиентом, а на клиентской - и выбирать ничего не надо, у TCP-клиента только одно соединение.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
26 минут назад, kami сказал:

На стороне TCP сервера для этого нужно отправить данные в конкретное соединение с конкретным клиентом, а на клиентской - и выбирать ничего не надо, у TCP-клиента только одно соединение.

Как отправить понятно. На стороне сервера я фиксирую клиента и отправляю в него. Как на стороне клиента получить эту информацию?  

Сейчас я посылаю запрос и жду ответ. Если ответ не пришел, ошибка по таймауту.

SetLength(t_out, SizeOf(HTTPRequest));
      Move(HTTPRequest, t_out[0], SizeOf(HTTPRequest));
      Client1.Socket.WriteBufferOpen;
      Client1.Socket.Write(t_out, 13);
      Client1.Socket.WriteBufferFlush;
      Client1.Socket.WriteBufferClose;
      SetLength(t_in, SizeOf(THTTPAnswer));
      try
        Client1.Socket.ReadBytes(t_in, SizeOf(THTTPAnswer), false);
        Client1.Disconnect;

А как сделать, чтобы принимать информацию в произвольный момент времени?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

я попробовал добавить к клиенту idIOHandler и IdConnectionIntercept у которого есть метод OnRecive, но что-то он не отрабатывает, когда я с сервера шлю что-либо. Или я не умею его правильно настроить.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Получилось сделать с помощью обычного таймера

procedure TForm1.Timer1Timer(Sender: TObject);
begin
if client.Connected then
if Client.Socket.InputBufferIsEmpty = false then
 memo1.Lines.Add(Client.Socket.ReadChar);
end;

Но это как-то не по православному)) Неужели нет нормального метода получения данных.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
2 часа назад, IVGSoft сказал:

Можете использовать поток вместо таймера

А как поток поможет?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
3 часа назад, gonzales сказал:

А как поток поможет?

Очень просто - в потоке проверяйте наличие данных. Появился ответ - меняйте состояние контролов. Он будет работать в фоновом режиме. Можно даже снизить ему приоритет.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
13 часов назад, IVGSoft сказал:

Очень просто - в потоке проверяйте наличие данных. Появился ответ - меняйте состояние контролов. Он будет работать в фоновом режиме. Можно даже снизить ему приоритет.

В смысле в бесконечном цикле?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
10 часов назад, gonzales сказал:

В смысле в бесконечном цикле?

Почему бесконечном? :) Тут как захотите. Обычно делают как-то так :

While not (Terminated or fStopped) do

begin

  DoSomeActions;

end;

Изменено пользователем IVGSoft

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Для Андроид и Ios вам лучше использовать push. Сервер отправляет пуши (индивидуально, группе, или всем), а приложения их получают, парсят полученный JSON и раскидывают по контролам.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
В 21.02.2019 в 23:28, IVGSoft сказал:

Почему бесконечном? :) Тут как захотите. Обычно делают как-то так :

While not (Terminated or fStopped) do

begin

  DoSomeActions;

end;

Че то я все таки торможу. Пните плиз в нужную сторону. 

У меня есть TCPClient, который открывает сокет с сервером, и пока сокет открыт сервер шлет туда информацию об обновлении. Также через этот же сокет идут управляющие команды от клиента к серверу. Как мне это все добро запихнуть в поток?

Раньше, когда я делал обновление по таймеру, конструкция была такая

MasterTask := TTask.Run(
  procedure
   begin
    try
     IdTCPClient := TIdTCPClient.Create(nil);
     TThread.Synchronize(nil,
      procedure
       begin
        IdTCPClient.Host := Form1.Client1.Host;
        IdTCPClient.Port := Form1.Client1.Port;
        Form1.MasterTimer.Enabled := false;
        t_out := Form1.TimerBuffer;
        l := Length(Form1.RoomElements);
       end);
     if l > 0 then
      begin
       if IdTCPClient.Connected = false then
        IdTCPClient.Connect;
       
       if IdTCPClient.Connected = true then
        begin
         IdTCPClient.Socket.Write(t_out, Length(t_out));
         IdTCPClient.Socket.WriteBufferFlush;
         SetLength(t_in, l * 8);
         IdTCPClient.Socket.ReadBytes(t_in, l * 8, false);
         for j := 0 to l - 1 do
          begin
           if MasterTask.Status <> TTaskStatus.Canceled then
            begin
             TThread.Synchronize(nil,
              procedure
               begin
               //обновление элемента
               end);
            end;
          end;
       IdTCPClient.Disconnect;
       IdTCPClient.Free;
       TThread.Synchronize(nil,
        procedure
         begin
          Form1.MasterTimer.Enabled := true;
         end);  
    except
     on Er: Exception do
      begin  
      //исключение
      end;

то есть  внутри TTask создавался TCPClient и там же умирал. Но теперь мне этого не надо, нужно чтобы клиент жил все время, пока активно приложение. Если я сделаю 

While not (Terminated or fStopped) do

то я смогу опрашивать клиента, на предмет прихода ответа от сервера. А как мне в потоке послать запрос? 

Надеюсь понятно объяснил вопрос 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

ЗЫ. Я конечно могу открыть новое соединение и послать туда запрос и закрыть его, но хотелось бы все делать через одно соединение

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
19 часов назад, gonzales сказал:

Как мне это все добро запихнуть в поток?

Создайте свой класс потока. Я так поступаю для полного контроля над потоком.

Например как-то так :

  TBeePosMessageThread = class(TThread)
  private
    function ExtractData(aPacket : PBeePosDataPacket): string;
    procedure ProcessPacket(aPacket : pointer);
    procedure DoProcess(aPacket : PBeePosDataPacket);
  protected
    procedure Execute; override;
  end;

implementation

{ TBeePosMessageThread }

procedure TBeePosMessageThread.DoProcess(aPacket: PBeePosDataPacket);
var
  jObj : TJSONObject;
  sObj : string;
  cmd : string;
  typ : string;
  oObj : string;
  lock : boolean;
  id, uid : TBeePosID;
  price : Currency;
  qty : double;
  table : integer;
begin
  sObj := ExtractData(aPacket);
  jObj := nil;
  try
    jObj := TJSONObject.ParseJSONValue(sObj) as TJSONObject;
    cmd := jObj.Values['command'].Value;
    typ := jObj.Values['type'].Value;
    if cmd = 'update' then
      begin
        //New order received
        if typ = 'order' then
          begin
            oObj := (jObj.Values['object'] as TJSONObject).ToJSON;
            Synchronize(procedure
                          begin
                            MainFrm.OrderReceived(oObj);
                          end);
          end;
        //order lock changed
        if typ = 'orderlock' then
          begin
            try
              lock := jObj.Values['value'].Value.ToBoolean;
            except
            end;
            try
              id := jObj.Values['object'].Value.ToInt64;
            except
            end;
            try
              uid := jObj.Values['user'].Value.ToInt64;
            except
            end;
            try
              table := jObj.Values['table'].Value.ToInteger;
            except
            end;
            Synchronize(procedure
                          begin
                            MainFrm.SetOrderLocked(id, uid, lock, table);
                          end);
          end;
        //menu item stock changed
        if typ = 'menuitem' then
          begin
            id := 0;
            try
              id := jObj.Values['object'].Value.ToInt64;
            except
            end;
            try
              price := jObj.Values['price'].Value.ToDouble;
            except
            end;
            try
              qty := jObj.Values['qty'].Value.ToDouble;
            except
            end;
            Synchronize(procedure
                          begin
                            MainFrm.MenuStockChanged(id, qty, price);
                          end);
          end;
        //ingredient stock changed
        if typ = 'ingredient' then
          begin
            id := 0;
            try
              id := jObj.Values['object'].Value.ToInt64;
            except
            end;
            try
              price := jObj.Values['price'].Value.ToDouble;
            except
            end;
            try
              qty := jObj.Values['qty'].Value.ToDouble;
            except
            end;
            Synchronize(procedure
                          begin
                            MainFrm.IngredientStockChanged(id, qty, price);
                          end);
          end;
        //Customer received
        if typ = 'customer' then
          begin
            oObj := (jObj.Values['object'] as TJSONObject).ToJSON;
            Synchronize(procedure
                          begin
                            MainFrm.CustomerAdded(oObj);
                          end);
          end;
      end;
    if cmd = 'delete' then
      begin
        if typ = 'order' then
          begin
            try
              id := jObj.Values['object'].Value.ToInt64;
            except
            end;
            Synchronize(procedure
                          begin
                            MainFrm.RemoveOrder(id);
                          end);
          end;
      end;
  finally
    if Assigned(jObj) then
      FreeAndNil(jObj);
  end;
end;

procedure TBeePosMessageThread.Execute;
var
  Len : integer;
  Buf : TIdBytes;
  pBuf : pointer;
begin
  while not Terminated do
    begin
      sleep(10);
      if MainFrm.MessagingClient.Connected then
        begin
          len := 0;
          try
            if MainFrm.MessagingClient.IOHandler.CheckForDataOnSource(100) then
              begin
                len := MainFrm.MessagingClient.IOHandler.InputBuffer.Size;
                try
                  MainFrm.MessagingClient.IOHandler.ReadBytes(Buf, len, false);
                  GetMem(pBuf, len);
                  Move(Buf[0], pBuf^, len);
                  ProcessPacket(pBuf);
                finally
                  SetLength(Buf, 0);
                end;
              end;
          except
            on E:EIdException do
              Synchronize(procedure
                begin
                  MainFrm.MessagingClientDisconnected(nil);
                end);
          end;
        end
      else
        begin
          TTask.Run(procedure
            begin
              Synchronize(procedure
                      begin
                        MainFrm.MessagingClientDisconnected(nil);
                      end)
            end);
          Exit;
        end;
    end;
end;

function TBeePosMessageThread.ExtractData(aPacket: PBeePosDataPacket): string;
var
  str : string;
begin
  DecryptPacket(aPacket.Data, aPacket.BufferSize);
  SetLength(str, (aPacket.BufferSize div SizeOf(Char)) - 1);
  Move(aPacket.Data^, str[1], aPacket.BufferSize);
  Result := TIdDecoderMIME.DecodeString(str);
end;

procedure TBeePosMessageThread.ProcessPacket(aPacket: pointer);
var
  aDataPacket : PBeePosDataPacket;
begin
  try
    if TBeePosContext.CheckPacket(aPacket) then
      begin
        GetMem(aDataPacket, SizeOf(TBeePosDataPacket));
        aDataPacket.Header := PBeePosDataPacket(aPacket).Header;
        aDataPacket.BufferSize := PBeePosDataPacket(aPacket).BufferSize;
        aDataPacket.Data := nil;
        GetMem(aDataPacket.Data, aDataPacket.BufferSize);
        Move(pointer(NativeUInt(aPacket) + SizeOf(TBeePosMessagePacket))^,
             aDataPacket.Data^, aDataPacket.BufferSize);
        try
          DoProcess(aDataPacket);
        finally
          FreeMem(aDataPacket.Data);
        end;
      end
  finally
    FreeMem(aDataPacket);
    FreeMem(aPacket);
  end;
end;

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Спасибо большое, очень полезный пример! Сам уже думал о собственном классе.

А можно комментарий, я по коду не совсем понял

1. Метод Execute вызывается после создания экземпляра класса и живет, пока экземпляр не будет уничтожен, верно?

2. По методу Execute

while not Terminated do
    begin
      sleep(10);
      if MainFrm.MessagingClient.Connected then
        begin
          len := 0;
          try
            if MainFrm.MessagingClient.IOHandler.CheckForDataOnSource(100) then
              begin
                len := MainFrm.MessagingClient.IOHandler.InputBuffer.Size;
                try
                  MainFrm.MessagingClient.IOHandler.ReadBytes(Buf, len, false);
                  GetMem(pBuf, len);
                  Move(Buf[0], pBuf^, len);
                  ProcessPacket(pBuf);
                finally
                  SetLength(Buf, 0);
                end;
              end;
          except
            on E:EIdException do
              Synchronize(procedure
                begin
                  MainFrm.MessagingClientDisconnected(nil);
                end);

У Вас клиент MessagingClient лежит на основной форме MainFrm, и Вы обращаетесь к нему из потока, но без синхронизации, это нормально, так работает? Мне казалось, что любые обращения с элементами главного потока должны идти через синхронизацию. 

if MainFrm.MessagingClient.Connected then

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

on E:EIdException do
              Synchronize(procedure
                begin
                  MainFrm.MessagingClientDisconnected(nil);
                end);

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
1 минуту назад, gonzales сказал:

1. Метод Execute вызывается после создания экземпляра класса и живет, пока экземпляр не будет уничтожен, верно?

По умолчанию - да. Но можно указать флаг Suspended при создании. Тогда метод Execute будет запущен после снятия єтого флага.

2 минуты назад, gonzales сказал:

У Вас клиент MessagingClient лежит на основной форме MainFrm, и Вы обращаетесь к нему из потока, но без синхронизации, это нормально, так работает? Мне казалось, что любые обращения с элементами главного потока должны идти через синхронизацию. 

Синхронизация нужна при обращении к визуальным компонентам. И то только при обновлении (перерисовке) их свойств. Для чтения не надо.

4 минуты назад, gonzales сказал:

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

Да, именно так. Если есть изменения визуальных компонентов - надо использовать синхронизацию.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
2 минуты назад, gonzales сказал:

Спасибо большое!!!

Пошел делать!!!

Удачи! :)

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
5 минут назад, IVGSoft сказал:

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

Да, интересно. Но для начала сделаю класс потока и переведу все общение клиентов на него. Потом подумаю о синхронизации))

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

А можно еще вопрос, у idTCPClient есть свойство Socket и IOHandler, у которых все свойства и методы идентичны. В чем разница между ними? Работает и так и так.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
19 минут назад, gonzales сказал:

А можно еще вопрос, у idTCPClient есть свойство Socket и IOHandler, у которых все свойства и методы идентичны. В чем разница между ними? Работает и так и так.

Ну, IOHandler можно менять. Например на свой с шифрованием. Рекомендуется использовать его.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

Войти

  • Последние посетители   0 пользователей онлайн

    Ни одного зарегистрированного пользователя не просматривает данную страницу