Передача файлов с помощью pipes и другие мелочи на Delphi

Поставили как-то передо мной задачу написать несколько юнитов на Delphi, обеспечивающих доступ к следующим возможностям Windows:

  1. Shared memory.
  2. Pipes.

Использовать компоненты сторонних разработчиков было нельзя. Shared memory предполагалось использовать для сортировки текстовых файлов больших размеров – несколько десятков или даже сотен мегабайт, на которых TStringList падал с EOutOfMemory. Pipes – для передачи файлов от одного приложения другому.

Переписать код примеров из документации Microsoft с C на Delphi – это было даже не полдела, а процентов 10. Главное было связать это все вместе, дописать, где нужно, дополнительные функции и заставить всё это работать, а не просто существовать в виде исходного кода. В результате родился такой уродливый монстр, что его не то, что показывать кому-то, а просто смотреть на него было страшно. Однако, может, кто-то найдет в его недрах что-то для себя полезное (а вдруг?), поэтому я решил его здесь выложить. На всеобщее поругание. Полный исходный код этой abomination выложен на GitHub. В тексте статьи будут даны названия файлов, где можно посмотреть тот или иной кусок реализации.

Первая задача – сортировка текстового файла – решалась так:

  1. Открываем файл (CreateFile).
  2. Используем CreateFileMapping и MapViewOfFile (common\MemoryMappedFileUnit.pas).
  3. Определяем кодировку файла. Здесь я ограничился, по сути, только двумя кодировками: UTF-16 и ACP – однобайтовая кодировка с кодовой страницей по умолчанию. В русскоязычной Windows обычно используется Windows-1251.
  4. Разбиваем текст на строки. То есть ищем маркеры конца строк и, соответственно, считаем текст от маркера до маркера одной строкой.
  5. Сортируем полученный массив строк (SortingTest\MergeSorterUnit.pas).
  6. Записываем отсортированный массив строк обратно в файл.
  7. PROFIT!

Сортировку заказчик обязательно хотел сделать многопоточной, поэтому в качестве алгоритма сортировки была взята Merge Sort. (Спасибо тому гуру, который создал рабочую реализацию и выложил ее на Stackoverflow.)

Вторая задача – передача файла от одного приложения к другому – оказалась куда сложнее. Получить реализацию pipes на Delphi на основе примеров Microsoft – это одно, но сделать из нее рабочую передавалку файлов – совсем другое. В качестве программы-сервера Microsoft предлагает несколько вариантов:

  1. Multithreaded Pipe Server.
  2. Named Pipe Server Using Overlapped I/O.
  3. Named Pipe Server Using Completion Routines.

Как выяснилось в результате двухнедельных мучений, реализация механизма передачи файла на основе первого варианта сервера (Multithreaded Pipe Server) у меня вышла слишком сложной. Второй вариант (Named Pipe Server Using Overlapped I/O) получился намного проще (правда, заказчик по каким-то своим причинам очень хотел именно первый вариант сервера). Но всё равно пришлось помучиться, чтобы довести всё это дело до ума. В результате родился передатчик файлов на основе pipes: TCommunicationEndpointBase (CommunicationTest\SimpleCommunicationUnit.pas).

Вкратце, передача файлов работает так:

  1. Создаем сервер, то есть создаем named pipe.
  2. Создаем клиент, который подключается к этому named pipe.
  3. Крутим цикл «посылаем запрос – ждем ответ – читаем ответ» на клиенте и на сервере.
  4. Большие файлы посылаем частями и потом собираем файл из этих кусочков.
  5. По окончании передачи файлов закрываем соединение.

Все нюансы реализации можно посмотреть на GitHub, где выложен полный код всего вышеперечисленного. Всё оформлено в виде одного приложения (BlitzTest\BlitzTest.dproj), достаточно его скачать, собрать и запустить.

Да, еще там до кучи лежит оптимизированная реализация функции StringReplace (BlitzTest\StringReplaceCustomUnit.pas). Может, кому пригодится.

Любые комментарии (в том числе и откровенная ругань) приветствуются.

