Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/delphi/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Delphi Can';t从Datasnap服务器检索大于约260.000字节的TStream_Delphi_Datasnap_Firedac_Delphi 10.1 Berlin - Fatal编程技术网

Delphi Can';t从Datasnap服务器检索大于约260.000字节的TStream

Delphi Can';t从Datasnap服务器检索大于约260.000字节的TStream,delphi,datasnap,firedac,delphi-10.1-berlin,Delphi,Datasnap,Firedac,Delphi 10.1 Berlin,我有一个Delphi10.1 Berlin Datasnap服务器,它不能返回大于260.000字节的数据包(通过TStream) 我按照Delphi中的\Object Pascal\DataSnap\FireDAC示例对其进行了编程,该示例也显示了这个问题 打开该示例,将ServerMethodsUnit.pas上qOrders组件的IndexFieldName设置为空,并将其SQL属性更改为: select * from Orders union select * from Orders

我有一个Delphi10.1 Berlin Datasnap服务器,它不能返回大于260.000字节的数据包(通过TStream)

我按照Delphi中的\Object Pascal\DataSnap\FireDAC示例对其进行了编程,该示例也显示了这个问题

打开该示例,将ServerMethodsUnit.pas上qOrders组件的IndexFieldName设置为空,并将其SQL属性更改为:

select * from Orders
union 
select * from Orders
现在要发送的数据量超过了260.000字节,这似乎是您无法从客户端检索数据的地方。正在获取EFDException[FireDAC][Stan]-710。无效的二进制存储格式

数据作为从服务器上的FDSchemaAdapter获取的流发送,然后加载到客户机上的另一个FDSchemaAdapter。客户端和服务器之间的连接也是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参数上得到的结果:

TFile.WriteAllText('C:\Temp\JSON_Client.json', FDStoredProcGet.Params[0].asBlob);
我可以看到客户端截短了数据

您知道如何修复它,或者知道如何从Datasnap服务器检索到我的客户端的所有流内容吗

更新:我已更新到Delphi 10.1柏林更新2,但问题仍然存在


谢谢。

在服务器上压缩流,在客户端上解压缩流。Delphi10.1提供了必要的类(
System.ZLib.TZCompressionStream
System.ZLib.TZDecompressionStream
)。在线文档包含一个示例,说明如何使用这些例程从流压缩和解压缩数据。将输出保存到ZIP文件,以检查其大小是否小于260 KB。

解决方法:运行HTTP服务器,为大文件请求提供服务。代码生成并存储问题中所示的文件,并将其URL返回给客户端:

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

https://example.com/files/... -> for big files
如果您已经使用Apache作为反向代理,那么可以将Apache配置为将HTTP GET请求路由到位于/files/的资源


为了获得更多的控制(身份验证),您可以在为这些文件的请求提供服务的不同端口上运行HTTP服务器(基于Indy)。Apache可能被配置为将HTTP请求映射到正确的目标,客户端将只看到一个HTTP端口。

我在使用DataSnap的西雅图(我没有安装Berlin)时遇到类似问题 不涉及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;
我很感激您可能更喜欢自己的工作,将结果分块发送


顺便说一句,我尝试在服务器上调用我的
GetStream
,方法是在服务器主窗体的方法中创建一个
TServerMethods
实例,并直接从该实例调用
GetStream
,这样就不涉及服务器的
TDSTCPServerTransport
。这将正确返回流,因此问题似乎出现在传输层或其输入和/或输出接口中。

我已经编写了一个解决方案。看到无法传递大于255Kb的数据,我将其拆分为不同的255Kb数据包,并分别发送(我还添加了压缩,以最小化带宽和往返)

在服务器上,我将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在Tserver方法上声明为私有

客户端通过以下方式检索它:

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;

现在它工作正常。

问题似乎不是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;

@马克:我想亨利基的意思是一个函数,而不是一个函数调用…
我已经修改了您的代码,因此只有一个函数就足够了,并且可以使用具有不同SchemaAdapter/StoredProcess的项目。
最大streamsize声明为常量(MaxDataSnapStreamSize),并设置为$F000,这是函数句柄(请参见System.Classes)中TStream.CopyFrom的MaxBuffSize。
FComprStream是TMemorySTream类型的私有字段,由servermodule的构造函数和析构函数负责

