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

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

Сервер на основе сокетов

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

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

Схема работы архитектуры «Клиент - сервер»

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

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

2. Процесс открытия сокета. Для начала сессии «клиент - сервер» важно открыть сокет и порт. Используется свойство Listen.

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

Применение TServerSocket в приложениях на языке Delphi «Клиент - сервер»

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

Количество подключенных в текущий момент пользователей;

Число активных процессов;

Число свободных процессов;

Порт, хост-имя и локальный IP-адрес;

Разблокировка и блокировка сокета.

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

Документация

На сегодняшний день литературы по разработке приложений «клиент - сервер» Delphi, связанной с разработкой и применением сокетов, представлено очень мало. Поэтому для детального изучения данного аспекта можно обратиться к документации по Linux и Unix-системам, т.к. в ней достаточно подробно рассматривается технология развертывания приложений с применением сокетов, однако, как правило, на языках Perl или С++.

Для разработки приложения «Телефонный справочник» используется среда визуального программирования Delphi 7. Проект программы будет содержать три окна:

  • 1. fMain - главная форма программы.
  • 2. fEditor - форма добавления/редактирования записей.
  • 3. fDM - модуль данных.

На форму fDM добавим компонент ADOConnection, 1 компонент типа ADOTable, 1 компонент типа DataSource. Щелкните дважды по свойству ConnectionString компонента ADOConnection. Откроется окно подключения компонента к ADO (Рис.2.):

Puc. 2. Окно подключения к ADO.

Нажмем кнопку Build. Открывается новое окно (Рис.3.), содержащее настройки подключения:

Рис. 3.

Выбрать Поставщик данных - Microsoft OLE DB Provider for SQL Server. На вкладке Подключение: выбрать имя сервера, на котором расположена ваша БД. Теперь свойство Connected переведем в True. У ADOTable изменим следующие свойства: Connection на ADOConnection1; TableName на Abonent; Active на True. У DataSource изменить следующие свойства (это будет ссылка на таблицу): DataSet на ADOTable.

Перейдем на главную форму. Выберем команду File -> Use Unit и подключите к ней модуль DM. Теперь мы сможем видеть таблицы из главной формы.

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

Рис. 4.

На главную форму fMain добавим три кнопки Button (Рис.5). Они предназначены для редактирования текущей записи, добавления новой и удаления.

Рис.5.

На вкладке DataControls сосредоточены визуальные (видимые пользователю) компоненты отображения данных, такие как DBGrid (сетка, отображающая все данные в виде таблицы, и позволяющая редактировать их), DBEdit (поле редактирования данных, предназначенная для ввода или редактирования одного поля записи, то есть, ячейки таблицы), DBMemo (для редактирования MEMO-полей) и т.д. Единственным исключением является компонент DBNavigator. Этот компонент предназначен не для отображения данных, а для перемещения по записям набора данных, для вставки новой записи или удаления старой. Добавим и выделим сетку DBGrid, в ее свойстве DataSource выберите fDM.ADOTable. Сетка среагирована, и мы видим названия полей (Рис.6.).

Рис. 6.

Теперь создадим фильтр записей по различным критериям. Для этого добавим на форму два компонента GroupBox. В инспекторе объекта (Рис.7.) в свойстве Caption компонента GroupBox1 введем «Фильтр по одному полю», а у компонента GroupBox2 «Фильтр по нескольким полям».

Рис.7.

На GroupBox1 добавим 3 метки Label, 1 ComboBox, 1 Edit, и 2 RadioButton. У Label1 в свойстве Caption напишем «Поиск в», у Label2 «Введите данные», у Label3 «Способ». В свойстве Items компонента ComboBox введем две строки: фамилия, телефон (Рис.8.).

Рис.8.

У RadioButton свойство Caption «С начала строки» и «Любое вхождение». На второй GroupBox добавим три метки Label со свойством Caption: Фамилия, Имя, Телефон. Три компонента Edit и одну кнопку Button со свойством Caption «Найти». И добавим на форму еще одну кнопку Button, для снятия фильтра.

