Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.225.149.32] |
|
Сообщ.
#1
,
|
|
|
Создание именованых каналов. Автор: Стас "Hexorg" Пономарёв.
В этой статье мы будем разбирать связь двух приложений с помощью именованых каналов (named pipes). Рассмотрим типы каналов, а также создадим клиент и сервер. 1.Для чего именованые каналы нужны: Именовыные каналы являются средством «общения» программ. Они широко используются в UNIX-подобных системах, однако и в Windows они нашли применение. С помощью именованых каналов можно передать любую информацию, так как все каналы работают через файлы. Даже для чтения\записи данных в канале используется функция WinAPI для работы с файлами – ReadFile() и WriteFile(). Передаваться может переменная любого типа (Integer, Boolean, Tbitmap и так далее). Однако, надо заметить, что для передачи строки, она должна быть ограничена – String[40]; Иначе при чтении канала будет появляться ошибка. 2.Пример отправяемой переменной: Далее в статье я буду использовать отправляемую переменную типа TpipeData, этот тип будет такой: type TpipeData = packed record //Имя можно изменить, а тип packed record оставьте pEvent:byte; ClientName: string[40]; //Строка фиксированной длины обязательна Date:TdateTime; End; Как я уже сказал, здесь можно передавать любые переменные ==========СЕРВЕРНАЯ ЧАСТЬ========== 3.Использование потоков (TThread): Как такового события записи в канал нет, поэтому придётся запускать цикл чтения. Как известно, циклы подвешивают программы (а точнее зацикливают). Application.ProcessMessages при работе с функцией ReadFile не поможет, так как ReadFile будет выполнятся до тех пор, пока кто-то что-то не запишет. Поэтому надо использовать потоки (TThread). Хочу заметить, что обращение к визуальным компонентам (формам, кнопкам, лэйблам и так далее) из дополнительных потоков невозможно. Для того, чтобы полученные данные не остались в дополнительном потоке, надо его синхронизироватьс главным. Для этого есть функция TThread.Synchronize(method: TtreadMethod); Параметром этой функции надо передовать Какой-нибудь метод(нами созданный) дополнительного потока, где есть обращение к визуальным компонентам. А теперь напишем код нашего потока: type TPipeThread = class(TThread) private PipeData:TPipeData; //Переменная, описывающая сдруктуру данных канала Protected procedure Execute; override; //Здесь мы будем создавать канал и читать данные из него public procedure ShowData; //Здесь будем передовать данные форме constructor Create(CreateSuspended: Boolean); reintroduce; overload; //Создание потока end; 4.Работа с именоваными каналами: Напишем нашу процедуру TpipeThread.Execute; но сначала рассмотрим «настройку» наших каналов. Функция CreateNamedPipe доступна только серверу. Ей должны быть переданы следующие параметры: 1. lpName – имя нашего канала, а точнее его директория. Если сервер будет расположен на той-же машине, что и клиент, то первый параметр должен быть такой: '\\.\PIPE\ИмяКанала', где имя канала – ваше название, только латинскими буквами 2. dwOpenMode – работа канала может быть: PIPE_ACCESS_INBOUND – сервер может только читать канал PIPE_ACCESS_OTTBOUND – сервет может только записывать PIPE_ACCESS_DUPLEX – сервер может и писать и читать 3. 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, то есть – запакованный - наш канал работает с пакетами. 4. hMaxInstances – максимальное число одновременных подключений – любое ваше число, однако можно указать PIPE_UNLIMITED_INSTANCES для безконечного числа клиентов. 5. nOutBufferSize – разбер буфера чтения. Можно найти так – SizeOf(TpipeData) Это как раз тип нашего буфера. 6. nInBufferSize – размер буфера записи. Обычно такой же как и буфер чтения. 7. nDefaultTimeOut – максимальное время чтения (в милисекундах) 8. lpSecurityArtributes – просто пишем nil. Также нам понядобятся фцнкции ConnectNamedPipe, ReadFile и DisconnectNamedPipe, но их параметры мы рассмотрим в процессе написания кода. И так, а теперь наша процедура: procedure TPipeThread.Execute; var hPipe: THandle; //Указатель на наш канал bytesRead: DWORD; //Количество прочитанных байт begin 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 Synchronize(ShowData); //Синхронизируемся с главным потоком finally DisconnectNamedPipe(hPipe); //Закрываем канал, параметры – только указатель end; end; end; 5.Ну вот, это большая часть нашего сервера. Функцию 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 Data:TpipeData; 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; //Если произошла ошибка, выходим Data.pEvent:=0; Data.ClientName:='PipeClient'; Data.Date:=Time; //Функция Time записана в модуле SysUtils и возвращает текущее время if WriteFile(hPipe, Data, SizeOf(TpipeData), bytesWritten, nil) then DisconnectNamedPipe(hpipe); //Если удачно запиали, закрываем канал. end; Исходник клиента - так же можно найти в аттаче! Всё. Теперь вы знаете, как передовать кучу переменных своим программам из других! Автор: Стас "Hexorg" Пономарёв. Хочу поблагодарить www.sources.ru www.delphimasters.ru а также Alex-Eraser Прикреплённый файлPipes.zip (5.49 Кбайт, скачиваний: 434) |