alex87
|
|
« : 25-03-2010 08:05 » |
|
Программа работает на асинхронном сокете по протоколу UDP, принимает сообщения и отсылает их... Проблема стоит в памяти, при получении сообщения и пересылке его, память растет и не сбрасывается. Проверял переменные обьявленные динамически удаляются, статические зануляются, глобальных 2 переменных(для сокета). Что еще может быть? В чем проблема? может чего то я не знаю?
|
|
« Последнее редактирование: 29-03-2010 18:06 от Вад »
|
Записан
|
|
|
|
baldr
|
|
« Ответ #1 : 25-03-2010 08:16 » |
|
А ты сокет явно закрываешь после окончания работы с ним?
|
|
|
Записан
|
Приличный компьютер всегда будет стоить дороже 1000 долларов, потому что 500 долларов - это не вполне прилично
|
|
|
resource
Молодой специалист
Offline
Пол:
|
|
« Ответ #2 : 25-03-2010 08:21 » |
|
alex87, ну ты удивляешь конечно. Тут что по твоему, экстрасенсы? Как можно понять в чем проблема твоего кода, не видя самого кода?
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #3 : 25-03-2010 09:14 » |
|
>>статические зануляются, глобальных 2 переменных
вот об этом поподробнее )
|
|
|
Записан
|
|
|
|
alex87
|
|
« Ответ #4 : 25-03-2010 15:42 » |
|
TDateTime DateTime = TDateTime::CurrentDateTime(); AnsiString *date_M = new AnsiString; AnsiString *time_M = new AnsiString; AnsiString *IpAddr = new AnsiString; AnsiString *host = new AnsiString; AnsiString *Vashnost = new AnsiString; AnsiString *crit = new AnsiString; AnsiString pp="",temp="",o=""; char *buf; int *j= new int; int *nLength = new int; int *nLen = new int; int *z = new int; *nLen = 0; *nLength = 0; *j = 1024; struct sockaddr_in from; HOSTENT *hst; //для определения имя хоста *nLength=sizeof(struct sockaddr_in); if ((buf = (char *) malloc(*j)) != NULL) { switch (WSAGETSELECTEVENT(msg.LParam)) { case FD_READ: { *nLen=recvfrom(serv,buf,*j,0,(struct sockaddr*)&from,&(*nLength)); if(*nLen>0) { *date_M=DateTime.DateString(); //дата *time_M=DateTime.TimeString(); //время *IpAddr = inet_ntoa (from.sin_addr ); // получаем IP адрес hst = gethostbyaddr((char*)&from.sin_addr,4,AF_INET); //получение имени хоста *host = hst->h_name; buf[*nLen]='\0'; //ставим конец сообщения } } } //......тут ост.код //...... } else { LogProgram("Buffer is not create :( "); Close(); } pp=""; temp=""; o=""; free(buf); delete crit; delete host; delete Vashnost; delete IpAddr; delete z; delete j; delete date_M; delete time_M; delete nLength; delete nLen;
вот кусок кода с динамическими переменными, я же правильно делаю?! после использования я удаляю... Почему не освобождается память?
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #5 : 25-03-2010 16:54 » |
|
тихий ужас...
щас попробуем переделать
|
|
|
Записан
|
|
|
|
resource
Молодой специалист
Offline
Пол:
|
|
« Ответ #6 : 25-03-2010 16:59 » |
|
Алексей1153++, а может так щас модно
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #7 : 25-03-2010 17:12 » |
|
alex87, ты определись, на каком языке пишешь - C или C++ Я оставил как было - C Но хорошо обдумай ))) привёл в человечий вид и исправил одну уязвимость - buf[min(nLen,j-1)]=0 TDateTime DateTime = TDateTime::CurrentDateTime(); AnsiString date_M; AnsiString time_M; AnsiString IpAddr; AnsiString host; AnsiString Vashnost; AnsiString crit=""; AnsiString pp="",temp="",o=""; char* buf=0; int j=1024; int nLength=0; int nLen=0; int z=0; struct sockaddr_in from; HOSTENT hst=0; //для определения имя хоста nLength=sizeof(struct sockaddr_in); if(buf=new char[j]) { switch (WSAGETSELECTEVENT(msg.LParam)) { case FD_READ: { nLen=recvfrom(serv,buf,j,0,(struct sockaddr*)&from,&nLength); if(nLen>0) { date_M=DateTime.DateString(); //дата time_M=DateTime.TimeString(); //время IpAddr = inet_ntoa (from.sin_addr ); // получаем IP адрес hst = gethostbyaddr((char*)&from.sin_addr,4,AF_INET); //получение имени хоста host = hst->h_name; buf[min(nLen,j-1)]=0; //() ставим конец сообщения } } } //......тут ост.код //...... delete []buf; buf=0; } else { LogProgram("Buffer is not create :( "); Close(); }
pp=""; temp=""; o="";
не тестировал Плохо что модно... Вернее - Что модно, оно всегда плохо и обычно некрасиво
|
|
« Последнее редактирование: 25-03-2010 17:21 от Алексей1153++ »
|
Записан
|
|
|
|
resource
Молодой специалист
Offline
Пол:
|
|
« Ответ #8 : 25-03-2010 17:21 » |
|
ты определись, на каком языке пишешь - C или C++ Я оставил как было - C
С предложением определиться не поспоришь. А вот насчет того, что изначально было на C.... я бы так не сказал, равно как и то что это был C++. По моему мы имели возможность наблюдать какой то совершенно дикий микс. Я бы назвал это C+
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #9 : 25-03-2010 17:21 » |
|
да, я уже тоже понял ) Я сначала первые строчки не увидел - подправил
C+ - нет такого оператора, но есть C-- ))))
|
|
|
Записан
|
|
|
|
alex87
|
|
« Ответ #10 : 25-03-2010 23:59 » |
|
Алексей, именно С++, хотя я ранее писал на просто С, борланд С под дос, может из за этого я путаюсь.... Про исправления, буду тестировать.
|
|
|
Записан
|
|
|
|
sss
Специалист
Offline
|
|
« Ответ #11 : 26-03-2010 02:33 » |
|
alex87, а чем смотрите статус памяти, диспетчером задач? Менеджер памяти оперирует гранулами памяти (ну скажем по 4K ). Как только гранула заканчивается, он резервирует еще одну страницу (гранула может быть и больше). Как только вы освобождаете память, занятая гранула может быть еще не освобождена полностью (например даже фрагментирована) . Поэтому сложно полагаться на показатели диспетчера задач можно, но сложно.
|
|
« Последнее редактирование: 26-03-2010 03:02 от sss »
|
Записан
|
while (8==8)
|
|
|
alex87
|
|
« Ответ #12 : 26-03-2010 03:08 » |
|
alex87, а чем смотрите статус памяти, диспетчером задач? Менеджер памяти оперирует гранулами памяти (ну скажем по 4K ). Как только гранула заканчивается, он резервирует еще одну страницу (ой проговорился, гранула может быть и больше). Как только вы освобождаете память, занятая гранула может быть еще не освобождена полностью (например даже фрагментирована) . Поэтому сложно полагаться на показатели диспетчера задач можно, но сложно. Да, диспечер задач. Но все равно, спустя 1 час работы программы, непрерывный прием сообщения(2 шт. в секунду) память сжирается около 2000кб-2500кб, т.е. было 1896кб, а стало ~4000кб и она растет...
|
|
|
Записан
|
|
|
|
alex87
|
|
« Ответ #13 : 26-03-2010 10:10 » |
|
ни чего не получается в отчаянии. К примеру есть же программы когда отрабатывают функцию, память освобождают и дальше работают. Как это реализовано то блин.
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #14 : 26-03-2010 10:28 » |
|
alex87, а ты уверен, что проблема именно в этом участке кода ?
|
|
|
Записан
|
|
|
|
alex87
|
|
« Ответ #15 : 30-03-2010 09:34 » |
|
Код написания логов: void __fastcall TSYSLOGclient::LogMessage(AnsiString Data_Priem, AnsiString Time_Priem, AnsiString from_host,AnsiString from_IP, AnsiString code, AnsiString message, AnsiString crit_kod) { FILE *out; TDateTime DateTime = TDateTime::CurrentDateTime(); AnsiString date_sys="", uu="", dir_log="", log_P="",log_L="",file_str="",mes_into_file=""; WORD m,y,d; uu = FormatDateTime("yyyy_mm_dd", DateTime); //меняем формат даты date_sys = uu; dir_log = GetCurrentDir(); //получить директорию где наша программа log_P = dir_log+"\\Logs"; //установить папки логов log_L = log_P+"\\"+ date_sys; //папка по дате if (!DirectoryExists(log_P)) { if (!CreateDir(log_P)) { MessageDlg("Папка не была создана!!!", mtError, TMsgDlgButtons() << mbOK, 0); } } else { if (!DirectoryExists(log_L)) { if (!CreateDir(log_L)) { MessageDlg("Папка не была создана!!!", mtError, TMsgDlgButtons() << mbOK, 0); } } else { // открыть поток mes_into_file = Time_Priem+"\t"+Data_Priem+"\t"+from_host+"\t"+from_IP+"\t"+code+"\t"+message; file_str = log_L+"\\"+"!Logs_"+date_sys+".log"; //общий лог out = fopen(file_str.c_str(),"a+"); if (out == NULL) MessageDlg("Файл логов, общий, не был открыт!!!", mtError, TMsgDlgButtons() << mbOK, 0); else { fprintf(out,"%s",mes_into_file); fprintf(out,"\n"); } file_str=""; fclose(out); // закрытие потока file_str = log_L+"\\"+from_host+".log"; // лог по отправителю out = fopen(file_str.c_str(),"a+"); if (out == NULL) MessageDlg("Файл логовпо отправителю не был открыт!!!", mtError, TMsgDlgButtons() << mbOK, 0); else { fprintf(out,"%s",mes_into_file); fprintf(out,"\n"); } file_str=""; fclose(out); // закрытие второго потока if (crit_kod <= 2) { file_str = log_L+"\\"+"!Crits.log"; out = fopen(file_str.c_str(),"a+"); if(out==NULL) MessageDlg("Файл логов для критики не был открыт!!!", mtError, TMsgDlgButtons() << mbOK, 0); else { fprintf(out,"%s",mes_into_file); fprintf(out,"\n"); } fclose(out); file_str=""; } else { if (crit_kod == 2 || crit_kod == 3) { file_str = log_L+"\\"+"!Errors.log"; out = fopen(file_str.c_str(),"a+"); if(out==NULL) MessageDlg("Файл для Errors не был открыт!!!", mtError, TMsgDlgButtons() << mbOK, 0); else { fprintf(out,"%s",mes_into_file); fprintf(out,"\n"); } fclose(out); file_str=""; } } } } file_str=""; }
Проблема: при записи сообщений в логи через некоторое время просто напросто не может открыть файл. Что не так?
|
|
« Последнее редактирование: 31-03-2010 03:23 от alex87 »
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #16 : 30-03-2010 10:51 » |
|
alex87, есть проблемы с кодом: явные синтаксические ошибки (например, на 4-й строке отсутствует "{") и отсутствует форматирование (очень тяжело читать)!
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
alex87
|
|
« Ответ #17 : 31-03-2010 00:29 » |
|
RXL, исправил код, легче читать?
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #18 : 31-03-2010 03:52 » |
|
Ничуть не лучше. Воздуху нет в твоем кода. А когда нет воздуху, то это куча чего-то непонятного... Вот тебе образчик для понимания сути: void __fastcall TSYSLOGclient::LogMessage( AnsiString Data_Priem, AnsiString Time_Priem, AnsiString from_host, AnsiString from_IP, AnsiString code, AnsiString message, AnsiString crit_kod ) { FILE *out; TDateTime DateTime = TDateTime::CurrentDateTime(); AnsiString date_sys, uu, dir_log, log_P, log_L, file_str, mes_into_file; WORD m, y, d;
uu = FormatDateTime("yyyy_mm_dd", DateTime); //меняем формат даты date_sys = uu; dir_log = GetCurrentDir(); // получить директорию где наша программа log_P = dir_log + "\\Logs"; // установить папки логов log_L = log_P + "\\" + date_sys; // папка по дате
if (!DirectoryExists(log_P)) { if (!CreateDir(log_P)) { MessageDlg( "Папка не была создана!!!", mtError, TMsgDlgButtons() << mbOK, 0 ); } } else { if (!DirectoryExists(log_L)) { if (!CreateDir(log_L)) { MessageDlg( "Папка не была создана!!!", mtError, TMsgDlgButtons() << mbOK, 0 ); } } else { // открыть поток mes_into_file = Time_Priem + "\t" + Data_Priem + "\t" + from_host + "\t" + from_IP + "\t" + code + "\t" + message; file_str = log_L + "\\" + "!Logs_" + date_sys + ".log"; //общий лог out = fopen(file_str.c_str(), "a+");
if (out == NULL) MessageDlg( "Файл логов, общий, не был открыт!!!", mtError, TMsgDlgButtons() << mbOK, 0 ); else { fprintf(out, "%s", mes_into_file); fprintf(out, "\n"); }
file_str = ""; fclose(out); // закрытие потока
file_str = log_L + "\\" + from_host + ".log"; // лог по отправителю out = fopen(file_str.c_str(),"a+");
if (out == NULL) MessageDlg( "Файл логов по отправителю не был открыт!!!", mtError, TMsgDlgButtons() << mbOK, 0 ); else { fprintf(out, "%s", mes_into_file); fprintf(out, "\n"); }
file_str = ""; fclose(out); // закрытие второго потока
if (crit_kod <= 2) { file_str = log_L + "\\" + "!Crits.log"; out = fopen(file_str.c_str(), "a+");
if (out == NULL) MessageDlg( "Файл логов для критики не был открыт!!!", mtError, TMsgDlgButtons() << mbOK, 0 ); else { fprintf(out, "%s", mes_into_file); fprintf(out, "\n"); }
fclose(out); file_str = ""; } else { if (crit_kod == 2 || crit_kod == 3) { file_str = log_L + "\\" + "!Errors.log"; out = fopen(file_str.c_str(), "a+");
if (out == NULL) MessageDlg( "Файл для Errors не был открыт!!!", mtError, TMsgDlgButtons() << mbOK, 0 ); else { fprintf(out, "%s", mes_into_file); fprintf(out, "\n"); }
fclose(out); file_str = ""; } } }
} file_str = ""; }
Код - писец! Сплошной копипаст... Обрати внимание на предпоследнюю скобку - ни к селу, ни к городу.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #19 : 31-03-2010 04:00 » |
|
alex87, вот мой вариант форматирования struct MyParams() { AnsiString Data_Priem; AnsiString Time_Priem; AnsiString from_host; AnsiString from_IP; AnsiString code; AnsiString message; AnsiString crit_kod;
MyParams( AnsiString Data_Priem ="", AnsiString Time_Priem ="", AnsiString from_host ="", AnsiString from_IP ="", AnsiString code ="", AnsiString message ="", AnsiString crit_kod ="" ) : Data_Priem (Data_Priem ) , Time_Priem (Time_Priem ) , from_host (from_host ) , from_IP (from_IP ) , code (code ) , message (message ) , crit_kod (crit_kod ) { }
void fprintf_all(FILE *out) { fprintf(out,"%s\t",Time_Priem); fprintf(out,"%s\t",Data_Priem); fprintf(out,"%s\t",from_host); fprintf(out,"%s\t",from_IP); fprintf(out,"%s\t",code); fprintf(out,"%s\t",message); fprintf(out,"\n"); } };
void __fastcall TSYSLOGclient::LogMessage(const MyParams& MP) { WORD m=0; WORD y=0; WORD d=0;
FILE *out; TDateTime DateTime = TDateTime::CurrentDateTime();
AnsiString date_sys=""; AnsiString uu=""; AnsiString dir_log=""; AnsiString log_P=""; AnsiString log_L=""; AnsiString file_str="";
uu = FormatDateTime("yyyy_mm_dd", DateTime); //меняем формат даты date_sys = uu; dir_log = GetCurrentDir(); //получить директорию где наша программа log_P = dir_log+"\\Logs"; //установить папки логов log_L = log_P+"\\"+ date_sys; //папка по дате
if(!DirectoryExists(log_P)) { if(!CreateDir(log_P)) { MessageDlg("Папка не была создана!!!",mtError, TMsgDlgButtons() << mbOK, 0); } } else { if(!DirectoryExists(log_L)) { if(!CreateDir(log_L)) { MessageDlg("Папка не была создана!!!",mtError, TMsgDlgButtons() << mbOK, 0); } } else { // открыть поток file_str = log_L+"\\"+"!Logs_"+date_sys+".log"; //общий лог out = fopen(file_str.c_str(),"a+"); if (out == NULL) { MessageDlg("Файл логов, общий, не был открыт!!!",mtError, TMsgDlgButtons() << mbOK, 0); } else { MP.fprintf_all(out); }
file_str=""; fclose(out); // закрытие потока file_str = log_L+"\\"+MP.from_host+".log"; // лог по отправителю out = fopen(file_str.c_str(),"a+"); if(out == NULL) { MessageDlg("Файл логовпо отправителю не был открыт!!!",mtError, TMsgDlgButtons() << mbOK, 0); } else { MP.fprintf_all(out); }
file_str=""; fclose(out); // закрытие второго потока if(MP.crit_kod <= 2) { file_str = log_L+"\\"+"!Crits.log"; out = fopen(file_str.c_str(),"a+"); if(out==NULL) { MessageDlg("Файл логов для критики не был открыт!!!",mtError, TMsgDlgButtons() << mbOK, 0); } else { MP.fprintf_all(out); } fclose(out); file_str=""; } else { if(MP.crit_kod == 2 || MP.crit_kod == 3) { file_str = log_L+"\\"+"!Errors.log"; out = fopen(file_str.c_str(),"a+"); if(out==NULL) { MessageDlg("Файл для Errors не был открыт!!!",mtError, TMsgDlgButtons() << mbOK, 0); } else { MP.fprintf_all(out); } fclose(out); file_str=""; } } } } file_str=""; }
|
|
« Последнее редактирование: 31-03-2010 04:05 от Алексей1153++ »
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #20 : 31-03-2010 13:55 » |
|
Offtopic: Узнаю брата Федю (c) Леш, тебе не хватает отделения пробелами операторов и комментариев. Так читаемость будет значительно лучше. Поставлю в угол.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #21 : 31-03-2010 14:58 » |
|
>> отделения пробелами операторов и комментариев - это как ? А я, кстати, не то в структурку вынес, но это всё фигня )) Что я хотел сказать - сказал. Главное - красиво
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #22 : 31-03-2010 16:25 » |
|
Это так: var = 1 + 2; obj.method(arg1, arg2); // Вызов функции.
Неужели это откровение?
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #23 : 31-03-2010 16:38 » |
|
комментарии я у него не трогал, а пробелы не люблю лишние. Мне просто не нравится так )
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #24 : 31-03-2010 16:47 » |
|
Тебе не нравится, а зато чужой код читать легче.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #25 : 31-03-2010 16:54 » |
|
Чем легче то ? Вот есть формула трёхэтажная, я действительно делаю либо так a=( ( (b+c)*(d+e) * (f+g) +5 ) + ( (h+i) * (j+k) - 10 ) );
либо так a=( ( (b+c)*(d+e) * (f+g) +5 ) + ( (h+i) * (j+k) - 10 ) );
а вообще, основной упор делаю на визуальную "колоночность", особенно в случае с вышенарисованной структурой, где много посторяющихся имён полей, и резон тут наипростяцкий (кроме наглядности) - alt+выделение мышью - копируем изменённую колонку, или вставляем колонку пробелов/табуляций (пробелы даже предпочтительнее при выравнивании там)
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #26 : 31-03-2010 18:14 » |
|
Леш, ИМХО, это неряшливо и ужасно — только глаза разбегаются, а читаемость не улучшается. Если хочется разбить выражение по его структуре, то не надо размазывать на пол страницы в ширину. Также не надо вставлять пробел (а то и несколько) в одном случае, а в другом - лепить. Лучший ориентир в плане читаемости — типографика: полистай книги, посмотри как их верстают — все делается для облегчения чтения и концентрации внимания на строке. Вот несколько вариантов переработки твоего кода. Облагораживаем: a = ( ( (b + c) * (d + e) * (f + g) + 5 ) + ( (h + i) * (j + k) - 10 ) );
Убираем ненужные скобки (за исключением двух крайних — чисто для показа границ): a = ( (b + c) * (d + e) * (f + g) + 5 + (h + i) * (j + k) - 10 ); Доводим до упрощенного состояния: a = (b + c) * (d + e) * (f + g) + 5 + (h + i) * (j + k) - 10;
Разве так не лучше читается? Хочется отделить одну формулу от другой, напиши так: a = (b + c) * (d + e) * (f + g) + 5; a += (h + i) * (j + k) - 10;
Оптимизатор легко сведет это в одну цепочку. Рекомендую посмотреть код огромных open source проектов, на их стиль. Самый яркий пример — код ядра Linux — его пишут сотни рук со всего света, а читают его — многие и многие тысячи глаз.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #27 : 31-03-2010 18:22 » |
|
Ром, все "облагораживаем/улучшаем" не пойдут, ведь это был только пример, в общем случае он не упрощается убираением скобок а у меня разбегаются глаза вот от этого a = (b + c) * (d + e) * (f + g) + 5 + (h + i) * (j + k) - 10;
то ли дело a=(b+c)*(d+e)*(f+g) + 5 + (h+i)*(j+k) - 10;
А в книгах код всегда ужасен, по-моему.
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #28 : 31-03-2010 18:56 » |
|
Леш, ну где я про код говорил... Неужели ты художественную литературу не читаешь? :'(
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #29 : 31-03-2010 19:03 » |
|
А там кода не бывает как раз ))
|
|
|
Записан
|
|
|
|
|