Indy TCPClient отправляет файл в TCPServer Leadng Пробелы и символ @ - PullRequest
0 голосов
/ 31 января 2020

Я только начал "играть" с Инди. Использовал этот пост , как отправить файл с сервера на клиент, используя indy , и изменил его так, чтобы мой Клиент был единственным, кто отправляет данные на сервер.

Клиент имеет следующее код для отправки данных:

procedure TForm1.btnConnectClick(Sender: TObject);
begin
  TCPClient.Host:='192.168.88.117';
  TCPClient.Port:=32832;

  TCPClient.Connect;

end;

procedure TForm1.btnSendClick(Sender: TObject);
var
  AStream : TFileStream;
begin
  if not TCPClient.Connected then Exit;

  TCPClient.IOHandler.WriteLn('SEND_FILE '+GetComputerName+FormatDateTime('yyyy-mm-dd_hhnnsszzz',now)+'.txt');

  try
    AStream := TFileStream.Create(ExtractFilePath(Application.ExeName)+'\1.txt', fmOpenRead or fmShareDenyWrite);

    TCPClient.IOHandler.LargeStream := true;
    TCPClient.IOHandler.Write(AStream, 0, True);

  finally
    AStream.Free;
  end;

  TCPClient.Disconnect; // otherwise the file is locked on the server side

end;

Сервер имеет следующий код для получения данных:

procedure TForm1.TCPServerExecute(AContext: TIdContext);
var
  AStream : TFileStream;
  cmd, params, filename : string;
