-
Доброго всем времени суток. Возникла следующая проблема, пишу приложение с использованием WinAPI функций. Задача приложения следующая: Принимать входящие соединения от клиентов, парсить трафик и перенаправлять соединения на нужный мне порт. Получается что-то вроде порт-маппеда. Количество одновременно работающих соединений 100-1000. После долгих тестов (более суток работы) при попытке соединения приложение начало выдавать ошибку "Out of memory". В самом приложение используются массивы вида array [0..7FFF] of char для чтения данных и передачи. В каждом потоке получается 4 таких массива. Если соединение разрывается, поток соответственно уничтожается. функции GetMem, FreeMem и т.п. не использую. Кто может подсказать в чем может быть проблема?
-
проблема в отсутствии лога по которому было бы видно, что Если соединение разрывается, поток соответственно уничтожается.
-
Логи собственно присутствуют. Поток действительно уничтожается. Появилась мысль, что проблема может быть в следующем: указатели на все потоки хранятся в структуре типа TList. Возможно ли, что проблема в ней? Т.к. к серверу за сутки подключений было более 50000, процедуру Pack я не вызывал. При создании потока он автоматически добавляется в структуру, затем удаляется. По логам видно что идентификатор потока только увеличивается, идентификатор берется:
var
Id:Integer;
tunels:Tlist;
...
Id:=integer(tunels.items[I]);
Если я правильно понимаю, то в памяти постоянно выделяется место под указатель, но при удалении итема, место не высвобождается (а если и высвобождается, то не используется повторно).
-
50000 * 4 ~ 200 кб
Мелочь. Ищи не там где светлее, а где лежит.
-
Логи собственно присутствуют.
Ну тогда ты как минимум знаешь на какой операции получается аутовмемори.
-
> Ну тогда ты как минимум знаешь на какой операции получается > аутовмемори
Да, сообщение вылетает при попытке создания нового тунеля.
TTunel = class (TObject)
initserversocket, serversocket, clientsocket : integer; curSockEngine : TObject; ConnectOrErrorEvent : Cardinal; hServerThread, hClientThread : integer; idServerThread, idClientThread : LongWord; LastTime:TDateTime; public
ErrorMessage:String;
NeedDeInit: boolean;
TunelWork:boolean;
MustBeDestroyed : boolean;
procedure SendAction(action : byte);
procedure NewAction(action: byte; Caller: TObject);
procedure EncryptAndSend(Packet:Tpacket; ToServer:Boolean);
published
constructor Create(SockEngine : TObject);
procedure Run;
destructor Destroy; override;
end;
-
> Maloj2007 © (12.10.10 16:32) [5]
Здесь тоже светло, но лежит не здесь.
-
> Сергей М. © (12.10.10 16:40) [6]
По твоим подозрениям где может лежать? Почитал форумы, многие пишут что такая проблема возникает при частом выделении и освобождении памяти.
-
> Maloj2007 © (12.10.10 16:49) [7]
Ну так в приведенном тобой коде нет ни намека ни на выделение ни на освобождение)
-
Кидаю основные функции маппета, т.е. функции которые исполняю.тся потоками.
type
TCharArray = array[0..$7FFF] of AnsiChar;
TCharArrayEx = array[0..$FFFE] of AnsiChar; PPacket = ^TPacket;
TPacket = packed record case Integer of
0: (Size: Word;
Data: array[0..$7FFE] of Byte);
1: (PacketAsByteArray: array[0..$7FFF] of Byte);
2: (PacketAsCharArray: TCharArray);
3: (pckSize: Word;
pckId: Byte;
pckData: array[0..$7FFE] of Byte);
end;
TTunel = class (TObject)
initserversocket, serversocket, clientsocket : integer; curSockEngine : TObject; ConnectOrErrorEvent : Cardinal; hServerThread, hClientThread : integer; idServerThread, idClientThread : LongWord; EncDec:TEncDec;
LastTime:TDateTime;
public
ErrorMessage:String;
NeedDeInit: boolean;
TunelWork:boolean;
MustBeDestroyed : boolean;
ServerKey:Integer;
UserKey:Integer;
IsBot:Boolean;
procedure SendAction(action : byte);
procedure NewAction(action: byte; Caller: TObject);
procedure EncryptAndSend(Packet:Tpacket; ToServer:Boolean);
published
constructor Create(SockEngine : TObject);
procedure Run;
destructor Destroy; override;
end;
TSocketEngine = class (TObject)
tunels : TList;
private
WSA: TWSAData;
hServerListenThread : integer;
idServerListenThread: Cardinal;
ServerListenSock: Integer;
function WaitClient(var hSocket, NewSocket: TSocket): Boolean;
function WaitForData(Socket: TSocket; Timeout: Longint): Boolean;
procedure DeInitSocket(VAR hSocket : integer; const ExitCode: Integer);
function InitSocket(var hSocket: TSocket; Port: Word; IP: String): Boolean;
function GetSocketData(Socket: TSocket; var Data; const Size: Word): Boolean;
function ConnectToServer(var hSocket: TSocket; Port: Word; IP: Integer): Boolean;
public
ErrorMessage:String;
ServerPort : Word;
RedirrectIP : Integer;
RedirrectPort : Word;
isServerTerminating : boolean;
procedure SendAction(action : byte);
procedure NewAction(action: byte; Caller: TObject);
published
procedure DestroyDeadTunels;
constructor Create; Procedure StartServer; destructor Destroy; override; end;
-
procedure ServerListen(CurrentEngine: TSocketEngine);
var
NewSocket: TSocket;
NewTunel : Ttunel;
begin
try
with CurrentEngine do
begin
if not InitSocket(ServerListenSock, ServerPort, '0.0.0.0') then
begin
SendAction(TSockEngineActionListenErrror);
exit;
end;
SendAction(TSockEngineActionListen);
while WaitClient(ServerListenSock, NewSocket) do
begin
SendAction(TSockEngineActionNewConnect);
NewTunel := TTunel.Create(CurrentEngine);
NewTunel.serversocket := NewSocket; NewTunel.initserversocket := NewSocket; NewTunel.Run; end;
end;
except
Addtolog('Error ServerListen'+IntToStr(GetLastError));
end;
end;
procedure ServerBody(thisTunel:Ttunel);
var
StackAccumulator : TCharArrayEx;
PreAccumulator : TCharArray;
AccumulatorLen : Cardinal;
BytesInStack : Longint;
curPacket,NewPacket : TPacket;
RecvBytes : Int64;
PreSize, LastResult : Word;
EventTimeout : boolean;
IP: Integer;
IPb:array[0..3] of Byte absolute ip;
buff:String;
begin
try
with TSocketEngine(thisTunel.curSockEngine) do
begin
thisTunel.ConnectOrErrorEvent := CreateEvent(nil, true,false,PChar('ConnectOrErrorEvent'+IntToStr(thisTunel.hServerThread)));
thisTunel.hClientThread :=BeginThread(nil, 0, @ClientBody, thisTunel, 0, thisTunel.idClientThread);
thisTunel.SendAction(TTunelActionConnectClient);
EventTimeout := (WaitForSingleObject(thisTunel.ConnectOrErrorEvent, 30000) <> 0);
if (EventTimeout) or (not thisTunel.TunelWork) then
begin
CloseHandle(thisTunel.ConnectOrErrorEvent);
thisTunel.MustBeDestroyed := true;
thisTunel.TunelWork := false;
DeinitSocket(thisTunel.serversocket,WSAGetLastError);
TerminateThread(thisTunel.hServerThread,0);
end;
CloseHandle(thisTunel.ConnectOrErrorEvent);
ip := RedirrectIP;
AccumulatorLen := 0;
LastResult := 1;
FillChar(PreAccumulator[0],$7fff,0);
While (thisTunel.serversocket <> -1) do
try
ioctlsocket(thisTunel.serversocket, FIONREAD, BytesInStack);
if BytesInStack = 0 then
BytesInStack := 1;
RecvBytes := recv(thisTunel.serversocket, PreAccumulator[0], BytesInStack, 0); if RecvBytes <= 0 then
break
else
PreSize := RecvBytes;
LastResult := PreSize;
if lastresult = 1 then
begin
ioctlsocket(thisTunel.serversocket, FIONREAD, BytesInStack);
if BytesInStack > $7FFE then
BytesInStack := $7FFE;
if BytesInStack > 0 then
begin
RecvBytes := recv(thisTunel.serversocket, PreAccumulator[presize], BytesInStack, 0);
if RecvBytes <= 0 then
break
else
LastResult := LastResult + RecvBytes;
end;
end;
if LastResult > 0 then
begin
Move(PreAccumulator[0], StackAccumulator[AccumulatorLen], LastResult);
FillChar(PreAccumulator[0],$7fff,0);
Inc(AccumulatorLen, LastResult);
except
break;
end;
try
thisTunel.sendAction(TTunelActionDisconnectClient);
DeinitSocket(thisTunel.serversocket,WSAGetLastError);
if thisTunel.clientsocket <> -1 then
DeinitSocket(thisTunel.clientsocket,WSAGetLastError);
thisTunel.TunelWork := false;
thisTunel.MustBeDestroyed := true;
except
end;
end;
except
end;
end;
-
Procedure ClientBody(thisTunel:Ttunel);
var
PreAccumulator : TCharArray;
StackAccumulator : TCharArrayEx;
AccumulatorLen : Cardinal;
curPacket : TPacket;
BytesInStack : Longint;
PreSize, LastResult : Word;
IP: Integer;
IPb:array[0..3] of Byte absolute ip;
recvbytes : int64;
begin
try
with TSocketEngine(thisTunel.curSockEngine) do
begin
if not InitSocket(thisTunel.clientsocket,0,'0.0.0.0') then
begin
EndThread(0);
end;
ip := RedirrectIP;
if not ConnectToServer(thisTunel.clientsocket, RedirrectPort, RedirrectIP) then
begin
SetEvent(thisTunel.ConnectOrErrorEvent); EndThread(0);
end;
thisTunel.SendAction(TTunelActionConnectServer);
thisTunel.TunelWork := true;
SetEvent(thisTunel.ConnectOrErrorEvent);
AccumulatorLen := 0;
LastResult := 1;
While (thisTunel.clientsocket <> -1) do
begin try
ioctlsocket(thisTunel.clientsocket, FIONREAD, BytesInStack);
if BytesInStack = 0 then
BytesInStack := 1;
RecvBytes := recv(thisTunel.clientsocket, PreAccumulator[0], BytesInStack, 0); if RecvBytes <= 0 then
break
else
PreSize := RecvBytes;
LastResult := PreSize;
if lastresult = 1 then begin
ioctlsocket(thisTunel.clientsocket, FIONREAD, BytesInStack);
if BytesInStack > $7FFE then
BytesInStack := $7FFE; if BytesInStack > 0 then begin
RecvBytes := recv(thisTunel.clientsocket, PreAccumulator[presize], BytesInStack, 0);
if RecvBytes <= 0 then
break
else
LastResult := LastResult + RecvBytes;
end;
end;
if LastResult > 0 then
begin
Move(PreAccumulator[0], StackAccumulator[AccumulatorLen], LastResult);
FillChar(PreAccumulator[0],$7fff,0);
inc(AccumulatorLen, LastResult);
except
AddToLog('2'+IntToStr(GetLastError));
end;
end;
thisTunel.SendAction(TTunelActionDisconnectServer);
DeinitSocket(thisTunel.clientsocket,WSAGetLastError);
if thisTunel.clientsocket <> -1 then
DeinitSocket(thisTunel.serversocket,WSAGetLastError);
thisTunel.TunelWork := false;
thisTunel.MustBeDestroyed := true;
end;
except
Addtolog('Error ClientBody'+IntToStr(GetLastError));
end;
end;
-
> EndThread
Этот вызов запросто м.б. источником утечек. Пользоваться им нужно с умом, а не абы где.
> одновременно работающих соединений 100-1000
И какждое соединение у тебя обслуживается тредом с мегабайтным стеком каждый.
1 мб * 1000 ~ 1гб
Не мудрено что в ВАП процесса в какой-то момент времени при создании очередного треда не нашлось свободного региона размером в 1 мб
-
> Сергей М. © (12.10.10 17:40) [12]
Если я правильно понял тебя, ты предлогаешь уменьшить буфер?
ЗЫ: Система работает на Win Server x64 16 Гб ОП
-
Возьми профайлер и прогони им - он явно покажет где у тебя закавыка...
-
> ЗЫ: Система работает на Win Server x64 16 Гб ОП 32 разрядному приложению это как бы до лампочки... не зря же 32 разрядная XP "видит" не больше 3 гиг. да и то через одно место, а нормально только 2.
-
> Maloj2007 © (12.10.10 18:15) [13] > предлогаешь уменьшить буфер?
Ни про какой буфер я не говорил. Я лишь заострил твое внимание на том что каждый твой тред требует выделения ему в ВАП твоего 32-разрядного (!) процесса региона размером 1 мб. А тредов у тебя, как ты заявил, может одновременно существовать до 1000. А ВАП - оно не резиновое. + дефрагментация.
Так что мотай на ус.
И про EndThread призадумайся. Exit() придумана не для Пушкина.
-
> Сергей М. © (12.10.10 21:33) [16]
Спасибо, буду думать =)
-
IO completion port ?
Порт завершения ввода-вывода (IO completion port, IOCP). Реализованный в ядре ОС и доступный через системные вызовы объект «очередь» с операциями «поместить структуру в хвост очереди» и «взять следующую структуру с головы очереди» — последний вызов приостанавливает исполнение потока в случае, если очередь пуста, и до тех пор, пока другой поток не осуществит вызов «поместить». Главнейшей особенностью IOCP является то, что структуры в него могут помещаться не только явным системным вызовом из режима пользователя, но и неявно внутри ядра ОС как результат завершения асинхронной операции ввода-вывода на одной из дескрипторов файлов. Для достижения такого эффекта необходимо использовать системный вызов «связать дескриптор файла с IOCP». В этом случае помещенная в очередь структура содержит в себе код ошибки операции ввода-вывода, а также, для случая успеха этой операции — число реально введенных или выведенных байт. Реализация порта завершения также ограничивает число потоков, исполняющихся на одном процессоре/ядре после получения структуры из очереди. Объект специфичен для MS Windows, и позволяет обработку входящих запросов соединения и порций данных в серверном программном обеспечении в архитектуре, где число потоков может быть меньше числа клиентов (нет требования создавать отдельный поток с расходами ресурсов на него для каждого нового клиента).
wiki.
|