Раздел 4. Управление памятью в операционных системах

4.1. Классификация способов организации памяти

В данном разделе будет рассмотрен седьмой уровень иерархической модели ОС - уровень управления виртуальной памятью.

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

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

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


4.2. Управление физической памятью

Все, что связано с управлением физической памятью, относится к историческим аспектам ОС, поскольку современные ОС используют виртуальную память.

Из существующих систем MS DOS попадает в классификации в разряд однозадачных систем управления физической памятью.

4.2.1. Однозадачные системы

4.2.1.1. Простые и оверлейные системы

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

Операционная система

Программа

Свободная область

Как правило, вся свободная память передается прикладной программе, но может быть и возвращена операционной системе.*

Если места в памяти для загрузки программы не хватает, то организуется оверлейная программа.

Оверлеи грузятся поочередно в одно и то же место памяти.

MS DOS - типичный пример такой системы управления памятью.

Недостаток такой системы - нет защиты ОС от пользователя.

Возможные средства защиты:

 

  1. доступ к ОС должен выполняться только через примитивы ОС;
  2. введение граничного регистра.

 

 

-------------------        --------------

|        ЦП       |        |Операционная|

|-----------------|        |  система   |

||регистр границы||----> А --------------

|-----------------|        |            |

-------------------        |            |

                            \/\/\/\/\/\/

Доступ к памяти всегда происходит с проверкой на значение А:

if (M <= A) {

    ИСКЛЮЧЕНИЕ_ПО_ОШИБКЕ

} else {

ДОСТУП К М;

}

 

где М - адрес памяти для доступа.

 

4.2.1.2. Сегментация памяти

 

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

Хотя это понятие используется теперь более широко.

Так, для обеспечения размера памяти в 1 МВ требуется 20 разрядов регистра. Как осуществить доступ, если регистры имеют по 16 разрядов?

Введено следующее понятие СЕГМЕНТ:СМЕЩЕНИЕ. Любой адрес представлен таким вектором. Если адрес начала сегмента выровнять на начало параграфа и ограничить сегмент размером 64 К, то для адресации можно использовать два 16-ти разрядных регистра.

 

 

 

 

 

Для реализации данного способа адресации используются сегментные регистры CS, DS, SS, ES, а также базовые и индексные регистры BX, BP, SI, DI.

4.2.1.3. Блочная организация памяти

Вся память организована по блочному принципу. Блоки организуются в цепочку и могут находиться в состояние "занят" или "свободен".

В начале каждого блока находится МСВ - memory control block.

------------          -----------

|    ОС    |          |  байт   | последний/непоследний

------------          ---------------------

|//////////|занят     |         |         | хозяин блока

------------          ---------------------

|          |свободен  |         |         | размер блока

|          |          -------------------------- этот адрес

------------          |     резерв 11 байтов   | возвращается

|//////////|занят     -------------------------- <----    при

------------          |       сам блок         | выделении па-

|          |свободен  |                        | мяти по запросу

------------

Признак последнего блока - 'z' - 5AH; признак непоследнего блока 'm' - 4DH.

Хозяин блока - адрес PSP, если блок свободен, то поле = 0. Размер блока представлен в параграфах - участках по 16 байтов.

Размер MCB - 16 байтов - параграф.

Адрес начала цепочки MCB хранится в переменной DOS и может быть получен с помощью функции 52H прерывания DOS,

MOV AH, 52H

INT 21H

Свободные блоки являются основой динамического управления памятью. В любой ОС имеются примитивы:

ВЫДЕЛИТЬ_БЛОК()            GETMEM(ADDR, SIZE);

ОСВОБОДИТЬ_БЛОК()          FREEMEM(ADDR, SIZE);

4.2.1.4. Стратегии управления памятью (введение)

 

При управлении памятью необходимо рассматривать следующие стратегии:

 

  1. стратегии выборки;
  2. стратегии размещения;
  3. стратегии замещения.

Стратегии 1 и 3 рассматриваются при управлении виртуальной памятью, стратегия 2 рассматривается как при управлении физической, так и при управлении виртуальной памятью.

Стратегии выборки отвечают на вопрос КОГДА загружать блок.

Два основных направления существует в этих стратегиях:

 

  1. по запросу;
  2. с упреждением.

Стратегии размещения отвечают на вопрос КУДА поместить блок.

Три основных направления существуют в этих стратегиях:

 

  1. в первый подходящий свободный блок;
  2. в наиболее подходящий свободный блок;
  3. в наименее подходящий свободный блок.

Стратегии замещения отвечают на вопрос КОГО ВЫТОЛКНУТЬ, если для размещения нового блока не хватает места.

4.2.2. Многозадачные системы

В этих системах физическая память разделяется между программами. Т.е. создаются РАЗДЕЛЫ, выделяемые для программ.

Разделы могут быть фиксированными и переменными по размеру.

4.2.2.1. Фиксированные разделы (неперемещаемые программы)

Существует два варианта использования фиксированных разделов:

 

  1. фиксированные разделы и неперемещаемые программы;
  2. фиксированные разделы и перемещаемые программы.

 

В первом случае программы привязываются к одному из разделов и могут выполняться только в своих разделах.

----------                        В свободные разделы не могут

|   ОС   |                        загружаться программы, при-

----------  -----                 вязанные к другим разделам.

|   Р1   |<--||| Очередь к Р1     Поэтому может оказаться, что,

----------  -----                 например, есть большая

|   Р2   |  -----                 очередь к разделу Р1 и нет

|        |<--||| Очередь к Р2     очереди к разделу Р3.

----------  -----                 Если иметь специальный про-

|        |  -----                 цессор ввода/вывода, то

|   Р3   |<--||| Очередь к Р3     можно одновременно выполнять

|        |  -----                 программу в одном разделе и

----------                        перезагружать программу в

                                  другом разделе.

4.2.2.2. Фиксированные разделы (перемещаемые программы)

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

 

 

 

 

 

---------------------   ----------

|        ЦП         |   |   ОС   |

|-------------------|   ----------

||Регистр границы 1||-- |   Р1   |<--

|-------------------| ->----------  |

|-------------------|   |   Р2   |  | ----- Одна

||Регистр границы 2||-- |        |<----|||  очередь

|-------------------| ->----------  | ----- ко всем

---------------------   |        |  |       разделам

ЦП выполняет задание    |   Р3   |<-|

раздела 2               ----------

Защита памяти осуществляется использованием двух регистров границ - верхней и нижней границ.

4.2.2.3. Переменные разделы

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

-----------                 При завершении задачи в памяти

|   ОС    | Очередь задач   появляется "дыра".

-----------  -----------

|   Т1    |<--Т5|Т6|Т7|     Существует два способа устранения

|         |  -----------    этого недостатка:

-----------                 - слияние дыр;

|   Т2    |                 - сборка мусора.

-----------

|   Т3    |           Сборка мусора - серьезная проблема, т.к.

|         |           - задачи должны быть перемещаемыми;

-----------           - задачи должны приостанавливаться во время

|   Т4    |             перемещения.

|---------|

|своб.обл.|

|---------|

 

 

-----------              -----------             -----------

|   ОС    |              |   ОС    |             |   ОС    |

-----------              -----------             -----------

|   Т1    |              |   Т1    |             |   Т1    |

|         |              |         |             |         |

-----------              -----------             -----------

|/////////|              |/////////|             |   Т4    |

-----------   Слияние    |/////////|             -----------

|/////////|     дыр      |/////////|   Сборка    |/////////|

|/////////|              |/////////|   мусора    |/////////|

-----------              -----------             |/////////|

|   Т4    |              |   Т4    |             |/////////|

-----------              -----------             |/////////|

|/////////|              |/////////|             |/////////|

-----------              -----------             -----------

 

 

4.2.2.4. Свопинг

Мы уже рассмотрели с вами мультипрограммирование с разделением времени. Такое же мультипрограммирование можно рассмотреть и относительно памяти машины.

В мультипрограммировании с разделением времени программа снимается с процессора по прерыванию от таймера или по запросу примитива ядра.

В мультипрограммировании с разделением памяти программа не только снимается с процессора, но и выгружается из памяти. А с диска загружается новая программа для выполнения в течение следующего кванта времени. Это и есть свопинг.

----------

|   ОС   | Образы задач, хранящиеся на диске.

---------- ---------- ---------- ----------

|Область | |   А    | |   В    | |   С    |

|свопинга| |        | |        | |        |

|        | |        | |        | |--------|

|        | |--------| |        |

|        |            |--------|

----------

Недостатком свопинга является наличие потерь времени на перезагрузку задач с диска.

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

 

 

 

 

 

 


4.3. Управление виртуальной памятью

4.3.1. Общая характеристика виртуальной памяти

                4.3.1.1. Определения виртуальной памяти и иерархия видов памяти

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

Такая возможность обеспечивается за счет дисковой памяти. Поэтому в системах управления виртуальной памятью задействованы все уровни иерархии памяти:

                     ----------------------

     |       ^       |   Внешняя память   |

     |       |       ----------------------

     |       |            ^          v

     |       |       ----------------------

     |       |       | Оперативная память |

     |       |       ----------------------

     |       |            ^          v

     v       |       ----------------------

  скорость объем     |     Кэш-память     |

                     ----------------------

Память любого уровня можно рассматривать как кэш памяти более высокого уровня.

Кэш-память основана на предвосхищении наиболее вероятного использования данных процессором.

Эффективность кэша зависит от:

 

  1. физических свойств;
  2. алгоритмов кэша;
  3. программы (условных переходов).

 

Типичный размер кэша - 1 % ОЗУ. При этом вероятность успеха при доступе составляет 90-95 %.

 

4.3.1.2. Особенности виртуальной памяти

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

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

 

Таким образом, в системе существует два адреса: V - виртуальный и R - реальный. Виртуальный адрес отображается в реальный адрес с помощью “механизма отображения”.

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

Способы распределения физической памяти, которые были рассмотрены ранее, относились к связанному распределению. Т.е. объект должен занимать в памяти непрерывную последовательность ячеек.

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

Однако, несвязанность памяти нельзя доводить до абсурда, т.к. с увеличением степени несвязанности увеличиваются затраты на механизм отображения.

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

4.3.1.3. Механизм отображения виртуальных адресов в физические

Механизм основан на ведении таблицы отображения виртуального адреса V в физический адрес R.

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

Поэтому рассматриваются блоки виртуальной памяти, которым соответствуют блоки физической памяти.

Чем больше размер блоков, тем меньше затрат на механизм отображения. Но увеличиваются затраты на обмен с внешней памятью.

Если блоки памяти могут иметь переменный размер, то говорят о сегментной организации виртуальной памяти.

Если блоки имеют фиксированный размер, то говорят о страничной организации виртуальной памяти.

Механизм же отображения один как для сегментной, так и для страничной организации.

Виртуальный адрес - это вектор:

V = (b, d),

Где:        b - это номер блока;

d - это смещение внутри блока.

b - это смещение внутри таблицы отображения блоков.

Строка таблицы содержит адрес блока в памяти и другую информацию о блоке.

Схема отображения имеет следующий вид:

 

 

4.3.2. Страничная организация виртуальной памяти

4.3.2.1. Одноуровневая страничная организация

Страничная виртуальная память состоит из блоков фиксированного размера, называемых страницами. Размер страниц обычно составляет от 512 байтов до 4К. Например, в процессоре Intel размер страницы равен 4К.

 

Виртуальный адрес равен

V = (p, d),

Где         р - номер страницы;

d - смещение внутри страницы.

При фиксированном размере блока проще реализуются стратегии размещения страниц в памяти.

Строка таблицы страниц обычно содержит следующие данные:

p

m

a

n

s

p   -    бит присутствия страницы;

m  -    бит модификации страницы;

a   -    права доступа к странице;

n   -    адрес в физической памяти, если страница загружена;

s    -    адрес во внешней памяти, если страница не загружена.

Таблица страниц загружается вместе с задачей и хранится в оперативной памяти.

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

4.3.2.2. Двухуровневая страничная организация

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

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

V = (k, p, d),

Где         k - строка каталога, содержащая адрес таблицы страниц;

р - смещение в выбранной таблице страниц;

d - смещение в физической памяти.

 

 

 

 

Например, в процессоре Intel с 32-х разрядными регистрами каталог может содержать информацию о 1024 таблицах (1 К).

Каждая таблица содержит информацию о 1024 страницах (1 К). Поскольку каждая страница имеет размер 4 К, то одна таблица страниц может адресовать память 4 К х 1 К = 4 М.

Один каталог адресует память 4 М х 1 К = 4 Г.

4.3.2.3. Совместное использование страниц процессами

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

Обычно программы содержат процедуры и данные.

Данные программ чаще имеют характер неразделяемых данных, а доступ к разделяемым данным как раз и организуется рассматриваемым методом.

Неизменяемые процедуры называются реентерабельными или повторновходимыми.

Таким образом, каждая страница должна быть классифицирована как разделяемая или нет.

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

 

 

 

4.3.3. Сегментная организация виртуальной памяти

                4.3.3.1. Одноуровневая сегментная организация

В отличие от страниц сегменты могут иметь различные размеры. Виртуальный адрес

V = (s, d),

Где         s - номер сегмента;

d - смещение внутри сегмента.

 

Схема отображения остается прежней.

Например, архитектура Intel использует следующую терминологию: виртуальный адрес представляет собой вектор СЕЛЕКТОР:СМЕЩЕНИЕ, таблица отображения - это таблица дескрипторов; строка таблицы - дескриптор сегмента.

В общем случае дескриптор содержит следующие данные:

p

b

l

t

a

r

e

p   -    бит присутствия;

b   -    адрес сегмента в памяти, если загружен;

l    -    длина сегмента;

t    -    тип сегмента; (чтение, запись, код, данные)

a   -    бит доступа;

r    -    привилегии;

e   -    адрес во внешней памяти, если не загружен.

  1. Если сегмента нет в памяти, то вырабатывается прерывание по отсутствию сегмента.
  2. Если длина сегмента меньше величины смещения в виртуальном адресе, то вырабатывается прерывание по выходу за пределы сегмента.
  3. Если тип операции не соответствует типу сегмента, то вырабатывается прерывание по ошибке.
  4. Если уровень привилегий запроса не соответствует уровню привилегий дескриптора, то вырабатывается прерывание по защите сегмента.

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

 

 


4.3.3.2. Комбинированная сегментно-страничная организация (вместе с таблицей процессов)

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

Виртуальный адрес в этом случае состоит из трех компонентов:

 

V = (s, p, d),

 

Где         s - смещение в таблице сегментов процесса, указывающее на адрес таблицы страниц сегмента;

p - смещение в таблице страниц сегмента,  указывающее на адрес страницы в памяти;

d - смещение от начала страницы.

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

 

 

 

 

 

 

 

 


4.3.4. Стратегии управления виртуальной памятью

4.3.4.1. Главные задачи управления виртуальной памятью

Управление виртуальной памятью решает три главные задачи:

1)                   Трансляция виртуального адресного пространства на физическую память. Данный вопрос был рассмотрен выше.

2)                   Сброс содержимого физической памяти на диск и подкачка страниц с диска в физическую память при необходимости. Данный вопрос будет рассмотрен сейчас.

3)                   Кроме того, система управления должна содержать набор сервисов для приложений, в частности средства разделения памяти. Это будет рассмотрено в следующем параграфе.

Стратегии управления виртуальной памятью похожи на ранее перечисленные стратегии управления физической памятью и решают следующие задачи.

4.3.4.2. Стратегии решения второй главной задачи

1) Стратегии вталкивания (выборки) (fetch policy) определяют, в какой момент следует загрузить страницу или сегмент с диска в оперативную память. Два конкурирующих алгоритма вталкивания существуют.

Вталкивание по запросу (когда произошла ошибка доступа к странице)

Достоинство: в памяти находятся только те страницы или сегменты, которые действительно нужны процессу.

Недостаток: потери времени из-за ожидания загрузки страницы или сегмента.

Однако, загружая несколько соседних страниц вместе с запрашиваемой, страницей, можно уменьшить число операций ввода/вывода, как делается, например, в Windows 2000.

Вталкивание с упреждением

Достоинство: повышение скорости выполнения программы.

Из-за принципов локальности высока вероятность загрузки именно тех блоков, которые потребуются программе.

С понижением цен на аппаратуру, обеспечивающим возможность использования дополнительной памяти, потери от неправильного прогноза уменьшаются.

2) Стратегии размещения (placement policy)  определяют, в какие участки оперативной памяти наиболее целесообразно поместить загружаемую страницу или сегмент.

В системах со страничной организацией решение этой проблемы выполняется очень просто, т.к. страницы имеют фиксированный размер. Это обусловило широкое распространение систем со страничной организацией виртуальной памяти.

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

 

Три возможных алгоритма используются:

 

  1. первая подходящая зона;
  2. лучшее соответствие - выбирается такая зона, чтобы после размещения сегмента остаток был минимальным; данный способ ведет к появлению большого числа маленьких неиспользуемых зон.
  3. больший излишек - выбирают такую зону, чтобы остаток был максимальным; используют, чтобы устранить недостаток предыдущего метода.

3) Стратегии выталкивания (замещения) (replacement policy) определяют, какие сегменты или страницы можно вытолкнуть из памяти, чтобы освободить место для загрузки нового сегмента или страницы.

 

Стратегии выталкивания существуют для двух способов распределения памяти процессам:

 

  1. постоянное распределение, когда процессу предоставляется фиксированный размер памяти;
  2. переменное распределение, когда размер памяти, предоставляемой процессу, меняется во времени.

 

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

При локальных стратегиях для выталкивания ищутся страницы, принадлежащие только процессу, активизировавшему выталкивание.

Первый вариант делает всю систему более чувствительной к поведению отдельных процессов, поэтому является менее предпочтительным.

4.3.4.3. Стратегии выталкивания с постоянным распределением

Рассмотрим указанные стратегии в порядке улучшения их характеристик.

1. Выталкивание случайной страницы

 

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

2. Выталкивание первой пришедшей страницы

 

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

По своей эффективности данный алгоритм близок к первому алгоритму.

3. Выталкивание реже всего используемой страницы

 

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

Бит обращения сбрасывается, если при проверке он оказался установленным.

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

 

4. Выталкивание дольше всех не использовавшейся страницы

 

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

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

4.3.4.4. Стратегии выталкивания с переменным распределением

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

Два вида локальности существует.

 

  1. Временная локальность. К ячейкам, к которым недавно было обращение, с большой вероятностью будут обращения в ближайшем будущем.
  2. Пространственная локальность. В случае обращения к некоторой ячейке, с большой вероятностью можно ожидать обращения к соседним ячейкам.

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

Среди реализуемых методов, использующих данный вывод, рассмотрим два алгоритма:

 

  1. Алгоритм, поддерживающий постоянной частоту прерываний по отсутствию страницы;
  2. Алгоритм рабочего множества (working set).

Первый алгоритм

 

Рассмотрим зависимость частоты прерываний по отсутствию страницы от доли страниц, находящихся в памяти.

 

Если частота прерываний больше Fg, то это означает, что памяти маловато и процессу предоставляется память, чтобы частота стала равной Fg.

Если частота прерываний меньше Fg, то это означает, что у процесса можно отнять некоторую часть памяти.

Алгоритм рабочего множества

 

Под рабочим множеством понимается набор страниц, к которым процесс обращается наиболее часто.

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

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

Сложность алгоритма определяется сложностью идентификации рабочего множества.

Практически рабочим множеством называют набор страниц, к которым процесс обращался в интервале времени W. Величину W называют окном рабочего множества. Т.е. размер рабочего множества зависит от размера окна.

Выбор размера окна сильно влияет на характеристики алгоритма.

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

Ниже приводится иллюстрация динамики изменения рабочего множества в процессе выполнения программы.

Кривые подъема связаны с загрузкой новых страниц при переходе от одного алгоритма программы к другому.

Кривые спада иллюстрируют выгрузку страниц, использованных предыдущим алгоритмом и не используемых текущим алгоритмом.

Интервалы без изменений количества загруженных страниц иллюстрируют работу программы внутри текущего алгоритма.

 

 

 

 


4.4. Управление памятью в современных ОС

