Пытаюсь создать качалку файлов по сети - есть проблема.
От: Mastak  
Дата: 23.09.03 12:06
Оценка:
Я пытаюсь создать качалку файлов по сети с помощью DXPlay. Создаю структуру для персылки:

TDXFileMessage = record
    MessageType: DWORD;  {MessageType is absolutely necessary}
    BlockSize:Word; {размер блока в байтах или размер реально прочитанной информации}
    Buf:array[0..0] of Char;  
end;


По логике работы надо делать изменяемый размер пакета, чтобы подстраиваться под разную скорость передачи. Теперь пытаюсь отправить это сообщение:


var
  Msg:^TDXFileMessage;
  MsgSize:integer;
begin
  MsgSize:=SizeOf(TDXFileMessage)+POCKET_SIZE;
  GetMem(Msg,MsgSize);
  Msg.MessageType:=DXFILE_MESSAGE;
  setlength(msg.Buf,POCKET_SIZE);                         <<<-ОШИБКА
  BlockRead(FromF,msg.Buf,POCKET_SIZE,NumRead);
  Msg.BlockSize:=NumRead;
  DXPlay1.SendMessage(DPID_ALLPLAYERS,Msg,MsgSize);
end;

Код немного упрощён для лучшего восприятия. B помеченной строке деглюкер Delphi выдаёт:

(245): Constant object cannot be passed as var parameter

Так как же всё-таки установить динамический размер буфера перед отправкой? Что я делаю не так?
Re: Пытаюсь создать качалку файлов по сети - есть проблема.
От: DrDred Россия  
Дата: 23.09.03 12:16
Оценка: 3 (1) -1
Здравствуйте, Mastak, Вы писали:

M>Я пытаюсь создать качалку файлов по сети с помощью DXPlay. Создаю структуру для персылки:


M>TDXFileMessage = record

M> MessageType: DWORD; {MessageType is absolutely necessary}
M> BlockSize:Word; {размер блока в байтах или размер реально прочитанной информации}
M> Buf:array[0..0] of Char;
Buf: PChar;
M>end;

M>setlength(msg.Buf,POCKET_SIZE); <<<-ОШИБКА

GetMem(msg.Buf, POCKET_SIZE);

и не забыть про FreeMem, когда буфер станет не нужен...
... << RSDN@Home 1.1 beta 1 >>
--
WBR, Alexander
Re[2]: Пытаюсь создать качалку файлов по сети - есть пробле
От: Mastak  
Дата: 23.09.03 14:23
Оценка:
Большое спасибо!
Re[2]: Пытаюсь создать качалку файлов по сети - есть пробле
От: akasoft Россия  
Дата: 23.09.03 18:43
Оценка: 22 (1)
Здравствуйте, DrDred, Вы писали:

M>>TDXFileMessage = record

M>> MessageType: DWORD; {MessageType is absolutely necessary}
M>> BlockSize:Word; {размер блока в байтах или размер реально прочитанной информации}
M>> Buf:array[0..0] of Char;
DD>Buf: PChar;

Нельзя так делать! Поток байт должен быть непрерывным и именно данными, бо такова природа коммуникаций по DirectPlay. По формату сначала следует идентификатор сообщения размером DWORD, а затем сами данные.

Оставляя в стороне вопрос, "а зачем надо было делать качалку средствами DirectPlay (?)", по существу вопроса скажу, что делать надо так.

Запись для упаковки сообщения определена почти правильно, но лучше добавить зарезервированное слово packed, дабы лишить компилятор возможности мелко пакостить оптимизацией кода, и добавить тип-ссылку на наше сообщение, типа так

PDXFileMessage = ^TDXFileMessage;
TDXFileMessage = packed record
    MessageType: DWORD;  {MessageType is absolutely necessary}
    BlockSize: Word; {размер блока в байтах или размер реально прочитанной информации}
    Buf: array[0..0] of Char;  
end;


Затем объявляем переменную, выделяем память и отправляем сообщение

var
    Msg: PDXFileMessage;
    MsgSize: Integer;
    FileBuf: Pointer;
