我正在尝试将流从移动设备(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" />
答案 0 :(得分:2)
最后我发现了这个问题,我错误地想到了哪些数据没有发送,流被发送,但是服务器无法处理流,因为TIdIOHandler.ReadStream
功能没有正确读取流的大小。在AByteCount
参数中传递-1值时会发生这种情况。然后使用TIdIOHandler.ReadInt64
或TIdIOHandler.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在所有平台上都以相同的方式运行,因此流的发送或接收方式应该没有区别。
我在代码中看到的唯一问题是:
在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
。