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

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Подскажите как правильно сделать отложенную обрабо 
:(
    Опции темы
vogel
Дата 11.7.2008, 18:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



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

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



Уважаемые коллеги. 
Возникла у меня проблема при обработке большго потока данных. Описываю ситуацию :

1. Есть DLL-ка, которая внедряется в чужой процесс, хукает send и recv и передаёт перехваченный трафик главному приложению при помощи WM_COPYDATA  вот как-то так :

Код

function ProcessPacket(msg : TIPCMessage) : boolean;
var
  copyDataStruct : TCopyDataStruct;
begin
  // Locate parent window
  parentWindowHandle := FindWindow(nil, PARENT_WINDOW_CAPTION);
  if parentWindowHandle <> 0 then
  begin
    msg.Pid  := GetCurrentProcessID;
    // Send WM_COPYDATA message
    copyDataStruct.dwData := NOTIFY_API_CALL; //use it to identify the message contents
    copyDataStruct.cbData := sizeOf(msg);
    copyDataStruct.lpData := @msg;
    //
    if SendMessage(parentWindowHandle, WM_COPYDATA, MAGIC_NUMBER, Integer(@copyDataStruct)) = 1
    then result := true else result := false;
  end;
end;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~ Наша замена recv'у
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function recvHookProc(s: TSocket; var Buf; len, flags: Integer): Integer; stdcall;
var
  msg     : TIPCMessage;
  rawData : array [0..$FFFF] of byte;
  i, packetLen : word;
begin
  // Вызываем оригинальный приёмщик, но данные пишем к себе в буфер
  Result := recvNextHook(s, rawData[0], Len, Flags);

  // Общее для данной посылки
  msg.Operation := OT_RECV;
  msg.Sock      := s;
  msg.TimeStamp := now();

  // Разбираем входной поток на пакеты
  i := 0;
  while i < Result do
  begin
    packetLen := rawData[i] + rawData[i+1]*$100;
    // Готовим к отправке наш пакетик
    msg.Size := packetLen;
    Move(rawData[i], msg.Data[0], packetLen);
    // Засылаем собранную инфу в наше приложение
    ProcessPacket(msg);
    // TODO: обрабатываем блокировку и фильтрацию пакетов
    // Переходим к следующему пакету
    i := i + packetLen;
  end;

  // Передаём данные дальше по цепочке
  Move(rawData[0], Buf, Result);
end;



2. Есть основное приложение, которое получает это сообщение и обрабатывает полученный пакет. ВОт примерно так :
Код

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~ Обслуживаем сообщение от DLL-ки
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
procedure TfmMain.WMCOPYDATA(var Msg: TWMCopyData);
var
  ipcMessage : TIPCMessage;
  packetBuffer : string;
begin
  // Проверяем, что нам пришло наше сообщение
  if (Msg.From <> MAGIC_NUMBER) then
  begin
    ReplyMessage(1);
    exit;
  end;

  // Выделяем значимую часть
  ipcMessage := PIPCMEssage(Msg.CopyDataStruct.lpData)^;

  // Что будем делать с пришедшими данными ?
  case Msg.CopyDataStruct.dwData of
    NOTIFY_API_CALL: // Уведомление о вызове функции
    begin
      // Если работать с данными пакета как со строкой
      SetLength(packetBuffer, ipcMessage.Size);
      Move(ipcMessage.Data[0], packetBuffer[1], ipcMessage.Size);
      self.LogPacket(ipcMessage.Operation, packetBuffer);
    end;
  end;
  // reply to sender
  ReplyMessage(1);
  application.ProcessMessages();
end;



И всё это работает прекрасно, если приложение-жертва посылает пакеты достаточно редко. Как только приложение-жертва начинает активный обмен по сети, причём достаточно большими объёмами данных - возникают проблемы :

self.LogPacket(ipcMessage.Operation, packetBuffer), которая занимается идентификацией и разбором пакетов отрабатывает долго и приложение падает с 'Stack Overflow' в TfmMain.WMCOPYDATA(var Msg: TWMCopyData);

Внимание вопрос : Каким образом реализовать такую обработку событий, чтобы пришедшие данный быстро-быстро копировались в какое-либо временное хранилище и мы быстро-быстро выходили из  TfmMain.WMCOPYDATA. В то время как некий тред в фоновом режими уже занимался разгребанием и логированием пакетов.

Очень расчитываю на помощь знающих людей.


PM MAIL   Вверх
ne0n
Дата 12.7.2008, 01:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


PlayBoy
**


Профиль
Группа: Участник
Сообщений: 733
Регистрация: 5.8.2005
Где: Н.Новгород

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



ну собственно что первое приходит в голову это реализовать подобие клиент-сервера.
Т.е. отправила dll данные-> ждем-> приоложение получает данные, обрабатывает их-> отправляет потвержение того что все обработано нашей dll библиотеке-> при получение подтверждения обработки, dll отправляет следующию порцию данных итд. Вроде так никаких переполнений возникнуть не должно...

Это сообщение отредактировал(а) ne0n - 12.7.2008, 01:47
PM MAIL ICQ   Вверх
vogel
Дата 13.7.2008, 14:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



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

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



Спасибо, но ткаой вариант не пойдёть. Ибо в этом случа мы спровоцируем лаги в приложении-жертвне, в котором захуканы send и recv.
Попробую переформклирвать вопрос : Каким образом я могу вызывать процедуру асинхронно ? То есть вызывать self.LogPacket(ipcMessage.Operation, packetBuffer) таким образом, чтобы не ждать окончания выполнения, а переходить сразу дальше. 
Чувствую, что это можно сделать через потоки, но пока не понимаю как...

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


Эксперт
***


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

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



Я бы сделал так: выделил бы буфер в разделяемой памяти (CreateFileMapping). При получении данных писал бы в этот буфер, затем - отправлял бы уведомление другой программе (например через SetEvent) и сразу же выходил бы из функции. 
Другая программа имела бы один или несколько потоков, которые бы ждали наступления события (WaitForSingleObject) и как только оно получено - начинали обработку блока. После того, как блок обработан - пометить его свободным и отправлять уведомление первой программе (наверное, опять через SetEvent).
Нужно только отслеживать, какая часть общего буфера свободна, а какая - занята (проще всего делать это циклически, т.е. ввести маркер начала необработанных данных и конца, при достижении конца буфера - начинать запись с его начала). Если данные прибывают чаще, чем вторая программа успевает их обработать, то надо, либо отбрасывать часть данных, либо выделять дополнительный буфер, либо приостанавливать приём-передачу, пока вторая программа не обработает данные. Для последнего и нужен был второй SetEvent - в случае, если места в буфере мало, то первая программа может ждать на этом событии, пока вторая её не уведомит, что в буфере появилось новое свободное место.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
Riply
Дата 14.7.2008, 10:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Комодератор
Сообщений: 572
Регистрация: 27.3.2007
Где: St. Petersburg

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



Цитата(vogel @  13.7.2008,  14:05 Найти цитируемый пост)
Каким образом я могу вызывать процедуру асинхронно ? То есть вызывать self.LogPacket(ipcMessage.Operation, packetBuffer) таким образом, чтобы не ждать окончания выполнения, а переходить сразу дальше. 


Я делала подобное (данные надо было получать сразу от нескольких процессов) используя Pipe - ы.  
А как только ты определился с "каналом связи" далее все будет ограничиваеться только воображением.
Вариантов масса : от ReadFileEx (например, с очередью)  до пула потоков.  smile

P.S. 
Слишком обще сформулирована задача, вот и получаются общие ответы.  smile 
PM MAIL   Вверх
vogel
Дата 14.7.2008, 13:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



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

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



Хммм, почему слишком обще ??
Я же в первом посте написал по поводу "канала связи" - это WM_COPYDATA.
Вопрос в том, что обработка принятых данных (на некоторых этапах) идёт медленне, чем эти данные поступают. Из-за этого stack overflow.

Мне самому видится такое решение :

1. Обработчик WM_COPYDATA складывает полученные строки, например, в TStringList и тутже возвращает управление - этим мы достигаем быстроту ответов и избавляемся от stack ovwrflow (как мне кажется).

2. При этом отдельный TThread мониторит этот самый TStringList на предмет count > 0 и елси оно так - выгребает строку и обрабатывает её.

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

PM MAIL   Вверх
Rennigth
Дата 14.7.2008, 14:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1708
Регистрация: 21.6.2004
Где: Moscow

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



vogel, Логичнее будет использовать пул потоков.


--------------------
(* Honesta mors turpi vita potior *)
PM MAIL ICQ   Вверх
vogel
Дата 14.7.2008, 14:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



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

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



Цитата(Rennigth @ 14.7.2008,  14:15)
vogel, Логичнее будет использовать пул потоков.

Спасибо за идею, мне б примерчик.

А вообще по теме вопроса - проблему решил :
1. Надо было сделать все переменные в обработчике WM_COPYDATA глобальными - это сняло проблему stack overflow
2. Данные заносятся в TStringList и обрабатываются отдельным потоком - это снялдо проблему производительности.

Спасибо всем участникам за обсуждение, однако пока не закрываю - жду интересных решений. Желательно с примерами.
Вот пул потоков очень даже заинтересмоало.
PM MAIL   Вверх
CodeMonkey
Дата 14.7.2008, 14:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


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

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



Цитата(vogel @  14.7.2008,  13:33 Найти цитируемый пост)
избавляемся от stack ovwrflow

Причём тут stack overflow вообще? smile Уберите Application.ProcessMessages из обработчика сообщения.

Цитата(vogel @  14.7.2008,  13:33 Найти цитируемый пост)
Обработчик WM_COPYDATA складывает полученные строки, например, в TStringList

У вас получается, что вы будете гонять данные впустую между различными буферами. Не самое изящное решение. Для межпроцессного взаимодействия лучше использовать специально предназначенные для этого IPC функции, а вовсе не сообщения Windows, предназначенные (в первую очередь, конечно) для визуального интерфейса.

Добавлено через 48 секунд
Цитата(vogel @  14.7.2008,  14:47 Найти цитируемый пост)
1. Надо было сделать все переменные в обработчике WM_COPYDATA глобальными - это сняло проблему stack overflow

Не сняло, а скрыло ;)


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
vogel
Дата 14.7.2008, 16:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



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

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