4.4.1. Архитектура управления памятью

Базовые принципы управления памятью, изложенные выше, широко используются во всех современных ОС. Но в каждой из систем существуют нюансы их использования.

Так из трех существующих моделей памяти:

 

  1. модели сегментированной памяти;
  2. модели памяти со сплошной адресацией;
  3. модели памяти со страничной организацией,

 

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

С появлением 32-х разрядных систем широко стала использоваться сплошная адресация. Диапазон адресов 4 гигабайта достаточен для большинства сегодняшних приложений.

 

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

Фрагментация, имеющая место при сплошной адресации, устраняется использованием страничной организации.

Кроме того, страничная организация позволяет ограничить доступ процессов к чужой памяти.

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

NetWare 4.0 имеет заново спроектированную систему управления памятью. В ней реализован режим сплошной адресации и механизм страничного обмена. Не смотря на страничную организацию, виртуальная память в системе не реализована.

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

ОС семейства Windows использует модель памяти со сплошной адресацией. Поддерживается страничная организация виртуальной памяти.

OS/2 управляет памятью аналогично Windows.

4.4.2. Защита памяти

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

Для этого используются следующие основные способы:

1)                   Доступ ко всем системным структурам данных и пулам памяти производится из режима ядра, так что у пользовательских потоков нет к ним доступа.

2)                   У каждого процесса имеется индивидуальное закрытое адресное пространство, защищенное от доступа других процессов.

3)                   Аппаратные средства защиты памяти на уровне архитектуры процессора

4)                   Разделяемые объекты имеют атрибуты контроля доступа, проверяемые при попытках процессов обратиться к этим объектам.

 

На уровне архитектуры существуют три метода защиты памяти (вспомним причины возникновения аппаратных исключений):

 

  1. проверка типа;
  2. проверка границ;
  3. проверка уровня полномочий.

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

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

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

 

Например, 4 уровня полномочий допускается в архитектуре Intel (два бита). Уровень 0 - самый высокий уровень защиты, уровень 3 - самый низкий.

NetWare 3.1 не имела защиты памяти. Из-за отсутствия издержек на защиту обработка данных на сервере была очень эффективной.

NetWare 4.0 использует уровни защиты, предоставляемые процессором Intel. Программы могут выполняться в следующих доменах:

 

  1. OS, работающем на уровне 0;
  2. OS_PROTECTED, работающем на уровне 3.

UnixWare предусматривает собственную защиту памяти и использует уровни 0 и 3 защиты процессора Intel.

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

Кроме того, используется уровни 0 и 3 защиты процессора Intel для выполнения программ ядра и пользователя.

Кроме того, используется защита на уровне страниц.

OS/2 широко использует кольцевую схему защиты процессора Intel, помещая ядро в кольцо 0, а процессы пользователя - в 3.

Кроме того, ОС управляет доступом через атрибуты страничной памяти.

 

 

 

 

 

 

 

 

4.4.3. Распределение памяти

4.4.3.1. Типы программных интерфейсов для распределения памяти

Для работы с памятью обычно предоставляется два набора программных интерфейсов:

 

  1. переносимые интерфейсы, удовлетворяющие стандартам ANSI;
  2. специфические для ОС интерфейсы.

4.4.3.2. Переносимые интерфейсы

К переносимым интерфейсам относят четыре функции управления памятью:

 

  1. выделить память заданного размера; malloc()
  2. выделить память под массив элементов calloc();
  3. перераспределить память realloc();
  4. освободить память free().

4.4.3.3. Специфические интерфейсы

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

Специфические интерфейсы дают следующие преимущества:

 

  1. позволяют использовать средства распределения памяти, соответствующие архитектуре ОС;
  2. предоставляют большие возможности управления памятью на нижнем уровне;
  3. используют конкретные схемы управления памятью;
  4. оптимизируют производительность.

NetWare 3.1 имеет хорошо оптимизированный для файловых средств доступ к памяти через кэш-буфер, обеспечивающий очень высокую производительность файлового сервера. ОС поддерживает 5 различных пулов памяти, обладающих различными характеристиками времени доступа и размера. Пул кэш-буфера является наиболее используемым пулом.

 

  1. Alloc - выделение заданного числа байтов памяти из пула краткосрочного распределения памяти.
  2. Free - освобождение памяти, выделенной функцией Alloc.
  3. AllocNonMovableCashMemory - выделение памяти из пула кэш-буфера.
  4. FreeNonMovableCashMemory - освобождение памяти, выделенной предыдущей функцией.
  5. AllocSemiPermMemory - выделение памяти из пула долгосрочной памяти.
  6. FreeSemiPermMemory - освобождение памяти, выделенной предыдущей функцией.

NetWare 4.1 управление памятью полностью изменено. Схема пулов была очень запутанной и не очень эффективной из-за фрагментации. Новая ОС выделяет каждому процессу собственный пул памяти, с управлением запросами на основе рабочего множества. Запрос памяти осуществляется к общему пулу, спроектированному с учетом страничной архитектуры и защиты памяти.

Для управления памятью рекомендуется использовать интерфейс ANSI.

 

UnixWare обеспечивает очень широкий набор средств и полную управляемость памятью. В основном объекты памяти представляются в виде именованных или неименованных файлов, доступ к которым может быть произведен средствами файловой системы.

 

  1. mmap - для виртуального объекта памяти задает отображение в адресном пространстве процесса;
  2. munmap - отменяет отображение.

ОС семейства Windows к стандартным средствам управления памятью добавляет следующие интерфейсы:

 

  1. функции Win32;
  2. функции виртуальной памяти;
  3. функции динамической памяти.

Win32 - это семейство интерфейсов, пригодных для программирования в любой Windows-среде.

 

Примеры функций Win32:

  1. GlobalAlloc - выделяет в памяти заданное число байтов. Тип выделяемого блока задается атрибутом:
    FIXED - неперемещаемый,
    MOVEABLE - перемещаемый,
    DISCARDABLE - выгружаемый.
  1. GlobalLock - блокирует блок, который становится неперемещаемым и невыгружаемым.
  2. GlobalUnlock - разблокирует блок;
  3. GlobalReAlloc - изменяет размер блока в сторону уменьшения или увеличения.
  4. GlobalFree - освобождает блок.

Функции виртуальной памяти позволяют разработчику использовать все возможности Windows по управлению памятью.

 

  1. VirtualAlloc - выделение памяти в пространстве виртуальных адресов.
  2. VirtualFree - освобождает память, выделенную с помощью предыдущей функцией.
  3. VirtualLock - физическая блокировка страниц в памяти;
  4. VirtualUnlock - разблокировка памяти;
  5. VirtualProtect - устанавливает атрибуты блока памяти, возможные значения атрибутов - PAGE_NOACCESS, PAGE_READONLY, PAGE_READWRITE.

Функции динамической памяти используются для вторичного распределения собственной памяти.

 

  1. HeapCreate - используется для создания в виртуальном адресном пространстве процесса динамически распределяемой области.
  2. HeapAlloc - выделяет память из области, созданной предыдущей функцией.
  3. HeapReAlloc - перераспределяет память в локальной динамической области.
  4. HeapFree - освобождает память;
  5. HeapDestroy - уничтожает объект динамически распределенной области.

OS/2 имеет три класса API для управления памятью:

 

  1. распределение собственной памяти;
  2. вторичное распределение памяти;
  3. распределение разделяемой памяти.

 

  1. DosAllocMem - выделяет память в собственном виртуальном адресном пространстве вызывающего процесса;
  2. DosFreeMem - освобождает память, ранее распределенную как собственную.

Функции вторичного распределения памяти, распределенной предварительно функцией DosAllocMem:

  1. DosSubSetMem,
  2. DosSubAllocMem,
  3. DosSubFreeMem,
  4. DosSubUnsetMem

 

 

4.4.4. Совместное использование памяти

4.4.4.1. Способы совместного использования памяти

Разделяемая память имеет очень большое значение для многозадачных ОС, т.к. задачи должны иметь возможность обмениваться информацией друг с другом.

 

В основном совместное использование памяти обеспечивается следующими средствами:

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

 

  1. механизмами экспорта/импорта на этапах компиляции и загрузки. На этапе компиляции и компоновки программа определяет статическую область памяти как допустимую для экспорта. Другие программы во время компиляции и компоновки могут импортировать данные из этой области.

NetWare

В NetWare 3.1 вся память является разделяемой и отображаемой. NetWare 4.0 реализует совместное использование переменных через механизм импорта/экспорта.

В одном модуле переменная описана и имеет атрибут "export", в другом модуле переменная используется и имеет атрибут "extern". Связи устанавливаются на этапах компиляции, компоновки и загрузки.

UnixWare

Ранее упоминавшаяся функция mmap создает разделяемый файл, отображаемый в памяти, если функции передан флаг MAP_SHARED.

  1. shmget - задание сегмента разделяемой памяти.
  2. shmat - подключает сегмент разделяемой памяти к вызывающему процессу.
  3. shmdt - отключает процесс от разделяемой области памяти.

Windows

Разделяемая память реализуется с помощью отображенных в памяти файлов, именованных или неименованных.

  1. CreateFileMapping - создание объекта отображения файла.
  2. OpenFileMapping - открытие и получение доступа к созданному объекту отображения файла.
  3. UnmapViewOfFile - отменяет отображение файла.

 

 


OS/2

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

  1. DosAllocSharedMem - выделяет область разделяемой памяти, которая может быть именованной или неименованной.
  2. DosGetSharedMem - получение доступа к разделяемому блоку памяти.

4.4.4.2. Пример реализации разделяемой памяти в Win32

Разделяемой называется память, видимая более, чем одному процессу, или присутствующая в виртуальном адресном пространстве более чем одного процесса.

Для реализации разделяемой памяти используются примитивы Win32 и объект "раздел", который иначе называется файлом, отображаемым в память, (file mapping object).

Объект "раздел" может быть связан с файлом на диске, а может, и нет.

Поскольку "раздел" – это объект, то его структура полностью соответствует понятию объекта:

Тип объекта

Раздел

Атрибуты объекта

Максимальный размер

Атрибуты защиты

Файл – страничный/проецируемый

Сервисы

Создать

Открыть

Спроецировать/отменить проецирование

 

Структурная схема разделяемой памяти на основе "файла, отображаемого в память"

Для создания объекта "раздел" используется Win32 вызов:

hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE,  // Current file handle.

                              NULL,                 // Default security.

                              PAGE_READWRITE,       // Read/write permission.

                              0,                    // Max. object size.

                              100,                  // Size of hFile.

                              "MyFileMappingObject"); // Name of mapping object.

INVALID_HANDLE_VALUE – означает, что используется не обычный дисковый файл, а страничный файл.

"MyFileMappingObject" – именование позволяет осуществить доступ к объекту из другого процесса.

Другой процесс должен открыть объект:

hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS,   // Read/write permission.

                           FALSE,                 // Do not inherit the name

                          "MyFileMappingObject"); // of the mapping object.

Затем каждый процесс должен отобразить этот объект на свою область виртуальной памяти.

lpMapAddress = (LPINT)MapViewOfFile(hMapFile,     // Handle to mapping object.

                                    FILE_MAP_ALL_ACCESS,// Read/write permission

                                    0,                    // Max. object size.

                                    0,                    // Size of hFile.

                                    0);                   // Map entire file.

Затем один процесс может что-то писать в указанную память, например,

*lpMapAddress = 57;

А другой процесс может читать данные:

INT i = *lpMapAddress;// i будет равно 57

По окончании работы процессы должны закрыть отображение и закрыть объект:

UnmapViewOfFile(lpMapAddress);

CloseHandle(hMapFile);


Выводы по разделу 4

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

4.1. Классификация способов организации памяти

4.2. Управление физической памятью

4.2.1. Однозадачные системы

4.2.1.1. Простые и оверлейные программы

4.2.1.2. Сегментация памяти

4.2.1.3. Блочная организация памяти

4.2.1.4. Стратегии управления памятью (введение)

4.2.2. Многозадачные системы

4.2.2.1. Фиксированные разделы и неперемещаемые программы

4.2.2.2. Фиксированные разделы и перемещаемые программы

4.2.2.3. Переменные разделы

4.2.2.4. Свопинг

4.3. Управление виртуальной памятью

4.3.1. Общая характеристика виртуальной памяти

4.3.1.1. Иерархия видов памяти

4.3.1.2. Особенности виртуальной памяти

4.3.1.3. Механизм отображения виртуальных адресов

4.3.2. Страничная организация виртуальной памяти

4.3.2.1. Одноуровневая страничная организация

4.3.2.2. Двухуровневая страничная организация

4.3.2.3. Совместное использование страниц процессами

4.3.3. Сегментная организация виртуальной памяти

4.3.3.1. Одноуровневая сегментная организация

4.3.3.2. Комбинированная сегментно-страничная организация совместно с таблицей процессов

4.3.4. Стратегии управления виртуальной памятью (продолжение)

4.3.4.1. Главные задачи управления виртуальной памятью

4.3.4.2. Стратегии решения второй главной задачи

4.3.4.3. Стратегии выталкивания с постоянным распределением памяти

4.3.4.4. Стратегии выталкивания с переменным распределением памяти

4.4. Управление памятью в современных ОС

4.4.1. Архитектуры управления памятью

4.4.2. Защита памяти

4.4.3. Программные интерфейсы распределения памяти

4.4.3.1. Типы программных интерфейсов

4.4.3.2. Переносимые интерфейсы

4.4.3.3. Специфические интерфейсы

4.4.4. Совместное использование памяти

4.4.4.1. Способы совместного использования памяти

4.4.4.2. Пример реализации разделяемой памяти в Win32

 

 

 


5. Управление коммуникациями в ОС

5.1. Общая характеристика коммуникаций

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

Коммуникации между процессами можно разделить на два следующих варианта:

 

  1. внутренние коммуникации
  2. сетевые коммуникации

 

Внутренние коммуникации - это коммуникации между процессами, выполняющимися на одном компьютере.

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

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

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

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

В рамках стандартов создаются коммуникационные протоколы. Один из наиболее удачных вариантов системы коммуникационных протоколов разработала Международная организация стандартизации ISO (International Standards Organisation), создавшая стандартную многоуровневую модель коммуникаций - модель OSI (Open System Interconnection).

Эта модель используется в основном для сетей, но может быть использована и для построения протоколов внутренних коммуникаций, если рассматривать только три верхних уровня модели.

Эта модель имеет следующий вид.

 

------------------                          ------------------

| Приложения     |                          | Приложения     |

------------------                          ------------------

| Презентации    |                          | Презентации    |

------------------                          ------------------

| Сеанса         |                          | Cеанса         |

------------------                          ------------------

| Транспортный   |                          | Транспортный   |

------------------                          ------------------

| Сетевой        |                          | Сетевой        |

------------------                          ------------------

| Канальный      |                          | Канальный      |

------------------                          ------------------

| Физический     |                          | Физический     |

------------------                          ------------------

         v                                          ^

-------------------------------------------

Взаимодействие между парами уровней (верх-низ) описывается межуровневыми интерфейсами.

Взаимодействие между машинами на одном уровне описывается протоколами.

Если взаимодействующие приложения выполняются на разных машинах, то в работе участвуют все уровни модели. Если приложения выполняются на одной машине, то задействуются три верхних уровня модели.

Краткая характеристика уровней

Уровень приложения. Протоколы этого уровня уникальны для каждой программной системы и определяют характер и объем передаваемых данных.

Уровень презентации. Этот уровень отвечает за преобразование собственных данных приложений в форматы, подготавливаемые для обмена. И наоборот, принятые данные преобразует в вид, понятный приложению.

Уровень сеанса. Этот уровень обеспечивает синхронизацию коммуникаций в определенных точках выполнения приложений.

Транспортный уровень. Этот уровень обеспечивает надежную и последовательную передачу данных между узлами сети.

Сетевой уровень. Этот уровень обеспечивает маршрутизацию пакетов с данными и передачу отдельных пакетов.

Канальный уровень. Этот уровень обеспечивает помехоустойчивое кодирование данных.

 

Физический уровень. Этот уровень отвечает за характеристики сигналов в линиях связи.

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

Заголовок

Данные

Хвост

Заголовок в основном содержит адресную информацию, хвост - контрольную информацию.

С точки зрения поля данных различают жесткие и гибкие протоколы.

В жестком протоколе структура передаваемых данных известна и не меняется. Это обеспечивает высокую эффективность жесткого протокола.

В гибком протоколе формат данных не определен заранее. Гибкие протоколы легче адаптируются к изменяющимся требованиям.

Структура гибкого протокола имеет следующий приблизительный вид:

Заголовок

Тип

Данные

Тип

Данные

Тип

Данные

Хвост

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

Принципы взаимодействия уровней соответствуют тем принципам, которые мы рассматривали при знакомстве с уровнями операционной системы.

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

При переходе от уровня к уровню используется принцип вложенности протоколов. Т.е. поле данных протокола некоторого уровня - это весь протокол следующего уровня.

Например.

Далее мы будем знакомиться с протоколами, начиная с сетевого уровня и выше вплоть до уровня презентации.

Подчеркнем, что эту многоуровневую модель коммуникаций мы рассматриваем в рамках 8-го уровня многоуровневой модели операционной среды.

На всех уровнях, которые мы в дальнейшем собираемся изучать, используется терминологические особенности технологии КЛИЕНТ-СЕРВЕР, поэтому ниже рассмотрим элементы этой технологии.

5.2. Концепции технологии Клиент-Сервер

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

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

З а п р о с ы

         |- - > - > - > -  | > - > - > - > ----------|

         ^                 |                         v

      ---|------           |                    -----|----

      | Клиент |           |                    | Сервер |

      ---|-----            |                    -----|----

         ^                 |                         v

         |-- - < - < - < - | < - < - < - < ----------|

О т в е т ы

Этот процесс чаще всего происходит на физически разделенных компьютерах через тот или иной тип сети. Хотя это и не обязательно.

Чтобы приложение стало действительно приложением клиент/сервер, необходима соответствующая архитектура. Логика приложения должна быть разделена на отдельные компоненты - запрашивающий компонент, который дает запросы и ожидает возврата от отвечающего компонента - сервера.

Такое логическое разделение позволяет лучше подготовить приложение к распределению в сети.

Независимость компонентов приложения - обязательное требование архитектуры клиент/сервер.

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

Клиент: ввод/вывод информации; взаимодействие с пользователем; формулировка запросов; логика приложения.

Сервер: запросы к совместно используемым ресурсам; обработка транзакций.

Правильное распределение кода по клиентскому и серверному компонентам является ключевым вопросом  в  проектировании системы.

Взаимодействие между серверными и клиентскими компонентами приложения может быть декомпозировано на приведенные выше 7 уровней. И на каждом из уровней, особенно начиная с 3-го, можно выделять клиентский и серверный компоненты.

Существует множество методов взаимодействия между клиентскими и серверными компонентами: Выбор метода определяется тем, к какому из двух видов относится взаимодействие: к внутреннему или внешнему.

 

5.3. Внутренние коммуникации между процессами

5.3.1. Неименованные каналы

 

                Как следует из всего предыдущего материала, современные операционные системы являются многозадачными.

                Суть многозадачности состоит в возможности одновременного (параллельного) выполнения нескольких программ. При этом понятие одновременности носит несколько условный характер.

                Если процессоров много, то программы действительно могут выполняться параллельно. Но, если процессор один (а так бывает очень часто), то говорят о псевдопараллельном выполнении программ.

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

                Однако параллельное (или псевдопараллельность) выполнение программ – это всего лишь один из уровней параллельности (более высокий).

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

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

                В статике это выглядит следующим образом.

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

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

 

                Если с одновременным выполнением программ более менее ясно, зачем они нужны, то не совсем может быть ясно, зачем нужны программы с одновременным выполнением процедур.

 

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

 

                Представим себе, что написана программа расчетов по каким-нибудь формулам, т.е. программа, не взаимодействующая с внешней средой. Она запущена на выполнение и работает. В целом время ее работы, конечно, важно, но оно может в разное время быть разным. Например, при первом запуске в системе выполнялось 3 программы, а во втором – 10. Ясно, что при втором запуске программа будет работать дольше. Но это и не очень страшно, подсчитает свои формулы не за 5, например, а за 10 сек.

 

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

                Для реализации подобного типа систем и нужны многопоточные программы.

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

 

                Структура такой многопоточной программы выглядит следующим образом:

 

