简体   繁体   English

为什么我的Delphi Indy idHTTpServer在CLOSE_WAIT时停止响应?

[英]Why does my Delphi Indy idHTTpServer stop responding when CLOSE_WAIT?

The Environment 环境

I've created a web server in Delphi using Indy component TidHTTPServer. 我已经使用Indy组件TidHTTPServer在Delphi中创建了一个Web服务器。 I'm using Delphi XE2 which came with Indy version 10.5.8. 我正在使用Indy版本10.5.8附带的Delphi XE2。 The server is running as a desktop app with a form that displays a log of the connections and their requests. 该服务器正在作为一个桌面应用程序运行,其形式为显示连接及其请求的日志。 It is running on Windows 7 Professional. 它在Windows 7 Professional上运行。 Requests are for SQL data from a Firebird database. 请求是针对来自Firebird数据库的SQL数据。 The response is JSON. 响应为JSON。 All traffic is HTTP. 所有流量均为HTTP。

The Challenge 挑战

When I was testing it with a small number of users everything worked great. 当我与少量用户进行测试时,一切工作正常。 Now that I have rolled it out to about 400 users there are communication problems. 现在,我已将其推广到约400个用户,出现了通信问题。 The server stops responding to requests and the only way I can get it to respond again is to reboot the machine it is running on and then restart it. 服务器停止响应请求,而我可以使其再次响应的唯一方法是重新启动运行它的计算机,然后重新启动它。 The need to reboot occurs more frequently during high volume times. 在高容量时间内,重新引导的需求更加频繁。

The Symptoms 病征

Using Windows netstat I have noticed that whenever a TCP connection of type CLOSE_WAIT occurs the server stops responding to requests and I have to reboot again 使用Windows netstat时,我注意到每当发生CLOSE_WAIT类型的TCP连接时,服务器就会停止响应请求,而我必须再次重新启动

The Test Procedure 测试程序

I have been able to simulate this hanging even when there is no traffic on the server. 即使服务器上没有流量,我也能够模拟这种挂起。 I created a web page that sends multiple requests with a delay between each request. 我创建了一个网页,该网页发送多个请求,每个请求之间都有延迟。

The web page let's me specify the number of requests to make, how long to wait between each request, and how long to wait before timing out. 该网页让我指定要发出的请求数,每个请求之间要等待多长时间以及超时之前要等待多长时间。 Even at one millisecond between requests the server seems to respond without issue. 甚至在两次请求之间的一毫秒之间,服务器似乎也没有问题。

The Test Results 测试结果

If I set the time out period of each request to a very small number, like 1 msec, I can make my Delphi HTTP Server hang. 如果将每个请求的超时时间设置为一个很小的数字(例如1毫秒),则可以使Delphi HTTP Server挂起。 At a 1 msec timeout requests to my server fail every time, as I would expect. 正如我所期望的,在1毫秒的超时时间内,对我的服务器的请求每次都会失败。 The time out is so short my server can't possibly respond quickly enough. 超时太短了,我的服务器可能无法足够快地响应。

What I don't understand is that after I force this timeout at the client side, even a relatively small number of requests (fewer than 50), my Delphi web server no longer responds to any requests. 我不明白的是,在客户端强制超时后,即使请求数量相对较少(少于50个),Delphi Web服务器也不再响应任何请求。 When I run netstat on the server machine there are a number of CLOSE_WAIT socket connections. 当我在服务器计算机上运行netstat时,有许多CLOSE_WAIT套接字连接。 Even after an hour and after closing my server the CLOSE_WAIT socket connections persist. 即使经过一个小时,并且在关闭服务器后,CLOSE_WAIT套接字连接仍然存在。

The Questions 问题

What is going on? 到底是怎么回事? Why does my Delphi Indy idHTTPServer stop responding when there are (even just one) CLOSE_WAIT socket connection? 当有(甚至只有一个)CLOSE_WAIT套接字连接时,为什么我的Delphi Indy idHTTPServer停止响应? The CLOSE_WAITs don't go away and the server does not start responding again. CLOSE_WAIT不会消失,服务器也不会再次开始响应。 I have to reboot. 我必须重启。

What am I not doing? 我在做什么

Here is the results of netstat command showing CLOSE_WAITs: 这是netstat命令显示CLOSE_WAIT的结果:

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

Here is the essence of my web server: 这是我的Web服务器的本质:

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.  

There are at least two major issues with this code that could cause the crashing: 此代码至少存在两个可能导致崩溃的主要问题:

  1. The database and transaction objects are global to all threads created by IdHTTPServer . 数据库和事务对象是IdHTTPServer创建的所有线程的全局对象。 When you disconnect the database it would disconnect for all threads. 当您断开数据库连接时,它将断开所有线程的连接。

  2. If there is a run time error assigning content text this line AResponseInfo.ContentText := CreateAnErrorResponse; 如果在分配内容文本时发生运行时错误,则此行AResponseInfo.ContentText := CreateAnErrorResponse; is not in an exception block. 不在异常块中。

Here is how I would fix this: 这是我要解决的方法:

...
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.     

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM