Sources.RU Magazine Поиск по журналу
 

Ссылки

Ищите видеокамеры супер наружного наблюдения? Купите видеокамеры наружного наблюдения
Небольшие расценки. Качественный ремонт квартир нашими мастерами.
Привезем кресло кровать магазин нашим клиентам.

Как передавать файлы через TClientSocket и TServerSocket

Автор: DeVoid

Очень часто возникают вопросы по работе с сокетами, а толкового описания работы с ними нету. Максимум что можно найти в Интернете - исходники чата и то для Delphi. Поэтому, чтобы понять принцип работы компонентов TClientSocket и TServerSocket в С++, предлагаю написать программу для обмена файлами.

В общих чертах передача файлов через сокеты выглядит следующим образом: вся информация передается пакетами, и если в одном из пакетов встречается #file - это значит, что пришел заголовок файла с последующей информацией о нем (имя, размер) и клиент должен принимать файл указанного размера. В чистом виде заголовок файла выглядит так: file#filename#filesize#. Когда клиент принимает такой заголовок, он обрабатывает его(выделяет имя файла и его размер), создает буфер размером filesize и в него пишет всю последующую информацию. Когда размер переданной информации равен размеру файла, посылает на сервер команду "end", сервер обрабатывает эту команду и закрывает поток.

Итак, начнем мы с того, что определимся, кто будет посылать файл, а кто принимать. В моем примере - Сервер отправляет файл, а клиент принимает, все просто, ничего сложного здесь нету. Дальше нужно оформить внешний вид клиента и сервера. У меня они выглядят так:

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

Сейчас будем писать сервер - он будет отправлять файлы.

Сервер:

На форме: OpenDialog1 (TOpenDialog), Server (TServerSocket), Memo1 (TMemo) и две кнопки.

Сначала настроим Server:

Port = 1001 ; // Порт по которому будет работать и клиент и сервер
Active = false ; // Пока неактивен

Эти настройки можно выбрать в Object Inspector'е.

Перейдем на вкладку Events и опишем подключение и отключение клиента:

В OnAccept:

Memo1->Lines->Add("К Вам подключились ;");

Для OnError:

ErrorCode = 0 ;
ShowMessage("Server Error");

Теперь напишем обработчик для нажатия кнопки запуска сервера:

Server->Active = true ;
Server->Open() ;
Memo1->Lines->Add("Создан сервер.");

Теперь нужно создать поток, который мы и будем передавать клиенту:

TMemoryStream *MS = new TMemoryStream ;

А теперь подходит время к самому главному в описании сервера - передачи файла клиенту. Пишем в обработчике кнопки Send (Отправить файл):

void *P;   // указатель на файл
int Size ; // размер
if( OpenDialog1->Execute() )
{
	MS->LoadFromFile( OpenDialog1->FileName ); // выбираем  файл 
	Memo1->Lines->Add( "Загрузили требуемый файл в поток..." ); // заполняем лог 
}
Server->Socket->Connections[0]->SendText( "file#" + OpenDialog1->FileName + "#" + IntToStr( MS->Size ) + "#" ); 
// отправляем заголовок

Memo1->Lines->Add ( "Послали заголовок" );
MS->Position = 0 ;      // Устанавливаем поток в начальную позицию ;
P    = MS->Memory ;     // присваиваем указателю поток файла
Size = Server->Socket->Connections[0]->SendBuf( P , MS->Size );               // отправляем буфер клиенту; Size
                                                                              //равно размеру отправленной  информации
Memo1->Lines->Add( "Отправлено: " + IntToStr( Size ) + " из " + IntToStr( MS->Size ) ); // заполняем лог

С отправкой все в порядке, но серверу еще необходимо обработать команду "end", которая придет тогда, когда клиент примет файл. Для этого в OnClientRead пишем:

if(Server->Socket->Connections[0]->ReceiveText()=="end") // если клиент прислал команду "end"
{
	Memo1->Lines->Add("Клиент принял файл"); // записываем в лог
	MS->Clear() ;                            // Очищаем поток
}

С сервером и отправкой файла все готово, дальше нужно написать клиента, который бы принимал поток пакетов от сервера. Чем мы и займемся.

Клиент:

Для программы-клиента основной задачей является получение информации о файле, который передает клиент и получение, и сохранение самого файла. Итак, на форме - Client (TClientSocket) , Memo1 (TMemo), SaveDialog1 (TSaveDialog) и кнопка соединения - Button1.

Снова начнем с того, что настроим Client (TClientSocket1):

Port = 1001 ;         // Клиент и сервер должны работать на одинаковых портах 
Active = false ;      // Пока неактивен
Address = 127.0.0.1 ; // Адрес укажем пока свой, что б протестировать работу сервера и клиента локально
Host = 127.0.01 ;