void proc1() {    //поток, читающий данные из канала строго в заданные

                  //моменты времени

      while (true) {

            ...

}

 

}

void proc2() {    //поток, обрабатывающий данные, полученные первым потоком

 

      while (true) {

            ...

}

 

}

 

void main() {

 

      CreateThread(..., proc1, ...);//параметров у этой функции много, proc1 -

      CreateThread(..., proc2, ...);// это только один из параметров

 

      while (клавиша не нажата) {

}

}

 

Потоки proc1() и proc2() должны взаимодействовать через общие структуры данных. Вот такой общей структурой данных для них и является “канал”.

 

                Канал – это участок разделяемой памяти, который процессы могут использовать для взаимодействия. Процесс, который создает канал, является процессом – сервером. Процесс, который соединяется с каналом, является процессом – клиентом. Один процесс пишет информацию в канал, другой процесс читает из канала.

 

                Каналы могут быть двух типов:

 

  1. неименованные каналы;
  2. именованные каналы.

 

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

 

            Неименованный канал создается вызовом:

 

CreatePipe(&hReadPipe,&hWritePipe,NULL,0);

 

                Вызов возвращает две ссылки, одна из которых используется для чтения, а другая для записи.

Там, где передан 0, указывается размер канала. При значении 0 система устанавливает размер по умолчанию.

 

                Чтение производится вызовом:

 

ReadFile(hReadPipe,&NewByte,1,&NumberOfBytesRead,NULL);

 

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

 

                Запись производится вызовом:

 

WriteFile(hWritePipe,Buffer,nBytes,&NumberOfBytesWritten,NULL);

 

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

 

                Канал закрывается вызовами:

 

CloseHandle(hReadPipe);

CloseHandle(hWritePipe);

5.3.2. Обмен сообщениями

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

 

Архитектура объектно-ориентированных событийно управляемых приложений существенно отличается от традиционных "линейных" (ДОС) приложений.

Грубо архитектуру линейного приложения можно представить следующим образом:

 

Call Proc1;

Call Proc2;

Call ProcN;

 

                Архитектуру объектно-ориентированного приложения можно представить следующим образом:

Это набор объектов – программных конструкций, отображающих объекты реального мира.

Объекты не могут существовать сами по себе. Им присуще стремление взаимодействовать друг с другом. Можно выделить два способа взаимодействия объектов:

 

  1. В методе одного объекта выполняется прямой вызов метода (который объявлен как доступный) другого объекта.
  2. Обмен сообщениями между объектами.

 

Прямой вызов

 

Взаимодействие объектов через сообщения

В сообщении указывается адрес получателя (другой объект) и данные. Некоторое средство, называемое Диспетчер, получает сообщения и перераспределяет их получателям.

Объекты в обязательном порядке должны иметь в своем составе метод-обработчик сообщения, т.е. функцию, которая вызывается при получении сообщения.

Доступной архитектурой такого типа является архитектура Turbo Vision, реализованная в среде ДОС компанией Borland. Можно сказать, что это был предвестник объектно-ориентированного программирования в среде Windows. ДОС-приложение, написанное в среде Turbo Vision, почти ничем не отличается объектно-ориентированного Windows-приложения.

В Windows-среде функции диспетчера выполняет ядро ОС, а в качестве объектов выступают экземпляры оконных классов. Метод-обработчик сообщений называют оконной функцией.

ОС собирает информацию о всех событиях, происходящих в системе, и рассылает эту информацию оконным функциям, для которых информация предназначена.

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

Чтобы иметь возможность обмена сообщениями между объектами приложений, необходимо сообщение описать. Например:

UINT  WM_SENDAPP;

Т.е. сообщение – это число целого типа.

Затем сообщение необходимо зарегистрировать в системе. Это делается функцией:

WM_SENDAPP = RegisterWindowMessage("SEND_APP");

Этот вызов возвращает уникальное для системы значение сообщения. Если все приложения вызовут данную функцию, то функция вернет всем приложениям одно и тоже значение, что является предпосылкой для возможности обмена сообщениями.

В объявление класса, который должен обрабатывать сообщение, включается обработчик сообщения:

afx_msg  LRESULT  SendApp(WPARAM wParam,LPARAM lParam);

В реализации класса создается код обработчика:

LRESULT CName::SendApp(WPARAM wParam,LPARAM lParam) {

AfxMessageBox("Test message!");

      if (wParam == ...) {

      }

      if (lParam == ...) {

      }

      return result;

}

Как осуществляется диспетчеризация (т.е. вызов нужного обработчика) сообщений? Надо реализовать оператор примерно следующего вида:


 

switch (msg) {

case WM_SENDAPP0 : SendApp0(wParam, lParam);

break;

case WM_SENDAPP1 : SendApp1(wParam, lParam);

                        break;

...

}

 

Если сообщений много, то оператор будет чрезвычайно громоздким. Поэтому создана другая технология, связывающая сообщение с обработчиком, называемая "message map". Идея состоит в том, что создается массив указателей на функции-обработчики, а индексами массива являются числовые значения сообщений.

 

На уровне приложения message map выглядит примерно следующим образом:

 

BEGIN_MESSAGE_MAP()

      //{{AFX_MSG_MAP()

      ON_WM_PAINT()

      ON_REGISTERED_MESSAGE(WM_SENDAPP,SendApp)

      //}}AFX_MSG_MAP

END_MESSAGE_MAP()

 

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

 

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

 

API Windows предлагает функции SendMessage() (синхронная) и PostMessage() (асинхронная) для обмена сообщениями между объектами.

Функция SendMessage() завершается, когда завершится выполнение обработчика сообщения в объекте-получателе и возвращает результат обработки, т.е. то значение, которое обработчик вернет оператором   return.

 

 

 

 

 

 

 

 

 

 

 

 

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

 

                Обе функции имеют одинаковый набор параметров:

BOOL PostMessage(
  HWND hWnd,      // получатель
  UINT Msg,       // сообщение
  WPARAM wParam,  // первый параметр
  LPARAM lParam   // второй параметр
);

 

Пример вызова функции:

if (!PostMessage(HWND_BROADCAST,WM_SENDAPP,1,1)) {

      AfxMessageBox("Error");

}

 

 

 

 


5.3.3. Обмен данными через Clipboard

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

Традиционным способом использования Clipboard является обмен данными при участии пользователя.

Однако и другие, более развитые средства обмена данными, такие как DDE и OLE, используют концепции Clipboard в своей основе.

Важнейшей концепцией Clipboard является концепция формата данных.

Широкое распространение Clipboard и получила из-за фактической стандартизации ряда форматов данных, используемых при обмене между приложениями.

Эта стандартизация позволила снять с пользователя проблемы преобразования данных при передаче их из одного приложения в другое, что и способствовало широкому распространению Clipboard. Среда сама берет на себя проблемы преобразования данных на основе ряда стандартных форматов.

Стандартизация форматов, однако, не ограничивает возможности их расширения.

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

RegisterClipboardFormat(строка с именем нового формата),

передав этой функции имя формата.

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

Стандартные форматы данных обозначаются константами, имеющими символические имена. Приведем в качестве примера пару таких форматов:

 

  1. cf_Text -   ASCIIZ строка;
  2. cf_BitMap - один из форматов графического изображения.

 

Существует еще несколько (~25) стандартных форматов данных, поддерживаемых Clipboard.

Ниже мы рассмотрим порядок обмена данными через Clipboard с точки зрения программиста, а не пользователя.

Передача данных в Clipboard

Передача данных в Clipboard включает в себя ряд этапов.

 

  1. Подготовка данных для передачи в Clipboard;
  2. Открытие Clipboard;
  3. Очистка Clipboard;
  4. Посылка данных в Clipboard;
  5. Закрытие Clipboard.

 

 

1. Подготовка данных для передачи в Clipboard;

Посылку данных рассмотрим на примере передачи в Clipboard ASCIIZ - строки.

1) Выделяем блок динамической памяти:

HGLOBAL GHandle = GlobalAlloc(GMEM_MOVEABLE, strlen(P) + 1);

Где:

HGLOBAL GHandle - ссылка на блок динамической памяти;

P - указатель на передаваемую строку.

 

2) Фиксируем блок от перемещений на время передачи в него данных и одновременно получаем указатель на блок:

LPTSTR GPtr = (LPTSTR)GlobalLock(GHandle);

Где: GPtr - указатель на блок.

3) Копируем строку в память:

strcpy(GPtr, P);

4) Разрешаем перемещение блока памяти:

GlobalUnlock(GHandle);

2. Открытие Clipboard

Открытие Clipboard выполняется вызовом функции

OpenClipboard(),

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

 

3. Очистка Clipboard

Очистка Clipboard выполняется вызовом функции

EmptyClipboard(),

перед засылкой туда новых данных.

4. Посылка данных в Clipboard

Посылка данных в Clipboard заключается в передаче Clipboard ссылки на блок данных с указанием формата этих данных. Эти действия осуществляются с помощью функции:

SetClipboardData(cf_Text, GHandle).

После вызова этой функции Clipboard начинает владеть данными.

5. Закрытие Clipboard

Закрытие Clipboard выполняется вызовом функции

CloseClipboard(),

после которого Clipboard становится доступным другим приложениям.

Прием данных из Clipboard

Прием данных из Clipboard включает в себя следующие этапы:

 

  1. Открытие Clipboard;
  2. Проверка наличия требуемого формата данных в Clipboard;
  3. Чтение данных из Clipboard;
  4. Закрытие Clipboard.

 

1. Открытие Clipboard

Открытие Clipboard выполняется аналогично предыдущему случаю вызовом функции:

 

OpenClipboard().

2. Проверка наличия требуемого формата данных в Clipboard

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

IsClipboardFormatAvailable(cf_Text),

которая возвращает True, если данные запрашиваемого формата присутствуют.

 

3. Чтение данных из Clipboard

Чтение данных, в случае успешного выполнения всех предыдущих операций, выполняется за несколько шагов:

1) Получение ссылки на блок памяти, содержащий данные требуемого формата:

HGLOBAL GHandle = GetClipboardData(cf_Text);

2) Получение адреса данного блока памяти и фиксирование его от возможных перемещений на время считывания данных:

LPTSTR GPtr = (LPTSTR)GlobalLock(GHandle);

3) Выделение локальной памяти для чтения в нее интересующих данных:

 

P = malloc(strlen(GPtr) + 1));

4) Копирование данных из глобальной памяти в локальную память:

strcpy(P, GPtr);

5) Разрешение перемещения глобального блока памяти:

GlobalUnlock(GHandle).

4. Закрытие Clipboard

Закрытие Clipboard в случае приема данных выполняется аналогично закрытию в случае передачи данных: CloseClipboard().

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

 


Поддержка обмена данными через Clipboard в современных средах программирования

 

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

Посмотрим, как, например, можно реализовать обмен данными через Clipboard в среде проектирования Delphi.

Среда проектирования Delphi предоставляет объект TClipboard, который включает в себя все необходимые действия для управления Clipboard.

Включение модуля Clipbrd в состав проекта, автоматически создает, открывает и делает доступным экземпляр Clipboard. При этом, компоненты TEdit и TMemo, обеспечивающие работу с текстовыми строками, а также компонент TImage, обеспечивающий работу с графическими образами, получают возможность использования своих методов работы с Clipboard.

Так, например, скопировать информацию в Clipboard, из компонента Memo типа TMemo, можно следующим образом:

Memo.SelectAll;                      выделить весь текст

Memo.CopyToClipboard;             скопировать выделенный текст

возможен другой метод:

Clipboard.AsText := Memo.Text;

Вырезать текст можно следующим образом:

Memo.SelectAll;                          выделить весь текст

Memo.CutToClipboard;              вырезать выделенный текст

Скопировать текст из Clipboard можно следующим образом:

Memo.PasteFromClipboard; или:

Memo.Text := Clipboard.AsText;

Перед считыванием данных из Clipboard целесообразно проверить, есть ли в Clipboard данные требуемого типа. Это можно сделать с помощью вызова метода объекта Clipboard:

Clipboard.HasFormat(cf_Text),

который вернет True, если данные в формате cf_Text есть в Clipboard.

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

Копирование графических данных (в формате cf_BitMap) свойства Picture компонента Image типа TImage в Clipboard:

Сlipboard.Assign(Image.Picture).

Копирование графических данных из Clipboard в компонент Image:

If Clipboard.HasFormat(cf_BitMap) Then begin

Image.Picture.Assign(Clipboard);

End;

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

                В Microsoft VC++, например, для класса CEdit есть следующие методы работы с Clipboard:

Copy(), Paste(), Cut() и другие.


5.3.4. Обмен данными по технологии DDE

Обмен данными через Clipboard выполняется, как правило, при непосредственном участии пользователя.

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

DDE - это вид (или протокол) взаимодействия между приложениями, который использует разделяемую память для обмена данными между ними.

Технология DDE не получает широкого распространения из-за сложности реализации, но занимает промежуточное место между технологией Clipboard и технологией OLE, которая тоже чрезвычайно сложна, но развивается.

Технология DDE полностью вписывается в технологию Клиент-Сервер, элементы которой были рассмотрены ранее. Т.е. обмен данными по протоколу DDE происходит между двумя приложениями, одно из которых является клиентом, а другое сервером. Сервер загружается и ждет запросов на обслуживание от клиента. Клиент посылает серверу запрос на получение данных или выполнение работы. Сервер, получив запрос, выполняет действия и посылает ответ.

Сервер может иметь множество клиентов в одно и то же время и клиент может запрашивать данные от множества серверов. Более того, приложение может быть одновременно и клиентом и сервером.

Технология DDE изначально создавалась для организации обмена данными между приложениями, выполняющимися на одной машине, но существует и развитие этой технологии для приложений, выполняющихся на разных машинах, называемое NetDDE.

Технология DDE зародилась в недрах операционных систем, управляемых событиями.

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

Обрабатывая эти события, операционная система формирует сообщения приложениям.

Приложение, которое выполняется в системе, управляемой событиями, находится в бесконечном цикле и проверяет наличие сообщений ему от операционной системы. В соответствие с характером принимаемых сообщений приложение выполняет требуемые действия и снова посылает сообщения системе.

С учетом указанной особенности, в развитии технологии DDE можно выделить три этапа.

1) Первый этап существования DDE, когда DDE реализовывался на основе прямого обмена системными сообщениями между приложениями.

Для посылки сообщений могут быть использованы системные функции SendMessage() (синхронная) и PostMessage() (асинхронная).

 

Для DDE выделена специальная группа сообщений, а именно:

  1. wm_DDE_Initiate   -     Инициировать сеанс обмена данными;
  2. wm_DDE_Terminate  -     Завершить сеанс обмена данными;
  3. wm_DDE_Execute    -     Выполнить команду;
  4. wm_DDE_Ack        -     Получить статус операции;
  5. wm_DDE_Poke       -     Послать данные серверу;
  6. wm_DDE_Request    -     Запросить данные у сервера;
  7. wm_DDE_Advise     -     Установить связь с сервером;
  8. wm_DDE_UnAdvise   -     Завершить связь с сервером;
  9. wm_DDE_Data       -     Послать данные клиенту.

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

Вместо непосредственной посылки DDE-сообщений с помощью функций SendMessage() и PostMessage() приложения используют функции DDEML.

 

 

2) Второй этап существования DDE, когда DDE-приложения программируются с помощью DDEML.

Этот этап далее будет рассмотрен более подробно. Отметим только, что с появлением DDEML сложность создания DDE-приложений уменьшилась, но все равно оставалась еще довольно высокой.

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

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

К знакомству с этими концепциями мы и перейдем.

Система адресации данных

DDE-протокол использует трехуровневую систему адресации передаваемых данных:

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

Topic Name - имя темы, определяющее некоторый логический контекст данных. Для серверов, работающих на основе файловых документов, имя темы чаще всего совпадает с именем файла документа.

Item Name - имя элемента данных. Имя элемента идентифицирует элемент данных, который может быть передан за одну транзакцию.

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

 

Для DDE эти имена формируются с помощью специальной функции. Например, для имени сервиса вызов такой функции может иметь следующий вид:

 

HSZ ServiceName = DdeCreateStringHandle(idInst,MyServiceName,CP_WINANSI);

Все три уровня описываются строками. Строки хранятся в специальной глобальной таблице. Ссылки на соответствующие строки называются атомами.

Обмен конкретными значениями данных всегда сопровождается указанием (явным или неявным) всех трех уровней, к которым относятся передаваемые данные.

Концепция функции отклика

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

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

DDE-приложение определяет функцию отклика, которую DDEML будет вызывать в ответ на события, возникающие при создании соединения между приложениями, при обмене данными и при разъединении. DDEML передает в эту функцию номер транзакции, а функция по этому номеру выполняет запрограммированные действия.

Ниже представлена примерная структура функции отклика:


 

HDDEDATA CALLBACK DdeCallback(

UINT     wType,

UINT     wFmt,

HCONV    hConv,

HSZ      hsz1,

HSZ      hsz2,

HDDEDATA hData,

DWORD    lData1,

DWORD    lData2)

{

 

       if (wType == XTYP_DISCONNECT) {

реакция на соответствующую транзакцию

       }

 

       if (wType == XTYP_ADVDATA) {

реакция на соответствующую транзакцию

       }

 

return(0);

}

 

wType - тип транзакции;

wFmt - формат данных, аналогичный форматам данных Clipboard;

hСonv - ссылка на канал;

hsz1 - ссылка на строку; это или пара (Сервис, Тема) или

hsz2 - ссылка на строку; пара (Тема, Элемент)

hData - ссылка на передаваемые или принимаемые данные;

lData1, lData2 – дополнительные данные.

 

Этапы функционирования DDE-приложения

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

Инициализация сервера

Инициализация сервера включает в себя следующие этапы:

1) В 16-ти разрядных системах существовал этап - регистрация функции отклика в среде. Это делается с помощью системной функции, имеющей следующее имя:

MakeProcInstance(...).

Функции MakeProcInstance передается адрес функции отклика, а она возвращает зарегистрированный указатель на функцию отклика.

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

В современных системах можно использовать прямо имя функции отклика.

2) Регистрация сервера в DDEML. Это делается с помощью функции DDEML, имеющей следующее имя:

DdeInitialize(&idInst,DdeCallback,APPCMD_FILTERINITS,0);

Функции DDEInitialize передается зарегистрированный указатель на функцию отклика, а возвращает она идентификатор приложения как DDE-приложения - idInst - и код ошибки регистрации.

 

3) Регистрация "сервиса", предоставляемого сервером. Это делается с помощью функции DDEML, имеющей следующее имя:

DdeNameService(idInst,ServiceName,0,DNS_REGISTER);

Функции DDENameService передаются идентификатор DDE-приложения и строка с именем сервиса, а сама функция возвращает код ошибки регистрации.

После выполнения перечисленных этапов инициализация сервера заканчивается, и он переходит в режим ожидания запросов от клиентов.

Инициализация клиента

Инициализация клиента включает в себя следующие этапы:

1.       Регистрация функции отклика в среде (для 16-ти разрядных систем). Это делается аналогично регистрации функции отклика сервера.

2.       Регистрация клиента в DDEML. Это также делается аналогично регистрации сервера.

3.       Создание канала связи между клиентом и сервером.

Создание канала выполняется только по инициативе клиента и осуществляется с помощью вызова функции, имеющей следующее имя:

HCONV hConv = DdeConnect(idInst,ServiceName,TopicName,NULL);

Функции DDEConnect передаются строки с именами сервиса и темы, а также зарегистрированный идентификатор DDE-клиента.

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

Этапом создания канала заканчивается инициализация клиента и появляется возможность обмена данными между клиентом и сервером.

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

Последний этап инициализации клиента - создание канала связи - выполняется при участии сервера.

Создание канала - сложная процедура, включающая следующую последовательность действий.

При вызове клиентом функции DDEConnect, DDEML посылает транзакцию XTYP_CONNECT всем серверам, зарегистрировавшим требуемый сервис.

