Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Создание межпроцессной сети для взаимодействия небольших программ в духе книги Минского 'Общество разума'

Delphi , Программа и Интерфейс , Процессы и Сервисы

Минималистичный подход к межпроцессному взаимодействию в Linux для Delphi/Pascal

Введение

В этой статье мы рассмотрим различные способы организации межпроцессного взаимодействия (IPC) между небольшими консольными программами на Linux с использованием Object Pascal (Delphi/Free Pascal). Особое внимание уделим минималистичным подходам, которые позволяют эффективно передавать простые данные (например, булево значение) между процессами с минимальными накладными расходами.

Постановка задачи

Как видно из обсуждения на форуме, у пользователя Curt Carpenter возникла следующая задача:

- Имеются две независимые консольные программы PA и PB

- Программа PB должна предоставлять программе PA доступ к одному булеву значению (true/false)

- Для PA значение должно быть доступно только для чтения

- PB может изменять это значение в любой момент

- Требуется реализовать это взаимодействие с минимальным использованием ресурсов в Linux

Возможные решения

1. Использование файлов-семафоров

Самый простой подход, предложенный dbannon, предполагает использование временного файла:

// Программа PB (запись)
var
  f: TextFile;
begin
  AssignFile(f, '/tmp/ipc_semaphore');
  Rewrite(f);
  WriteLn(f, 'True'); // или 'False'
  CloseFile(f);
end;

// Программа PA (чтение)
var
  f: TextFile;
  s: string;
  value: Boolean;
begin
  if FileExists('/tmp/ipc_semaphore') then
  begin
    AssignFile(f, '/tmp/ipc_semaphore');
    Reset(f);
    ReadLn(f, s);
    CloseFile(f);
    value := (s = 'True');
  end;
end;

Плюсы:

- Простота реализации

- Не требует специальных библиотек

Минусы:

- Задержки из-за операций ввода-вывода - Проблемы с блокировкой файлов при параллельном доступе

- "Грязный" подход с точки зрения системного администрирования

2. Использование Shared Memory (разделяемая память)

Более эффективное решение, предложенное TRon, использует системные вызовы shmget/shmat:

// Общий модуль для PA и PB
unit SharedBool;

{$mode objfpc}{$H+}
{$modeswitch advancedrecords}

interface

uses
  BaseUnix, SysUtils;

type
  TSimpleSharedBool = record
  private
    FKey: TKey;
    FId: cint;
    FStore: PBoolean;
    procedure AllocateSMS;
    procedure AttachSMS;
    function GetValue: Boolean;
    procedure SetValue(AValue: Boolean);
  public
    class function Create(AKey: cint): TSimpleSharedBool; static;
    property Value: Boolean read GetValue write SetValue;
  end;

implementation

class function TSimpleSharedBool.Create(AKey: cint): TSimpleSharedBool;
begin
  Result.FKey := AKey;
  Result.AllocateSMS;
  Result.AttachSMS;
  Result.FStore^ := False;
end;

procedure TSimpleSharedBool.AllocateSMS;
begin
  FId := shmget(FKey, SizeOf(Boolean), IPC_CREAT or &666);
  if FId < 0 then
    Halt(1);
end;

procedure TSimpleSharedBool.AttachSMS;
begin
  FStore := shmat(FId, nil, 0);
  if PtrInt(FStore) = PtrInt(-1) then
    Halt(2);
end;

function TSimpleSharedBool.GetValue: Boolean;
begin
  Result := FStore^;
end;

procedure TSimpleSharedBool.SetValue(AValue: Boolean);
begin
  FStore^ := AValue;
end;

end.

Плюсы:

- Максимальная производительность (работа в памяти)

- Минимальные накладные расходы - Нативная поддержка в Linux

Минусы:

- Более сложная реализация

- Требует работы с системными вызовами

3. Использование SimpleIPC

Библиотека SimpleIPC, входящая в состав Free Pascal, предоставляет более высокоуровневый интерфейс:

// Программа PB (сервер)
uses
  SimpleIPC;

var
  Server: TSimpleIPCServer;
begin
  Server := TSimpleIPCServer.Create(nil);
  try
    Server.ServerID := 'my_ipc_server';
    Server.Global := True;
    Server.StartServer;
    Server.BooleanMessage['flag'] := True; // или False
    // Ожидаем соединений
    while True do
      Sleep(1000);
  finally
    Server.Free;
  end;
end;

// Программа PA (клиент)
uses
  SimpleIPC;

var
  Client: TSimpleIPCClient;
  FlagValue: Boolean;
