Delphi Indy TCPClient将文件发送到TCPServer,并使用空格和@字符

Delphi Indy TCPClient将文件发送到TCPServer,并使用空格和@字符,delphi,indy,Delphi,Indy,我刚开始和印第一起“玩”。使用此帖子并对其进行了修改,使我的客户机成为向服务器发送数据的人 客户端具有以下代码来发送数据: procedure TForm1.btnConnectClick(Sender: TObject); begin TCPClient.Host:='192.168.88.117'; TCPClient.Port:=32832; TCPClient.Connect; end; procedure TForm1.btnSendClick(Sender: TOb

我刚开始和印第一起“玩”。使用此帖子并对其进行了修改,使我的客户机成为向服务器发送数据的人

客户端具有以下代码来发送数据:

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
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;
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;
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;
2.每次发送文件后,我都需要断开连接,否则Indy TCPServer会将文件锁定,这是预期的行为吗?我如何判断文件是否已完成?我需要在另一个线程中逐个处理接收到的文件

多谢各位

更新1

根据Remy的建议,我对服务器进行了如下更改:

   @HERE IS SOME STUFF
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;
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;
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;
在客户端上,我使用ITask将整个传输移动到一个线程中,如下所示:

   @HERE IS SOME STUFF
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;
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;
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;
我每10秒用计时器检查一次任务是否正在运行,如果没有,我会创建一个新任务,如下所示:

   @HERE IS SOME STUFF
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;
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;
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会跟踪背景中的连接。连接A-A、B-B、C-C,它们不能混淆。我这样问是因为从服务器和客户端我只发送OK或send,它就可以工作了。(希望不仅仅是运气好)

这很重要,因为一旦它完全工作,我将有多个客户端(android设备)将数据发送到此服务器。而且有时会有一个以上的客户端上传数据的可能性很高

我还测试了这样的情况:如果我开始将文件复制到客户机输入目录中,但复制没有完成,而是任务开始了。它工作正常,第一次运行时检测到350个文件,下一次运行时检测到500个文件

还测试了如果我简单地停止服务器,会发生什么也可以工作。如果我使用TCPServer.Active:=false

在客户端,如果连接丢失,WriteLn和ReadLn会导致异常(我猜是服务器超时)

在服务器端,一旦完成,我只需将接收到的文件从OUT重命名为DAT。我不能100%确定这是否保证文件确实100%正确传输。然而,我无法在测试期间生成损坏的文件

总之,整个想法是:

TCPClient运行在Android手机上,用户可以扫描条形码,我从中创建一个控制文件,然后每隔10秒上传到服务器。然后由服务器处理并发送到另一台服务器

问候

更新2

已从服务器中删除此行:

 DelFilesFromDir(ExtractFilePath(Application.ExeName), '*.out', FALSE);
在Execute方法中加入是个坏主意

我需要找到另一种方法来删除可能的垃圾文件


TransferActive也是一个全局变量,正在被Transfer后台线程修改。但由于一次只运行一个线程,我认为这应该是安全的。

1)此代码中没有任何内容会导致收到的文件中出现额外的字节。如果源文件中不存在这些字节,则Indy之外的其他内容必须干扰数据。2) 不,您不需要断开连接即可完成传输。您告诉
Write()
在文件数据之前发送文件大小,并告诉
ReadStream()
在数据之前读取该大小。这是最好的,并确保TCP连接在传输完成后仍然可以使用。当
t文件流
被销毁时,接收到的文件被解锁。确保这一点。根据所示代码,我看不出你所描述的是如何发生的,无论是对于#1还是#2,除非有外部干扰。代码本身很好。尽管如此,我还是建议让客户端在发送
SEND_file
命令之前打开源文件,并让服务器在创建目标文件之后向客户端发送成功/失败回复,然后再让客户端发送文件数据。另一方面,
ExtractFilePath()
为您提供了一个尾随反斜杠,你真的不应该用你的应用程序文件夹来写文件。使用
SHGetFolderPath()
SHGetKnownFolderPath()
请求Windows在您的用户配置文件中提供一个文件夹,保证您具有写入权限。Remy感谢您的帮助,问题1和问题2似乎不再存在。我不知道我做了什么,但在编写了第一个Android客户端,然后回到旧的windows项目之后,问题就消失了。可能是因为我在测试过程中做错了什么。无论如何,现在我已经更新了代码,它似乎工作正常。但是如果您可以对其进行注释,那就太好了:)ExtractFilePath(Application.ExeName)仅用于开发版本。在Android上,我使用的是“Home”目录。