Delphi: создание и использование сокета вне основного потока - PullRequest
2 голосов
/ 12 января 2012

Код ниже предназначен для чтения данных через сокет в потоке.

//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 ]

3 голосов
/ 13 января 2012

Этот тип кода является неправильным использованием потока (и неправильным использованием свойства FreeOnTerminate), поскольку вызывающий код блокируется в ожидании завершения потока, поэтому нет никакой причины иметь поток на первом месте..

С учетом сказанного по умолчанию TClientSocket работает в неблокирующем режиме, который использует оконные сообщения для запуска событий сокета.Поток, который активирует сокет, должен иметь цикл сообщений, чтобы эти уведомления сокета могли быть получены и отправлены правильно.В противном случае вам придется использовать сокет в режиме блокировки, как сказал Мартин.

Обновление:

Показанный вами обновленный код просто неверен на нескольких уровнях,Поток все неправильно.Использование 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; 
0 голосов
/ 12 января 2012

Если вы хотите, чтобы все взаимодействие клиента <> с сервером происходило внутри этой функции и методов потока MyThreadForReading, самый простой способ - создать экземпляр TClientSocket в конструкторе потока (или в верхней части метода Execute).Передайте PChar, hostAddr, порт и т. Д. В качестве параметров конструктора.В конструкторе / execute загрузите TClientSocket с параметрами, событием onRead и установите для clientType значение «ctBlocking».В разделе Выполнить подключитесь и считайте свои данные в PChar в цикле, пока он не будет полностью подключен. Когда он будет включен, у вас есть несколько вариантов уведомления основного потока о том, что буфер PChar заполнен.Я хотел бы PostMessage () что-то, чтобы запустить обработчик сообщений основного потока, но если вам нужен ваш основной поток для ожидания, тогда используйте WaitFor (), как предложено Дэвидом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...