Почему мой Delphi Indy idHTTpServer перестает отвечать при CLOSE_WAIT?
Окружающая среда
Я создал веб-сервер в Delphi с использованием компонента Indy TidHTTPServer. Я использую Delphi XE2, который поставляется с версией Indy 10.5.8. Сервер работает как настольное приложение с формой, которая отображает журнал соединений и их запросов. Он работает на Windows 7 Professional. Запросы для данных SQL из базы данных Firebird. Ответ JSON. Весь трафик HTTP.
Соревнование
Когда я тестировал его с небольшим количеством пользователей, все работало отлично. Теперь, когда я разослал его примерно 400 пользователям, возникли проблемы со связью. Сервер перестает отвечать на запросы, и единственный способ заставить его ответить снова - это перезагрузить компьютер, на котором он работает, и затем перезапустить его. Необходимость перезагрузки возникает чаще в периоды большого объема.
Симптомы
Используя Windows netstat, я заметил, что всякий раз, когда происходит TCP-соединение типа CLOSE_WAIT, сервер перестает отвечать на запросы, и я должен перезагрузиться снова
Процедура испытаний
Я был в состоянии смоделировать это зависание, даже когда на сервере нет трафика. Я создал веб-страницу, которая отправляет несколько запросов с задержкой между каждым запросом.
На веб-странице я могу указать количество запросов, сколько ждать между каждым запросом и сколько ждать до истечения времени ожидания. Даже в течение одной миллисекунды между запросами сервер, кажется, отвечает без проблем.
Результаты теста
Если я установлю период ожидания каждого запроса на очень маленькое число, например 1 мсек, я могу заставить свой сервер Delphi HTTP зависать. По истечении 1 мсек запросы к моему серверу каждый раз терпят неудачу, как я и ожидал. Тайм-аут настолько мал, что мой сервер не может ответить достаточно быстро.
Чего я не понимаю, так это того, что после принудительного тайм-аута на стороне клиента, даже относительно небольшого числа запросов (менее 50), мой веб-сервер Delphi больше не отвечает ни на какие запросы. Когда я запускаю netstat на серверном компьютере, существует несколько соединений сокетов CLOSE_WAIT. Даже через час и после закрытия моего сервера соединения сокета CLOSE_WAIT сохраняются.
Вопросы
Что здесь происходит? Почему мой Delphi Indy idHTTPServer перестает отвечать, когда есть (хотя бы одно) соединение через сокет CLOSE_WAIT? CLOSE_WAIT не исчезают, и сервер не начинает отвечать снова. Я должен перезагрузиться.
Что я не делаю?
Вот результаты команды 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
Вот суть моего веб-сервера:
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.
1 ответ
В этом коде есть как минимум две основные проблемы, которые могут вызвать сбой:
База данных и объекты транзакций являются глобальными для всех потоков, созданных
IdHTTPServer
, Когда вы отключите базу данных, она будет отключена для всех потоков.Если при назначении содержимого текста этой строки произошла ошибка
AResponseInfo.ContentText := CreateAnErrorResponse;
не находится в блоке исключения.
Вот как я бы это исправить:
...
procedure TfrmWebServer.btnStartClick(Sender: TObject);
begin
{set the IP's and proit 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.