2016-11-28 2 views
3

Я пытаюсь отправить поток с мобильного устройства (iOS, Android) на TCP-сервер. Для сервера и клиента я использую компоненты Indy.Процедура TIdIOHandler.Write не работает с мобильных устройств

Проблема возникает, когда я пытаюсь отправить поток из приложения FMX, запущенного на мобильном устройстве. Если я запустил клиентский код из Windows, клиент отправит поток в приложение Server. Но я запускаю тот же код с мобильного устройства, поток не отправляется.

Это пример минимального, полного и проверяемого для сервера и клиента, который может воспроизвести проблему.

Сторона сервера. Сервер представляет собой приложение 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. 

Client (FMX Application)

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 компонента с мобильного устройства ?.

UPDATE:

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

У вас есть соответствующие разрешения для доступа к сети в мобильных манифестах приложений? –

+0

Я просто добавил используемые разрешения. – Salvador

ответ

1

Indy работает одинаково на всех платформах, так что не должно быть никакой разницы в том, как поток отправки или получения.

Единственные проблемы я вижу в вашем коде:

  1. утечка памяти в коде клиента при работе в Windows,

  2. ненужный вызов InputBuffer.Clear в коде сервера

Но я не вижу ничего, что могло бы вызвать проблему, о которой вы описали. Вам придется отлаживать сообщения с помощью отладчика и сниффера пакетов, чтобы узнать, что происходит не так.

Передаваемый байт должен выглядеть следующим образом:

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.

+0

Спасибо Remy за место утечки памяти в клиенте и 'InputBuffer.Clear' .call (это всего лишь пример кода, чтобы показать проблему). Я несколько дней борется с этой проблемой, я действительно использовал андроидный пакетный сниффер, но только показывает данные, отправленные с помощью процедуры «IOHandler.WriteLn», никакие данные не отправляются для IOHandler. Процедура письма, когда используется с потоком параметр. – Salvador

+0

Уверяю вас, что 'Write (TStream)' работает. Он использует ту же самую перезагрузку 'Write (TIdBytes), что' WriteLn() 'использует внутренне. Даже если данные 'TStream' не отправляются, размер потока будет. Так что еще что-то должно происходить. Убедитесь, что 'IOHandler.SendBufferSize'> 0,' LStream.Size' действительно есть> 0 и т. Д. Используйте отладчик, чтобы войти в исходный код Indy, если вам нужно. –

+0

Я только что проверил значение свойства 'IOHandler.SendBufferSize', а значение равно' 32768', а размер потока - '1024'. Также я отлаживал все связанные функции: 'TIdIOHandler.Write' ->' TIdIOHandler.WriteDirect' ........ -> 'TIdStackVCLPosix.WSSend', наконец, я закончил вызов' Posix.SysSocket.send ', который возвращает 1024 (количество записанных байтов), однако данные не отправляются. – Salvador

2

Наконец-то я нашел ошибку, я ошибся, думая, что данные не были отправлены. Поток отправляется, но сервер не может обработать поток, потому что функция TIdIOHandler.ReadStream не правильно считывает размер потока. Это происходит, когда значение -1 передается в параметре AByteCount.Затем функции TIdIOHandler.ReadInt64 или TIdIOHandler.ReadInt32 используются для считывания размера потока, и внутренне эти функции пытаются преобразовать значение Endianness целого с помощью функции GStack.NetworkToHost.

Я исправил проблему, читая размер потока без преобразования endianess.

я заменил эту строку

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

для этого кода

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