Функция отклика каждого сервера, в качестве реакции на эту транзакцию, проверяет у себя возможность поддержки заданных сервиса и темы, и если сервер поддерживает их, то он возвращает в DDEML соответствующий признак (1) при завершении выполнения функции отклика. Это означает, что канал между клиентом и сервером может быть создан.

if (wType == XTYP_CONNECT) {

       if (DdeCmpStringHandles(hsz2,ServiceName) == 0) {

              if (DdeCmpStringHandles(hsz1,TopicName) == 0) {

                     return (HDDEDATA)(1);

              }

       }

}

 

Получив ожидаемый признак от функции отклика сервера, DDEML завершает выполнение функции DDEConnect клиента, возвращая ему идентификатор канала.

При этом DDEML передает серверу новую транзакцию xtyp_Connect_Confirm и идентификатор созданного канала путем повторного вызова его функции отклика.

 

if (wType == XTYP_CONNECT_CONFIRM) {

       hConversation = hConv;

}

 

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

 

 

Обмен данными

Три варианта обмена данными могут быть реализованы с помощью DDE-технологии.

 

  1. Передача данных от сервера к клиенту по запросу последнего.
  2. Передача данных от клиента серверу.
  3. Передача данных от сервера к клиенту без запроса от последнего при изменениях данных.

Все действия по обмену данными со стороны клиента выполняются с помощью функции

DdeClientTransaction(...).

Этой функции передаются: идентификатор канала, тип транзакции и другие данные, определяемые этим типом. Например, имя элемента, формат данных, адрес данных.

Передача данных от сервера по запросу от клиента

Для получения данных от сервера по запросу, клиент вызывает функцию DdeClientTransaction, посылая транзакцию XTYP_REQUEST в DDEML.

HDDEDATA result = DdeClientTransaction(NULL,0,hConversation,ItemName,CF_TEXT,XTYP_REQUEST,1000,NULL);

 

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

DdeGetData(result,buffer,sizeof buffer,0);

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

 

Посмотрим теперь, как реагирует на транзакцию XTYP_REQUEST сервер.

DDEML передает эту транзакцию серверу, вызывая его функцию отклика. Кроме типа транзакции, DDEML передает серверу формат данных и имена темы и элемента.

Если сервер поддерживает запрашиваемые формат, тему и элемент, то он в вызове функции отклика возвращает ссылку на требуемые данные, сформированную функцией DdeCreateDataHandle.

 

if (wType == XTYP_REQUEST) {

return DdeCreateDataHandle(idInst,buffer,strlen(buffer),0,hsz2,CF_TEXT,HDATA_APPOWNED);

}

 

которая запрашивает блок глобальной памяти и переписывает в него данные из локального буфера.

 

 

Передача данных от клиента серверу

Клиент посылает данные серверу, используя транзакцию XTYP_POKE в вызове функции DdeClientTransaction(). В эту функцию также передаются имя темы, имя элемента, формат данных и адрес данных.


DdeClientTransaction(hPokeData,-1,hConversation,ItemName,CF_TEXT,XTYP_POKE,1000,NULL)

 

                Данные предварительно должны быть сформированы вызовом:

 

hPokeData = DdeCreateDataHandle(idInst,Data,strlen(Data),0,ItemName,CF_TEXT,HDATA_APPOWNED);

 

DDEML передает транзакцию серверу, вызывая его функцию отклика и указывая имя темы, имя элемента, формат данных и адрес данных.

Данные прочитываются сервером с помощью функции DdeGetData. В случае успешного завершения операции сервер обязан возвратить в DDEML подтверждение, возвращая при выходе из функции отклика значение DDE_FACK.

 

if (wType == XTYP_POKE) {

       DdeGetData(hData,buffer,sizeof buffer,0);

       return DDE_FACK;

}

Передача данных от сервера без запроса от клиента

Если клиенту необходимо постоянно следить за изменениями данных, он может создать advise loop - извещающий канал.

Извещающий канал может быть создан двух типов:

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

Три рассматриваемых алгоритма передачи данных, а именно:

 

  1. передача данных по запросу от клиента;
  2. передача по теплому каналу;
  3. передача по горячему каналу,

 

различаются повышением нагрузки на канал. С этой точки зрения наиболее предпочтительным следует считать первый алгоритм, а наименее предпочтительным - третий.

Рассмотрим пример функционирования горячего извещающего канала.

Горячий извещающий канал создается по инициативе клиента путем передачи транзакции XTYP_ADVSTART в вызове функции DdeClientTransaction.

 

DdeClientTransaction(NULL,0,hConversation,ItemName,CF_TEXT,XTYP_ADVSTART,1000,NULL);

 

Эта транзакция передается серверу путем вызова его функции отклика.

Если сервер поддерживает создание извещающего канала, то он должен вернуть TRUE при выходе из функции отклика:

 

if (wType == XTYP_ADVSTART) {

       return TRUE;

}

 

а при каждых изменениях данных он должен теперь вызывать функцию

DdePostAdvise(idInst,TopicName,ItemName);

которой передаются имена темы и элемента, описывающие изменившиеся данные.

 

Вызов функции DdePostAdvise() приводит к тому, что DDEML посылает транзакцию XTYP_ADVREQ серверу.

Функция отклика сервера возвращает ссылку на измененные данные таким же способом, как сервер делал при реакции на транзакцию XTYP_REQUEST, т.е., вызывая функцию DdeCreateDataHandle().


if (wType == XTYP_ADVREQ) {

return DdeCreateDataHandle(idInst,buffer,strlen(buffer),0,hsz2,CF_TEXT,HDATA_APPOWNED);

}

 

Получив ссылку на данные, DDEML посылает транзакцию XTYP_ADVDATA клиенту, передавая ему эту ссылку. Клиент прочитывает данные в своей функции отклика с помощью функции DdeGetData(). При выходе из функции отклика, в случае успешного прочтения данных, клиент должен вернуть DDE_FAck в DDEML.

 

if (wType == XTYP_ADVDATA) {

DdeGetData(hData,buffer,sizeof buffer,0);

DdeFreeDataHandle(hData);

return DDE_FACK;

}

Закрытие извещающего канала производится по инициативе клиента передачей транзакции XTYP_ADVSTOP в DDEML.

DdeClientTransaction(NULL,0,hConversation,ItemName,CF_TEXT,XTYP_ADVSTOP,1000,NULL);

 

Сервер, получив эту транзакцию, должен предусмотреть отказ от последующих вызовов функции DdePostAdvise при изменениях данных.

Разрыв соединения

Разрыв соединения может быть произведен как по инициативе клиента, так и по инициативе сервера.

Разрыв соединения производится вызовом функции

DdeDisconnect(hConversation);

которой передается идентификатор разрываемого канала.

В ответ на этот вызов DDEML передает транзакцию XTYP_DISCONNECT партнеру по связи.

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

Кроме того, приложения - бывшие партнеры по связи должны вызвать функцию

DdeUninitialize(idInst);

освобождающую ресурсы DDEML, связанные с соответствующими приложениями.

 

И, наконец, вызывается процедура (для 16-ти разрядных приложений)

FreeProcInstance(),

разрушающая связь функции отклика приложения со средой, в которой оно функционирует.

 

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

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


Организация обмена данными по протоколу DDE между Delphi-приложениями

В Delphi-среде приложения DDE-сервер и DDE-клиент создаются путем включения в форму специализированных невизуальных компонентов:

  1. компоненты TDDEServerConv и TDDEServerItem включаются в приложение-сервер;
  2. компоненты TDDEClientConv и TDDEClientItem включаются в приложение-клиент.

 

Соответствующая настройка свойств компонентов на этапе проектирования приложений обеспечивает взаимодействие приложений при их выполнении.

Рассмотрим последовательно этапы создания DDE-сервера и DDE-клиента в среде Delphi.

Создание DDE-сервера

1. Компоненты DDEServerConv и TDDEServerItem помещаются в форму. Среда проектирования автоматически присваивает компонентам имена:

DDEServerConv1 и DDEServerItem1.

2. Производится связывание компонентов DDEServerConv1 и DDEServerItem1 путем присваивания свойству ServerConv компонента TDDEServerItem значения, равного имени компонента TDDEServerConv

- DDEServerConv1.

 

Данное присваивание позволяет связать компоненты только во время проектирования приложения, но для того, чтобы они оказались связанными и во время выполнения, необходимо в инициализационной части приложения сделать явное присваивание:

DDEServerItem1.ServerConv := DDEServerConv1;

3. Система трехуровневой адресации данных выглядит следующим образом:

 

  1. имя сервиса - имя исполняемого файла приложения-сервера, или имя проекта без расширения;
  2. имя темы - имя компонента TDDEServerConv - DDEServerConv1.
  3. имя элемента - имя компонента TDDEServerItem - DDEServerItem1.

4. Данные содержатся в свойстве Text компонента TDDEServerItem.

Чтобы передать данные, предназначенные для клиентов, например, из компонента TEdit - строки ввода - в компонент TDDEServerItem, необходимо выполнить присваивание:

DDEServerItem1.Text := Edit1.Text;

где Edit1 - имя компонента TEdit, включенного в форму приложения-сервера для отображения данных.

Данный текст должен быть включен в обработчик события OnChange компонента TEdit.

Если клиент передал данные серверу с помощью метода PokeData, то принятые сервером данные могут быть отображены, например, в строке редактирования следующим образом:

Edit1.Text := DDEServerItem1.Text,

 

Данный текст должен быть написан в обработчике события OnPokeData компонента TDDEServerItem.

Создание DDE-клиента

1. Компоненты TDDEClientConv и TDDEClienItem помещаются в форму. Среда проектирования автоматически присваивает им имена:

DDEClientConv1 и DDEClientItem1.

2. Производится связывание компонентов DDEClientConv1 и DDEClientItem1 путем присваивания свойству DDEConv компонента TDDEClientItem значения, равного имени компонента TDDEClientConv - DDEClientConv1.

Данное присваивание позволяет связать компоненты только во время проектирования приложения, но для того, чтобы они оказались связанными и во время выполнения, необходимо в инициализационной части приложения сделать явное присваивание:

DDEClientItem1.DDEConv := DDEClientConv1;

3. Производится присвоение имен сервиса, темы и элемента соответствующим свойствам компонентов клиента:

 

  1. свойству DDEService компонента TDDEClientConv присваивается значение, равное имени проекта сервера;
  2. свойству DDETopic компонента TDDEClientConv присваивается значение, равное имени компонента TDDEServerConv сервера;
  3. свойству DDEItem компонента TDDEClientItem присваивается значение, равное имени компонента TDDEServerItem сервера.

4. Если свойству ConnectMode компонента TDDEClientConv присвоить значение ddeAutomatic, а свойству ServiceApplication компонента TDDEClientConv присвоить значение, равное полному (с учетом пути) имени программы сервера, то приложение сервер будет загружаться и устанавливать соединение с клиентом автоматически при загрузке последнего.

Со стороны клиента соединение устанавливается вызовом метода

SetLink,

которому передаются строки с именами сервиса и темы.

5. Запрос данных от сервера выполняется путем вызова метода

DDEClientConv1.RequestData(DDEClientItem1.DDEItem)

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

 

6. Передача данных от клиента серверу выполняется путем вызова метода

DDEClientConv1.PokeData(...),

которому передаются имя элемента данных и указатель на ASCIIZ-строкe, содержащую передаваемые данные.

7. Сами данные содержатся в свойстве Text компонента TDDEClientItem и обновляются вместе с данными сервера.

 

Данные могут выводиться в строку редактирования Edit1 типа TEdit с помощью присваивания:

Edit1.Text := DDEClientItem1.Text;

Указанная строка должна быть включена в обработчик события OnChange компонента TDDClientItem.

8. Разрыв соединения выполняется по инициативе клиента методом CloseLink компонента TDDEClientConv.

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

 


5.3.5. Обмен данными по технологии OLE

5.3.5.1. Введение

OLE является столь большой областью технологии, что имеет отношение не только к связыванию и внедрению объектов. Кроме технологий, включающих в себя связывание и внедрение объектов, OLE имеет прямое отношение к технологиям ActiveX, структурированной памяти, автоматизации и перетаскивания объектов. Все эти технологии базируются на программной технологии, названной Component Object Model или COM.

Что такое технология OLE? Это набор объектов, построенных поверх объектной модели СОМ, которые допускают коммуникации в ходе выполнения процессов. Большая часть технологии связана с получением нескольких приложений, процессов, машин или операционных систем, общающихся друг с другом.

OLE - это не просто технология обмена данными между приложениями, OLE является ядром современной концепции документно-ориентированной среды. Поэтому давайте на технологию OLE посмотрим более широко, чем просто на способ обмена данными.

5.3.5.2. Понятие документно-ориентированной среды

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

Под документом понимается не только текст, а любой файл, подготовленный пользователем.

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

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

Следующие технологии существуют для организации д-о работы.

 

  1. OLE фирмы Microsoft;
  2. OpenDoc - все, кроме Microsoft, т.е. Apple, IBM, Novell, Oracle, Xerox;
  3. CORBA - Common Object Request Broker Architecture Общая Архитектура Посредника Запросов между Объектами - IBM, HP, DEC, Sun.

 

Мы будем рассматривать технологию OLE.

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

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

Следующим шагом на пути д-о работы является понятие Clipboard. Windows как многозадачная среда сразу же проектировалась с высокоэффективными средствами обмена данными между приложениями. Достоинство Clipboard состоит в том, что исчезла проблема преобразования данных при передаче их из одного приложения в другое. Преобразование происходит незаметно для пользователя на основе ряда установленных стандартов. Приложение автоматически контролирует наличие данных в буфере: данные есть - функция Paste - активна, данных нет - функция Paste - пассивна.

 

 

 

                           ---------------------------

            Copy           |Визуальное  представление |  Paste

              -------------|         фрагмента        |-------------

              |            ----------------------------            |

   ----------------------                                ---------------------

   | Приложение-источник|                                |Приложение-приемник|

   |   ------------     |                                |  -----------------|

   |   | Фрагмент |     |                                |  |Визуальное     ||

   |   ------------     |                                |  |представление  ||

   |                    |                                |  |фрагмента      ||

   |                    |                                |  -----------------|

   ----------------------                                ---------------------

Недостатком является невозможность накопления данных; при появлении нового блока данных, старые теряются, а также ограниченность размера блока, т.к. Clipboard - это буфер в ОЗУ.

Следующим шагом развития технологии обмена данными стала технология DDE. Фрагмент, который мы хотим перенести в другое приложение, должен быть частью файла, который называется документом-источником. Фрагмент помещается в Clipboard командой Copy, а считывается не командой Paste, a командой PasteLink.

                           ---------------------------

            Copy           |Визуальное  представление |  Paste Link

              -------------|      фрагмента           |-------------

              |            ----------------------------            |

   ----------------------                                ---------------------

   | Приложение-источник|                                |Приложение-приемник|

   |   ------------     |                                |  -----------------|

   |   | Фрагмент |     |                                |  |Визуальное     ||

   |   ------------     |                                |  |представление  ||

   |                    |                                |  |фрагмента      ||

   |                    |                                |  -----------------|

   ----------------------                                ---------------------

             ^                                                     |         

             |               ссылка на оригинал                    |          

             -------------------------------------------------------         

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

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

Часто производится сравнение протоколов DDE и OLE и спрашивается, когда какой протокол необходимо применять. Протокол DDE целесообразно использовать для обмена простыми типами данных, например, текстовыми строками, в то время как протокол OLE способен поддерживать обмен существенно более сложной информацией, такой как звуки, изображения.

5.3.5.3. Принципы OLE

В OLE понятие объекта имеет следующий смысл: объект - это совокупность трех видов данных:

 

  1. собственные данные в том внутреннем формате, в котором их создало приложение-сервер;
  2. данные для визуального представления;
  3. служебная информация о программе-сервере.

 

Т.о. OLE-объект – это данные, разделяемые двумя приложениями. Приложение-клиент только отображает данные (2).

Для приложения-клиента появился термин - OLE-контейнер - это приложение, которое может содержать объект. OLE-сервер - это приложение, которое может создавать и редактировать объект.

Появилось понятие "составной документ" как документ, содержащий различные типы данных, оформленные как объекты.

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

При этом понятие нахождения объекта в приложении-клиенте может приобретать двоякий смысл.

Вариант 1. Внедрение объекта. Смысл внедрения состоит в том, что все три вида данных объекта копируются в составной документ.

 

 

 

 

 


Достоинство данного метода состоит в законченности файла составного документа. Все, что нужно, есть в нем самом. Это важно в случае возможного перенесения документа на другую физическую машину.

Недостаток состоит в том, что велик размер файла составного документа. Если есть несколько составных документов с данным объектом, то они скопированы в каждом из составных документов.

Paste Link

 

 

 

 

 

 

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

Визуальное представление (1)

 
 

 

 

 

 

 

 

 


Связывание экономит место на диске, особенно, когда один объект является источником для многих клиентов, но при переносе на другую машину документа-клиента необходимо отследить, чтобы все документы-серверы тоже были перенесены.

Исторически протокол OLE существует в двух версиях - OLE 1.0 и OLE 2.0. Сейчас, естественно, предпочтение отдается OLE 2.0.

Версия OLE 1.0 была полностью реализована средствами DDE и обладает следующим неудобством. При активизации объекта в документе-клиенте, загружается приложение-сервер и ему передается объект для редактирования. Это хорошо, но плохо то, что приложение-сервер загружается в отдельном окне и при редактировании объекта не видно окружения, содержащегося в документе-клиенте. Недостатки, связанные с этим устранены в версии OLE - OLE 2.0.

 

 

5.3.5.4. Характеристика технологии OLE 2.0

5.3.5.4.1. Версия OLE - OLE 2.0 реализует концепцию визуального редактирования - редактирования на месте. Приложение-сервер запускается при активизации объекта, но оно как бы запускается в окне приложения-клиента. Приложение-клиент как бы превращается в приложение-сервер. Было приложение-клиент, а стало приложение-сервер. В строку меню приложения-клиента встраиваются позиции меню приложения-сервера, необходимые для редактирования, аналогично согласованно корректируются и панели инструментов. А поле документа-клиента с визуальным представлением различных объектов не исчезает.

5.3.5.4.2. Другой особенностью ОLЕ 2.0 является возможность приложения предоставить ряд своих функций (например, проверку орфографии) для доступа из других приложений - OLE-Automation. Модуль, выполняющий нужную функцию, оформляется в виде объекта ОLЕ и передается в другое приложение.

Автоматизация OLE предоставляет возможности по управлению объектами, которые размещаются в других приложениях или в DLL (динамических библиотеках).

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

Автоматизация включает в себя СЕРВЕРЫ автоматизации и КЛИЕНТОВ автоматизации.

Сервер обеспечивает возможности, а клиент получает к ним доступ. Серверы делятся на серверы внутренней обработки и на локальные серверы. Сервер внутренней обработки - это DLL, загружаемая в адресное пространство программы клиента. Локальные серверы - это автономные программы.

 

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

 

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

Пример простого сервера автоматизации OLE

В приложении - сервере автоматизации должен быть модуль, содержащий следующее объявление:

 

AutoClassInfo   : TAutoClassInfo = (

AutoClass    : TMyAuto;        // Имя класса объекта автоматизации

ProgID       : 'AutoProj.MyAuto';     // Имя приложения.Имя модуля

ClassID      : '12345678-1234-1234-123456789ABC';

Description  : 'Sam';

Instancing   : acMultiInstance);

 

Ключевыми элементами являются ProgID и ClassID, которые попадают в реестр (регистрационную базу данных) при регистрации, которая выполняется при запуске приложения с ключом /regserver. ClassID - случайное число, а чтобы не ссылаться на такое сложное число используется ProgID.

Instancing позволяет организовать сервер с доступом к нему либо нескольких клиентов одновременно, либо одного, либо внутренний сервер.

Услуги, предоставляемые сервером, реализуются методами, помещаемыми в раздел Automated объявления класса:

TMyAuto = Class(TAutoObject)

Private

      Private declarations

Automated

      Procedure ShowDialog;

End TMyAuto;

Procedure TMyAuto.ShowDialog;

Begin

      ShowMessage('Hello!');