Если вдруг (ну, а вдруг?) кого-то заинтересуют другие мои репозитории, то могу и о них пару слов написать. Если, конечно, этот текст вообще опубликуют.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 22

    0
    Неплохо.
    На сайте overbyte.eu есть ветка MIDWARE — я использовал изложенные автором идеи для реализации подобного обмена.
      0
      Да, разумеется, в сети много всего есть: есть и готовые решения. Я сразу говорю: мне была поставлена задача это всё дело реализовать без использования готовых решений. Так было нужно заказчику — не столько качество, сколько чего-то другое. Так что, мое решение в силу этого тоже далеко не самое оптимальное: тем более, заказчик по итогам всё равно от него отказался вообще. А я решил его выложить, чтоб труд за зря не пропадал. Может, кто-то найдет в этой куче кода что-то для себя полезное.
        0
        Повторюсь еще раз. На майлслотах все это делается куда проще… Описание в MSDN есть. Подробное и с примерами.

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

        В целом MailSlot и Pipe это как UDP и TCP сокеты.

        Ну и ваш пример весьма показателен для современного мира — любители фреймворков живут исключительно внутри своих фремворков и очень слабо себе представляют возможности и API платформы, под которой они работают. Выход за пределы фреймворка сопровождается острым приступом агаророфобии.
          0
          Я не понял, о какой агаророфобии вы говорите, и о каких любителях, так что буду трактовать ваши слова вольно. Если применить их к тем, о ком я думаю, то вы, вероятнее всего, где-то правы.
            0
            Ну попробую перефразировать. Долго работал под Win. Ну и общался с себе подобными. Среди них было немало людей, писавших на VCL (Дельфи и Билдер). Так вот значительная часть из них была ограничена исключительно рамками того, что предоставляет VCL. И если там чего-то нет, то все, «это невозможно потому что этого нет в VCL». Какие возможности предоставляет WinAPI они понятия не имеют — для них это темный лес, страшно и непонятно. Что такое MSDN (на мой взгляд, одна из лучших справочных систем) ни разу не слышали.

            То, что в некоторых случаях эффективнее сделать свою библиотечку (а она получается очень компактной), содержащей UDP и TCP сокеты и TCP сервер для работы в синхроне в отдельном потоке для них просто откровение — люди не представляют что такое WINSOCKS и как с ним работать.

            Про ряд интересных функций, которых нет в стандартных заголовках (но они описаны в MSDN и «достаются» через GetProcAddress) уж и говорить не стоит…

            Вот это и есть агарофобия — в данном случае паническая боязнь выйти за пределы фреймворка на просторы системного API.
              0
              Значит, я правильно вас понял. Да, всё именно так и есть. У меня сложилось точно такое же представление о «делфийском народе».
                0
                Ну не только делфиском. На билдере все примерно также… Стремление быстро слепить продукт из VCL компонентов, не вдаваясь в детали. Это обратная сторона «низкого порога вхождения», о котором так часто упоминалось в обсуждениях Делфи.

                Есть ощущение, что подобное и в других областях наблюдается (та же мобильная разработка — скорее схватить самый модный на текущий момент фреймворк и вперед — к монетизации своего приложения).

                Я сам продолжительное время на билдере работал, но начинал еще под досом, с Turbo C 1.0 Б потом 2.0, Turbo C++, Borland C++ — 4-я версия была невозможно глючной, с нее ушел на MSVC и потом уже вернулся на билдер 6-й.

                Помню что когда с доса на вин переходил, первое приложение написал на чистом WinAPI. Просто чтобы понимать как оно работает. Ну и в билдере не боялся апишные функции использовать там, где по каким-то причинам VCL не устраивала или ее нехватало.

                Что касается межпроцессных и межпоточных коммуникаций, то в последнем виндовом проекте этого было много и пришлось глубоко вникать в эту тему.
                  0
                  Это не «низкий порог вхождения», а особенность нашего национального постсоветского программирования, распространенного в 1990-е и ранние 2000-е.
                  Я сам продолжительное время на билдере работал, но начинал еще под досом, с Turbo C 1.0 Б потом 2.0, Turbo C++, Borland C++ — 4-я версия была невозможно глючной, с нее ушел на MSVC и потом уже вернулся на билдер 6-й.

                  Вот-вот — эта особенность нас, старичков. (Рассыпает песочек...)

                  В 1990-е у нас Delphi и ее брат-близнец CBuilder были дико популярны. Мне сложно сказать, с чем эта дикая популярность была связана (может, книги по Pascal сюда завозили бесплатно, а за все остальные книги просили денежку), но отсюда берет начало эта распространенность Delphi на нашей территории. Собственно, не будь России, Абракадабра уже давно бы загнулась — в цивилизованных странах ее продукты уже давно нахрен никому не нужны. Одни мы их еще покупаем, вот она, видимо, потому еще шевелится.

                  Современное же поколение программеров этой Delphi-манией уже не охвачено. Они, полагаю, даже не в курсе, что сей уродец мира программирования существует.
                    0
                    Ой ой, а сколько желчи полилось… Расскажите мне чем писать под виндовс так что бы не блевать в процессе, так что бы все в одном и в найтиве… да да С# в пролете. Да и что бы мне не надо было собирать среду из кусков так что ее невозможно потом воспроизвести на другом ПК.
                      0
                      Разговор не о том на чем писать (под винды есть Visual Studio, выросшая из MSVC + MFC). Вопрос о том как писать.

                      Хотите писать под винду? Учите прежде всего WinAPI и те системные средства, что она предлагает. Ну и просто архитектурные решения, позволяющие понимать когда и где выгоднее использовать асинхрон в общем потоке, а когда и где — синхрон в отдельном потоке.
                      0
                      Современное поколение охвачено манией мобильной и ваб разработки. А там тоже фреймворки. И вопросы там «а вот этот фреймворк может это, а тот модет то». И мало кому (у меня такое впечатление) призодит в голову, что все «то» или «это» опирается на возможности платформы. А «может» или «не может» определяется исключительно тем, дошли у разработчика фреймворка руки реализовать «то» или «это», или нет.

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

                      Для себя я как-то пришел к тому, что когда чего-то нехватает, я просто дописываю это сам. А если этот кусок кода из одного проекта вдруг требуется в другом, он оформлаяется в виде библиотеки.

                      Долго жил под виндами. Писал на С и С++. Потом несколько лет назал в жищни случился крутой поворот и теперь я пишу под AS/400 главным образом на специфическом языке RPG (мало известен за пределами этой платформы, хотя у MS была попытка создать VisualRPG).

                      Язык специфический, ориентирован прежде всего на работу с БД и коммерческие рассчеты. И там совершенно нет такой вещи как списки. А я очень привык с ними работать. Даже больше, чем со статическими массивами. И очень мне их нехватало. В RPG есть указатели. И есть понятие структуры. Но все это как-то коряво там выглядит.

                      Но к счастью, С/С++ на этой платформие тоже поддерживается (более того, там есть концепция ILE — интегрированная языковая среда, позволяющая одну программу собирать из нескольких модулей на разных языках). Так что довольно скоро я (для себя в первую очередь, но разработка имела успех и у коллег) сделал сервисную программу (некий аналог виндовой dll) в которой было реализовано несколько алгоритмов на базе списков, которые достаточно легко можно использовать в RPG программах. Да, на это было потраченотнекоторое количество личного времени, но сейчас все это сильно упрощает разработку.

                      Потребуется что-то еще — будет что-то еще. Не проблема.
                        0
                        Это не «мания», это запросы рынка. Да, сейчас 80% программирования — это написание сайтов (то есть .Net Core + тонна js, как раз зоопарк этих фреймворков), 15% — это Ведроид, и на десктоп остается 5%. Мы, старички, как вид программеров вымираем. А, может, даже уже вымерли.
                          0
                          Ну не знаю… В процентах мне сложно судить, но три года назад достаточно много вакнсий было именно на серьезную разработку — промавтоматицация, телекоммуникации, финтех и прочие подобные вещи. Т.е. по «разработчик С/С++» вылазит очень много чего разного.
                            0
                            Разумеется, мои проценты тоже не из скрупулезного статистического исследования взяты. Так, на глазок.
                            Да, «разработчик C++» действительно вылазит, но объемы всё равно не сравнимы с «Full stack developer». А так много чего вылазит: Go, Rust, Java…
                              0
                              В целом соглашусь. Но для нас пока еще есть работа :-) И, надеюсь, еще будет. Кто-то все рано должен обеспечивать то, на что весь этот фуллстек опирается — самый нижний уровень.
        0

        Эээ, пытаться сортировать в стринглисте
        Да вы батенька извращенец

          0
          Почему?
          0
          Делал давно такую интеграцию через простой бинарный протокол поверх стандартных пайпов на java/fpc/delphi/c++ для linux/windows/wine github.com/speaking-fish?tab=repositories
            0
            Описание слишком краткое чтобы понять в чем проблема.

            С многопоточностью и межпоточной коммуникацией работал много. Как под Win раньше, так и под AS/400 сейчас.

            Непонятно в чем сложность

            реализация механизма передачи файла на основе первого варианта сервера (Multithreaded Pipe Server) оказывается слишком сложной.


            Наиболее простой на мой взгляд подход. Правда, скажу сразу — с пайпами не работал — немного пробовал, не понравилось (под мои задачи). Под Win использовал MailSlots и сокеты (как TCP, так и UDP). Под AS/400 богаче выбор — UnixSockets, socketpair, DataQueue, UserQueue.

            Наиболее простым оказалась синхронная реализация в отдельном потоке. Под Win задача была такая — с одной стороны N (около 30-ти плюс-минус) удаленных контроллеров, которые шлют некоторую информацию по UDP протоколу. С другой — M (3-5 обычно) интерфейсных клиентов, которые с этой информацией работают. Поток данных двунаправленный. Информация от контроллера должна быть некоторым образом обработана и на основе определенных признаков отдана одному или нескольким клиентам. Информация от клиента анализируется и передается (на основе анализа) на один из контроллеров.

            Все это работает в режиме 24/7. Т.е. постоянно, годами.

            Было три потока:

            — поток контроллеров с открытым UDP портом в который все контроллеры послылают информацию
            — поток клиентов в котором работал TCP сервер и был набор TCP соединений для уже подключившихся клиентов
            — поток обработки и маршрутизации

            Каждый из потоков создавал свой именованный MailSlot для приема входящей информации. Это оказалось проще чем обмен через память с синхронизацией. Любой поток мог послать данные любому потоку просто записав их в его MailSlot.

            На AS-ке задачи другие. Там распараллеливание обработки между задачами (поток не используем т.к. у сопровождения сложности с их мониторингом). Там классическая Batch машина. Одна задача берет данные и подготавливает задания на обработку и несколько обработчиков, которые эти данные параллельно обрабатывают и отдают результат.

            Коммуникация была комплексная. Для раздачи заданий использовалась UserQueue — очередь данных. Хороша тем, что в нее кто угодно может писать и кто угодно читать. Она не привязана к конкретному процессу. Головная задача выкладывала туда задания, а обработчики их оттуда разбирали по мере освобождения (выполнил текущее задание — отдал результат — взял из очереди следующее).

            Для результатов головная задача держала открытый UnixSocket (DGRAM — он не требует постоянного соединения). Туда обработчики скидывали результаты обработки.

            Тоже все работает.
              0
              С каких пор асинхронный вариант проще синхронного? В данном случае, лучше всего подходит как раз синхронный сервер, он элементарен в реализации.
                0
                Вот и я о том же… Зачатую асинхрон вообще не имеет смысла. Вот отдал ты в асинхронную обработку очередной блок данных и что дальше? Прежде чем начать обрабатывать следующий блок нужно убедиться что предыдущий получен адресатом. Т.е. тупо ждать подтверждения о корректном приеме, просто ничего не делая (да, можно начать обрабатывать следующий блок, но в ситуации когда истек таймаут подтверждения предыдущего, придется все отложить и перейти к обработке нештатной ситуации с предыдущим блоком, а это лишние сложности в коде).

                В таких ситуациях (для себя) решил так — обработка в одном потоке, передача — синхрон в отдельном потоке, который уже сам все проблемы разруливает.
                0
                Очень интересно, не забыть бы потом исходники изучить)

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое