【问题标题】:Delphi: Create and use socket outside main threadDelphi:在主线程外创建和使用套接字
【发布时间】:2012-01-12 12:15:06
【问题描述】:

以下代码旨在通过线程内的套接字读取数据。

//main method from dll
function GetSocketData(const IP, Port: PChar): PChar; export; cdecl;
var
  Thread: TMyThread;
  DataIsRead: TEvent;
begin
  DataIsRead := TEvent.Create(nil, True, False, '');
  Thread:= TMyThread.Create(DataIsRead, IP, Port);
  DataIsRead.WaitFor(INFINITE);
  Result := BlockAlloc(Thread.ResultData);
  DataIsRead.Free;
  Thread.Free;
end;

TMyThreadBase = class(TThread)
protected
  FResultData: string;
public
  constructor Create;

  property ResultData: string read FResultData;
end;

constructor TMyThreadBase.Create;
begin
  inherited Create(False); // Suspended
  FResultData := '';
  FreeOnTerminate := False;
end;

TMyThread = class(TMyThreadBase)
private
  FMyData: TMyData;
  FSocketCom: TSocketCom;
  //other params
protected
  procedure Connect(Sender: TObject);
  procedure Execute; override;
public
  constructor Create(DataIsRead: TEvent; const IP, Port: PChar);
  destructor Destroy; override;
end;

constructor TMyThread.Create(const IP, Port: PChar);
begin
  inherited Create;
  /init params/

  CoInitialize(nil);
  FSocketCom := ComCreate(FPort, FIP);
  FSocketCom.OnConnect := Connect;//Connect method sends the special command to the port {`ClientSckt.Socket.SendBuf(B[0], Count)`}
  FSocketCom.Reopen;

  FMyData := TMyData.Create(DataIsRead, FSocketCom);//class used for received data interpretation
  //DataIsRead event is being set when all data is interpreted
  FSocketCom.SetRxFunc(FMyData.NCData);//set method for data interpretation
  FMyData.InitData(...);//init values needed while data is being interpreted
end;

destructor TMyThread.Destroy;
begin
  CoUninitialize;
  inherited;
end;

procedure TMyThread.Execute;
begin
  inherited;
  while not Terminated do
    Sleep(100);
  //that is the place where I do not know what to do to wait while OnRead event is fired.
end;

TSocketCom = class(TCustomCom)
private
  ClientSckt: TClientSocket;

  procedure SocketConnect(Sender: TObject; Socket: TCustomWinSocket);
  procedure SocketRead(Sender: TObject; Socket: TCustomWinSocket);
protected
  procedure SetThread; override;
public
  constructor Create;
  destructor  Destroy; override;
  function Open:Boolean;  override;
  function Read( Buf:PAnsiChar; Size:Integer; Wait:Integer = 0 ):Integer;  override;
end;

procedure TCustomCom.SetRxFunc(OnRxData: TRxDataEvent);
begin
  ...
    SetThread;
  ...
end;

function TSocketCom.Open:boolean;
var
  i,j:integer;
begin
  ...
  ClientSckt:=TClientSocket.Create(nil);
  ClientSckt.ClientType:=ctBlocking;
  ClientSckt.HostAndAddress:='127.0.0.0';
  ClientSckt.Port:=1234;
  ClientSckt.OnConnect:=SocketConnect;
  ClientSckt.Open;
  ...
end;

function TSocketCom.Read(Buf:PAnsiChar;Size:Integer; Wait:Integer):Integer;
begin
  if Opened then
    Result:=ClientSckt.Socket.ReceiveBuf(Buf^,Size);
  if Result<0 then Result:=0;
end;

procedure TSocketCom.SetThread;
begin
  inherited;
  ClientSckt.OnRead:=SocketRead;
end;

问题:OnRead 事件没有被触发,尽管它接缝了所有需要的实例都在线程中创建。建立连接并发送命令。

【问题讨论】:

  • 如果您想等待线程,您应该使用Terminate 将其标记为已完成并简单地调用WaitFor 而不是while 循环。
  • 好的。这将解决持有主线程的问题。但是如何强制在该线程中读取套接字?正如我所看到的,只有从执行中调用的方法才能在线程中运行。但是套接字读取发生在调用其 Read 方法时。我不能在 Execute 中调用它。
  • 为什么不能在Execute中调用呢?
  • 因为在服务器发送数据时,ClientSocket 的 OnRead 事件会调用它。
  • 动态加载您的 onRead 事件 - 请参阅我的另一篇文章。如果您想在线程中有效地使用 TClientSocket,最好尽量避免在表单上使用组件并依赖消息来触发事件 - 只需在线程构造函数的代码中或在“执行”方法的顶部构造一个 TClientSocket 套接字。