End TMyAuto.ShowDialog;

 

Процедура ShowDialog теперь доступна из приложений клиентов:

Простой клиент автоматизации OLE (пример 1)

Приложение-клиент выполняет теперь следующие действия, чтобы сделать доступной процедуру ShowDialog сервера:

Var

      V : Variant;

Begin

      V := CreateOleObject('AutoProj.MyAuto');

      V.ShowDialog;

End;

 

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

                Например, вам требуется создать неинтерактивное (работающее без участия пользователя) приложение, создающее документ Word и записывающее в него некоторый текст.

 

                Такой пример будет выглядеть следующим образом:

Простой клиент автоматизации OLE (пример 2)

Следующий пример обеспечивает доступ из приложения Delphi к функциям Word.

Var

      V : Variant;

Begin

      V := CreateOleObject('Word.Basic');

      V.FileNew('Normal');

      V.Insert('Hello from Delphi!');

      V.FileSaveAs('C:\SAMPLE.DOC');

End;

Этот код создает документ Word, вставляет в него строку и сохраняет его. Внешне все очень просто, но за внешней простотой скрыто множество сложнейших действий.

Следует обратить внимание на тип данных Variant и на методы FileNew, Insert, FileSaveAs, которые являются не процедурами Delphi, а методами Word.

Как осуществляется запуск Word. Для этого опять требуется реестр.

По словам 'Word Basic' в реестре находится значение специального идентификатора CLSID. По нему в этом же реестре находится строка примерно следующего вида: С:\WINWORD\WINWORD.EXE /Automation. По ключу /Automation Word возвращает ссылку на объект автоматизации, после чего оказываются доступными его методы.

 

Создание клиента автоматизации средствами Microsoft

 

Создание клиента автоматизации с использованием средств Microsoft существенно сложнее, чем с использованием Delphi, однако это позволяет более детально увидеть механизмы работы клиента.

 

Во-первых, каждое приложение MS Office содержит файл, содержащий библиотеку классов. Например, классы Word, доступные клиенту, содержатся в файле MSWORD.OLB.

 

Эти классы можно импортировать в собственное приложение.

 

Например, класс _Application описывает приложение Word. Если вы сделаете объявление вида:

 

_Application m_app;

 

то это является основой использования возможностей Word в своем приложении.

 

Затем надо найти уникальный зарегистрированный в реестре идентификатор приложения Word.

Это делается функцией:

 

CLSIDFromProgID("Word.Application",&clsid)

 

Т.е. вы по текстовой строке ProgId - "Word.Application" находите число CLSID в реестре.

 

Затем необходимо проверить, загружено ли приложение сервер. Это делается вызовом:

 

GetActiveObject(clsid,NULL,&pUnk);

 

Данный вызов возвращает указатель на переменную типа IUnknown. Этот тип является базовым в СОМ-технологии, на которой основана OLE 2.0.

Через этот интерфейс клиент получает доступ к объектам автоматизации, предоставляемым сервером.

                Как он это делает? Он это делает путем запроса следующего вида:

 

pUnk->QueryInterface(IID_IDispatch,(void**)&pDisp);

 

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

 

                Если приложение-сервер активно (GetActiveObject вернула TRUE), то диспетчер присоединяется к экземпляру m_app приложения-сервер методом

 

m_app.AttachDispatch(pDisp);

 

                Если приложение-сервер загружено не было, то диспетчер создается методом

 

m_app.CreateDispatch("Word.Application");

 

в этом методе он же и присоединяется к экземпляру m_app.

 

                После выполнения указанных действий становятся доступными методы приложения-сервер.

 

Например, создать экземпляр класса документов приложения:

 

Documents Docs(m_app.GetDocuments());

 

Класс Documents так же, как и класс _Application, описан в файле MSWORD.OLB.

 

Далее можно создать экземпляр класса отдельного документа:

 

_Document adoc(Docs.Open(docfilename);

 

Класс _Document так же описан в файле MSWORD.OLB.

 

Теперь с документом можно работать. Например, требуется сохранить его в определенном формате:

 

adoc.SaveAs(filename, FormatNum);

 

После завершения работы с документом его надо закрыть:

 

adoc.Close();

 

надо отсоединить диспетчер:

 

m_app.DetachDispatch();

 

и завершить приложение

 

m_app.Quit();

 

                Если посмотреть на то, как выглядят методы в файле MSWORD.ODB, то можно увидеть, что все они вызываются через один интерфейс:

 

InvokeHelper(Id, ...);

 

которому передается уникальный идентификатор метода.

 

Например:

 

GetDocuments() - InvokeHelper(0x6, ...);

 

Quit() - InvokeHelper(0x451, ...);

 

SaveAs() - InvokeHelper(0x178, ...)

 

 

                Подобные средства приходится использовать, если необходимо создать приложение, работающее с документами Word без участия оператора.

 

                Традиционные способы работы с файлами в случае doc-файлов, xls-файлов и им подобных работать практически невозможно.

 

                В чем причина этого? Дело в том, что подобные файлы представляют собой структурированные хранилища – особый тип файлов.

 

5.3.5.4.3. Ядром ОLЕ 2.0 стал способ хранения данных в составном документе.

 

Составной документ представляется в виде набора хранилищ, в каждом из которых может содержаться объект, созданный приложением-сервером. Само приложение-клиент не знает способа хранения объекта, созданного другим приложением. Поэтому при сохранении документа клиент как бы "просит" сервер сохранить свои объекты, а сам предоставляет хранилища для этого. При этом общая структура объекта едина для всех приложений, поддерживающих протокол ОLE, и выглядит так, как было показано ранее.

Структурированные хранилища (структурированная память) - это технология записи объектов или данных на диск. Эта техника обеспечивает все услуги, которые существуют в стандартном файловом вводе/выводе. Можно записывать файлы на диск, можно создавать каталоги и подкаталоги. Отличие структурированной памяти от стандартного файлового ввода/вывода заключается в том, что каждый набор каталогов и файлов структурированной памяти размещается внутри большого единого файла, подобно тому, как набор таблиц InterBase размещается внутри единого файла .GDB.

 

Файл структурированной памяти называется составным файлом. Каталоги внутри этих составных файлов называются потоками.

Например, все DOC-файлы в действительности являются файлами структурированной памяти.

Возможно, структурированная память будет стандартной формой файлового ввода/вывода в будущих ОС.

 

 

 

 

Элементы работы со структурированной памятью

Составной файл создается вызовом функции StgCreateDocFile, которая возвращает ссылку на интерфейсный объект IStorage. Этот вызов как бы создает новый чистый жесткий диск.

              Основные методы работы с объектом IStorage:

 

  1. CreateStream;
  2. OpenStream;
  3. CreateStorage;
  4. OpenStorage;

 

Для получения потока вызывается метод этого объекта CreateStream, который возвращает ссылку на поток типа IStream. Этот вызов как бы создает новый файл на диске.

 

В поток можно писать данные методом Write. Читать данные из потока можно методом Read.

              Основные методы работы с объектом IStream:

  1. Read;
  2. Write;
  3. Seek;

 

В конце работы с потоком и памятью их надо освободить методами Release.

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

Кроме того, возможно создание вложенных хранилищ, т.е. хранилищ внутри других хранилищ. Это напоминает наличие подкаталогов внутри каталогов.

Структурированная память - это очень заманчивая система, когда требуется хранить очень много файлов. Эта система позволяет все их спрятать внутри одного файла.

5.3.5.4.4. Дополнительные возможности OLE 2.0:

  1. Развитие метода Drag-and-Drop для перемещения любых объектов внутри любых окон, а не только файлов из File Manager;
  2. Возможность создания вложенных объектов;
  3. Возможность частичного настраивания связей при перемещении документов из каталога в каталог;
  4. Возможность выбора способа внедрения объекта в зависимости от цели, что позволяет экономить память, например, можно внедрить таблицу из Excel в Word как таблицу (для активного редактирования), а можно - только как битовый образ (только для отображения).

 

5.3.5.4.5. Иерархия средств OLE 2.0

OLE 2.0 - это набор интерфейсов, позволяющих клиенту и серверу обмениваться данными.

Базовый протокол обмена – это протокол UTD - Uniform Data Transfer (Унифицированная передача данных).

UTD - это расширение протокола обмена через Clipboard, предусматривающее механизмы уведомления об изменении данных и переговоры о форматах.

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

Самое важное в механизмах OLE 2.0 - это отказ от протокола DDE, основанного на механизме передачи сообщений, в пользу протокола COM - Component Object Model, основанного на механизме удаленных процедурных вызовов - RPC Remote Procedure Call.

COM - это протокол низкого уровня OLE, предусматривающий набор стандартов для реализации объектов, способы коммуникации объектов друг с другом и набор функций API.

СОМ - это технология, которая лежит под OLE, но сама не является частью OLE.

СОМ - это способ реализации объектов на уровне ОС. Это означает, что объекты СОМ могут быть интегрированы в саму ОС и действовать в качестве естественного ее расширения.

Если объекты СОМ располагаются в DLL, то они становятся доступными из различных языков, таким образом, объекты СОМ разрабатываются для преодоления границ между программами, языками, операционными системами и машинами.

 

Примером конкурентной технологии является технология CORBA.

Объекты СОМ могут размещаться либо в DLL, либо в исполняемых модулях, или, со временем, на удаленных машинах. Когда они размещены в DLL, то известны под именем серверов внутренней обработки.

При размещении внутри исполняемого модуля, их называют локальными серверами. При размещении на удаленной машине их называют распределенными объектами.

Взаимосвязь уровней представлена ниже:

5.3.5.4.6. Перспективы OLE

  1. OLE из средства обмена данными превратилось в средство взаимодействия приложений и хранения данных;
  2. В OS Windows OLE является основой объектно-ориентированного пользовательского интерфейса;
  3. В будущих версиях Windows OLE, основанная на RPC, будет сетевой технологией OLE 3.0, позволяющей клиенту и серверу находиться на разных физических машинах.

5.3.5.4.7. Недостатки OLE

  1. чрезвычайно сложная технология для разработчиков;
  2. некоторая несогласованность в интерфейсах разных приложений;
  3. некоторое расхождение в понятиях "объекта" в OLE и ООП;
  4. ненасытность в отношении аппаратных ресурсов;
  5. трудности перевода на сетевую технологию, связанные с изначальной ориентированностью протокола UTD на одиночную машину, и состоящие в больших объемах передаваемых данных.

 


5.3.5.5. Пример проектирования OLE-контейнера

5.3.5.5.1. Трехуровневая адресация OLE-объекта

Как уже было сказано, разработка приложения, поддерживающего протокол OLE, является чрезвычайно сложной задачей. Сложность повышается, если отсутствуют внятные описания этого протокола.

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

Delphi с его средствами визуального проектирования и набором готовых компонентов дает возможность хотя бы обозреть этапы создания OLE-приложения.

 

Вспомним трехуровневую адресацию данных в протоколе DDE. В OLE также поддерживается трехуровневая адресация объектов следующего вида:

 

  1. Класс OLE-объекта определяет приложение-сервер, которое создало OLE-объект. Класс должен быть определен как для связанного, так и для встроенного объекта.
  2. Документ OLE-объекта определяет файл-источник, который содержит данные для объекта. Документ должен быть определен обязательно только для связанного объекта.
  3. Элемент (item) OLE-объекта определяет, какая часть OLE-документа содержит данные для связывания или встраивания. Элемент используется если необходимо использовать меньшую часть данных, чем целый файл документа.

5.3.5.5.2. Общая характеристика технологии создания OLE-клиента

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

Кратко идея выглядит следующим образом. Построение OLE приложения-клиента связано с помещением компонента OleContainer на форму.

Помещение нового объекта связано с вызовом метода InsertObjectDialog, а считывание объекта из Clipboard - с вызовом метода PasteSpecialDialog.

 

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

 

Действия, которые могут быть выполнены с объектом OLE, описываются в Реестре в разделе VERB. Например, для объекта mplayer такими действями являются: Open, Play, Edit.

 

Эти действия хранятся в свойстве ObjectVerbs контейнера.

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

Эти функции решают следующие задачи по загрузке:

 

  1. создание нового объекта;
  2. чтение объекта из Clipboard;
  3. перетаскивание объекта из сервера.

 

Загрузка компонента с помощью одного из перечисленных вариантов сопровождается установкой таких свойств компонента как Класс, Документ и Элемент.

 

 

5.3.5.5.3. Методика создания приложения с OLE-контейнером

Методика создания приложения с OLE-контейнером включает в себя следующие этапы.

 

  1. Разработка процедуры вставки объектов.
  2. Разработка процедуры переноса объектов из Clipboard.
  3. Разработка процедуры перетаскивания объектов.
  4. Разработка процедур поддержки хранения объектов в файлах.

1. Разработка процедуры вставки объектов

Процедура вставки объекта основана на вызове специальной диалоговой функции:

function InsertOLEObjectDlg(..., Var Info : Pointer) ) : Boolean.

 

Эта функция открывает специальное диалоговое окно, позволяющее выбрать тип OLE-объекта для вставки, например, Audio Recorder, Bitmap Image, Video Clip, Wave Sound и т. д.

 

Самое важное - это то, что в случае выбора типа объекта, функция создает структуру данных для инициализации OLE-контейнера и возвращает указатель на эту структуру в переменной Info.

 

Инициализация OLE-контейнера происходит, когда указатель на описанную структуру передается свойству PInitInfo компонента OLE-контейнера.

 

OLEContainer.PInitInfo := Info;

При инициализации устанавливаются свойства компонента OLE-контейнера ObjClass, ObjDoc, ObjItem в соответствие с состоянием диалогового окна вставки объекта.

При инициализации OLE-контейнера активизируется OLE-сервер и берет на себя управление, если объект уже существовал, то он передается в сервер. Пользователь теперь может редактировать объект как хочет.

2. Разработка процедуры переноса объектов из Clipboard

Некоторые OLE-серверы позволяют пользователю копировать OLE-объекты в Clipboard. Тогда приложение OLE-контейнер имеет возможность получить из Clipboard-а этот объект.

 

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

Function PasteSpecialDlg(...,Var Info : Pinter) : Boolean;

 

Info - указатель на структуру данных для инициализации OLE-контейнера.

3. Разработка процедуры перетаскивания объектов

Перетаскивание OLE-объекта из сервера и опускание его в OLE-контейнер - это наиболее простой способ связать или встроить объект.

В этом случае нет необходимости вызывать специальные диалоговые функции создания нового объекта или чтения объекта из Clipboard-а.

Чтобы форма имела возможность принять перетаскиваемый из сервера объект, она должна быть зарегистрирована в среде как "мишень для перетаскивания". Это делается вызовом специальной функции следующего вида:

procedure RegisterFormAsOleDropTarget(Form : TForm;Const Fmts : Array Of BOLEFormat);

 

Когда объект перетаскивается на форму, возникает событие OnDragDrop.

 

Форма приложения имеет обработчик этого события FormDragDrop. Процедура-обработчик имеет параметр Source, в котором ей передается ссылка на перетаскиваемый объект.

Этот объект имеет свойство PInitInfo, соответствующее свойству PInitInfo OLE-контейнера.

 

Поэтому для инициализации контейнера при перетаскивании достаточно присвоить полю контейнера PInitInfo значение поля PInitInfo передаваемого объекта Source:

 

OLEContainer.PInitInfo := Source.PInitInfo;

 

5. Разработка процедур поддержки хранения объектов в файлах

Функции сохранения OLE-объектов в файлах и восстановления их из файлов Delphi также берет на себя.

Для сохранения OLE-объекта в файле необходимо использовать метод SaveToFile компонента OLE-контейнера:

 

OLEContainer.SaveToFile(FileName);

Для загрузки OLE-объекта из файла, необходимо использовать метод LoadFromFile компонента OLE-контейнера:

 

OLEContainer.LoadFromFile(FileName);

 

5.3.5.5.4. Выводы

  1. Каждый OLE-объект должен храниться в собственном OLE-контейнере. Активизация OLE-сервера производится двойным щелчком левой клавиши мыши, когда курсор мыши находится в площади контейнера. Для управления позициями меню сервера и клиента, необходимо пользоваться значениями свойства GroupIndex компонентов меню.
  2. Вставка объектов производится с помощью специальной диалоговой функции InsertOLEObjectDlg, возвращающей информацию для инициализации контейнера.
  3. Для обеспечения возможности чтения OLE-объектов из Clipboard-а необходимо использовать функцию PasteSpecialDlg, возвращающую информацию для инициализации OLE-контейнера.
  4. Для перетаскивания объектов методом Drag&Drop, необходимо форму зарегистрировать как мишень для перетаскивания с помощью метода RegisterFormAsOLEDropTarget. В обработчике событий OnDragDrop необходимо из источника события извлечь информацию для инициализации OLE-контейнера.
  5. Для работы с файлами объектов следует использовать методы компонента OLE-контейнера LoadFromFile и SaveToFile.

 

 

Средства внутренних коммуникаций:

 

  1. Неименованные каналы;
  2. Сообщения;
  3. Clipboard;
  4. DDE;
  5. OLE.

 

 

 

 

 

5.4. Внешние коммуникации

5.4.1. Протоколы ТСР/IP

5.4.1.1. Определение и достоинства протокола TCP/IP

 

                Коммуникационный протокол – это набор правил и форматов данных, необходимых для установления связи и передачи данных.

 

                Достоинства TCP/IP

 

  1. Протоколы основаны на открытых и доступных стандартах, не зависящих от конкретного оборудования.
  2. Протоколы не зависят от физического уровня, что позволяет использовать их в различных сетях: Ethernet, X.25 и других.
  3. Протоколы имеют гибкую систему адресации, которая может быть использована как в локальных, так и в глобальных сетях.
  4. Протоколы поддерживают стандартные протоколы высокого уровня для передачи файлов, электронной почты, терминального доступа.

5.4.1.2. Архитектура TCP/IP

                Архитектура протокола основана на представлении, что коммуникационная инфраструктура включает три объекта:

 

  1. Процессы;
  2. Хосты;
  3. Сети.

 

Процессы являются основными коммуникационными объектами, поскольку, в конечном итоге, между ними осуществляется передача информации.

Выполнение процессов происходит на хостах.

Передача информации проходит через сети, к которым подключены хосты.

 

Чтобы доставить данные процессу, их сначала надо передать нужному хосту, а затем, процессу, который выполняется на данном хосте.

От коммуникационной инфраструктуры требуется маршрутизация и доставка данных хосту, а хост обязан доставить информацию нужному процессу.

 

На этих концепциях сформированы четыре уровня архитектуры:

 

  1. Уровень приложений/процессов
  2. Транспортный уровень (хост – хост)
  3. Интернет-уровень
  4. Уровень сетевого интерфейса

 

Уровни сетевого интерфейса составляют протоколы доступа к физической сети. К этому уровню относятся протоколы Ethernet, IEEE802.x, PPP (Point-to-Point Protocol)

 

Уровень Интернет составляют протоколы, обеспечивающие передачу данных между хостами, подключенными к различным сетям. Основной функцией этих протоколов является выбор маршрута – маршрутизация. Сетевые элементы, осуществляющие передачу из одной сети в другую, называются маршрутизаторами. Иногда их называют шлюзами. Основной представитель уровня Интернет – протокол IP (Internet Protocol).

 

Протоколы транспортного уровня обеспечивают передачу данных между хостами. К транспортному уровню относятся протоколы TCP (Transmission Control Protocol) и UDP (User Datagram Protocol).

 


Протоколы уровня приложений обеспечивают функционирование прикладных услуг, таких как:

 

  1. терминальный доступ (Telnet)
  2. передача файлов (FTP)
  3. передача почты (SMTP)

 

Передача файлов

Электронная почта

Эмуляция терминала

Сетевая файловая система

Менеджмент сети

Уровень приложений

процессов

FTP

SMTP

Telnet

NFS

SNMP

Transmission Control Protocol TCP

User Datagram protocol UDP

Транспортный уровень

Address Resolution

Internet Protocol IP

Internet Control Message protocol ICMP

Уровень Интернет

Ethernet, serial

Уровень сетевого интерфейса

Витая пара, коаксиальный кабель, волоконно-оптический кабель, спутниковый канал

