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

时间:2012-01-12 12:15:07

标签: 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(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事件未被触发,虽然它接缝所有需要的实例都是在线程中创建的。建立连接并发送命令。

2 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:0)

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