2017-01-25 1 views
5

У меня есть сервер Datasnap Delphi 10.1 Berlin, который не может вернуть пакеты данных (через TStream) больше, чем около 260 000 байт.Не удается получить TStreams больше, чем около 260 000 байт с сервера Datasnap

Я запрограммировал его в соответствии с образцом Delphi, который также показывает эту проблему: \ Object Pascal \ DataSnap \ FireDAC.

Проблему можно увидеть только открыв этот образец, установив заготовку в IndexFieldName компоненты qOrders на ServerMethodsUnit.pas, и изменяя его свойство SQL для:

select * from Orders 
union 
select * from Orders 

Теперь объем данных, отправляемым более 260 000 байт, что, кажется, является тем местом, где вы не можете получить его от клиента. Получение EFDException [FireDAC] [Stan] -710. Недопустимый формат двоичной памяти.

Данные отправляются как поток, который вы получаете из FDSchemaAdapter на сервере, и вы загружаете другой клиент FDSchemaAdpater на клиенте. Соединение между клиентом и сервером также является FireDAC.

Это как сервер возвращает этот поток:

function TServerMethods.StreamGet: TStream; 
begin 
    Result := TMemoryStream.Create; 
    try 
    qCustomers.Close; 
    qCustomers.Open; 
    qOrders.Close; 
    qOrders.Open; 
    FDSchemaAdapter.SaveToStream(Result, TFDStorageFormat.sfBinary); 
    Result.Position := 0; 
    except 
    raise; 
    end; 
end; 

И это, как клиент получает его:

procedure TClientForm.GetTables; 
var 
    LStringStream: TStringStream; 
begin 
    FDStoredProcGet.ExecProc; 
    LStringStream := TStringStream.Create(FDStoredProcGet.Params[0].asBlob); 
    try 
    if LStringStream <> nil then 
    begin 
     LStringStream.Position := 0; 
     DataModuleFDClient.FDSchemaAdapter.LoadFromStream(LStringStream, TFDStorageFormat.sfBinary); 
    end; 
    finally 
    LStringStream.Free; 
    end; 
end; 

Клиент не получает все данные по параметру Blob. Я сохраняю содержимое Stream на сервере и содержимое, которое приходит на параметр Blob на клиенте, и они имеют одинаковый размер, но содержимое параметра Blob имеет усеченный контент, а последние несколько килобайт - это нули ,

Это, как я сохранить на сервере содержимое, которое будет идти в поток:

FDSchemaAdapter.SaveToFile('C:\Temp\JSON_Server.json', TFDStorageFormat.sfJSON); 

Это, как я могу проверить, что я получаю от параметра клиента двоичных объектов:

TFile.WriteAllText('C:\Temp\JSON_Client.json', FDStoredProcGet.Params[0].asBlob); 

I может видеть, что Клиент получает данные усеченными.

Вы знаете, как исправить это, или обходной путь для извлечения всего содержимого Stream с сервера Datasnap моему клиенту ?.

Обновление: Я обновил Delphi 10.1 Berlin Update 2, но проблема остается.

спасибо.

ответ

0

Я закодировал обходное решение. Увидев, что я не могу передавать данные размером более 255 КБ, я разделил их на разные пакеты 255 Кбит и отправил их отдельно (я также добавил сжатие, чтобы свести к минимуму полосу пропускания и обратные вызовы).

На сервере я изменил StremGet на два разных вызова: StreamGet и StreamGetNextPacket.

function TServerMethods.StreamGet(var Complete: boolean): TStream; 
var Data: TMemoryStream; 
    Compression: TZCompressionStream; 
begin 
    try 
    // Opening Data 
    qCustomers.Close; 
    qCustomers.Open; 
    qOrders.Close; 
    qOrders.Open; 

    // Compressing Data 
    try 
     if Assigned(CommStream) then FreeAndNil(CommStream); 
     CommStream := TMemoryStream.Create; 
     Data := TMemoryStream.Create; 
     Compression := TZCompressionStream.Create(CommStream); 
     FDSchemaAdapter.SaveToStream(Data, TFDStorageFormat.sfBinary); 
     Data.Position := 0; 
     Compression.CopyFrom(Data, Data.Size); 
    finally 
     Data.Free; 
     Compression.Free; 
    end; 

    // Returning First 260000 bytes Packet 
    CommStream.Position := 0; 
    Result := TMemoryStream.Create; 
    Result.CopyFrom(CommStream, Min(CommStream.Size, 260000)); 
    Result.Position := 0; 

    // Freeing Memory if all sent 
    Complete := (CommStream.Position = CommStream.Size); 
    if Complete then FreeAndNil(CommStream); 
    except 
    raise; 
    end; 
end; 

