【问题标题】:Why does my Delphi Indy idHTTpServer stop responding when CLOSE_WAIT?为什么我的 Delphi Indy idHTTpServer 在 CLOSE_WAIT 时停止响应?
【发布时间】:2018-12-28 15:03:53
【问题描述】:

环境

我在 Delphi 中使用 Indy 组件 TidHTTPServer 创建了一个 Web 服务器。我正在使用 Indy 10.5.8 版附带的 Delphi XE2。这 服务器作为桌面应用程序运行,其表单显示连接及其请求的日志。它在 Windows 7 上运行 专业的。请求来自 Firebird 数据库的 SQL 数据。响应是 JSON。所有流量都是 HTTP。

挑战

当我对少数用户进行测试时,一切都很好。现在我已经向大约 400 个用户推出了它 沟通问题。服务器停止响应请求,我能让它再次响应的唯一方法是重新启动它正在运行的机器,然后重新启动它。重新启动的需要更频繁地发生在 高音量时间。

症状

使用 Windows netstat,我注意到每当出现 CLOSE_WAIT 类型的 TCP 连接时,服务器都会停止响应请求,我必须再次重新启动

测试程序

即使服务器上没有流量,我也能够模拟这种挂起。我创建了一个发送多个请求的网页 每个请求之间的延迟。

网页让我指定要发出的请求数、每个请求之间要等待多长时间,以及在超时之前要等待多长时间。即使在请求之间的一毫秒,服务器似乎也没有问题地响应。

测试结果

如果我将每个请求的超时时间设置为一个非常小的数字,比如 1 毫秒,我可以让我的 Delphi HTTP Server 挂起。正如我所料,每次对我的服务器发出 1 毫秒的超时请求都会失败。超时时间太短了,我的服务器可能无法足够快地响应。

我不明白的是,在我在客户端强制此超时后,即使是相对少量的请求(少于 50 个),我的 Delphi Web 服务器也不再响应任何请求。当我在服务器机器上运行 netstat 时,有许多 CLOSE_WAIT 套接字连接。即使在一小时后关闭我的服务器后,CLOSE_WAIT 套接字连接仍然存在。

问题

发生了什么事?当有(甚至只有一个)CLOSE_WAIT 套接字连接时,为什么我的 Delphi Indy idHTTPServer 停止响应? CLOSE_WAIT 不会消失,服务器不会再次开始响应。我必须重新启动。

我没有做什么?

这是显示 CLOSE_WAITs 的 netstat 命令的结果:

C:\Windows\system32>netstat -abn | findstr 62000
TCP    0.0.0.0:62000          0.0.0.0:0             LISTENING
TCP    10.1.1.13:62000        9.49.1.3:57036        TIME_WAIT
TCP    10.1.1.13:62000        9.49.1.3:57162        CLOSE_WAIT
TCP    10.1.1.13:62000        9.49.1.3:57215        CLOSE_WAIT
TCP    10.1.1.13:62000        9.49.1.3:57244        CLOSE_WAIT
TCP    10.1.1.13:62000        9.49.1.3:57263        CLOSE_WAIT
TCP    10.1.1.13:62000        9.49.1.3:57279        ESTABLISHED
TCP    10.1.1.13:62000        104.236.216.73:59051  ESTABLISHED

这是我的网络服务器的本质:

unit MyWebServer;

interface

Uses
...

Type
  TfrmWebServer = class(TForm)
    ...
    IdHTTPServer: TIdHTTPServer;
    ...
    procedure IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    procedure IdHTTPServerDisconnect(AContext: TIdContext);
    procedure btnStartClick(Sender: TObject);
    ...  
    dbFirebird : TIBDatabase;
    txFireird  : TIBTransaction;
    ...
  private
    function CreateSomeResponseStringData: string;
  end;


implementation

procedure TfrmWebServer.btnStartClick(Sender: TObject);
  begin
    {set the IP's and proit to listen on}
    IdHTTPServer.Bindings.Clear;
    IdHTTPServer.Bindings.Add.IP   := GetSetting(OPTION_TCPIP_ADDRESS);
    IdHTTPServer.Bindings.Add.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT));
    {start the web server}
    IdHTTPServer.Active := TRUE;
    ...
    dbFirebird.Transactrion := txFirebird;
    ...
  end;

procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  var
    qryFirebird : TIBSql;

  function CreateSomeResponseStringData: string;
    begin
      qryFirebird := NIL;
      qryFirebird := TIBSql.Create(IdHTTPServer);
      qryFirebird.Database := dbFirebird;
      dbFirebird.Connected := FALSE;
      dbFirebird.Connected := TRUE;
      qryFirebird.Active := TRUE;
      Result := {...whatever string will be returned}
    end;

  function CreateAnErrorResponse: string;
    begin
      Result := {...whatever string will be returned}
    end;

  begin
    try        
      AResponseInfo.ContentText := CreateSomeResponseStringData;
      {Clean up: What do I do here to make sure that the connection that was served is:
         - properly closed so that I don't run out of resourses?
         - anything that needs to be cleaned up is freed so no memory leaks
         - TIME_WAIT, CLOSE_WAIT, any other kind of _WAITs are not accumulating?}
    except;
      AResponseInfo.ContentText := CreateAnErrorResponse;
    end;
    qryFirebird.Free;
  end;