begin
  Client := TSimpleIPCClient.Create(nil);
  try
    Client.ServerID := 'my_ipc_server';
    if Client.ServerRunning then
    begin
      FlagValue := Client.BooleanMessage['flag'];
      WriteLn('Flag value: ', FlagValue);
    end;
  finally
    Client.Free;
  end;
end;

Плюсы:

- Простота использования - Кроссплатформенность

- Встроенная поддержка в FPC

Минусы:

- Больший расход памяти (как отметил Curt Carpenter)

- Использует DBus в Linux, что может быть избыточно для простых задач

4. Использование Unix Domain Sockets

Альтернативный подход с использованием сокетов:

// Программа PB (сервер)
uses
  Sockets, BaseUnix;

var
  Socket: LongInt;
  Addr: TUnixSockAddr;
  Value: Boolean;
begin
  Socket := fpSocket(AF_UNIX, SOCK_STREAM, 0);
  Addr.sun_family := AF_UNIX;
  StrPCopy(Addr.sun_path, '/tmp/ipc_socket');
  fpBind(Socket, @Addr, SizeOf(Addr));
  fpListen(Socket, 1);

  Value := True; // Устанавливаем значение
  while True do
  begin
    // Принимаем соединение и отправляем значение
    var ClientSocket := fpAccept(Socket, nil, nil);
    fpSend(ClientSocket, @Value, SizeOf(Value), 0);
    fpClose(ClientSocket);
  end;
end;

// Программа PA (клиент)
uses
  Sockets, BaseUnix;

var
  Socket: LongInt;
  Addr: TUnixSockAddr;
  Value: Boolean;
begin
  Socket := fpSocket(AF_UNIX, SOCK_STREAM, 0);
  Addr.sun_family := AF_UNIX;
  StrPCopy(Addr.sun_path, '/tmp/ipc_socket');
  if fpConnect(Socket, @Addr, SizeOf(Addr)) = 0 then
  begin
    fpRecv(Socket, @Value, SizeOf(Value), 0);
    WriteLn('Received: ', Value);
  end;
  fpClose(Socket);
end;

Плюсы:

- Гибкость (можно расширить для передачи большего количества данных)

- Нативная поддержка в Linux

- Хорошая производительность

Минусы:

- Более сложная реализация, чем SimpleIPC

- Требует управления соединениями

5. Использование inotify (отслеживание изменений файлов)

Решение, предложенное duralast, основано на механизме inotify:

uses
  BaseUnix, SysUtils;

procedure WatchFile;
var
  fd, wd: cint;
  buf: array[0..4095] of char;
  len: cint;
begin
  fd := fpinotify_init();
  if fd < 0 then Exit;

  wd := fpinotify_add_watch(fd, '/tmp/ipc_flag', IN_MODIFY);
  if wd < 0 then Exit;

  while True do
  begin
    len := fpRead(fd, @buf, SizeOf(buf));
    if len > 0 then
    begin
      // Файл был изменен, можно читать значение
      var f: TextFile;
      AssignFile(f, '/tmp/ipc_flag');
      Reset(f);
      var Value: Boolean;
      ReadLn(f, Value);
      CloseFile(f);
      WriteLn('Value changed to: ', Value);
    end;
  end;

  fpinotify_rm_watch(fd, wd);
  fpClose(fd);
end;

Плюсы:

- Реагирует только на изменения, не требует polling

- Нативная поддержка в Linux

Минусы:

- Все еще требует работы с файлами

- Более сложная реализация, чем простой polling файла

Оптимальное решение для задачи Curt Carpenter

Исходя из требований Curt Carpenter (минимальное использование ресурсов, необходимость уведомления о "новой почте"), наиболее подходящими решениями являются:

  1. Shared Memory (shmget/shmat) - абсолютный минимум по использованию ресурсов, максимальная производительность.
  2. Unix Domain Sockets - немного больше накладных расходов, но более гибкое решение.
  3. SimpleIPC - если размер исполняемых файлов не критичен.

Для конкретной задачи передачи одного булева значения я рекомендую использовать разделяемую память, как это показано в примере TRon. Это дает:

- Минимальные накладные расходы

- Мгновенную передачу данных между процессами

- Нативную поддержку в Linux

Расширенный пример с использованием Shared Memory

Приведу более полный пример с обработкой ошибок и корректным освобождением ресурсов:

unit SharedBool;

{$mode objfpc}{$H+}
{$modeswitch advancedrecords}

interface

uses
  BaseUnix, SysUtils;

type
  TSharedBool = record
  private
    FKey: TKey;
    FId: cint;
    FStore: PBoolean;
    procedure CreateSharedMemory;
    procedure OpenSharedMemory;
    procedure AttachSharedMemory;
    procedure DetachSharedMemory;
    function GetValue: Boolean;
    procedure SetValue(AValue: Boolean);
  public
    constructor Initialize(AKey: Integer);
    destructor Finalize;
    property Value: Boolean read GetValue write SetValue;
    class function GenerateKey(const Path: string; ProjectID: Byte): TKey; static;
  end;

