DelphiBuilder / NetCom7

The fastest communications possible. Delphi rulez.
MIT License
174 stars 43 forks source link

[TncServerSource] wrong aData size on server side #20

Closed frankwu-delphi closed 6 months ago

frankwu-delphi commented 3 years ago

/////////////////////////////////////////////// //commands: /////////////////////////////////////////////// const cmdUserLogin = 0; cmdFileName = 1; cmdFileSize = 2; cmdFileStream = 3; /////////////////////////////////////////////// //on client side: /////////////////////////////////////////////// procedure TForm1.Button1Click(Sender: TObject); var BS: TBytesStream; begin Client.ExecCommand(cmdFileName, BytesOf('client.png'));//send filename BS := TBytesStream.Create(); BS.LoadFromFile('client.png'); // send client's filesize Client.ExecCommand(cmdFileSize, BytesOf(BS.Size.ToString)); // send file Client.ExecCommand(cmdFileStream, BS.Bytes); //in this line, BS.Bytes is correct file size BS.Free; end; /////////////////////////////////////////////// //on server side: /////////////////////////////////////////////// function TForm1.ServerHandleCommand(Sender: TObject; aLine: TncLine; aCmd: Integer; const aData: TArray; aRequiresResult: Boolean; const aSenderComponent, aReceiverComponent: string): TArray; var BS: TBytesStream; begin SetLength(Result, 0);

case aCmd of cmdUserLogin: begin end; cmdFileName: begin Memo1.Lines.Add(stringOf(aData)); end; cmdFileStream: begin try BS := TBytesStream.Create(aData); BS.SaveToFile('_srv_test.png'); Memo1.Lines.Add('received file's size:' + Length(BS.Bytes).ToString);//<---Wrong file size BS.Free; finally end; end; cmdFileSize: begin Memo1.Lines.Add(stringOf(aData));//correct file size end; end; end; /////////////////////////////////////////////// code write with delphi10.4.2 + FMX

SeanSolberg commented 10 months ago

I ran into the same problem. The issue is not really with NetCom7. Rather, the issue is with TBytesStream from Embarcadero. When you write to TBytesStream using normal streaming methods, the fBytes array of bytes will be expanded to a larger size when needed by TBytesStream internally. It does this with blocks that are ever increasing in size. So, unless the content you put into the stream just so happens to end on the last byte of fBuffer, there will always be some extra bytes at the end of fBytes that are not used. However, when you then send these TBytes to the .ExecCommand call of the NetComm7 client and server, it assumes that you want to send all of the bytes held in the TBytes array. SO. you need to pack the fBytes array before sending it. Unfortunately, when Embarcadero wrote TBytesStream they didn't put fBytes as protected so we can't simply inherit a TPackableBytesStream class from TBytes. We need to copy TBytesStream to TPackableBytesStream and re-implement it with a .Pack method as shown below.

{ TPackableBytesStream }

constructor TPackableBytesStream.Create(const ABytes: TBytes);
begin
  inherited Create;
  FBytes := ABytes;
  SetPointer(Pointer(FBytes), Length(FBytes));
  Capacity := Size;
end;

procedure TPackableBytesStream.Pack;
begin
  SetLength(self.fBytes, size);
end;

function TPackableBytesStream.Realloc(var NewCapacity: NativeInt): Pointer;
begin
  if (NewCapacity > 0) and (NewCapacity <> Size) then
    NewCapacity := (NewCapacity + (cMemoryDelta - 1)) and not (cMemoryDelta - 1);
  Result := Pointer(FBytes);
  if NewCapacity <> Capacity then
  begin
    SetLength(FBytes, NewCapacity);
    Result := Pointer(FBytes);
    if NewCapacity = 0 then
      Exit;
    if Result = nil then raise EStreamError.CreateRes(@SMemoryStreamError);
  end;
end;
SeanSolberg commented 10 months ago

Better solution:
I modified the ExecCommand function to allow passing in of the aDataSize. This is more efficient than doing a pack.

function TncClientSource.ExecCommand( const aCmd: Integer; const aData: TBytes = nil; const aDataSize: integer = -1; const aRequiresResult: Boolean = True; const aAsyncExecute: Boolean = False; const aPeerComponentHandler: string = ''; const aSourceComponentHandler: string = ''): TBytes;

Inside this function when the Command is being setup, I added the following logic so that if -1 is passed in then it works as before by sending the whole TBytes array. If you pass in the aDataSize, then it will just send that many bytes.

if aDataSize < 0 then
  Command.DataSize := length(aData)
else
  Command.DataSize := aDataSize;
DelphiBuilder commented 6 months ago

Hi there, I have been under a lot the last years, and especially material sciences are very involved, being in a plasma physics lab day and night for five years until I finished a machine. I am finally out of the cave. The purpose of the buffer you receive in the OnHandleEvents not being the actual size of the buffer received, is that this buffer NEVER gets to a different size for speed. The way to get the count of bytes received is NOT to rely on the Length of the buffer, but use the parameter supplied with the appropriate name (don't remember it off-by-heart at this hour of writing, but it is clear which parameter it is which gives you the ACTUAL count of your data).

I am planning a version 8 soon, and all these notes have been noted. This in particular concerns how intuitive programmers find the library, so it is also noted for the architecting of version 8.

Best regards.