perl6
Помогающий
Offline
|
|
« : 01-11-2015 10:27 » |
|
Доброго времени суток, господа. Собственно, вот, в чем загвоздка. Был на коленке написан парсер торрент трекера локально ресурса. Трекер довольно неплохо развивается. Так вот, парсер этот, само собой - многопоточный. И все бы ничего, если бы не хитрожопый админ. То ли делать ему было нечего, то ли еще, что, но в один прекрасный день он взял, да и обрубил этот самый многопоток. К слову, дабы не наглеть и слишком не нагружать ресурс, парсил я его очень аккуратно. Теперь, не могу понять, как, но парсится только в один поток. При попытке парсить в 10 (как раньше, с теми же задержками) - начинает отдаваться ошибка 404. Первой мыслью было, что айпи лочит (в локальной сети он у нашего прова - статичен и сер ). Но, ставил парсинг в один поток почти на сутки, никаких проблем, все замечально. Если бы был бан по айпи, логично предположить, что под конец все равно бы полезли ошибки после n-количества запросов. В голову приходит, что проблема в потоках, почему то таймстемпы у них - одинаковые практически. P.S. Чуть не забыл, сам быдлокод sub go { $ua->post('авторизовались'); #спим
$ua->get('сграбили');
#спим }
for (1 .. 10) { threads->new(\&main); #спим чтоб потоки слишком быстро не шапарили друг за другом }
$_->join for threads->list;
sub main { while (1) { go } }
|
|
« Последнее редактирование: 01-11-2015 10:37 от perl6 »
|
Записан
|
|
|
|
RXL
|
|
« Ответ #1 : 01-11-2015 19:06 » |
|
На твоем месте, во-первых, отказался бы от многопоточности (в Perl с ней беда). Во-вторых, обратил внимание, что в примере всего один объект LWP::UserAgent, используемый несколькими потоками.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #2 : 01-11-2015 20:24 » |
|
Спасибо, Роман. А что не так с многопоточностью в perl ? Про один объект как-то не подумал, а чем он может помешать? До этого все ведь работало. И как для каждого потока свой объект создавать???
|
|
« Последнее редактирование: 01-11-2015 20:32 от perl6 »
|
Записан
|
|
|
|
RXL
|
|
« Ответ #3 : 01-11-2015 20:58 » |
|
Достаточно ли ты знаешь о многопоточности, чтобы ее применять? "все работало" — аргумент чайников.
Если хочешь параллельности, лучше разберись с асинхронным программированием и AnyEvent.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #4 : 02-11-2015 13:21 » |
|
Достаточно ли ты знаешь о многопоточности, чтобы ее применять? "все работало" — аргумент чайников. Достаточно ли мы знаем о жизни, Роман? Кто мы, венец творения или, злая шутка создателя. Но живем же как-то, выживаем в этой стране. Это я к тому, что мне и не надо знать, за меня написал какой то умелец с CPAN модуль threads, а мне остается его только подключить? Не??? Если хочешь параллельности, лучше разберись с асинхронным программированием и AnyEvent.
Переписывать код времени нет и сил, трудозатраты не сопоставимы с результатом, как мне кажется. А так, я на этот модуль внимание обращал, еще смотрел в сторону POE.
|
|
|
Записан
|
|
|
|
RXL
|
|
« Ответ #5 : 02-11-2015 13:26 » |
|
за меня написал какой то умелец с CPAN модуль threads, а мне остается его только подключить? Не???
За меня автомобиль собрали на заводе. Мне надо только сесть и поехать, учиться и права - не обязательно. Добавлено через 50 секунд:Переписывать код времени нет и сил, трудозатраты не сопоставимы с результатом, как мне кажется. А так, я на этот модуль внимание обращал, еще смотрел в сторону POE.
Во-вторых, обратил внимание, что в примере всего один объект LWP::UserAgent, используемый несколькими потоками.
Самый быстрый способ. Добавлено через 2 минуты и 29 секунд:И как для каждого потока свой объект создавать???
Один автомобиль на несколько водителей. На каждом повороте они по очереди меняются. Все едут по своим маршрутам. Где они в итоге окажутся? Создать локальную копию для потока логично будет в main и передать в параметрах в go. Добавлено через 1 минуту и 19 секунд:До этого все ведь работало.
Работало, значит неисправный код попадал в ситуацию, когда его неисправность маскировалась чем-либо или редко проявлялась.
|
|
« Последнее редактирование: 02-11-2015 13:34 от RXL »
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #6 : 03-11-2015 12:59 » |
|
Создать локальную копию для потока логично будет в main и передать в параметрах в go. Что то не соображу, как это сделать. У меня в go также происходит формирование заголовков и генерация рандомного UA из списка.
|
|
|
Записан
|
|
|
|
RXL
|
|
« Ответ #7 : 03-11-2015 13:49 » |
|
Сейчас $ua создается как глобальная переменная?
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #8 : 03-11-2015 16:19 » |
|
Сейчас $ua создается как глобальная переменная?
ДА! как-то так $ua = LWP::UserAgent->new; P.S. Попутно начал ковырять AnyEvent::HTTP, но, сдается мне, дело не в отсутствии асинхронности запросов. Т.е., запускаю я один раз скрипт без потоков вообще в бесконечном цикле (да можно и с одним потоком) - все нормально. Запускаю одновременно 2 раза один скрипт, все, начинают вылезать траблы. Не пойму, как это объяснить, ограничения что ли с одного айпи на коннекты.
|
|
« Последнее редактирование: 03-11-2015 16:23 от perl6 »
|
Записан
|
|
|
|
RXL
|
|
« Ответ #9 : 03-11-2015 17:21 » |
|
Сейчас $ua создается как глобальная переменная?
ДА! как-то так $ua = LWP::UserAgent->new; sub main { my $ua = LWP::UserAgent->new;
go($ua) }
sub go { my ($ua) = @_;
... } или сразу в go: sub go { my $ua = LWP::UserAgent->new;
... } Рекомендую взять за заметку: use strict; use warnings; По началу код может не взлететь, но после исправления требований use strict станет надежнее. Добавлено через 2 минуты и 33 секунды:P.S. Попутно начал ковырять AnyEvent::HTTP, но, сдается мне, дело не в отсутствии асинхронности запросов. Т.е., запускаю я один раз скрипт без потоков вообще в бесконечном цикле (да можно и с одним потоком) - все нормально. Запускаю одновременно 2 раза один скрипт, все, начинают вылезать траблы. Не пойму, как это объяснить, ограничения что ли с одного айпи на коннекты.
Траблы у тебя от незнания. AnyEvent - это способ сделать много вещей параллельно и при этом использовать всего один процесс и поток. Но асинхронное программирование требует писать качественный код. Даже если есть лимиты на стороне сервера, сперва нужно исправить свой код, а потом уже искать проблемы на другой стороне.
|
|
« Последнее редактирование: 03-11-2015 17:29 от RXL »
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #10 : 03-11-2015 17:33 » |
|
или сразу в go: Так и сделал. Спасибо. Только, думаю дело не в этом.
|
|
|
Записан
|
|
|
|
RXL
|
|
« Ответ #11 : 03-11-2015 23:49 » |
|
В этом в первую очередь. Нельзя использовать один объект с внутренним состоянием одновременно в нескольких потоках.
А в чем дело с сервером, нуждается в исследовании. На твоем бы месте я добавил логирование в файл всех неудавшихся подключений: запрос с заголовками, код ответа с заголовками. Или можно собрать трафик wireshark или tcpdump и посмотреть, что там творится.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #12 : 04-11-2015 12:39 » |
|
В этом в первую очередь. Нельзя использовать один объект с внутренним состоянием одновременно в нескольких потоках. Хорошо, будем переписывать под AnyEvent. Я просто довольно много примеров смотрел, там в основном просто загрузка списка разных урлов идет, хоть и асинхронно. А, если, мне надо после загрузки обработать результат и выждав таймаут (дабы не заддосить запросами), отправить на сервер. Тут все равно не получится асинхронности, т.к. пауза тормозит ввыполнение всего кода. Да и сайт один, а не список разных сайтов. А в чем дело с сервером, нуждается в исследовании. На твоем бы месте я добавил логирование в файл всех неудавшихся подключений: запрос с заголовками, код ответа с заголовками. Или можно собрать трафик wireshark или tcpdump и посмотреть, что там творится.
Сохранял в файл результаты и мониторил HttpAnalyzer'om, чтоб кроме htpp всякий мусор не собирался. Никаких изменений не заметил, к слову, ответы такие же, как и при нормальном запросе, только результата нет. Добавлено через 2 часа, 8 минут и 48 секунд:Собственно, код, отправляет запрос. Вот, что не ясно, а как, собственно, потом работать с $data, ведь я обработываю результат (регекспом) и снова отправляю данные на сервер, но, подождав timeout секунд 10... С libwww проще было разобраться, а тут что то не догоняю... use warnings; use AnyEvent; use AnyEvent::HTTP;
my $cv = AnyEvent->condvar; http_get "http://www.cpan.org", headers => { 'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2)' }, timeout => 20, cookie_jar => {}, sub { my( $data, $headers ) = @_; print $data; $cv->send; };
$cv->recv; в sub, это, что, локальный коллбек что ли, где потом данные хранятся...
|
|
« Последнее редактирование: 04-11-2015 14:48 от perl6 »
|
Записан
|
|
|
|
RXL
|
|
« Ответ #13 : 04-11-2015 17:28 » |
|
Так будет понятнее: use strict; use warnings; use EV; use AnyEvent; use AnyEvent::HTTP; sub my_data_handler { my ($data, $headers) = @_; print $data; exit; # чтобы выйти из цикла событий, т.к. запрос у нас один } http_get( "http://www.cpan.org", headers => { 'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2)' }, timeout => 20, cookie_jar => {}, \&my_data_handler ); EV::loop();
AnyEvent поддерживает много разных событийных движков. Я использовал цикл событий EV. Что здесь происходит: * http_get добавляет задание в цикл событий. * EV::loop запускает бесконечный цикл событий. * Отработав http_get вызовет my_data_handler и отдаст ему заголовки и данные. Если нужно запустить несколько запросов, ставь несколько заданий перед запуском цикла. Также можно ставить задания во время выполнения цикла из любого колбека. Когда нужно остановить цикл - реши программно. Например, подсчетом поставленных и завершенных заданий. #!/usr/bin/perl use strict; use warnings; use EV; use AnyEvent; use AnyEvent::HTTP; use Data::Printer;
my $request_cnt = 0; # подсчет заданий
sub my_data_handler { # обработчик ответа my ($data, $headers) = @_;
p $headers; exit unless --$request_cnt; # завершаемся в конце }
sub add_request { # функция установки однотипных заданий http_get( "http://www.cpan.org", headers => { 'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2)' }, timeout => 20, cookie_jar => {}, \&my_data_handler ); ++$request_cnt; }
add_request for 1 .. 5; # ставлю 5 заданий
EV::loop(); Но надо еще обрабатывать ошибки.
|
|
« Последнее редактирование: 04-11-2015 17:47 от RXL »
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #14 : 04-11-2015 17:47 » |
|
Если нужно запустить несколько запросов, ставь несколько заданий перед запуском цикла. Также можно ставить задания во время выполнения цикла из любого колбека. Спасибо, попробую разобраться. Касательно запросов - запросы то к одному сайту, как быть с таймаутами? P.S. однотипные задания (как я это вижу) - просто загрузка урлы, у меня ведь загрузил урлу (одну), распарсил, выждал, отправил ответ. P.P.S. Вот примерно каким был код (полностью), пока не залочили многопоток. #!/usr/bin/perl -w
$| = 1;
use LWP::UserAgent; use LWP::ConnCache; use HTTP::Cookies; use threads; use utf8;
sub goGet {
$ua = LWP::UserAgent->new; $ua->conn_cache(LWP::ConnCache->new()); $ua->agent('Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17'); $ua->timeout(30); #$ua->max_redirect(0); $ua->default_header('Accept-Encoding' => "gzip,deflate"); $ua->default_header('Accept-Language'=> "*/*"); $ua->default_header('Accept'=> "*/*"); $ua->default_header('Accept-Charset' => "windows-1251,utf-8"); $cookie_jar = HTTP::Cookies ->new( ignore_discard=>1, ); $ua ->cookie_jar($cookie_jar);
$response = $ua->get($url, 'Referer', $referer);
if (regexp) {
sleep (int(rand(40))+20);
$ua->get($regexp, 'Referer', $referer)
}
sleep (int(rand(100)+20)) }
for (1 .. 10) { threads->new(\&main); sleep (int(rand(90)+25)); }
$_->join for threads->list;
sub main
{ while (1) { goGet }}
Вообще, конечно, не ясно, асинхронный запрос или, многопоточно бы грабим данные, как лочится айпи...
|
|
« Последнее редактирование: 04-11-2015 17:55 от perl6 »
|
Записан
|
|
|
|
RXL
|
|
« Ответ #15 : 04-11-2015 17:55 » |
|
Почитай AnyEvent::Handler: http://search.cpan.org/dist/AnyEvent/lib/AnyEvent/Handle.pmЭто низкоуровневая работа с циклом событий. Там есть таймеры, сокеты и т.д. Немного наврал: таймеры в самом AnyAvent. Одно важное НО: хендлер существует только пока ты его хранишь в переменной, как только значение уничтожается, хендлер удаляется из цикла событий. AnyEvent::HTTP фактически делает следующее: создает хендлер работы с сокетом и сохраняет в своих данных ссылку на него, формирует и посылает запрос, принимает данные и обрабатывает их, по окончанию приема убивает хендлер и вызывает пользовательский колбек. Добавлено через 5 минут и 59 секунд:P.P.S. Вот примерно каким был код (полностью), пока не залочили многопоток.
А ты можешь писать форматированный код? Убить хочется за такое... Добавлено через 2 минуты и 40 секунд:sub goGet { my $ua = LWP::UserAgent->new; Без my ты используешь глобальную переменную. Если бы ты написал use strict, то perl сразу бы тебе сказал, что не знает такую. Тоже самое с остальными переменными.
|
|
« Последнее редактирование: 04-11-2015 18:04 от RXL »
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #16 : 04-11-2015 18:13 » |
|
Отформатированный код #!/usr/bin/perl -w
$| = 1;
use LWP::UserAgent; use LWP::ConnCache; use HTTP::Cookies; use threads; use utf8;
sub goGet { $ua = LWP::UserAgent->new; $ua->conn_cache(LWP::ConnCache->new()); $ua->agent('Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17'); $ua->timeout(30); #$ua->max_redirect(0); $ua->default_header('Accept-Encoding' => "gzip,deflate"); $ua->default_header('Accept-Language' => "*/*"); $ua->default_header('Accept' => "*/*"); $ua->default_header('Accept-Charset' => "windows-1251,utf-8"); $cookie_jar = HTTP::Cookies->new(ignore_discard => 1); $ua->cookie_jar($cookie_jar);
$response = $ua->get($url, 'Referer', $referer);
if (regexp) { sleep(int(rand(40)) + 20); $ua->get($regexp, 'Referer', $referer); }
sleep(int(rand(100) + 20)); }
for (1 .. 10) { threads->new(\&main); sleep(int(rand(90) + 25)); }
$_->join; for threads->list;
sub main { while (1) { goGet; } } По поводу my - уже исправлял, я ведь и уточнил, что это исходный вариант. С потоками и fork'ми ну никак не работает, потому и ковыряю AnyEvent... А код привел, чтобы показать, что там не банальная скачка, а именно работа с данными, парсинг.
|
|
« Последнее редактирование: 04-11-2015 22:00 от RXL »
|
Записан
|
|
|
|
RXL
|
|
« Ответ #17 : 04-11-2015 22:02 » |
|
Парсинг — вещь вполне быстрая. Ты ж не интерактивно разбираешь страницы.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #18 : 05-11-2015 09:07 » |
|
Парсинг — вещь вполне быстрая. Ты ж не интерактивно разбираешь страницы.
Да, там у меня несложная регулярка. Собственно, вопрос по поводу второго запроса. sub my_data_handler { # обработчик ответа my ($data, $headers) = @_; if ($data =~ /regexp/) { $str = $1
} exit unless --$request_cnt; # завершаемся в конце }
Так вот, вытащил я из $data нужные мне данные, а как теперь работать то с $str. Насколько я понял, колбэк data_handler срабатывает только для add_request. Т.е., я не могу впихнуть еще один запрос в add_request или, допустим, сделать еще один отдельный запрос, а обрабатывать его в одном колбэке data_handler. Нужно, что, городить новый запрос и к нему отдельный колбэек? Наподобие add_request_1 и data_handler_1
|
|
|
Записан
|
|
|
|
RXL
|
|
« Ответ #19 : 05-11-2015 10:21 » |
|
Не понял вопроса.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #20 : 05-11-2015 12:29 » |
|
Не понял вопроса.
Не совсем понятно, что делать дальше, когда я вытащил значение при помощи регулярки. Как дальше отправить данные на сервер, с теми же заголовками, но с урлой, хранимой в $str. Во всех примерах, ну и в вашем, Роман, данные или выводятся просто на печать или, сохраняются в файл. sub my_data_handler { my ($data, $headers) = @_; if ($data =~ /regexp/) { $str = $1 # тут храним урлу для второго запроса к этому же сайту
} exit unless --$request_cnt; }
|
|
« Последнее редактирование: 05-11-2015 12:31 от perl6 »
|
Записан
|
|
|
|
RXL
|
|
« Ответ #21 : 05-11-2015 16:23 » |
|
Добавить новое задание в цикл событий. Через http_post, к примеру. Асинхронное программирование строится на регистрации обработчиков ожидаемых событий. Поставил запрос на получение данных и колбек на завершение. В колбеке обработал данные, поставил запрос на отсылку данных и колбек на завершение. В этом колбеке можешь проделать еще какие-то действия и поставить другой обработчик.
|
|
« Последнее редактирование: 05-11-2015 16:28 от RXL »
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #22 : 05-11-2015 18:44 » |
|
!/usr/bin/perl -w
use EV; use AnyEvent; use AnyEvent::HTTP;
my $string_return;
sub my_data_handler { my ($data, $headers) = @_; $data =~ m//; my $str = $1; $str =~ s//g; $string_return = $str }
sub my_data_handler_2 { }
sub add_request { http_get( $url, headers => { 'User-Agent' => 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36', 'Accept-Encoding' => 'gzip,deflate', 'Accept-Language' => '*/*', 'Accept' => '*/*', 'Accept-Charset' => 'windows-1251,utf-8', 'Referer' => '' }, timeout => 20, cookie_jar => {}, \&my_data_handler ); }
sub add_request_2 { http_get( $string_return, headers => { 'User-Agent' => 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36', 'Accept-Encoding' => 'gzip,deflate', 'Accept-Language' => '*/*', 'Accept' => '*/*', 'Accept-Charset' => 'windows-1251,utf-8', 'Referer' => '' }, timeout => 20, \&my_data_handler_2 ); }
add_request ;
add_request_2;
EV::loop();
Вот такой вот быдлокодинг. Убрал счетчик, а то что то нагромождение всего. Похоже, второй запрос вообще ничего не знает про $string_return, она не видна за пределами первого колбека. Да, и куки на тот же домен не идут. Делается первый запрос нормально, потом виснет, видимо, не знает, что делать дальше
|
|
« Последнее редактирование: 05-11-2015 18:47 от perl6 »
|
Записан
|
|
|
|
RXL
|
|
« Ответ #23 : 06-11-2015 00:09 » |
|
Не могу я более видеть твоего "кода". Работать он не будет. Попробуй дальше самостоятельно. Магии тут никакой нет - просто надо понять принцип работы. Перечитай мои последние посты - это должно помочь. Детали разобрать помогу, а вот возиться с твоим кодом никакого желания нет.
|
|
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #24 : 06-11-2015 08:56 » |
|
Не могу я более видеть твоего "кода". С аксакалами perl программирования не спорю, тягаться мне не стоит. Работать он не будет. Да это я уже и сам понял. Попробуй дальше самостоятельно. Магии тут никакой нет - просто надо понять принцип работы. Принцип тут такой, на мой взгляд, что AnyEvent подходит для простого парсинга, не более. Параллельно стянуть штук 10 страниц, вывеси на печать или, все это скласть в файл. Перечитай мои последние посты - это должно помочь. Их уже выучил, как отче наш. Но, что то не помогает. Разница с примерами из cpan'a в том, что у вас коллбэек вынесен за пределы http_get запроса, где также просто напросто распечатывается результат... Детали разобрать помогу, а вот возиться с твоим кодом никакого желания нет.
Хорошо, спасибо. Но, я буду рыть в сторону POE, мне кажется, событийная машина спасет отца русской демократии... Не состоявшегося отца, конечно, но все-таки спасет.
|
|
« Последнее редактирование: 06-11-2015 09:21 от perl6 »
|
Записан
|
|
|
|
perl6
Помогающий
Offline
|
|
« Ответ #25 : 06-11-2015 19:35 » |
|
Собственно, резюмирую свои наблюдения.... Асинхронная HTTP загрузка была запилена с небольшими костылями на POE (благо, POE: Cookbook изобилует примерами)... Но, как я уже писал много раз, вся эта асинхронность подходит для разных сайтов, когда цель - спарсить, допустим, списки проксей с разных источников. Тогда, да, все получается здорово, очереди нет (допустим, сайт1 - медленный, сайт2 - шустрый). Обычно, пока сайт1 чухается, парсер простаивает, ибо - очередь. А тут - нет, я сам удивился, что хоть запрос к сайт2 был позже, обработался он быстрее, чем от медленного сайт1. Иными словами, вот она - асинхронность. Правда, мне она никак не подходит, у меня домен один, сделал запрос -> выждал -> отравил ответ. Все равно, очередь и простой.
|
|
|
Записан
|
|
|
|
|