Android 如何为下一个客户端连接正确重新启动Indy TCP服务器?

Android 如何为下一个客户端连接正确重新启动Indy TCP服务器?,android,delphi,tcp,indy,delphi-10.1-berlin,Android,Delphi,Tcp,Indy,Delphi 10.1 Berlin,我有一个Indy TCP服务器,客户端连接到它,发送信息,服务器接收,创建一个巨大的TStringList并发送回客户端。这种情况每天发生数千次,因此我决定将六台服务器放在不同的.exe上,在不同的端口上执行相同的操作,并将客户端应用程序每次连接到一个随机端口上 发生了什么: 1) 从客户第一次尝试连接到他收到所有他需要的信息的那一刻是相当高的,就像他做了两次工作一样 2) 大约10%的情况下,客户机尝试连接,而服务器似乎忽略了尝试,客户机除了重试之外,没有启动,并且卡住了 服务器: MaxC

我有一个Indy TCP服务器,客户端连接到它,发送信息,服务器接收,创建一个巨大的TStringList并发送回客户端。这种情况每天发生数千次,因此我决定将六台服务器放在不同的.exe上,在不同的端口上执行相同的操作,并将客户端应用程序每次连接到一个随机端口上

发生了什么:

1) 从客户第一次尝试连接到他收到所有他需要的信息的那一刻是相当高的,就像他做了两次工作一样

2) 大约10%的情况下,客户机尝试连接,而服务器似乎忽略了尝试,客户机除了重试之外,没有启动,并且卡住了

服务器:

  • MaxConnections
    设置为1
  • ListenQueue
    0
  • 终止等待时间
    至5000
  • ReuseSocket
    在客户端和服务器上设置为False
  • UsaNagle
    :=在客户端和服务器上为True
尝试连接到服务器的客户端6秒
计时器

  IdDownloadClient.IOHandler.MaxLineLength := MaxInt;
  IdDownloadClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
  // Choose a random port
  IdDownloadClient.Connect;
  IdDownloadClient.IOHandler.WriteLn(lat+','+long);
  Timer6.Enabled := True;
客户端
定时器6
(25毫秒):

服务器
OnExecute
事件:

begin
try
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
LatLong := AContext.Connection.IOHandler.ReadLn;
if LatLong <> '' then begin
latF := StrToFloat(StringReplace(Copy(LatLong,0,ansipos(',',LatLong)-1),'.',',',[rfIgnoreCase, rfReplaceAll]));
lonF := StrToFloat(StringReplace(Copy(LatLong,ansipos(',',LatLong)+1,11),'.',',',[rfIgnoreCase, rfReplaceAll]));

// Creates a TStringList from a Memo to send (around half a sec of proccessing)
bufferlist := TStringList.Create;
bufferlist.Add('h-023.64086400000,-046.57425900000 99999999 0300 0301 0001 test|123 test');
  for J := 0 to Memo1.Lines.Count-2 do
    begin
      if ((abs(latF-StrToFloat(StringReplace(Copy(Memo1.Lines[J],2,16),'.',',',[rfIgnoreCase, rfReplaceAll]))) < 0.1) and (abs(lonF-StrToFloat(StringReplace(Copy(Memo1.Lines[J],19,16),'.',',',[rfIgnoreCase, rfReplaceAll]))) < 0.1)) then
      bufferlist.Add(Memo1.Lines[J]);
    end;

///////// Start to send
  for i := 0 to bufferlist.Count-1 do
    begin
      AContext.Connection.IOHandler.WriteLn(bufferlist[i]);
    end;

AContext.Connection.IOHandler.WriteLn('@'); // Send '@' to the client to say the list is over
bufferlist.Free;
end;
except
    if Assigned(bufferlist) then bufferlist.Free;
    Exit;
