Indy 10 IdTCPClient Чтение данных с использованием отдельного потока?

Вопрос: То, что я ищу, - это наиболее типичный или лучший способ использования отдельного потока для получения данных с использованием 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;

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

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

2 ответа

Решение

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

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;

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

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

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

Другие вопросы по тегам