ЛАБОРАТОРНАЯ РАБОТА № 6. Использование технологий OLE, СОМ и ActiveX



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

Лабораторная работа рассчитана на 4 академических часа.

Теоретическая часть. От OLE к ActiveX

В данной лабораторной работе рассматривается использование технологии OLE (Object Linking and Embedding — связывание и внедрение объектов), которую можно определить как объектно-ориентированный протокол совместного доступа к данным и программному коду из разных процессов. OLE позволяет программистам создавать приложения для работы с составными документами, представляющими собой динамические связанные структуры, отдельные части которых могут разрабатываться в различных программах.

ActiveX и OLE фирмы Microsoft — еще один шаг к более совершенным, т. е. более надежным и эффективным, программам.

В основе ActiveX и OLE лежит очень простая идея, но, как оказалось, она позволяет существенно повысить эффективность программирования.

Первоначально OLE была задумана как технология интеграции программных продуктов, входящих в комплект Microsoft Office. Предшественницей OLE является реализованная в Windows технология динамического обмена данными DDE (Dynamic Data Exchange), до сих пор широко применяемая в данной среде. Однако многие разработчики не без оснований считают, что DDE трудно использовать, поскольку это технология низкого уровня.

В качестве технологии более высокого уровня была реализована OLE 1.0 OLE 1. Она расширила возможности протокола DDE и, используя его как базовый механизм коммуникаций, позволила активизировать встроенный объект в документе, т. е. получить составной документ. Таким образом, OLE 1.0 унаследовала многие проблемы асинхронного протокола. Эта технология имела множество недостатков, а ее компоновка была слишком сложна для пользователей среднего уровня. Кроме того, установленные связи легко нарушались, например, в результате изменения маршрута доступа к файлу связанного объекта.

С помощью OLE 1 пользователь мог, например, объединить электронную таблицу, созданную Microsoft Excel, с текстовым документом «производства» Microsoft Word. Идея состояла в том, чтобы документно-ориентированная (document-centric) модель работы с компьютером позволила бы пользователю больше думать об информации и меньше о приложениях, ее обрабатывающих. Как следует из слов «связывание и внедрение», составные документы можно создать, либо связав два разных документа, либо полностью внедрив один документ в другой.

OLE 1, как и большинство первых версий программных продуктов, была несовершенна. Архитекторам следующей версии предстояло улучшить первоначальный проект. Вскоре они поняли, что составные документы лишь частный случай более общей проблемы: как разные программные компоненты должны предоставлять друг другу сервисы? Для решения этой проблемы архитекторы OLE создали группу технологий, область применения которых гораздо шире составных документов. Основу OLE 2 составляет важнейшая из этих технологий Модель многокомпонентных объектов (Component Object Model — СОМ). Новая версия OLE не только обеспечивает поддержку составных документов лучше, чем первая, но и, несомненно, идет куда дальше простого объединения документов, созданных в разных приложениях. OLE 2 позволяет по-новому взглянуть на взаимодействие любых типов программ.

В начале 1996 г. Microsoft ввела в оборот новый термин — ActiveX. Сначала он относился к технологиям, связанным с Интернетом, и приложениям, выросшим из него, вроде WWW (World Wide Web). Поскольку большинство разработок Microsoft в данной области было основано на СОМ, то и ActiveX была непосредственно связана с OLE. Однако очень скоро новый термин стал захватывать территории, традиционно принадлежавшие OLE, и вот теперь все вернулось на круги своя: OLE, как встарь, обозначает только технологию создания составных документов связыванием и внедрением, а разнообразные технологии на основе СОМ, ранее объединенные под именем OLE, собраны под знаменем ActiveX. А некоторые технологии, название которых содержало слово «OLE», даже перекрестили — теперь это технологии ActiveX.

Понятие СОМ

