使用 Indy TCPClient/TCPServer 从手机 XE8 发送图片

Using Indy TCPClient/TCPServer to send picture from mobile XE8

我有一个用 Delphi XE8 编写的简单移动应用程序,允许用户拍照,然后使用 Indy TCPClient/TCP Server 将照片发送到服务器。

我搜索了论坛并找到了很多以各种方式发送数据的示例。我尝试的每一种方法都会在服务器端导致访问冲突或数据损坏。

我的最终目标是将包含唯一标识符、描述和图片(位图)的记录从客户端发送到服务器。 但我开始尝试简单地从 windows 客户端向服务器发送带有一些文本的记录。然后我会尝试将解决方案实施到我的移动应用程序中。

type
  TSendRec = record
//    SONo: string;
    Text: string;
//    Bitmap: TBitMap;
  end

我已经按照下面的代码尝试了以下3种方法:

  1. 使用流发送

  2. 使用 RawToBytes 和 TIDBytes 发送。

  3. 使用WritelnReadln

  4. 发送一行文本

当我尝试使用流发送时,我遇到了以下访问冲突:

Project memorystream_server.exe raised the exception class $C0000005 with message 'access violation at 0x00409e46: write of address 0x0065d1bc

当我尝试在服务器端访问 MiRec.Text 的值时发生错误。

Memo1.Lines.Add(MiRec.Text);

所以我假设 MIRec 的读取由于某种原因失败了:

当我使用 RawToBytes 发送时,没有出现错误消息,但 MIRec.Text 的值是垃圾。

当我使用 WriteLn 发送一行文本时,服务器正确接收并显示数据,没有发生访问冲突。

我尝试遵循从其他帖子中找到的有关此问题的示例。如果能深入了解我做错了什么,我将不胜感激。

以下是我的客户端和服务器端代码片段:

客户端

procedure TfrmMemoryStreamClient.btnSendClick2(Sender: TObject);
var
  Buffer: TIdBytes;
  MIRec: TSendRec;
  msRecInfo: TMemoryStream;
  msRecInfo2: TIdMemoryBufferStream;
begin
  IdTCPClient1.Connect;

  MIRec.Text := 'Hello World';

  if rbSendStream.Checked then
  begin
    msRecInfo := TMemoryStream.Create;
    try
      msRecInfo.Write(MIRec, SizeOf(MIRec));
      IdTCPClient1.IOHandler.Write(msRecInfo, 0, False);
    finally
      msRecInfo.Free;
    end;
{
    msRecInfo2 := TIdMemoryBufferStream.Create(@MIRec, SizeOf(TSendRec));
    try
      IdTCPClient1.IOHandler.Write(msRecInfo2);
    finally
      msRecInfo.Free;
    end;
}
  end
  else
  if rbSendBytes.Checked then
  begin
    Buffer := RawToBytes(MIRec, SizeOf(MIRec));
    IdTCPClient1.IOHandler.Write(Buffer);
  end
  else
  if rbWriteLn.Checked then
  begin
    IdTCPClient1.Socket.WriteLn(Edit1.Text);
  end;

  IdTCPClient1.DisConnect;
end;

服务器

procedure TStreamServerForm.IdTCPServer1Execute(AContext: TIdContext);
var sName: String;
  MIRec: TSendRec;
  Buffer: TIdBytes;
  msRecInfo: TMemoryStream;
begin

  if not chkReceiveText.Checked then
  begin
    try
      if chkReadBytes.Checked then
      begin
        AContext.Connection.IOHandler.ReadBytes(Buffer, SizeOf(MIRec));
        BytesToRaw(Buffer, MIRec, SizeOf(MIRec));
        Memo1.Lines.Add(MiRec.Text);
      end
      else
      begin
        msRecInfo := TMemoryStream.Create;

        try
          // does not read the stream size, just the stream data
          AContext.Connection.IOHandler.ReadStream(msRecInfo, SizeOf(MIRec), False);

          msRecInfo.Position := 0;
          msRecInfo.Read(MIRec, SizeOf(MIRec));
          Memo1.Lines.Add(MiRec.Text);
        finally
          msRecInfo.Free;
        end;
{
        AContext.Connection.IOHandler.ReadStream(msRecInfo, -1, False);
        msRecInfo.Position := 0;
        msRecInfo.Read(MIRec, SizeOf(MIRec));
        Memo1.Lines.Add(MiRec.Text);
}
      end;

      Memo1.Lines.Add('read File');
    except
      Memo1.Lines.Add('error in read File');
    end;
  end
  else
  begin
    sName := AContext.Connection.Socket.ReadLn;
    Memo1.Lines.Add(sName);
  end;

  AContext.Connection.Disconnect;
end;

