Indy 10 IdTCPClient Считывает данные с помощью отдельного потока?
-
23-08-2019 - |
Вопрос
Вопрос: То, что я ищу, это наиболее типичная или наилучшая практика способ использования отдельного потока для получения данных с использованием 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, как захватить все байты во входном буфере" чтобы получить основу для того, что у меня есть на данный момент.
Спасибо за любую помощь!
Решение
Если вы хотите избежать накладных расходов, связанных с созданием классов потоков для каждого обмена данными клиент-сервер, вы могли бы создать класс подвижных потоков, как описано в
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;
Другие советы
Вы на правильном пути.Инди - это предназначенный чтобы тебя вот так использовали.Он использует блокирующие розетки, так что ReadBytes
вызов не перезвонит до тех пор, пока не будет прочитано то, что вы запросили.Сравните это с неблокирующими сокетами, где вызов может вернуться раньше, поэтому вы либо проводите опрос, либо получаете уведомление асинхронно, чтобы определить, когда запрос был выполнен.
Indy разработан с расчетом на то, что объекты сокета будут иметь свои собственные потоки (или волокна).Инди поставляется с TIdAntifreeze
для людей, которые хотят перетаскивать компоненты сокетов в свои формы и модули данных и использовать компоненты Indy из основного потока GUI, но обычно это не очень хорошая идея, если вы можете этого избежать.
Поскольку ваш поток не может работать без FSocket
будучи назначенным, я советую вам просто получить это значение в конструкторе класса.Подтвердите в конструкторе, если он не назначен.Кроме того, это ошибка чтобы создать свой поток без приостановки, так зачем вообще давать такую возможность?(Если созданный поток не приостановлен, то он начнет выполняться, проверьте, является ли FSocket
назначается и завершается неудачей, потому что создающий поток еще не получил возможность назначить это поле.)