Indy 10. Как отправить несколько команд одновременно? - PullRequest
0 голосов
/ 04 ноября 2018

Скажите, что у меня неправильная идеология в этом коде.

Одной командой я отправляю и получаю ответ без проблем. Но если я хочу (точнее, необходимо отправить его по заданию) N команд и правильно получить все ответы.

Как правильно отправить одну за другой команду, ожидая, пока предыдущая будет полностью завершена.

Отправляя несколько команд, теряя соединение, но иногда пропуская, измените CheckForDataOnSource (100); - не помогает.

Сегодня у меня есть примерно следующий код:

ButtonClick(Sender: TObject);
var
  MyServ: TMyServer;
begin
  ...
  MyServ.SendCommand('command 1');
  ...
  MyServ.SendCommand('command N');
end;

unit MyServer;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdContext, IdBaseComponent, IdComponent,
  IdCustomTCPServer, IdTCPServer, Vcl.StdCtrls, IdGlobal,
  XMLDoc, XMLDOM, XMLIntf, ActiveX, Vcl.ExtCtrls, Vcl.ComCtrls, Base64, IdSync, IdCmdTCPServer,
  IdYarn, IdTCPConnection, IdThreadSafe;

type
  TMyServer = class(TIdTCPServer)
  private
    FEndResponse: string;
    FTotalStr: string;
    procedure Execute(AContext: TIdContext);
    //procedure ParseRecv(RecvText: string);
  public
    procedure Run(BindIP: string; BindPort: Integer);
    procedure Stop;
    procedure SendCommand(Cmd: string);
    constructor Create(AOwner: TComponent);
    destructor Destroy; override;
  end;

  TMySync = class(TIdSync)
  protected
    procedure DoSynchronize; override;
  public
    Data: string;
    Server: TMyServer;
  end;

  TMyContext = class(TIdServerContext)
  public
    Queue: TIdThreadSafeStringList;
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
    destructor Destroy; override;
  end;

var
  FReady: Boolean = True;

implementation

uses Main;

procedure TMySync.DoSynchronize;
begin
  //Server.ParseRecv(Data);
  MainForm.Memo1.Lines.Add(Data);
end;

constructor TMyServer.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ContextClass := TMyContext;
  OnExecute := Execute;
  FEndResponse := '</response>';
end;

destructor TMyServer.Destroy;
begin
  Stop;
  inherited Destroy;
end;

constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
  inherited Create(AConnection, AYarn, AList);
  Queue := TIdThreadSafeStringList.Create;
end;

destructor TMyContext.Destroy;
begin
  Queue.Free;
  inherited Destroy;
end;


procedure TMyServer.Run(BindIP: string; BindPort: Integer);
begin
  DefaultPort := 0;
  Bindings.Clear;
  with Bindings.Add do begin
    IP := BindIP;
    Port := BindPort;
  end;
  Active := True;
end;

procedure TMyServer.Stop;
begin
  Active := False;
end;

procedure TMyServer.SendCommand(Cmd: string);
var
  List: TList;
  Ctx: TMyContext;
begin
  List := Contexts.LockList;
  try
    //Connection Server <-> Client once one to one
    Ctx := TMyContext(List[0]);
    Ctx.Queue.Add(Cmd);
  finally
    Contexts.UnlockList;
  end;
end;

procedure TMyServer.Execute(AContext: TIdContext);
var
  Buffer: TIdBytes;
  Size, RecvSizeLen: Integer;
  RecvStr: string;
  Sync: TMySync;
  Ctx: TMyContext;
  Queue: TStringList;
begin
  Buffer := nil;

  with AContext.Connection.IOHandler do begin
    CheckForDataOnSource(100);
    if not InputBufferIsEmpty then begin
      Size := StrToInt(ReadLn());
      InputBuffer.ExtractToBytes(Buffer);
      SetString(RecvStr, PAnsiChar(@Buffer[0]), Size);

      FTotalStr := FTotalStr + RecvStr;
      if ((Copy(FTotalStr, Length(FTotalStr) - Length(FEndResponse), Length(FEndResponse))) = FEndResponse)
      then begin
        if Length(FTotalStr) > 0 then begin
          Sync := TMySync.Create;
          try
            Sync.Server := Self;
            Sync.Data := FTotalStr;
            Sync.Synchronize;
          finally
            Sync.Free;
          end;
        end;
        FTotalStr := '';
      end;
    end else begin
      Ctx := TMyContext(AContext);
      Queue := Ctx.Queue.Lock;
      //????????????????????????????????
      while (Queue.Count > 0) do begin
        try
          Ctx.Connection.IOHandler.Write(Queue[0] + #0);
          Queue.Delete(0);
        finally
          Ctx.Queue.Unlock;
        end;
      end;
    end;
  end;
end;

end.

1 Ответ

0 голосов
/ 04 ноября 2018

Я вижу ряд проблем с вашим Execute кодом.

  • при чтении ответа вы сначала считываете размер из соединения, но затем игнорируете этот размер при вызове ExtractToBytes(), поэтому он возвращает все доступные байты, а затем вы используете размер для копирования части Из извлеченных байтов в строку и выбросить остальные байты. Так что эта логика потенциально отбрасывает байты из вашего протокола и портит ваши коммуникации. Вам вообще не следует использовать ExtractToBytes(), например, вместо этого используйте метод ReadBytes() IOHandler или, возможно, ReadString(), в зависимости от используемой версии Delphi и характера читаемых вами строк.

  • при проверке очереди на отправку данных вы блокируете очередь, но затем не разблокируете ее, если она пуста. И если он не пустой, вы разблокируете его в конце каждой итерации цикла, не блокируя его сначала. Ваш try..finally должен быть перемещен за пределы цикла while.

Попробуйте вместо этого:

// if you are using D2009+, add this...
uses
 ..., System.AnsiStrings;

procedure TMyServer.Execute(AContext: TIdContext);
var
  IO: TIdIOHandler;
  Buffer: TIdBytes;
  Size: Integer;
  RecvStr: AnsiString;
  Sync: TMySync;
  Ctx: TMyContext;
  Queue: TStringList;
begin
  IO := AContext.Connection.IOHandler;
  if IO.InputBufferIsEmpty then
  begin
    IO.CheckForDataOnSource(100);
    IO.CheckForDisconnect;
  end;
  if not IO.InputBufferIsEmpty then
  begin
    Size := StrToInt(IO.ReadLn());

    // if using D2007 or earlier...
    RecvStr := IO.ReadString(Size);

    // if using D2009 or later...
    IO.ReadBytes(Buffer, Size, False);
    SetString(RecvStr, PAnsiChar(Pointer(Buffer)), Size);

    FTotalStr := FTotalStr + RecvStr;
    if AnsiEndsStr(FEndResponse, FTotalStr) then
    begin
      Sync := TMySync.Create;
      try
        Sync.Server := Self;
        Sync.Data := FTotalStr;
        Sync.Synchronize;
      finally
        Sync.Free;
      end;
      FTotalStr := '';
    end;
  end else
  begin
    Ctx := TMyContext(AContext);
    Queue := Ctx.Queue.Lock;
    try
      while (Queue.Count > 0) do
      begin
        IO.Write(Queue[0] + #0);
        Queue.Delete(0);
      end;
    finally
      Ctx.Queue.Unlock;
    end;
  end;
end;
...