Теперь сделаем окно редактора данных. Создадим новую форму (File -> New -> Form). Ее свойство Name переименуем в fEditor. Командой File -> Use Unit подключим к форме модуль данных DM. Теперь нам нужно установить на форму такие компоненты: 8 Label, 7 DBEdit, 1 DBComboBox.

Доработаем компонент DBComboBox. Щелкним дважды по его свойству Items, открыв редактор. В нем введем три строки:

Улица

Переулок

Проспект

Сохраним текст, нажав кнопку ОК.

Подключим компоненты контроля. Удерживая , выделим все компоненты контроля (все компоненты, кроме Label). В их свойстве DataSource выберем fDM.DSLichData, подключив компоненты к нужному набору данных (таблице). Снимем общее выделение, и выделим первый DBEdit. В его свойстве DataField выберем поле "Фамилия". Это свойство подключает выбранный компонент к определенному полю таблицы. Таким же образом подключим к соответствующим полям остальные компоненты.

В правой нижней части установим навигационный компонент DBNavigator с вкладки Data Controls. Этот компонент предназначен для перемещения по записям, включения режима редактирования записи, сохранения или отмены сделанных изменений, добавления новой записи или удаления существующей. В его свойстве DataSource выбираем fDM.DSLichData, чтобы подключить компонент к главной таблице. Нам нужна от этого компонента только возможность перехода на начало или конец таблицы, на следующую или предыдущую запись. Поэтому раскроем его свойство VisibleButtons (видимость кнопок компонента) и переведем в False все кнопки, кроме nbFirst, nbPrior, nbNext и nbLast. Нажатие на эти кнопки приведет к вызову соответствующих методов компонента ADOTable. Эти методы делают следующее:

First - переход на первую запись таблицы. Prior - переход на предыдущую запись. Next - переход на следующую запись. Last - переход на последнюю запись.

Когда у DBNavigator останется всего четыре кнопки, эти кнопки окажутся вытянутыми. Уменьшим ширину компонента, чтобы кнопки приняли более привычный вид.

fDM.TLichData.Append;

DBEdit1.SetFocus;

Методом Append добавляем в таблицу новую запись. Далее мы переводит фокус ввода на DBEdit1, чтобы пользователю не пришлось делать это самому.

if fDM.TLichData.Modified then

fDM.TLichData.Post;

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

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

fDM.TLichData.Append;

fEditor.ShowModal;

Сгенерируем процедуру OnClick для кнопки "Редактировать". Тут будет лишь одна строчка кода:

fEditor.ShowModal;

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

Код для кнопки «Удалить»:

fDM.TLichData.Delete;

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

Сгенерируем событие OnChange у компонента Edit2.

if ((Length(Edit2.Text) > 0)and(Length(ComboBox1.Text) > 0)) then

fDM.TLichData.Filtered:=false;

if RadioButton1.Checked then

fDM.TLichData.Filter:=Combobox1.Text + " LIKE "+ #39 + Edit2.Text + "%" + #39

fDM.TLichData.Filter:=Combobox1.Text + " LIKE "+ #39+ "%" + Edit2.Text + "%" + #39;

fDM.TLichData.Filtered:=true;

else fDM.TLichData.Filtered:=false;

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

Ключевое слово LIKE позволяет по заданному шаблону сравнивать строки. При этом нужно знать следующее:

символ "%" (процент) - заменяет любую последовательность из символов.

Знак #39 - означает номер символа " (одинарная кавычка) в кодовой таблице ASCII. Дело в том, что значение на фильтрацию нужно указывать в одинарных кавычках, а так как одинарные кавычки используются в Delphi для ограничения строк, то чтобы внутри строки поставить одинарную кавычку, её нужно поставить дважды.

Фильтр по нескольким полям. Сгенерируем событие OnClick у кнопки Найти.

procedure TfMain.Button3Click(Sender: TObject);

fDM.TLichData.filtered:=false;

if length(edit4.text) > 0 then

filtr:= "ФАМИЛИЯ LIKE "+ #39 + Edit4.Text + "%" + #39;

if length(edit5.text) > 0 then

if length(filtr) >

filtr:=filtr + add + "ИМЯ LIKE "+ #39 + Edit5.Text + "%" + #39;

if length(edit6.text) > 0 then

