2012-01-12 2 views
2

Код, приведенный ниже, предназначен для считывания данных через сокет в потоке.Delphi: создание и использование сокета вне основной темы

//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 не запускается, хотя в нем есть все необходимые экземпляры. Выполняется соединение, и команда отправляется.

+1

Если вы хотите ждать поток, вы должны использовать 'Terminate', чтобы пометить его, как это сделано и просто вызвать' WaitFor' вместо того, чтобы 'while' цикла. –

+0

ОК. Это устранит проблему с удержанием основного потока. Но как я могу заставить сокет читать в этом потоке? Как я видел, только вызовы метода из execute запускаются с потоком. Но чтение сокетов происходит при вызове метода Read. Я не могу назвать это в Execute. – Yuriy

+0

Почему вы не можете позвонить в Execute? –

ответ

0

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

+0

PS - не забудьте закрыть и освободить TClientSocket перед выходом из потока. –

3

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

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

Update:

Обновленный код показали просто неправильно на нескольких уровнях. Потоки - все неправильно. Использование 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

Я согласен 99% - может возникнуть необходимость в этой «полной инкапсуляции приема данных», возможно, что-то связано с вызовом DLL, который возвращает данные с сервера с минимальной «поддержкой», или, возможно, позже может быть вызвана DLL из других потоков без контура сообщений (как вы указываете, это не будет работать в неблокирующем режиме). –

+0

Хм. Вызов кода заблокирован, но при условии, что у этого приложения есть пользовательский интерфейс, он не будет использовать его таким образом, чтобы пользовательский интерфейс мог обновляться во время выполнения запроса, тогда как без потока, которого не было бы? Я ненавижу, когда приложения замораживаются на время поиска в Интернете. – eis

+0

@eis - OP предлагает, чтобы он вызывал его из основного потока и хотел подождать, пока поток не восстановит данные. Это блокирует основной поток и предотвращает обработку сообщений до тех пор, пока поток не сообщит о его завершении. Реми, вероятно, верны в том, что то, о чем просит ОП, не то, что он хочет. Разумеется, в течение десятилетий я опубликовал миллионы раз, что ожидание в обработчике событий потока пользовательских интерфейсов для результатов из другого потока - действительно плохая идея, но многие разработчики все еще настаивают на «вызове потоков и ожидании возврата». –

Смежные вопросы