Архитектура протоколов TCP/IP

 

Процесс-клиент

 

Базовая коммуникационная схема TCP/IP

 

Каждый коммуникационный узел должен иметь уникальный адрес. Существует несколько уровней адресации.

На сетевом уровне (уровень 1) имеется МАС-адрес (media access control).

На IP-уровне имеется IP- или Интернет-адрес, который адресует хост.

Хост, получив данные, доставляет их процессу. Адрес процесса называется номером порта.

 

Таким образом, чтобы однозначно адресовать процесс необходимо указать:

 

  1. Номер порта
  2. Тип протокола транспортного уровня
  3. Интернет-адрес

 

МАС-адрес используется шлюзами в автоматическом режиме и для пользователя значения не имеет.

 

 

 

 

 


 

5.4.1.3. Соответствие между моделями TCP/IP и ISO OSI

TCP/IP

ISO OSI

 

Уровень приложения

Уровень приложений

Уровень представления

Уровень сеанса

Транспортный уровень

Транспортный уровень

Уровень Интернет

Сетевой уровень

Уровень сетевого интерфейса

Уровень канала

Физический уровень

5.4.2. Протокол IP

5.4.2.1. Структура заголовка IP-пакета

Протокол IP обеспечивает доставку фрагмента данных от источника к получателю.

Протокол IP выполняет три основных функции:

 

  1. адресацию
  2. фрагментацию
  3. маршрутизацию

 

Данные, формат которых понятен протоколу IP, называются датаграммой. Датаграмма состоит из заголовка и данных, полученных от протоколов верхних уровней.

 

 


IP-датаграмма

 

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

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

В процессе обработки датаграммы протокол иногда вынужден выполнять ее фрагментацию. Это необходимо делать, когда сети, через которые вынуждена проходить датаграмма, используют разные размеры кадра. Например, есть сети с размером кадра до 4470, а есть (Ethernet) с размером до 1500.

 

0 - 3

4 - 7

8 - 11

12 - 15

16 - 19

20 - 23

24 - 27

28 – 31

Version

IHL

Type of service

Total length

Identification

Flags

Fragment Offset

TTL

Protocol

Header Checksum

Source address

Destination address

Options

Padding

Data

Структура заголовка IP-датаграммы

 

Заголовок занимает минимум 20 байтов и содержит следующие поля:

 

Version – определяет версию протокола. Обычно, 4.

 

IHL – internet header length – длина заголовка в 32-битных словах. При 20 байтах IHL = 5.

 

Type of service - ,битовое поле

 

  1. 0 – 2 - Precedence – относительная значимость датаграммы. Большее значение соответствует большему приоритету.
  2. 3 – Delay – 0 – нормальная задержка при обработке, 1 – низкое значение задержки.
  3. 4 – Throughput. Скорость передачи. 0 – нормальная, 1 – высокая скорость.
  4. 5 – Reliability. Надежность. 0 – нормальная, 1 – высокая надежность.
  5. 6 – 7 – резерв.

 

                Это поле определяет правила обработки датаграммы при передаче через сети. Иногда правила противоречат друг другу. Например, низкая задержка противоречит высокой надежности. Стандарты разрешают эти противоречия.

 

Total length – размер датаграммы. Ограничен 65535 байтами.

 

Identification – поле, используемое при фрагментации. Это поле одинаково для всех фрагментов одной датаграммы.

 

Flags – битовое поле, тоже используется при фрагментации.

 

  1. 0 – резерв
  2. 1 – DF – 0 – можно фрагментировать, 1 – нельзя фрагментировать.
  3. 2 – MF – 0 – последний фрагмент, 1 – не последний фрагмент.

 


Fragment offset – номер фрагмента.

 

Объединяются датаграммы с одинаковыми полями Identification, Source address, Destination address, protocol.

 

TTL – time to live – время жизни датаграммы. Если 0, то датаграмма уничтожается. Каждый модуль протокола уменьшает значение этого поля на число секунд, затраченных на обработку. Цель этого поля – уничтожать "заблудившиеся" датаграммы.

 

Protocol – номер протокола верхнего уровня. Для TCP – 6, для UDP – 17.

 

Header checksum – контрольная сумма.

 

Source address и destination address – IP-адреса источника и получателя.

 

Options – содержит дополнительные параметры протокола.

 

Padding – выравнивает заголовок до границы 32-битного слова.

5.4.2.2. IP-адресация

                Каждый IP-адрес состоит из двух частей: адреса сети и адреса хоста в этой сети. Существует 5 форматов адреса, отличающихся по числу битов, отводимых для адреса сети и хоста. Эти форматы определяют классы адресов отA до D. Класс определяют первые три бита.

 

0

7 - сеть

24 – хост

Класс A

 

1

0

14 – сеть

16 - хост

Класс B

 

1

1

0

21 – сеть

8 – хост

Класс С

1

1

1

0

28 – групповой адрес

Класс D

1

1

1

1

0

Зарезервировано

Класс Е

 

Уникальность адресов обеспечивается их централизованным назначением.

 

В классе А число сетей ограничено126 (0 и 127 – зарезервированы). Это устаревшая структура.

В классе В число сетей 16 382. Адреса сетей такого класса также сейчас не предоставляются.

В классе С число сетей равно 2 097 150. При этом число хостов может быть не более 254. Самый распространенный в настоящее время класс сетей. Но и он истощается.

Поэтому введена дополнительная иерархия – адрес хоста может быть разделен на адрес подсети и адрес хоста в подсети.

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

 

                Посмотрим, как используется маска подсети.

190.50.0.0

 
Предположим, что есть сеть класса В с адресом 190.50.0.0 и маска подсети равна 255.255.255.0.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 



Пусть, например, хосту Н1 надо обратиться к хосту Н3. Н1 берет адрес Н3 190.50.2.1 и накладывает на него маску подсети 255.255.255.0, получая результат 190.50.2.0. Поскольку Н1 находится в подсети 190.50.1.0, то Н3 напрямую недоступен. Сверившись со своей маршрутной таблицей Н1 обнаруживает, что следующий адрес на пути к Н3 – это R1.

 

Н1 отображает IP-адрес R1 на его физический адрес и посылает R1 датаграмму. R1 ищет адрес назначения в своей маршрутной таблице, пользуясь той же маской подсети, и определяет местонахождение Н3 в подсети, соединенной с его интерфейсом 190.50.2.3.

 

R1 доставляет датаграмму хосту Н3, получив предварительно его физический адрес по IP-адресу.

 

Пусть теперь хост Е из внешней сети хочет отправить датаграмму Н3. Поскольку 190.50.2.1 – адрес класса В, то маршрутизатору сети, где находится этот хост Е, известно, что Н3 находится в сети 190.50.0.0. Т.к. шлюзом в эту сеть является R2, то датаграмма от Е в конце концов дойдет до R2.

 

R2 получив датаграмму, накладывает маску, выделяет адрес подсети 190.50.2.0, определяет R1 в качестве следующего узла на пути к Н3 и посылает R1 датаграмму, которую R1 отправляет Н3.

 

Хосту Е неизвестна внутренняя топология сети 190.50.0.0. Он просто отсылает датаграмму шлюзу R2. Только R2 и другие хосты внутри сети определяют существование подсетей и маршруты доступа к ним.

 

5.4.3. Протокол UDP

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

Из-за минимальной функциональности протокол требует меньших накладных расходов по сравнению с TCP.

Структура заголовка UDP представлена ниже.

 

0 - 15

16 - 31

Source port

Destination Port

Length

Checksum

Data

 

Длина датаграммы не может быть меньше 8 байтов.

Если контрольная сумма используется, то она вычисляется по следующим полям IP-заголовка:

 

  1. Protocol (равен 17)
  2. Source address
  3. Destination address

 

Протокол UDP используют в следующих случаях:

 

  1. при взаимодействии с сервером доменных имен DNS (порт 53)
  2. при синхронизации времени (порт 123)
  3. при удаленной загрузке (порт клиента 67, порт сервера 68)
  4. при удаленном копировании (порт 69)
  5. при удаленном вызове процедур (порт 111)

 

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

 


 

5.4.4. Протокол TCP

5.4.4.1. Структура TCP-сегмента

ТСР – протокол, поддерживающий надежную передачу данных с предварительным установлением связи между источником и получателем.

ТСР характеризуется следующими особенностями:

 

  1. Перед фактической передачей данных необходимо установление связи, т.е. запрос и подтверждение на возможность передачи данных. После обмена данными сеанс должен быть явно завершен.
  2. Доставка информации является надежной, т.е. нет дублирования, пропадания и нарушения очередности пакетов.
  3. Возможность управления потоком во избежание переполнения или затора.
  4. Доставка экстренных данных.

 

TCP-канал - это двунаправленный поток данных между соответствующими объектами обмена – источником и получателем. Данные передаются в виде пакетов различной длины, называемых сегментами. Формат ТСР сегмента представлен ниже.

 

0 – 3

4 - 7

8 - 11

12 - 15

16 - 19

20 - 23

24 - 27

28 – 31

Source port

Destination port

Sequence number

Acknowledgement number

Offset

Reserved

Flags

Window

Checksum

Urgent pointer

Options

Padding

Data

Формат TCP-сегмента

 

Положение каждого сегмента в потоке фиксируется порядковым номером (Sequence number). Сегмент также содержит номер подтверждения (Acknowledgement number), определяющий номер первого неподтвержденного байта в потоке.

Поле Window определяет количество байтов, которые получатель готов принять, начиная с байта, номер которого определен как Acknowledgement number.

Поле Offset указывает на начало данных в сегменте. Поле необходимо т.к. заголовок может иметь переменную длину.

Поле Flags содержит 6 управляющих битов:

 

  1. URG – экстренные данные
  2. ACK – в заголовке есть подтверждение
  3. PSH – немедленная передача данных
  4. RST – уничтожение канала
  5. SYN – управляющее сообщение
  6. FIN – прекращение передачи

 

Поле Checksum – контрольная сумма

Поле Urgent pointer – указатель на экстренные данные (при установленном флаге URG).

Поле Options – дополнительные параметры.

Поле Padding – выравнивающее до 32 бит поле.


 

5.4.4.2. Этапы TCP-сеанса

 

TCP-сеанс состоит из трех этапов.

 

Этап 1 – "рукопожатие" (handshaking).

  1. Одна из сторон, становящаяся клиентом, посылает другой стороне – серверу – сегмент SYN.
  2. Если сервер готов общаться с клиентом, то он создает канал (создает необходимые структуры данных) сегмент SYN, ACK.
  3. Клиент создает канал (необходимые структуры данных) и посылает в ответ сегмент ACK.

 

На этом этап создания канала завершается.

 

Этап 2. Обмен данными.

После создания канала стороны обмениваются данными в дуплексном режиме.

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

Данные буферизируются на каждой из сторон.

Когда требуется немедленная передача данных, протокол верхнего уровня устанавливает флаг PSH.

Ошибочные данные повторяются. Ошибки обнаруживаются за счет использования контрольных сумм.

Если данные доставлены без ошибок, получатель подтверждает это сегментом ACK.

Если отправитель не получил подтверждения в течение некоторого времени, он повторно посылает данные. (Негативное подтверждение NAK отсутствует)

 

Этап 3. Завершение сеанса.

Любая из сторон может завершить передачу, отправив сегмент FIN. Другая сторона подтверждает получение такого сегмента, но может продолжать передачу по уже одностороннему каналу. Когда эта вторая сторона пошлет сегмент FIN, а первая пошлет подтверждение, тогда канал полностью закрывается.

 

Состояния TCP-канала

 

  1. CLOSED – канал не существует
  2. LISTEN – готовность сервера к приему запросов
  3. SYN-SENT – клиент послал сегмент SYN с запросом на соединение
  4. SYN-RECEIVED – сервер получил SYN и отослал SYN, ACK
  5. ESTABLISHED – канал установлен
  6. FIN-WAIT-1 – сторона послала сегмент FIN
  7. CLOSE-WAIT – сторона приняла FIN и отправила на него ACK
  8. FIN-WAIT-2 – получен ACK на посланный FIN
  9. CLOSING – ожидание подтверждения на запрос окончания связи
  10. LAST-ACK – ожидание подтверждения на запрос окончания связи (канал уже односторонний)
  11. TIME-WAIT – таймаут перед окончательным разрушением канала.

 

Заключение

 

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

 

 

 


5.4.5. Программные средства поддержки сетей

5.4.5.1. Состав программных средств поддержки сетей

В реальных ОС сетевые протоколы поддерживаются с помощью API, драйверов протоколов, а также драйверов устройств сетевых адаптеров.

Соотношение между перечисленными объектами и 7-уровневой архитектурой выглядит следующим образом.

 

7

Сетевое приложение

6

Библиотеки сетевых API

5

4

Драйверы протоколов TCP/IP, IPX/SPX, NetBEUI

3

2

Библиотека драйверов NDIS (network driver interface specification)

1

Сетевые адаптеры

 

К сетевым API относятся следующие средства:

 

  1. Почтовые ящики – MailSlots
  2. Именованные каналы – Named Pipes
  3. Удаленные вызовы процедур – RPC
  4. Протокол NetBIOS
  5. Сокеты – Sockets

 

 

1. Почтовые ящики

 

Почтовые ящики обеспечивают механизм ненадежной односторонней передачи сообщений.

Почтовый ящик создается вызовом:

 

MsFile = CreateMailslot("\\\\.\\mailslot\\mymailslot",

                        0,

                        MAILSLOT_WAIT_FOREVER,

                        NULL);

 

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

 

Клиент открывает почтовый ящик вызовом:

 

MsFile = CreateFile("\\\\pc_name\\mailslot\\mymailslot",

                    GENERIC_READ|GENERIC_WRITE,

                    FILE_SHARE_READ|FILE_SHARE_WRITE,

                    NULL,

                    OPEN_EXISTING,

                    FILE_ATTRIBUTE_NORMAL,

                    NULL);

 

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

 

WriteFile(MsFile, S, S.GetLength(), &NumberOfBytesWritten, NULL);

 

Сервер может читать сообщения из почтового ящика с помощью вызова:

 

ReadFile(MsFile, buffer, sizeof(buffer), &NumberOfBytesRead, NULL);

 

Если данных нет, например, они еще не передавались, то процесс, вызвавший операцию ReadFile(), приостановит свое выполнение. Это далеко не всегда приемлемо.

 

 

Чтобы преодолеть указанный недостаток, можно пойти двумя путями:

 

  1. Выполнять операцию ReadFile() в отдельном потоке
  2. Использовать технологии асинхронного ввода-вывода.

 

Асинхронный ввод-вывод, т.е. ввод-вывод без ожидания – это целая технология, элементы которой мы рассмотрим позже на примере именованных каналов.

 

                Применительно к Mailslot для асинхронного чтения данных может быть использована функция:

 

BOOL GetMailslotInfo(
  HANDLE hMailslot,          // mailslot handle
  LPDWORD lpMaxMessageSize,  // maximum message size
  LPDWORD lpNextSize,        // size of next message
  LPDWORD lpMessageCount,    // number of messages
  LPDWORD lpReadTimeout      // read time-out interval
);

 

Получив из этой функции число сообщений (должно быть больше нуля), можно вызывать функцию ReadFile() с параметром:

 

ReadFile(MsFile, buffer, NextSize ,&NumberOfBytesRead, NULL);

 

После завершения работы приложения закрывают почтовый ящик с помощью вызова:

 

CloseHandle(MsFile);

 

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

 

                Пример потока, выполняющего операцию чтения из почтового ящика, представлен ниже:

 

UINT ReadThread(LPVOID pParam)

{

DWORD cbMessage, cMessage, cbRead;

BOOL  fResult;

char  buffer[100];

 

while (1) {

Sleep(100);

fResult = GetMailslotInfo(MsFile,

NULL,

&cbMessage,//размер следующего сообщения

&cMessage, //количество сообщений

NULL);

if (!fResult) {

CString S;

S.Format("GetMailslotInfo error = %d",GetLastError());

AfxMessageBox(S);

return 0;

}

if (cbMessage == MAILSLOT_NO_MESSAGE) {

continue;

}

while (cMessage > 0) {

memset(buffer,0,100);

fResult = ReadFile(MsFile,buffer,cbMessage,&cbRead,NULL);

if (!fResult) {

AfxMessageBox("ReadFile error %d",GetLastError());

return 0;

}else{

CString S;

S.Format("%s",buffer);

pEdit->SetWindowText(S);

}

fResult = GetMailslotInfo(MsFile,NULL,&cbMessage,&cMessage,NULL);

if (!fResult) {

CString S;

S.Format("GetMailslotInfo error = %d",GetLastError());

AfxMessageBox(S);

return 0;

}

}

}

return 0;

}

 

 

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

 

Именованные каналы предоставляют приложениям надежную двухстороннюю связь. Именованный канал создается вызовом:

 

NP = CreateNamedPipe("\\\\.\\pipe\\mypipe",

                     PIPE_ACCESS_DUPLEX,

                     PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT,

                     1,

                     0,

                     0,

                     INFINITE,

                     NULL);

 

Машина, создавшая такой канал, становится сервером. Сервер переходит в режим ожидания запросов клиента с помощью вызова:

 

ConnectNamedPipe(NP, NULL);

 

                Поскольку вызов функции ConnectNamedPipe() приводит к ожиданию процесса, то лучше ее вызывать в отдельном потоке.

 

Клиент подключается к каналу с помощью вызова:

 

NP = CreateFile("\\\\pc_name\\pipe\\mypipe",

                GENERIC_READ|GENERIC_WRITE,

                0,

                NULL,

                OPEN_EXISTING,

                FILE_ATTRIBUTE_NORMAL,

                NULL);

 

После того, как соединение между сервером и клиентом будет установлено, они могут обмениваться данными с помощью вызовов ReadFile() и WriteFile(), аналогично предыдущему случаю.

 

Аналогичным образом (CloseHandle()) канал закрывается.

 

Здесь уместно рассказать более подробно об асинхронном вводе-выводе.

 

Основы асинхронного ввода-вывода

 

Существует два варианта ввода-вывода:

 

  1. Синхронный ввод-вывод, при котором поток, вызвавший эту операцию, блокируется до завершения
  2. Асинхронный ввод-вывод, при котором поток вызывает операцию ввода-вывода (ReadFile() или WriteFile()) и сразу же получает управление обратно. Операция ввода-вывода выполняется не зависимо от потока, но когда она завершается, поток узнает об этом с помощью дополнительных средств.

 

Существует три способа асинхронного ввода-вывода.

 

  1. Синхронный ввод-вывод в отдельном потоке. Самый простой и многими рекомендуемый способ. Программа продолжает выполняться в своем потоке, а ожидание завершения ввода-вывода происходит в дополнительном потоке.
  2. Ввод-вывод с перекрытием. В этом случае к операции ввода-вывода “привязывается” событие. Когда операция ввода-вывода начинается, процесс переходит к ожиданию этого события. При этом ожидание может выполняться без блокировки. Когда операция ввода-вывода завершается, событие взводится и сигнализирует процессу о своем завершении.
  3. Ввод-вывод с процедурой завершения. К операции ввода-“привязывается” специальная процедура, которая вызывается операционной системой, когда операция завершается. В начале работы операционной системе надо передать адрес этой процедуры. Внутри процедуры можно взводить, например, некоторый флаг, установка которого говорит о том, что операция завершилась.

 

Про первый вариант говорить не будем, т.к. он использует традиционный вариант вызовов ReadFile() и WriteFile().

 

Про третий вариант поговорим, когда будем рассматривать протокол NetBIOS.

 

На данном этапе рассмотрим второй вариант, при этом для конкретности рассмотрим его применительно к именованным каналам.

 

Первое, что нужно сделать, это создавать объект (в данном случае, именованный канал) с флагом FILE_FLAG_OVERLAPPED.

 

