Using Indy TCPClient/TCPServer to send picture from mobile XE8

时间:2015-09-14 15:26:08

标签: delphi mobile indy delphi-xe8

I have a simple mobile application written in Delphi XE8 that allows the user to take a picture and then send the picture to a server using Indy TCPClient/TCP Server.

I have scoured the forums and found numerous examples to send the data in a variety of ways. Every method I try results in an access violation or corrupt data on the server side.

My ultimate goal is to send a record containing a unique identifier, description and a picture(bitmap) from the client to the server. But I'm starting out be trying to simply send a record with some text from a windows client to the server. I will then try to implement the solution into my mobile app.

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

I have tried the following 3 methods as per the code below:

  1. Send Using a Stream

  2. Send using RawToBytes and TIDBytes.

  3. Send a line of text using Writeln and Readln

When I try to send using a stream I get the following access violation:

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

The error occurs when I try to access the value of MiRec.Text on the server side.

Memo1.Lines.Add(MiRec.Text);

So I assume the read of the MIRec is failing for some reason:

When I send using RawToBytes, no error message occurs but the value of MIRec.Text is garbage.

When I just send a line of text using WriteLn, the server receives and displays the data correctly and no access violation occurs.

I tried to follow examples that I have found from other posts on this issue. I would greatly appreciate any insight into what I am doing wrong.

Following are my client and server side code snippets:

Client

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;

Server

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;

1 个答案:

答案 0 :(得分:0)

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

此外,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;