if length(filtr) > 0 then add:= " and " else add:="";

filtr:=filtr + add + "телефон LIKE "+ #39 + Edit6.Text + "%" + #39;

if length(filtr) > 0 then

fDM.TLichData.Filter:= filtr;

fDM.TLichData.filtered:=true;

Showmessage("Все поля пусты!");

Для отключения фильтра нажимаем на кнопку «Снять фильтр». Код у нее такой:

fDM.TLichData.Filter:= "";

fDM.TLichData.Filtered:= false;

Итак создадим новую БД, назовем ее Test и сохраним ее в папке C:\ClientServer\Server\Data (процесс создания новой БД в Аксесе я, по понятным, причинам опускаю, и приведу только структуру таблиц - входящих в нашу базу).

1. Таблица первая, назовем ее First

2. Таблица вторая, а назовем ее уже по хитрому - Second

Ну вот, как бы, с базой и все.

Программа

2.1. Приложение сервер.

Созданим новое приложение и сохраним его под именем Server в папке C:\ClientServer\Server. Добавим в приложение Remote Data Module с вкладки Multitier репозитария (Рис. 1).

Рис. 1 Репозитарий с выделенным Remote Data Module.

При добавлении RDM мастер добавления запросит параметры данного модуля - там введем имя нашего сервера "Test".


Все остальное оставим без изменений. После нажатия кнопки "Ок" в проекте появится форма подобная обычному дата модулю с именем Test. Сохраним ее под именем RDMFrm.pas .

Выложим на нее компоненты ADOConnection (одна штука), ADOTable и DataSetProvider (по две штуки). Компоненты ADOTable и DataSetProvider обзовем - adotFirst, adotSecond, dspFirst и dspSecond соответственно. По двойному клику на компоненте ADOConnection вывалится мастер Connection String. Выбераем пункт "Use Connection String" и нажимаем кнопочку Build. В появившемся окне "Свойства связи с данными" выберем пункт "Microsoft Jet 4.0 OLE DB Provider" и нажмем кнопку "Далее". На владке "Подключение" введем путь к БД, нажмем кнопочку "Проверить подключение" и получим сообщение что проверка подключения выполнена. После чего закроем мастер путем нажатия на кнопке ОК. Еще у компонента ADOConnection свойство есть LoginPrompt, которое установим в False и Connected - True. У компонетов ADOTable свойство Connection установим равным ADOConnection путем выбора из выпадающего списка. Свойство TableName установим в First и Second соответсвенно. Свойство CursorType - ctDynamic, свойство TableDirect - True. Вызвать FieldsEditor и добавить туда все поля.

У компонентов DataSetProvider:

Вот на этом процесс создания примитивного MIDAS сервера можно считать завершенным. Чтобы зарегистрировать сервер в подсистеме DCOM нужно запустить приложение с параметром /regserver - Server.exe /regserver.
И последний штрих, это приложение СокетСервер от Борланда, которое находится в папке Delphi?/Bin/scktsvr.exe. Для того чтобы клиент мог видеть сервер нужно запустить scktsvr.exe (если у вас установлена Win NT/w2k/XP то можно это приложение зарегистрировать как службу, для этого его нужно запустить с параметром scktsvr.exe /install)

2.2. Приложение клиент

Создадим новое приложение, и сохраним его в папке C:\ClientServer\Client под именем Client. Разместим на главной форме по два компонента ClientDataSet(обзовем их cdsFirst, cdsSecond), DataSource(обзовем их dsFirst, dsSecond), DBGrid(обзовем их dbgFirst, dbgSecond) один компонент SocetConnection.

SocetConnection

Если вы все сделали правильно, то при установке свойства Connected в True, должно запустится приложение сервер.

ClientDataSet

Запустим Редактор Полей (двойной клик по компоненту) и занесем туда все поля. Для компонентов ClientDataSet напишем обработчик sdsAfterPost

Procedure TMainForm.cdsAfterPost(DataSet: TDataSet);
begin
with (DataSet as TClientDataSet) do begin
if State in then
try
ApplyUpdates(0);
Refresh;
except
raise EDatabaseError.Create("Ошибочка вышла");
end;
end;
end;

