Голосование

Какую ОС Вы используете?

Windows 98/ME
Windows NT
Windows 2000
Windows XP
Windows 2003
Windows Vista
Windows 7
Windows 8
Windows 10
Linux
Другая ОС



Посмотреть результаты
Другие опросы ...

 

Лента RSS, новости сайта Новости сайта
Лента RSS, новости форума Новости форума
  Bookmark and Share

Архив исходников

   
  Базы данных
  Графика & Мультимедиа
  Сети & Интернет
  Система
  Разное
   

Кнопки, Ссылки и Баннеры ...

 


Automatic translation


English German French Italian Spanish
Portuguese Greece Japan Chinese Korean


Ссылки и Баннеры


скрыть

 

Delphi Sources

Delphi Sources

СТАТЬИ

 

. : Отправка файла через неблокирующий сокет : .

 

Приветствую!

Я хотел поделиться своим опытом и написать об отправке файла через неблокирующий сокет от сервера к клиенту. "А что тут писать?" – спросите вы и будите совершенно правы и неправы одновременно. У людей, которые давно занимаются программированием, данная статья вызовет лишь легкую улыбку, но тем, кто впервые пытается разобраться в технологии сокетов она, возможно, поможет найти ответы на их вопросы.

Отправка файла через неблокирующий сокет

В интернете есть множество статей описывающих технологию отправки и приемки файла, в том числе и на данном сайте. Но все эти статьи описывают прием лишь одного кусочка данных или сообщения, пришедшего к клиенту и последующую обработку этого блока в процедуре ClientSocket1Read / ServerSocket1ClientRead. Но мне же нужно было отправить файл, в идеале любого размера и все это сопроводить анимацией прогресс бара. Следовательно, методы sendtext и sendstream не годились, т.к. они не дают возможности визуализировать процесс отправки файла по кусочкам.

В первых статьях по сокетам, что мне попались в поисковиках, использовался для отправки и приема данных класс TmemoryStream. Со стороны сервера отправка проходила гладко и без заморочек, прогресс бар не зависал и выдавал равномерное приращение полосочки с процентами. На клиенте творилось нечто мистическое – при передаче тестового файла 104 Мб примерно с 20% полоса прогресса начинала замедляться и еще секунд через 5 выскакивала ошибка StackOverflow. Тут можно долго рассуждать где была ошибка: я виноват, либо класс кривой. Исходя из того, что почти все примеры в сети одинаковые и технология приема данных на клиентской стороне не отличается кардинально ни чем кроме имен переменных я решил поменять класс TmemoryStream на другой.

Другим классом оказался TfileStream и он уже заработал как часы. После использования этого класса все нестыковки в передаче данных ушли в небытие. Именно используя этот класс у меня получилось передать тестовый файл от сервера к клиенту. Ниже я рассмотрю лишь функцию отправки и приема файла. Остальное, довольно подробно прокомментировано в исходнике.

Сервер
 


//посылка файла через сокет
procedure TForm1.SendFileSocket(fName: string);
var
nSend : Integer;
sBuf : Pointer;
begin
try
// открытие файла для чтения и последующей отправки
fs := TFileStream.Create(edt1.Text, fmOpenRead);
// курсор на начальную позицию, с которой нужно слать файл
fs.Position := 0;

repeat
// выделение памяти под считываемые данные
GetMem(sBuf, bSize + 1);
// чтение куска данных (bSize) из файла
nSend := fs.Read(sBuf^, bSize);
// если что то прочиталось, то отправляем клиенту
if nSend > 0 then
begin
ServerSocket1.Socket.Connections[0].SendBuf(sBuf^, nSend);
// корректировка значений прогрес бара
Progress(fs.Position, fs.Size);
// задержка иначе будут потери пакетов
Sleep(SleepTime);
end;
// освобождение участка памяти
FreeMem(sBuf);
Application.ProcessMessages;
until nSend <= 0; // цикл выполняется пока хоть
// 1 байт будет прочитан из потока fs
finally
if Assigned(fs) then fs.Free;
end;
end;


Со стороны сервера отправку организовал в 1 процедуру SendFileSocket(fName: string).
Первое что нужно сделать, это создать экземпляр класса TfileStream для чтения файла и установить курсор в начало. Далее необходимо выделить память под данные, которые будут отправляться кусками. В моем случае использовался блок bSize размером в 4000 байт. Я, если честно, до сих пор не уловил для чего нужно выделять память на 1 байт больше:

 


GetMem(sBuf, bSize + 1);


но думаю, что туда залетит символ окончания строки #0, чтобы при освобождении памяти система знала, где заканчивается строка по адресу sBuf.

Следующее действие это чтение кусочка данных из тестового файла.

 


nSend := fs.Read(sBuf^, bSize);


