Текст книги "Программирование в Delphi. Трюки и эффекты"
Автор книги: Александр Чиртик
Жанр: Программирование, Компьютеры
сообщить о неприемлемом содержимом
Текущая страница: 20 (всего у книги 24 страниц)
Краткое описание сетевых компонентов
В Delphi 7 количество компонентов, предусмотренных для программирования самых различных сетевых приложений, просто радует глаз (см. вкладки IndyClients и IndyServers). Правда, в книге будет рассмотрено построение приложения на базе только компонентов IdTCPServer и IdTCPClient (написание клиент-серверных приложений с использованием всех сетевых компонентов могло бы занять всю книгу).
Начнем с компонента сервера IdTCPServer. Для использования возможностей сервера этот компонент нужно поместить на форму (компонент неотображаемый). При настройке компонента наиболее полезны следующие его свойства:
• Active – активизирует или деактивизирует сервер (по умолчанию имеет значение False);
• Bindings – настраивает серверные сокеты (присоединяет их к определенному порту компьютера, позволяет определять диапазон IP-адресов и портов клиентов) с помощью окна Binding Editor;
• ListenQueue – ограничивает максимальное количество запросов на установление соединения от клиентов в очереди;
• MaxConnections – позволяет ограничить максимальное количество клиентов, присоединяемых к серверу;
• MaxConnectionReply – позволяет настроить сообщение, посылаемое сервером новым клиентам, когда их количество достигает значения, установленного в свойстве MaxConnections.
Рассмотрим несколько подробнее настройку серверных сокетов с использованием свойства Bindings. На рис. 11.1 показано, как с помощью окна Binding Editor настроить сервер на обслуживание клиентов с любыми IP-адресами, при этом в данном случае серверный сокет присоединяется к порту 12340.
Рис. 11.1. Использование окна Binding Editor
Для более детальной настройки каждого серверного сокета можно использовать окна Object TreeView и Object Inspector так, как показано на рис. 11.2.
Рис. 11.2. Настройка серверного сокета
На этом настройку сервера можно завершить (хотя здесь использованы далеко не все возможности компонента IdTCPServer). Основная же работа сервера при обработке запросов клиентов может реализовываться в обработчике события OnExecute. В этот обработчик передается ссылка на объект TIdPeerThread – поток, ассоциированный с клиентом, присоединенным к серверу. Посредством этого объекта (а точнее, его свойства Connection) можно получать и отправлять данные, а также получать и устанавливать множество полезных параметров соединения. Первый пример использования объекта TIdPeerThread при обработке запроса клиента приведен немного ниже в листинге 11.1.
Теперь рассмотрим порядок конфигурирования клиента (IdTCPClient), чтобы он был способен взаимодействовать с сервером. Чтобы использовать компонент TCP-клиента, достаточно поместить этот компонент на форму (компонент неотображаемый). После этого нужно указать значения, по меньшей мере, следующих его свойств (остальные упоминаются по мере необходимости в приведенных далее примерах):
• Host – имя или IP-адрес компьютера, на котором запущен сервер;
• Port – номер порта, к которому присоединен серверный сокет.
По сути, даже эти свойства на этапе разработки формы настраивать необязательно. Приложение получается гораздо более гибким, если предоставлять, например, пользователю возможность выбирать (или вводить) имя или адрес сервера.
Простой обмен данными
Прежде чем приступить к работе с описанными в предыдущем разделе компонентами IdTCPServer и IdTCPClient, ознакомьтесь с созданием несложного клиент-серверного приложения, клиентская и серверная части которого выполняют следующие функции.
• Клиентское приложение соединяется с сервером и отправляет ему введенную пользователем строку, ждет ответа, выводит полученный от сервера текст и затем отсоединяется от сервера.
• Серверное приложение принимает строку от клиентского приложения и посылает ответ (также текстовый), после чего разрывает соединение. Кроме этого, приложение ведет подсчет количества обслуженных клиентов и запоминает IP-адрес компьютера, с которого пришел последний запрос.
Реализация как серверного, так и клиентского приложения в данном случае предельно проста. Проект серверного приложения называется SimpleServer. Внешний вид формы сервера (во время работы приложения) представлен на рис. 11.3.
Рис. 11.3. Внешний вид простого сервера
Текстовое поле (Edit) с количеством обработанных запросов имеет имя txtCount, а текстовое поле с адресом последнего обслуженного компьютера названо txtFrom. Вся работа сервера заключается в обработке события Execute для компонента IdTCPServer, помещенного на форму (присоедините этот компонент к порту 12340 и установите значение свойства Active = True) (листинг 11.1).
Листинг 11.1. Реализация простого сервера
procedureTForm1. IdTCPServer 1 Execute (AThread: TIdPeerThread);
var
strText: string;
begin
//Принимаем от клиента строку
strText:= AThread.Connection.ReadLn;
//Отвечаем
AThread.Connection.WriteLn('Принял строку:'+ strText);
//Обно вим сведения на форме сервера (сервер многопоточный,
//поэтому используем синхронизацию)
section.Enter;
Inc(processed,1);
txtCount.Text:= IntToStr(processed);
txtFrom.Text:= AThread.Connection.Socket.Binding.PeerIP;
section.Leave;
//Закрываем соединение с пользователем
AThread.Connection.Disconnect;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
section:= TCriticalSection.Create;
end;
При ответе клиенту сервер повторяет принятую от него строку и добавляет текст 'Принял: в начало строки.
Анализируя листинг 11.1, можно заметить, что даже в рассматриваемом простейшем сервере пришлось с помощью критической секции применить синхронизацию при обновлении внешнего вида формы (в секцию uses необходимо дополнительно добавить имя модуля SyncObjs).
Теперь рассмотрим реализацию клиентской части (проект SimpleClient). Внешний вид клиентского приложения показан на рис. 11.4.
Рис. 11.4. Внешний вид клиента
Естественно, что для работы клиентского приложения на форму необходимо поместить экземпляр компонента IdTCPClient (его имя – IdTCPClient1). Свойству же Port этого компонента нужно присвоить значение 12 34 0. Текстовое поле (Edit) для ввода строки, подлежащей отправке не сервер, в рассматриваемом случае имеет имя txtMessage; текстовое поле (Edit), в которое вводится имя или адрес сервера, названо txtServer; поле со строками ответов (Memo) имеет имя txtResults.
Вся работа клиентского приложения выполняется при нажатии кнопки Обработать. Текст соответствующего обработчика приведен в листинге 11.2.
Листинг 11.2. Реализация простого клиента
procedureTForm1. Button 1 Click(Sender: TObject);
begin
//Соединяемся с сервером и посылаем ему введенную строку
IdTCPClient1.Host:= txtServer.Text;
IdTCPClient1.Connect;
IdTCPClient1.WriteLn(txtMessage.Text);
txtMessage.Text:= '';
//Ожидаем ответ и закрываем соединение
txtResults.Lines.Append(IdTCPClient1.ReadLn);
IdTCPClient1.Disconnect;
end;
Примечание
Для простоты в реализации клиентского приложения не предусмотрена обработка исключений, генерация которых возможна, например, при неправильном указании адреса компьютера, на котором запущено серверное приложение. В более сложных примерах, с которыми вы познакомитесь позже в этой главе, обработка указанных исключений реализована.
Теперь можно запускать сервер и клиенты (на произвольном количестве компьютеров) и наблюдать за результатами их работы. Только не забудьте запустить сервер до того, как начнете обращаться к нему с помощью программы-клиента.
Слежение за компьютером по сети
Теперь рассмотрим более интересный пример использования сетевых компонентов IdTCPServer и IdTCPClient, который может быть полезен для людей, имеющих отношение к администрированию компьютеров сети.
Серверная программа предварительно запускается на наблюдаемом компьютере. В этом примере программа-сервер позволяет клиентской программе получать следующие сведения о компьютере, на котором она (программа-сервер) запущена:
• разрешение монитора;
• глубину цвета для монитора;
• полноразмерную копию экрана;
• копию экрана, уменьшенную (или увеличенную) до заданных размеров.
Для получения указанных сведений программа-клиент должна посылать серверу следующие строковые значения:
• get_screen_width и get_screen_height– для получения ширины и высоты экрана в пикселах соответственно;
• get_screen_colors – для получения значения установленной для монитора глубины цвета (бит на точку);
• get_screen – для получения полноразмерной копии экрана;
• get_screen: X,Y – для получения копии экрана, приведенной к размеру X х Y.
Сначала рассмотрим реализацию сервера (проект SpyServer). Весь код, обеспечивающий работу сервера, помещен в модуль Unit1.pas формы Form1. Код обработчика запросов клиентов – главной процедуры для сервера – приведен в листинге 11.3.
Листинг 11.3. Обработчик клиентских запросов
procedureTForm1. IdTCPServer 1 Execute (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;
Используемая в листинге 11.3 процедура SendScreen, отправляющая клиенту копию экрана, приведена в листинге 11.4.
Листинг 11.4. Снятие копии экрана
//Процедура снимает копию экрана, преобразует полученное изображение к
//заданному размеру и отправляет преобразованное изображение клиентской
//программе
procedure SendScreen(width: Integer; height: Integer;
Connection: TIdTCPServerConnection);
var
ScreenCopy: TCanvas;
gr: TBitmap;
stream: TMemoryStream;
rcDest, rcSource: TRect;
begin
rcDest:= Rect(0, 0, width, height); //Конечный размер изображения
rcSource:= Screen.Deskt opRect; //Исходный размер изображения
//Создаем канву и присоединяем ее к контексту Рабочего стола
ScreenCopy:= TCanvas.Create;
ScreenCopy.Handle:= GetDC(0);
//Создаем объект для хранения копии экрана и копируем изображение
gr:= TBitmap.Create;
gr.Height:= height;
gr.Width:= width;
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.
При реализации сервера использован таймер. Он применен для скрытия формы сервера при запуске приложения (не забудьте установить его свойство Enabled равным True и свойство Interval равным 5 0). Компонент IdTCPServer (с именем IdTCPServerl) в этом примере присоединен к порту 12341 (не забудьте также установить свойство Active равным True).
Теперь перейдем к реализации клиентского приложения (проект SpyClient). Внешний вид формы (Form1) клиента во время работы показан на рис. 11.5 (видно, что пользователь наблюдаемого компьютера только что проиграл в игру Сапер).
Рис. 11.5. Внешний вид клиента слежения
Описания, имена и значения настроенных вручную свойств самых важных компонентов формы клиента приведены в табл. 11.1.
Таблица 11.1. Основные компоненты формы клиента слежения и их свойства
Работа клиентского приложения начинается с соединения с сервером. Код, отвечающий за реализацию этой операции, приведен в листинге 11.5.
Листинг 11.5. Соединение с сервером
procedure TForm1.cmbConnectClick(Sender: TObject);
begin
if (cmbConn ect.Caption = 'Подключиться') then
begin
if (txtServer.Text = '') then
//Не введено имя сервера
MessageDlg('Введите имя машины-сервера в текстовое поле',
mtInformation, [mbOK], 0)
else begin
//Подключаемся к серверу
IdTCPClient1.Host:= txtServer.Text;
try
IdTCPClient1.Connect;
except
MessageDlg('Не удается соединиться с указанным сервером',
mtError, [mbOK], 0);
Exit;
end;
end
end
else begin
//Отключаемся от сервера
IdTCPClient1.Disconnect;
end;
end;
Если соединение с сервером выполнено успешно, то выполняется обработчик TForm1. IdTCPClient1Connected, подготавливающий приложение-клиент к периодическим запросам данных с сервера (листинг 11.6).
Листинг 11.6. Действия, выполняемые при соединении с сервером
procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
txtServer.Enabled:= False;
cmbConn ect.Caption:= ' Отключиться ';
//Начинаем периодически запрашивать данные с сервера
Timer1.Enabled:= True;
//Выполним первый запрос, не дожидаясь срабатывания таймера
Timer 1 Timerr(Nil);
end;
При отсоединении от сервера также выполняются действия, прекращающие периодические запросы данных и переводящие клиента в состояние ожидания подключения (первоначальное состояние программы) (листинг 11.17).
Листинг 11.7. Действия при отсоединении от сервера
procedure TForm1.IdTCPClient1Disconnected(Sender: TObject);
begin
txtServer.Enabled:= True;
cmbConn ect.Caption:= ' Подключиться ';
Timer 1. Enableddd:= False;
end;
Самой сложной частью работы клиентского приложения является обработка данных, получаемых от сервера. Клиентское приложение запрашивает данные по таймеру и обрабатывает полученные данные. Код реализации этих действий приведен в листинге 11.8.
Листинг 11.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);
//... копия экрана
//.....первый вариант – копирование экрана без сжатия
//
WriteLn('get_screen');
//.....второй вариант – сжатие на стороне сервера
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;
Текст листинга 11.8 содержит достаточное количество комментариев, поэтому дополнительно пояснять его код нет смысла. Остановимся пояснить лишь, зачем в процедуре TForm1. Timer1Timer предусмотрено два варианта получения изображения с сервера.
Дело в том, что сжатие (в данном примере разрешение экрана наблюдаемого компьютера больше размера компонента imgScreen) на стороне сервера требует от компьютера, на котором запущено серверное приложение, большего процессорного времени на снятие копии экрана. Это снижает нагрузку на сеть при передаче изображения, а также экономит ресурсы компьютера-клиента, однако качество сжатого изображения получается несколько хуже, чем при предоставлении компоненту Image возможности масштабировать изображение самостоятельно.
Если же не использовать сжатие изображения на сервере, возрастает нагрузка на сеть при передаче полноразмерной копии экрана, а вся работа по сжатию изображения возлагается на компонент imgScreen (то есть на компьютере клиента дополнительно тратится процессорное время). При большом разрешении экрана наблюдаемого компьютера (или при наблюдении сразу за несколькими компьютерами) машина клиента, если она недостаточно мощная, может начать весьма ощутимо «тормозить». Качество сжатого изображения при этом получается более высоким.
В качестве относительно эффективного решения можно предложить использовать большие промежутки времени между запросами данных с сервера слежения с масштабированием изображения на серверной стороне (если, конечно, машина сервера не является очень маломощной).
Многопользовательский разговорник
В завершение знакомства с компонентами IdTCPClient и IdTCPServer для организации сетевого взаимодействия будет рассмотрено создание полноценного клиент-серверного приложения – многопользовательского разговорника. Как можно догадаться из названия, это приложение будет позволять обмениваться сообщениями большому количеству пользователей.
Поскольку этот пример будет несколько сложнее предыдущих примеров главы (в плане организации сетевого взаимодействия), то рассмотрим подробно основные этапы его проектирования, разработки и реализации (начиная с требований и поведений клиента и сервера и заканчивая нюансами реализации приложений).
Требования к клиентскому и серверному приложениямПользователи при работе с клиентскими приложениями должны иметь следующие возможности:
• видеть полный текст разговора с момента их подключения к серверу;
• отсылать сообщения как всем, так и только определенным пользователям;
• видеть список пользователей, участвующих в разговоре (при этом список должен автоматически обновляться при отключении или присоединении новых пользователей);
• получать уведомления об отключении или присоединении новых пользователей (прямо в тексте разговора).
Серверное приложение, кроме управления подключением, отключением пользователей, а также доставки сообщений, должно выполнять протоколирование событий (подключение, отключение пользователей, от кого и кому послано то или иное сообщение).
При реализации серверного приложения нужно преодолеть некоторые сложности, связанные с тем, что к серверу будут постоянно подключены сразу несколько пользователей, причем информация о каждом пользователе будет постоянно храниться и использоваться сервером. Кроме того, нужно обеспечить надежную работу клиентского, а главное, серверного приложения при проблемах, связанных с неисправностями сети.
Наконец, нужно обеспечить автоматическую рассылку клиентским приложениям следующей информации (клиенты эту информацию специально с сервера не запрашивают):
• текст сообщений;
• уведомления о присоединении или отключении пользователей.
Формат сообщений клиента и сервераКлиент и сервер обмениваются только текстовыми сообщениями (не путайте с сообщениями, которыми обмениваются пользователи в ходе разговора). Строка любого сообщения состоит из двух частей: префикса и текста сообщения. Префикс отделяется от текста сообщения символом: (двоеточие). Префикс определяет действия, которые должны быть выполнены с полученным сообщением.
Возможны следующие сообщения от клиента серверу:
• name: имя_пользователя – с помощью этого сообщения клиентская программа сообщает серверу имя, под которым должен быть зарегистрирован пользователь (это имя будет отображаться и другим пользователям);
• text: текст – при получении этого сообщения сервер должен разослать текст всем участникам разговора (включая отправителя);
• имя_адресата: текст – при получении этого сообщения сервер должен отправить текст только заданному префиксом пользователю имя_адресата, а также должен отправить копию автору сообщения.
К сообщениям третьего типа относятся все сообщения, принимаемые сервером и не начинающиеся со слов text: или name:.
В свою очередь, сервер может посылать клиентской программе сообщения следующего вида.
• ok: – указывает, что пользователь зарегистрирован и может вступать в разговор.
• error: сообщение_об_ошибке – указывает, что пользователь по каким-то причинам не может участвовать в разговоре. При получении этого сообщения клиентская программа должна показать окно с текстом сообщение_об_ошибке и разорвать соединение с сервером.
• adduser: имя_пользователя – при получении такого сообщения клиентская программа должна добавить строку имя_пользователя в список участников разговора.
• deluser: имя_пользователя – при получении такого сообщения клиентская программа должна удалить строку имя_пользователя из списка участников разговора.
• text: текст – при получении такого сообщения клиентская программа должна добавить текст к тексту разговора.
Перед рассмотрением реализации клиентской и серверной частей нужно сказать несколько слов об использовании специальных сообщений клиента (name: имя_ пользователя) и сервера (ok: и error: сообщение об ошибке). Дело в том, что в предлагаемой реализации сервера присоединение нового пользователя к разговору происходит следующим образом.
1. Клиентское приложение присоединяется к серверу (количество пользователей ограничено, поэтому сервер может послать лишнему пользователю сообщение error: с соответствующим текстом, описывающим ошибку, и тут же разорвать установленное соединение).
2. Клиентское приложение посылает серверу сообщение с именем пользователя (префикс name:).
3. Если имя, под которым хочет зарегистрироваться новый пользователь, уже используется, то клиентскому приложению отправляется сообщение error: с пояснением ошибки.
4. Если имя, под которым хочет зарегистрироваться новый пользователь, свободно, то сервер сохраняет его (и рассылает всем остальным клиентским приложениям), а также посылает приложению присоединенного пользователя список всех остальных пользователей. Только после этого сервер дает новому пользователю возможность участвовать в разговоре (сообщение ok:).
Остальные нюансы будут рассмотрены при описании исходного кода клиентского и серверного приложений.
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.