Далее, объявим переменные, которые нам будут необходимы:

В *.h-файле проекта, в секции private объявим:

private:	
	AnsiString Name; 

После этого в *.cpp файле объявляем:

TMemoryStream *MS = new TMemoryStream ; // создаем поток под принимаемый файл
void Write( AnsiString Text );          // ф-я записи получаемой информации в поток
int Size ;                              // размер передаваемого файла
bool Receive ;                          // передаем ли мы на данный момент файл
AnsiString FileName ;                   //  имя файла

Следующим шагом создания клиента - будет описание функции Write. Она должна сохранять получаемую информацию в файл.

void Write( AnsiString Text )
{
	if(MS->Size < Size)  // если мы еще принимаем файл и размер потока меньше размера файла
	{
		MS->Write( Text.c_str() , Text.Length() );         // записываем в поток
		Form1->Memo1->Lines->Add( "Принимаем данные..." ); // пишем лог
	}
	if(MS->Size == Size) // если файл принят и размер потока соответствует размеру файла
	{
		Receive = false ;                         // останавливаем режим передачи
		MS->Position = 0 ;                        // переводим каретку потока в начало
		Form1->Client->Socket->SendText( "end" ); // пишем серверу, что мы приняли файл
		CreateDir( "Downloads" );                 // создаем папку для сохраненных файлов
		MS->SaveToFile( "Downloads\"+FileName ); // сохраняем туда наш файл
		MS->Clear() ;                             // освобождаем поток
		Size = 0 ;
		Form1->Memo1->Lines->Add("Файл принят !"); // пишем в лог что файл принят
	}
}

Далее, важно еще правильно описать событие OnRead, вот как оно должен выглядеть:

void __fastcall TForm1::ClientRead( TObject *Sender,
      TCustomWinSocket *Socket )
{
	AnsiString Rtext ;  // текст, который посылает сервер
	Rtext = Client->Socket->ReceiveText() ;
	if( Receive == true ) // если мы в режиме передачи файла, то
	{
		Write( Rtext ); // записываем его в поток
	}
	else // если нет , то
	{
		Memo1->Lines->Add( "Приняли текст :" + Rtext );     // пишем в лог все что принимаем от сервера
		if(Rtext.SubString( 0,Rtext.Pos("#")-1) == "file" ) // Если это строка типа 
		// file#filename#filesize#, то начинаем парсерить полученную информацию :
		{
			Rtext.Delete( 1 , Rtext.Pos( "#" ) ) ;            // удаляем слово file
			Name = Rtext.SubString( 0 , Rtext.Pos( "#" ) -1 );// Определяем имя файла
			FileName = Name.SubString( Name.LastDelimiter( "\" ) + 1 , Name.Length() );
			// Выделяем чистое имя файла , например с c:\test.txt , берем test.txt
			Rtext.Delete( 1 , Rtext.Pos( "#" ) );                               // Удаляем последний разделитель
			Size = StrToInt( Rtext.SubString( 0 , Rtext.Pos( "#" ) - 1) ) ;     // Определяем размер файла
			Rtext.Delete( 1 , Rtext.Pos( "#" ) );                               // Удаляем последний разделитель
			Memo1->Lines->Add( "Размер файла: " + IntToStr( Size ) + " байт" ); // Выводим размер файла в лог
			Memo1->Lines->Add( "Имя файла: " + Name );                          // Выводим имя файла в лог
			Receive = true; 
			// Переводим сервер в режим приёма файла
			
		}
	}
}

Все самое страшное позади, и теперь осталось только описать события OnConnect и OnError:

void __fastcall TForm1::ClientConnect( TObject *Sender,
      TCustomWinSocket *Socket )
{
	Memo1->Lines->Add( "Вы присоеденились ;" );
}

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

void __fastcall TForm1::ClientError( TObject *Sender,
      TCustomWinSocket *Socket, TErrorEvent ErrorEvent, int &ErrorCode )
{
	ErrorCode = 0;
	ShowMessage( "Client Error" );        
}

А так же написать обработчик для кнопки соединения:

void __fastcall TForm1::Button1Click( TObject *Sender )
{
	Client->Open() ;  // открываем 
	Memo1->Lines->Add( "Коннектимся..." );
}

Вот и все готово, теперь можно протестировать, что же у нас получилось. Все принятые файлы клиент сохранят в папку Downloads.

Статью написал DeVoid © 25.10.04


Скачать исходники:



 Desingn by Шишкин Алексей (Лёха).
 ©2004-2008 by sources.ru.