Вверх ↑
Этот топик читают: Гость
Ответов: 48
Рейтинг: 0
#1: 2014-08-07 20:16:07 ЛС | профиль | цитата
Появилась необходимость в передачи файлов по локальной сети, с одного компьютера на несколько других. Собрал вот такую схему code_34122.txt
Только ни как не могу понять как после приема данных из порта сохранить полученный файл на компьютер. Файлы планирую передавать разных форматов и размеров, например txt, exe, dll. Может кто то подобное уже делал? И с помощью каких компонентов в hiasm это лучше реализовывать?
карма: 0

0
файлы: 1code_34122.txt [2KB] [342]
Ответов: 4631
Рейтинг: 749
#2: 2014-08-07 21:21:45 ЛС | профиль | цитата
Как мы уже обсуждали, протокол TCP и существующие у нас компоненты не предусматривают простой передачи файлов. Требуется протокол более высокого уровня.
Могу предложить такой вариант. На управляющем компьютере настраиваешь веб-сервер (Apache). Размещаешь нужные файлы для доступа из сети. Клиентам отправляешь простые команды "скачать файл по такому-то адресу". Скачиваешь файлы компонентом HTTP_Get.

[offtop]В качестве анонса. Планирую сделать компонент на базе самодельного протокола, который будет иметь возможность передавать файлы и строки по сети с полезными возможностями (типа предупреждение другой стороны о начале/конце передачи, размере данных, возможности прерывать передачу).
При этом хочу реализовать некий "стек протоколов": когда один компонент (протокол) является транспортом для другого. Скажем этот компонент отправки файлов использует в качестве транспорта TCP. Можно заменить транспорт, например, на MailSlot. Можно между ними вклинить какой-нибудь другой протокол, например, шифрование SSL[/offtop]
карма: 26

0
Разработчик
Ответов: 4698
Рейтинг: 426
#3: 2014-08-07 22:13:53 ЛС | профиль | цитата
Netspirit писал(а):
Как мы уже обсуждали, протокол TCP и существующие у нас компоненты не предусматривают простой передачи файлов.

Немного не понял: почему? Считал аттрибуты, передал аттрибуты, на той стороне создал файл с такими аттрибутами, взял содержимое файла, передал на ту сторону и записал там на диск. А если атрибуты не важны, так тогда вообще в три шага - считал, передал, записал. Чем TCP не годится то?
карма: 10
0
Ответов: 4631
Рейтинг: 749
#4: 2014-08-07 22:29:27 ЛС | профиль | цитата
Я, например, не представляю, как это сделать в режиме DataType=dtStream. Как это можно сделать в режиме dtString я описывал в теме о новых TCP компонентах.

Основная сложность - отслеживание начала-конца данных.
То-есть: шлешь, например, команду "отправляю файл такого-то размера" ("атрибуты"). Затем начинаешь отправлять файл. На принимающей стороне происходит серия событий onRead. Причем, эти события выдают данные определенными порциями, размер которых нельзя контролировать. Принимающая сторона получает первую порцию, которая содержит "команду"+первую часть файла (нет гарантии, что 2 вызова doSend дадут два события onRead). Значит, уже нужно как-то отделять команду от собственно файла. Например, разделителем, или фиксированной длиной команды.

Соответственно, в обработчике onRead нужен сложный накопитель: накапливаем данные, пока не получим разделитель. Парсим команду. Переключаем ветку накопителя, чтобы он последующие принятые данные считал содержимым файла и сохранял на диск. Как только весь файл указанного размера получен, нужно переключить накопитель опять на ожидание команды.

Если я не ошибаюсь, то реализация этого на дискретных компонентах весьма громоздкая.
Опять же, существующий DataToFile не приспособлен для чтения-записи файлов порциями. И если для отправки можно использовать StreamToString, то прием - с костылями.

У кого есть другие теоретические/практические соображения - выкладывайте.
карма: 26

0
Разработчик
Ответов: 4698
Рейтинг: 426
#5: 2014-08-07 23:13:23 ЛС | профиль | цитата
На dtStream:
code_34123.txt
Скрин схемы
Для теста надо запустить два экземпляра, на одном нажать Open server, на другом Select file
------------ Дoбавленo в 23.13:
Можно еще и сразу целиком файл в StreamPack отправлять (тогда схема совсем простая), но это не есть хорошо, файлы могут быть и больше нескольких мегабайт.
карма: 10
0
файлы: 1code_34123.txt [4KB] [331]
Ответов: 48
Рейтинг: 0
#6: 2014-08-07 23:44:56 ЛС | профиль | цитата
Assasin Спасибо большое за наглядный пример. Испытал в действии, работает отлично. Единственное, отправлял файл в 9мб. первый раз выбило с ошибкой, второй раз передал, но если сравнивать по свойствам файла то размер у переданного немного меньше чем у оригинала
карма: 0