NP = CreateNamedPipe("\\\\.\\pipe\\mypipe",

                     PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,...

 

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

 

OVERLAPPED OverLap;

 

Обычно в качестве последнего параметра передается NULL, что указывает на синхронный ввод-вывод.

 

ReadFile(hPipe, buffer, BUF_SIZE, &NumberOfBytesRead, &OverLap);

 

                Если такая структура передана в вызов, то операция ReadFile() завершается сразу же. Т.е. как бы дается команда “начать ввод-вывод”. За состоянием ввода-вывода можно следить теперь, получая информацию из этой структуры.

 

 

Как выглядит структура перекрытия:

 

typedef struct _OVERLAPPED

    DWORD  Internal;     //резервируется ОС

    DWORD  InternalHigh; //резервируется ОС

    DWORD  Offset;       //используется при вводе-выводе с дисковыми файлами

    DWORD  OffsetHigh;   //используется при вводе-выводе с дисковыми файлами

    HANDLE hEvent;       //ссылка на событие

} OVERLAPPED

 

                Для коммуникационных устройств типа COM-портов и именованных каналов большинство параметров не используются.

                Для нас важно, что в эту структуру входит событие hEvent.

 

                Событие надо создать вызовом:

 

OverLap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

 

HANDLE CreateEvent(

  LPSECURITY_ATTRIBUTES lpEventAttributes, //указатель на атрибуты безопасности

  BOOL bManualReset,  // флаг события ручного сброса

  BOOL bInitialState, // флаг начального состояния

  LPCTSTR lpName      // указатель на имя объекта-события

);

После выполнения операции ReadFile() необходимо перейти к ожиданию события OverLap.hEvent, сигнализирующего о завершении операции ввода-вывода.

 

WaitForSingleObject(OverLap.hEvent, TIME_OUT);

 

DWORD WaitForSingleObject(

  HANDLE hHandle,        // ссылка на объект ожидания

  DWORD dwMilliseconds   // интервал тайм-аута в ms

);

Значение параметра TIME_OUT может варьироваться от 0 до INFINITE. При значении 0 функция сразу же завершится.

 

DWORD result;

while (1) {

       result = WaitForSingleObject(OverLap.hEvent, TIME_OUT);

if (WAIT_TIMEOUT == result){

          // делать что-то

          continue;

}else if (WAIT_OBJECT_0 == result) {

          break;

}

}

Когда операция ввода-вывода завершится, процесс выйдет из цикла по break-у, и тогда можно узнать количество реально переданных данных.

Это делается операцией:

 

DWORD bytesTrans;

GetOverlappedResult(hPipe, &OverLap, &bytesTrans, FALSE);

 

BOOL GetOverlappedResult(

  HANDLE hFile,                       //ссылка на файл, канал или com-устройство

  LPOVERLAPPED lpOverlapped,          //указатель на структуру перекрытия

  LPDWORD lpNumberOfBytesTransferred, //указатель на число пртнятых байтов

  BOOL bWait                          //флаг ожидания

);

Т.е. в буфере buffer находится bytesTrans вновь полученных байтов.

 

Пример потока, выполняющего операцию чтения с перекрытием, представлен ниже:

 

UINT ReadThread(LPVOID pParam)

{

char       buffer[IN_BUF_SIZE];

DWORD      NumberOfBytesRead;

DWORD      bytesTrans;

OVERLAPPED OverLapRd;

 

memset(&OverLapRd,0,sizeof(OVERLAPPED));

OverLapRd.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);

 

while (1) {

memset(buffer,0,IN_BUF_SIZE);

if (!ReadFile(hPipe,buffer,IN_BUF_SIZE,&NumberOfBytesRead,&OverLapRd)) {

if (GetLastError() == ERROR_IO_PENDING) {//операция не завершена

WaitForSingleObject(OverLapRd.hEvent,INFINITE);

}else{

AfxMessageBox("ReadFile error!");

return 0;

}

}

GetOverlappedResult(hPipe,&OverLapRd,&bytesTrans,FALSE);

pReadEdit->SetWindowText(buffer);

}

return 0;

}

 

                Подводя итог, перечислим этапы ввода-вывода с перекрытием:

 

  1. Создать объект с флагом: FILE_FLAG_OVERLAPPED;
  2. Создать структуру перекрытия: OVERLAPPED OverLap;
  3. Создать событие в этой структуре: OverLap.hEvent = CreateEvent(…);
  4. Вызвать функцию ввода-вывода: ReadFile(…, &OverLap);
  5. Перейти к ожиданию события: WaitForSingleObject(OverLap.hEvent);
  6. При наступлении события узнать количество принятых байтов вызовом
    GetOverlappedResult()

 

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

 

Основы безопасности объектов

 

                Еще одна проблема обременяет использование именованных каналов, а именно, проблема безопасности.

                Если создать именованный канал вызовом:

 

CreateNamedPipe(..., NULL);

 

Т.е. если последним параметром этой функции передать NULL, то обмен данными между сервером и клиентом, расположенными на разных машинах, производится не будет.

 

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

 

                Атрибут безопасности содержит три параметра, важнейшим из которых является указатель на дескриптор безопасности. Именно эта структура и содержит права доступа к объекту.

 

                Дескриптор безопасности содержит информацию о следующих компонентах безопасности объекта:

 

  1. Идентификатор безопасности владельца (owner sid)
  2. Идентификатор безопасности группы (group sid)
  3. Собственный список доступа (DACL)
  4. Системный список доступа (SACL)

 

                Списки доступа могут быть проинициализированы вызовом, затем в них могут быть добавлены элементы (sid и права (разрешающие и запрещающие)).

                Затем список может быть добавлен либо к SACL либо к DACL.

 

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

 

Система в этом случае ищет SID, содержащийся в маркере доступа, среди SID списков доступа объекта, и действует соответственно правам, привязанным к данному SID, - разрешает заявленную операцию или отказывает в ней.

 

 

 

                Чтобы использовать дескриптор безопасности объекта, надо выделить под него память, а затем проинициализировать его. Пример вызова представлен ниже:

 

PSECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR,SECURITY_DESCRIPTOR_MIN_LENGTH);

 

InitializeSecurityDescriptor(pSD,SECURITY_DESCRIPTOR_REVISION);

 

Где PSECURITY_DESCRIPTOR    pSD; - указатель на дескриптор безопасности.

 

/************************************************************************************

 

                Затем необходимо найти идентификатор безопасности пользователя, который будет владеть объектом.

                Это делается по имени пользователя следующим образом:

 

LookupAccountName(NULL,buffer,pSid,&cbSid,pDomainName,&cbDomainName,&eUse)

Где    buffer содержит имя пользователя;

       pSid – это указатель на идентификатор беопасности;

       cbSid – размер идентификатора;

       pDomainName – домен, в котором находится имя пользователя;

       cbDomainName – размер строки, описывающей домен;

       eUse – тип идентификатора.

 

                Затем идентификатор безопасности привязывается к дескриптору безопасности. Это делается вызовом:

 

BOOL SetSecurityDescriptorOwner(

  PSECURITY_DESCRIPTOR pSecurityDescriptor, // адрес дескриптора безопасности

  PSID pOwner,                              // адрес SID владельца

  BOOL bOwnerDefaulted                      // флаг умолчания

);

 

                То же самое делается и для группы, владеющей объектом.

 

                Затем создается список контроля доступа:

 

BOOL InitializeAcl(

  PACL pAcl,            // адрес списка контроля доступа

  DWORD nAclLength,     // размер списка контроля доступа

  DWORD dwAclRevision   // уровень редакции списка контроля доступа

);

 

и в него вносятся элементы, либо позволяющие операции определенному пользователю (через его sid, найденный функцией LookupAccountName), либо запрещающие какие-либо операции:

 

BOOL AddAccessAllowedAce(

  PACL pAcl,            // адрес списка контроля доступа

  DWORD dwAceRevision,  // уровень редакции списка контроля доступа

  DWORD AccessMask,     // маска доступа

  PSID pSid             // адрес идентификатора безопасности

);

 

BOOL AddAccessDeniedAce(

  PACL pAcl,            // адрес списка контроля доступа

  DWORD dwAceRevision,  // уровень редакции списка контроля доступа

  DWORD AccessMask,     // маска доступа

  PSID pSid             // адрес идентификатора безопасности

);

 

                На последнем этапе заполненный список заносится в дескриптор безопасности:

 

BOOL SetSecurityDescriptorDacl(

  PSECURITY_DESCRIPTOR pSecurityDescriptor, // адрес дескриптора безопасности

  BOOL bDaclPresent,    // флаг присутствия собственного списка ACL

  PACL pDacl,           // адрес собственного списка ACL

  BOOL bDaclDefaulted   // флаг собственного списка ACL по умолчанию

);

 

********************************************************************************************/

 

                Если указатель на список контроля доступа будет равен NULL, то будет использован список (NULL discretionary ACL), позволяющий полный доступ к объекту:

 

SetSecurityDescriptorDacl(pSD,TRUE,(PACL)NULL,FALSE);

 

                Т.е. этапы, отмеченные /*** ... ***/, могут быть опущены, если нет необходимости регламентировать для различных пользователей доступ к объекту.

 

                Дескриптор безопасности вносится в атрибут безопасности, а указатель на атрибут безопасности передается в качестве параметра в функцию создания объекта.

 

SECURITY_ATTRIBUTES     sa;

 

sa.nLength              = sizeof(sa);

sa.lpSecurityDescriptor = pSD;

sa.bInheritHandle       = TRUE;

 

hPipe = CreateNamedPipe ( ..., &sa);

 

                Важно отметить, что отсутствие атрибута безопасности (NULL вместо &sa) и использование NULL-списка контроля доступа приводят к противоположным результатам.

                В первом случае доступ полностью отсутствует, а во втором случае предоставляется полный доступ.

 

 

 


3. Удаленные вызовы процедур

 

Вспомним опять традиционную модель процедурного программирования. Прикладная программа выглядит как последовательность вызовов процедур, следующим образом:

 

Begin

...

Call1;

Call2;

...

End.

 

Удаленные вызовы процедур – это средство расширения традиционной модели программирования на распределенные системы.

 

Хотя и можно использовать УВП сами по себе, чаще они все-таки являются средством реализации COM и DCOM.

Понятия интерфейса и глобально уникальных идентификаторов являются фундаментальными для RPC, COM и DCOM.

 

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

 

Процедурный интерфейс скрывает все эти действия.

 

Базовым недостатком RPC является синхронность. Т.е. когда клиент обращается с RPC-запросами к процедуре сервера, то он блокируется до момента, пока сервер не выполнит функцию и не возвратит результаты.

Существуют усовершенствованные RPC, обеспечивающие асинхронные вызовы. В них для оповещения клиента о выполнении RPC используются обратные вызовы. Хотя структура синхронной программы существенно проще асинхронной.

Программа, реализующая технологию клиент/сервер с помощью RPC, будет выглядеть следующим образом:

Begin

...

CSInterface(Call1);

CSInterface(Call2);

...

End.

Процедура CSInterface() - интерфейсная процедура, первый аргумент которой - это, обычно, имя серверной процедуры, которую необходимо выполнить. Могут передаваться и дополнительные параметры в процедуру CSInterface().

Структура удаленного вызова процедуры может выглядеть следующим образом:

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

Procedure CSInterface(P : Procedure);

Begin

Case P Of

Call1 : Begin

Создание структур данных для передачи по сети;

Посылка запроса на сервер;

Выполнение процедуры Call1 на сервере;

Подготовка ответа;

Посылка ответа клиенту;

Прием ответа;

End;

Call2 : Begin

...

End;

Else

...

End Case;

End CSInterface;

 

 

Синхронный характер процедурного интерфейса иллюстрируется следующим рисунком:

      Клиент                                        Сервер

-------------------                           -------------------

|                 |                           |                 |

|1. ServerRequest |     -----> 2. ------>     |-----> 3.        |

|  (приостанов    |                           |       .         |

|   выполнения)   |                           |       .         |

|         6.<-----|     <----- 5. <------     |4. RespondClient;|

|         .       |                           |                 |

|         .       |                           |                 |

-------------------                           -------------------

Цифры показывают порядок происходящих событий:

 

  1. Запрос;
  2. Передача запроса;
  3. Начало обработки запроса;
  4. Ответ;
  5. Посылка ответа;
  6. Получение результата клиентом.

 

Реализация без ожидания иллюстрируется следующим образом:

      Клиент                                        Сервер

-------------------                           -------------------

|                 |                           |                 |

|1. ServerRequest |     -----> 2. ------>     |-----> 3.        |

|         .       |                           |       .         |

|         .       |                           |       .         |

|         6.<-----|     <----- 5. <------     |4. RespondClient;|

|         .       |                           |                 |

|         .       |                           |                 |

-------------------                           -------------------

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

Структура программы клиента с процедурой обратного вызова включает в себя следующие элементы:

1) Описание процедуры обратного вызова. Содержание процедуры обратного вызова полностью определяется характером задач уровнем протокола. На сетевом уровне (3) - это может быть простая установка флага, извещающего клиента о завершении операции. На уровне презентации (6), например, в протоколе DDE, - это достаточно сложные действия.

Procedure CallbackProc;

Begin

...

End CallbackProc;

2) Предусматривается передача адреса процедуры CallbackProc обратного вызова интерфейсной процедуре CSInterface. Если в качестве фактического параметра передается NULL, то выполнение процедуры CSInterface происходит синхронно, а если передается не NULL, а адрес CallbackProc, то выполнение происходит асинхронно.

Т.е. по выполнению запроса коммуникационная среда сама вызывает процедуру CallbackProc, и тем самым оповещает клиента о выполнении запроса.

Приведенная технология асинхронного взаимодействия клиента и сервера на основе процедуры обратного вызова широко используется на всех уровнях коммуникаций, и далее мы в этом убедимся.

 

                Создание RPC начинается с описания интерфейса, экспортируемого сервером.

                Интерфейс создается на языке описания интерфейсов IDL (Interface Definition Language). Язык описания интерфейса имеет строго определенные правила. Например:

 

  1. Все функции имеют тип void
  2. Параметры сопровождаются модификаторами [in], [out] (входной, выходной)
  3. Типы данных стандартные (int, long). ANSI

 

Интерфейс содержит глобальный уникальный идентификатор (GUID). С его использованием RPC регистрируется в системе. На основе регистрации идет поиск машины, на которой RPC выполняется, при вызове RPC процессом-клиентом.

GUID создается специальной программой uuidgen.exe.

                Пример результата работы этой программы

 

96fa7b8c-92f7-430b-b552-bdb73188c853

 

                Пример интерфейса:

 

[ uuid (96fa7b8c-92f7-430b-b552-bdb73188c853),

  version (1.0),

  pointer_default (unique) ]

interface AboutRemoteSystem

{

       void get_disk_free_space([in, string, size_is (256)] char* RootPathName,

             [out] long* SecPerClus,

             [out] long* BytesPerSec,

             [out] long* NumFreeClus);

}

 

Текст интерфейса обрабатывается специальным компилятором, в результате чего формируются так называемые суррогаты (stubs) клиента и сервера.

 

                Если, например, файл интерфейса имеет имя syscommands.idl, то компилятор сформирует файлы:

syscommands.h

syscommands_s.c – суррогат сервера

syscommands_c.c – суррогат клиента

 

                Файлы syscommands.h, syscommands_s.c необходимо включить в проект сервера, а файлы syscommands.h, syscommands_c.c необходимо включить в проект клиента.

 

Суррогат клиента выполняет следующие действия:

 

  1. Преобразует параметры в вид, пригодный для передачи по каналу
  2. Устанавливает соединение с сервером
  3. Передает серверу данные
  4. Ждет ответа от сервера
  5. Получает данные
  6. Преобразует данные в вид, пригодный клиенту
  7. Передает данные клиенту

 

Суррогат сервера выполняет следующие действия:

 

  1. Ждет запросы от клиентов
  2. Получив запрос, преобразует принятые данные в вид, пригодный для выполнения процедуры
  3. Выполняет процедуру
  4. Готовит данные-результаты к передаче по каналу
  5. Передает данные по каналу.

 

Внешне программа клиента, вызывающего удаленную процедуру, очень похожа на программу, вызывающую локальную процедуру.

 

                Фрагмент клиента представлен ниже:

 

char Drive[] = "C:\\";

int  SecPerClus, BytesPerSec, NumFreeClus;

get_disk_free_space ((unsigned char __RPC_FAR *)Drive, &SecPerClus, &BytesPerSec, &NumFreeClus);

 

 

 

Программа сервер существенно сложнее реализуется, чем программа-клиент. Этапы инициализации сервера следующие:

 

  1. Регистрирует в библиотеке RPC коммуникационный протокол, который будет поддерживать сервер (TCP/IP или именованные каналы)

 

RpcServerUseProtseqEp(...);

 

  1. Регистрирует в библиотеке RPC интерфейс, который поддерживает сервер

 

RpcServerRegisterIf();

 

  1. Получает набор ссылок, через которые сервер будет взаимодействовать с библиотекой RPC

 

RpcServerInqBindings(&pBindVector)

 

  1. Через полученные ссылки публикует свои возможности в специальной базе данных “RPC name service database”. Клиент запрашивает специальный сервис “RPC name service” при поиске сервера, способного выполнить запрос.

 

RpcNsBindingExport();

 

  1. Переходит в состояние ожидания запросов.

 

RpcServerListen()

 

Непосредственное соединение обеспечивают специальные библиотеки. При этом возможна передача как по протоколам TCP/IP (сокеты), так и через именованные каналы.

 

 


4. Протокол NetBIOS

На сетевом уровне обмен производится без подтверждения с помощью структур данных, называемых датаграммами.

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

К наиболее распространенным протоколам сетевого уровня относятся следующие протоколы:

IPX (Internetwork Packet Exchange) среды Novell. В прошлые годы до 70% сетей реализовали протоколы этой среды. Фактически стандартный протокол.

IP (Internet Protocol) используется в различных вычислительных средах. Очень широко распространенный протокол. История его распространения идет от сетей UNIX.

NetBIOS поддерживается в сетях IBM, Novell, Microsoft. Особенно популярен в сетях IBM.

Непосредственно передачу и прием информации осуществляют сетевые карты, установленные в компьютере.

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

Для вызова драйвера среда предоставляет специальный программный интерфейс в виде прерывания (int 5Ch (MSDOS)) или адреса точки входа в драйвер.

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

В среде Windows используется вызов:

 

UCHAR Netbios(
  PNCB pncb
);

 

Программа не формирует пакеты, которые передаются непосредственно в канал. Эти пакеты формирует коммуникационная среда, а именно, драйвер. Но он формирует их на основе информации, предоставляемой прикладной программой.

Информация драйверу предоставляется в виде структуры данных, называемой, обычно, "управляющий блок".

В управляющий блок входит:

  1. адресная информация;
  2. характер команды;
  3. адрес буферов данных;
  4. результирующие поля.

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

Работа прикладной программы, осуществляющей обмен по сети, содержит следующие типовые этапы:

 

  1. инициализация;
  2. обмен данными;
  3. деинициализация.

Характер 1)-го и 3)-го этапов определяется видом протокола.

Рассмотрим в качестве примера компоненты прикладных программ, обменивающихся данными по протоколу NetBIOS. Общие идеи останутся такими же и при других протоколах.

Основой протокола NetBIOS является структура данных, называемая NCB - Network Control Block. (В протоколе IPX есть структура, служащая тем же целям ECB - Event Control Block.)

Паскалевское описание структуры NCB имеет следующий вид:

Type

TNCB = Record

                   Cmd                  :     Byte; command code

                   CCode                :     Byte; error code

                   LocalSessionNumber   :     Byte; session number

                   NetworkNameNumber    :     Byte; name number

                   Buffer               :     Pointer; data buffer pointer

                   Size                 :     Word; data buffer size

                   CallName             :     Array[0..15] Of Char;

                   OurName              :     Array[0..15] Of Char;self name

                   ReceiveTimeout       :     Byte; receive timeout

                   SendTimeout          :     Byte; send timeout

                   PostRoutine          :     Pointer; post-procedure addr

                   AdapterNumber        :     Byte; adapter number

                   FinalCCode           :     Byte; final error code

                   Reserved             :     Array[0..13] Of Char;reserved

End TNCB;

Cmd                - код команды;

CCode              - код ошибки, выдаваемый до выполнения команды;

LocalSessionNumber - номер канала для транспортного уровня;

NetworkNameNumber  - номер имени станции;

