Примеры работы с анонимными каналами

Вначале рассмотрим простой пример, в котором процесс-сервер выполняет следующие действия:

− создание анонимного канала;

− создание дочернего процесса;

− передача созданному дочернему процессу одного из дескрипторов созданного анонимного канала, используя для этого командную строку.

В этом случае дочерний процесс будет клиентом анонимного канала. Для определенности передадим клиенту дескриптор для записи в анонимный канал и оставим серверу дескриптор для чтения. Сначала приведем программу процесса-клиента анонимного канала.

Листинг 1. Пример процесса клиента анонимного канала.

// Клиент пишет в анонимный канал.

// Дескриптор анонимного канала передается клиенту через командную строку.

#include <windows.h>

#include <conio.h>

int main(int argc, char *argv[])

{

HANDLE hWritePipe;

// преобразуем символьное представление дескриптора в число

hWritePipe = (HANDLE)atoi(argv[1]);

// ждем команды о начале записи в анонимный канал

_cputs("Press any key to start communication.\n");

_getch();

// пишем в анонимный канал

for (int i = 0; i < 10; i++)

{

DWORD dwBytesWritten;

if (!WriteFile(

hWritePipe,

&i,

sizeof(i),

&dwBytesWritten,

NULL))

{

_cputs("Write to file failed.\n");

_cputs("Press any key to finish.\n");

_getch();

return GetLastError();

}

_cprintf("The number %d is written to the pipe.\n", i);

Sleep(500); }

// закрываем дескриптор канала

CloseHandle(hWritePipe);

_cputs("The process finished writing to the pipe.\n");

_cputs("Press any key to exit.\n");

_getch();

return 0;

}

 

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

 

Листинг 2. Пример процесса сервера анонимного канала.

// Сервер читает из анонимного канала. // Дескриптор анонимного канала передается // клинету через командную строку.

#include <windows.h> #include <conio.h>

int main() {

char lpszComLine[80]; // для командной строки

STARTUPINFO si;

PROCESS_INFORMATION pi;

HANDLE hWritePipe, hReadPipe, hInheritWritePipe;

// создаем анонимный канал if(!CreatePipe(

&hReadPipe, // дескриптор для чтения
&hWritePipe, // дескриптор для записи
NULL, // атрибуты защиты по умолчанию,

// в этом случае дескрипторы
// hReadPipe и hWritePipe ненаследуемые
0)) // размер буфера по умолчанию

{

_cputs("Create pipe failed.\n");

_cputs("Press any key to finish.\n");

_getch();

return GetLastError();

}

// делаем наследуемый дубликат дескриптора hWritePipe if(!DuplicateHandle(

GetCurrentProcess(), // дескриптор текущего процесса
hWritePipe, // исходный дескриптор канала

GetCurrentProcess(), // дескриптор текущего процесса
&hInheritWritePipe, // новый дескриптор канала
0, // этот параметр игнорируется

TRUE, // новый декскриптор наследуемый

DUPLICATE_SAME_ACCESS ))// доступ не изменяем

{

_cputs("Duplicate handle failed.\n");

_cputs("Press any key to finish.\n");

_getch();

return GetLastError();

}

// закрываем ненужный дескриптор CloseHandle(hWritePipe);

// устанавливаем атрибуты нового процесса ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO);

// формируем командную строку wsprintf(lpszComLine, "C:\\Client.exe %d", (int)hInheritWritePipe);

// запускаем новый консольный процесс if (!CreateProcess(

NULL, // имя процесса

lpszComLine, // командная строка

NULL, // атрибуты защиты процесса по умолчанию

NULL, // атрибуты защиты первичного потока по умолчанию

TRUE, // наследуемые дескрипторы текущего процесса

// наследуются новым процессом
CREATE_NEW_CONSOLE, // новая консоль
NULL, // используем среду окружения процесса предка

NULL, // текущий диск и каталог, как и в процессе предке

&si, // вид главного окна - по умолчанию

&pi // здесь будут дескрипторы и идентификаторы

// нового процесса и его первичного потока )) {

_cputs("Create process failed.\n"); _cputs("Press any key to finish.\n"); _getch();

return GetLastError(); }

// закрываем дескрипторы нового процесса CloseHandle(pi.hProcess); CloseHandle(pi.hThread);

// закрываем ненужный дескриптор канала CloseHandle(hInheritWritePipe);

// читаем из анонимного канала for (int i = 0; i < 10; i++) {

int nData;

DWORD dwBytesRead;

if (!ReadFile(

hReadPipe, &nData, sizeof(nData), &dwBytesRead, NULL)) {

_cputs("Read from the pipe failed.\n"); _cputs("Press any key to finish.\n"); _getch();

return GetLastError(); } _cprintf("The number %d is read from the pipe.\n", nData); }

// закрываем дескриптор канала CloseHandle(hReadPipe);

_cputs("The process finished reading from the pipe.\n");

_cputs("Press any key to exit.\n");

_getch();

return 0; }

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

 

Листинг 3. Пример процесса клиента анонимного канала.

// Клиент сначала пишет в анонимный канал, а потом читает из него.

// Дескриптор анонимного канала передается клиенту через командную строку.

#include <windows.h> #include <conio.h>

