Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Прикладное программирование на CC++ с нуля до мультимедийных и сетевых приложений

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
☆
Введение в компьютерные коммуникации 91
инсталлирован протокол IP, то машина имеет предопределенный адрес
127.0.0.1, который можно использовать, например, если вы создаете на своем компьютере одновременно и локальный и удаленный хосты. Для отладки раз­рабатываемых коммуникационных программ имеет смысл все тестировать «на себе» и только потом переходить к работе в сети.
Напомним также, что при подключении компьютера к сети ему присваи­вается реальный или внутрисетевой IP-адрес. При модемном подключении к провайдеру присваиваемый адрес будет меняться от сеанса к сеансу. Для того чтобы узнать свой текущий адрес в операционной системе Windows 98, можно запустить программу winipcfg, а в более поздних версиях ОС достаточно от­крыть свойства значка модема на панели задач. Можно также воспользоваться утилитой ipconfig.
Идентификация адресов хостов еще не достаточна для организации связи. Дело в том, что на машине может работать одновременно несколько комму­никационных процессов. Вы можете в одно и то же время скачивать из Ин­тернета необходимый файл и работать, например, в Интернет-пейджере ICQ. Для того чтобы такая многозадачность могла работать и процессы не мешали бы друг другу, каждому приложению, занимающемуся приемом или переда­чей данных, сопоставляется так называемый порт. Не следует путать такой виртуальный порт с физическими последовательными и параллельными пор­тами компьютера (COM и LPT), хотя и те и другие предназначены для обмена данными. Порт присваивается процессу в виде номера, представленного це­лым числом в достаточно широких пределах. Вообще говоря, номер порта мо­жет быть назначен процессу произвольно, но необходимо позаботиться о том, чтобы для двух или более запущенных на компьютере процессов порты не оказались одинаковыми. Следует, также, помнить, что существуют умолча­ния, в соответствии с которыми назначаются порты некоторых стандартных процессов. Так, например, порт 3128 назначается по умолчанию прокси-сер­верам, и занимать это значение другими процессами не рекомендуется. Здесь уместно сделать одно крайне важное замечание. На практике некоторые про­вайдеры ограничивают произвол в использовании номеров портов обычными пользователями — закрывают те или иные номера или диапазоны номеров. Делается это с целью обеспечения безопасности сетей, защиты от вирусов. По этой причине не исключена ситуация, когда разрабатываемое вами приложе­ние отказывается работать в сети. Возможно, необходимо обратиться к про­вайдеру и узнать о возможности использования конкретного номера порта.
Теперь понятно, что всякий информационный процесс (процесс обмена данными между клиентом и сервером или двумя узлами) однозначно задается парой IP-адресов хостов и парой портов, назначенных приложениям на хос­тах. Совокупность адресов и портов процесса принято называть сокетом. Во­обще говоря, понятие сокета в информатике более фундаментально, но точ­ная формулировка автору неизвестна. Заметим только, что часто и не совсем верно под сокетом понимается постоянное соединение двух хостов, в отличие от соединения, создаваемого для «одноразовой» передачи данной порции ин­формации.
92 Глава 3. Передача информации в сетях
Конкретные средства для передачи данных определяются средой разра­ботки приложений. В используемой нами интегрированной оболочке Borland
C++ Builder такие средства существуют и весьма разнообразны. На вкладке FastNet палитры визуальных компонентов собраны инструменты, позволяю-
щие создавать коммуникационные приложения для широкого круга задач.
Компоненты NMMsg è NMMSGServ являются клиентским и серверными VCL, обеспечивающими передачу и прием текстовых сообщений между парой узлов. Обмен информацией производится по протоколу транспортного уровня TCP/IP. Для передачи данных произвольного типа (также по протоколу TCP/IP) целесообразно использовать так называемые потоки (Stream). В по­ток может быть помещена порция информации любого вида, и эта порция «переносится потоком» с одного компьютера на другой. Клиентская и сервер­ная части потоков представлены компонентами NMStrm è NMStrmServ соот­ветственно. Компонент NMUDP предназначен для передачи пользовательских дейтаграмм по протоколу UDP. Здесь разделение на серверную и клиентскую часть отсутствует. VCL наделена функциями как приема, так и передачи дей­таграмм.
На этой вкладке сосредоточены и многие другие коммуникационные ви­зуальные компоненты. С их помощью можно создавать программы для рабо­ты с электронной почтой, FTP-системами, web-страницами и многое другое. Непосредственно к тематике наших изысканий эти инструменты отношения не имеют, однако для тех, кто заинтересуется указанными возможностями, мы можем порекомендовать отличную книгу (Козлова А. В. Программирова­ние для Интернет в C++ Builder 5. — М.: БИНОМ, 2001).
С помощью визуальных компонентов Builder'а можно организовывать и сокетные соединения. Компоненты ClientSocket è ServerSocket, используемые для этих целей, находятся в палитре компонентов на вкладке Internet. К сожа­лению, в версиях Builder'а до шестой включительно не реализованы средства для работы с протоколом реального времени RTP. Тем не менее, уже вклю­ченных в пакет компонентов нам хватит для знакомства с программировани­ем коммуникаций, к которому мы и переходим.
Простейшие коммуникационные программы
Начнем с программы, точнее — с пары программ, которые выполняют очень простую задачу: Клиент передает по сети хосту сообщение, формируе­мое пользователем, а сервер принимает сообщение и представляет его для прочтения. Данные передаются по протоколу ТСР по указанному IP-адресу сервера и на указанный порт. Сообщение передается в виде текстовой строки. Как указывалось в предыдущем разделе, для достижения указанной цели дос­таточно воспользоваться парой визуальных компонентов NMMsg è NMMSGServ. Представляемые далее программы мы разрабатываем «по мини­муму», то есть используя только самые необходимые средства.
Организация интерфейсов клиентской и серверной программы показана на рис. 3.1.
Простейшие коммуникационные программы 93
Ðèñ. 3.1
В клиентском приложении в поле редактирования IP-address задается ад­рес сервера. Напомним, что для работы на одном компьютере с имитацией сети можно указать адрес 127.0.0.1. В поле редактирования MESSAGE вписы­вается текст передаваемого сообщения. Кнопка RUN выполняет передачу, а кнопка CLOSE завершает работу программы.
Если к моменту передачи запущен сервер, то на нем в поле RECEIVE
MESSAGE FROM отобразится имя, которое назначил себе клиент. В строке MESSAGE показано само сообщение. Здесь кнопка CLOSE также завершает
работу.
Начнем проектирование программы. В новом проекте для клиента необ­ходимо, в соответствии с рисунком, разместить на форме две метки (Label), два поля редактирования (Edit), две кнопки (Button) и компонент NMMsg со вкладки FastNet палитры стандартных компонентов. Уже на этапе проектиро­вания свойство Text объекта Edit1 имеет смысл установить в значение
127.0.0.1 — адрес по умолчанию. Теперь посмотрим на вкладку Properties (Свойства) компонента NMMSg инспектора объектов (Object Inspector), пока­занную на рис. 3.2.
Ðèñ. 3.2
94 Глава 3. Передача информации в сетях
Свойство FromName задает имя клиента, отправляющего сообщение. Мы, не мудрствуя лукаво, просто назвали себя CLIENT, что и отображено в сер­верной части предыдущего рисунка. В свойстве Host можно было бы задать предопределенным значение для IP-адреса сервера. Мы оставили это свойст­во пустым, поскольку задаем его в первом поле редактирования. В свойстве Port задано значение 6711. Выбор порта, как отмечалось, достаточно произ­волен, однако есть рекомендация во избежание путаницы использовать для NMMsg именно этот номер. На остальных свойствах останавливаться не бу­дем — мы же работаем «по минимуму». Значит, примем то, что предлагается по умолчанию.
Теперь приведем весь небольшой листинг программы.
#include <vcl.h> #pragma hdrstop #include "Unit1.h" //—————————————————————————————————————­#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //—————————————————————————————————————­__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
NMMsg1->Host=Edit1->Text; } //—————————————————————————————————————­void __fastcall TForm1::Button1Click(TObject *Sender) {
NMMsg1->PostIt(NMMsg1->FromName);
NMMsg1->PostIt(Edit2->Text); } //—————————————————————————————————————­void __fastcall TForm1::Button2Click(TObject *Sender) {
Close(); }
Инициализация формы сопровождается присвоением свойству NMMsg1->Host значения, содержащегося в первом поле редактирования. Об­работчик нажатия первой кнопки выполняет две операции посылки дан­ных — два раза вызывает метод NMMsg1->PostIt(). Аргументом этой функ­ции будет текстовая строка, посылаемая на сервер. Как можно видеть, первый раз это будет свойство NMMsg1->FromName, то есть значение выбранного нами для себя имени клиента. Второй раз метод PostIt отправляет строку, на­ходящуюся во втором поле редактирования — свойство Edit2->Text. Вот, соб­ственно, и все, что содержится в минимальной программе клиента!
При проектировании серверного приложения на форме нужно разместить две метки, два поля редактирования, закрывающую кнопку и компонент NMMSGServ все с той же вкладки FastNet палитры компонентов. Прежде
Простейшие коммуникационные программы 95
всего, нужно в инспекторе объектов проконтролировать то, что порт сервера задается значением 6711. Листинг серверного приложения еще меньше, чем программа клиента.
#include <vcl.h> #pragma hdrstop #include "Unit1.h" //—————————————————————————————————————­#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //—————————————————————————————————————­__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner) { } //—————————————————————————————————————­void __fastcall TForm1::NMMSGServ1MSG(TComponent *Sender,
const AnsiString sFrom, const AnsiString sMsg)
{
Edit1->Text=sFrom;
Edit2->Text=sMsg; } //—————————————————————————————————————­void __fastcall TForm1::Button1Click(TObject *Sender) {
Close(); }
Нам необходимо организовать обработчик события, заключающегося в том, что сервер обнаруживает поступление сообщения от клиента. Это стан­дартное событие (Event) для компонента NMMSGServ. Для написания обра­ботчика нужно при проектировании на форме выделить компонент. В ин­спекторе объектов для него перейти на вкладку Events и дважды щелкнуть мышью на событии OnMSG. Откроется текст программы с пока пустым те­лом обработчика. Как можно видеть из листинга, функция-обработчик будет называться NMMSGServ1MSG, а в теле обработчика стоят два выражения присваивания, заносящие в поля редактирования аргументы sFrom è sMsg этой функции. Проницательный читатель уже догадался, что первый аргумент это имя клиента, а второй — само сообщение.
Теперь нам остается стартовать на компьютере обе программы, написать в клиенте свое сообщение, отослать его серверу кнопкой RUN и, если все сде­лано правильно, практически мгновенно принять сообщение на сервере. В локальной сети или, тем более, в Интернете задержка при передаче сообще­ния может быть заметной, а иногда и весьма значительной, но это уже, как говорится, дело техники, а не программиста.
Перейдем к паре программ, демонстрирующих передачу информации с помощью так называемых потоков. Объектами, транспортирующими данные, здесь будут визуальные компоненты NMStream è NMStreamServ для клиент-
96 Глава 3. Передача информации в сетях
ской и серверной программы соответственно. В отличие от предыдущих при­ложений, ориентированных на пересылку текстовых строк, потоковый метод позволяет передавать данные произвольного типа. Мы здесь все же по-преж­нему перешлем строку (ориентированными на поток средствами), но на сер­вере запишем эту строку в файл. Кроме того, здесь мы воспользуемся инстру­ментами контроля за процессом соединения компьютеров и самой транспор­тировкой.
Интерфейсы клиентского и серверного приложения представлены на рис. 3.3.
Ðèñ. 3.3
По нажатию первой кнопки клиентской программы происходит пробное соединение с сервером с сразу следующим отсоединением. В информацион­ном поле в правой части окна отобразится результат такого теста. На рисунке продемонстрировано успешное соединение — текст Connection OK и отсоеди­нение — текст Disconnected. Â ïîëå MESSAGE вводится передаваемая строка. Нажатие кнопки SENT выполняет отсылку данных. В информационном поле в случае успеха появляется сообщение Message sent. Кнопка STOP, как обыч­но, закрывает программу.
В серверной программе каждое подключение клиента сопровождается ин­формацией CLIENT connect. Можно видеть, что в нашем примере присутству­ет два таких сообщения на сервере. Первое связано с тестовой проверкой, а второе — реакция на пересылку сообщения. Кроме того, получение потока с данными сопровождается информацией RECEIVE STREAM.
При проектировании программы-клиента на форму необходимо помес­тить три кнопки, метку, поле редактирования. Для организации информаци­онного поля следует разместить также ранее нами не используемый компо­нент Memo. Со вкладки FastNet берем инструмент NMStream. Для него в ин­спекторе объектов зададим IP-адрес хоста 127.0.0.1 и оставим предлагаемый по умолчанию порт. Для объекта Memo установим в ИСТИНА (true) свойст
-
Простейшие коммуникационные программы 97
во вертикальной полосы прокрутки. Вы легко сделаете это в инспекторе объ­ектов.
Листинг клиентского приложения представлен далее.
#include <vcl.h> #pragma hdrstop #include "Unit1.h" //—————————————————————————————————————­#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //—————————————————————————————————————­__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Memo1->Clear(); } //—————————————————————————————————————­void __fastcall TForm1::Button1Click(TObject *Sender) {
NMStrm1->Connect();
NMStrm1->Disconnect(); } //—————————————————————————————————————­void __fastcall TForm1::Button2Click(TObject *Sender) {
Close(); } //—————————————————————————————————————­void __fastcall TForm1::NMStrm1ConnectionFailed(TObject *Sender) {
Memo1->Lines->Add("Can't connect"); } //—————————————————————————————————————­void __fastcall TForm1::NMStrm1Connect(TObject *Sender) {
Memo1->Lines->Add("Connection OK!"); } //—————————————————————————————————————­void __fastcall TForm1::NMStrm1Disconnect(TObject *Sender) {
Memo1->Lines->Add("Disconnected"); } //—————————————————————————————————————­void __fastcall TForm1::NMStrm1MessageSent(TObject *Sender) {
Memo1->Lines->Add("Message sent"); } //—————————————————————————————————————­void __fastcall TForm1::Button3Click(TObject *Sender) {
TMemoryStream *MS;
98 Глава 3. Передача информации в сетях
MS = new TMemoryStream();
StreamLn(MS,Edit1->Text);
NMStrm1->PostIt(MS);
MS->Free(); }
При инициализации формы выполняется очистка информационного поля Memo. Нажатие первой кнопки Button1 производит тестовое подключение к серверу и отключение от сервера с помощью методов Connect è Disconnect объекта NMStrm. С кнопкой Button2 все ясно — это закрытие приложения.
Далее следует ряд функций обработки некоторых событий для компонен­та NMStrm. Суть обрабатываемых событий понятна из названий функций. В результате возникновения тех или иных ситуаций в информационное поле помещаются соответствующие строки. Напомним, что для написания обра­ботчика необходимо в инспекторе объектов на вкладке Events компонента дважды щелкнуть мышью справа от названия события и в пустое тело функ­ции вставить соответствующий код.
Главная акция программы — передача информации на сервер выполняет­ся обработчиком нажатия кнопки Button3. Поскольку мы организуем потоко­вый обмен данными, необходимо создать поток нужного типа. В данном слу­чае мы объявляем класс потока в оперативной памяти TMemoryStream и соз­даем объект MS этого класса. Отметим, что в Builder'е можно оперировать с потоками других типов, например, со строковым потоком TStringStream. Не­трудно понять, что выражение StreamLn(MS,Edit1->Text); помещает в поток
MS строку, содержащуюся в данный момент в поле редактирования. Метод PostIt выполняет отправку потока на сервер. Последнее выражение в теле
функции освобождает поток.
Текст серверной программы представлен в листинге.
#include <vcl.h> #pragma hdrstop #include "Unit1.h" //—————————————————————————————————————­#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //—————————————————————————————————————­__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Memo1->Clear(); } //—————————————————————————————————————­void __fastcall TForm1::NMStrmServ1ClientContact(TObject *Sender) {
Memo1->Lines->Add("CLIENT contact"); } //—————————————————————————————————————­void __fastcall TForm1::NMStrmServ1MSG(TComponent *Sender,
const AnsiString sFrom, TStream *strm)
Простейшие коммуникационные программы 99
{
TMemoryStream *SS;
SS = new TMemoryStream();
SS->CopyFrom(strm,strm->Size);
Memo1->Lines->Add("RECEIVE STREAM");
SS->SaveToFile("111.txt");
SS->Free();
}
//—————————————————————————————————————-
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Close();
}
В качестве контроля за состоянием сервера здесь представлен обработчик события, заключающегося в контакте клиента с сервером. Информация о воз­никновении события помещается строкой в поле Memo. Обработчик NMStrmServ1MSG реагирует на получение сервером сообщения от клиента. Так же, как и в клиентской программе, здесь организуется поток в памяти SS. Далее с помощью метода CopyFrom потока в него из полученного сообщения извлекаются данные. Первым аргументом strm будет аргумент самого обработ­чика — собственно сам поток. Второй аргумент — свойство strm->Size пред­ставляет собой размер блока данных, переданный потоком от клиента, выра­женный в количестве байтов. Следующее выражение информирует пользова­теля о получении потока в информационном поле. Как и планировалось, мы не воспроизводим принятую строку, а помещаем ее в файл. Для этой цели служит метод SaveToFile потока в памяти. Последнее действие обработчика — очистка потока. После того как строка успешно принимается сервером, в ра­бочем каталоге программы создается текстовый файл 111.txt, который и будет содержать строку. Подчеркнем, что следующий акт передачи данных уничто­жит старый файл и создаст новый с тем же именем.
Обе предыдущие пары программ используют для передачи данных прото­кол ТСР. Теперь познакомимся с инструментом транспортировки данных (на­зываемых в данном случае дейтаграммами) по протоколу UDP. Поскольку один и тот же компонент NMUDP может выступать и в качестве клиента, и в качестве сервера, мы разработаем единое приложение, пересылающее инфор­мацию от себя к себе, тем более что все написанные нами программы пока ориентированы для работы на одном компьютере.
Сформулируем задачу следующим образом. Формируется массив случай­ных целых чисел. Подсчитывается и выводится в информационное поле сум­ма этих чисел. Массив рассматривается в качестве буфера в оперативной па­мяти. Этот буфер по протоколу UDP отправляется на «удаленный» (на самом деле — локальный) хост. Там он читается как буфер, снова превращается в массив. Подсчитывается сумма элементов массива и также выводится в ин­формационное поле. Естественно, если программа работает правильно, ис ходная и полученная суммы должны совпадать. Таким образом, рабочее окно
-
100 Глава 3. Передача информации в сетях
приложения после выполнения пересылки данных должно выглядеть так, как это показано на рис. 3.4.
Ðèñ. 3.4
Итак, на форме нового проекта необходимо разместить две кнопки, ин­формационное поле Memo и компонент NMUDP со вкладки FastNet. Сразу установим необходимые свойства этого компонента в инспекторе объектов (рис. 3.5).
Ðèñ. 3.5
IP-адрес удаленного хоста мы, как всегда, устанавливаем в 127.0.0.1. По­скольку мы общаемся сами с собой, удаленный и локальный порты должны совпадать, а выбранное значение 6767 рекомендовано для служб UDP, но от­нюдь не строго фиксировано. Остальные свойства нас пока не интересуют и будут оставлены по умолчанию. Теперь переходим к листингу программы.
#include <stdlib.h>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//—————————————————————————————————————-
#pragma package(smart_init)
#pragma resource "*.dfm"
#define MAX 2048