Цитата
Причём тут stack overflow вообще? smile 

Ну Вы внимательно прочитайте первое сообщение топика - Вам сразу станет понятно причём оно тут.

Цитата
У вас получается, что вы будете гонять данные впустую между различными буферами. Не самое изящное решение. Для межпроцессного взаимодействия лучше использовать специально предназначенные для этого IPC функции, а вовсе не сообщения Windows, предназначенные (в первую очередь, конечно) для визуального интерфейса.


Я весь внимание. Очень хочется посмотреть, что же именно вы понимаете под IPC ?? MMF, NamedPipes, UDP ??
Кстати, WM_COPYDATA в частности относится к механизмам IPC.


Цитата
Не сняло, а скрыло ;)

Сняло. Ибо память под локальные переменные внутри процедур отводится из стека.
А скрыть эту проблему нельзя - у вас либо переполняется стек либо нет.

Это сообщение отредактировал(а) vogel - 14.7.2008, 16:23
PM MAIL   Вверх
Rennigth
Дата 14.7.2008, 16:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1708
Регистрация: 21.6.2004
Где: Moscow

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



Цитата(vogel @  14.7.2008,  14:47 Найти цитируемый пост)
Вот пул потоков очень даже заинтересмоало. 

Вот тут можно почитать про встроеммые механизмы винды.
Если не подойдет, надо самому организовывать, и затачивать под Ваши задачи.