Тут следует обратить внимание, что в функции GetMem используется указатель sBuf без «птички» ^ , но в функции fs.Read указатель уже пишется как sBuf^. Почему так? Описывать это я не буду, т. к. те, кто действительно хотят в этом разобраться, следует почитать основы Delphi, а те, кто просто ищут кусок кода для своей программы все равно не запомнят объяснения. Чтобы не путаться в том, где ставить птичку, а где нет, могу сказать только следующее: если параметр передается как указатель, то переменная-указатель пишется без птички. Но если параметр функции есть сама область памяти, на которую ссылается указатель, то тогда нужно ставить символ ^.

 


if nSend > 0 then
begin
ServerSocket1.Socket.Connections[0].SendBuf(sBuf^, nSend);

Если было считано хоть что то в буфер sBuf, то можно это «хоть что то» отправить клиенту. В моем случае тестирование проводилось с 1 клиентом, который селился в ServerSocket1.Socket.Connections[0] , но вы можете организовать, например, отправку файла всем клиентам в цикле. После того, как отправили данные клиенту, необходимо это дело визуализировать на экране:

 


Progress(fs.Position, fs.Size);
// задержка иначе будут потери пакетов
Sleep(SleepTime);


Тут я использую задержку, т.к. при отсутствии оной бывают ситуации потери пакетов. Например на сервере ушло 100% данных, а на клиенте пришло только 99%. В этих случаях необходимо дописывать контроль целостности передаваемых данных, но это уже совсем другая история.

 


FreeMem(sBuf);


После отправки куска файла необходимо освободить память функцией FreeMem. Возможно, что можно выделить память всего лишь раз до цикла отправки и освободить ее уже после выполнения цикла, но это уже вам для раздумий.

Клиент
 


procedure TForm1.ClientSocket1Read(Sender: TObject;
// Socket: TCustomWinSocket);
var
nRead : Integer;
rBuf : Pointer;
begin
...
else // режим получения файла
begin
repeat
Socket.Lock;
// выделение памяти под принятый кусок данных
GetMem(rBuf, bSize + 1);
// считывание данных nRead = количество считанных байт
nRead := Socket.ReceiveBuf(rBuf^, bSize);
// если что то считалось, то запись данных в файл
if nRead > 0 then
begin
fs.WriteBuffer(rBuf^, nRead);
Gauge1.Progress := fs.Size;
end;
FreeMem(rBuf);
Socket.Unlock;
Application.ProcessMessages;
until (nRead <= 0);
// если все данные считались, то переключение режима
// приема обратно и освобождение переменной потока
if fs.Size = fSize then
begin
Receiving := False;
fs.Free;
Jornal('Файл принят!', Unassigned, clGreen);
end;
end;
end;


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

 


nRead := Socket.ReceiveBuf(rBuf^, bSize);
// если что то считалось, то запись данных в файл
if nRead > 0 then
begin
fs.WriteBuffer(rBuf^, nRead);


Если считывается хотябы 1 байт, то пишем его в файловый поток (в файл).


Как сказано в руководствах – при передаче кусков данных через сокет возможны ситуации, когда кусок данных придет в неизменном виде, а возможно и его фрагментирование либо образование большего фрагмента данных. Т.е. если мы ожидаем на входе блок размером bSize = 4000 байт, то на практике может прилететь 4500 байт. Я проводил опыты с выводом размера принятого фрагмента в журнал (RichEdit) и оказывалось, что при жестко заданном размере в 8000 байт после нескольких секунд отправки данных он (размер) мог поменять на 8189 байт. Поэтому, чтобы не ориентироваться на фиксированный размер блока, считывание происходит в цикле.

 


Repeat
...
until (nRead <= 0);

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


Как только размер потока fs станет равным присланному до начала отправки размеру файла fSize, то это означает, что мы получили весь файл и можно его сохранить, освободив затем поток.


f fs.Size = fSize then
begin
Receiving := False;
fs.Free;
Jornal('Файл принят!', Unassigned, clGreen);
end;

Как переслать размер файла до начала отправки всего файла? Можно это сделать обычной функцией sendtext – например SendText(ExtractFileName(YourFileName) + '#' + YourFuncFileSize(YourFileName)) либо посмотрите как это сделано в данной программе. Я использовал класс TstringList для передачи команд между клиентом и сервером. Да, это не очень красиво, но я ставил перед собой задачу максимально быстро разобраться в технологии приема/передачи файла.


На этом все.
Удачи в этом интересном и развивающем мозг деле!

   

Замечания и вопросы по статье отсылайте на Crusl@mail.ru.
 

Исходный код и оригинал статьи: client-server-socket.zip (17 Кб).

Дата: 27.01.2014, Автор: Владимир.






Назад

   

 































































































































































































 

© 2004-2016 "DS"

E-mail: Отправить письмо


ВКонтакте   Twitter   Facebook