TIdIOHandler.Write程序不适用于移动设备
我试图从移动设备(iOS,Android)向TCP服务器发送流。对于服务器和客户端,我使用Indy组件。TIdIOHandler.Write程序不适用于移动设备
当我尝试从运行在移动设备中的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.
问题是,如何可以使用印部件从移动设备发送一个流?
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" />
印运行在所有平台上以同样的方式,所以应该有一个流是如何发送或接收的没有区别。
我在你的代码中看到的唯一的问题是:
在客户端代码中的内存泄漏在Windows上运行时
在服务器代码
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
。
感谢Remy在客户端和'InputBuffer.Clear'.call中发现内存泄漏(这只是一个示例代码来显示问题)。我一直在努力解决这个问题好几天,我真的使用了一个android数据包嗅探器,但只显示IOHandler.WriteLn程序发送的数据,当与流一起使用时,IOHandler.Write程序没有发送数据参数。 – Salvador
我向你保证'写(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
最后,我发现这个问题,我犯了一个错误的思维,其数据没有被发送,该流发送,但因为TIdIOHandler.ReadStream
函数没有被正确读取流的大小,服务器无法处理的数据流。当在AByteCount
参数中传递-1值时会发生这种情况。然后使用TIdIOHandler.ReadInt64
或TIdIOHandler.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);
你有在移动应用程序清单的网络接入相应的权限? –
我刚刚添加了使用的权限。 – Salvador