Все технологии OLE и ActiveX, описанные ниже, построены на основании, обеспеченном СОМ. Итак, что же такое СОМ? Чтобы ответить на этот вопрос, зададимся сначала другим: «Каким образом одна часть программного обеспечения должна получать доступ к сервисам, предоставляемым другой частью?» На сегодняшний день ответ зависит от того, что представляют собой эти части.

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

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

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

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

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

В СОМ любая часть программного обеспечения реализует свои сервисы как один или несколько объектов СОМ (не следует путать объекты СОМ с объектами в языках программирования типа С++; несмотря на то что у них есть общие черты, это разные понятия; далее будет описано соотношение объектов СОМ и объектов других видов.). Каждый такой объект поддерживает один или несколько интерфейсов, состоящих из методов. Метод — это функция или процедура, которая выполняет некоторое действие и может быть вызвана программным обеспечением, использующим данный объект (клиентом объекта). Методы, составляющие каждый из интерфейсов, обычно определенным образом взаимосвязаны. Клиенты могут получить доступ к сервисам объекта СОМ только через вызовы методов интерфейсов объекта, у них нет непосредственного доступа к данным объекта.

Большинство объектов СОМ поддерживают более одного интерфейса. Сам объект всегда реализуется внутри некоторого сервера. Сервер может быть либо динамически подключаемой библиотекой (ОБЬ), подгружаемой во время работы приложения, либо отдельным самостоятельным процессом.

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

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

Любой СОМ-объект — это экземпляр определенного класса. Объекты одного класса могут, например, реализовывать сервисы корректировки орфографии и словаря синонимов, тогда как объекты другого класса — представлять банковские счета. Обычно знать класс объекта необходимо для запуска экземпляра этого объекта, выполняемого с помощью библиотеки СОМ. Эта библиотека присутствует на любой системе, поддерживающей СОМ, и имеет доступ к справочнику всех доступных на данной машине классов COM-объектов. Клиент может, например, вызвать функцию библиотеки СОМ, передав ей класс нужного ему СОМ-объ-екта и задав один из поддерживаемых объектом интерфейсов, указатель которого нужен клиенту в первую очередь. (Эти сервисы реализованы библиотекой СОМ в виде обычных вызовов функций, а не через методы интерфейса СОМ.) Затем библиотека СОМ запускает сервер, реализующий объекты данного класса. Кроме того, библиотека возвращает клиенту указатель требуемого интерфейса вновь созданного экземпляра объекта. Далее клиент может запросить указатели на другие необходимые ему интерфейсы непосредственно у самого объекта.

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

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

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

Разработку OLE-контейнеров и серверов проще всего вести в среде \%иа1С++ при помощи специальных мастеров (например, Арр^агс1), а также библиотеки МЕС.

Порядок выполнения работы

o 1. Создать приложение Cnt, для этого:

o • чтобы приступить к созданию нового проекта, выберите в окне компилятора MicrosoftVisualC++ в меню File команду New;

o • в окне New выберите элемент MFC AppWizard(exe), в результате этого будет запущен мастер приложений, работа с которым осуществляется в шесть этапов:

o — в первом окне установите опцию Singledocument;

o — во втором окне не задавайте поддержку баз данных;

o — в третьем окне установите опцию Container, указывающую на то, что приложение будет OLE-контейнером. В этом же окне следует включить поддержку элементов управления ActiveX;

o — в следующем, четвертом окне необходимо оставить все опции, заданные по умолчанию;

o — в пятом окне установите опцию MFC Standard, опцию включения комментариев в программу и опцию статической компоновки библиотеки MFC;

o — наконец, в шестом окне просмотрите список классов, которые будут созданы автоматически, и щелкните по кнопке Finish;

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

o • осталось только построить исполняемый файл приложения, выбрав для этого в меню Build команду Rebuild All. В результате в папку DEBUG будет добавлен файл СМТ.ЕХЕ.

o 2. Проанализировать программный код.

Приложение включает пять основных исходных файлов, сгенерированных мастером AppWizard: CNT.CPP, MAINFRM.CPP, CNTDOC.CPP, CNTVIEW.CPP и CNTRITEM.CPP. Листинги файлов приведены в приложении 5.