Buffer             - адрес буфера данных;

Size               - размер буфера данных;

CallName           - имя станции назначения;

OurName            - собственное имя станции;

ReceiveTimeout     - интервал ожидания выполнения команды приема;

SendTimeout        - интервал ожидания выполнения команды передачи;

PostRoutine        - адрес процедуры обратного вызова;

AdapterNumber      - номер сетевой карты;

FinalCCode         - код завершения команды;

Reserved           - резерв для внутренних целей протокола.

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

В первом случае, вызвав процедуру драйвера, программа находится в ней до завершения операции.

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

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

Procedure PostProc; Interrupt;

Begin

Flag := 1;

End PostProc;

Инициализация

Инициализация включает в себя проверку наличия интерфейса NetBIOS и добавление имени станции в таблицу локальных имен.

Здесь уместно сказать о принципе адресации в протоколе NetBIOS.

Для адресации используются имена, представляющие собой строки символов размером до 16 байтов.

Для добавления имени надо передать строку с именем в поле OurName NCB и выдать команду "Добавить имя", т.е. вызвать драйвер. Драйвер выполняет команду и возвращает так называемый "номер имени", который в дальнейших операциях используется как собственный адрес.

(В протоколе IPX, например, инициализация заключается в открытии "сокета" - некоего виртуального разъема, через который осуществляется операция обмена данными.)

После добавления локального имени на каждой из станций может быть начат обмен данными.

Обмен данными

Обмен данными осуществляется с помощью команд "передать датаграмму" и "принять датаграмму".

Для передачи данных готовится NCB, в котором указаны:

 

  1. код команды;
  2. адрес и размер буфера с данными;
  3. номер имени;
  4. имя станции назначения;
  5. адрес процедуры обратного вызова.

 

Далее производится вызов драйвера, которому передан адрес этого NCB.

Для приема данных готовится NCB, в котором указаны

 

  1. код команды;
  2. адрес и размер буфера для приема данных;
  3. номер имени;
  4. адрес процедуры обратного вызова.

 

Далее производится вызов драйвера, которому передан адрес этого NCB.

Когда операция приема закончится, в поле CallName будет записано имя станции, от которой получена датаграмма.

Деинициализация

Деинициализация заключается в удалении имени из таблицы локальных имен станции.

Для этого готовится NCB, в котором указаны:

 

  1. код команды;
  2. удаляемое имя;
  3. адрес процедуры обратного вызова.

Далее производится вызов драйвера, которому передан адрес этого NCB. (В протоколе IPX выполняется закрытие сокета.)

Если программа, выполняемая на передающей станции, выдаст команду "передать датаграмму" раньше, чем программа, выполняемая на приемной станции, выдаст команду "принять датаграмму", то переданная датаграмма будет потеряна. Более того, приемная программа "зависнет" в ожидании приема.

Передать датаграмму

ПРД   ---|----------------------

Датаграмма будет потеряна

Принять датаграмму

ПРМ   --------|-----------------

Передать датаграмму

ПРД   --------------|-----------

Датаграмма будет принята

Принять датаграмму

ПРМ   ---|----------------------

Т.е. здесь встают те же вопросы синхронизации, что и в теории процессов, рассмотренной ранее.

Как синхронизировать выполнение двух программ, выполняющихся на различных компьютерах?

Выдавать команду передачи, если на приемной стороне не выдана команда приема бессмысленно.

Решение состоит в том, что программа должна иметь архитектуру без ожидания и всегда находиться в состоянии приема.

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

Flag := 0;

AddName;

While Flag = 0 Do ;  добавление имени завершено

Flag := 0;

ReceiveDatagram;

While True Do Begin

If Flag = 1 Then Begin прием завершен

Flag := 0;

< Анализ того, что принято;>

ReceiveDatagram; снова начать прием

End If;

< Другие действия по алгоритму приложения;>

If KeyPressed Then Begin пример передачи и

Сh := ReadKey; завершения работы

Case Ch Of

's' : SendDatagram;

#27 : Break;

Else

End Case;

End If;

End While;

Flag := 0;

DeleteName;

While Flag = 0 Do ;  удаление имени завершено

Программой-сервером принято называть программу, которая грузится первой из всех других программ, а затем находится в режиме ожидания приема так, как это изображено выше.

Обмен данными на транспортном уровне гарантирует прием пакетов, причем в той же очередности, что и передача пакетов.

Допускается многократная передача пакета до получения квитанции о правильном приеме.

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

Примерами протоколов транспортного уровня являются следующие: SPX (Sequenced  Packet  Protocol) среды Novell – надуровень протокола IPX;

TCP (Transmission Control Protocol) - надуровень протокола IP;

NetBIOS - ранее описанный протокол может быть использован и на транспортном уровне.

Работа на транспортном уровне требует к инициализации, выполняемой на сетевом уровне, добавить процедуры создания канала, а к деинициализации добавить процедуры разрушения канала.

Например, по протоколу NetBIOS инициализация должна включать следующие действия:

 

  1. проверка наличия NetBIOS;
  2. добавление имени к таблице локальных имен станции;
  3. создание канала.

 

Для создания канала программа-сервер готовит NCB и вызывает функцию "Listen", выполнение которой завершается, когда программа-клиент после проверки наличия NetBIOS и добавления имени вызовет функцию "Call".

(Аналогичные функции существуют и в протоколе SPX.)

Завершаясь, функции Listen и Call, каждая на своей стороне, возвращают в NCB в поле LocalSessionNumber номер канала, присвоенный коммуникационной средой. Этот номер используется при последующих операциях обмена данными.

Деинициализации, описанной на сетевом уровне, предшествует закрытие канала на транспортном уровне.

Для этого готовится NCB, в котором указан логический номер закрываемого канала, и вызывается функция "HangUp".

После закрытия канала удаляется имя станции и программа завершает свою работу.

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

Но существуют некоторые отличия, связанные с синхронизацией.

При создании канала устанавливаются времена таймаута, по истечению которых завершаются операции приема и передачи данных.

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

Но если в течение таймаута операция Send не смогла передать блок, потому что на другой стороне не вызвана операция Receive, то эта операция Send заканчивается с такой ошибкой, что после этого дальнейшая передача становится невозможной, т.к. нарушается протокол канала (очередность доставки блоков).

Хотя вызов операции Send программой одной станции может быть теперь произведен чуть раньше, чем вызов операции Receive программой другой станции.

Структура программы, осуществляющей обмен по транспортному каналу, может выглядеть следующим образом:

Flag := 0;

AddName;

While Flag = 0 Do ;  добавление имени завершено

Flag := 0;

Listen; для сервера, загружаемого первым

Call;   для клиента, загружаемого вторым

While Flag = 0 Do ;  создание канала завершено

Flag := 0;

Receive;

While True Do Begin

If Flag = 1 Then Begin прием завершен

Flag := 0;

< Анализ того, что принято; >

Receive; снова начать прием

End If;

 

< Другие действия по алгоритму приложения; >

 

If KeyPressed Then Begin пример передачи и

Сh := ReadKey; завершения работы

Case Ch Of

's' : Send;

#27 : Break;

Else

End Case;

End If;

 

End While;

Flag := 0;

HangUp;

While Flag = 0 Do ;  закрытие канала завершено

Flag := 0;

DeleteName;

While Flag = 0 Do ;  удаление имени завершено

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

Тем не менее, мы рассмотрели вопросы коммуникаций по протоколу NETBIOS, сделав акцент на проблеме синхронизации программ.

5.4.5.2. Программные средства работы с сокетами

                Сокеты являются фактическим стандартом взаимодействия систем через существующие сети TCP/IP. Они позволяют соединяться машинам, на которых выполняются разные операционные системы.

 

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

 

                Рассмотрим основные этапы создания  соединения, обмена данными и разъединения в приложениях сервера и клиента.

 

Сервер. Создание соединения.

 

                Сокет создается вызовом:

 

SrvLstSock = socket(AF_INET, SOCK_STREAM, 0);

 

Где:

AF_INET – тип адреса;

SOCK_STREAM – тип транспорта, поток или датаграммы

                После создания сокета надо заполнить специальную структуру данных sockaddr, в которой указывается тип адреса, номер порта и IP-адрес.

 

struct sockaddr_in SrvLstAddr;

 

SrvLstAddr.sin_family      = AF_INET;

SrvLstAddr.sin_port        = htons(LocalPort);

SrvLstAddr.sin_addr.s_addr = htonl(INADDR_ANY);

 

                Далее, созданный сокет привязывают к описанной структуре данных:

 

bind(SrvLstSock,(struct sockaddr*)&SrvLstAddr,sizeof(SrvLstAddr)));

 

                Следующим этапом является перевод сокета в состояние прослушивания:

 

listen(SrvLstSock, 1);

 

Функции передается число возможных подключений, в данном случае 1.

 

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

 

SrvrSock = accept(SrvLstSock,(struct sockaddr*)&SrvrAddr,&SrvrAddrLen);

 

Функция accept() приостанавливает выполнение программы (синхронная), поэтому ее лучше выполнить в отдельном потоке.

 

 

Клиент. Создание соединения.

 

                Клиент создает сокет аналогичным вызовом:

 

ClntSock = socket(AF_INET,SOCK_STREAM,0);

 

                Затем заполняет структуру данных, указывая адрес и порт удаленного хоста:

 

struct sockaddr_in ClntSAddr;

 

ClntSAddr.sin_family      = AF_INET;

ClntSAddr.sin_port        = htons(RemotePort);

ClntSAddr.sin_addr.s_addr = inet_addr(RemoteHost);

 

                После этого клиент вызывает функцию соединения с удаленным хостом, которую лучше выполнять в цикле и в отдельном потоке:

 

while (SOCKET_ERROR == connect(ClntSock,(struct sockaddr*)&ClntSAddr,sizeof(ClntSAddr))){

}

 

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

 

Обмен данными.

 

                Обмен данными производится с помощью вызовов:

 

send(ClntSock, trnbuf, strlen(trnbuf), 0));//клиент посылает данные

 

recv(SrvrSock, recbuf, strlen(recbuf), 0);//сервер принимает данные

 

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

 

Закрытие соединения.

 

                Для закрытия соединения надо вызвать функции:

 

shutdown(SrvrSock,2);//2 - запрет передачи и приема

closesocket(SrvrSock);//закрытие сокета

closesocket(SrvLstSock);//закрытие сокета

 

Выводы

 

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

 

//-----------------------------------------------------------------------------------------------------------------------------------------

 

 

 

4.3.6. Порты завершения ввода-вывода

 

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

 

                Если сервер создает всего один поток, то он будет обслуживать клиентов по очереди, и клиенту будут долго ждать обслуживания.

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

 

                Порты завершения ввода-вывода призваны компенсировать недостатки этих двух крайних вариантов.

 

                Общая идея заключается в следующем.

                Один активный поток обслуживает клиента.

Если этот поток блокируется по каким-то причинам, например, для чтения/записи файла, то активизируется другой поток (если есть ждущие клиенты).

                Если поток завершил обслуживание клиента, то он без переключения контекста переходит к обслуживанию следующего клиента, если таковые имеются.

 

                Приложение использует объект IoCompletion, который представляется в Win32 как порт завершения.

                Если какой-нибудь файл сопоставлен с портом завершения, то по окончании любой операции ввода-вывода, связанной с этим файлом, в очередь порта завершения ставится пакет завершения.

 

                Создавая порт завершения, приложение указывает максимальное число потоков, сопоставленных с портом. Если число активных потоков равно заданному максимальному значению, то выполнение потока, ждущего на порте, запрещено.

                По завершении обработки текущего запроса активный поток проверяет, имеется ли в очереди порта другой пакет. Если да, то он переходит к его обработке без переключения контекста.

 

Работа порта завершения

 

                Высокоуровневая схема работы порта завершения представлена на рисунке.

                Порт завершения создается вызовом CreateIoCompletionPort(). Здесь порт по сути дела является синонимом очереди.

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

               

                Потоки, блокированные на порте завершения, пробуждаются по принципу LIFO (последний пришел – первый вышел). Т.е. следующий пакет достанется потоку, заблокированному последним.

                Стеки потоков, блокированных длительное время, могут быть выгружены в страничный файл. Так что система минимизирует объем памяти, занимаемой долго блокированными потоками.

 

                Серверные потоки ждут входящие запросы, вызывая для этого порта функцию GetQueuedCompletionStatus().

 

                Получив пакет из порта завершения, поток начинает обработку запроса и становится активным. В процессе обработки данных поток может блокироваться (например, из-за работы с файлами или из-за синхронизации с другими потоками). Win2000 обнаруживает, что одним активным потоком на порте завершения стало меньше. Тогда она пробуждает другой, ждущий на порте завершения, поток (если в очереди есть пакет).

 

                Рекомендуется устанавливать максимальное число активных потоков на порте завершения примерно равным числу процессоров.

 

 

5.4.6. Сетевое программное обеспечение уровня приложений

                В качестве примеров рассмотрим два варианта приложений, основных на протоколах TCP/IP.

                Первый вариант – это протокол передачи файлов FTP – самый распространенных протокол передачи файлов через Интернет.

                Второй вариант – это протокол передачи электронной почты SMTP.

5.4.6.1. Протокол FTP

                В соответствии с технологией клиент-сервер для получения и отсылки файлов на удаленном компьютере должна быть установлена программа FTP-сервер.

                Если такая программа установлена, то с помощью FTP-клиента можно к ней подсоединиться и передавая команды, определенные FTP-протоколом, получать нужные результаты.

 

                Для написания клиентских программ существует API, обеспечивающий программиста функциями, соответствующими командам, отсылаемым на FTP-сервер.

                Примеры API-вызовов мы сейчас и рассмотрим.

 

                Для создания FTP-соединения используется структура данных FTPINFO, которая заполняется при инициализации соединения, а потом используется во всех API-вызовах на протяжении всего сеанса связи.

 

                Установление соединения начинается вызовом:

 

ftp_prconnect(&ftpinfo,ip_address);

 

                Если соединение установлено, то клиент должен передать серверу имя пользователя и пароль. Эти действия выполняются вызовами:

 

ftp_user(&ftpinfo,user_name);

ftp_passwd(&ftpinfo,password);

 

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

 

ftp_ascii(&ftpinfo);

ftp_dir(&ftpinfo,dir,filename);

 

                Затем переходят в двоичный режим приема файлов:

 

ftp_binary(&ftpinfo);

 

                Получение и передача файла обеспечиваются вызовами:

 

ftp_getfile(&ftpinfo,remfile,locfile);

ftp_putfile(&ftpinfo,locfile,remfile);

 

                Закрытие FTP-соединения производится вызовом:

 

ftp_bye(&ftpinfo);

 

                Если рассмотреть структуру данных FTPINFO, то можно увидеть в ней такие элементы данных, как сокеты.

5.4.6.2. Протокол SMTP

     Основная задача протокола SMTP (Simple Mail Transfer Protocol) заключается в том, чтобы обеспечивать передачу электронных сообщений (почту). Для работы через протокол SMTP клиент создаёт TCP соединение с сервером через порт 25. Затем клиент и SMTP сервер обмениваются информацией, пока соединение не будет закрыто или прервано. Основной процедурой в SMTP является передача почты (Mail Procedure). Далее идут процедуры форвардинга почты (Mail Forwarding), проверка имён почтового ящика и вывод списков почтовых групп. Самой первой процедурой является открытие канала передачи, а последней - его закрытие.

Команды SMTP указывают серверу, какую операцию хочет произвести клиент. Команды состоят из ключевых слов, за которыми следует один или более параметров. Ключевое слово состоит из 4-х символов и отделено от аргумента одним или несколькими пробелами. Каждая командная строка заканчивается символами CR LF.

Подсоединившись к SMTP-серверу с помощью какой-нибудь терминальной программы, например, Telnet или HyperTerminal, можно подавать команды с клавиатуры. А можно написать программу с использованием сокетов и, таким образом, обеспечить программную отправку почтовых сообщений.

Вот синтаксис всех команд протокола SMTP (SP - пробел):

HELO <SP> <domain> <CRLF>
MAIL <SP> FROM:<reverse-path> <CRLF> 
RCPT <SP> TO:<forward-path> <CRLF> 
DATA <CRLF>
RSET <CRLF> 
SEND <SP> FROM:<reverse-path> <CRLF> 
SOML <SP> FROM:<reverse-path> <CRLF> 
SAML <SP> FROM:<reverse-path> <CRLF> 
VRFY <SP> <string> <CRLF> 
EXPN <SP> <string> <CRLF> 
HELP <SP> <string> <CRLF> 
NOOP <CRLF> 
QUIT <CRLF>

Обычный ответ SMTP сервера состоит из кода ответа, за которым через пробел следует дополнительный текст. Код ответа служит индикатором состояния сервера..

Отправка почты

Первым делом подключаемся к SMTP серверу через порт 25. Теперь надо передать серверу команду HELO и наш IP адрес:

C: HELO 195.161.101.33
S: 250 smtp.mail.ru is ready 

При отправке почты передаём некоторые нужные данные (отправитель, получатель и само письмо):

C: MAIL FROM:<svv@mail.ru> 'указываем отправителя
S: 250 Sender OK
 
C: RCPT TO:<svv@yandex.ru> 'указываем получателя
S: 250 Recipient OK (will queue)

указываем серверу, что будем передавать содержание письма (заголовок и тело письма)

C: DATA 
S: 354 Enter mail; end with <CRLF>.<CRLF> 

передачу письма необходимо завершить символами CRLF.CRLF

C: From: Drozd <drozd@mail.ru> 
C: To: Drol <drol@mail.ru>
C: Subject: Hello 

между заголовком письма и его текстом не одна пара CRLF, а две.

C: Hello, Drol!

заканчиваем передачу символами CRLF.CRLF

S: 250 Message accepted for delivery

Теперь завершаем работу, отправляем команду QUIT:

C: QUIT
S: 221 smtp.mail.ru is closing transmission channel

 

Другие команды

1.     SEND - используется вместо команды MAIL и указывает, что почта должна быть доставлена на терминал пользователя.

2.     SOML, SAML - комбинации команд SEND или MAIL, SEND и MAIL соответственно.

3.     RSET - указывает серверу прервать выполнение текущего процесса. Все сохранённые данные (отправитель, получатель и др.) удаляются. Сервер должен отправить положительный ответ.

4.     VRFY - просит сервер проверить, является ли переданный аргумент именем пользователя. В случае успеха сервер возвращает полное имя пользователя.

5.     EXPN - просит сервер подтвердить, что переданный аргумент - это список почтовой группы, и если так, то сервер выводит членов этой группы.

6.     HELP - запрашивает у сервера полезную помощь о переданной в качестве аргумента команде.

7.     NOOP - на вызов этой команды сервер должен положительно ответить. NOOP ничего не делает и никак не влияет на указанные до этого данные.

 

Набор команд и тексты ответов слегка различаются в серверах разных производителей.

 

 


Заключение

                В данном разделе было рассмотрено сетевое программное обеспечение операционных систем.

 

  1. Была дана общая характеристика коммуникаций.
  2. Затем была рассмотрена концепция технологии клиент-сервер
  3. Затем были рассмотрены внутренние коммуникации, к средствам которых были отнесены

1.              Неименованные каналы (для потоков)

2.              Сообщения

3.              DDE-технология

4.              OLE-технология

  1. Следующим этапом было рассмотрение внешних коммуникаций на базе протокола TCP/IP. Здесь были рассмотрены:

1.              Общая характеристика протоколов TCP/IP

·                Определение и достоинства

·                Архитектура

·                Соответствие модели OSI

2.              Протокол IP

·                Структура заголовка

·                IP-адресация

3.              Протокол UDP

4.              Протокол TCP

·                Структура TCP-сегмента

·                Этапы TCP-сеанса

5.              Программные средства поддержки сетей

·                Состав программных средств

                                                                                                     1.             Почтовые ящики

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

                                                                                                     3.             Удаленные вызовы процедур

                                                                                                     4.             Протокол NetBIOS

·                Программные средства поддержки сокетов

6.              Программное обеспечение уровня приложений

·                Протокол FTP

·                Протокол SMTP

 

 

 

 

 

 

 

 

Используются технологии uCoz