function TServerMethods.StreamGetNextPacket(var Complete: boolean): TStream; 
begin 
    // Returning the rest of 260000 bytes Packets 
    Result := TMemoryStream.Create; 
    Result.CopyFrom(CommStream, Min(CommStream.Size - CommStream.Position, 260000)); 
    Result.Position := 0; 

    // Freeing Memory if all sent 
    Complete := (CommStream.Position = CommStream.Size); 
    if Complete then FreeAndNil(CommStream); 
end; 

CommStream: TStream объявлен как закрытый в TServerMethods.

И клиент получает это так:

procedure TClientForm.GetTables; 
var Complete: boolean; 
    Input: TStringStream; 
    Data: TMemoryStream; 
    Decompression: TZDecompressionStream; 
begin 
    Input := nil; 
    Data := nil; 
    Decompression := nil; 

    try 
    // Get the First 260000 bytes Packet 
    spStreamGet.ExecProc; 
    Input := TStringStream.Create(spStreamGet.ParamByName('ReturnValue').AsBlob); 
    Complete := spStreamGet.ParamByName('Complete').AsBoolean; 

    // Get the rest of 260000 bytes Packets 
    while not Complete do begin 
     spStreamGetNextPacket.ExecProc; 
     Input.Position := Input.Size; 
     Input.WriteBuffer(TBytes(spStreamGetNextPacket.ParamByName('ReturnValue').AsBlob), Length(spStreamGetNextPacket.ParamByName('ReturnValue').AsBlob)); 
     Complete := spStreamGetNextPacket.ParamByName('Complete').AsBoolean; 
    end; 

    // Decompress Data 
    Input.Position := 0; 
    Data := TMemoryStream.Create; 
    Decompression := TZDecompressionStream.Create(Input); 
    Data.CopyFrom(Decompression, 0); 
    Data.Position := 0; 

    // Load Datasets 
    DataModuleFDClient.FDSchemaAdapter.LoadFromStream(Data, TFDStorageFormat.sfBinary); 
    finally 
    if Assigned(Input) then FreeAndNil(Input); 
    if Assigned(Data) then FreeAndNil(Data); 
    if Assigned(Decompression) then FreeAndNil(Decompression); 
    end; 
end; 

Он теперь работает отлично.

+1

Спасибо за этот ответ! Я реализовал свое собственное. Я сделал это, поэтому мне нужно только вызвать одну функцию от клиента, чтобы получить пакеты, доставленные в 260kb. – Henrikki

0

Сжатие потока на сервере и распаковка его на клиенте. Delphi 10.1 предоставляет необходимые классы (System.ZLib.TZCompressionStream и System.ZLib.TZDecompressionStream). В онлайн-документации содержится example, в которой показано, как использовать эти процедуры для сжатия и распаковки данных из потока и в поток. Сохраните вывод в файл ZIP, чтобы проверить, меньше ли он 260 KB.

+2

Спасибо Олаф, сжатие данных является хорошим предложением, и может ускорить время отклика (сервер не в локальной сети, но размещенный на облаке Амазонки). Но это только задержит проблему, в конце концов я снова удалю этот предел в 260Kb. Я просто не знаю, что делать. Должен ли я попытаться отправить данные несколькими меньшими вызовами по 260 Кбит каждый, перестроив Stream на клиенте, чтобы передать мои наборы данных в FDSchemaAdapter?. –

+0