o 3. Внести, где это требуется, изменения.

o 4. Отладить программы.

o 5. Проверить работоспособность контейнера, для этого: • запустите приложение Сщ (рис. Л6.1);

Рис. Л6.1. Окно приложения Cnt

o • выберите в меню Edit команду InsertNewObject..., в результате чего откроется стандартное диалоговое окно вставки объекта;

o • в этом окне выделите элемент, соответствующий электронной таблице Excel (рис. Л6.2).

Рис. Л6.2. Выбор внедряемого объекта

6. Оценить результат (рис. Л6.3).

Рис. Л6.3. Внедрение электронной таблицы Excel в приложение Cnt

o 7. Создать приложение по выбору преподавателя.

o 8. Сдать и защитить работу.

Защита отчета по лабораторной работе

Отчет по лабораторной работе должен состоять из:

o 1. Листингов программ.

o 2. Результатов работы.

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

Контрольные вопросы

o 1. Охарактеризуйте технологии 01_Е, СОМ и АсНуеХ.

o 2. В чем заключаются преимущества и недостатки OLE?

o 3. В чем заключаются преимущества и недостатки СОМ?

o 4. Опишите развитие от OLE до АсНуеХ.

o 5. Как связаны У1$иа1С++ и 01_Е?

ЛАБОРАТОРНАЯ РАБОТА № 7. Создание сетевых приложений на Delphi с использованием Windows Sockets API

Цель работы: изучить метод разработки сетевых приложений в среде Delphi на низком уровне с использованием АРI. Лабораторная работа рассчитана на 4 академических часа.

Теоретическая часть. Сетевые приложения

Для поддержки сетевых приложений существует технология, названная «сокеты». Сокет — это модель одного конца соединения со всеми присущими ему свойствами и методами. По сути, это прикладной программный интерфейс, входящий в состав многих операционных систем (ОС) и призванный для поддержки сетевых возможностей ОС. В стандарте структуры протоколов семиуровневой модели OSI-сокеты лежат на так называемом транспортном уровне, ниже находится сетевой протокол ip, а выше — протоколы сеансового уровня, такие как ftp, рорЗ, smtp и т. д.

В Windows поддержка сокетов включена, начиная с версии 3.11, и названа winsock. Для написания приложений с сетевой поддержкой существует специальный winsock api.

Все сетевые приложения построены на технологии «клиент — сервер»; это значит, что в сети существует, по крайней мере, одно приложение, являющееся сервером, типичная задача которого — это ожидание запроса на подключение от приложений-клиентов, которых может быть теоретически сколько угодно, и выполнение всевозможных процедур в ответ на запросы клиентов. Для клиент-серверной технологии абсолютно неважно, где расположены клиент и сервер — на одной машине или на разных. Конечно, для успешного соединения клиента с сервером клиенту необходимо иметь минимальный набор данных о расположении сервера — для сетей tcp/ip это ір-адрес компьютера, где расположен сервер, и адрес порта, на котором сервер ожидает запросы от клиентов.

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

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

Механизм работы сокетов таков: на серверной стороне запускается серверный сокет, который после запуска сразу переходит в режим «прослушивания» (т. е. ожидания соединения клиентов). На стороне клиента создается сокет, для которого указывается ір-адрес и порт сервера и дается команда на соединение. Когда сервер получает запрос на соединение, ОС создает новый экземпляр сокета, с помощью которого сервер может обмениваться данными с клиентом. При этом сокет, который создан для «прослушивания», продолжает находиться в режиме приема соединений, таким образом, программист может создать сервер, работающий с несколькими подключениями от клиентов.

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

Рассмотрим минимальный набор функций из winsock api, необходимых для написания элементарного клиента и сервера. Сами функции находятся в файле winsock.dll. Файл winsock.pas содержит необходимые объявления импортируемых функций winsock api и базовые структуры данных. К сожалению, этот файл импортирует не все необходимые нам функции, и позже мы напишем свой файл импорта.