end;
end;
开始
尝试
AContext.Connection.IOHandler.DefStringEncoding:=IndyTextEncoding\u UTF8;
LatLong:=AContext.Connection.IOHandler.ReadLn;
如果LatLong“”则开始
latF:=StrToFloat(StringReplace(复制(LatLong,0,ansipos(',',',,LatLong)-1),,,,,,,,[rfIgnoreCase,rfReplaceAll]);
lonF:=StrToFloat(StringReplace(复制(LatLong,ansipos(',',',LatLong)+1,11),,,,,,,,[rfIgnoreCase,rfReplaceAll]);
//从要发送的备忘录创建一个TStringList(大约半秒的处理时间)
bufferlist:=TStringList.Create;
缓冲区列表。添加('h-023.64086400000,-046.57425900000 9999990300 0301 0001试验| 123试验');
对于J:=0到1.Lines.Count-2 do
开始
如果((abs(latF StrToFloat(StringReplace)(复制(备忘录1.行[J],2,16),“,”,“,[rfIgnoreCase,rfReplaceAll]))小于0.1)和(abs(lonF StrToFloat(复制(备忘录1.行[J],19,16),“,”,[rfIgnoreCase,rfReplaceAll]))小于0.1),则
bufferlist.Add(备忘录1.行[J]);
结束;
/////////开始发送
对于i:=0的bufferlist.Count-1 do
开始
AContext.Connection.IOHandler.WriteLn(bufferlist[i]);
结束;
AContext.Connection.IOHandler.WriteLn('@');//将“@”发送到客户端以说明列表已结束
bufferlist.Free;
结束;
除了
如果已分配(bufferlist),则bufferlist.Free;
出口
结束;
结束;
由于所有的连接都来自3G/4G手机,我假设其中一些手机的终端坏了,这就是问题的原因,那么我在代码中做错了什么


我可以做些什么来解决这个问题,或者至少改进它?

您的服务器的
OnExecute
正在吞噬所有引发的异常。当客户端断开连接时,在该客户端的套接字上执行的下一个套接字I/O将引发异常,您将捕获并丢弃该异常。因此,服务器将不知道客户端已断开连接,并将继续触发
OnExecute
事件。而且,由于您的服务器设置为
MaxConnections=1
,因此在前一个客户端完全释放之前,新客户端无法连接到服务器。您的
OnExecute
代码必须直接调用
AContext.Connection.Disconnect()
,或引发未捕获的异常,以释放客户端线程

简单的经验法则-不要接受例外情况!如果捕获到不知道如何处理的异常,则应重新引发它,因为它可能在调用堆栈的更高层进行处理。对于
TIdTCPServer
,永远不要吞下从
eideexception
派生的Indy异常,让服务器来处理它。您使用
try/except
来释放
bufferlist
应该改为
try/finally

事实上,我建议不要使用
TStringList
来收集响应数据,因为它实际上并没有帮助您,而是妨碍了服务器的性能。在完整构建整个
TStringList
之前,客户端不会收到来自服务器的任何响应,这可能会导致客户端超时。最好在创建文本时将每行文本发送给客户机,这样客户机就知道服务器实际上在做什么,而不是死机/冻结

我在
OnExecute
代码中看到的另一个问题是,它直接访问UI控件(a
TMemo
),而不与UI线程同步
TIdTCPServer
是一个多线程组件,其事件在工作线程中激发。在工作线程中访问UI控件时,必须与UI线程同步

最后,您对
Copy()
StringReplace()
的过度使用是一个严重的问题,并且通常使代码难以维护

请尝试类似以下内容:

procedure IdTCPServer1Connect(AContext: TIdContext);
begin
  // do this assignment one time, not on every OnExecute loop iteration
  AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;

procedure IdTCPServer1Execute(AContext: TIdContext);
var
  s: string;
  latF, lonF: Double;
  fmt: TFormatSettings;
  lines: TStringList;
  j: Integer;
begin
  s := AContext.Connection.IOHandler.ReadLn;
  if s = '' then Exit;

  fmt := TFormatSettings.Create;
  fmt.DecimalSeparator := '.';

  latF := StrToFloat(Fetch(s, ','), fmt);
  lonF := StrToFloat(s, fmt);

  AContext.Connection.IOHandler.WriteLn('h-023.64086400000,-046.57425900000 99999999 0300 0301 0001 test|123 test');

  lines := TStringList.Create;
  try
    TThread.Synchronize(nil,
      procedure
      begin
        lines.Assign(Memo1.Lines);
      end
    );

    for J := 0 to lines.Count-2 do
    begin
      s := lines[J];
      if (abs(latF-StrToFloat(Copy(s, 2, 16), fmt)) < 0.1) and (abs(lonF-StrToFloat(Copy(s, 19, 16), fmt)) < 0.1) then
        AContext.Connection.IOHandler.WriteLn(s);
    end;
  finally
    lines.Free;
  end;

  AContext.Connection.IOHandler.WriteLn('@'); // Send '@' to the client to say the list is over
end;

服务器的
OnExecute
正在吞咽所有引发的异常。当客户端断开连接时,在该客户端的套接字上执行的下一个套接字I/O将引发异常,您将捕获并丢弃该异常。因此,服务器将不知道客户端已断开连接,并将继续触发
OnExecute
事件。而且,由于您的服务器设置为
MaxConnections=1
,因此在前一个客户端完全释放之前,新客户端无法连接到服务器。您的
OnExecute
代码必须直接调用
AContext.Connection.Disconnect()
,或引发未捕获的异常,以释放客户端线程

简单的经验法则-不要接受例外情况!如果捕获到不知道如何处理的异常,则应重新引发它,因为它可能在调用堆栈的更高层进行处理。对于
TIdTCPServer
,永远不要接受派生的Indy异常
procedure IdTCPServer1Connect(AContext: TIdContext);
begin
  // do this assignment one time, not on every OnExecute loop iteration
  AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;

procedure IdTCPServer1Execute(AContext: TIdContext);
var
  s: string;
  latF, lonF: Double;
  fmt: TFormatSettings;
  lines: TStringList;
  j: Integer;
begin
  s := AContext.Connection.IOHandler.ReadLn;
  if s = '' then Exit;

  fmt := TFormatSettings.Create;
  fmt.DecimalSeparator := '.';

  latF := StrToFloat(Fetch(s, ','), fmt);
  lonF := StrToFloat(s, fmt);

  AContext.Connection.IOHandler.WriteLn('h-023.64086400000,-046.57425900000 99999999 0300 0301 0001 test|123 test');

  lines := TStringList.Create;
  try
    TThread.Synchronize(nil,
      procedure
      begin
        lines.Assign(Memo1.Lines);
      end
    );

    for J := 0 to lines.Count-2 do
    begin
      s := lines[J];
      if (abs(latF-StrToFloat(Copy(s, 2, 16), fmt)) < 0.1) and (abs(lonF-StrToFloat(Copy(s, 19, 16), fmt)) < 0.1) then
        AContext.Connection.IOHandler.WriteLn(s);
    end;
  finally
    lines.Free;
  end;

  AContext.Connection.IOHandler.WriteLn('@'); // Send '@' to the client to say the list is over
end;
procedure Timer6Timer(Sender: TObject);
var
  response: TStringList;
begin
  with IdDownloadClient do
  begin
    try
      if IOHandler.InputBufferIsEmpty then
      begin
        IOHandler.CheckForDataOnSource(0);
        IOHandler.CheckForDisconnect;
        if IOHandler.InputBufferIsEmpty then Exit;
      end;

      response := TStringList.Create;
      try
        // The server send an '@' to say the complete response has been sent
        IOHandler.Capture(response, '@', False);

        // use response as needed...
      finally
        response.Free;
      end;
    except
      IOHandler.InputBuffer.Clear;
    end;
    Disconnect;
  end;
  Timer6.Enabled := False;
end;