1
votes

I use this code to send a file to a client.

procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
var
 hFile    : THandle;
 FileBuff : array [0..1023] of byte;
 dwRead   : DWORD;
 Recieved : String;
begin
 Recieved := Athread.Connection.ReadLn;
 if Recieved = 'FILE' then begin
   Memo1.Lines.Add('Sending File...');
   hFile := CreateFileW('FILE.bin',
   GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
   if hFile = INVALID_HANDLE_VALUE then exit;
   SetFilePointer(hFile, 0, nil, FILE_BEGIN);
   while true do begin
     ReadFile(hFile, FileBuff[0], 1024, dwRead, nil);
     if dwRead <= 0 then break;
     Athread.Connection.WriteBuffer(FileBuff[0], dwRead);
   end;
   CloseHandle (hFile);
   Athread.Connection.Disconnect;
 end;
end;

This works like a charm but if the client disconnects while the file is sending, Indy terminates the Execute Thread immediately so the filehandle is still open! Is there a way to close the filehandle after the client disconnects?

Thank you for your help.

1
Are you tried using try..finally..end?RRUZ

1 Answers

6
votes

Your code has three problems in it:

1) accessing the TMemo directly is not thread-safe, so it can cause deadlocks and/or crashed. UI access must be done in the context of the main thread only. You can use Indy's TIdSync or TIdNotify class to safely access the UI from server events.

2) like RRUZ mentioned, you are not protecting the file handle from exceptions. If an exception is raised, like when a client disconnects, you are not closing the file handle.

3) you are opening the file using a relative path. Always use absolute paths to ensure the correct file is used.

Try this:

procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread); 
var 
 hFile    : THandle; 
 FileBuff : array [0..1023] of byte; 
 dwRead   : DWORD; 
 Recieved : String; 
begin 
  Recieved := Athread.Connection.ReadLn; 
  if Recieved = 'FILE' then begin 
    // I'll leave this as an exercise for you to figure out...
    //Memo1.Lines.Add('Sending File...'); 

    hFile := CreateFile('C:\Path\FILE.bin', GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0); 
    if hFile = INVALID_HANDLE_VALUE then RaiseLastOSError;
    try
      repeat 
        if not ReadFile(hFile, FileBuff[0], SizeOf(FileBuff), dwRead, nil) then RaiseLastOSError;
        if dwRead = 0 then Break; 
        AThread.Connection.WriteBuffer(FileBuff[0], dwRead);
      until False;
    finally
      CloseHandle(hFile); 
    end;
    AThread.Connection.Disconnect; 
  end; 
end; 

Alternatively, you can pass the filename to Indy and let it manage the file for you:

procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread); 
var 
 Recieved : String; 
begin 
  Recieved := Athread.Connection.ReadLn; 
  if Recieved = 'FILE' then begin 
    // I'll leave this as an exercise for you to figure out...
    //Memo1.Lines.Add('Sending File...'); 

    AThread.Connection.WriteFile('C:\Path\FILE.bin', True); 
    AThread.Connection.Disconnect; 
  end; 
end;