function wsastartup(wversionrequired: word; var wsdata:

twsadata): integer; stdcall;

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

function wsacleanup: integer; stdcall;

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

function socket(af, struct, protocol: integer):

tsocket; stdcall;

Функция создает сокет. Порт и адрес задается в функции bind (сервер) или connect (клиент). Входящий параметр af — спецификация семейства сокетов (af_inet, af_ipx и др.), struct — спецификация типа нового сокета (принимает значение sock stream или sock_dgram), protocol — специфический протокол, который будет использоваться сокетом. Если функция выполнена без ошибок, она возвращает дескриптор на новый сокет, если ошибки есть, возвращается invalid socket.

function connect(s: tsocket; var name: tsockaddr;

namelen: integer): integer; stdcall;

Функция соединения для клиента. Структура адреса содержит порт (необходимо привести функцией htons) и адрес (для клиента необходимо привести из имени или спецификации ip4 — ххх.ххх.ххх.ххх).

function bind(s: tsocket; var addr: tsockaddr; namelen: integer): integer; stdcall;

Функция ассоциирует адрес с сокетом. Структура адреса содержит порт (необходимо привести функцией htons) и адрес (для сервера обычно указывается inaddr any — любой).

function send(s: tsocket; var buf; len, flags: integer): integer; stdcall;

Функция отправки данных. Помещает в очередь сокета s кусок данных из buf длиной len. Последний параметр отвечает за вид передачи сообщения. Может быть проигнорирован (0).

function recv(s: tsocket; var buf; len, flags: integer): integer; stdcall;

Функция получения данных.

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

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

program winsock_server ;

//Простейшее приложение-сервер.

//Сокеты работают в блокирующем режиме.

//На каждое соединение создается отдельный поток.

{$apptype console} uses

sysutils, winsock, windows; var

vwsadata : twsadata; vlistensocket,vsocket : tsocket; vsockaddr : tsockaddr;

trid : thandle; const

cport = word(33); csigexit = 'q';

//Процедура отдельного потока для каждого клиента.

procedure socketthread;

var sockname : tsockaddr;

abuf : array of char;

vbuf : string;

vsize : integer;

s :tsocket;

bufsize : integer;

begin

s := vsocket;

if s = invalid_socket then exit; vsize := sizeof(tsockaddr); getpeername(s, sockname, vsize);

writeln(format('client accepted, remote address [%s].', [inet_ntoa (sockname.sin_addr)]));

//Определяем размер буфера чтения для сокета, vsize := sizeof(bufsize);

getsockopt(s,sol_socket,so_rcvbuf,pchar(@ bufsize),vsize);

writeln(format('receive buffer size [%d]',[bufsize]));

setlength(abuf,bufsize);

repeat

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

vsize := recv(s,abuf[0],bufsize,0); if vsize<=0 then break; setlength(vbuf,vsize); lstrcpyn(@vbuf[1],@abuf[0],vsize);

writeln(format('received from cleint: %s',[vbuf])); until vbuf = 'q';

