TIdIOHandler.Write程序无法在移动设备上运行

时间:2016-11-28 18:45:38

标签: android ios delphi delphi-10.1-berlin

我正在尝试将流从移动设备(iOS,Android)发送到TCP服务器。对于服务器端和客户端,我使用的是Indy组件。

当我尝试从移动设备中运行的FMX应用程序发送流时​​,会出现此问题。如果我从Windows运行客户端代码,客户端会将流发送到服务器应用程序。但我从移动设备运行相同的代码,不发送流。

这是服务器和客户端的最小,完整和可验证的示例,可以重现该问题。

服务器端。服务器是 VCL 应用程序。

unit uServer;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdBaseComponent, IdComponent,
  IdCustomTCPServer, IdTCPServer, Vcl.StdCtrls, IdContext;

type
  TFrmServer = class(TForm)
    IdTCPServer1: TIdTCPServer;
    MemoLog: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure IdTCPServer1Execute(AContext: TIdContext);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  FrmServer: TFrmServer;

implementation

uses
  IdGlobal,
  IdIOHandler,
  System.StrUtils;

{$R *.dfm}

procedure TFrmServer.FormCreate(Sender: TObject);
begin
  IdTCPServer1.Bindings.Clear;
  IdTCPServer1.DefaultPort := 28888;
  IdTCPServer1.Active := True;
  MemoLog.Lines.Add('Running');
end;

procedure TFrmServer.IdTCPServer1Execute(AContext: TIdContext);
var
  LHandler  : TIdIOHandler;
  s: string;
  LMemoryStream : TMemoryStream;
  AFormatSettings: TFormatSettings;
  d : Int64;
begin
  try
    LHandler := AContext.Connection.IOHandler;
    s := LHandler.ReadLn(LF, IdTimeoutDefault, MaxInt);
    AFormatSettings := TFormatSettings.Create;
    if (s <> '') then
    begin
       if StartsText('<',  s) and EndsText('>',  s)  then
       begin
            TThread.Queue(nil,
              procedure
              begin
                MemoLog.Lines.Add(Format('%s', [s], AFormatSettings));
              end
            );
            LMemoryStream := TMemoryStream.Create;
            try
                LHandler.LargeStream := True;
                LHandler.ReadStream(LMemoryStream, -1, False);
                d := LMemoryStream.Size;
                TThread.Queue(nil,
                  procedure
                  begin
                    MemoLog.Lines.Add(Format('Stream Size %d', [d], AFormatSettings));
                  end
                );
            finally
              LMemoryStream.Free;
            end;
       end
       else
         LHandler.InputBuffer.Clear;
    end;
  except
    on E: Exception do
    begin
      s := E.Message;
      TThread.Queue(nil,
        procedure
        begin
          MemoLog.Lines.Add(Format('Exception %s', [s], AFormatSettings));
        end
      );
    end;
  end;
end;

end.

客户(FMX申请)

unit uClient;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, FMX.ScrollBox,
  FMX.Memo, FMX.Controls.Presentation, FMX.StdCtrls;

type
  TFrmClient = class(TForm)
    IdTCPClient1: TIdTCPClient;
    Button1: TButton;
    MemoLog: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    procedure Send;
  public
    { Public declarations }
  end;

var
  FrmClient: TFrmClient;

implementation

{$R *.fmx}

type

  TSendThread = class(TThread)
  private
    FTCPClient : TIdTCPClient;
  public
    procedure Execute; override;
    constructor Create(ATCPClient : TIdTCPClient); reintroduce;
  end;

procedure TFrmClient.Button1Click(Sender: TObject);
begin
   Send;
end;

procedure TFrmClient.FormCreate(Sender: TObject);
begin
 try
  IdTCPClient1.Port := 28888;
  IdTCPClient1.Host := '192.168.1.134'; //change this to the ip of the  TCP server.
  IdTCPClient1.ConnectTimeout := 5000;
  IdTCPClient1.Connect();
  MemoLog.Lines.Add('Connected');

  except on E: Exception do
     MemoLog.Lines.Add('Exception ' + E.Message);
 end;
end;

procedure TFrmClient.Send;
begin
  if IdTCPClient1.Connected then
    TSendThread.Create(IdTCPClient1);
end;

{ TSendThread }

constructor TSendThread.Create(ATCPClient: TIdTCPClient);
begin
  inherited Create(False);
  FTCPClient := ATCPClient;
end;

procedure TSendThread.Execute;
var
  LStream : TStream;
  d : Int64;
begin
   LStream := TMemoryStream.Create;
   try
     //Send a text from all the platforms works perfect.
     FTCPClient.IOHandler.WriteLn('<Hello>');
     LStream.Size := 1024;
     LStream.Position := 0;
     d := LStream.Size;
     FTCPClient.IOHandler.LargeStream := True;
     //this only works from Windows
     FTCPClient.IOHandler.Write(LStream,  d, True);
   finally
     LStream.Free;
   end;
end;

end.

问题是,如何使用移动设备中的Indy组件发送流?

更新:

Android权限

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

2 个答案:

答案 0 :(得分:2)

最后我发现了这个问题,我错误地想到了哪些数据没有发送,流被发送,但是服务器无法处理流,因为TIdIOHandler.ReadStream功能没有正确读取流的大小。在AByteCount参数中传递-1值时会发生这种情况。然后使用TIdIOHandler.ReadInt64TIdIOHandler.ReadInt32函数来读取流的大小,并在内部这些函数尝试使用GStack.NetworkToHost函数转换整数的Endianness。

我修复了读取流大小而不转换字节的问题。

我替换了这一行

LHandler.ReadStream(LMemoryStream, -1, False);

代码

LHandler.LargeStream := True;
LHandler.ReadBytes(LBytes, SizeOf(Int64), False);
d := BytesToInt64(LBytes);
LHandler.ReadStream(LMemoryStream, d, False);

答案 1 :(得分:1)

Indy在所有平台上都以相同的方式运行,因此流的发送或接收方式应该没有区别。

我在代码中看到的唯一问题是:

  1. 在Windows上运行时客户端代码中的内存泄漏

  2. 在服务器代码中对InputBuffer.Clear进行不必要的调用

  3. 但我没有看到任何会导致你所描述的问题的事情。您必须使用调试器和数据包嗅探器调试通信,以找出问题所在。

    传输的字节如下所示:

    3C 48 65 6C 6C 6F 3E 0D 0A 00 00 00 00 00 00 04 00
    

    随后是1024字节的随机数据(因为您没有使用任何有意义的数据填充TMemoryStream)。

    话虽如此,在此示例中,您并不需要将d传递给ASize TIdIOHandler.Write(TStream)参数。您可以传递-1(当前Position的所有数据)或0(整个流)。默认值为0