Создание именных каналов

Материал из DRKB

Создание именованных каналов[править | править код]

В этой статье мы будем разбирать связь двух приложений с помощью именованных каналов (named pipes). Рассмотрим типы каналов, а также создадим клиент и сервер.

Для чего именованные каналы нужны[править | править код]

Именованные каналы являются средством «общения» программ. Они широко используются в UNIX-подобных системах, однако и в Windows они нашли применение. С помощью именованных каналов можно передать любую информацию, так как все каналы работают через файлы. Даже для чтения\записи данных в канале используется функция WinAPI для работы с файлами - ReadFile() и WriteFile(). Передаваться может переменная любого типа (Integer, Boolean, Tbitmap и так далее). Однако, надо заметить, что для передачи строки, она должна быть ограничена - String[40]; Иначе при чтении канала будет появляться ошибка.

Пример отправляемой переменной[править | править код]

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

type TPipeData = packed record  // Имя можно изменить, а тип packed record оставьте
  pEvent: Byte;
  ClientName: string[40]; // Заметьте, ограниченная строка
  Date: TDateTime;
end;

Как я уже сказал, здесь можно передавать любые переменные


СЕРВЕРНАЯ ЧАСТЬ[править | править код]

Использование потоков (TThread)[править | править код]

Как такового события записи в канал нет, поэтому придётся запускать цикл чтения. Как известно, циклы подвешивают программы (а точнее зацикливают). Application.ProcessMessages при работе с функцией ReadFile не поможет, так как ReadFile будет выполнятся до тех пор, пока кто-то что-то не запишет. Поэтому надо использовать потоки (TThread). Хочу заметить, что обращение к визуальным компонентам (формам, кнопкам, лэйблам и так далее) из дополнительных потоков невозможно. Для того, чтобы полученные данные не остались в дополнительном потоке, надо его синхронизировать с главным. Для этого есть функция TThread.Synchronize(method: TThreadMethod); Параметром этой функции надо передавать Какой-нибудь метод (нами созданный) дополнительного потока, где есть обращение к визуальным компонентам. А теперь напишем код нашего потока:

type
  TPipeThread = class(TThread)
  private
    PipeData: TPipeData; // Вот он – наша приёмная переменная, а точнее буфер.
  protected
    procedure Execute; override; // Здесь мы будем создавать канал и читать данные из него
  public
    procedure ShowData; // Здесь будем передавать данные форме
    constructor Create(CreateSuspended: Boolean); reintroduce; overload; // Создание потока
  end;


Работа с именованными каналами[править | править код]

Здесь мы рассмотрим нашу процедуру TPipeThread.Execute; но сначала рассмотрим «настройку» наших каналов. Функция CreateNamedPipe доступна только серверу. Ей должны быть переданы следующие параметры:

  • lpName - имя нашего канала, а точнее его директория. Если сервер будет расположен на той-же машине, что и клиент, то первый параметр должен быть такой: '\\.\PIPE\ИмяКанала', где имя канала - ваше название, только латинскими буквами
  • dwOpenMode - работа канала может быть:
    • PIPE_ACCESS_INBOUND - сервер может только читать канал
    • PIPE_ACCESS_OTTBOUND - сервер может только записывать
    • PIPE_ACCESS_DUPLEX - сервер может и писать и читать
  • dwPipeMode - режим канала здесь говорится о синхронности\асинхронности канала, и о том, каким методом будет производится обмен данных в канале. Значения:
    • PIPE_WAIT - синхронный канал
    • PIPE_NOWAIT - асинхронный канал
    • PIPE_READMODE_BYTE - метод чтения - байты
    • PIPE_READMODE_MESSAGE - метод чтения пакеты
    • PIPE_TYPE_BYTE - тип канала - байты
    • PIPE_TYPE_MESSAGE - тип канала пакеты.
