问题我正在寻找的是使用单独线程在Indy 10中使用IdTCPClient接收数据的最典型或最佳实践方式。
背景:下面的代码是我为了清楚起见而删除了实际数据处理部分的示例。线程的想法是接收所有数据(变量大小,标头声明消息长度的其余部分)然后解析它(这就是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, how to grab all the bytes in the InputBuffer”以获得我到目前为止的基础。
感谢您的帮助!
答案 0 :(得分:8)
如果您想避免为每个客户端 - 服务器数据交换创建线程类所带来的开销,您可以创建一个运动线程类,如
中所述。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;
答案 1 :(得分:6)
你走在正确的轨道上。 Indy 意图可以像这样使用。它使用阻塞套接字,因此ReadBytes
调用在读取您所要求的内容之前不会返回。与非阻塞套接字相比,调用可以提前返回,因此您可以轮询或异步通知以确定请求何时被填充。
Indy的设计期望套接字对象具有自己的线程(或光纤)。对于想要将套接字组件拖放到其表单和数据模块上并使用主GUI线程中的Indy组件的人来说,Indy附带TIdAntifreeze
,但如果你能避免它,那通常不是一个好主意。 / p>
由于你的线程在没有分配FSocket
的情况下无法工作,我建议你只需在类的构造函数中接收该值。如果未分配,则在构造函数中断言。此外,创建一个非挂起的线程是错误,那么为什么要提供选项呢? (如果线程未创建暂停,则它将开始运行,检查是否已分配FSocket
,并且因为创建线程尚未分配该字段而失败。)