И подставим его на обработку события AfterPost обоих ClientDataSet"ов.

DataSource

Свойство Значение
DataSet cdsFirst и cdsSecond соответственно

DBGrid

Свойство Значение
DataSource dsFirst и dsSecond соответственно

Запустим Редактор колонок (двойной клик по компоненту) и занесем туда все поля.
Положим на форме возле Гридов по кнопке и в обработчик onClick запишем:

  • для dbgFirst - cdsFirst.Post
  • dbgSecond - cdsSecond.Post

Все. Закрываем Делфи (для чистоты эксперимента). И запускаем приложение клиент. Если вы все сделали правильно то вы должны увидеть нечто подобное этому


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

Итак, сначала о компоненте сервера IdTCPServer (закладка Indy Servers ). Для использования возможностей сервера этот компонент нужно поместить на форму (компонент неотображаемыи). При настройке компонента полезными являются следующие его свойства:

  • Active - активизирует или деактивизирует сервер (по умолчанию False );
  • Bindings - настраивает серверные сокеты (присоединяет их к определенно му порту компьютера, позволяет задавать диапазон IP-адресов и портов клиентов при помощи диалогового окна настройки свойства Binding ;
  • ListenQueue - численное значение, ограничивающее максимальное количество запросов на установление соединения от клиентов в очереди;
  • MaxConnections - позволяет ограничить максимальное количество клиентов, присоединенных к серверу;

Рассмотрим несколько подробнее настройку серверных гнезд с использованием свойства Bindings . Так, на рис. 1 показано, как при помощи диалогового окна свойства Binding настроить сервер на обслуживание клиентов с любыми IP-адресами, при этом серверный сокет присоединяется к порту 12340.

Рис. 1. Настройка свойства Binding .

На этом настройку сервера можно и завершить (хотя здесь используются далеко не все возможности компонента IdTCPServer ). Основная же работа сервера при обработке запросов клиентов может реализоваться в обработчике события OnExecute . В этот обработчик передается ссылка на объект TIdContext - поток, ассоциированный с клиентом, присоединенным к серверу. Посредством этого объекта (а точнее, его свойства Connection ) можно получать и отправлять данные, а также получать и устанавливать множество полезных параметров соединения. Первый пример использования объекта TIdContext при обработке запроса клиента приведен в листинге 1.

Теперь рассмотрим, как сконфигурировать клиент (IdTCPClient - закладка Indy Clients ), чтобы он был способен взаимодействовать с нашим сервером. Чтобы использовать компонент ТСР-клиента, достаточно поместить его на форму (компонент также неотображаемый).

После этого как минимум нужно настроить следующие его свойства (остальные упоминаются по мере необходимости в приведенных далее примерах):

  • Host - имя или IP-адрес компьютера, на котором запущен сервер;
  • Port - номер порта, к которому присоединен серверный сокет.

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

Простой обмен данными

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

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

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

Рис. 2. Внешний вид простого сервера

Текстовое поле (Edit ) с количеством обработанных запросов имеет имя txtCount , а текстовое поле с адресом последнего обслуженного компьютера названо txtFrom . Вся работа сервера заключается в обработке события Execute для компонента IdTCPServer , помещенного на форму (присоедините этот компонент к порту 12340 и установите значение свойства Active = True ) (листинг 1).

Листинг 1. Реализация простого сервера

Procedure TForm1.FormCreate(Sender: TObject); begin section:= TCriticalSection.Create; end; procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); var strText: String; begin //Принимаем от клиента строку strText:= AContext.Connection.Socket.ReadLn; //Отвечаем AContext.Connection.Socket.WriteLn("Took the line: " + strText); //Обновим сведения на форме сервера (сервер многопоточный, //поэтому используем синхронизацию section.Enter; Inc(processed, 1); txtCount.Text:= IntToStr(processed); txtFrom.Text:= AContext.Connection.Socket.Binding.PeerIP; section.Leave; //Закрываем соединение с пользователем AContext.Connection.Disconnect; end;

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

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

Теперь рассмотрим реализацию клиентской части (проект SimpleClient ). Внешний вид клиентского приложения приведен на рис. 2.

