Голосование

Собираетесь ли Вы поменять работу в ближайшее время?

Не знаю
Еще поработаю
Никогда не поменяю
Собираюсь (полгода)
Собираюсь (год)
Уже поменял



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

 

Лента 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-2018 "DS"

Отправить письмо / Реклама


ВКонтакте   Facebook   Twitter