| [Домашняя страничка][Резюме][Фотоальбом][Диплом][Научные статьи] | |
|
|
|
2.2. Концепции доступа к СУБД на уровне коммуникаций между процессами |
|
2.2.1. Именованные каналы 2.2.1.1.
Программные каналы в UNIX Традиционным
средством взаимодействия и
синхронизации процессов в ОС UNIX
являются программные каналы (pipes).
Теоретически программный канал
позволяет взаимодействовать любому
числу процессов, обеспечивая дисциплину
FIFO (first-in-first-out). Другими словами, процесс,
читающий из программного канала,
прочитает те данные, которые были
записаны в программный канал наиболее
давно. В традиционной реализации
программных каналов для хранения данных
использовались файлы. В современных
версиях ОС UNIX для реализации
программных каналов применяются другие
средства, в частности, очереди сообщений.
Различаются
два вида программных каналов -
именованные и неименованные.
Именованный программный канал может
служить для общения и синхронизации
произвольных процессов, знающих имя
данного программного канала и имеющих
соответствующие права доступа.
Неименованным программным каналом
могут пользоваться только создавший его
процесс и его потомки (необязательно
прямые). Для
создания именованного программного
канала (или получения к нему доступа)
используется обычный файловый
системный вызов open.
Для создания же неименованного
программного канала существует
специальный системный вызов pipe
(исторически более ранний). Однако после
получения соответствующих дескрипторов
оба вида программных каналов
используются единообразно с помощью
стандартных файловых системных вызовов read,
write
и close.
Системный
вызов pipe
имеет следующий синтаксис: pipe(fdptr);
где
fdptr
- это указатель на массив из двух целых
чисел, в который после создания
неименованного программного канала
будут помещены дескрипторы,
предназначенные для чтения из
программного канала (с помощью
системного вызова read)
и записи в программный канал (с помощью
системного вызова write).
Дескрипторы неименованного
программного канала - это обычные
дескрипторы файлов, т.е. такому
программному каналу соответствуют два
элемента таблицы открытых файлов
процесса. Поэтому при последующем
использовании системных вызовов read
и write
процесс совершенно не обязан отличать
случай использования программных
каналов от случая использования обычных
файлов (собственно, на этом и основана
идея перенаправления ввода/вывода и
организации конвейеров). Для
создания именованных программных
каналов (или получения доступа к уже
существующим каналам) используется
обычный системный вызов open.
Основным отличием от случая открытия
обычного файла является то, что если
именованный программный канал
открывается на запись, и ни один процесс
не открыл тот же программный канал для
чтения, то обращающийся процесс
блокируется (усыпляется) до тех пор, пока
некоторый процесс не откроет данный
программный канал для чтения (аналогично
обрабатывается открытие для чтения).
Повод для использования такого режима
работы состоит в том, что, вообще говоря,
бессмысленно давать доступ к
программному каналу на чтение (запись)
до тех пор, пока некоторый другой
процесс не обнаружит готовности писать
в данный программный канал (соответственно
читать из него). Понятно, что если бы эта
схема была абсолютной, то ни один
процесс не смог бы начать работать с
заданным именованным программным
каналом (кто-то должен быть первым).
Поэтому в числе флагов системного
вызова open
имеется флаг NO_DELAY, задание которого
приводит к тому, что именованный
программный канал открывается
независимо от наличия соответствующего
партнера. Запись
данных в программный канал и чтение
данных из программного канала (независимо
от того, именованный он или
неименованный) выполняются с помощью
системных вызовов read
и write.
Отличие от случая использования обычных
файлов состоит лишь в том, что при записи
данные помещаются в начало канала, а при
чтении выбираются (освобождая
соответствующую область памяти) из
конца канала. Как всегда, возможны
ситуации, когда при попытке записи в
программный канал оказывается, что
канал переполнен, и тогда обращающийся
процесс блокируется, пока канал не
разгрузится (если только не указан флаг
нежелательности блокировки в числе
параметров системного вызова write),
или когда при попытке чтения из
программного канала оказывается, что
канал пуст (или в нем отсутствует
требуемое количество байтов информации),
и тогда обращающийся процесс
блокируется, пока канал не загрузится
соответствующим образом (если только не
указан флаг нежелательности блокировки
в числе параметров системного вызова read).
Окончание
работы процесса с программным каналом (независимо
от того, именованный он или
неименованный) производится с помощью
системного вызова close.
В основном, действия ядра при закрытии
программного канала аналогичны
действиям при закрытии обычного файла.
Однако имеется отличие в том, что при
выполнении последнего закрытия канала
по записи все процессы, ожидающие чтения
из программного канала (т.е. процессы,
обратившиеся к ядру с системным вызовом read
и отложенные по причине недостатка
данных в канале), активизируются с
возвратом кода ошибки из системного
вызова. Это совершенно оправданно в
случае неименованных программных
каналов: если достоверно известно, что
больше нечего читать, то зачем
заставлять далее ждать чтения. Для
именованных программных каналов это
решение не является очевидным, но
соответствует общей политике ОС UNIX о
раннем предупреждении процессов. 2.2.1.2.
Программные каналы
в Win32 Идеальным
механизмом для потоковой передачи
данных в Win32 является канал (pipe), т. е.
выделенная "линия" передачи данных,
соединяющая два процесса. То, что
отправляется в канал одним процессом,
может быть принято другим; поток данных
может быть одно- или двунаправленным.
Данные в канал заносятся с
использованием функции Win32 WriteFile, а
считываются с помощью функции ReadFile. В
Windows NT реализуются два типа каналов:
именованные и анонимные. Именованным
каналам, как и файлам, присваиваются
имена. Именованный канал формируется
функцией CreateNamedPipe, которая кроме этого
передает в вызывающую программу
дескриптор канала. После того как
именованный канал сформирован, можно
подключить к нему клиентский процесс,
передав имя канала в функцию CreateFile.
Именованные каналы можно использовать
для связи между двумя процессами,
функционирующими на одном ПК, либо для
связи между процессом, выполняющимся на
сервере сети, и процессом на рабочей
станции. Анонимные
каналы не имеют имен. Анонимный канал
формируется путем вызова из программы
функции CreatePipe; в вызывающую программу
передаются дескриптор записи и
дескриптор чтения. Если к анонимному
каналу нужно подключить еще одну
программу, то необходимо передать ей
дескриптор канала из первой программы.
Поскольку дескрипторы каналов Win32
привязаны к процессам (иными словами,
как и указатели, действительны только в
контексте конкретного процесса), то в
одном из процессов необходимо создать
копию дескриптора, которой сможет
воспользоваться клиент. Это сопряжено с
дополнительной работой, поэтому
большинство программистов предпочитают
полагаться на именованные каналы, за
исключением случаев, когда клиентский
процесс представляет собой дочерний
процесс, в который дескриптор канала
можно передать с помощью механизма
наследования. Поскольку анонимные
каналы непригодны для обмена данными
через сеть, их можно использовать для
обмена данными лишь между программами,
работающими на одном ПК. Две
программы, PServer и PClient, приведены в
качестве примера использования
именованного канала для
однонаправленной пересылки данных. Один
раз в секунду в программе PServer
генерируется случайная величина от 1 до
10 и соответствующим образом
устанавливается длина линейки в
индикаторе прохождения обработки. Если
к каналу подключен клиентский процесс,
то случайное число из PServer записывается
в канал. Число считывается из канала
программой PClient и используется для
обновления ее собственного индикатора
прохождения обработки. Когда PServer и PClient
работают совместно, показания их
индикаторов прохождения обработки
меняются синхронно - наглядное
свидетельство того, что на пути потока
информации, передаваемого по каналу, нет
препятствий. Программа
PServer рассчитана только на среду Windows NT,
поскольку в Windows 95 нет средств для работы
с серверами именованных каналов.
Программа PClient успешно функционирует в
средах Windows 95 и Windows NT. Если
из функции CreateNamedPipe в вызывающую
программу передается верный дескриптор,
то в PServer с целью пересылки в канал
непрерывной последовательности байтов
запускается фоновый поток. Пересылка
выполняется следующим образом:
::WriteFile
(hPipe, &val, 1,
&dwBytesWritten,
NULL); Перед
отправкой данных в канал из потока
организуется вызов локальной функции по
имени IsClientConnected, которая проверяет,
действительно ли клиент подключен к
каналу. Благодаря этому сохраняется
пропускная способность при
неработающей программе PClient; заодно
предотвращается и переполнение канала.
Данные канала хранятся в буфере,
выделенном операционной системой. С
помощью функции CreateNamedPipe можно указать
размеры входного и выходного буферов
канала, но эти значения рассматриваются
лишь как рекомендательные; реальные
размеры определяются системой. В ходе
неофициального тестирования выяснилось,
что если указать размеры этих буферов,
равными нулю, то в Windows NT 4.0 организуются
буферы размером около 1 Мбайт каждый. В
Windows NT существует особенность: если
клиентский процесс отключается от
именованного канала, то последующие
процессы не могут быть подключены к
каналу до тех пор, пока из процесса,
образовавшего канал, не будет вызвана
функция API DisconnectNamedPipe. Эта функция
вызывается из программы PServer в том
случае, если происходит разрыв
соединения, для того чтобы к каналу
можно было подключить второй экземпляр
PClient. Как эта программа получает
информацию о разрыве соединения? Как вы
помните, раз в секунду в программе PServer
выполняется проверка состояния
соединения, а очередной байт данных
выводится в канал только в том случае,
если к другому его концу подключен
клиент. Если работа PClient прервалась в
момент, когда эта программа подключена к
каналу, то в течение следующей секунды
или около того об этом станет известно
программе PServer и будет вызвана функция
DisconnectNamedPipe. После
того как соединение установлено, из PClient
запускается собственный фоновой поток,
в котором байты из канала непрерывно
считываются и передаются основному
потоку через функцию PostMessage:
BYTE byte;
DWORD dwBytesRead = 999;
while (dwBytesRead && ::ReadFile
(hPipe, &byte, 1, &dwBytesRead, NULL))
::PostMessage(hWnd,
WM_USER_UPDATE, (WPARAM) byte, 0); В
ответ на сообщения WM_USER_UPDATE основным
потоком обновляется индикатор
прохождения обработки. Фоновый цикл
бесконечен и выполняется до тех пор,
пока программа PServer не будет завершена и
соединение разорвано. В этом случае из
функции ReadFile в вызывающую программу
передается значение FALSE (Ложь), цикл while
завершается и из данного потока для
основного потока выдается сообщение
WM_USER_CONNECTION_LOST, извещающее его о разрыве
соединения. В ответ основным потоком
заново активизируется кнопка Connect, что
позволяет восстановить соединение в
случае повторного запуска PServer.
Когда
средствами программы PServer формируется
именованный канал, флаг PIPE_TYPE_BYTE
используется для организации байт-канала
(byte pipe) - канала, данные из которого
считываются и записываются блоками
размером 1 и более байт, в точности как из
файла. Фрагмент
исходного текста программы PServe,
предназначенный для формирования
именованного канала. // //
Сформировать именованный канал // m_hPipe = ::CreateNamedPipe (
"\\\\.\\pipe\\ipcdemo",
// Имя канала
PIPE_ACCESS_OUTBOUND,
// Доступ только для записи
PIPE_TYPE_BYTE | PIPE_NOWAIT,
// Запись байтов, без ожидания
1,
// По одному экземпляру за один раз,
// пожалуйста
0,
// Размер выходного буфера (байт)
0,
// Размер входного буфера (байт) 0, // Время ожидания (мс)
NULL
// Использовать дескриптор
безопасности
// по умолчанию ); // //
Проверка ошибок. // if (m_hPipe == INVALID_HANDLE_VALUE) { MessageBox ("Unable to
create a named pipe.", "Error", MB_OK | MB_ICONSTOP); return -1; } if (m_hPipe == NULL) { MessageBox ("Windows 95 doesn't support named pipe
servers. " \ "This
application must be run under Windows NT.", "Error", MB_OK |
MB_ICONSTOP);
return -1; } Фрагмент
исходного текста программы PClient,
предназначенный для подключения к
именованному каналу
CString strServerName; m_wndServerName.GetWindowText (strServerName); CString string = "\\\\" + strServerName +
"\\pipe\\ipcdemo"; m_hPipe = ::CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL); if (m_hPipe == INVALID_HANDLE_VALUE) { CString strErrMsg = "Unable to open " + string + ". "\ "Make sure the Pipe Server application is running on " \ "server " +
strServerName + " and try again."; MessageBox (strErrMsg,
"Error", MB_OK | MB_ICONEXCLAMATION);
return; } 2.2.2. Сокеты (программные гнезда) Операционная система UNIX с самого
начала проектировалась как сетевая ОС в
том смысле, что должна была обеспечивать
явную возможность взаимодействия
процессов, выполняющихся на разных
компьютерах, соединенных сетью передачи
данных. Главным образом, эта возможность
базировалась на обеспечении файлового
интерфейса для устройств на основе
понятия специального файла. Другими
словами, два или более процессов,
располагающихся на разных компьютерах,
могли договориться о способе
взаимодействия на основе использования
возможностей соответствующих сетевых
драйверов. На уровне ядра механизм программных гнезд поддерживается тремя составляющими: компонентом уровня программных гнезд (независящим от сетевого протокола и среды передачи данных), компонентом протокольного уровня (независящим от среды передачи данных) и компонентом уровня управления сетевым устройством (см. Рис 2.3). Допустимые комбинации протоколов и драйверов задаются при конфигурации системы, и во время работы системы их менять нельзя. Легко видеть, что по своему духу организация программных гнезд близка к идее потоков, поскольку основана на разделении функций физического управления устройством, протокольных функций и функций интерфейса с пользователями. Однако это менее гибкая схема, поскольку не допускает изменения конфигурации "на ходу". Взаимодействие процессов на основе программных гнезд основано на модели "клиент-сервер". Процесс-сервер "слушает (listens)" свое программное гнездо, одну из конечных точек двунаправленного пути коммуникаций, а процесс-клиент пытается общаться с процессом-сервером через другое программное гнездо, являющееся второй конечной точкой коммуникационного пути и, возможно, располагающееся на другом компьютере. Ядро поддерживает внутренние соединения и маршрутизацию данных от клиента к серверу. Программные гнезда с общими
коммуникационными свойствами, такими
как способ именования и протокольный
формат адреса, группируются в домены.
Наиболее часто используемыми являются
"домен системы UNIX" для процессов,
которые взаимодействуют через
программные гнезда в пределах одного
компьютера, и "домен Internet" для
процессов, которые взаимодействуют в
сети в соответствии с семейством
протоколов TCP/IP. Выделяются два типа программных гнезд -
гнезда с виртуальным соединением (stream
sockets) и датаграммные гнезда (datagram sockets).
При использовании программных гнезд с
виртуальным соединением обеспечивается
передача данных от клиента к серверу в
виде непрерывного потока байтов с
гарантией доставки. При этом до начала
передачи данных должно быть установлено
соединение, которое поддерживается до
конца коммуникационной сессии.
Датаграммные программные гнезда не
гарантируют абсолютной надежной,
последовательной доставки сообщений и
отсутствия дубликатов пакетов данных -
датаграмм. Но для использования
датаграммного режима не требуется
предварительное дорогостоящее
установление соединений, и поэтому этот
режим во многих случаях является
предпочтительным. Система по умолчанию
сама обеспечивает подходящий протокол
для каждой допустимой комбинации "домен-гнездо".
Например, протокол TCP используется по
умолчанию для виртуальных соединений, а
протокол UDP - для датаграммного способа
коммуникаций. Для работы с программными гнездами
поддерживается набор специальных
библиотечных функций. Рассмотрим кратко
интерфейсы и семантику этих функций. Для создания нового программного
гнезда используется функция socket:
sd =
socket(domain, type, protocol); где значение параметра domain
определяет домен данного гнезда,
параметр type
указывает тип создаваемого
программного гнезда (с виртуальным
соединением или датаграммное), а
значение параметра protocol
определяет желаемый сетевой протокол.
Возвращаемое функцией значение
является дескриптором программного
гнезда и используется во всех
последующих функциях. Вызов функции close
приводит к закрытию указанного
программного гнезда. Для связывания ранее созданного
программного гнезда с именем
используется функция bind:
bind(sd,
socknm, socknlen);
Здесь sd
- дескриптор ранее созданного
программного гнезда, socknm
- адрес структуры, которая содержит имя (идентификатор)
гнезда, соответствующее требованиям
домена данного гнезда и используемого
протокола (в частности, для домена
системы UNIX имя является именем объекта в
файловой системе, и при создании
программного гнезда действительно
создается файл), параметр socknlen
содержит длину в байтах структуры socknm
(этот параметр необходим, поскольку
длина имени может весьма различаться
для разных комбинаций "домен-протокол").
С помощью функции connect
процесс-клиент запрашивает систему
связаться с существующим программным
гнездом (у процесса-сервера): connect(sd,
socknm, socknlen);
Смысл параметров такой же, как у
функции bind,
однако в качестве имени указывается имя
программного гнезда, которое должно
находиться на другой стороне
коммуникационного канала. Если тип
гнезда с дескриптором sd
является датаграммным, то функция connect
служит только для информирования
системы об адресе назначения пакетов,
которые в дальнейшем будут посылаться с
помощью функции send;
никакие действия по установлению
соединения в этом случае не
производятся. Функция listen
предназначена для информирования
системы о том, что процесс-сервер
планирует установление виртуальных
соединений через указанное гнездо: listen(sd,
qlength); Здесь sd
- это дескриптор существующего
программного гнезда, а значением
параметра qlength
является максимальная длина очереди
запросов на установление соединения,
которые должны буферизоваться системой,
пока их не выберет процесс-сервер. Для выборки процессом-сервером
очередного запроса на установление
соединения с указанным программным
гнездом служит функция accept:
nsd =
accept(sd, address, addrlen); Параметр sd
задает дескриптор существующего
программного гнезда, для которого ранее
была выполнена функция listen;
address
указывает на массив данных, в который
должна быть помещена информация,
характеризующая имя программного
гнезда клиента, со стороны которого
поступает запрос на установление
соединения; addrlen
- адрес, по которому находится длина
массива address.
Если к моменту выполнения функции accept
очередь запросов на установление
соединений пуста, то процесс-сервер
откладывается до поступления запроса.
Выполнение функции приводит к
установлению виртуального соединения, а
ее значением является новый дескриптор
программного гнезда, который должен
использоваться при работе через данное
соединение. По адресу addrlen
помещается реальный размер массива
данных, которые записаны по адресу address.
Процесс-сервер может продолжать "слушать"
следующие запросы на установление
соединения, пользуясь установленным
соединением. Для передачи и приема данных через
программные гнезда с установленным
виртуальным соединением используются
функции send
и recv:
count
= send(sd, msg, length, flags); count
= recv(sd, buf, length, flags); В функции send
параметр sd
задает дескриптор существующего
программного гнезда с установленным
соединением; msg
указывает на буфер с данными, которые
требуется послать; length
задает длину этого буфера. Наиболее
полезным допустимым значением
параметра flags
является значение с символическим
именем MSG_OOB, задание которого означает
потребность во внеочередной посылке
данных. "Внеочередные" сообщения
посылаются помимо нормального для
данного соединения потока данных,
обгоняя все непрочитанные сообщения.
Потенциальный получатель данных может
получить специальный сигнал и в ходе его
обработки немедленно прочитать
внеочередные данные. Возвращаемое
значение функции равняется числу
реально посланных байтов и в нормальных
ситуациях совпадает со значением
параметра length.
В функции recv
параметр sd
задает дескриптор существующего
программного гнезда с установленным
соединением; buf
указывает на буфер, в который следует
поместить принимаемые данные; length
задает максимальную длину этого буфера.
Наиболее полезным допустимым значением
параметра flags
является значение с символическим
именем MSG_PEEK, задание которого приводит к
переписи сообщения в пользовательский
буфер без его удаления из системных
буферов. Возвращаемое значение функции
является числом байтов, реально
помещенных в buf.
Для посылки и приема сообщений в
датаграммном режиме используются
функции sendto
и recvfrom:
count
= sendto(sd, msg, length, flags, socknm, socknlen); count
= recvfrom(sd, buf, length, flags, socknm, socknlen); Смысл параметров sd,
msg,
buf
и lenght
аналогичен смыслу одноименных
параметров функций send
и recv.
Параметры socknm
и socknlen
функции sendto
задают имя программного гнезда, в
которое посылается сообщение, и могут
быть опущены, если до этого вызывалась
функция connect.
Параметры socknm
и socknlen
функции recvfrom
позволяют серверу получить имя
пославшего сообщение процесса-клиента. Наконец, для немедленной ликвидации
установленного соединения используется
системный вызов shutdown:
shutdown(sd,
mode); Вызов этой функции означает, что нужно немедленно остановить коммуникации либо со стороны посылающего процесса, либо со стороны принимающего процесса, либо с обеих сторон (в зависимости от значения параметра mode). Действия функции shutdown отличаются от действий функции close тем, что, во-первых, выполнение последней "притормаживается" до окончания попыток системы доставить уже отправленные сообщения. Во-вторых, функция shutdown разрывает соединение, но не ликвидирует дескрипторы ранее соединенных гнезд. Для окончательной их ликвидации все равно требуется вызов функции close. [Содержание]
|
|
[Диплом индекс][Доклад][Реферат Рус][Реферат Укр][Abstract] |
|
| Copyright (c) 1998-2001, Alexandr S. Lukichov
|
|