Московский физико-технический институт
Опубликован: 12.12.2007 | Доступ: свободный | Студентов: 5584 / 1902 | Оценка: 4.34 / 4.14 | Длительность: 13:57:00
ISBN: 978-5-94774-827-7
Лекция 7:

Межпроцессный обмен

< Лекция 6 || Лекция 7: 12 || Лекция 8 >

Организация каналов в ОC Windows

Анонимные каналы

Анонимные каналы в Windows - это полудуплексное средство потоковой передачи байтов между родственными процессами. Они функционируют в пределах локальной вычислительной системы и хорошо подходят для перенаправления выходного потока одной программы на вход другой. Анонимные каналы реализованы при помощи именованных каналов с уникальными именами.

Анонимные каналы создаются процессом сервером при помощи функции CreatePipe:

BOOL CreatePipe(
  PHANDLE hReadPipe,                       // описатель для чтения
  PHANDLE hWritePipe,                      // описатель для записи
  LPSECURITY_ATTRIBUTES lpPipeAttributes,  // атрибуты безопасности
  DWORD nSize                              // размер канала
);

Функция CreatePipe возвращает два описателя (дескриптора) для чтения и записи в канал. После создания канала необходимо передать клиентскому процессу эти дескрипторы (или один из них), что обычно делается с помощью механизма наследования.

Для наследования описателя нужно, чтобы дочерний процесс создавался функцией CreateProcess с флагом наследования TRUE. Предварительно нужно создать наследуемые описатели. Это можно, например, сделать путем явной спецификации параметра bInheritHandle структуры SECURITY_ATTRIBUTES при создании канала.

Другим способом является создание наследуемого дубликата имеющегося описателя при помощи функции DuplicateHandle и последующая передача его создаваемому процессу через командную строку или каким-либо иным образом.

Получив нужный описатель, клиентский процесс, так же как и сервер, может далее взаимодействовать с каналом при помощи функций ReadFile и WriteFile. По окончании работы с каналом оба процесса должны закрыть описатели при помощи функции CloseHandle.

Прогон программы общения процесса через анонимный канал с самим собой

#include <windows.h>
#include <stdio.h>

int main()
{
	HANDLE hRead, hWrite;
	char BufIn[100], *BufOut = "0123456789";
	int BufSize = 100;
	int BytesOut = 10, BytesIn = 5, i;
	
	if(!CreatePipe(&hRead, &hWrite, NULL, BufSize))  
		printf("Create pipe failed.\n");
	
	WriteFile(hWrite, BufOut, BytesOut, &BytesOut, NULL);
	 printf("Write into pipe %d bytes : ", BytesOut);
        for(i=0; i<BytesOut;i++) printf("%c",BufOut[i]);
	 printf("\n");
	
	ReadFile(hRead, BufIn, BytesIn, &BytesIn, NULL);
	 printf("Read from pipe %d bytes : ", BytesIn);
	
	 for(i=0; i<5;i++) printf("%c",BufIn[i]);
	 
	return 0;
}

В приведенной программе создается анонимный канал, в него записывается строка цифр, затем часть этой строки читается и выводится на экран.

Прогон программы общения через анонимный канал клиента и сервера

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

Именованные каналы

Именованные каналы являются объектами ядра ОС Windows, позволяющими организовать межпроцессный обмен не только в изолированной вычислительной системе, но и в локальной сети. Они обеспечивают дуплексную связь и позволяют использовать как потоковую модель, так и модель, ориентированную на сообщения. Обмен данными может быть синхронным и асинхронным.

Каналы должны иметь уникальные в рамках сети имена в соответствии с правилами именования ресурсов в сетях Windows (Universal Naming Convention, UNC), например, \\ServerName\pipe\PipeName. Для общения внутри одного компьютера имя записывается в форме \\.\pipe\PipeName, где "." обозначает локальную машину. Слово "pipe" в составе имени фиксировано, а PipeName - имя, задаваемое пользователем. Эти имена, подобно именам открытых файлов, не являются именами объектов. Они относятся к пространству имен под управлением драйверов файловых систем именованных каналов ( \Winnt\System32\Drivers\Npfs.sys ), привязанному к специальному объекту устройству \Device\NamedPipe, на которое есть ссылка в каталоге глобальных имен объектов \??\Pipe (эти последние имена "видит" утилита WinObj).

Имена созданных именованных каналов можно перечислить с помощью свободно распространяемой утилиты pipelist с сайта http://www.sysinternals.com. Поскольку имена каналов интегрированы в общую структуру имен объектов, приложения могут открывать именованные каналы с помощью функции CreateFile и взаимодействовать с ними через функции ReadFile и WriteFile.

Использование именованных каналов

Сервер создает именованный канал при помощи функции CreateNamedPipe (см. MSDN).

Помимо имени канала в форме, описанной выше, в число параметров функции входят: флаг, указывающий модель передачи данных; параметр, определяющий синхронный или асинхронный режим работы канала, а также указывающий, должен ли канал быть односторонним или двухсторонним. Кроме того, имеется необязательный дескриптор защиты, запрещающий несанкционированный доступ к именованному каналу, и параметр, определяющий максимальное число одновременных соединений по данному каналу.

Повторно вызывая CreateNamedPipe, можно создавать дополнительные экземпляры этого же канала.

После вызова CreateNamedPipe сервер выполняет вызов ConnectNamedPipe и ждет отклика от клиентов, которые соединяются с каналом при помощи функции CreateFile или CallNamedPipe, указывая при вызове имя созданного сервером канала. Легальный клиент получает описатель, представляющий клиентскую сторону именованного канала, и работа серверной функции ConnectNamedPipe на этом завершается.