writeln(format('client disconnected, remote address

[%s].',[inet_ntoa(sockname.sin_addr)]));

setlength(abuf,0);

closesocket(s);

end;

begin

• • •

writeln('starting application...');

//Объявляем, что программа будет использовать //windows sockets.

if wsastartup($101,vwsadata)<>0 then halt(l); writeln('using windows sockets.');

//Создаем прослушивающий сокет, vlistensocket :=

socket(af_inet,sock_stream,ipproto_ip); writeln(format('creating socket on port [%d].',[cport]));

if vlistensocket = invalid_socket then halt(l); fillchar(vsockaddr,sizeof(tsockaddr) , 0) ; vsockaddr.sin_family := af_inet; vsockaddr.sin_port := htons(cport); vsockaddr.sin_addr.s_addr := inaddr_any; writeln('binding socket...');

//Привязываем адрес и порт к сокету.

if bind(vlistensocket,vsockaddr,sizeof(tsockaddr)) <> 0 then halt(1);

//Начинаем прослушивать.

if listen(vlistensocket,somaxconn) <> 0

then halt(1);

writeln('socket status: listening.'); repeat

//Ожидаем подключения.

vsocket := accept(vlistensocket,nil,nil);

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

createthread(nil,0,@socketthread,0,0,trid); until false;

closesocket(vlistensocket) ;

wsacleanup;

end

В тексте программы использована не описанная ранее функция getpeername(), которая возвращает информацию о канале, ассоциированном с сокетом. В данном контексте она нужна для получения информации о ip-адресе подключившегося клиента. Подробно об этой функции, как и о всех других функциях winsock, можно прочитать в windows sockets 2 application program interface, входящем в состав Win32 programmer's reference.

Приведенный выше код можно использовать только как учебное пособие. Для того чтобы использовать его в качестве основы для настоящего приложения, необходимо некоторое количество доработок, так как многие вещи, которые могут привести в будущем к серьезным ошибкам, были сознательно опущены для уменьшения размера кода и акцентирования внимания именно на аспектах использования winsock. Если вы недостаточно хорошо знакомы с понятием потоков (threads) в windows, то вам лучше использовать класс tthread, существующий в delphi специально для поддержки многопоточных приложений. Исходный код клиента представлен ниже:

program winsock_client;

{$apptype console} uses

sysutils,

winsock;

const

cport = 33; csigexit = 'q'; var

vwsadata : twsadata; vsocket : tsocket; vsockaddr : tsockaddr; buf : string; begin

if wsastartup($101,vwsadata)<>0 then halt(l); vsocket := socket(af_inet,sock_stream,ipproto_ip); if vsocket = invalid_socket then halt(l); fillchar(vsockaddr,sizeof(tsockaddr),0); vsockaddr.sin_family := af_inet; vsockaddr.sin_port := htons(cport);

vsockaddr.sin_addr.s_addr := inet_addr('127.0.0.1'); if connect(vsocket,vsockaddr,sizeof(tsockaddr)) = socket_error then halt(l);

repeat readln(buf);

if send(vsocket,buf[1],length(buf),0) = socket_error

then break;

until buf = csigexit;

closesocket(vsocket);

wsacleanup;

end

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

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

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

function select(nfds: integer; readfds, writefds,

exceptfds: pfdset; timeout: ptimeval): longint;

stdcall;

Эта функция позволяет контролировать состояние набора сокетов.

Аргумент nfds игнорируется и оставлен только для совместимости. Должен быть равен 0. readfs, writefds, exceptfds — указатели на наборы сокетов, для которых нужно контролировать состояние чтения, отправки данных и ошибок соответственно. Наборы хранятся в структуре pfdset, управление которой осуществляется специальными макросами, описанными в winsock.pas:

procedure fs_zero(var fdset: tfdset);

Обнуляет структуру, устанавливает количество контролируемых сокетов в 0.

procedure fd_set(socket: tsocket; var fdset: tfdset);

Добавляет указанный сокет в структуру.

procedure fd_clr(socket: tsocket; var fdset: tfdset);

Удаляет указанный сокет из структуры.

function fd_isset(socket: tsocket; var fdset: tfdset):

boolean

Возвращает true, если указанный сокет является членом указанной структуры.

Аргумент timeout является ссылкой на структуру типа ptimeval, в которой можно указать время ожидания срабатывания функции select. В случае указания в качестве значения времени задержки 0 или nil в качестве аргумента timeout функция select будет ждать бесконечно, как при выполнении операции в блокирующем режиме.

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

Давайте рассмотрим логику работы во втором случае, так как первый случай банален.

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

arg := 1;

ioctlsocket(socket,fionbio,arg);

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

arg := 0;

ioctlsocket(socket, fionbio, arg);

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

Введем новые переменные wfds :tfdset; i : integer; tv : ttimeval. Затем определим основной цикл, в котором будем обрабатывать данные:

repeat

until connum>0;

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

fd_zero(wfds);

for i:=l to connum do

begin

fd_set(sock[i],wfds); end;

Далее, указываем в структуре tv время задержки для функции

select:

tv.tv_sec := 5; tv.tv_usec := 0;

Теперь можно вызывать функцию select (так как мы следим только за приемом данных, то в качестве writefds, exceptfds мы указываем nil):

select(0,@wfds,nil,nil,@tv);

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

if wfds.fd_count=0 then continue; for i:=0 to wfds.fd_count-l do begin

vsocket := wfds.fd_array[i];

//Обработка поступивших данных с сокета vsocket. end;

Условие выхода из основного цикла — отсутствие открытых сокетов в массиве sockarray. Условия закрытия сокета мы оставляем на совести читателя, добавим только лишь, что сокет попадет в обработку select и при наступлении события разрыва связи (для обработки можно использовать то, что количество принятых байтов функцией recv будет равно нулю, а также функцию wsagetlasterror).

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

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

Помимо функции select существует еще два метода работы с асинхронными сокетами:

function wsaasyncselect(s: tsocket; hwindow: hwnd;

wmsg: u_int; levent: longint): integer; stdcall;

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

const

wm mysocket = wm user + 1;

type

tforml = class(tform)

private

procedure socket_proc(var msg:tmessage)/message wm_mysocket;

• • •

wsaasyncselect(vsocket,forml.handle,wm_ mysocket,fd accept+fd read);

procedure tforml.socket_proc(var msg: tmessage); begin

if ((msg.msg = wm_mysocket) and (msg.lparam = fd_accept)) then showmessage('connected'); end;

Рассмотрим еще один способ, который пригодится, если у вас нет окна приложения; этот способ основан на системных событиях (events). К сожалению, файл winsock.pas не импортирует соответствующие функции, в результате этого многие программисты пренебрегают возможностями событий. Напишем собственный импорт необходимых процедур:

function wsaeventselect(s: tsocket; event: thandle;

levent: longint):integer;stdcall;

external 'ws2_32.dll' name 'wsaeventselect';

function wsawaitformultipleevents(ncount: dword;

lphandles: pwohandlearray;

bwaitall: bool; dwmilliseconds: dword;

falertable:bool):integer;stdcall;

external 'ws2_32.dll' name 'wsawaitformultipleevents'; function wsacreateevent:thandle;stdcall; external 'ws2_32.dll' name 'wsacreateevent'; function wsaresetevent(event : thandle):bool;stdcall; external 'ws2_32.dll' name 'wsaresetevent'; function wsaenumnetworkevents(const s : tsocket; const event : thandle; lpnetworkevents : lpwsanetworkevents): longint ; stdcall;far;

external 'ws2_32.dll' name 'wsaenumnetworkevents'; function wsacloseevent(event : thandle):integer; stdcall; external 'ws2_32.dll' name 'wsacloseevent';

Также нам потребуется описание структуры wsanetworkevents.

const

fd_max_events = 10; type

twsanetworkevents = record lnetworkevents: longint;

ierrorcode: array[0..fd_max_events-1] of integer; end;

pwsanetworkevents = Pwsanetworkevents;

lpwsanetworkevents = pwsanetworkevents;

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

Затем, в цикле обработки мы организуем ожидание поступления события от сокета; это реализуется с помощью api функций waitforsingleobject — для ожидания одного события либо waitformultipleobjects — для ожидания набора событий. При наступлении события функция возвращает управление. Для однозначной идентификации, от какого сокета пришло уведомление, используется функция wsaenumnetworkevents, возвращающая структуру типа twsanetworkevents.

var

fevent : thandle;

//Создаем серверный сокет

feventclose := wsacreateevent;

wsaeventselect(socket,fevent, fd_close + fd_read ); repeat

waitforsingleobject(fevent,infinite);

wsaenumnetworkevents(fsocket,fevent,@ni) ;

case ni.lnetworkevents of

fd_close:break;

fd_read: begin

receivedata;

end;

end;

wsaresetevent(feventclose); until false;

wsacloseevent(feventclose);

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

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

send(vsocket,@buf1,length (buf1),0);

send(vsocket,@buf2,length(buf2),0);

фактически будет идентичен одному вызову send с объединенным буфером bufl+buf2.

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

При чтении из сокета данных мы можем наблюдать как бы «склейку» порций данных либо, наоборот, фрагментацию (не путать с фрагментацией пакетов на уровне tcp/ip). Такие ситуации должна обрабатывать наша программа. Решить проблему можно добавлением сигнатуры признака конца блока данных. Это имеет смысл, если приложения часто обмениваются небольшими блоками данных, где чаще всего возникает эффект «склеивания», но неэффективны при больших объемах, так как сканирование большого буфера на предмет сигнатуры отнимает много времени. Обычно это решается таким способом — в начале каждого пакета добавляется 32-битовое число, определяющее длину порции данных в байтах. Таким образом, принимающая часть, зная размер каждого блока, может распознать «склейку» и фрагментацию.

Об остальных аспектах сетевого программирования с использованием библиотеки winsock вы можете узнать из справки «windows sdk» в разделе «windows sockets 2 application program interface».

Порядок выполнения работы

· 1. Написать сетевое приложение с использованием Vinsock АРI в соответствии с заданным преподавателем вариантом. При этом один компьютер — сервер, другой — клиент.

· 2. Отладить программу.

· 3. Произвести обмен данными с соседним компьютером.

· 4. Изменить направление «клиент — сервер».

· 5. Еще раз обменяться данными.

· 6. Закончить работу с сокетами.

· 7. Сдать и защитить работу.

Защита отчета по лабораторной работе

Отчет по лабораторной работе должен состоять из:

· 1. Листингов программ.

· 2. Результатов работы.

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

Контрольные вопросы

· 1. Какое существует сетевое программное обеспечение?

· 2. Охарактеризуйте архитектуру «клиент — сервер».

· 3. Приведите понятие сокета.

· 4. Приведите понятие порта.

· 5. Какие существуют функции для работы с сокетами?

· 6. Охарактеризуйте синхронные и асинхронные операции ввода-вывода.

Варианты заданий

· 1. На базе примера написать чат. Программа должна передавать самой себе по VinSocket сообщения в обычном и кодированном виде. Использовать код Цезаря. Суть кода: все буквы сдвинуты на три позиции, то есть: «а» шифруется буквой «г», «б»—«д» и так далее, «э»—«а», «ю»—«б», «я»—«в». Аналогично сдвигается английский алфавит.

· 2. На базе примера написать чат. Программа должна передавать самой себе по WinSocket сообщения с присоединенными к ним файлами (бинарными в общем случае).

· 3. Написать интернет-игру. Программа должна передавать самой себе по WinSocket координаты точки. Эта точка должна или рисоваться, или должны выводиться ее координаты, либо указывать на ячейку таблицы Excel, либо отображаться каким-либо иным способом.

· 4. Написать программу, которая следит за использованием соединения и подсчитывает статистику передачи сообщений между двумя пользователями. Программа должна уметь работать на отдельном компьютере. В качестве программ пользователей использовать пример или программы из 1 —3 вариантов.

· 5. Написать распределенную базу данных. Одна программа посылает запросы на получение данных и на сохранение изменений в этих данных. Другая программа работает с таблицей Excel, читает из нее запрашиваемые данные или записывает данные в таблицу.

Таблицу можно не отображать на экране.

6. Написать «защищенную сеть». По нажатию кнопки в диспетчерской программе все указанные соединения должны закрываться. В диспетчерской программе должен быть список открытых соединений. В качестве пользовательских программ использовать пример или программы из 1—3 вариантов.


Дата добавления: 2020-04-08; просмотров: 389; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!