Indy 10 IdTCPClient Чтение данных, используя отдельный поток? - PullRequest
5 голосов
/ 16 февраля 2009

Вопрос: Я ищу наиболее типичный или лучший метод способ использования отдельного потока для получения данных с использованием IdTCPClient в Indy 10.

Справочная информация: Приведенный ниже код является примером того, что я пытаюсь сделать с фактическими частями обработки данных, удаленными для ясности. Идея потока состоит в том, чтобы получить все данные (переменный размер с заголовком, определяющим остальную часть длины сообщения), а затем проанализировать их (это то, что делает процедура HandleData) и запустить обработчик событий в зависимости от команды.

TIdIOHandlerSocket передается в поток основным приложением, которое также записывает данные в сокет по мере необходимости.

TScktReceiveThread = class(TThread)
  private
    { Private declarations }
    procedure HandleData;
  protected
    procedure Execute; override;
  public
    FSocket: TIdIOHandlerSocket;
    constructor Create(CreateSuspended: boolean);
  end;


procedure TScktReceiveThread.Execute;
var
  FixedHeader: TBytes;
begin
  Assert(FSocket <> nil, 'You must assign the connected socket to the receiving thread');
  SetLength(FixedHeader, 2);
   while not Terminated do
    begin
      if not FSocket.Connected then
        Suspend
      else
        begin
          FSocket.CheckForDataOnSource(10);
          if not FSocket.InputBufferIsEmpty then
           begin
            FSocket.ReadBytes(FixedHeader, SizeOf(FixedHeader), false);
            // Removed the rest of the reading and parsing code for clarity
            Synchronize(HandleData);
           end;
        end;
    end;
end;

В качестве префикса я использовал другой вопрос StackOverflow, который касается серверных компонентов Indy: « Delphi 2009, Indy 10, TIdTCPServer.OnExecute, как захватить все байты в InputBuffer » для получить основание того, что я имею до сих пор.

Спасибо за любую помощь!

Ответы [ 2 ]

8 голосов
/ 17 февраля 2009

Если вы хотите избежать накладных расходов, связанных с созданием классов потоков для каждого и каждого обмена данными между клиентом и сервером, вы можете создать класс подвижных потоков, как описано в

http://delphidicas.blogspot.com/2008/08/anonymous-methods-when-should-they-be.html

У меня была такая же проблема несколько дней назад, и я только что написал мне класс TMotileThreading, который имеет статические функции, которые позволяют мне создавать потоки, используя новую функцию анонимного метода в D2009. Выглядит примерно так:

type
  TExecuteFunc = reference to procedure;

  TMotileThreading = class
  public
    class procedure Execute (Func : TExecuteFunc);
    class procedure ExecuteThenCall (Func : TExecuteFunc; ThenFunc : TExecuteFunc);
  end;

Вторая процедура позволяет мне установить связь между клиентом и сервером, как в вашем случае, и выполнять некоторые действия при получении данных. Преимущество анонимных методов в том, что вы можете использовать локальные переменные вызывающего контекста. Таким образом, сообщение выглядит примерно так:

var
  NewData  : String;
begin
  TMotileThreading.ExecuteThenCall (
    procedure
    begin
      NewData := IdTCPClient.IOHandler.Readln;
    end,
    procedure
    begin
      GUIUpdate (NewData);
    end);
 end;

Метод Execute и ExecuteThenCall просто создают рабочий поток, задают для FreeOnTerminate значение true, чтобы упростить управление памятью, и выполняют предоставленные функции в процедурах Execute и OnTerminate рабочего потока.

Надеюсь, это поможет.

РЕДАКТИРОВАТЬ (в соответствии с запросом полной реализации класса TMotileThreading)

type
  TExecuteFunc = reference to procedure;

  TMotileThreading = class
  protected
    constructor Create;
  public
    class procedure Execute (Func : TExecuteFunc);
    class procedure ExecuteAndCall (Func : TExecuteFunc; OnTerminateFunc : TExecuteFunc;
                                SyncTerminateFunc : Boolean = False);
  end;

  TMotile = class (TThread)
  private
    ExecFunc             : TExecuteFunc;
    TerminateHandler     : TExecuteFunc;
    SyncTerminateHandler : Boolean;
  public
    constructor Create (Func : TExecuteFunc); overload;
    constructor Create (Func : TExecuteFunc; OnTerminateFunc : TExecuteFunc;
                        SyncTerminateFunc : Boolean); overload;
    procedure OnTerminateHandler (Sender : TObject);
    procedure Execute; override;
  end;

implementation

constructor TMotileThreading.Create;
begin
  Assert (False, 'Class TMotileThreading shouldn''t be used as an instance');
end;

class procedure TMotileThreading.Execute (Func : TExecuteFunc);
begin
  TMotile.Create (Func);
end;

class procedure TMotileThreading.ExecuteAndCall (Func : TExecuteFunc;
                                                 OnTerminateFunc : TExecuteFunc;
                                                 SyncTerminateFunc : Boolean = False);
begin
  TMotile.Create (Func, OnTerminateFunc, SyncTerminateFunc);
end;

constructor TMotile.Create (Func : TExecuteFunc);
begin
  inherited Create (True);
  ExecFunc := Func;
  TerminateHandler := nil;
  FreeOnTerminate := True;
  Resume;
end;

constructor TMotile.Create (Func : TExecuteFunc; OnTerminateFunc : TExecuteFunc;
                            SyncTerminateFunc : Boolean);
begin
  inherited Create (True);
  ExecFunc := Func;
  TerminateHandler := OnTerminateFunc;
  SyncTerminateHandler := SyncTerminateFunc;
  OnTerminate := OnTerminateHandler;
  FreeOnTerminate := True;
  Resume;
end;

procedure TMotile.Execute;
begin
  ExecFunc;
end;

procedure TMotile.OnTerminateHandler (Sender : TObject);
begin
  if Assigned (TerminateHandler) then
    if SyncTerminateHandler then
      Synchronize (procedure
                   begin
                     TerminateHandler;
                   end)
    else
      TerminateHandler;
end;
6 голосов
/ 16 февраля 2009

Вы на правильном пути. Indy - это , предназначенный для такого использования. Он использует блокирующие сокеты , поэтому вызов ReadBytes не возвращается, пока не будет прочитано то, что вы просили. Сравните это с неблокирующими сокетами, где вызов может вернуться рано, поэтому вы либо опрашиваете, либо будете получать асинхронные уведомления, чтобы определить, когда запрос был выполнен.

Indy разработан с учетом того, что объекты сокетов имеют свои собственные потоки (или волокна). Indy поставляется с TIdAntifreeze для тех, кто хочет перетаскивать компоненты сокетов на свои формы и модули данных и использовать компоненты Indy из основного потока графического интерфейса, но это не очень хорошая идея, если вы можете избежать этого.

Поскольку ваш поток не может работать без присваивания FSocket, я советую вам просто получить это значение в конструкторе класса. Утвердите в конструкторе, если он не назначен. Кроме того, это ошибка , чтобы создать ваш поток не приостановленным, так зачем вообще давать эту опцию? (Если поток не создан приостановленным, он начнет работать, проверит, назначен ли FSocket, и завершится ошибкой, поскольку потоку создания еще не удалось назначить это поле.)

...