int main(int argc, char *argv[]) {

HANDLE hWritePipe, hReadPipe;

HANDLE hEnableRead; // для синхронизации обмена данными

char lpszEnableRead[] = "EnableRead";

// открываем событие, разрешающее чтение hEnableRead = OpenEvent(EVENT_ALL_ACCESS, FALSE, lpszEnableRead);

// преобразуем символьное представление дескрипторов в число hWritePipe = (HANDLE)atoi(argv[1]); hReadPipe = (HANDLE)atoi(argv[2]);

// ждем команды о начале записи в анонимный канал

_cputs("Press any key to start communication.\n"); _getch();

// пишем в анонимный канал for (int i = 0; i < 10; i++) {

DWORD dwBytesWritten; if (!WriteFile(

hWritePipe, &i,

sizeof(i),

&dwBytesWritten, NULL)) {

_cputs("Write to file failed.\n"); _cputs("Press any key to finish.\n"); _getch();

return GetLastError(); } _cprintf("The number %d is written to the pipe.\n", i); } _cputs("The process finished writing to the pipe.\n");

// ждем разрешения на чтение WaitForSingleObject(hEnableRead, INFINITE); // читаем ответ из анонимного канала for (int j = 0; j < 10; j++) {

int nData;

DWORD dwBytesRead;

if (!ReadFile(

hReadPipe, &nData, sizeof(nData), &dwBytesRead, NULL)) {

_cputs("Read from the pipe failed.\n"); _cputs("Press any key to finish.\n"); _getch();

return GetLastError(); } _cprintf("The number %d is read from the pipe.\n", nData); }

_cputs("The process finished reading from the pipe.\n"); _cputs("Press any key to exit.\n"); _getch();

// закрываем дескрипторы канала CloseHandle(hWritePipe); CloseHandle(hReadPipe); CloseHandle(hEnableRead);

return 0; }

 

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

 

Листинг 4. Пример процесса сервера анонимного канала.

// Сервер сначала читает из анонимного канала, а затем пишет в него.

// Дескриптор анонимного канала передается клиенту через командную строку.

#include <windows.h> #include <conio.h>

int main() {

char lpszComLine[80]; // для командной строки

HANDLE hEnableRead; // для синхронизации обмена данными

char lpszEnableRead[] = "EnableRead";

STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE hWritePipe, hReadPipe; SECURITY_ATTRIBUTES sa;

// создаем событие для синхронизации обмена данными hEnableRead = CreateEvent(NULL, FALSE, FALSE, lpszEnableRead);

// устанавливает атрибуты защиты канала
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL; // защита по умолчанию

sa.bInheritHandle = TRUE; // дескрипторы наследуемые

// создаем анонимный канал if(!CreatePipe(

&hReadPipe, // дескриптор для чтения
&hWritePipe, // дескриптор для записи
&sa, // атрибуты защиты по умолчанию,

// дескрипторы наследуемые
0)) // размер буфера по умолчанию

{

_cputs("Create pipe failed.\n"); _cputs("Press any key to finish.\n"); _getch();

return GetLastError(); }

// устанавливаем атрибуты нового процесса ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO);

// формируем командеую строку wsprintf(lpszComLine, "C:\\Client.exe %d %d",

(int)hWritePipe, (int)hReadPipe); // запускаем новый консольный процесс if (!CreateProcess(

NULL, // имя процесса

lpszComLine, // командная строка

NULL, // атрибуты защиты процесса по умолчанию

NULL, // атрибуты защиты первичного потока по умолчанию

TRUE, // наследуемые дескрипторы текущего процесса

// наследуются новым процессом
CREATE_NEW_CONSOLE, // новая консоль
NULL, // используем среду окружения процесса предка

NULL, // текущий диск и каталог, как и в процессе предке

&si, // вид главного окна - по умолчанию

&pi // здесь будут дескрипторы и идентификаторы

// нового процесса и его первичного потока )) {

_cputs("Create process failed.\n"); _cputs("Press any key to finish.\n"); _getch();

return GetLastError(); }

// закрываем дескрипторы нового процесса CloseHandle(pi.hProcess); CloseHandle(pi.hThread);

// читаем из анонимного канала for (int i = 0; i < 10; i++) {

int nData;

DWORD dwBytesRead;

if (!ReadFile(

hReadPipe, &nData, sizeof(nData), &dwBytesRead, NULL)) {

_cputs("Read from the pipe failed.\n"); _cputs("Press any key to finish.\n"); _getch();

return GetLastError(); } _cprintf("The number %d is read from the pipe.\n", nData); } _cputs("The process finished reading from the pipe.\n");

// даем сигнал на разрешение чтения клиентом SetEvent(hEnableRead);

// пишем ответ в анонимный канал for (int j = 10; j < 20; j++) {

DWORD dwBytesWritten; if (!WriteFile(

hWritePipe, &j,

sizeof(j),

&dwBytesWritten, NULL)) {

_cputs("Write to file failed.\n"); _cputs("Press any key to finish.\n"); _getch();

return GetLastError(); } _cprintf("The number %d is written to the pipe.\n", j); }

// закрываем дескрипторы канала CloseHandle(hReadPipe); CloseHandle(hWritePipe); CloseHandle(hEnableRead);

_cputs("The process finished writing to the pipe.\n");

_cputs("Press any key to exit.\n");

_getch();

return 0; }

 

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

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