| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > Delphi: WinAPI и системное программирование > Подскажите как правильно сделать отложенную обрабо |
| Автор: vogel 11.7.2008, 18:14 | ||||
| Уважаемые коллеги. Возникла у меня проблема при обработке большго потока данных. Описываю ситуацию : 1. Есть DLL-ка, которая внедряется в чужой процесс, хукает send и recv и передаёт перехваченный трафик главному приложению при помощи WM_COPYDATA вот как-то так :
2. Есть основное приложение, которое получает это сообщение и обрабатывает полученный пакет. ВОт примерно так :
И всё это работает прекрасно, если приложение-жертва посылает пакеты достаточно редко. Как только приложение-жертва начинает активный обмен по сети, причём достаточно большими объёмами данных - возникают проблемы : self.LogPacket(ipcMessage.Operation, packetBuffer), которая занимается идентификацией и разбором пакетов отрабатывает долго и приложение падает с 'Stack Overflow' в TfmMain.WMCOPYDATA(var Msg: TWMCopyData); Внимание вопрос : Каким образом реализовать такую обработку событий, чтобы пришедшие данный быстро-быстро копировались в какое-либо временное хранилище и мы быстро-быстро выходили из TfmMain.WMCOPYDATA. В то время как некий тред в фоновом режими уже занимался разгребанием и логированием пакетов. Очень расчитываю на помощь знающих людей. |
| Автор: ne0n 12.7.2008, 01:46 |
| ну собственно что первое приходит в голову это реализовать подобие клиент-сервера. Т.е. отправила dll данные-> ждем-> приоложение получает данные, обрабатывает их-> отправляет потвержение того что все обработано нашей dll библиотеке-> при получение подтверждения обработки, dll отправляет следующию порцию данных итд. Вроде так никаких переполнений возникнуть не должно... |
| Автор: vogel 13.7.2008, 14:05 |
| Спасибо, но ткаой вариант не пойдёть. Ибо в этом случа мы спровоцируем лаги в приложении-жертвне, в котором захуканы send и recv. Попробую переформклирвать вопрос : Каким образом я могу вызывать процедуру асинхронно ? То есть вызывать self.LogPacket(ipcMessage.Operation, packetBuffer) таким образом, чтобы не ждать окончания выполнения, а переходить сразу дальше. Чувствую, что это можно сделать через потоки, но пока не понимаю как... |
| Автор: CodeMonkey 14.7.2008, 10:13 |
| Я бы сделал так: выделил бы буфер в разделяемой памяти (CreateFileMapping). При получении данных писал бы в этот буфер, затем - отправлял бы уведомление другой программе (например через SetEvent) и сразу же выходил бы из функции. Другая программа имела бы один или несколько потоков, которые бы ждали наступления события (WaitForSingleObject) и как только оно получено - начинали обработку блока. После того, как блок обработан - пометить его свободным и отправлять уведомление первой программе (наверное, опять через SetEvent). Нужно только отслеживать, какая часть общего буфера свободна, а какая - занята (проще всего делать это циклически, т.е. ввести маркер начала необработанных данных и конца, при достижении конца буфера - начинать запись с его начала). Если данные прибывают чаще, чем вторая программа успевает их обработать, то надо, либо отбрасывать часть данных, либо выделять дополнительный буфер, либо приостанавливать приём-передачу, пока вторая программа не обработает данные. Для последнего и нужен был второй SetEvent - в случае, если места в буфере мало, то первая программа может ждать на этом событии, пока вторая её не уведомит, что в буфере появилось новое свободное место. |
| Автор: vogel 14.7.2008, 13:33 |
| Хммм, почему слишком обще ?? Я же в первом посте написал по поводу "канала связи" - это WM_COPYDATA. Вопрос в том, что обработка принятых данных (на некоторых этапах) идёт медленне, чем эти данные поступают. Из-за этого stack overflow. Мне самому видится такое решение : 1. Обработчик WM_COPYDATA складывает полученные строки, например, в TStringList и тутже возвращает управление - этим мы достигаем быстроту ответов и избавляемся от stack ovwrflow (как мне кажется). 2. При этом отдельный TThread мониторит этот самый TStringList на предмет count > 0 и елси оно так - выгребает строку и обрабатывает её. главный вопрос теперь - критические секции - как бы не потерять время на них... |
| Автор: Rennigth 14.7.2008, 14:15 |
| vogel, Логичнее будет использовать пул потоков. |
| Автор: vogel 14.7.2008, 14:47 | ||
Спасибо за идею, мне б примерчик. А вообще по теме вопроса - проблему решил : 1. Надо было сделать все переменные в обработчике WM_COPYDATA глобальными - это сняло проблему stack overflow 2. Данные заносятся в TStringList и обрабатываются отдельным потоком - это снялдо проблему производительности. Спасибо всем участникам за обсуждение, однако пока не закрываю - жду интересных решений. Желательно с примерами. Вот пул потоков очень даже заинтересмоало. |
| Автор: CodeMonkey 14.7.2008, 14:52 | ||||
Причём тут stack overflow вообще?
У вас получается, что вы будете гонять данные впустую между различными буферами. Не самое изящное решение. Для межпроцессного взаимодействия лучше использовать специально предназначенные для этого IPC функции, а вовсе не сообщения Windows, предназначенные (в первую очередь, конечно) для визуального интерфейса. Добавлено через 48 секунд
Не сняло, а скрыло ;) |
| Автор: vogel 14.7.2008, 16:22 | ||||||
Ну Вы внимательно прочитайте первое сообщение топика - Вам сразу станет понятно причём оно тут.
Я весь внимание. Очень хочется посмотреть, что же именно вы понимаете под IPC ?? MMF, NamedPipes, UDP ?? Кстати, WM_COPYDATA в частности относится к механизмам IPC.
Сняло. Ибо память под локальные переменные внутри процедур отводится из стека. А скрыть эту проблему нельзя - у вас либо переполняется стек либо нет. |
| Автор: Rennigth 14.7.2008, 16:31 |
Вот http://wm-help.net/books-online/book/59464/59464-4.html#h11 можно почитать про встроеммые механизмы винды. Если не подойдет, надо самому организовывать, и затачивать под Ваши задачи. |
| Автор: CodeMonkey 14.7.2008, 17:44 | ||||||||
| Что ж вы такой непонятливый. Ладно, не будем намёками. Будем правду резать Смотрите:
Как это работает: приходит сообщение WM_COPYDATA, вы запускаете обработку данных, затем вызывается Application.ProcessMessages, который просматривает накопившиеся в очереди сообщения и запускает их обработчики. Если вам сообщения WM_COPYDATA поступают быстрее, чем вы их обрабатываете, то это значит, что на момент вызова Application.ProcessMessages в очереди окна уже есть одно или более сообщений WM_COPYDATA. Что значит, что обработчик WMCOPYDATA будет запускаться из Application.ProcessMessages, который в свою очередь вызывается из... правильно, WMCOPYDATA. Видите рекурсию? У вас получается такое дерево вызовов:
Причём чем больше разница в скорости прибытия/обработки сообщений, тем быстрее растёт вложенность вызовов. Вот вам и причина переполнения стека - рано или поздно стек закончится из-за большой рекурсии вызовов. Разумеется, если в потоке входящих данных появляется окно (замедление скорости данных), то начинается процесс выхода из дерева вызовов, и стек освобождается. И то, что вы вынесли переменные за пределы процедуры принципиально не меняет ситуации - это лишь меняет скорость заполнения стека (в стек при каждом вызове попадает меньше информации, но, тем не менее, всё ещё попадает). Да, это уменьшает вероятность переполнения стека, т.к. теперь стек заполняется дольше, а значит больше вероятность появления окна во входящих данных, чтобы стек успел освободиться. Тем не менее проблему вы не сняли, она всё ещё тут - достаточно поддерживать высокий темп входящих данных и стек рано или поздно снова переполнится. Именно поэтому я сказал, что проблему вы скрыли, а не решили. Теперь ситуация ясна? Убедите Application.ProcessMessages (зачем вы его вообще туда всунули?) и проблема решена. Поэтому я и сказал: причём тут переполнение стека, когда проблемы нет вообще - вы её просто специально создали.
Посмотрите, вам уже предлагали воспользоваться MMF или пайпами. Формально, да, WM_COPYDATA относится к IPC. Проблема в том, что оно ориентировано на простую пересылку данных между двумя GUI-приложениями. Поскольку фактически это сообщение является обёрткой вокруг MMF, то для вашего сценария нет смысла нагружать приложение дополнительными накладными расходами. Впрочем, это ваш выбор. |
| Автор: vogel 15.7.2008, 11:52 |
| CodeMonkey, Огромнейшее спасибо за подробное объяснение такому вот непонятливому. Проблема действительно решилась. А я был неправ. Теперь "раскуриваю" MMF, но оно меня как-то пугает... Через WM_COPYDATA всё происходит достаточно лего и просто |
| Автор: CodeMonkey 15.7.2008, 13:35 |
| Если вас устроит ваша реализация с WM_COPYDATA - ради бога, используйте её. Просто она будет не самой оптимальной (в плане быстродействия). Зато (наверное) наиболее просто реализуемой. И, кстати, стек потоков - правильность этого решения зависит от того, какие именно действия вы выполняете в процедуре обработки. Например, если у вас поток занимается тем, что производит какие-либо вычисления по полученным данным (т.е. работа идёт в основном процессором), то выделение дополнительных потоков просто ничего не даст, а наоборот - ухудшит ситуацию. Если же в обработке вы, скажем, просто копируете данные в файл, то тут процессор почти не участвует, и использование нескольких потоков (может) существенно увеличит пропускную способность обработки. Впрочем, даже в этом случае наиболее быстродействующим решением был бы один поток с использованием асинхронного ввода-вывода, но такого монстра вы замучаетесь писать (но если решитесь, то начать стоит http://www.google.com/search?&q=completion+port). Это так, информация к размышлению. |
| Автор: vogel 15.7.2008, 14:55 | ||
| Реализация с WM_COPYDATA оказалась наиболее простой и понятной, но и у неё есть свои недостсаки - как-то передача блоков только фиксированного рамера. В итоге, зная, что данные у меня длиногй максимум word - пришлось делать массив [0..$FFFF]. Это работает, но приносит дополнительные расходы, ибо размер передаваемых данных всегда разный и в-основном значительно меньше FFFF. Однако, я не уверен, что через MMF можно будет просто кидать динамические массивы или строки.
Именно это и происходит. Поэтому от потока я уже оказался. Ещё раз спасибо Вам за то, что разъяснили такую грубую ошибку. |
| Автор: CodeMonkey 15.7.2008, 15:28 | ||||||||
Непонятно, откуда вы это взяли? В вашем же примере:
Т.е. у вас уже написан код по передаче данных любого размера. Правда конкретно сейчас вы передаёте только одну запись фиксированного размера (TIPCMessage). Или вы говорите, что не знаете, как подготовить (упаковать в запись или что-то такое) данные произвольной длины для отправки?
Точно так же, как любые другие данные. При передаче вы все данные всё равно рассматриваете как массив (набор) байт, без разницы: строка это, запись или массив. |
| Автор: vogel 22.7.2008, 16:00 | ||||
Конкретно, если мне надо передать запись где есть строки, объявленные как string, делаем это, напрмер, так :
и дальше в коде :
получаем, что sizeOf(TSomeRecord) совсем не равен реальному размеру записи. И припопытке вычитать такую строку получаем AV. Однако, если я использую вместо string - array[0..$FFFF] of char, то всё работает прекрасно, хоть и значительно перерасходуется память. |
| Автор: Snowy 22.7.2008, 16:29 |
| Разумеется. Ведь string - это указатель. Передавать такую структуру через WM_COPYDATA бесполезно. Нужно передавать фиксированный блок. Либо использовать записи строгого размера, либо записать все данные в единый буфер/стрим. Записать данные можно вручную, либо через сериализацию. Ситуация подобна передаче данных через сокеты. Нужно передавать сплошной блок/поток данных, а не указатели на них. |
| Автор: CodeMonkey 22.7.2008, 17:36 | ||||
| Понятно, короче говоря, вы не знаете, как динамические данные упаковать в одну запись. Грубо говоря, вы должны разделить данные на данные фиксированного размера и динамические. Закиньте все фиксированные данные в структуру:
Далее, вы должны подготовить блок к передаче, например, так:
Ну и читать аналогично, только все действия в обратном порядке. |