В dwPipeMode надо передавать три параметра, например, в нашем примере: PIPE_WAIT or PIPE_READMODE_MESSAGE or PIPE_TYPE_MESSAGE. А теперь, давайте вспомним наш тип TPipeData - он у нас packed record, то есть - пакет. Поэтому наш канал работает с пакетами.
  • hMaxInstances - максимальное число одновременных подключений - любое ваше число, однако можно указать PIPE_UNLIMITED_INSTANCES для бесконечного числа клиентов.
  • nOutBufferSize - размер буфера чтения. Можно найти так - SizeOf(TPipeData) Это как раз тип нашего буфера.
  • nInBufferSize - размер буфера записи. Обычно такой же как и буфер чтения.
  • nDefaultTimeOut - максимальное время чтения (в милисекундах)
  • lpSecurityArtributes - просто пишем nil.

Также нам понадобятся функции ConnectNamedPipe, ReadFile и DisconnectNamedPipe, но их параметры мы рассмотрим в процессе написания кода. И так, а теперь наша процедура:

procedure TPipeThread.Execute;
var
  hPipe: THandle; // Дескриптор нашего канала
  bytesRead: DWORD;  // Количество прочитанных байт
begin
  try
    hPipe := CreateNamedPipe('\\.\PIPE\OurPipe', // Наше имя
      PIPE_ACCESS_INBOUND, // сервер может только читать канал
      PIPE_WAIT or PIPE_READMODE_MESSAGE or PIPE_TYPE_MESSAGE, // Синхронная работа, метод чтения - пакеты
      PIPE_UNLIMITED_INSTANCES,        // Бесконечно много клиентов
      SizeOf(TPipeData), // размер буфера чтения
      SizeOf(TPipeData), // размер буфера записи
      100,               // Тайм-аут
      nil);              // Атрибуты безопасности.
    if hPipe = INVALID_HANDLE_VALUE then Exit; // Если не удалось создать канал, то выходим

    while true do // Теперь читаем, пока не надоест!
    begin
      try
        ConnectNamedPipe(hPipe, nil); // Подключаемся к каналу, второй параметр
                                      // нужен только, если вместо PIPE_WAIT вы указали PIPE_NOWAIT

        // Теперь читаем, параметры – дескриптор канала, наш буфер,
        // кол-во прочитанных байт, и последнее опять таки только для PIPE_NOWAIT.
        if ReadFile(hPipe, PipeData, SizeOf(TPipeData), bytesRead, nil) then
        begin
          Synchronize(ShowData); // Синхронизируемся с главным потоком
        end;
      finally
        DisconnectNamedPipe(hPipe); // Закрываем канал, параметры – только дескриптор
      end;
    end;
  finally
     CloseHandle(hPipe); // Освобождаем дескриптор
  end;
end;


Ну вот, это большая часть нашего сервера. Функцию ShowData можно сделать такую:

procedure TPipeThread.ShowData;
begin
  case PipeData.pEvent of
    1: Form1.Memo1.Lines.Add('====== Событие 1 ======');
    2: Form1.Memo1.Lines.Add('========= Событие 2 ==========');
  end;
  Form1.Memo1.Lines.Add('Программа ' + PipeData.Clientname + ' открыла канал');
  Form1.Memo1.Lines.Add(DateTostr(PipeData.Date));
  Form1.Memo1.Lines.Add('');
end;


КЛИЕНТ[править | править код]

Теперь пришло время написать наш клиент. Так как нам нужно только подключиться и выкинуть данные серверу, а затем сразу отключиться, то весь код клиента можно поместить в одну процедуру:

procedure SendToServer; // Замечу, что должна быть переменная такого же
                        // типа TPipeData, как и у сервера. Допустим, это Data
var
  hPipe: THandle;
  bytesWritten: DWORD;
begin
  hPipe := CreateFile('\\.\PIPE\OurPipe', // Как видите, здесь мы подключаемся даже не к каналу, а к файлу
    GENERIC_WRITE, // Только запись
    FILE_SHARE_READ or FILE_SHARE_WRITE, // Обмениваемся чтением\записью
    nil, // Атрибуты безопасности
    OPEN_EXISTING,   // Канал должен быть создан
    0, 0);
  if hPipe = INVALID_HANDLE_VALUE then Exit; // Если произошла ошибка, выходим

  if WriteFile(hPipe, Data, SizeOf(TPipeData), bytesWritten, nil) then
    CloseHandle(hPipe); // Если удачно записали, закрываем канал.
end;


Author: Стас Пономарёв
Source: Взято из http://forum.sources.ru
ID: 02240