Я только начал "играть" с Инди. Использовал этот пост , как отправить файл с сервера на клиент, используя 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 является глобальной переменной, и изменяется в фоновом потоке передачи. Но так как одновременно работает только один поток, я подумал, что это должно быть безопасно.