在服务器端:

const
  MaxDataSnapStreamSize = $F000;

function TServerMethods1.StreamGet(const aFDSchemaAdapter: TFDSchemaAdapter; var aSize: Int64): TStream;
var
  lZIPStream: TZCompressionStream;
  lDataStream: TMemoryStream;
  I: Integer;
  lMinSize: Int64;
begin
if aSize=-1 then
  exit;
lDataStream:=TMemoryStream.Create;
  try
  if aSize=0 then
    begin
    FComprStream.Clear;
    with aFDSchemaAdapter do
      for I := 0 to Count-1 do
        begin
        DataSets[I].Close;
        DataSets[I].Open;
        end;
    lZIPStream := TZCompressionStream.Create(TCompressionLevel.clFastest, FComprStream);
      try
      aFDSchemaAdapter.SaveToStream(lDataStream, TFDStorageFormat.sfBinary);
      lDataStream.Position := 0;
      lZIPStream.CopyFrom(lDataStream, lDataStream.Size);
      finally
      lDataStream.Clear;
      lZIPStream.Free;
      end;
    lMinSize:=Min(FComprStream.Size, MaxDataSnapStreamSize);
    FComprStream.Position:=0;
    end
  else
    lMinSize:=Min(aSize, MaxDataSnapStreamSize);

  lDataStream.CopyFrom(FComprStream, lMinSize);
  lDataStream.Position := 0;
  aSize:=FComprStream.Size-FComprStream.Position;
  Result:=lDataStream;
  if aSize=0 then
    FComprStream.Clear;
  except
  aSize:=-1;
  lDataStream.Free;
  raise;
  end;
end;
在客户端:

procedure TdmClientModuleDS.GetTables(const aStPrGet: TFDStoredProc; const aFDSchemaAdapter: TFDSchemaAdapter);
var
  lSize: Int64;
  lZIPStream: TStringStream;
  lDataStream: TMemoryStream;
  lUNZIPStream:  TZDecompressionStream;
  I: Integer;
begin
  try
  lSize:=0;
  for I := 0 to aFDSchemaAdapter.Count-1 do
    aFDSchemaAdapter.DataSets[I].Close;
  aStPrGet.ParamByName('aSize').AsInteger:=0;
  aStPrGet.ExecProc;
  lZIPStream:=TStringStream.Create(aStPrGet.ParamByName('ReturnValue').AsBlob);
  lSize:=aStPrGet.ParamByName('aSize').AsInteger;
  while lSize>0 do
    with aStPrGet do
      begin
      ParamByName('aSize').AsInteger:=lSize;
      ExecProc;
      lZIPStream.Position:=lZIPStream.Size;
      lZIPStream.WriteBuffer(TBytes(ParamByName('ReturnValue').AsBlob),Length(ParamByName('ReturnValue').AsBlob));
      lSize:=ParamByName('aSize').AsInteger;
      end;
  lZIPStream.Position:=0;
  lDataStream:=TMemoryStream.Create;
  lUNZIPStream:=TZDecompressionStream.Create(lZIPStream);
  lDataStream.CopyFrom(lUNZIPStream, 0);
  lDataStream.Position:=0;
  aFDSchemaAdapter.LoadFromStream(lDataStream,TFDStorageFormat.sfBinary);
  finally
  if Assigned(lZIPStream) then
    FreeAndNil(lZIPStream);
  if Assigned(lDataStream) then
    FreeAndNil(lDataStream);
  if Assigned(lUNZIPStream) then
    FreeAndNil(lUNZIPStream);
  end;
end;

