如何使用 Indy TCP Server/Client 传输数据?

How to transfer data using Indy TCP Server/Client?

我想将数据从 TIdTCPServer 传输到 TIdTCPClient。

在服务器端我有:

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var x:Integer;
    Received:String;
    SendBuff:TBytes;
    hFile:THandle;
    fSize:Int64;
begin
 fSize:=0;
 if MOpenFileForRead(hFile,MGetExePath+'\test.jpg') then begin
  fSize:=MFileSize(hFile);
  SetLength(SendBuff,fSize);
  MReadFile(hFile,SendBuff[0],fSize);
  MCloseFile(hFile);
 end;

 // ... here the SendBuff contains valid data, I checked. 

 repeat
  Received:=AContext.Connection.Socket.ReadLn;
  if not AContext.Connection.Connected then Exit;
  if Received=CMD_TEST_FILE then begin
   AContext.Connection.Socket.Write(fSize);
   AContext.Connection.Socket.WriteBufferOpen;
   AContext.Connection.Socket.Write(SendBuff);
   AContext.Connection.Socket.WriteBufferClose;
  end;
 until False;
end;

客户端:

procedure TForm1.Button2Click(Sender: TObject);
var fSize:Int64;
    RecvBuff:TBytes;
    hFile:THandle;
begin
  IdTCPClient1.Socket.WriteLn(CMD_TEST_FILE);
  fSize:=IdTCPClient1.Socket.ReadInt64;
  SetLength(RecvBuff,fSize);
  IdTCPClient1.Socket.ReadBytes(RecvBuff,fSize);
  if MCreateFile(hFile, MGetExePath+'\new.jpg') then begin
   MWriteFile(hFile,RecvBuff[0],fSize);
   MCloseFile(hFile);
  end;
  Memo1.Lines.Add('ok');
end;

...但它不起作用。我检查了使用的读写数据功能,它们是可以的。在服务器上缓冲区设置正常,文件大小到达客户端正常,但客户端缓冲区的内容仅为零。

P.S: 我想以这种方式发送文件,而不是流或其他任何方式。

如果您查看 ReadBytes() 的签名,它有一个可选的 AAppend 参数,默认情况下为 True:

procedure ReadBytes(var VBuffer: TIdBytes; AByteCount: Integer; AAppend: Boolean = True); virtual;

当为真时,它从套接字读取字节并将它们附加到现有字节数组的末尾。由于您正在预分配数组,因此初始字节未定义,文件字节跟在未定义字节之后。

要解决此问题,您需要:

  1. 停止预分配字节数组,让ReadBytes()为你分配。

    procedure TForm1.Button2Click(Sender: TObject);
    var
      fSize: Int64;
      RecvBuff: TBytes;
      hFile: THandle;
    begin
      IdTCPClient1.Socket.WriteLn(CMD_TEST_FILE);
      fSize := IdTCPClient1.Socket.ReadInt64;
      // SetLength(RecvBuff,fSize); // <-- remove this line
      IdTCPClient1.Socket.ReadBytes(RecvBuffer, fSize);
      if MCreateFile(hFile, MGetExePath+'\new.jpg') then
      begin
        MWriteFile(haile, RecvBuff[0], fSize);
        MCloseFile(hFile);
      end;
      Memo1.Lines.Add('ok');
    end;
    
  2. 预分配数组,但将 AAppend 设置为 False,以便字节填充现有数组而不是附加到它。

    procedure TForm1.Button2Click(Sender: TObject);
    var
      fSize: Int64;
      RecvBuff: TBytes;
      hFile: THandle;
    begin
      IdTCPClient1.Socket.WriteLn(CMD_TEST_FILE);
      fSize := IdTCPClient1.Socket.ReadInt64;
      SetLength(RecvBuff, fSize);
      IdTCPClient1.Socket.ReadBytes(RecvBuff, fSize, False);
      if MCreateFile(hFile, MGetExePath+'\new.jpg') then
      begin
        MWriteFile(haile, RecvBuff[0], fSize);
        MCloseFile(hFile);
      end;
      Memo1.Lines.Add('ok');
    end;
    

Update:话虽如此,我强烈建议您改用 TStream,尽管您说您不想这样做。它将大大简化代码和内存管理,而不会破坏您选择使用的通信协议:

procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
begin
  AContext.Data := TFileStream.Create(MGetExePath+'\test.jpg', fmOpenRead or fmShareDenyWrite);
  AContext.Connection.IOHandler.LargeStream := True;
end;

TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
  Received: String;
begin
  Received := AContext.Connection.IOHandler.ReadLn;
  if Received = CMD_TEST_FILE then
  begin
    AContext.Connection.IOHandler.Write(TStream(AContext.Data), 0, True);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  FileName: string;
  Strm: TStream;
begin
  FileName := MGetExePath+'\new.jpg';
  Strm := TFileStream.Create(FileName, fmCreate);
  try
    try
      IdTCPClient1.IOHandler.WriteLn
(CMD_TEST_FILE);
      IdTCPClient1.IOHandler.ReadStream(Strm, -1, False);
    finally
      Strm.Free;
    end;
  except
    DeleteFile(FileName);
    raise;
  end;
  Memo1.Lines.Add('ok');
end;