0
Разработчик
Ответов: 4698
Рейтинг: 426
#7: 2014-08-08 01:09:44 ЛС | профиль | цитата
ruin писал(а):
Единственное, отправлял файл в 9мб. первый раз выбило с ошибкой, второй раз передал, но если сравнивать по свойствам файла то размер у переданного немного меньше чем у оригинала

Вот это странно. На сколько точно отличаются размеры? Я тестировал на файлах 2МБ
карма: 10
0
Ответов: 4631
Рейтинг: 749
#8: 2014-08-08 09:57:09 ЛС | профиль | цитата
Глянул коды. Как я понял, при dtStream, первые 4 байта указывают на размер передаваемого файла.
Ну, тогда можно использовать такой подход. Код чтения в сервере отличается от кода в клиенте, может из-за этого ошибка.

Assasin, не совсем понял, зачем в твоей схеме три связи на отправку. Как мне кажется, нужно сначала сформировать поток в памяти с упаковкой всех необходимых атрибутов, затем один раз выполнить doSend.
карма: 26

0
Разработчик
Ответов: 26170
Рейтинг: 2127
#9: 2014-08-08 10:59:15 ЛС | профиль | цитата
Netspirit писал(а):
нужно сначала сформировать поток в памяти

А тебе не кажется, что размер памяти ограничивает размер передаваемого файла
карма: 22

0
Разработчик
Ответов: 4698
Рейтинг: 426
#10: 2014-08-08 13:14:01 ЛС | профиль | цитата
Netspirit писал(а):
Assasin, не совсем понял, зачем в твоей схеме три связи на отправку. Как мне кажется, нужно сначала сформировать поток в памяти с упаковкой всех необходимых атрибутов, затем один раз выполнить doSend.

Netspirit, я использую что-то типа самопального протокола:
1. Сначала отправляется пакет с id = 0 и именем файла (сюда же можно атрибуты)
2. Отправляются куча последовательных пакетов с id = 1, куском данных (4096 байт или меньше) и размером переданного куска (чтобы если до конца файла меньше 4кб, то принимающая сторона не заполнила нулями остаток файла у себя)
3. Отправляется пакет с id = 2, означающий конец передачи файла.
Netspirit писал(а):
Глянул коды. Как я понял, при dtStream, первые 4 байта указывают на размер передаваемого файла.

Да, только не файла, а потока данных. Передавать можно что угодно, но вот этот тип dtStream - это уже можно сказать протокол поверх TCP.

Вообще как сейчас сделаны компоненты для работы по протоколу TCP мне не очень нравится: различные типы передачи данных (dtString, dtStream, dtInteger) мне кажутся больше похожими на костыли. Я думаю, не стоило делать еще один слой абстракции, а сделать просто три компонента: TCP_Client, TCP_Server (лучше реализацией контейнера, как в TCP_ServerEx), StreamRW. Первые два просто бы работали чисто с протоколом TCP и предоставляли пользователю по нижней точки безразмерный поток данных (Stream), с которым уже можно работать с помощью StreamRW (что-то вроде DataToFile, только для безразмерных потоков данных). Как раз StreamRW и должен был бы считывать/записывать различные типы данных (строки, штатные Stream, числа и т.д.).
------------ Дoбавленo в 13.14:
Интересные там в клиенте строчки кстати:
#pas
0: _hi_OnEvent(_event_onRead,integer(buf^));
2: _hi_OnEvent(_event_onRead,real(buf^));
Это получается, что если мы принимаем integer или real (тип передачи - dtInteger, dtReal), то если нам придет больше одного значения за раз, то остальные мы потеряем. Вот и доверяй после этого компонентам
карма: 10
0
Ответов: 4631
Рейтинг: 749
#11: 2014-08-08 15:27:27 ЛС | профиль | цитата
nesco писал(а):
А тебе не кажется, что размер памяти ограничивает размер передаваемого файла?
Да, ограничивает. Но там есть ещё концептуальные ошибки:
- во-первых, идет отправка только из st.Memory, следовательно поддерживается только MemoryStream;
- во-вторых, весь объем отправляется за раз, а в TSocket.Send нет разбиения на меньшие порции. Это приводит к тому, что если MemoryStream и может иметь размер несколько сотен Мб, то передать можно не больше нескольких мегабайт (или десятков Мб) - у сокета есть свой буфер, переполнение которого даст ошибку отправки.