Рис. 2. Внешний вид клиента

Естественно, что для работы клиентского приложения на форму помещен экземпляр компонента IdTCPClient (его имя - IdTCPClient1 ). Свойству Port этого компонента нужно присвоить значение 12340. Текстовое поле (Edit ) для ввода строки, подлежащей отправке не сервер, имеет имя txtMessage . Текстовое поле (Edit ), в которое вводится имя или адрес сервера, названо txtServer . Поле со строками ответов (Memo ) имеет имя txtResults .

Вся работа клиентского приложения выполняется при нажатии кнопки Обработать . Текст соответствующего обработчика приведен в листинге 2 .

Листинг 2. Реализация простого клиента

Procedure TForm1.Button1Click(Sender: TObject); begin //Соединяемся с сервером и посылаем ему введенную команду IdTCPClient1.Host:= txtServer.Text; IdTCPClient1.Connect; IdTCPClient1.Socket.WriteLn(txtMessage.Text); txtMessage.Text:= ""; //Ожидаем ответ и закрываем соединение txtResults.Lines.Append(IdTCPClient1.Socket.ReadLn); IdTCPClient1.Disconnect; end;

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

Исходный код . Выполнен на Delphi XE.

Слежение за компьютером по сети (IdTCPServer, IdTCPClient)

Теперь рассмотрим более интересный пример использования сетевых компонентов IdTCPServer и IdTCPCLient , который может быть полезен для людей, имеющих отношение к администрированию компьютеров сети.

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

  • разрешение монитора;
  • глубину цвета для монитора;
  • полноразмерную копию экрана;
  • копию экрана, уменьшенную (или увеличенную) до заданных размеров.

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

  • get_screen_width - для получения ширины и get_screen_height -для получения высоты экрана в пикселах;
  • get_screen_colors - для получения значения установленной для монитора глубины цвета (бит на точку);
  • get_screen - для получения полноразмерной копии экрана;
  • get_screen: X, Y - для получения копии экрана, приведенной к размеру Х х Y .

Сначала рассмотрим реализацию сервера (проект SpyServer ). Весь код, обеспечивающий работу сервера, помещен в модуле Unit1.pas формы Form1 . Обработчик запросов клиентов - главная процедура для сервера - приводится в листинге 3.

Листинг 3. Обработчик клиентских запросов

Procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread); var strText: String; width, height, i: Integer; dc: HDC; begin //принимаем от клиента строку strText:= AThread.Connection.ReadLn; //определяем, что нужно выполнить if strText = "get_screen_height" then //возвратим высоту экрана AThread.Connection.WriteInteger(Screen.Height) else if strText = "get_screen_width" then //возвратим ширину экрана AThread.Connection.WriteInteger(Screen.Width) else if strText = "get_screen_colors" then begin //возвратим количество бит на точку dc:= GetDC(0); AThread.Connection.WriteInteger(GetDeviceCaps(dc, BITSPIXEL)); ReleaseDC(0, dc) end else if strText = "get_screen" then //возвратим полноразмерную копию экрана SendScreen(Screen.Width, Screen.Height, AThread.Connection) else begin //строка вида "get_screen:x, y" //Определим значени высоты и ширины переданные пользователем strText:= Copy(strText, 12, Length(strText) - 11); i:= Pos(",", strText); //положение запятой width:= StrToInt(Copy(strText, 1, i - 1)); height:= StrToInt(Copy(strText, i+1, Length(strText) - i)); //возвратим копию экрана SendScreen(width, height, AThread.Connection); end; end;

Используемая в листинге 3 процедура SendScreen, отправляющая клиенту копию экрана, приведена в листинге 4.

Листинг 4. Снятие копии экрана

