2016-11-28 1 views
3

Ich versuche, einen Stream von einem mobilen Gerät (iOS, Android) an TCP-Server zu senden. Für die Server- und Client-Seite verwende ich die Indy-Komponenten.TIdiOHandler.Write-Prozedur funktioniert nicht von mobilen Geräten

Das Problem tritt auf, wenn ich versuche, einen Stream von einer FMX-Anwendung auf einem mobilen Gerät zu senden. Wenn ich den Client-Code von Windows ausführe, sendet der Client den Stream an die Server-App. Aber ich benutze denselben Code von einem mobilen Gerät, der Stream wird nicht gesendet.

Dies ist ein minimales, vollständiges und überprüfbares Beispiel für den Server und den Client, mit dem das Problem reproduziert werden kann.

Serverseite. Der Server ist eine VCL Anwendung.

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. 

Client (FMX Anwendung)

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. 

Die Frage ist: Wie kann ich einen Stream mit der Indy-Komponente von einem mobilen Gerät senden ?.

UPDATE:

Android Berechtigungen

<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" /> 
+0

Haben Sie die entsprechenden Berechtigungen für den Netzzugang in der mobilen Anwendung Manifeste haben? –

+0

Ich habe gerade die Berechtigungen hinzugefügt. – Salvador

Antwort

1

Indy auf allen Plattformen die gleiche Art und Weise arbeitet, so sollte es keinen Unterschied in der, wie ein Strom gesendet oder empfangen wird.

Die einzigen Probleme, die ich in Ihrem Code zu sehen sind:

  1. ein Speicherleck im Client-Code, wenn unter Windows

  2. einen unnötigen Aufruf zu InputBuffer.Clear im Servercode

Aber ich sehe nichts, das das Problem verursachen würde, das Sie beschrieben haben. Sie müssen die Kommunikation mit einem Debugger und Paket-Sniffer debuggen, um herauszufinden, was schief läuft.

Die übertragenen Bytes sollte wie folgt aussehen:

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

Gefolgt von 1024 Byte Zufallsdaten (da Sie nicht die TMemoryStream mit irgendwelchen aussagekräftige Daten bevölkern).

Das heißt, Sie müssen nicht wirklich d an den ASize Parameter von TIdIOHandler.Write(TStream) in diesem Beispiel übergeben. Sie könnten statt dessen -1 (alle Daten von aktuell Position) oder 0 (der gesamte Datenstrom) übergeben. Der Standardwert ist 0.

+0

Dank Remy für den Speicherverlust im Client und die 'InputBuffer.Clear' .call (Dies ist nur ein Beispielcode, um das Problem anzuzeigen). Ich habe seit Tagen mit diesem Problem gekämpft, ich habe wirklich einen Android-Paket-Sniffer verwendet, zeigt aber nur die Daten, die von der 'IOHandler.WriteLn'-Prozedur gesendet werden, keine Daten werden für die IOHandler.Write-Prozedur gesendet, wenn sie mit einem Stream verwendet wird Parameter. – Salvador

+0

Ich versichere Ihnen, dass 'Write (TStream)' funktioniert. Es verwendet die gleiche 'Write (TIdBytes)' Überladung, die 'WriteLn()' intern verwendet. Selbst wenn die "TStream" -Daten nicht gesendet würden, wäre die Stream-Größe. Also muss etwas anderes vor sich gehen. Stellen Sie sicher, dass 'IOHandler.SendBufferSize'> 0 ist,' LStream.Size' wirklich> 0 ist. Verwenden Sie den Debugger, um bei Bedarf in Indys Quellcode zu wechseln. –

+0

Ich habe gerade den Wert der 'IOHandler.SendBufferSize' Eigenschaft überprüft und der Wert ist' 32768' und die Stream Größe ist '1024'. Außerdem habe ich alle verwandten Funktionen debugged: 'TIdiOHandler.Write' ->' TIdIOHandler.WriteDirect' ........ -> 'TIdStackVCLPosix.WSSend', schließlich endete ich im Aufruf des' Posix.SysSocket.send 'die 1024 (die Anzahl der geschriebenen Bytes) zurückgibt, aber keine Daten gesendet werden. – Salvador

2

Endlich fand ich das Problem, ich machte einen Fehler zu denken, die Daten wurden nicht gesendet, der Stream wird gesendet, aber der Server kann den Stream nicht verarbeiten, weil die TIdIOHandler.ReadStream Funktion nicht korrekt die Größe des Streams liest. Dies geschieht, wenn im Parameter AByteCount ein Wert von -1 übergeben wird.Dann werden die TIdIOHandler.ReadInt64 oder TIdIOHandler.ReadInt32 Funktionen verwendet, um die Größe des Streams zu lesen, und intern versuchen diese Funktionen, die Endianness der Ganzzahl mit der Funktion GStack.NetworkToHost zu konvertieren.

Ich behob das Problem beim Lesen der Stream-Größe ohne Konvertierung der Endianess.

I ersetzt diese Zeile

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

für diesen Code

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