【问题标题】:TIdIOHandler.Write procedure is not working from mobile devicesTIdIOHandler.Write 过程在移动设备上不起作用
【发布时间】:2016-11-28 18:45:37
【问题描述】:

我正在尝试将流从移动设备(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 组件从移动设备发送流?

更新:

安卓权限

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

【问题讨论】:

  • 您在移动应用程序清单中是否拥有适当的网络访问权限?
  • 我刚刚添加了使用的权限。

标签: android ios delphi delphi-10.1-berlin


【解决方案1】:

最后我找到了问题,我误以为没有发送数据,发送了流,但是服务器无法处理流,因为TIdIOHandler.ReadStream函数没有正确读取流的大小.当在 AByteCount 参数中传递 -1 值时会发生这种情况。然后TIdIOHandler.ReadInt64TIdIOHandler.ReadInt32 函数用于读取流的大小,这些函数在内部尝试使用GStack.NetworkToHost 函数转换整数的字节顺序。

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

我替换了这一行

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

对于这个代码

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

【讨论】:

    【解决方案2】:

    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 传递给TIdIOHandler.Write(TStream)ASize 参数。您可以改为传递-1(来自当前Position 的所有数据)或0(整个流)。默认为0

    【讨论】:

    • 感谢 Remy 发现客户端内存泄漏和InputBuffer.Clear .call(这只是显示问题的示例代码)。我已经为这个问题苦苦挣扎了好几天,我真的使用了一个 android 数据包嗅探器,但只显示IOHandler.WriteLn 过程发送的数据,当与流参数一起使用时,IOHandler.Write 过程没有发送数据。
    • 我向你保证Write(TStream) 有效。它使用与WriteLn() 内部使用的相同的Write(TIdBytes) 重载。即使TStream 数据没有被发送,流大小也会。所以必须有其他事情发生。确保IOHandler.SendBufferSize > 0,LStream.Size 确实 > 0,等等。如果需要,请使用调试器进入 Indy 的源代码。
    • 我刚刚检查了IOHandler.SendBufferSize 属性的值,该值为32768,流大小为1024。我还调试了所有相关的函数:TIdIOHandler.Write - > TIdIOHandler.WriteDirect ..... -> TIdStackVCLPosix.WSSend,最后我结束了对Posix.SysSocket.send的调用,它返回1024(写入的字节数) 但是没有数据被发送。
    • @Salvador 这根本不可能。如果操作系统send() 接受字节,则只要存在活动连接,它们就会被传输。是什么让您认为数据没有被传输?你真的嗅探过两端的网络流量吗?
    • 我刚刚尝试使用 Wireshark 并发送了流(我的错)。问题出在 ReadStream 调用中的服务器部分。当 AByteCount 参数具有 -1 值(如 LHandler.ReadStream(LMemoryStream, -1, False);)时,使用 ReadInt64ReadInt32 函数。读取流的大小,这些函数在内部尝试转换整数的字节顺序。
    猜你喜欢
    • 1970-01-01
    • 2022-01-12
    • 2014-04-22
    • 1970-01-01
    • 2017-07-27
    • 1970-01-01
    • 1970-01-01
    • 2019-07-31
    • 2016-08-11
    相关资源
    最近更新 更多