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

БГУ Пособие - Программирование в C++ Builder

.pdf
Скачиваний:
585
Добавлен:
11.06.2015
Размер:
1 Мб
Скачать
☆

указывается параметром 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 аналогична работе клиента(

101

методы 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, LocalHost, LocalPort – соответственно, локальный IP-адрес, хост-имя, порт; RemoteAddress, RemoteHost, RemotePort – соответственно, удаленный IP-

102

адрес, хост-имя, порт; методы Lock и UnLock – соответственно, блокировка и разблокировка сокета.

 

Таблица 19

Свойства

События

 

Socket – класс TServerWinSocket, через

OnClientConnect – возникает, когда кли-

 

который вы имеете доступ к открытым

ент установил сокетное соединение и

 

сокетным каналам.

ждет ответа сервера.

 

ServerType – тип сервера.

OnClientDisconnect – возникает, когда

 

ThreadCacheSize – количество клиент-

клиент отсоединился .

 

ских процессов (Thread), которые будут

OnClientError – возникает, когда опе-

 

кэшироваться сервером. Кэширование

рация завершилась неудачно.

 

происходит для того, чтобы не созда-

OnClientRead – возникает, когда клиент

 

вать каждый раз отдельный процесс и

передал серверу данные.

 

не уничтожать закрытый сокет, а оста-

OnClientWrite – возникает, когда сервер

 

вить их для дальнейшего использова-

может отправлять данные клиенту по со-

 

ния. Тип: int.

кету.

 

Active –Значение True указывает на то,

OnGetSocket – в обработчике этого со-

 

что сервер работает и готов к приему

бытия можно отредактировать параметр

 

клиентов, а False – сервер выключен.

ClientSocket.

 

Port – номер порта для установления

OnGetThread – в обработчике события

 

соединений. Порт у сервера и у клиен-

можно определить уникальный процесс

 

тов должны быть одинаковыми. Реко-

(Thread) для каждого клиента канала,

 

мендуются значения от 1025 до 65535.

присвоив параметру SocketThread нуж-

 

Service – строка, определяющая службу

ную подзадачу TServerClientThread.

 

(ftp, http, pop и т.д.), порт которой будет

OnThreadStart, OnThreadEnd – возни-

 

использован. Тип: String.

кает, когда процесс Thread запускается

 

 

или останавливается.

 

 

OnAccept – возникает, когда сервер при-

 

 

нимает клиента или отказывает ему в со-

 

 

единении.

 

 

OnListen – возникает, когда сервер пере-

 

 

ходит в режим ожидания подсоединения

 

 

клиентов.

 

Пример. Создадим приложение-сервер. На форму нужно поместить кнопки Button1 и Button2, поле Edit1 и компоненты Memo1 и Memo2.

При создании формы вызывается обработчик события OnCreate (FormCreate), в котором активизируется сервер. Свойство компонента TServer- Socket->Active устанавливается в true, что означает, что сокетное соединение открыто и доступно для коммуникации с клиентами. В компоненте Edit1 указано значение прослушиваемого порта. Когда ServerSocket1 должен записать информацию в ClientSocket1, возникает событие OnClientWrite и вызывается функция ServerSocketClientWrite(). На рис. 25 при-

ведена форма приложения в процессе проектирования.

103

Рис. 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)

{

104

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().

105

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

//-- Клиент -------------------------------------------------------------------

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 <Classes.hpp>

#include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <ScktComp.hpp>

106

//---------------------------------------------------------------------------

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 <vcl.h> #pragma hdrstop #include "Unit1.h"

107

#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);

108

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 <Classes.hpp>

 

 

#include <Controls.hpp>

 

 

#include <StdCtrls.hpp>

 

 

#include <Forms.hpp>

 

 

#include <ScktComp.hpp>

 

 

//---------------------------------------------------------------------------

 

 

// ******************

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;

109

//---------------------------------------------------------------------------

#endif

//Реализация клиента--------------------------------------------------

 

 

// *****************

Client-Unit2.cpp

*****************

// Requires: 2 TButtons, 2 TMemo, TClientSocket

//---------------------------------------------------------------------------

 

 

#include <vcl.h>

 

 

#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();

}

110