【问题标题】:how do I disconnect inactive clients with TIdTCPServer?如何断开与 TIdTCPServer 的非活动客户端?
【发布时间】:2023-04-01 05:27:01
【问题描述】:

我正在尝试断开连接到 TIdTCPServer 的非活动客户端,无论这些客户端是从其 Internet 断开连接还是在一段时间内处于非活动状态。

我尝试在OnConnect 事件中设置超时,如下所示:

procedure TservForm.TcpServerConnect(AContext: TIdContext);
begin
  AContext.Connection.IOHandler.ReadTimeout := 26000;
  AContext.Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_SNDTIMEO, 15000);
end; 

但是客户端连接丢失后似乎没有触发断开连接。

我尝试使用SetKeepAliveValues(),但断开非活动客户端需要花费太多时间。

有没有更有用的方法来断开非活动客户端的连接?那么如果客户端没有接收或发送任何东西,例如在 30 秒内,服务器会断开它吗?

关于执行事件

procedure TservForm.TcpServerExecute(AContext: TIdContext);
var
  Connection: TConnection;
  cmd: String;
  Cache, OutboundCmds: TStringList;
  MS: TMemoryStream;
  I: integer;
  S: String;
begin
  Connection := AContext as TConnection;

  // check for pending outbound commands...
  OutboundCmds := nil;
  try
    Cache := Connection.OutboundCache.Lock;
    try
      if Cache.Count > 0 then
      begin
        OutboundCmds := TStringList.Create;
        OutboundCmds.Assign(Cache);
        Cache.Clear;
      end;
    finally
      Connection.OutboundCache.Unlock;
    end;

    if OutboundCmds <> nil then
    begin
      for I := 0 to OutboundCmds.Count - 1 do
      begin
        AContext.Connection.IOHandler.Writeln(OutboundCmds.Strings[I],
          IndyTextEncoding_UTF8);
        MS := TMemoryStream(OutboundCmds.Objects[I]);
        if MS <> nil then
        begin
          AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
          AContext.Connection.IOHandler.LargeStream := true;
          AContext.Connection.IOHandler.Write(MS, 0, true);
        end;
      end;
    end;
  finally
    if OutboundCmds <> nil then
    begin
      for I := 0 to OutboundCmds.Count - 1 do
        OutboundCmds.Objects[I].Free;
    end;
    OutboundCmds.Free;
  end;

  // check for a pending inbound command...
  if AContext.Connection.IOHandler.InputBufferIsEmpty then
  begin
    AContext.Connection.IOHandler.CheckForDataOnSource(100);
    AContext.Connection.IOHandler.CheckForDisconnect;
    if AContext.Connection.IOHandler.InputBufferIsEmpty then
    begin
    Exit;
    end;
  end;

  cmd := AContext.Connection.Socket.ReadLn(IndyTextEncoding_UTF8);

  ...............
  ...............

【问题讨论】:

  • 您的OnExecute 事件处理程序是什么样的?您使用的超时取决于您正在执行的 I/O 类型。此外,SetKeepAliveValues() 确实可以检测死连接(但不是非活动连接),尽管并非所有平台都支持自定义 keepalive 超时间隔。另一种选择是运行一个工作线程来监视连接并在非活动超时过去后关闭它们的套接字。您可以使用TIdContext.Data 属性来跟踪上次发送/接收时间。
  • @RemyLebeau 执行添加,在客户端我设置计时器每 10 秒向服务器发送命令

标签: delphi indy indy10 delphi-xe8


【解决方案1】:

客户端没有断开连接,因为在空闲时间没有到达ReadLn(),所以ReadTimeout没有效果,如果你没有发送很多命令,那么套接字缓冲区没有填满所以@ 987654323@ 也没有效果。

由于您已经在进行一些手动超时处理,您可以对其进行扩展以处理空闲超时,例如:

type
  TConnection = class(TIdServerContext)
    ...
  public
    LastSendRecv: LongWord;
    ...
  end;

...

procedure TservForm.TcpServerConnect(AContext: TIdContext);
var
  Connection: TConnection;
begin
  Connection := AContext as TConnection;
  AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
  AContext.Connection.IOHandler.LargeStream := True;
  AContext.Connection.IOHandler.ReadTimeout := 30000;
  AContext.Binding.SetSockOpt(Id_SOL_SOCKET, Id_SO_SNDTIMEO, 15000);
  Connection.LastSendRecv := Ticks;
end; 

procedure TservForm.TcpServerExecute(AContext: TIdContext);
var
  Connection: TConnection;
  cmd: String;
  Cache, OutboundCmds: TStringList;
  MS: TMemoryStream;
  I: integer;
  S: String;
begin
  Connection := AContext as TConnection;

  // check for pending outbound commands...
  OutboundCmds := nil;
  try
    Cache := Connection.OutboundCache.Lock;
    try
      if Cache.Count > 0 then
      begin
        OutboundCmds := TStringList.Create;
        OutboundCmds.Assign(Cache);
        Cache.Clear;
      end;
    finally
      Connection.OutboundCache.Unlock;
    end;

    if OutboundCmds <> nil then
    begin
      for I := 0 to OutboundCmds.Count - 1 do
      begin
        AContext.Connection.IOHandler.WriteLn(OutboundCmds.Strings[I]);
        MS := TMemoryStream(OutboundCmds.Objects[I]);
        if MS <> nil then               
          AContext.Connection.IOHandler.Write(MS, 0, true);    
      end;
      Connection.LastSendRecv := Ticks;
    end;
  finally
    if OutboundCmds <> nil then
    begin
      for I := 0 to OutboundCmds.Count - 1 do
        OutboundCmds.Objects[I].Free;
    end;
    OutboundCmds.Free;
  end;

  // check for a pending inbound command...
  if AContext.Connection.IOHandler.InputBufferIsEmpty then
  begin
    AContext.Connection.IOHandler.CheckForDataOnSource(100);
    AContext.Connection.IOHandler.CheckForDisconnect;
    if AContext.Connection.IOHandler.InputBufferIsEmpty then
    begin
      if GetTickDiff(Connection.LastSendRecv, Ticks) >= 30000 then
        AContext.Connection.Disconnect;
      Exit;
    end;
  end;

  cmd := AContext.Connection.Socket.ReadLn;    
  Connection.LastSendRecv := Ticks;

  ...
end;

【讨论】:

  • TIdTicks 在哪里声明?
  • 嗯,我在使用中添加了 idglobal,但未声明
  • 那么您使用的是旧版本的 Indy 10。TIdTicksTicks64()GetEalpsedTicks() 是 2 年前添加的。在您的情况下,您可以改用 Ticks(),因为您的超时不会超过 LongWord 的 49.7 天限制。
  • 我正在使用 xe 8 附带的 indy 不确定,因为如果从 xe 7 升级,我应该从 Tidticks LastsendRCv : LongWord; 更改变量并改用 Ticks 函数吗?
  • 这会导致服务器崩溃吗?因为在将这些行添加到 onconnect 事件后,当某些客户端进入时,我的服务器开始崩溃
猜你喜欢
  • 1970-01-01
  • 2020-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-07
  • 1970-01-01
  • 1970-01-01
  • 2015-06-17
相关资源
最近更新 更多