Indy 10 IdTCPClient 使用单独的线程读取数据?

发布于 2024-07-13 08:01:16 字数 1509 浏览 6 评论 0 原文

问题:我正在寻找的是在 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;

作为前缀,我使用了另一个处理 Indy 服务器组件的 StackOverflow 问题:“Delphi 2009,Indy 10,TIdTCPServer.OnExecute,如何抓取InputBuffer中的所有字节”以获得我所拥有的基础迄今为止。

谢谢你的帮助!

Question: What I'm looking for is the most typical or best practice way to use a separate thread to receive data using an IdTCPClient in Indy 10.

Background: The below code is a sample of what I'm trying to do with the actual data processing parts removed for clarity. The Idea of the Thread is to receive all data (Variable size with a header declaring the rest of the message length) and to then to parse it (That's what the HandleData procedure does) and trigger an Event Handler depending on the command.

The TIdIOHandlerSocket is passed to the thread by the main application which also Writes data to the socket as and when it is required.

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;

As a prefix, I have used another StackOverflow question which deals with the server components of Indy: "Delphi 2009, Indy 10, TIdTCPServer.OnExecute, how to grab all the bytes in the InputBuffer" to get the basis of what I have so far.

Thanks for any help!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

淤浪 2024-07-20 08:01:16

如果您想避免为每个客户端-服务器数据交换创建线程类所带来的开销,您可以创建一个移动线程类,如

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;

If you want to avoid the overhead imposed by creating thread classes for each and every client-server data exchange, you could create a motile threading class as described in

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

I had the same problem a few days ago and I just wrote me a class TMotileThreading which has static functions that let me create threads using the new anonymous method feature of D2009. Looks something like this:

type
  TExecuteFunc = reference to procedure;

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

The second procedure allows me to perform a client-server communication like in your case and do some stuff whenever the data has arrived. The nice thing about anonymous methods is that you can use the local variables of the calling context. So a communication looks something like this:

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

The Execute and ExecuteThenCall method simply create a worker thread, set FreeOnTerminate to true to simplify memory management and execute the provided functions in the worker thread's Execute and OnTerminate procedures.

Hope that helps.

EDIT (as requested the full implementation of class 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;
蓝梦月影 2024-07-20 08:01:16

你走在正确的轨道上。 Indy 就是打算这样使用的。 它使用阻塞套接字,因此ReadBytes调用在读取您所请求的内容之前不会返回。 与非阻塞套接字相比,在非阻塞套接字中,调用可能会提前返回,因此您可以异步轮询或收到通知来确定请求何时已被填充。

Indy 的设计预期套接字对象拥有自己的线程(或纤程)。 Indy 附带了 TIdAntifreeze,适合那些想要将套接字组件拖放到其表单和数据模块上并从主 GUI 线程使用 Indy 组件的人们,但如果您可以避免的话,这通常不是一个好主意它。

由于如果没有分配FSocket,您的线程就无法工作,因此我建议您只需在类的构造函数中接收该值即可。 如果未分配,则在构造函数中断言。 此外,创建非挂起线程是一个错误,那么为什么还要给出这个选项呢? (如果创建的线程没有挂起,那么它将开始运行,检查FSocket是否已分配,并失败,因为创建线程尚未分配该字段。)

You're on the right track. Indy is intended to be used like that. It uses blocking sockets, so the ReadBytes call doesn't return until it's read what you've asked for. Contrast that with non-blocking sockets, where a call may return early, so you either poll or get notified asynchronously to determine when a request has been filled.

Indy is designed with the expectation that the socket objects have their own threads (or fibers). Indy comes with TIdAntifreeze for the folks who want to drag and drop socket components onto their forms and data modules and use the Indy components from the main GUI thread, but that's not generally a good idea if you can avoid it.

Since your thread cannot work without FSocket being assigned, I advise you to simply receive that value in the class's constructor. Assert in the constructor if it's not assigned. Furthermore, it is an error to create your thread non-suspended, so why even give the option? (If the thread is not created suspended, then it will start running, check whether FSocket is assigned, and fail because the creating thread hasn't gotten to assign that field yet.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文