Возможно, эти два вопроса касаются вашей проблемы: [Загрузить файл на сервер DataSnap REST через TStream] (http://stackoverflow.com/questions/18110654/upload-file-to-datasnap-rest-server-via-tstream?rq = 1) и [Delphi XE2 DataSnap - Загрузить файл через TStream с панелью выполнения] (http://stackoverflow.com/questions/8892334/delphi-xe2-datasnap-download-file-via-tstream-with-progress- bar? rq = 1) –

0

Обходной путь: запустите HTTP-сервер, который обслуживает запросы для больших файлов.Код генерирует и сохраняет файл, как показано в вашем вопросе, и возвращает URL к клиенту:

https://example.com/ds/... -> for the DataSnap service 

https://example.com/files/... -> for big files 

Если вы уже используете Apache в качестве обратного прокси-сервера, вы можете настроить Apache для маршрутизации HTTP GET запросов к ресурсам в/файлы /.

Для большего контроля (аутентификации) вы можете запустить HTTP-сервер (на основе Indy) на другом порту, который обслуживает запросы к этим файлам. Apache может быть настроен для сопоставления HTTP-запросов правильному адресату, клиент будет видеть только один HTTP-порт.

+1

Это сработает, спасибо. Но похоже, что это слишком сложно для такой простой задачи. Я должен иметь возможность передавать пакеты размером более 260 КБ без необходимости создания вторичного сервера. Я буду считать это в крайнем случае, спасибо. –

2

Я получаю аналогичную проблему с Сиэтлом (у меня нет Берлина) с сервером DataSnap , который не включает FireDAC.

На моем DataSnap сервере у меня есть:

type 
    TServerMethods1 = class(TDSServerModule) 
    public 
    function GetStream(Size: Integer): TStream; 
    function GetString(Size: Integer): String; 
    end; 

[...] 

uses System.StrUtils; 

function BuildString(Size : Integer) : String; 
var 
    S : String; 
    Count, 
    LeftToWrite : Integer; 
const 
    scBlock = '%8d bytes'#13#10; 
begin 
    LeftToWrite := Size; 
    Count := 1; 
    while Count <= Size do begin 
    S := Format(scBlock, [Count]); 
    if LeftToWrite >= Length(S) then 
    else 
     S := Copy(S, 1, LeftToWrite); 
    Result := Result + S; 
    Inc(Count, Length(S)); 
    Dec(LeftToWrite, Length(S)); 
    end; 
    if Length(Result) > 0 then 
    Result[Length(Result)] := '.' 
end; 

function TServerMethods1.GetStream(Size : Integer): TStream; 
var 
    SS : TStringStream; 
begin 
    SS := TStringStream.Create; 
    SS.WriteString(BuildString(Size)); 
    SS.Position := 0; 
    OutputDebugString('Quality Suite:TRACING:ON'); 
    Result := SS; 
end; 

function TServerMethods1.GetString(Size : Integer): String; 
begin 
    Result := BuildString(Size); 
end; 

Как вы можете видеть, обе эти функции построить строку заданного размера с помощью то же BuildString функцию и возвращает его в виде потока и строки соответственно.

На двух системах Win10 здесь GetStream работает отлично для размеров до 30716 байт, но выше, что возвращает пустой поток и «размер» -1.

Ото, GetString отлично работает для всех размеров я испытал до и включая размер 32000000. Я до сих пор не удалось проследить, почему GetStream не удается. Однако, основываясь на наблюдении, что GetStringделает работу, я тестировал следующие обходные, который посылает поток в виде строки, и это прекрасно работает до до 32М, а также:

function TServerMethods1.GetStreamAsString(Size: Integer): String; 
var 
    S : TStream; 
    SS : TStringStream; 
begin 
    S := GetStream(Size); 
    S.Position := 0; 
    SS := TStringStream.Create; 
    SS.CopyFrom(S, S.Size); 
    SS.Position := 0; 
    Result := SS.DataString; 
    SS.Free; 
    S.Free; 
end; 

I что вы можете предпочесть свою собственную работу по отправке результата в куски.

Btw, я попытался назвать мой GetStream на сервере путем создания экземпляра TServerMethods in a method of the server's main form and calling GetStream directly from that, so that the server's TDSTCPServerTransport` не участвует. Это правильно возвращает поток, поэтому проблема кажется в транспортном уровне или интерфейсах ввода и/или вывода.

+1

Спасибо, Мартин, я мог очень легко изменить свой код, чтобы передать данные в виде строки вместо Stream. Я не пробовал, потому что я читал, что у Datasnap есть ограничение на 32 Кб для строк, но это должно быть для более старых версий Delphi. Я дам ему попробовать. Еще раз спасибо. –

+1

Я помню, как отключился размер жесткого кодированного буфера в DB.Pas вокруг эпохи D7, но iirc, который был связан с полями, а не с параметрами DBX, на основе которых используются типы возвращаемого метода сервера, afaik. Между тем, я немного продвинулся с помощью метода сервера, который возвращает вариант (ole), но у меня возникают проблемы с перезаписью безмолвной памяти. – MartynA

+0

Мне не повезло, вернув строку вместо Stream. Похоже, что компоненты FireDAC, которые подключаются к Серверу, усекают его, независимо от типа параметра. Я попробую разбить данные в пакетах 255 Кбит и отправить их отдельно. –

0

Проблема не является ни классом TStream, ни базовой инфраструктурой связи DataSnap, но компонент TFDStoredProc создает возвращаемый параметр типа ftBlob.На первом месте измените выходной параметр с ftBlob на ftStream. Затем измените процедуру GetTables на:

procedure TClientForm.GetTables; 
var 
    LStringStream: TStream; 
begin 
    spStreamGet.ExecProc; 
    LStringStream := spStreamGet.Params[0].AsStream; 
    LStringStream.Position := 0; 
    DataModuleFDClient.FDSchemaAdapter.LoadFromStream(LStringStream, 
    TFDStorageFormat.sfBinary); 
end; 
+0

Когда я исследовал эту проблему, причина, казалось, была в уровне JSON, который DataSnap использует в качестве транспорта для отправки потока с сервера на клиент , Возможно, вы правы в этом вопросе, но проблема с потоками DataSnap кажется серьезной проблемой. – MartynA