使用 Indy Tcp 服务器和客户端发送和接收 TStringStream

send and recive TStringStream with indyTcp server and client

我正在尝试使用 Indy TCP 组件将 TStringStream 从客户端发送到服务器,然后将其从服务器发送回客户端。

这是我的客户端代码:

var
  Jpg: TJPEGImage;
  StringStream: TStringStream;
  strcams, StringImageData: String;
  byt, i: integer;

procedure SendCommandWithParams(Command, Params: String);
begin
  Lock;
  try
    if not FTCP.Connected then
    begin
      exit;
    end;
    FTCP.Socket.WriteLn('1' + Command, IndyTextEncoding_UTF8);
    FTCP.Socket.WriteLn(Params, IndyTextEncoding_UTF8);
  finally
    Unlock;
  end;
end;

begin
  Jpg := TJPEGImage.Create;
  StringStream := TStringStream.Create('');
  try
    try
      Jpg.Performance := jpBestSpeed;
      Jpg.ProgressiveEncoding := True;
      Jpg.ProgressiveDisplay := True;
      Jpg.Assign(Image2.Picture.Bitmap);
      Jpg.CompressionQuality := 25;
      Jpg.Compress;
      Jpg.SaveToStream(StringStream);
      StringImageData := StringStream.DataString;
      strcams := '<[S:' + IntToStr(Length(StringImageData)) + 'B]>' +
        StringImageData;
      if Length(strcams) < byt then
      begin
       SendCommandWithParams('SIMGSEND', strcams + sep + 'IMGID5423' + sep);
      end;
    except
      on e: exception do
        //
    end;
  finally
    StringImageData := '';
    FreeAndNil(Jpg);
    FreeAndNil(StringStream);
  end;
end;

我可以接收 TStringStream 数据,但接收到的数据已损坏,有时它会被我发送的第二个参数 'IMGID5423' + sep 替换。我不确定这是否是因为通过 TCP 发送数据包的某些限制导致数据未完整到达,或者这是解析器问题?

我当前的解析器应该将每个以 #13#10 结尾的文本分开。外观如下:

var
  ReceiveParams, ReceiveStream: Boolean;
  S: string;
  Command: String;
begin
  Command := Fholdcommand;
  ReceiveParams := false;
  ReceiveStream := false;

  if Command[1] = '1' then // command with params
  begin
    Command := Copy(Command, 2, MaxInt);
    ReceiveParams := True;
  end;

  if ReceiveParams then // params incomming
  begin
    S := FTCP.Socket.ReadLn(IndyTextEncoding_UTF8);
    FCMD := Command;
    FPRMS := S;
    FSTREAM := false;
    if Assigned(FOnCallbackProc) then
    begin
      Synchronize(DoCallbackProc);
    end;
  end;

我仍然对真正的问题感到困惑。我尝试在本地程序中发送 TStringStream,并且正常接收,没有任何损坏。

我通过 Indy 发送的数据完全错误吗?

这是我接收数据的方式:

procedure CreateJpg(Data:string);
var
  StringStream : TStringStream;
  JpegImage : TJPEGImage;
  Bitmap : TBitmap;
  tmpPos:integer;
  pp:string;
  label check;
