Модераторы: Poseidon, Snowy, bems, MetalFan
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Удерживать TThread постоянно, Как вынести сетевой компонент в поток? 
:(
    Опции темы
ЧеловекБорща
  Дата 14.8.2013, 09:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 94
Регистрация: 5.6.2010

Репутация: нет
Всего: нет



Доброго времени суток!

Собственно вопрос о TThread. 

Моя программа должна создавать и поддерживать соединение с устройством в сети постоянно и непрерывно. 
Как это можно реализовать?

Сейчас такого нет, и нормально работать можно только по-отдельности с каждым устройством, без единовременной синхронизации нескольких устройств сразу, чего и надо бы добиться в итоге.

Пока в голову приходит следующее:
 Создать TThread,  он существует вечно(сколько мне надо), внути потока находится сетевой компонент, как свойствой вынесенный, при запуске потока, через событие, передается эксемпляр сетевого компонента в класс, следящий за потоком. Все. Класс пользует экземпляр сетевого компонента, и никто, никому не мешает.


Как это можно реализовать? Может какие библиотеки для простой реализации такого есть, ил ещё что-нибудь? 




PM MAIL   Вверх
kami
Дата 14.8.2013, 09:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 23
Всего: 72



Цитата(ЧеловекБорща @  14.8.2013,  09:27 Найти цитируемый пост)
при запуске потока, через событие, передается эксемпляр сетевого компонента в класс, следящий за потоком

Неправильно, если в качестве сетевых компонентов рассматривать TServer|ClientSocket и подобные. Всё написанное далее относится именно к этой плеяде.
В данном контексте сетевой компонент должен создаваться в методе Execute потока, при этом в потоке должна быть организована выборка сообщений.


Цитата(ЧеловекБорща @  14.8.2013,  09:27 Найти цитируемый пост)
Класс пользует экземпляр сетевого компонента, и никто, никому не мешает.

Класс должен пользовать потокобезопасные методы потока, создавшего сетевой компонент. Методы и события.
А уже сам поток:
1. если нужно передать информацию в соединение: из метода в основном потоке переключается (допустим, через SendMessage) в "себя", и из "себя же" отдает данные сетевому компоненту.
2. Если данные поступили из соединения: из "своего" метода переключается в основной поток, и там вызывает событие приема, которое и ловит нужный компонент.

На счет пункта 1 сейчас не уверен, а смотреть немножко лень - возможно, сокеты и нормально воспримут это. Но для пущей безопасности лучше не рисковать.
А вот обработка "Классом, пользующим сетевой компонент" данных не в контексте своего потока чревато боком.

Как с этим делом обстоит у (к примеру) Indy - не знаю, не умею их готовить smile


А что в вашем понимании "постоянно и непрерывно"? Сетевой обмен - достаточно медленная операция для современных машин, так что несколько соединений элементарно сможет держать основной поток, без создания дополнительных.
PM MAIL WWW   Вверх
ЧеловекБорща
Дата 14.8.2013, 10:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 94
Регистрация: 5.6.2010

Репутация: нет
Всего: нет



Цитата

если в качестве сетевых компонентов рассматривать TServer|ClientSocket и подобные. Всё написанное далее относится именно к этой плеяде.

Именно они, а конкретно блокирующий сокет(Blocking-socket).

Цитата

Класс должен пользовать потокобезопасные методы потока, создавшего сетевой компонент. Методы и события.

Потокобезопасные события я умею делать.
А вот как делать методы?

События - результат из потока
Методы - данные для обработки в поток
Я правильно вас понял?


Цитата

из метода в основном потоке переключается


Что вы имеете ввиду под переключением? 

Цитата

А что в вашем понимании "постоянно и непрерывно"? Сетевой обмен - достаточно медленная операция для современных машин, так что несколько соединений элементарно сможет держать основной поток, без создания дополнительных. 


Моя программа реализует обмен данными с сетевыми маршрутизаторами(включая им подчиненных), сейчас все происходит в основном потоке, когда происходит запрос передачи/получения, все, включая GUI, зависает в этот момент. 

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

PM MAIL   Вверх
kami
Дата 14.8.2013, 13:37 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 23
Всего: 72



Цитата(ЧеловекБорща @  14.8.2013,  10:16 Найти цитируемый пост)
А вот как делать методы?

ну, например:
1. работа производится в контексте потока, вызвавшего метод
Код

procedure TConnection.ThreadSafeAddPacket(Packet: TOutPacketStream);
begin
  FCriticalSection.Enter;
  try
    if Assigned(FSocket) and Assigned(FOutDataList) then
      begin
        FOutDataList.Add(Packet);
        PostMessage(FSocket.Handle, CM_SOCKETMESSAGE, FSocket.SocketHandle, MakeLParam(FD_WRITE, 0));
      end
    else
      Packet.Free;
  finally
    FCriticalSection.Leave;
  end;
end;

Естественно, в этом примере все обращения к FOutDataList так же должны обрамляться критической секцией.

2. работа производится в контексте "своего" потока, для этого в нем заранее было создано окно:
Код

function TRouteCalculator.CalculateRouteX(Addresses: TAddresses; ForceCalculate: Boolean; Timeout: Cardinal;  var RouteResult: TRouteResult): Boolean;
begin
  Result := false;
  try
    Result := SendMessageTimeout(FHWND, msgCalcRoute, integer(Addresses), integer(@RouteResult), SMTO_BLOCK, Timeout, res) <> 0;
    if not Result then // если мы не дождались ответа в течение установленного времени
      begin
        FDelayTimer.Tag := 1; // прерываем вычисления
        while FDelayTimer.Enabled do // и дожидаемся, пока он действительно прервется.
          begin
            PostMessage(FHWND, WM_NULL, 0, 0);
            Sleep(0);
          end;
      end;
    Result := Result and (res = 0);
  except
    on e: Exception do
      begin
        Result := false;
        ClearResult(RouteResult);
      end;
  end;
end;


Второй пример не слишком удачен, но все же...

Цитата(ЧеловекБорща @  14.8.2013,  10:16 Найти цитируемый пост)
а конкретно блокирующий сокет(Blocking-socket).

Опс. Я умолкаю - не работаю с сокетами в этом режиме, посему - подсказать нечего. :(


Это сообщение отредактировал(а) kami - 14.8.2013, 13:38
PM MAIL WWW   Вверх
ЧеловекБорща
Дата 14.8.2013, 13:48 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 94
Регистрация: 5.6.2010

Репутация: нет
Всего: нет



Цитата

ну, например:
1. работа производится в контексте потока, вызвавшего метод

т.е. все переменные с префиксом F описаны в TThread? 

НО разве Крит. секция не аналог блок. сокету? Допустимя я загоняю партию из 50 запросов,
и каждый раз будет задержка т.к. осн. поток ждет пока крит. секция пустит следующий запрос в обработку.

Или я не прав?

 
Цитата

Опс. Я умолкаю - не работаю с сокетами в этом режиме, посему - подсказать нечего. :(

Сокеты для меня нечто новое. Я не вижу различия между блокирующим и не блокирующим. Если можете кратро, в 2х словах, рассказать различие, буду рад.
Для меня осн. проблема вот в чем:
Цитата

  когда происходит запрос передачи/получения, все, включая GUI, зависает в этот момент. 



PM MAIL   Вверх
ЧеловекБорща
Дата 15.8.2013, 11:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 94
Регистрация: 5.6.2010

Репутация: нет
Всего: нет



Паралельно с большим проектом, пишу маленьки. Программа проверки и контроля баланса аккунта от моего провайдера инета.
Реализовал вашу идею там:
Код

const
  TC_LOGIN    = 1; //Команда "Авторизовать пользователя
  TC_RECVDATA = 2; //Получить данные с ПУ
  TC_EXIT     = 3; //Завершить работу TThread

type
  TAltolanThread = class(TThread)
  private
    fCommand: DWORD;
    fName: string;
    fPassword: string;
    HTTP: THTTPSendEx;
    fCS: TCriticalSection;
    fEvent: TEvent;
    ....

  protected
    procedure Execute; override;
  public
    procedure Terminate; overload; //Мой метод завершения потока
    procedure Login(aName, aPwd: string);
    procedure RecvData(aUI: TUserRecvInfo);
   ...
    constructor Create; overload;
    destructor Destroy; override;
  end;

implementation

{ TAltolanThread }

constructor TAltolanThread.Create;
begin
  inherited Create(True);
  FreeOnTerminate := True;
  HTTP := THTTPSendEx.Create
    ('Opera/9.80 (Windows NT 6.2; WOW64) Presto/2.12.388 Version/12.16');
  fCS := TCriticalSection.Create; //Синхронизация VCL to TThread
  fEvent := TEvent.Create(nil, True, False, 'ABIThreadImpulse'); //Удержание TThread в бесконечном рабочем состоянии
end;

destructor TAltolanThread.Destroy;
begin
  FreeAndNil(fCS);
  FreeAndNil(fEvent);
  FreeAndNil(HTTP);
  inherited;
end;


procedure TAltolanThread.RecvData(aUI: TUserRecvInfo);
begin
  fCS.Enter;
  try
    fRecvInfo := aUI;//принимаю данные из VCL в TThread
    fCommand := TC_RECVDATA; //Устанавливаю команду
    fEvent.SetEvent; // Signal, что надо обработать
  finally
    fCS.Leave;
  end;
end;

procedure TAltolanThread.Login(aName, aPwd: string);
begin
  fCS.Enter;
  try
    fName := aName;
    fPassword := aPwd;
    fCommand := TC_LOGIN;
    fEvent.SetEvent; // Signal
  finally
    fCS.Leave;
  end;
end;
end;

procedure TAltolanThread.Execute;
const
  cAuthURL     = 'http://billing.altolan.com/login/';
  cDataGET     = 'http://billing.altolan.com/';
  cDataGETNews = 'http://billing.altolan.com/login/?next=/';
var
  sCookie: string;
  vData: Variant;
  bResult: Boolean;
  sHTML, sPost, s: string;
begin
  NameThreadForDebugging('TAltolanThread');
  { Place thread code here }
  while not Terminated do //Бесконечный цикл, удерживающий TThread в постоянном рабочем состоянии 
  begin
    while (fEvent.WaitFor(INFINITE) = wrSignaled) do //Ожидание команды пользователя на обработку
    begin
      case fCommand of
        TC_LOGIN:
          begin
            sPost := 'username=' + fName + '&password=' + fPassword + '&user=&pin=';
            HTTP.MimeType := 'application/x-www-form-urlencoded';
            if HTTP.POST(cAuthURL, sPost, sHTML) then
            begin
              if HTTP.IsRedirect then
              begin
                sHTML := UTF8Decode(sHTML);
                sCookie := HTTP.Cookies[0];
                s := HTTP.Location;
                HTTP.Clear;
                if HTTP.GET(s, sHTML) then
                begin
                  sHTML := UTF8Decode(sHTML);
                  if HTTP.IsSucsessfull then
                  begin
                    DoLogin(rsLogined, 'Вы успешно авторизованы!', 0);
                  end
                  else
                  begin
                    DoLogin(rsUnknown, '2 Не удалось распознать ответ.', 0);
                  end;
                end
                else
                begin
                  DoLogin(rsConnectionError, 'Не удалось подключиться к серверу.', 0);
                end;
              end
              else
              begin
                if CheckBadLogin(sHTML) then
                  DoLogin(rsLoginError, 'Ошибка авторизации!' + #13 +
                    'Проверьте правильность имени пользователя или пароля.', 0)
                else
                  DoLogin(rsUnknown, '1 Не удалось распознать ответ.', 0);
              end;
            end
            else
            begin
              DoLogin(rsConnectionError, 'Не удалось подключиться к серверу.', 0);
            end;
          end;

        TC_RECVDATA:
          begin
            HTTP.Clear;
            HTTP.Cookies.Text := sCookie;
            if HTTP.GET(cDataGET, sHTML) then
            begin
              sHTML := UTF8Decode(sHTML);
              if HTTP.IsSucsessfull then
              begin
                // TODO1: Проверка на наличие формы авторизации

                // Номер договора
                if (riContractNumber in fRecvInfo) then
                begin
                  bResult := not GetContractNumber(sHTML, vData);
                  DoRecvInfo(riContractNumber, vData, bResult);
                end;

                //....

              end;
            end
            else
            begin
              // Error
            end;
          end;

        TC_EXIT:
          begin
            Exit; //Skip ret 2 waiting signal
          end;
      end;
      fEvent.ResetEvent; // No signal, ret to waiting signal
    end;
  end;
end;



Поток создается при создании гл. окна программы и стартует.
Уничтожается при завершении работы программы.

Скажите правильно ли я реализовал синхронизацию VCL to Thread? 

Синхронизацию Thread to VCL я реализую так: 
Код

 type
  TAltolanThread = class(TThread)
  private
    ...
    fOnLogin: TLoginEvent;
    //Временный переменные
    fOnLogin_State: TResultState;
    fOnLogin_str: string;
    fOnLogin_Code: Integer;
    //Заполнение временных переменных и вызов синхронизации для Sync_Login
    procedure DoLogin(aState: TResultState; aStr: string; aCode: Integer);
    procedure Sync_Login; //Метод вызываемый внутри Synchronize



  protected
    procedure Execute; override;
  public
    ...
    property OnLogin: TLoginEvent read fOnLogin write fOnLogin;
    ...
  end;

implementation

{ TAltolanThread }

....

procedure TAltolanThread.DoLogin(aState: TResultState; aStr: string; aCode: Integer);
begin
  fOnLogin_State := aState;
  fOnLogin_str := aStr;
  fOnLogin_Code := aCode;
  Synchronize(Sync_Login);
end;


procedure TAltolanThread.Sync_Login;
begin
  if Assigned(fOnLogin) then
    fOnLogin(Self, fOnLogin_State, fOnLogin_str, fOnLogin_Code);
end;

procedure TAltolanThread.Terminate;
begin
  inherited;
  fCommand := TC_EXIT;
  fEvent.SetEvent;
end;

end.


Может есть какие-то замечания?  smile 


PM MAIL   Вверх
northener
Дата 15.8.2013, 12:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1361
Регистрация: 2.9.2010

Репутация: 8
Всего: 20



Цитата(ЧеловекБорща @  14.8.2013,  13:48 Найти цитируемый пост)
Сокеты для меня нечто новое. Я не вижу различия между блокирующим и не блокирующим. Если можете кратро, в 2х словах, рассказать различие, буду рад.

Английским владеешь?
Вот как описывает эту разницу автор лучшей библиотеки для работы с сокетами (ICS):
http://users.telenet.be/sonal.nv/ics/faq/Frame_index.html
По этой ссылке General -> 7. Blocking or no blocking, what's the difference 

Это сообщение отредактировал(а) northener - 15.8.2013, 12:33


--------------------
Но только лошади летают вдохновенно.
Иначе лошади разбились бы мгновенно!
PM MAIL   Вверх
kami
Дата 15.8.2013, 14:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 23
Всего: 72



Имхо - не совсем правильно.
1. 
Цитата(ЧеловекБорща @  15.8.2013,  11:19 Найти цитируемый пост)
HTTP := THTTPSendEx.Create

Цитата(kami @  14.8.2013,  09:53 Найти цитируемый пост)
сетевой компонент должен создаваться в методе Execute потока, при этом в потоке должна быть организована выборка сообщений

У вас создается в основном потоке, а работа с ним ведется в контексте дополнительного. Может быть чревато боком, зависит от внутренней архитектуры THTTPSendEx (кстати, что это?).

2. Не увидел (а может - просто пропустил?) оформление в критическую секцию обращений из потока к переменным, которые могут быть изменены из главного.
Помимо этого - попробуйте дважды вызвать 
Цитата(ЧеловекБорща @  15.8.2013,  11:19 Найти цитируемый пост)
procedure TAltolanThread.Login(aName, aPwd: string);

Поток, скорее всего, обработает только одну (первую, хотя - не факт, может и только вторую) заявку на логин, т.к. Setting an event that is already set has no effect. 

Это сообщение отредактировал(а) kami - 15.8.2013, 14:54
PM MAIL WWW   Вверх
ЧеловекБорща
Дата 15.8.2013, 15:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 94
Регистрация: 5.6.2010

Репутация: нет
Всего: нет



northener, спасибо за информацию. Познавательно  smile 
kami
Так стоп, что-то я не совсем теперь понимаю работу Tthread.
Цитата

У вас создается в основном потоке, а работа с ним ведется в контексте дополнительного. Может быть чревато боком, зависит от внутренней архитектуры THTTPSendEx (кстати, что это?).

Что есть основной, а что дополнительный? 
Сейчас я думаю, что TThread это 1 поток. 

THTTPSendEx - обертка с доп. возможностями над Synapse THTTPSend чтоб легче работать было. 

Цитата

Не увидел (а может - просто пропустил?) оформление в критическую секцию обращений из потока к переменным, которые могут быть изменены из главного.
Помимо этого - попробуйте дважды вызвать 


Так вот же:
Код

procedure TAltolanThread.Login(aName, aPwd: string);
begin
  fCS.Enter; //Крит. секция
  try
    fName := aName; //Переменные, которые могут быть изменены.
    fPassword := aPwd;
    fCommand := TC_LOGIN;
    fEvent.SetEvent; // Signal
  finally
    fCS.Leave;
  end;
end;

PM MAIL   Вверх
kami
Дата 15.8.2013, 20:48 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1806
Регистрация: 25.8.2007
Где: Санкт-Петербург

Репутация: 23
Всего: 72



Эх, много писал, но случайно закрыл страницу...
Цитата(ЧеловекБорща @  15.8.2013,  15:02 Найти цитируемый пост)
Что есть основной, а что дополнительный? 

Основным обычно называют VCL-поток. Дополнительные - все остальные, включая создаваемые с помощью TThread.

Цитата(ЧеловекБорща @  15.8.2013,  15:02 Найти цитируемый пост)
THTTPSendEx - обертка с доп. возможностями над Synapse THTTPSend чтоб легче работать было. 

C Синапсами не работал, остановился на ICS, посему про внутреннюю их кухню не знаю ничего.
Цитата(ЧеловекБорща @  15.8.2013,  15:02 Найти цитируемый пост)
Так вот же:

А смысл использовать критическую секцию только в одном потоке? Там от нее смысла ноль. Критическая секция не должна допустить изменения данных одним потоком, пока с ним работает другой. А для этого все обращения потоко-небезопасных данных во всех потоках должны оформляться в критическую секцию.
Теперь - про 
Цитата(kami @  15.8.2013,  14:52 Найти цитируемый пост)
Поток, скорее всего, обработает только одну (первую, хотя - не факт, может и только вторую) заявку на логин,

Рассмотрим подробнее:
VCL-поток вызывает метод Login, в нем модифицируются приватные переменные потока и взводится Event. Это не значит, что управление моментально переключается на поток, ожидающий событие. Совсем не значит. Управление будет передано потоку тогда, когда это сочтет нужным планировщик потоков Винды. Это же относится к критическим секциям и многому другому (имхо, последняя ОС, которая передавала управление сразу же - XP без какого-то сервиспака). Посему - вполне может получиться так, что событие взведено, основной поток уже вышел из метода Login, а доп.поток так и не приступил к обработке...
И если методов типа Login несколько, и они вызываются друг за другом - скорее всего, произойдет замещение одной команды на другую, а первая так и не будет выполнена.

Или - другой вариант, основанный на 
Цитата(kami @  15.8.2013,  14:52 Найти цитируемый пост)
т.к. Setting an event that is already set has no effect. 

Допустим, что после взвода Event в методе Login переключение потоков произошло сразу же, наш поток приступил к получению данных методом Get. Это - достаточно длительная операция, более того - в ICS для синхронного GET внутри метода идет цикл обработки сообщений. В общем - достаточно долго.
При этом основной поток продолжает работу и в процессе выполнения доходит до еще одного метода, в котором должен взвестись Event. Вторичный поток просто не получит эту команду - ведь событие уже было взведено, поток еще находится внутри цикла обработки.
PM MAIL WWW   Вверх
ЧеловекБорща
Дата 18.8.2013, 00:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 94
Регистрация: 5.6.2010

Репутация: нет
Всего: нет



Цитата

А смысл использовать критическую секцию только в одном потоке? Там от нее смысла ноль. Критическая секция не должна допустить изменения данных одним потоком, пока с ним работает другой.


Такс, моя задача следующая:
Есть объект "Маршрутизатор" надо сделать  так, что этот объект не влиял на GUI при сетевых операциях и работал в фоне. 
т.е. мне надо вынести сокет в TThread.
1 сокет = 1 маршрутизатор

Сейчас реализую так же, как в маленьком проекте(код выше), но делаю очередь операций, т.е. чтобы операции 2,3,4 не исчезали если на операции 1 сокет завис. 


Это сообщение отредактировал(а) ЧеловекБорща - 18.8.2013, 00:20
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: Общие вопросы"
SnowyMetalFan
bemsPoseidon
Rrader

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0959 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.