БГУ Пособие - Программирование в C++ Builder. Учебное пособие по курсу методы программирования для студентов специальностей
Скачать 1.24 Mb.
|
Определение свойств Host и Port. Поместим компонент TClientSocket на форму. Чтобы установить соединение, нужно присвоить свойствам Host и Port компонента TClientSocket значения, соответст- вующие адресу сервера. Host – это символьное имя компьютера-сервера, с которым надо соединиться (например: localhost, nitro.borland.com или mmf410-2), либо его IP-адрес (например: 127.0.0.1, 192.168.0.88). Port – номер порта (от 1 до 65535) на данном хосте для установления соедине- ния. Обычно номера портов выбираются, начиная с 1001 (номера меньше 1000 могут быть заняты системными службами, например, по умолча- нию POP – 110, Http – 80). Открытие сокета. После назначения свойствам Host и Port соответ- ствующих значений, можно приступить к открытию сокета. Для этого нужно присвоить свойству Active значения true. Здесь полезно вставить обработчик исключительной ситуации на случай, если соединиться не удается. Открытие сокета можно выполнить, также с помощью метода ClientSocket->Open(); Авторизация. На этом этапе вы посылаете серверу свой логин (имя пользователя) и пароль. Этот пункт можно пропустить, если сервер не требует ввода логинов или паролей. Посылка/прием данных – это, собственно и есть то, для чего откры- валось сокетное соединение; Закрытие сокета – после выполнения операций необходимо закрыть сокет, присвоив свойству Active значение false или вызовом метода Cli- entSocket->Close();. 98 Свойства и методы компонента TClientSocket. На рис. 20 и 21 по- казаны свойства и события компонента TClientSocket в инспекторе объ- ектов. В табл. 18 приведены их краткие описания. Рис. 20. Свойства компонента TClientSocket Рис. 21. События компонента TclientSocket Пример программы-клиента на основе сокета. Поместим на фор- му компонент TClientSocket, две кнопки Button1 и Button2, два окна типа TEdit и два компонента Memo1 и Memo2. При нажатии на кнопку Get- FromServeSendToClient вызывается обработчик события OnClick – Button1Click(). Перед этим в Edit1 нужно ввести хост-имя, а в Edit2 – порт удаленного компьютера. Когда TClientSocket должен прочитать ин- формацию из сокетного соединения, возникает событие OnRead и вызы- вается функция ClientSocketRead(). 99 Таблица 18 Свойства События Active – True – сокет открыт, а False–закрыт. Host – строка (типа String), указы- вающая на имя компьютера, к кото- рому следует подключиться. Address – строка (типа String), ука- зывающая на IP-адрес компьютера, к которому следует подключиться. Если вы укажете в Host символьное имя компьютера, то IP адрес будет запрошен у DNS. Port – номер порта (от 1 до 65535), к которому следует подключиться. Service – строка, определяющая службу (ftp, http, pop и т.д.), к порту которой произойдет подключение. ClientType – тип соединения ctNon- Blocking или ctBlocking OnConnect – возникает при установлении со- единения. В обработчике события можно на- чинать авторизацию или прием/передачу дан- ных. OnConnecting – возникает при установлении соединения, но соединение еще не установле- но. Обычно такие промежуточные события ис- пользуются для обновления статуса. OnDisconnect – возникает при закрытии сокета из вашей программы либо из-за сбоя. OnError – возникает при ошибке в работе со- кета. Операторы открытия сокета следует за- ключить в блок try..catch. OnLookup – возникает при попытке получения от DNS IP-адреса хоста. OnRead – возникает, когда удален-ный компь- ютер послал данные. OnWrite – возникает, когда вам разрешена за- пись данных в сокет. На рис. 22 показана форма приложения в процессе проектирования. Рис. 22. Форма приложения-клиента Ниже приводятся коды приложения-клиента. TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //открытие клиента и установка соединения void __fastcall TForm1::Button1Click(TObject *Sender) 100 { TClientSocket *cs = Form1->ClientSocket; // формируем указатель cs->Close(); // закрытие соединения(если слушали порт ранее) try { cs->Port=Form1->PortEdit->Text.ToInt();//получение номера порта cs->Host = HostEdit->Text; // получения адреса хоста cs->Active = true; // установка соединения } catch(...) { Memo1->Text = "Some problem with connection";// если введены //неверные значения } } //очистка окна void __fastcall TForm1::ClearClick(TObject *Sender) { Memo1->Clear(); } // считывание клиентом сообщения void __fastcall TForm1::ClientSocketRead(TObject *Sender, TCustomWinSocket *Socket) { Memo1->Text = Socket->ReceiveText(); // запись сообщения в поле клиента } void __fastcall TForm1::ClientSocketWrite(TObject *Sender, TCustomWinSocket *Socket) { Socket->SendText(Memo2->Text); // } //вызов события посылки сообщения void __fastcall TForm1::Button2Click(TObject *Sender) { Form1->ClientSocketWrite(Form1,Form1->ClientSocket->Socket); } Ниже описываются методы свойства Socket компонента TClient- Socket. Свойство Socket в свою очередь является объектом класса TСustomWinSocket. SendText(String) – посылка текстовой строки через сокет. SendStream() – посылка содержимого указанного потока через сокет. Пересылаемый поток должен быть открыт. SendBuf(Buf, Count) – посылка буфера через сокет. Буфером может являться любой тип, например, структура или простой тип int. Буфер 101 указывается параметром Buf, вторым параметром необходимо указать размер пересылаемых данных в байтах (Count). ReceiveText() – получить сообщение. Программирование серверов на основе сокетов. Следует заметить, что для сосуществования отдельных приложений клиента и сервера не обязательно иметь несколько компьютеров. Достаточно иметь лишь один, на котором можно одновременно запустить и сервер, и клиент. При этом в качестве имени компьютера, к которому надо подключиться, нужно использовать хост-имя localhost или IP-адрес – 127.0.0.1. Сервер, основанный на сокетном протоколе, позволяет обслуживать сразу множество клиентов. Причем, ограничение на их количество вы можете указать сами (или убрать это ограничение, как сделано по умол- чанию). Для каждого подключенного клиента сервер открывает отдель- ный сокет, по которому можно обмениваться данными с клиентом. Дру- гим решением является создание для каждого подключения отдельного процесса (Thread). Создание сервера включает следующие шаги: Определение свойств Port и ServerType – чтобы к серверу могли подключаться клиенты, порт, используемый сервером, должен совпадать с портом, используемым клиентом. Свойство ServerType определяет тип подключения. Открытие сокета – открытие сокета и указанного порта. Осуществ- ляется установкой свойства ServerSocket->Active=true. Автоматически начинается ожидание подсоединения клиентов. Подключение клиента и обмен данными с ним – клиент устанав- ливает соединение и начинается обмен данными с ним. Отключение клиента – клиент отключается и закрывается его со- кетное соединение с сервером. Закрытие сервера и сокета – по команде администратора сервер за- вершает свою работу, закрывая все открытые сокетные каналы и пре- кращая ожидание подключений клиентов. Далее приводится краткое описание компонента TServerSocket. На рис. 23 и 24 приводятся свойства и события компонента, соответственно, которые отображаются в окне Инспектора объектов. Табл. 19 содержит описание указанных свойств и событий. Свойство Socket компонента TServerSocket. Как же сервер может отсылать данные клиенту или принимать данные? Если вы работаете че- рез события OnClientRead и OnClientWrite, то общаться с клиентом мож- но через свойство Socket (TCustomWinSocket). Отправка/посылка данных через свойство Socket класса TServerSocket аналогична работе клиента( 102 методы SendText(), SendBuf(), SendStream(), ReceiveText()). Однако, сле- дует выделить некоторые полезные свойства и методы, характерные для для сервера: ActiveConnections (Integer) – количество подключенных клиентов; ActiveThreads (Integer) – количество работающих процессов; Connections (array) – массив, состоящий из отдельных классов TClientWinSocket для каждого подключенного клиента. Например, такая команда Рис 23. Свойства компонента TServerSocket Рис. 24. События компонента TServerSocket ServerSocket1->Socket->Connections[i]->SendText(“Hello!”); отсылает i-тому подключенному клиенту сообщение “Hello!”); IdleThreads (Integer) – количество свободных процессов (такие процессы кэшируются сервером, см. свойство ThreadCacheSize); LocalAddress, Lo- calHost, LocalPort – соответственно, локальный IP-адрес, хост-имя, порт; RemoteAddress, RemoteHost, RemotePort – соответственно, удаленный IP- 103 адрес, хост-имя, порт; методы Lock и UnLock – соответственно, блоки- ровка и разблокировка сокета. Таблица 19 Свойства События Socket – класс TServerWinSocket, через который вы имеете доступ к открытым сокетным каналам. ServerType – тип сервера. ThreadCacheSize – количество клиент- ских процессов (Thread), которые будут кэшироваться сервером. Кэширование происходит для того, чтобы не созда- вать каждый раз отдельный процесс и не уничтожать закрытый сокет, а оста- вить их для дальнейшего использова- ния. Тип: int. Active –Значение True указывает на то, что сервер работает и готов к приему клиентов, а False – сервер выключен. Port – номер порта для установления соединений. Порт у сервера и у клиен- тов должны быть одинаковыми. Реко- мендуются значения от 1025 до 65535. Service – строка, определяющая службу (ftp, http, pop и т.д.), порт которой будет использован. Тип:String. OnClientConnect – возникает, когда кли- ент установил сокетное соединение и ждет ответа сервера. OnClientDisconnect – возникает, когда клиент отсоединился . OnClientError – возникает, когда опе- рация завершилась неудачно. OnClientRead – возникает, когда клиент передал серверу данные. OnClientWrite – возникает, когда сервер может отправлять данные клиенту по со- кету. OnGetSocket – в обработчике этого со- бытия можно отредактировать параметр ClientSocket. OnGetThread – в обработчике события можно определить уникальный процесс (Thread) для каждого клиента канала, присвоив параметру SocketThread нуж- ную подзадачу TServerClientThread. OnThreadStart, OnThreadEnd – возни- кает, когда процесс Thread запускается или останавливается. OnAccept – возникает, когда сервер при- нимает клиента или отказывает ему в со- единении. OnListen – возникает, когда сервер пере- ходит в режим ожидания подсоединения клиентов. Пример. Создадим приложение-сервер. На форму нужно поместить кнопки Button1 и Button2, поле Edit1 и компоненты Memo1 и Memo2. При создании формы вызывается обработчик события OnCreate (Form- Create), в котором активизируется сервер. Свойство компонента TServer- Socket->Active устанавливается в true, что означает, что сокетное соеди- нение открыто и доступно для коммуникации с клиентами. В компоненте Edit1 указано значение прослушиваемого порта. Когда ServerSocket1 должен записать информацию в ClientSocket1, возникает событие OnCli- entWrite и вызывается функция ServerSocketClientWrite(). На рис. 25 при- ведена форма приложения в процессе проектирования. 104 Рис. 25. Форма приложения-сервера Ниже приводится листинг приложения. void __fastcall TForm1::FormCreate(TObject *Sender) { TServerSocket *ServerSocket = Form1->ServerSocket; // указатель на //сервер сокет ServerSocket->Close(); // закрытие соединения(если слушали порт //Ранее) ServerSocket->Port = Form1->Edit1->Text.ToInt(); // получение номера //порта ServerSocket->Active = true; //прослушивание данного порта } //открытие сервера по нажатию кнопки void __fastcall TForm1::Button2Click(TObject *Sender) { ServerSocket->Close(); // закрытие соединения ServerSocket->Port=Form1->Edit1->Text.ToInt(); //получение порта ServerSocket->Active = true; //прослушивание данного порта } //--------------------------------------------------------------------------- void __fastcall TForm1::ServerSocketClientWrite(TObject *Sender, TCustomWinSocket *Socket) //запись сообщения { Socket->SendText(Memo1->Text);// отправка сообщения } void __fastcall TForm1::Button1Click(TObject *Sender) { Memo1->Clear(); } //--------------------------------------------------------------------------- void __fastcall TForm1::ServerSocketClientRead(TObject *Sender, TCustomWinSocket *Socket) { Memo2->Text = Socket->ReceiveText(); } //отправка сообщения i-тому клиенту void __fastcall TForm1::Button3Click(TObject *Sender) { 105 int n=ServerSocket->Socket->ActiveConnections; for(int i=0;i< n ;i++) ServerSocket->Socket->Connections[i]->SendText( Memo1->Text+IntToStr(i)); } Ниже рассматриваются некоторые приемы работы с компонентом TServerSocket. Хранение уникальных данных для каждого клиента. Если сервер будет обслуживать множество клиентов, то потребуется хранить инфор- мацию для каждого клиента (имя и др.), причем с привязкой этой ин- формации к сокету данного клиента. В некоторых случаях делать все это вручную (привязка к конкретному сокету, массивы клиентов и т.д.) не очень удобно. Поэтому для каждого сокета существует специальное свойство Data для хранения указанной информации. Посылка файлов через сокет. Рассмотрим посылку файлов через сокет. Достаточно открыть этот файл как файловый поток (TFileStream) и отправить его через сокет (SendStream). Поясним это на примере: ServerSocket1->Socket->Connections[i]->SendStream(srcfile); Нужно заметить, что метод SendStream() используется не только сер- вером, но и клиентом. Передача блоков информации. Посылаемые через сокет данные мо- гут не только объединяться в один блок, но и разъединяться по несколь- ким блокам. Дело в том, что через сокет передается обычный поток, но в отличие от файлового потока (TFileStream) он передает данные медлен- нее (сеть, ограниченный трафик и т.д.). Именно поэтому две команды: ServerSocket1->Socket->Connections[0]->SendText(“Hello,”); ServerSocket1->Socket->Connections[0]->SendText(“world!”); совершенно идентичны одной команде: ServerSocket1->Socket->Connections[0]->SendText(“Hello, world!”); Поэтому, если отправить через сокет файл, скажем, в 100 Кб, то по- лучателю блока придет несколько блоков с размерами, которые зависят от трафика и загруженности линии. Причем, размеры не обязательно бу- дут одинаковыми. Отсюда следует, что для того, чтобы принять файл или любые другие данные большого размера, следует принимать блоки данных, а затем объединять их в одно целое (и сохранять, например, в файле). Другим решением данной задачи является тот же файловый по- ток TFileStream (либо поток в памяти TMemoryStream). Принимать блоки данных из сокета можно через событие OnRead (OnClientRead), исполь- зуя универсальный метод ReceiveBuf(). Определить размер полученного блока можно методом ReceiveLength(). 106 Приведем еще один простой пример приложений для создания кли- ента и сервера в одном приложении, соединения между ними и обмена данными. Ниже приводится соответствующий программный код. //-- Клиент ------------------------------------------------------------------- void __fastcall TForm1::ClientSocketRead(TObject *Sender, TCustomWinSocket *Socket) { Edit1->Text = Socket->ReceiveText();} void __fastcall TForm1::Button1Click(TObject *Sender) { ClientSocket->Close(); if((Edit1->Text.Length() > 0)&&(Edit2->Text.Length() > 0)) { ClientSocket->Host = Edit2->Text; ClientSocket->Port = Edit1->Text.ToInt(); ClientSocket->Open(); } } //-- Сервер------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender){ ServerSocket->Close(); // закрытие соединения ServerSocket->Active = true; } //активизация сокета //--------------------------------------------------------------------------- void __fastcall TForm1::FormDestroy(TObject *Sender){ ServerSocket->Close(); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender){ ServerSocket->Socket->Connections[0]->SendText(Edit1->Text); } Потоки. Для создания распределенных приложений, работающих с несколькими клиентами, можно воспользоваться сокетным потоком. По- токи позволяют синхронизировать работу нескольких клиентов. Приве- дем пример создания многопотокового сервера и клиентской програм- мы. По каждому запросу клиента сервер создаёт поток и определённое время ожидает ответа клиента. Потоки создаваемые сервером, это по- томки класса TserverClientThread. Поэтому мы будем объявлять собст- венный потоковый класс, наследуя его от TServerClientThread. Ниже приводится код программы с комментариями. //Unit1.h- классы для сервера #ifndef Unit1H #define Unit1H #include #include #include #include #include 107 //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components TServerSocket *ServerSocket1; TMemo *Memo1; TButton *Button1; TButton *Button2; TMemo *Memo2; TLabel *Label1; TLabel *Label2; void __fastcall ServerSocket1GetThread(TObject *Sender, TServerClientWinSocket *ClientSocket, TServerClientThread *&SocketThread); void __fastcall Button1Click(TObject *Sender); void __fastcall Button2Click(TObject *Sender); private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); }; extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- // Потоки в сервере наследуются от TServerClientThread. // Мы будем объявлять потоковый класс следующим образом: class PACKAGE TMyServerThread : public Scktcomp::TServerClientThread { public: /* перед тем как завершить поток, устанавливаем FreeOnTerminate в false, и поток останется в кэше. При установке KeepInCache в false, после завершения вы- полнения потока, он будет удалён. */ __fastcall TMyServerThread(bool CreateSuspended, TServerClientWinSocket* ASocket) : Scktcomp::TServerClientThread(CreateSuspended, ASocket) { CreateSuspended = false; KeepInCache=true; FreeOnTerminate=false; }; /* Чтобы включить поток, переопределяем метод ClientExecute(), вызываемый из метода Execute() класса TServerClientThread.*/ void __fastcall ClientExecute(void); }; #endif //Реализация сервера------------------------------------------------ // *************** Server-Unit1.cpp *************** // Requires: TServerSocket, TMemo1, Tmemo2, //Button1-ServerOpen, Button2-Exit #include #pragma hdrstop #include "Unit1.h" 108 #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //-------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } const int WAITTIME = 60000;//Будем ждать клиента 60с int k=0; const int BUFSIZE = 32;//Размер буфера чтения/записи /*Вместо компоненты клиентского сокета поток сервер-клиент должен использовать объект ClientSocket класса TServerClientWinSocket, который создаётся, когда ожи- дающий сокет сервера выполнит клиентское соединение.*/ //Перегрузка метода ClientExecute() класса TServerClientThread void __fastcall TMyServerThread::ClientExecute(void) { // убеждаемся, что соединение активно while (!Terminated && ClientSocket->Connected){ try {//используем TWinSocketStream для чтения/записи через // блокирующий сокет TWinSocketStream *pStream=new TWinSocketStream(ClientSocket, WAITTIME); try { char buffer[BUFSIZE]; memset( buffer, 0, sizeof(buffer) ); if (pStream->WaitForData(WAITTIME)) { // даём клиенту 60с для начала записи if (pStream->Read(buffer, sizeof(buffer)) == 0) ClientSocket->Close(); //если не удаётся прочитать через 60с, закрываем соединение else { //помещение текста клиента в Memo1 cервера Form1->Memo1->Lines->Add(String("(Client) ")+String(buffer) ); strcpy(buffer,Form1- >Memo2->Text.c_str());//из Memo2 в buffer pStream->Write( buffer, sizeof(buffer));}//Возвращаем буфер клиенту } else ClientSocket->Close();//если не передается текст } __finally { delete pStream; } } /* Для обработки исключений используется HandleException(). */ catch (...) { HandleException(); } } } void __fastcall TForm1::ServerSocket1GetThread(TObject *Sender, TServerClientWinSocket *ClientSocket, TServerClientThread *&SocketThread) { //событие создает поток при открытии нового клиентского сокета //метод возвращает поток через SocketThread параметр // Рекомендуется использовать Thread-bloking сервер SocketThread = new TMyServerThread(false, ClientSocket); 109 k++; Memo1->Clear(); Memo1->Text="Открыт клиент:" +IntToStr(k); } void __fastcall TForm1::Button1Click(TObject *Sender) { ServerSocket1->Close(); ServerSocket1->Open(); } void __fastcall TForm1::Button2Click(TObject *Sender) { Close(); } //Создание клиента------------------------------------------------- #ifndef Unit2H #define Unit2H #include #include #include #include #include //--------------------------------------------------------------------------- // ****************** ClientMain.h ****************** class TForm2 : public TForm { __published: // IDE-managed Components TClientSocket *ClientSocket1; TButton *Button1; TButton *Button2; TMemo *Memo1; TButton *Button3; TLabel *Label1; TMemo *Memo2; TLabel *Label2; void __fastcall Button1Click(TObject *Sender); void __fastcall Button2Click(TObject *Sender); void __fastcall ClientSocket1Read(TObject *Sender, TCustomWinSocket *Socket); void __fastcall ClientSocket1Write(TObject *Sender, TCustomWinSocket *Socket); void __fastcall Button3Click(TObject *Sender); private: // User declarations AnsiString Server; public: // User declarations __fastcall TForm2(TComponent* Owner); }; extern PACKAGE TForm2 *Form2; 110 //--------------------------------------------------------------------------- #endif //Реализация клиента-------------------------------------------------- // ***************** Client-Unit2.cpp ***************** // Requires: 2 TButtons, 2 TMemo, TClientSocket //--------------------------------------------------------------------------- #include #pragma hdrstop #include "Unit2.h" #pragma package(smart_init) #pragma resource "*.dfm" TForm2 *Form2; //--------------------------------------------------------------------------- __fastcall TForm2::TForm2(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm2::Button1Click(TObject *Sender) { if (ClientSocket1->Active) ClientSocket1->Active = false; if (InputQuery("Computer to connect to", "Address Name:", Server)) { if (Server.Length() > 0) { ClientSocket1->Host = Server; ClientSocket1->Active = true; } } } void __fastcall TForm2::Button2Click(TObject *Sender) { ClientSocket1->Active = false; } void __fastcall TForm2::ClientSocket1Read(TObject *Sender, TCustomWinSocket *Socket) { if (ClientSocket1->Active == true) if (Socket->Connected == true) Memo1->Lines->Add( AnsiString("(Server) ") +Socket->ReceiveText() ); } void __fastcall TForm2::ClientSocket1Write(TObject *Sender, TCustomWinSocket *Socket) { if (ClientSocket1->Active == true) if (Socket->Connected == true) Socket->SendText("This text is passed to"+Memo2->Text); } void __fastcall TForm2::Button3Click(TObject *Sender) { Close(); } |