我有一个Delphi 10.1 Berlin Datasnap Server,它不能返回大于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。二进制存储格式无效。
数据作为Stream从服务器上的FDSchemaAdapter获取,然后加载到客户端上的另一个FDSchemaAdpater。客户端和服务器之间的连接也是FireDAC。
这是服务器返回Stream的方式:
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参数的内容的内容被截断,最后几个Kbytes为零
这就是我在服务器上保存将转到Stream的内容的方式:
FDSchemaAdapter.SaveToFile('C:\Temp\JSON_Server.json', TFDStorageFormat.sfJSON);
这是我查看客户端blob参数的内容:
TFile.WriteAllText('C:\Temp\JSON_Client.json', FDStoredProcGet.Params[0].asBlob);
我可以看到客户端获取截断的数据。
您知道如何修复它,或者从Datasnap Server检索所有Stream内容到我的客户端的解决方法吗?
更新:我已更新到Delphi 10.1 Berlin Update 2,但问题仍然存在。
谢谢。
答案 0 :(得分: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”的大小。
Otoh,GetString
适用于我测试过的所有尺寸,包括a
大小为32000000.我还没有设法追查GetStream
失败的原因。
但是,基于GetString
工作的观察,我测试了
下面的解决方法,它将一个流作为字符串发送,并且工作正常
也是32M:
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;
我感谢您可能更喜欢自己动手将结果发送到块中。
顺便说一下,我尝试通过创建TServerMethods GetStream
实例来调用我的in a method of the server's main form and calling
。不涉及GetStream directly from that, so that the server's
TDSTCPServerTransport`。这会正确返回流,因此问题似乎出现在传输层或输入和/或输出接口中。
答案 1 :(得分:1)
@Marc:我认为Henrikki的意思是单个函数,而不是单个函数调用...
我已经修改了您的代码,以使仅一个功能就足够了,因此可以使用具有不同SchemaAdapters / StoredProcedures的项目。
最大流大小声明为常量(MaxDataSnapStreamSize),并设置为$ F000,其中TStream.CopyFrom函数处理的最大大小为MaxBuffSize(请参见System.Classes)。
FComprStream是TMemorySTream类型的私有字段,在服务器模块的构造函数和析构函数中负责。
在服务器端:
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;
答案 2 :(得分:0)
压缩服务器上的流并在客户端上解压缩。 Delphi 10.1提供了必要的类(System.ZLib.TZCompressionStream
和System.ZLib.TZDecompressionStream
)。联机文档包含example,其中显示了如何使用这些例程来压缩和解压缩流中的数据。将输出保存为ZIP文件以检查它是否小于260 KB。
答案 3 :(得分:0)
解决方法:运行一个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端口。
答案 4 :(得分:0)
我编写了一个解决方法。看到我无法传递大于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:在TServerMethods上将TStream声明为私有。
客户端以这种方式检索它:
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;
现在工作正常。
答案 5 :(得分: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;