--------------------
(* Honesta mors turpi vita potior *)
PM MAIL ICQ   Вверх
CodeMonkey
Дата 14.7.2008, 17:44 (ссылка) |  (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


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

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



Что ж вы такой непонятливый.
Ладно, не будем намёками. Будем правду резать smile 
Смотрите:

Цитата(vogel @  11.7.2008,  18:14 Найти цитируемый пост)
Код
procedure TfmMain.WMCOPYDATA(var Msg: TWMCopyData);
var
  ipcMessage : TIPCMessage;
  packetBuffer : string;
begin
  ...
  Application.ProcessMessages;
end;


Как это работает: приходит сообщение WM_COPYDATA, вы запускаете обработку данных, затем вызывается Application.ProcessMessages, который просматривает накопившиеся в очереди сообщения и запускает их обработчики. Если вам сообщения WM_COPYDATA поступают быстрее, чем вы их обрабатываете, то это значит, что на момент вызова Application.ProcessMessages в очереди окна уже есть одно или более сообщений WM_COPYDATA. Что значит, что обработчик WMCOPYDATA будет запускаться из Application.ProcessMessages, который в свою очередь вызывается из... правильно, WMCOPYDATA. Видите рекурсию?
У вас получается такое дерево вызовов:
Код
WMCOPYDATA
  Application.ProcessMessages
    WMCOPYDATA
      Application.ProcessMessages
        WMCOPYDATA
          ...

Причём чем больше разница в скорости прибытия/обработки сообщений, тем быстрее растёт вложенность вызовов. Вот вам и причина переполнения стека - рано или поздно стек закончится из-за большой рекурсии вызовов. Разумеется, если в потоке входящих данных появляется окно (замедление скорости данных), то начинается процесс выхода из дерева вызовов, и стек освобождается.
И то, что вы вынесли переменные за пределы процедуры принципиально не меняет ситуации - это лишь меняет скорость заполнения стека (в стек при каждом вызове попадает меньше информации, но, тем не менее, всё ещё попадает). Да, это уменьшает вероятность переполнения стека, т.к. теперь стек заполняется дольше, а значит больше вероятность появления окна во входящих данных, чтобы стек успел освободиться. Тем не менее проблему вы не сняли, она всё ещё тут - достаточно поддерживать высокий темп входящих данных и стек рано или поздно снова переполнится. Именно поэтому я сказал, что проблему вы скрыли, а не решили.
Теперь ситуация ясна? Убедите Application.ProcessMessages (зачем вы его вообще туда всунули?) и проблема решена. Поэтому я и сказал: причём тут переполнение стека, когда проблемы нет вообще - вы её просто специально создали.

Цитата(vogel @  14.7.2008,  16:22 Найти цитируемый пост)
Я весь внимание. Очень хочется посмотреть, что же именно вы понимаете под IPC ?? MMF, NamedPipes, UDP ??Кстати, WM_COPYDATA в частности относится к механизмам IPC.

Посмотрите, вам уже предлагали воспользоваться MMF или пайпами. Формально, да, WM_COPYDATA относится к IPC. Проблема в том, что оно ориентировано на простую пересылку данных между двумя GUI-приложениями. Поскольку фактически это сообщение является обёрткой вокруг MMF, то для вашего сценария нет смысла нагружать приложение дополнительными накладными расходами. Впрочем, это ваш выбор.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
vogel
Дата 15.7.2008, 11:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



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

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



CodeMonkey, Огромнейшее спасибо за подробное объяснение такому вот непонятливому. 
Проблема действительно решилась. А я был неправ. 
Теперь "раскуриваю" MMF, но оно меня как-то пугает... Через WM_COPYDATA всё происходит достаточно лего и просто
PM MAIL   Вверх
CodeMonkey
Дата 15.7.2008, 13:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


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

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



Если вас устроит ваша реализация с WM_COPYDATA - ради бога, используйте её. Просто она будет не самой оптимальной (в плане быстродействия). Зато (наверное) наиболее просто реализуемой.

И, кстати, стек потоков - правильность этого решения зависит от того, какие именно действия вы выполняете в процедуре обработки. Например, если у вас поток занимается тем, что производит какие-либо вычисления по полученным данным (т.е. работа идёт в основном процессором), то выделение дополнительных потоков просто ничего не даст, а наоборот - ухудшит ситуацию. 
Если же в обработке вы, скажем, просто копируете данные в файл, то тут процессор почти не участвует, и использование нескольких потоков (может) существенно увеличит пропускную способность обработки. Впрочем, даже в этом случае наиболее быстродействующим решением был бы один поток с использованием асинхронного ввода-вывода, но такого монстра вы замучаетесь писать (но если решитесь, то начать стоит отсюда). Это так, информация к размышлению.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
vogel
Дата 15.7.2008, 14:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



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

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



Реализация с WM_COPYDATA оказалась наиболее простой и понятной, но и у неё есть свои недостсаки - как-то передача блоков только фиксированного рамера. В итоге, зная, что данные у меня длиногй максимум word - пришлось делать массив [0..$FFFF]. Это работает, но приносит дополнительные расходы, ибо размер передаваемых данных всегда разный и в-основном значительно меньше FFFF.

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

Цитата

Например, если у вас поток занимается тем, что производит какие-либо вычисления по полученным данным (т.е. работа идёт в основном процессором), то выделение дополнительных потоков просто ничего не даст, а наоборот - ухудшит ситуацию. 


Именно это и происходит. Поэтому от потока я уже оказался. Ещё раз спасибо Вам за то, что разъяснили такую грубую ошибку.
PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: WinAPI и системное программирование"
Snowybartram
MetalFanbems
PoseidonRrader
Riply

Запрещено:

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

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

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

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

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


 




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


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

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