Search code examples
delphi-7tcpclientindytcpserver

Send file via IdTCPServer upon request of client


I am trying to send a text-file via the TCP-server if a client request is performed. The examples on internet are either outdated or not useful. I know I should use a stream but I do not understand how to do it.

I adapted a code I found on the internet but I am stuck with regards to the stream. Can someone guide me in the right direction?

The server:

TForm2.bStartClick(Sender: TObject);
begin
  if not IdTCPServer1.Active then
  begin
    IdTCPServer1.Active := True;
    Log('Server started on port ' + IntToStr(IdTCPServer1.DefaultPort));
    bStart.Enabled := False;
    bStop.Enabled := True;
  end;
end;

procedure TForm2.bStopClick(Sender: TObject);
begin
  if IdTCPServer1.Active then
  begin
    IdTCPServer1.Active := False;
    Log('Server stopped');
    bStop.Enabled := False;
    bStart.Enabled := True;
  end;
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  bStart.Enabled := True;
  bStop.Enabled := False;
end;

procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
var
  s: string;   FS:TFileStream;
begin

  if s ='1' then
  begin
    FS := TFileStream.Create(MyPath, fmOpenShare);
    try
      AContext.Connection.WriteStream(FS);
    finally
      FS.Free;
    end;
  end;

end;

procedure TForm2.Log(const s: string);
begin
  mLog.Lines.Add(s);
end

The client:

procedure TForm1.bConnectClick(Sender: TObject);
begin
  IdTCPClient1.Host := eServer.Text;
  IdTCPClient1.Port := StrToInt(ePort.Text);
  IdTCPClient1.Connect;

  if IdTCPClient1.Connected then
  begin
    Log('Connected to ' + IdTCPClient1.Host + ':' +
      IntToStr(IdTCPClient1.Port));
    bConnect.Enabled := False;
    bDisconnect.Enabled := True;
    bSend.Enabled := True;
  end;
end;

procedure TForm1.bDisconnectClick(Sender: TObject);
begin
  IdTCPClient1.Disconnect;
  bDisconnect.Enabled := False;
  bConnect.Enabled := True;
  bSend.Enabled := False;
end;

procedure TForm1.bSendClick(Sender: TObject);
var
  FS: TFileStream;
begin
//  IdTCPClient1.IOHandler.WriteLn(eTextToSend.Text);

  FS := TFileStream.Create('C:\Users\xxx\Desktop\test.txt', fmCreate);
  try
    IdTCPClient1.ReadStream(FS);
  finally
    Fs.Free;
  end;


end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  bDisconnect.Enabled := False;
  bSend.Enabled := False;
end;

procedure TForm1.Log(const s: string);
begin
  mLog.Lines.Add(s);
end;

Solution

  • Your client is not sending any command to the server, and the server is not waiting for any command before sending a file.

    But, besides that, your code doesn't compile anyway, as TIdTCPConnection does not have WriteStream() and ReadStream() methods. The correct methods are TIdIOHandler.Write(TStream) and TIdIOHandler.ReadStream().

    But even then, your calls to write a stream and read a stream are mismatched, because Write(TStream) does not send the stream size to the peer by default, but ReadStream() does expect to receive a stream size by default.

    Try something more like this instead:

    The server:

    procedure TForm2.IdTCPServer1Execute(AContext: TIdContext);
    var
      s: string;
      FS: TFileStream;
    begin
      s := AContext.Connection.IOHandler.ReadLn;
      if s = 'SENDFILE' then
      begin
        try
          FS := TFileStream.Create(MyPath, fmOpenRead or fmShareDenyWrite);
        except
          AContext.Connection.IOHandler.WriteLn('500 Cannot open file');
          Exit;
        end;
        try
          AContext.Connection.IOHandler.WriteLn('200 Sending file');
          AContext.Connection.IOHandler.LargeStream := True;
          AContext.Connection.IOHandler.Write(FS, 0, True);
        finally
          FS.Free;
        end;
      end
      else    
        AContext.Connection.IOHandler.WriteLn('500 Unknown command');
    end;
    

    The client:

    procedure TForm1.bSendClick(Sender: TObject);
    var
      FS: TFileStream;
    begin
      FS := TFileStream.Create('C:\Users\xxx\Desktop\test.txt', fmCreate);
      try
        try
          IdTCPClient1.IOHandler.SendCmd('SENDFILE', 200);
          IdTCPClient1.IOHandler.LargeStream := True;
          IdTCPClient1.IOHandler.ReadStream(FS, -1, False);
        finally
          FS.Free;
        end;
      except
        DeleteFile('C:\Users\xxx\Desktop\test.txt');
        raise;
      end;
    end;
    

    In which case, you might consider using TIdCmdTCPServer instead, then you can use its CommandHandlers collection to define your commands visually at design-time, and assign OnCommand event handlers to process them at runtime, eg:

    // OnCommand handler for 'SENDFILE' command
    procedure TForm2.IdCmdTCPServer1SENDFILECommand(ASender: TIdCommand);
    var
      FS: TFileStream;
    begin
      try
        FS := TFileStream.Create(MyPath, fmOpenRead or fmShareDenyWrite);
      except
        ASender.SetReply(500, 'Cannot open file');
        Exit;
      end;
      try
        ASender.SetReply(200, 'Sending file');
        ASender.SendReply;
        AContext.Connection.IOHandler.LargeStream := True;
        AContext.Connection.IOHandler.Write(FS, 0, True);
      finally
        FS.Free;
      end;
    end;