procedure TfrmWebServer.IdHTTPServerDisconnect(AContext: TIdContext);
  begin
    {Maybe I do the "Clean Up" here? I tried Disconnect as shown but still lots of 
    TIME_WAIT tcp/ip connections accumulate. even after the app is closed}    
    AContext.Connection.Disconnect;
  end;

end.  

【问题讨论】:

  • CLOSE_WAIT 表示还没有关闭套接字。就这样吧。
  • 正常情况下TIdHTTPServer会为您关闭连接,您无需手动操作。此外,不需要在OnDisconnect 事件中调用Disconnect(),因为触发事件的客户端已经处于关闭状态,并且套接字将在OnDisconnect 退出后关闭(如果尚未关闭)关闭)。更有可能的情况是,您在 OnCommandGet 事件中做了一些不安全的事情,导致死锁阻止服务器在客户端与服务器端口断开连接后正确清理。但是您还没有显示该代码。
  • 顺便说一句,在btnStartClick() 中,您调用了两次Bindings.Add(),这是错误的。您正在添加两个单独的侦听套接字,一个侦听10.1.1.13,一个侦听0.0.0.0。您需要分配 1 个 Binding 对象的 IPPort,而不是 2 个对象,例如:Binding := IdHTTPServer.Bindings.Add; Binding.IP := GetSetting(OPTION_TCPIP_ADDRESS); Binding.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT)); 这是一个非常常见的新手错误。
  • @JonathanElkins 是的,这是可能的。您必须首先卸载 Delphi 附带的 Indy 版本,然后才能安装更新的版本(带有警告)。阅读 Indy 网站上的installation instructions
  • @JonathanElkins 是的(假设您事先将 DefaultPort 更改为 62000 - 80 是默认值)。每次调用Bindings.Add 都会创建一个新的监听套接字。当您激活服务器时,所有创建的 Binding 对象然后打开其分配的 IP/端口以接受客户端

标签: delphi delphi-xe2 indy10 idhttp


【解决方案1】:

这段代码至少有两个主要问题可能导致崩溃:

  1. 数据库和事务对象对于IdHTTPServer 创建的所有线程都是全局的。当您断开数据库时,它会断开所有线程。

  2. 如果分配内容文本时出现运行时错误,则此行 AResponseInfo.ContentText := CreateAnErrorResponse; 不在异常块中。

我将如何解决这个问题:

...
procedure TfrmWebServer.btnStartClick(Sender: TObject);
  begin
    {set the IP's and port to listen on}
    IdHTTPServer.Bindings.Clear;
    IdHTTPServer.Default.Port    := Str2Int(GetSetting(OPTION_TCPIP_PORT));
    IdHTTPServer.Bindings.Add.IP := GetSetting(OPTION_TCPIP_ADDRESS);
    {start the web server}
    IdHTTPServer.Active := TRUE;
    ...
  end;

procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  var
    {make these local to each thread}
    qryFirebird : TIBSql;
    dbFirebird  : TIBDatabase;
    txFirebird  : TIBTransaction;

  function CreateSomeResponseStringData: string;
    begin
      dbFirebird  := TIBDatbase.Create(IdHTTPServer);
      txFirebird  := TIBTransaction.Create(IdHTTPServer);
      qryFirebird := TIBSql.Create(IdHTTPServer);
      dbFirebird.Transaction := txFirebird;
      qryFirebird.Database := dbFirebird;
      ...Add params that do the log in to database
      dbFirebird.Connected := TRUE;
      qryFirebird.Active := TRUE;
      Result := {...whatever string will be returned}
    end;

  function CreateAnErrorResponse: string;
    begin
      Result := {...whatever string will be returned}
    end;

  begin
    try
      try        
        ...
        AResponseInfo.ContentText := CreateSomeResponseStringData;
        ...
      except;
        try
          AResponseInfo.ContentText := CreateAnErrorResponse;
        except
          {give up}
        end;
      end;
    finaly
      qryFirebird.Free;
      dbFirebird.Free;
      txFirebird.Free;
    end;
  end;

end.     

【讨论】:

  • 我对我的服务器应用程序进行了这些更改,它不再挂起。
猜你喜欢
  • 2010-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-21
  • 1970-01-01
  • 2012-03-21
  • 1970-01-01
相关资源
最近更新 更多