Multithreading Delphi:在主线程外创建和使用套接字

Multithreading Delphi:在主线程外创建和使用套接字,multithreading,delphi,sockets,Multithreading,Delphi,Sockets,下面的代码旨在通过线程内的套接字读取数据 //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(Dat

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

//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;
//dll中的main方法
函数GetSocketData(常量IP,端口:PChar):PChar;出口;cdecl;
变量
线程:TMyThread;
DataIsRead:TEvent;
开始
DataIsRead:=TEvent.Create(nil,True,False,”);
线程:=TMyThread.Create(数据读取、IP、端口);
DataIsRead.WaitFor(无限);
结果:=BlockAlloc(Thread.ResultData);
数据读取免费;
线程。免费;
结束;
TMyThreadBase=class(TThread)
受保护的
FResultData:字符串;
公众的
构造函数创建;
属性ResultData:字符串读取数据;
结束;
构造函数TMyThreadBase.Create;
开始
继承的创建(False);//暂停的
FResultData:='';
FreeOnTerminate:=False;
结束;
TMyThread=class(TMyThreadBase)
私有的
FMyData:TMyData;
FSocketCom:TSocketCom;
//其他参数
受保护的
程序连接(发送方:TObject);
程序执行;推翻
公众的
构造函数创建(DataIsRead:TEvent;const-IP,端口:PChar);
毁灭者毁灭;推翻
结束;
构造函数TMyThread.Create(常量IP,端口:PChar);
开始
继承创造;
/初始化参数/
共初始化(零);
FSocketCom:=ComCreate(FPort,FIP);
FSocketCom.OnConnect:=连接//Connect方法将特殊命令发送到端口{`ClientSckt.Socket.SendBuf(B[0],Count)`}
重新开放;
FMyData:=TMyData.Create(DataIsRead,FSocketCom)//用于接收数据解释的类
//解释所有数据时设置DataIsRead事件
FSocketCom.SetRxFunc(FMyData.NCData)//数据解释的集合方法
FMyData.InitData(…)//解释数据时需要初始值
结束;
析构函数TMyThread.Destroy;
开始
协商初始化;
继承;
结束;
程序TMyThread.Execute;
开始
继承;
虽然没有终止
睡眠(100);
//在触发OnRead事件时,我不知道要等待什么。
结束;
TSocketCom=class(TCustomCom)
私有的
ClientSckt:TClientSocket;
过程SocketConnect(发送方:TObject;套接字:TCustomWinSocket);
过程SocketRead(发送方:TObject;套接字:TCustomWinSocket);
受保护的
程序设置线程;推翻
公众的
构造函数创建;
毁灭者毁灭;推翻
函数开:布尔;推翻
函数读取(Buf:PAnsiChar;大小:整数;等待:整数=0):整数;推翻
结束;
过程TCustomCom.SetRxFunc(OnRxData:TRxDataEvent);
开始
...
设置线程;
...
结束;
函数TSocketCom.Open:boolean;
变量
i、 j:整数;
开始
...
ClientSckt:=TClientSocket.Create(nil);
ClientSckt.ClientType:=ctBlocking;
ClientSckt.hostanddress:=“127.0.0.0”;
ClientSckt.Port:=1234;
ClientSckt.OnConnect:=SocketConnect;
ClientSckt.Open;
...
结束;
函数TSocketCom.Read(Buf:PAnsiChar;Size:Integer;Wait:Integer):整数;
开始
如果开放的话
结果:=ClientSckt.Socket.ReceiveBuf(Buf^,大小);

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

这种代码是对线程的误用(以及对
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; 

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