TIdTCPServer 是一个多线程组件。它的 OnConnectOnDisconnectOnExecute 事件在工作线程的上下文中触发。因此,在访问 UI 控件(如备忘录)时,您必须与主 UI 线程同步。

此外,String 是编译器管理的数据类型,TBitmap 是对象。两者都将它们的数据存储在内存中的其他地方,因此您不能按原样写入包含此类字段的记录。您将只写入它们的数据指针的值,而不是写入所指向的实际数据。您需要在发送方将您的记录序列化为可传输的格式,然后在接收方将其反序列化。这意味着单独处理记录字段。

试试像这样的东西:

type
  TSendRec = record
    SONo: string;
    Text: string;
    Bitmap: TBitMap;
  end;

客户

procedure TfrmMemoryStreamClient.btnSendClick2(Sender: TObject);
var
  MIRec: TSendRec;
  ms: TMemoryStream;
begin
  MIRec.SONo := ...;
  MIRec.Text := 'Hello World';
  MIRec.Bitmap := TBitmap.Create;
  ...
  try
    IdTCPClient1.Connect;
    try
      IdTCPClient1.IOHandler.WriteLn(MIRec.SONo);
      IdTCPClient1.IOHandler.WriteLn(MIRec.Text);
      ms := TMemoryStream.Create;
      try
        MIRec.Bitmap.SaveToStream(ms);
        IdTCPClient1.IOHandler.LargeStream := True;
        IdTCPClient1.IOHandler.Write(ms, 0, True);
      finally
        ms.Free;
      end;
    finally
      IdTCPClient1.Disconnect;
    end;
  finally
    MIRec.Bitmap.Free;
  end;
end;

服务器

procedure TStreamServerForm.IdTCPServer1Execute(AContext: TIdContext);
var
  MIRec: TSendRec;
  ms: TMemoryStream;
begin
  MIRec.SONo := AContext.Connection.IOHandler.ReadLn;
  MIRec.Text := AContext.Connection.IOHandler.ReadLn;
  MIRec.Bitmap := TBitmap.Create;
  try
    ms := TMemoryStream.Create;
    try
      AContext.Connection.IOHandler.LargeStream := True;
      AContext.Connection.IOHandler.ReadStream(ms, -1, False);
      ms.Position := 0;
      MIRec.Bitmap.LoadFromStream(ms);
    finally
      ms.Free;
    end;
    TThread.Synchronize(nil,
      procedure
      begin
        Memo1.Lines.Add(MIRec.SONo);
        Memo1.Lines.Add(MIRec.Text);
        // display MIRec.Bitmap as needed...
      end;
    end;
  finally
    MIRec.Bitmap.Free;
  end;
end;

或者:

客户

procedure TfrmMemoryStreamClient.btnSendClick2(Sender: TObject);
var
  MIRec: TSendRec;
  ms: TMemoryStream;

  procedure SendString(const S: String);
  var
    Buf: TIdBytes;
  begin
    Buf := IndyTextEncoding_UTF8.GetBytes(S);
    IdTCPClient1.IOHandler.Write(Int32(Length(Buf)));
    IdTCPClient1.IOHandler.Write(Buf);
  end;

begin
  MIRec.SONo := ...;
  MIRec.Text := 'Hello World';
  MIRec.Bitmap := TBitmap.Create;
  ...
  try
    IdTCPClient1.Connect;
    try
      SendString(MIRec.SONo);
      SendString(MIRec.Text);
      ms := TMemoryStream.Create;
      try
        MIRec.Bitmap.SaveToStream(ms);
        IdTCPClient1.IOHandler.LargeStream := True;
        IdTCPClient1.IOHandler.Write(ms, 0, True);
      finally
        ms.Free;
      end;
    finally
      IdTCPClient1.Disconnect;
    end;
  finally
    MIRec.Bitmap.Free;
  end;
end;

服务器

procedure TStreamServerForm.IdTCPServer1Execute(AContext: TIdContext);
var
  MIRec: TSendRec;
  ms: TMemoryStream;

  function RecvString: String;
  begin
    Result := AContext.Connection.IOHandler.ReadString(
      AContext.Connection.IOHandler.ReadInt32,
      IndyTextEncoding_UTF8);
  end;

begin
  MIRec.SONo := RecvString;
  MIRec.Text := RecvString;
  MIRec.Bitmap := TBitmap.Create;
  try
    ms := TMemoryStream.Create;
    try
      AContext.Connection.IOHandler.ReadStream(ms, -1, False);
      ms.Position := 0;
      MIRec.Bitmap.LoadFromStream(ms);
    finally
      ms.Free;
    end;
    TThread.Synchronize(nil,
      procedure
      begin
        Memo1.Lines.Add(MIRec.SONo);
        Memo1.Lines.Add(MIRec.Text);
        // display MIRec.Bitmap as needed...
      end;
    end;
  finally
    MIRec.Bitmap.Free;
  end;
end;