Assasin писал(а):
предоставляли пользователю по нижней точки безразмерный поток данных (Stream), с которым уже можно работать с помощью StreamRW
У меня уже есть такой компонент, я потому и вывел точку Socket у новых TCP компонентов. Вот только следует учитывать как-бы две "парадигмы" работы с сетью:
- полностью "асинхронный" режим, когда ожидание и отправка данных "независимы", как сделано у нас благодаря событию onRead. Удобно применять в программах типа чатов, которые держат постоянное соединение, чтобы получать уведомления с серверов.
- "диалоговый" режим, когда программа работает в режиме запрос-ответ: одна сторона отправляет данные, затем переключается в ожидание ответа. И принятые данные будут интерпретированы в зависимости от последней посланной команды. Так работают протоколы типа HTTP, FTP, SMTP. Вот этот режим удобно реализовать компонентом SocketRW.
Если же приём будет вестись, как у нас, через событие onRead, то компонент SocketRW можно будет применять только для отправки данных.

Assasin писал(а):
я использую что-то типа самопального протокола
Вот я и хочу сделать отдельный компонент для подобного протокола, чтобы не приходилось всё это в схеме городить. Примерно:

doSendString
doSendStringAsync
doSendFile
doSendFileAsync
doSendStream
doSendStreamAsync
doAbortSend
doRejectRecv

onSend
onSendAborted - передача отменена другой стороной
onBeforeString - будет принята строка. Точка Length Содержит длину строки. Можно отказаться от приема через doRejectRecv
onBeforeFile - аналогично onBeforeString, можно указать имя для сохранения. Можно предусмотреть получение оригинального имени.
onBeforeStream - аналогично onBeforeString
onString - выдаёт принятую строку
onFile - происходит после получения файла
onStream
onErrorSend
onErrorProto - неправильные ответы другой стороны

Данные:
Stream - поток, куда будет записан принятый поток.
OutFileName - имя файла для сохранения.

Свойства:
Length - длина принимаемых данных


Assasin писал(а):
Это получается, что если мы принимаем integer или real
А поскольку тип данных мы не можем изменить в рантайм, то что ж это за программа, которая обменивается одними Интегерами

Assasin писал(а):
если нам придет больше одного значения за раз, то остальные мы потеряем
А case _prop_DataType of нужно ставить внутрь цикла
while count > 0 do
begin

end;

карма: 26

0
Разработчик
Ответов: 4698
Рейтинг: 426
#12: 2014-08-08 17:12:15 ЛС | профиль | цитата
Netspirit писал(а):
А case _prop_DataType of нужно ставить внутрь цикла

Не, не поможет: придет нам буфер размером 7 байт, а мы работаем в режиме dtInteger - первый раз считаем нормально, а второй раз попадется один мусорный байт (если вообще ошибка доступа к памяти не возникнет).
карма: 10
0
Ответов: 48
Рейтинг: 0
#13: 2014-08-08 18:25:50 ЛС | профиль | цитата
Assasin писал(а):
Вот это странно. На сколько точно отличаются размеры? Я тестировал на файлах 2МБ
Для убедительности проверил несколько раз, разница до и после передачи видна на скриншотеhttps://yadi.sk/i/WvSZFgJwZJ5Lt
карма: 0

0
Ответов: 4631
Рейтинг: 749
#14: 2014-08-08 19:38:30 ЛС | профиль | цитата
Assasin писал(а):
Не, не поможет
Ну, как бы подразумевались соответствующие проверки. А раз предполагаем, что недостающая часть может прийти в следующем фрагменте, приходим к тому, что нужен некий потоковый накопитель, роль которого при dtStream исполняет MemoryStream.

Я даже сделал соответствующий компонент, чтобы обрабатывать приходящие данные на уровне схемы. Например, можно накапливать данные, пока не будет найден некий разделитель. Данные до разделителя выдаются, данные после остаются в накопителе. Можно переключить накопитель, чтобы он выдавал данные и генерировал событие после указанного количества данных. Таким образом можно было послать размер файла, а после разделителя - содержимое. Переключая режимы накопителя можно разбирать такие протоколы.

Таким же образом можно обрабатывать HTTP ответы: до разделителя #13#10#13#10 - заголовки, после - тело. Получили заголовки, можем решать, что делать с телом. В примерах HTTP через TCP мы обычно накапливаем ответ аж до разрыва соединения, а только потом разбираем его. Что не очень подходит для закачки больших файлов и работы с постоянным соединением.

До практического применения компонента пока не дошел.
карма: 26

0
14
Сообщение
...
Прикрепленные файлы
(файлы не залиты)