begin
  GData := Data;

  if LeftStr(GData,4) = '<[S:' then 
  begin
    tmpPos := Pos(WideString('B]>'),GData);
    pp := Copy(GData,5,tmpPos-5);
    CDataELen :=  StrToInt(pp); //MidStr(st,5,tmppos - 5);
    CData := RightStr(GData,length(GData)-(tmppos+2));
    goto check;
  end;

  CData := CData + GData;

  check:

  //if CDataELen = length(CData) then
  begin
    StringStream := TStringStream.Create('');
    JpegImage := TJpegImage.Create;
    StringStream.WriteString(CData);
    CData := '';
    try
      try
        StringStream.Seek(0, soFromBeginning);
        JpegImage.LoadFromStream(StringStream);
        Bitmap := TBitmap.Create;
        with Bitmap do
        begin
          Canvas.Lock;
          try
            Width := JpegImage.Width;
            Height := JpegImage.Height;
            Canvas.Draw(0, 0, JpegImage);
          finally
            Canvas.Unlock;
          end;
        end;

        img.Picture.Bitmap.Width := Bitmap.Width;
        img.Picture.Bitmap.Height := Bitmap.Height;
        img.Picture.Bitmap.Canvas.Draw(0, 0, Bitmap);

      except
        on E: Exception do
          //
      end;
    finally
      FreeAndNil(StringStream);
      FreeAndNil(JpegImage);
      FreeAndNil(Bitmap);
    end;
  end;
end;

您的问题似乎是您将二进制数据视为文本。二进制数据可以包含任何内容,例如 #13#10 换行符或任何内容。

如果您希望将该数据作为文本发送,则需要使用文本编码。比如编码为base64.

或者以二进制而不是文本形式传输内容。

问题是您将 JPG 二进制数据保存到 TStringStream,然后让它 重新解释 二进制数据,就好像它是字符串数据一样。你不能那样做。您需要将 JPG 数据保存为二进制流,例如 TMemoryStream,然后使用字符串安全编码对二进制数据进行 编码,例如 Base64。

尝试更像这样的东西:

uses
  ..., IdCoder, IdCoderMIME;

...

var
  Jpg: TJPEGImage;
  JpegStream: TMemoryStream;
  strcams, StringImageData: String;
begin
  try
    JpegStream := TMemoryStream.Create;
    try
      Jpg := TJPEGImage.Create;
      try
        Jpg.Performance := jpBestSpeed;
        Jpg.ProgressiveEncoding := True;
        Jpg.ProgressiveDisplay := True;
        Jpg.Assign(Image2.Picture.Bitmap);
        Jpg.CompressionQuality := 25;
        Jpg.Compress;
        Jpg.SaveToStream(JpegStream);
      finally
        Jpg.Free;
      end;
      JpegStream.Position := 0;
      StringImageData := TIdEncoderMIME.EncodeStream(JpegStream);
    finally
      JpegStream.Free;
    end;
    strcams := '<[S:' + IntToStr(Length(StringImageData)) + 'B]>' + StringImageData;
    SendCommandWithParams('SIMGSEND', strcams + sep + 'IMGID5423' + sep);
  except
    on e: exception do
      //
  end;
end;

然后在接收端:

procedure CreateJpg(Data: string);
var
  JpegStream: TMemoryStream;
  JpegImage: TJPEGImage;
  Bitmap: TBitmap;
  tmpPos, tmpLen: integer;
  pp: string;
begin
  try
    if not TextStartsWith(Data, '<[S:') then
    begin
      // bad data, do something else...
      Exit;
    end;

    tmpPos := Pos('B]>', Data);
    pp := Copy(Data, 5, tmpPos-5);
    tmpLen := StrToInt(pp);
    Data := Copy(Data, tmpPos+3, tmpLen);

    Bitmap := TBitmap.Create;
    try
      JpegImage := TJpegImage.Create;
      try
        JpegStream := TMemoryStream.Create;
        try
          TIdDecoderMIME.DecodeStream(Data, JpegStream);
          JpegStream.Position := 0;
          JpegImage.LoadFromStream(JpegStream);
        finally
          JpegStream.Free;
        end;
        with Bitmap do
        begin
          Canvas.Lock;
          try
            Width := JpegImage.Width;
            Height := JpegImage.Height;
            Canvas.Draw(0, 0, JpegImage);
          finally
            Canvas.Unlock;
          end;
        end;
      finally
        JpegImage.Free;
      end;
      img.Picture.Assign(Bitmap);
    finally
      Bitmap.Free;
    end;        
  except
    on E: Exception do
      //
  end;
end;