//процедура снимает копию экрана, приводит полученное //изображение к заданному размеру и отправляет //преобразованное изображение клиентской программе procedure SendScreen(width1: Integer; height1: Integer; Connection: TIdTCPServerConnection); var ScreenCopy: TCanvas; gr: TBitmap; stream: TMemoryStream; rcDest, rcSource: TRect; begin rcDest:= Rect(0,0,width1,height1); //конечный размер изображения rcSource:= Screen.DesktopRect; //исходный размер изображения //создаем канву и присоединяем ее к контексту Рабочего стола ScreenCopy:= TCanvas.Create; ScreenCopy.Handle:= GetDC(0); //создаем объект для хранения копии экрана и копируем изображение gr:= TBitmap.Create; gr.Height:= height1; gr.Width:= width1; gr.Canvas.CopyRect(rcDest, ScreenCopy, rcSource); ReleaseDC(0, ScreenCopy.Handle); //сохраняем изображение в поток данных stream:= TMemoryStream.Create; gr.SaveToStream(stream); //отправляем изображение клиенту Connection.WriteStream(stream, true, true); stream.Clear; stream.Free; gr.Free; end;

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

Компонент IdTCPServer (с именем IdTCPServer1 ) в этом примере присоединен к порту 12341 (не забудьте также установить свойство Active = True ).

Теперь о реализации клиентского приложения (проект SpyClient ). Внешний вид формы (Form1 ) клиента во время работы приводится на рис. 3 (видно, что пользователь наблюдаемого компьютера только что проиграл в игру Сапер).

Рис. 3

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

Таблица 1. Основные компоненты формы клиента слежения и их свойства

Работа клиентского приложения начинается с соединения с сервером. Код, отвечающий за эту опреацию, приведен в листинге 5.

Листинг 5.Соединение с сервером

Procedure TForm1.cmbConnectClick(Sender: TObject); begin if cmbConnect.Caption = "Подключиться" then begin if txtServer.Text = "" then //не введено имя сервера MessageDlg("Введите имя машины-сервера в текстовом поле", mtInformation, , 0) else begin //подключаемся к серверу IdTCPClient1.Host:= txtServer.Text; try IdTCPClient1.Connect; except MessageDlg("Не удается соединиться с указанным сервером", mtError, , 0); Exit; end; end end else //отключаемся от сервера IdTCPClient1.Disconnect; end;

Если соединение с сервером произошло успешно, то выполняется обработчик TForm1.IdTCPClient1Connected , подготавливающий приложение-клиент к периодическим запросам данных с сервера (листинг 6).

Листинг 6. Действия выполняемые при соединении с сервером

Procedure TForm1.IdTCPClient1Connected(Sender: TObject); begin txtServer.Enabled:= False; cmbConnect.Caption:= "Отключиться"; //начинаем периодически запрашивать данные с сервера Timer1.Enabled:= True; //выполним первый запрос, не дожидаясь срабатыввания таймера Timer1Timer(nil); end;

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

Листинг 7. Действия при отсоединении от сервера

Procedure TForm1.IdTCPClient1Disconnected(Sender: TObject); begin txtServer.Enabled:= True; cmbConnect.Caption:= "Подключиться"; Timer1.Enabled:= False; end;

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

Procedure TForm1.Timer1Timer(Sender: TObject); var stream: TMemoryStream; begin //запрашаваем у сервера данные о наблюдаемом компьютере with (IdTCPClient1) do begin //...разрешение WriteLn("get_screen_width"); WriteLn("get_screen_height"); lblResolution.Caption:= IntToStr(ReadInteger) + "x" + IntToStr(ReadInteger); //...глубина цвета WriteLn("get_screen_colors"); lblColors.Caption:= IntToStr(ReadInteger); //...копия экрана //.....1-й вариант - копирование экрана без сжатия //WriteLn("get_screen"); //.....2-й вариант - сжатие на стороне сервера WriteLn("get_screen:" + IntToStr(imgScreen.Width) + "," + IntToStr(imgScreen.Height)); //....получаем данные stream:= TMemoryStream.Create; ReadStream(stream); stream.Position:= 0; //....формируем изображение imgScreen.Picture.Bitmap.LoadFromStream(stream); stream.Clear; stream.Free; end; end;

В тексте листинга 8 создано большое количество комментариев, поэтому дополнительно пояснять его нет смысла. Остановимся лишь на том, зачем в процедуре TForm1.Timer1Timer предусмотрено два варианта получения изображения с сервера.

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

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

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

Исходный код . Выполнен на Delphi 7.