感谢Olaf,压缩数据是一个很好的建议,可以加快响应速度(服务器不在本地网络上,而是托管在Amazon的云上)。但这只会延迟问题,最终我会再次达到260Kb的极限。我只是不知道该怎么办。我是否应该尝试以每次260Kb的几个较小的调用发送数据,在客户端重建流以将数据集提供给FDSchemaAdapter?也许这两个问题可以解决您的问题:感谢Martyn,我可以很容易地更改代码,将数据作为字符串而不是流传递。我没有尝试,因为我读到Datasnap对字符串的限制是32Kb,但它必须适用于旧版本的Delphi。我要试一试。再次感谢。我记得在D7时代,我被硬编码的缓冲区大小(以DB.Pas为单位)绊倒了,但iirc与字段有关,而不是与服务器方法返回类型所基于的DBX参数有关,afaik。同时,我在返回(ole)变量的服务器方法方面取得了一些进展,但在静默内存覆盖方面遇到了一些问题。我没有太多机会返回字符串而不是流。看起来连接到服务器的FireDAC组件会截断它,而不管参数的类型如何。我会尝试将数据分成255Kb的数据包,然后分别发送出去。@MarcGuillot:就像我说的,我不认为是FireDAC造成了这个问题。我很难为情地说,尽管花了几个小时在这上面,但到目前为止,我还没有弄清楚特朗卡
const
  MaxDataSnapStreamSize = $F000;

function TServerMethods1.StreamGet(const aFDSchemaAdapter: TFDSchemaAdapter; var aSize: Int64): TStream;
var
  lZIPStream: TZCompressionStream;
  lDataStream: TMemoryStream;
  I: Integer;
  lMinSize: Int64;
begin
if aSize=-1 then
  exit;
lDataStream:=TMemoryStream.Create;
  try
  if aSize=0 then
    begin
    FComprStream.Clear;
    with aFDSchemaAdapter do
      for I := 0 to Count-1 do
        begin
        DataSets[I].Close;
        DataSets[I].Open;
        end;
    lZIPStream := TZCompressionStream.Create(TCompressionLevel.clFastest, FComprStream);
      try
      aFDSchemaAdapter.SaveToStream(lDataStream, TFDStorageFormat.sfBinary);
      lDataStream.Position := 0;
      lZIPStream.CopyFrom(lDataStream, lDataStream.Size);
      finally
      lDataStream.Clear;
      lZIPStream.Free;
      end;
    lMinSize:=Min(FComprStream.Size, MaxDataSnapStreamSize);
    FComprStream.Position:=0;
    end
  else
    lMinSize:=Min(aSize, MaxDataSnapStreamSize);

  lDataStream.CopyFrom(FComprStream, lMinSize);
  lDataStream.Position := 0;
  aSize:=FComprStream.Size-FComprStream.Position;
  Result:=lDataStream;
  if aSize=0 then
    FComprStream.Clear;
  except
  aSize:=-1;
  lDataStream.Free;
  raise;
  end;
end;
procedure TdmClientModuleDS.GetTables(const aStPrGet: TFDStoredProc; const aFDSchemaAdapter: TFDSchemaAdapter);
var
  lSize: Int64;
  lZIPStream: TStringStream;
  lDataStream: TMemoryStream;
  lUNZIPStream:  TZDecompressionStream;
  I: Integer;
begin
  try
  lSize:=0;
  for I := 0 to aFDSchemaAdapter.Count-1 do
    aFDSchemaAdapter.DataSets[I].Close;
  aStPrGet.ParamByName('aSize').AsInteger:=0;
  aStPrGet.ExecProc;
  lZIPStream:=TStringStream.Create(aStPrGet.ParamByName('ReturnValue').AsBlob);
  lSize:=aStPrGet.ParamByName('aSize').AsInteger;
  while lSize>0 do
    with aStPrGet do
      begin
      ParamByName('aSize').AsInteger:=lSize;
      ExecProc;
      lZIPStream.Position:=lZIPStream.Size;
      lZIPStream.WriteBuffer(TBytes(ParamByName('ReturnValue').AsBlob),Length(ParamByName('ReturnValue').AsBlob));
      lSize:=ParamByName('aSize').AsInteger;
      end;
  lZIPStream.Position:=0;
  lDataStream:=TMemoryStream.Create;
  lUNZIPStream:=TZDecompressionStream.Create(lZIPStream);
  lDataStream.CopyFrom(lUNZIPStream, 0);
  lDataStream.Position:=0;
  aFDSchemaAdapter.LoadFromStream(lDataStream,TFDStorageFormat.sfBinary);
  finally
  if Assigned(lZIPStream) then
    FreeAndNil(lZIPStream);
  if Assigned(lDataStream) then
    FreeAndNil(lDataStream);
  if Assigned(lUNZIPStream) then
    FreeAndNil(lUNZIPStream);
  end;
end;