标签: multithreading delphi sockets


【解决方案1】:

这种代码是对线程的误用(和FreeOnTerminate属性的误用),因为调用代码被阻塞等待线程终止,所以一开始就没有理由有线程.

话虽如此,默认情况下TClientSocket 在非阻塞模式下运行,它在内部使用窗口消息来触发套接字事件。激活套接字的线程需要有一个消息循环,这样才能正确接收和分派这些套接字通知。否则,正如 Martin 所说,您必须在阻塞模式下使用套接字。

更新:

您显示的更新代码在多个级别上都是完全错误的。线程都是错误的。 TClientSocket 的用法都是错误的。鉴于您的 GetSocketData() 函数的阻塞性质,根本不需要在内部使用任何线程(特别是因为您声明 GetSocketData() 本身是在线程中调用的,所以额外的线程是多余的),你应该根本不用担心TClientSocket 事件,尤其是OnRead 事件,因为它根本不会在阻塞模式下被调用(这是你问题的根源!)。

您使您的代码比它需要的复杂得多。改用类似的东西:

function GetSocketData(const IP, Port: PChar): PChar; export; cdecl; 
var 
  ClientSckt: TClientSocket; 
  //other params 
begin 
  Result := nil;
  try
    /init params/ 

    CoInitialize(nil); 
    try
      ClientSckt := TClientSocket.Create(nil); 
      try
        ClientSckt.ClientType := ctBlocking; 
        ClientSckt.HostAndAddress := IP; 
        ClientSckt.Port := Port; 
        ClientSckt.Open; 
        try
          // send the special command to the port from here
          // read all data and interpret from here
          Result := BlockAlloc(...); 
        finally
          ClientSckt.Close;
        end;
      finally
        ClientSckt.Free;
      end;
    finally
      CoUninitialize; 
    end;
  except
  end;
end; 

【讨论】:

  • 我同意 99% - 可能需要这种“数据接收的完整封装”,可能与从服务器返回数据的 DLL 调用有关,并且具有最低的“支持”,或者也许稍后可能会从没有消息循环的其他线程调用 DLL(正如您所指出的,这在非阻塞模式下不起作用)。
  • 嗯。调用代码被阻止,但假设这个应用程序有一个 UI,不会以这种方式使用它允许 UI 在请求进行时更新,而没有线程就不会发生?当应用程序在网络查找期间冻结时,我讨厌它。
  • @eis - OP 建议他/她从主线程调用它,并希望等到线程检索到数据。这将阻塞主线程并阻止消息处理,直到线程发出完成信号。 Remy 可能是正确的,因为 OP 要求的不是他/她想要的。当然,几十年来我已经发布了无数次,在 UI 线程事件处理程序中等待来自另一个线程的结果是一个非常糟糕的主意,但许多开发人员仍然坚持“调用线程并等待返回”。
  • 如果阻塞函数在主线程的上下文中被调用,那么它必须泵送主消息队列以保持 UI 响应(TThread.WaitFor() 在内部执行此操作)。如果您摆脱线程并直接在阻塞函数中以非阻塞模式使用套接字,该函数仍然必须无论如何都必须泵送消息(这次是手动),但这些消息将包括套接字通知。
  • 谢谢。只是在检查没有什么我没有到达这里。
【解决方案2】:

如果您希望所有客户端 服务器通信都发生在此函数和线程 MyThreadForReading 方法中,最简单的方法是在线程构造函数中(或在 Execute 方法的顶部)构造 TClientSocket 的实例。将 PChar、hostAddr、端口等作为构造函数参数传递。在构造函数/执行中,使用参数、onRead 事件加载 TClientSocket,并将 clientType 设置为“ctBlocking”。在 Execute 中,连接并在循环中将数据读入 PChar,直到它全部进入。当它进入时,您可以选择通知主线程 PChar 缓冲区“已满”。我会 PostMessage() 触发主线程消息处理程序,但如果您需要主线程等待,请按照 David 的建议使用 WaitFor()。

【讨论】:

  • PS - 不要忘记在退出线程之前关闭并释放 TClientSocket。
猜你喜欢
  • 2020-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-05
  • 2013-05-15
  • 1970-01-01
  • 2015-04-26
相关资源
最近更新 更多