begin
    // Предварительно в буфер FileBuf загружаем поток байт из файла,
    // имеем FileBuf - указатель на данные, NumRead - количество реально прочитанных данных из файла в буфер
    // Размер этого буфера не превышает размера выбранного нами "окна" - POCKET_SIZE
    // Иначе придётся добавлять поле NumRead в нашу запись
    // Для подчитывания файла удобно пользоваться TFileStream, а для буфера сообщения в памяти TMemoryStream,
    // на который следует "наложить" нужный record...
    // А если вернуться к тексту примера в топике, тогда
    MsgSize := SizeOf(TDXFileMessage) + NumRead;
    GetMem(Msg, MsgSize);
    try
        with Msg^ do
        begin
            MessageType := DXFILE_MESSAGE;
            Move(FileBuf^, Buf[0], NumRead);
            BlockSize := NumRead;
        end;
        DXPlay1.SendMessage(DPID_ALLPLAYERS, Msg, MsgSize);
    finally
        FreeMem(Msg);
    end;


И ещё не забыть придумать механизм сборки таких "пакетов данных" снова в файл...
... << RSDN@Home 1.1 beta 2 >>
Re[3]: Пытаюсь создать качалку файлов по сети - есть пробле
От: Mastak  
Дата: 24.09.03 11:29
Оценка:
Я попытался реализовать то же самое с помощью TFileStream — работает, но при динамическом размере пакетов на каждые 100-200 байт правильно переданной информации приходится какой-то левый символ (чаще всего #0). Если размер пакета задан жёстко, то есть buf:array[1..512] of Char, то передаётся без ошибок (много раз проверял). Мне всё-таки нужен динамический пакет. Расскажи, пожалуйста, поподробнее. Я в твой способ не очень врубился. Зачем одновременно TFileStream, TMemoryStream и указатель на файл Pointer? Как этот указатель вообще получить?
Re[4]: Пытаюсь создать качалку файлов по сети - есть пробле
От: akasoft Россия  
Дата: 25.09.03 07:39
Оценка:
Здравствуйте, Mastak, Вы писали:

M> Расскажи, пожалуйста, поподробнее. Я в твой способ не очень врубился. Зачем одновременно TFileStream, TMemoryStream и указатель на файл Pointer? Как этот указатель вообще получить?


В моём примере FileBuf/NumRead — просто отдельные переменные, позволяющие получать кусок данных из файла. Т.е. для FileBuf с помощью GetMem выделяется POCKET_SIZE байт, затем открывается нужный файл, позиционируется, считывается кусок, закрывается, и считанные данные перемещаются в буфер подготовки сообщения DirectPlay для отправки. И так, сколько нужно раз.

Но поскольку такие вещи делать лень, и их уже много раз делали, и велосипед изобретать хочется не всегда, то я и предложил использовать TFileStream/TMemoryStream. С первым всё понятно — открываем им файл и читаем из него нужный объём данных размером в наше "окно".

А вот использование TMemoryStream не так очевидно. По сути, это кусок памяти, с которым можно работать, как с потоком байт одинаковыми с TFileStream методами. Для DirectPlay также нужно подготовить грубо говоря кусок памяти в нужном формате, чтобы потом можно было его передать. У TMemoryStream есть поле Memory — нужный нам указатель на буфер данных, и поле Size.

Формат сообщения DirectPlay, как я уже писал ранее, такой: DWORD идентификатор сообщения, а затем собственно данные. Итого имеем

var
    RamS: TMemoryStream;
    FileS: TFileStream;
    MsgId: DWORD;
    ...
begin
    ...
    // Создаём потоки, позиционируем файл, куда надо, готовимся
    ...
    RamS.Clear; // Сбрасываем наш "буфер". Для оптимизации можно попробовать попользоваться свойствами Capacity и Position
    // Готовим буфер для отправки
    MsgId := DXFILE_MESSAGE; 
    RamS.Write(MsgId, SizeOf(MsgId)); // Идентификатор
    // Поскольку в сообщении полей кроме ид и числа байт собственно нет, то мы можем сделать это поле
    // вычисляемым, как размер принятого блока - SizeOf(DWORD)
    // Если же ещё есть поля, то можно вычислить размер блока данных на основе информации о Position и
    // Size у FileS, т.к. размер может быть <= POCKET_SIZE.
    RamS.CopyFrom(FileS, POCKET_SIZE);
    // Отправляем
    DXPlay1.SendMessage(DPID_ALLPLAYERS, RamS.Memory^, RamS.Size);
    ...


Вот такая вот простая идея...
... << RSDN@Home 1.1 beta 2 >>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.