implementation

constructor TSharedBool.Initialize(AKey: Integer);
begin
  FKey := AKey;
  FId := -1;
  FStore := nil;

  // Пытаемся открыть существующий сегмент
  FId := shmget(FKey, SizeOf(Boolean), 0);
  if FId = -1 then
    CreateSharedMemory
  else
    OpenSharedMemory;

  AttachSharedMemory;
end;

destructor TSharedBool.Finalize;
begin
  if Assigned(FStore) then
    DetachSharedMemory;
end;

procedure TSharedBool.CreateSharedMemory;
begin
  FId := shmget(FKey, SizeOf(Boolean), IPC_CREAT or IPC_EXCL or &666);
  if FId = -1 then
    raise Exception.Create('Failed to create shared memory segment: ' + SysErrorMessage(fpGetErrNo));
end;

procedure TSharedBool.OpenSharedMemory;
begin
  FId := shmget(FKey, SizeOf(Boolean), 0);
  if FId = -1 then
    raise Exception.Create('Failed to open shared memory segment: ' + SysErrorMessage(fpGetErrNo));
end;

procedure TSharedBool.AttachSharedMemory;
begin
  FStore := shmat(FId, nil, 0);
  if PtrInt(FStore) = PtrInt(-1) then
    raise Exception.Create('Failed to attach shared memory: ' + SysErrorMessage(fpGetErrNo));

  // Инициализируем значение, если это новый сегмент
  if FStore^ <> False then
    FStore^ := False;
end;

procedure TSharedBool.DetachSharedMemory;
begin
  if shmdt(FStore) = -1 then
    raise Exception.Create('Failed to detach shared memory: ' + SysErrorMessage(fpGetErrNo));
  FStore := nil;
end;

function TSharedBool.GetValue: Boolean;
begin
  if not Assigned(FStore) then
    raise Exception.Create('Shared memory not attached');
  Result := FStore^;
end;

procedure TSharedBool.SetValue(AValue: Boolean);
begin
  if not Assigned(FStore) then
    raise Exception.Create('Shared memory not attached');
  FStore^ := AValue;
end;

class function TSharedBool.GenerateKey(const Path: string; ProjectID: Byte): TKey;
begin
  Result := ftok(PChar(Path), ProjectID);
  if Result = -1 then
    raise Exception.Create('Failed to generate key: ' + SysErrorMessage(fpGetErrNo));
end;

end.

Пример использования:

// Программа PB (устанавливает значение)
var
  SharedBool: TSharedBool;
begin
  SharedBool.Initialize(TSharedBool.GenerateKey('/tmp', 42));
  try
    SharedBool.Value := True; // или False
    WriteLn('Press Enter to exit...');
    ReadLn;
  finally
    SharedBool.Finalize;
  end;
end;

// Программа PA (читает значение)
var
  SharedBool: TSharedBool;
begin
  SharedBool.Initialize(TSharedBool.GenerateKey('/tmp', 42));
  try
    WriteLn('Current value: ', SharedBool.Value);
    WriteLn('Press Enter to exit...');
    ReadLn;
  finally
    SharedBool.Finalize;
  end;
end;

Заключение

Для реализации минималистичного межпроцессного взаимодействия в Linux на Delphi/Free Pascal существует несколько подходов:

  1. Для самых простых случаев - файлы-семафоры или inotify
  2. Для максимальной производительности - разделяемая память (shmget/shmat)
  3. Для баланса между простотой и функциональностью - Unix Domain Sockets или SimpleIPC

В контексте задачи Curt Carpenter по созданию "межпроцессной сети" для множества небольших программ оптимальным выбором является использование разделяемой памяти, так как этот подход:

- Обеспечивает минимальные накладные расходы

- Позволяет мгновенно передавать данные между процессами

- Нативно поддерживается Linux

- Масштабируется для более сложных сценариев

Примеры кода, приведенные в статье, можно адаптировать для конкретных нужд, добавляя обработку ошибок и дополнительные функции по мере необходимости.

Создано по материалам из источника по ссылке.

Статья описывает различные методы межпроцессного взаимодействия в Linux для Delphi/Pascal, уделяя особое внимание минималистичным подходам, таким как файлы-семафоры, разделяемая память, SimpleIPC, Unix Domain Sockets и inotify.


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




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


:: Главная :: Процессы и Сервисы ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-06-27 01:34:29/0.0043289661407471/0