begin
  params := AContext.Connection.IOHandler.ReadLn();
  cmd := Fetch(params);

  if cmd = 'SEND_FILE' then
  begin
    filename := ExtractFileName(params);

    TThread.Queue(nil,
      procedure
      begin
        Memo1.Lines.Add('Command '+cmd+' File Name '+filename);
      end
    );



    AStream := TFileStream.Create(ExtractFilePath(Application.ExeName)+'\'+filename, fmCreate);
    try
      AContext.Connection.IOHandler.LargeStream:=true;
      AContext.Connection.IOHandler.ReadStream(AStream, -1, false);

    finally
      AStream.Free;
    end;

  end;
end;

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

У меня только две проблемы:

1. по какой-то причине файл, полученный на сервере, всегда имеет одинаковое количество начальных пробелов, за которым следует символ @.

Исходный файл выглядит следующим образом

HERE IS SOME STUFF

Полученный файл на сервере выглядит следующим образом :

   @HERE IS SOME STUFF

2. Похоже, что после каждой отправки файла мне нужно отключиться, в противном случае Indy TCPServer сохраняет файл заблокированным, это ожидаемое поведение? Как я могу сказать, что файл готов? Мне нужно будет обработать полученные файлы в другом потоке один за другим.

Спасибо.

ОБНОВЛЕНИЕ 1

Так, как рекомендовано Реми, я изменил сервер следующим образом:

procedure TForm1.TCPServerExecute(AContext: TIdContext);
var
  AStream : TFileStream;
  cmd, params, filename : string;
begin
  params := AContext.Connection.IOHandler.ReadLn();
  cmd := Fetch(params);

  if cmd = 'SEND_FILE' then
  begin
    filename := ExtractFileName(params);

    AStream := TFileStream.Create(ExtractFilePath(Application.ExeName)+'\'+filename, fmCreate);
    try
      AContext.Connection.IOHandler.WriteLn('OK');

      AContext.Connection.IOHandler.LargeStream:=true;
      AContext.Connection.IOHandler.ReadStream(AStream, -1, false);
    finally
      AStream.Free;
    end;
  end;

  params := AContext.Connection.IOHandler.ReadLn();
  cmd := Fetch(params);

  if cmd = 'SENT' then
    RenameFile(filename,ChangeFileExt(filename,'.dat'));


end;

На клиенте я переместил весь перевод в поток с помощью ITask следующим образом:

procedure TForm1.BackgroundTransfer;
var
    TCPClient : TidTCPClient;
    AStream : TFileStream;
    Files : TStringDynArray;
    i : integer;
    isDone : boolean;
begin
  Files := TDirectory.GetFiles(ExtractFilePath(Application.ExeName)+'\1\','*.out');

  TThread.Queue(TThread.CurrentThread,
    procedure
    begin
      Memo1.Lines.Add(IntToStr(Length(Files)));
    end
  );

  if Length(Files) = 0 then Exit;

  TCPClient := TidTCPClient.Create(nil);
  try
    TransferActive:=true;
    TCPClient.Disconnect;
    TCPClient.Host:='192.168.88.117';
    TCPClient.Port:=32832;
    TCPClient.Connect;

    for i := Low(Files) to High(Files) do
    begin

      isDone:=false;

      if FileExists(Files[i]) = true then
      begin
        TCPClient.IOHandler.WriteLn('SEND_FILE '+Files[i]);

        try
          // wait for server
          repeat
            sleep(100);
            if TCPClient.IOHandler.ReadLn() = 'OK' then break;

          until ( true );

          AStream := TFileStream.Create(Files[i], fmOpenRead or fmShareDenyWrite);

          TCPClient.IOHandler.LargeStream := true;
          TCPClient.IOHandler.Write(AStream, 0, True);

          isDone:=true;
          TCPClient.IOHandler.WriteLn('SENT '+Files[i]);
        finally
          AStream.Free;
        end;

        if isDone = true then
          System.SysUtils.DeleteFile(Files[i]);
      end;

    end;
  finally
    TCPClient.Free;
    TransferActive:=false;
  end;

end;

Я проверяю таймером каждый 10 секунд, если Задача запущена, если нет, я создаю новую, как это:

procedure TForm1.Timer2Timer(Sender: TObject);
  if TransferActive = false then
  begin
    inc(ThreadTimer);
    Panel1.Caption:=ThreadTimer.ToString;
    Panel1.Color:=clRed;
  end
  else
  begin
    Panel1.Color:=clGreen;
  end;

  if ThreadTimer >= 10 then
  begin
    ThreadTimer:=0;

    TransferTask := TTask.Create(BackGroundTransfer);
    TransferTask.Start;
  end;
end;

Если я правильно понял, Indy отслеживает соединения в фоновом режиме. Соединения AA, BB, C - C, они не могут перепутать. Я спрашиваю об этом, потому что со стороны сервера и клиента я просто отправляю OK или SENT, и это работает. (надеюсь, не просто по счастливой случайности)

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

Я также проверил это, например, что произойдет, если я начну копировать файлы в каталог INPUT клиентов, и копирование не будет выполнено, но Задание началось. Он работал, при первом запуске он обнаружил 350 файлов, при следующем запуске 500.

Также проверял, просто ли я останавливал сервер, что тоже работает, что работает. Если я использую TCPServer.Active: = false;

На стороне клиента WriteLn и ReadLn правильно вызывают исключение (по-видимому, тайм-аут сервера), если соединение потеряно.

На стороне сервера я просто переименовываю полученный файл из OUT в DAT как только это будет сделано. Я не уверен на 100%, гарантирует ли это, что файл действительно был правильно передан на 100%. Однако я не смог создать поврежденный файл во время тестирования.

В любом случае вся идея такова:

TCPClient работает на телефоне Android, где пользователь сканирует штрих-коды, я создаю управляющий файл из этого, который затем через 10 се c интервалов загружается на сервер. И с этого момента Обработано сервером и отправлено на другой сервер.

С уважением

ОБНОВЛЕНИЕ 2

Удалена эта строка с сервера:

 DelFilesFromDir(ExtractFilePath(Application.ExeName), '*.out', FALSE);

было плохой идеей вставлять внутрь метода Execute.

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

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

...