Раз приложение простейшее, то я решил использовать БД MS Jet 4.0 (проще говоря БД MS Access). Это решение вызвано тем что Jet (прошу не путать с MS Access) является бесплатным продуктом и поставляется вместе с MS Windows (то бишь для работы нашей программы ненужно устанавливать на компьютер клиента сам Access). Да и редактор БД очень неплохой, и ядро поддерживает кучу типов полей для удовлетворения самых извращенных желаний при хранении данных.

Итак создадим новую БД, назовем ее Test и сохраним ее в папке C:\ClientServer\Server\Data (процесс создания новой БД в Аксесе я, по понятным, причинам опускаю, и приведу только структуру таблиц - входящих в нашу базу).

1. Таблица первая, назовем ее First (ну не литератор я!)

2. Таблица вторая, а назовем ее уже по хитрому - Second

Ну вот, как бы, с базой и все.

2. Программа
2.1. Приложение сервер.

Созданим новое приложение и сохраним его под именем Server в папке C:\ClientServer\Server. Добавим в приложение Remote Data Module с вкладки Multitier репозитария (Рис. 1).

При добавлении RDM мастер добавления запросит параметры данного модуля - там введем имя нашего сервера "Test".

Все остальное оставим без изменений. После нажатия кнопки "Ок" в проекте появится форма подобная обычному дата модулю с именем Test. Сохраним ее под именем RDMFrm.pas .

Выложим на нее компоненты ADOConnection (одна штука), ADOTable и DataSetProvider (по две штуки). Компоненты ADOTable и DataSetProvider обзовем - adotFirst, adotSecond, dspFirst и dspSecond соответственно. По двойному клику на компоненте ADOConnection вывалится мастер Connection String. Выбераем пункт "Use Connection String" и нажимаем кнопочку Build. В появившемся окне "Свойства связи с данными" выберем пункт "Microsoft Jet 4.0 OLE DB Provider" и нажмем кнопку "Далее". На владке "Подключение" введем путь к БД, нажмем кнопочку "Проверить подключение" и получим сообщение что проверка подключения выполнена. После чего закроем мастер путем нажатия на кнопке ОК. Еще у компонента ADOConnection свойство есть LoginPrompt, которое установим в False и Connected - True. У компонетов ADOTable свойство Connection установим равным ADOConnection путем выбора из выпадающего списка. Свойство TableName установим в First и Second соответсвенно. Свойство CursorType - ctDynamic, свойство TableDirect - True. Вызвать FieldsEditor и добавить туда все поля.

У компонентов DataSetProvider:

Вот на этом процесс создания примитивного MIDAS сервера можно считать завершенным. Чтобы зарегистрировать сервер в подсистеме DCOM нужно запустить приложение с параметром /regserver - Server.exe /regserver.
И последний штрих, это приложение СокетСервер от Борланда, которое находится в папке Delphi?/Bin/scktsvr.exe. Для того чтобы клиент мог видеть сервер нужно запустить scktsvr.exe (если у вас установлена Win NT/w2k/XP то можно это приложение зарегистрировать как службу, для этого его нужно запустить с параметром scktsvr.exe /install)

2.2. Приложение клиент

Создадим новое приложение, и сохраним его в папке C:\ClientServer\Client под именем Client. Разместим на главной форме по два компонента ClientDataSet(обзовем их cdsFirst, cdsSecond), DataSource(обзовем их dsFirst, dsSecond), DBGrid(обзовем их dbgFirst, dbgSecond) один компонент SocetConnection.

SocetConnection

Если вы все сделали правильно, то при установке свойства Connected в True, должно запустится приложение сервер.

ClientDataSet

Запустим Редактор Полей (двойной клик по компоненту) и занесем туда все поля. Для компонентов ClientDataSet напишем обработчик sdsAfterPost

И подставим его на обработку события AfterPost обоих ClientDataSet"ов.

DataSource

Свойство Значение
DataSet cdsFirst и cdsSecond соответственно

DBGrid

Свойство Значение
DataSource dsFirst и dsSecond соответственно

Запустим Редактор колонок (двойной клик по компоненту) и занесем туда все поля.
Положим на форме возле Гридов по кнопке и в обработчик onClick запишем.