После того как соединение по именованному каналу установлено, клиент и сервер могут использовать его для чтения и записи данных через Win32-функции ReadFile и WriteFile.

Прогон программы общения двух процессов через именованный канал

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

Сервер

#include <stdio.h>
#include <windows.h>

void main()
{
	PROCESS_INFORMATION piProcInfo;
	STARTUPINFO SI;
	char * ClientName = "client.exe";
	HANDLE hPipe;
	LPTSTR PipeName = TEXT("\\\\.\\pipe\\MyPipe");
	char Buff[255];
	DWORD iNumBytesToRead = 255, i;
	
	ZeroMemory(&SI, sizeof(STARTUPINFO));
	SI.cb = sizeof(STARTUPINFO); 
	ZeroMemory(&piProcInfo, sizeof(piProcInfo));

	hPipe = CreateNamedPipe( 
          PipeName,			   // имя канала
          PIPE_ACCESS_DUPLEX,       // чтение и запись из канала
          PIPE_TYPE_MESSAGE |       // передача сообщений по каналу
          PIPE_READMODE_MESSAGE |   // режим чтения сообщений 
          PIPE_WAIT,                // синхронная передача сообщений 
          PIPE_UNLIMITED_INSTANCES, // число экземпляров канала 
          4096,			   // размер выходного буфера
          4096,		          // размер входного буфера  
          NMPWAIT_USE_DEFAULT_WAIT, // тайм-аут клиента 
          NULL);                    // защита по умолчанию
	
	if (hPipe == INVALID_HANDLE_VALUE) 
    {
	 printf("CreatePipe failed: error code %d\n", (int)GetLastError());
        return;
    }
	
	if((CreateProcess(NULL, ClientName, NULL, NULL, FALSE, 0, NULL, NULL, &SI, &piProcInfo))==0)
	{
	printf("create client process: error code %d\n", (int)GetLastError());
	return;
	}
	
	if((ConnectNamedPipe(hPipe, NULL))==0)
	{
		printf("client could not connect\n");
		return;
	}
	
	ReadFile(hPipe, Buff, iNumBytesToRead, &iNumBytesToRead, NULL);
	for(i=0; i<iNumBytesToRead; i++) printf("%c",Buff[i]);
 }
7.1.

Клиент

#include <stdio.h>
#include <windows.h>

void main()
{
	HANDLE hPipe;
	LPTSTR PipeName = TEXT("\\\\.\\pipe\\MyPipe");
	DWORD NumBytesToWrite;
	char Buff[] = "Message from Client";

	hPipe = CreateFile( 
         PipeName,	      // имя канала
         GENERIC_READ |  // чтение и запись в канал
         GENERIC_WRITE, 
         0,              // нет разделяемых операций 
         NULL,           // защита по умолчанию
         OPEN_EXISTING,  // открытие существующего канала 
         0,              // атрибуты по умолчанию
         NULL);          // нет дополнительных атрибутов 

	WriteFile(hPipe, Buff, strlen(Buff), &NumBytesToWrite, NULL);
	
}

В данном примере сервер создает канал, затем запускает процесс-клиент и ждет соединения. Далее он читает сообщение, посланное клиентом.

Помимо перечисленных выше система представляет еще ряд полезных функций для работы с именованными каналами. Для копирования данных из именованного канала без удаления их из канала используется функция PeekNamedPipe. Функция TransactNamedPipe применяется для объединения операций чтения и записи в канал в одну операцию, которая называется транзакцией. Имеются информационные функции для определения состояния канала, например, GetNamedPipeHandleState или GetNamedPipeInfo. Полный перечень находится в MSDN.

Заключение

Организация совместной деятельности и общения процессов является важной и актуальной задачей. К основным способам межпроцессного обмена традиционно относят каналы и разделяемую память, для организации которых используют разделяемые ресурсы. Анонимные каналы поддерживают потоковую модель, в рамках которой данные представляют собой неструктурированную последовательность байтов. Именованные каналы, поддерживающие как потоковую модель, так и модель, ориентированную на сообщения, обеспечивают обмен данными не только в изолированной вычислительной среде, но и в локальной сети.

< Лекция 6 || Лекция 7: 12 || Лекция 8 >
Ирина Оленина
Ирина Оленина
Николай Сергеев
Николай Сергеев

Здравствуйте! Интересует следующий момент. Как осуществляется контроль доступа по тому или иному адресу с точки зрения обработки процессом кода процесса. Насколько я понял, есть два способа: задание через атрибуты сегмента (чтение, запись, исполнение), либо через атрибуты PDE/PTE (чтение, запись). Но как следует из многочисленных источников, эти механизмы в ОС Windows почти не задействованы. Там ключевую роль играет менеджер памяти, задающий регионы, назначающий им атрибуты (PAGE_READWRITE, PAGE_READONLY, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_NOACCESS, PAGE_GUARD: их гораздо больше, чем можно было бы задать для сегмента памяти) и контролирующий доступ к этим регионам. Непонятно, на каком этапе может включаться в работу этот менеджер памяти? Поскольку процессор может встретить инструкцию: записать такие данные по такому адресу (даже, если этот адрес относится к региону, выделенному менеджером памяти с атрибутом, например, PAGE_READONLY) и ничего не мешает ему это выполнить. Таким образом, менеджер памяти остается в стороне не участвует в процессе...