Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2009.10.18;
Скачать: CL | DM;

Вниз

Асинхронные сокеты "забивают" очередь сообщений   Найти похожие ветки 

 
SpellCaster   (2008-04-01 17:01) [0]

Делаю класс асинхронного сокета и столкнулся с проблемкой зависания проги. Прога коннектится к серверу и начинает принимать от него непрерывный поток данных. Соответственно, после каждого Recv в очередь сразу же ставится следующее сообщение с событием FD_READ, которое тут же и обрабатывается. В итоге сообщения от главного окна просто не могут пробиться сквозь этот цикл. Вместе с тем, если запускать в начале процедуры SocketEvent Application.ProcessMessages, то все работает (естественно). Однако есть у меня сомнение: ведь эта процедура сразу вытаскивает из очереди очередное сообщение. Вполне вероятно, что это может оказаться следующая мессага от того же сокета - и тогда SocketEvent будет запущен второй раз, а уже после обработки этого сообщения управление вернется к первой копии SocketEvent. То есть, к примеру, может получиться так, что сначала обработается FD_CLOSE, а потом придет очередь FD_READ, что не очень-то здорово.
Поэтому хочу спросить у знатоков, есть ли какой-то хороший путь решения данной проблемы? Или, может, я зря волнуюсь насчет перепутывания сообщений? В идеале хотелось бы иметь способ вместо ProcessMessages обработать только мессаги формы, а потом уже с чистой совестью заниматься с сокетом.
Посмотрел сорсы ICS, там реализована собственная выборка собщений. В принципе, можно сделать так же, но я пока не разобрался, как ее применять.
В общем, если что посоветуете, буду рад... только два условия:
1) Не хочется юзать нити
2) Видеть реплики типа "Зачем тебе это надо" и "не занимайся ерундой" также не горю желанием.


 
Сергей М. ©   (2008-04-01 17:09) [1]

Показывай код обработки оконных сообщений. посылаемых созданными тобой гнездами ...


 
SpellCaster   (2008-04-01 17:33) [2]


// Socket message receiver
procedure TAsyncClientSocket.DoEvent(WParam, LParam: Longint);
var SockErr: Integer;
   Event: Word;
begin
 Event:=WSAGetSelectEvent(LParam);
 SockErr:=WSAGetSelectError(LParam);
 // если произошла ошибка
 if SockErr<>0 then
   begin DoError(WParam,Event,SockErr); Exit; end;

 // если пришло ожидаемое событие, обнуляем таймер
 if fRespEvent=Event then fRequestTime:=0;

 // на обработку соединялки с проксей отправляем только события READ и WRITE
 case Event of
   FD_CONNECT        : begin
                         fActive:=True;
                         if fProxyUsed then begin fSocksState:=ssConnecting; Exit; end;
                       end;
   FD_READ, FD_WRITE : if HandleSocksConnect then Exit;
   FD_CLOSE          : begin
                         fSocksState:=ssNone;
                         fRequestTime:=0;
                         Close;
                       end;
   else Exit;
 end;
 if Assigned(OnEvent) then OnEvent(Self,WParam,Event);
end;


ну и в программе


procedure TClient.SocketEvent(Sender: TObject; Sckt: TSocket; Event: Word);
var received: Integer;
   req: ShortString;
   Msg: TMsg;
begin
 Application.ProcessMessages;

 case Event of
   // сокет соединился
   MsgConnect:
     begin
        ...
     end;
   // в буфер сокета поступили данные
   MsgRead:
     begin
          ...
           begin
             // Проверяем результат: если ошибка - соединение закрываем и переподключаемся
             received:=fSock_in.Recv(@fBuf[0],PacketSize);
             if received<=0 then
               begin SetState(nsErr,"receive: "+WSALastErr); Close; Exit; end
             // всё ОК - пишем полученные данные в выходной поток и обнуляем счетчик попыток
             else
             begin
               ...
             end;
     end; // MsgRead
   MsgWrite  :   ;
   MsgClose  : begin
                 SetState(nsStop);
                 SetState(nsInactive);
               end;
 end;
end;



HandleSocksConnect возвращает False, если соединение с соксом уже установдено.


 
Сергей М. ©   (2008-04-02 08:31) [3]


> HandleSocksConnect возвращает False, если соединение с соксом
> уже установдено


А если не установлено, то что творится в этой ф-ции ?


> Application.ProcessMessages;


Это, разумеется, следует убрать.


 
SpellCaster   (2008-04-02 11:48) [4]

> А если не установлено, то что творится в этой ф-ции ?

Устанавливается коннект с проксей. Приводить, наверно, не буду, но смысл в том, что в зависимости от состояния коннекта читаются или отправляются пакеты для установки связи.

> Это, разумеется, следует убрать.
Так тогда мессаги от юзера вообще не проходят!


 
Григорьев Антон ©   (2008-04-02 12:54) [5]

А как огранизован приём сообщений классом TAsyncClientSocket? Какое окно за это отвечает? Кто вызывает TAsyncClientSocket.DoEvent?


 
SpellCaster   (2008-04-02 13:03) [6]

> [5] Григорьев Антон ©   (02.04.08 12:54)

Вот такая процедурка, одна на всех

function InnerWndProc(wnd: hWnd; msg, wParam, lParam: Longint): Longint; stdcall;
var sock: TObject;
  s: string;
begin
 if not ((msg = WM_SOCKMSG) or (msg = WM_TIMER)) then
 begin
   Result:=DefWindowProc(wnd,msg,wparam,lparam);
   Exit;
 end;
 // else
 Result:=0;
 sock:=TObject(Pointer(GetWindowLong(wnd,GWL_USERDATA)));
 if sock=nil then Exit;
 s:=sock.ClassName;
 if s="TAsyncClientSocket" then
   case msg of
     WM_SOCKMSG: TAsyncClientSocket(sock).DoEvent(WParam, LParam);
     WM_TIMER:   TAsyncClientSocket(sock).DoTimer;
   end
 else
 if s="TAsyncServerSocket" then
   case msg of
     WM_SOCKMSG: TAsyncServerSocket(sock).DoEvent(WParam, LParam);
     WM_TIMER:   TAsyncServerSocket(sock).DoTimer;
   end;
end;


Окошко у каждого сокета своё, внутреннее.

P.S. Огромное спасибо за статью на Королевстве. Я именно по ней изучал сокеты :)


 
Сергей М. ©   (2008-04-02 13:18) [7]


> Устанавливается коннект с проксей


Кто такая "прокся" ?
Как устанавливается коннект с ней ?
Каково вообще назначение класса TAsyncClientSocket ?
Это чистой воды транспортный класс ?
Если да, то какого лешего он лезет в прикладной протокол ?


 
SpellCaster   (2008-04-02 13:44) [8]

> [7] Сергей М. ©   (02.04.08 13:18)

Ну вот, снова началось...

Прокси-сервер, работающий по протоколу Socks5.
По спецификациям протокола.
Получать и отправлять данные в асинхронном режиме.
Не знаю, как ты определяешь транспортный или прикладной класс. Однако я сделал так, как сделал. Причем в обоих паках инет-компонентов, что я глядел, соксы упрятаны на самый низкий уровень. Ибо нет смысла вытаскивать их выше.

А теперь, может, закончим оффтоп и вернемся к обработке сообщений?


 
Сергей М. ©   (2008-04-02 13:52) [9]


> закончим оффтоп и вернемся к обработке сообщений?
>


Это далеко не оффтоп.

Но если тебе по барабану все эти важные моменты, то показывай, что у тебя творится в теле HandleSocksConnect


 
SpellCaster   (2008-04-02 14:15) [10]

Прости, но эти важные моменты, на мой взгляд, не имеют никакого отношения к проблеме. Соединение через сокс проходит нормально. Разъясни, как это влияет на ответ по сабжу, может, я чего-то недопонимаю...
// Connect to Socks
function TFrostAsyncClientSocket.HandleSocksConnect: Boolean;
var len: Integer;
   auth:  TSocks5AuthSel;
   ameth: TSocks5AuthMeth;
   req:   TSocks5Request;
   repl:  TSocks5Reply;
   Addr: array[0..255] of Byte;

begin
 Result:=True;
 if fProxyUsed and (fSocksState in [ssConnecting..ssRequesting]) then
 case fSocksState of
   ssConnecting:      // Договариваемся о методах аутентификации
         begin
           fSocksState:=ssMethSelecting;
            ...
           Send(@auth,3,True);
         end;
   ssMethSelecting:   // Устанавливаем связь с хостом
         begin
           if Recv(@ameth,SizeOf(ameth))<=0 then begin Close; WSASetLastError(WSAESOCKSCONNECT); Exit; end;
           ...
           fSocksState:=ssRequesting;
           Send(@req,SizeOf(req));
           Send(@Addr[0],len,True);
         end;
   ssRequesting:  // теперь читаем ответ - ровно столько, сколько прислали
         begin
           // сначала читаем версию, ответ, резерв, тип адреса и 1 байт адреса
           len:=Recv(@repl,SizeOf(repl));
            ...
           if Recv(@Addr,len-1+2)<=0 then begin Close; WSASetLastError(WSAESOCKSCONNECT); Exit; end;
            ...
           fSocksState:=ssEstablished;
           fActive:=True;
           if Assigned(OnEvent) then OnEvent(Self,fSckt,FD_CONNECT);
           PostMessage(fHwnd,WM_SOCKMSG,fSckt,MakeLong(FD_WRITE,0));
         end;
 end  // case
 else Result:=False;
end;

Я убрал заполнение свойств структур, чтобы не очень много получилось. Основнй смысл в том, что метод что-то делает только тогда, когда стоит признак использования прокси и состояние коннекта к сокс-серверу не равно ssEstablished.


 
Сергей М. ©   (2008-04-02 14:38) [11]

Send и Recv - это методы класса TFrostAsyncClientSocket ?
Какой режим гнезда они используют ?


 
SpellCaster   (2008-04-02 16:51) [12]

Да, это методы класса, но они по большому счету просто вызывают винсоковские функции, плюс считают трафик и производят проверку активности сокета.


 
Сергей М. ©   (2008-04-02 17:34) [13]

Какой режим гнезда они используют ?
Они чтго, на время выполнения переводят гнездо в блок.режим ?
Или обращаются совсем к другому
гнезду, нежели то самое неблокирующее гнездо, вокруг которого ты затеял сыр-бор ?


 
SpellCaster   (2008-04-02 19:46) [14]

> Какой режим гнезда они используют ?

Они не меняют режима, т.е. в данном случае в асинхронном. Считай, что это обычные вызовы send/recv winsock-a. А что тебя смутило, разве я как-то не так их использую?


 
Сергей М. ©   (2008-04-02 21:10) [15]


> что тебя смутило, разве я как-то не так их использую?


Так ведь все эти твои send/recv, относящиеся к какому-то там "прокси", точно так же являются инициаторами потенциальных событий FD_READ и FD_WRITE, обрабатывая которые ты вновь вызываешь эту самую  HandleSocksConnect, в которой ты опять вызываешь send/recv, относящиеся к какому-то там "прокси", которые являются инициаторами потенциальных событий FD_READ и FD_WRITE, которые ..

Короче, сказ про то как "у попа была собака")

Кстати, где обработка FD_WRITE ?
Нет ее !
Только не говори, что она не нужна или ты не  знаешь зачем она нужна)


 
SpellCaster   (2008-04-03 15:08) [16]

> [15] Сергей М. ©   (02.04.08 21:10)
> Короче, сказ про то как "у попа была собака")

Для этого как раз и используется переменная fSocksState, которая отслеживает текущее состояние соединения с проксей. Никакого сказа про попа нету, иначе сокет никогда бы не сумел нормально добраться до удалённого хоста, а крутился бы в локалке.

> Кстати, где обработка FD_WRITE ?
> Только не говори, что она не нужна или ты не  знаешь зачем
> она нужна)

Событие FD_WRITE возникает в двух случаях: после FD_CONNECT и когда предыдущий вызов Send закончился ошибкой по причине переполнения выходного буфера. В нашем случае второй вариант не рассматривается. А обработка - она как суслик: она есть, даже если ты ее не видишь ;)
> [2] SpellCaster   (01.04.08 17:33)

FD_READ, FD_WRITE : if HandleSocksConnect then Exit;


Так, это все конечно интересно, но есть ли что сказать по существу вопроса?


 
Сергей М. ©   (2008-04-03 15:18) [17]


> она есть, даже если ты ее не видишь


Я даже по огрызкам вижу, что первым делом при входе в HandleSocksConnect ты тут же озабочен каким-то там fSocksState, хотя первым делом должен идти анализ причин возникновения события - то ли буфер передачи освободился, то ли буфер приема не пуст.


> второй вариант не рассматривается


Почему ? Что за откровение такое тебе пришло, что это событие никогда у тебя не возникнет ?


 
SpellCaster   (2008-04-03 16:07) [18]

> то ли буфер передачи освободился, то ли буфер приема не пуст


> Что за откровение такое тебе пришло, что это событие никогда
> у тебя не возникнет

Учитывая, что эта процедура выполняется сразу после создания сокета, а пересылаемые данные не превышают 100 байт, я ОЧЕНЬ сильно сомневаюсь в вероятности возникновения FD_WRITE как результата освобождения занятого буфера.
Имеется в виду, во время установки связи через соксы. Потом - вполне возможно, но я его и пересылаю в обработчик.

Пожалуйста, не надо давить на меня интеллектом. Я и так знаю, что ты гуру. А вот начальный вопрос так и остается без ответа.


 
Сергей М. ©   (2008-04-03 16:33) [19]


> во время установки связи через соксы


Какие "соксы" ?

Ты о чем - о SOCKS4, о SOCKS5 ?
Там куча режимов, вплоть до необходимости реконнекта кл.гнезда по указанному сервером адресу !
Почем мне знать, какой протокол в каком режиме ты решил использовать ?

Ты бросил огрызок код - нате, мол, догадывайтесь сами о том что я не изволил привести и не собираюсь приводить.

Ну и какого ответа ты после этого ждешь ?


> я ОЧЕНЬ сильно сомневаюсь в вероятности


Не надо сомневаться. Не сильно ни слабо. Надо действовать в точном соответствии с логикой Winsock, а не надеяться на авось.

И уж если тебе приспичило запихнуть в один флакон все это, то проще, надежней и очевидней бфло бы вынести handshake-логику в отдельную процедуру, никак не связанную ни с какими событиями гнезда, для чего нужно всего лишь на время "переговоров" с прокси перевести гнездо в блок.режим.


 
Сергей М. ©   (2008-04-03 17:07) [20]

Если я правильно понял, ты сочиняешь некий "упрощенный" TClientSocket, способный работать с сервером через некий socks-прокси в "прозрачном" режиме ?


 
Сергей М. ©   (2008-04-03 17:20) [21]


> Вполне вероятно, что это может оказаться следующая мессага
> от того же сокета - и тогда SocketEvent будет запущен второй
> раз


Откуда ей, этой "мессаге от того же сокета" взяться, если recv() ты вызываешь после Application.ProcessMessages ?

Очередное FD_READ-событие возникнет не раньше чем будет выполнен вызов recv().

Равно как и очередной вызов send() следует выполнять не раньше FD_WRITE-события, если оно явилось следствием отказа WSAEWOULDBLOCK при предыдущем вызове send()


 
SpellCaster   (2008-04-04 13:53) [22]

> [19] Сергей М. ©   (03.04.08 16:33)
> Ты о чем - о SOCKS4, о SOCKS5 ?

Говорил же...
>Прокси-сервер, работающий по протоколу Socks5.
Обычный метод, без аутентификации.

> Ты бросил огрызок код - нате, мол, догадывайтесь сами о
> том что я не изволил привести и не собираюсь приводить.
>
> Ну и какого ответа ты после этого ждешь ?

"Отгрыз" я код по единственной причине - чтобы не загружать приведенный фрагмент лишними строками, где не происходит ничего важного, кроме заполнения полей записи. Не думаю, что кто-то захочет ковыряться в длинном листинге.
К тому же позволю себе заметить, что данная процедура весьма слабо соотносилась с сабжем.

> для чего нужно всего лишь на время "переговоров" с прокси
> перевести гнездо в блок.режим

Хм, это, конечно, вариант... однако прога на время установки соединения будет блокироваться, чего бы не хотелось. Можно еще с неблокирующими сделать, в цикле проверять готовность и периодически вызывать ProcessMessages. Но мне это кажется слегка нелогичным.

> Если я правильно понял, ты сочиняешь некий "упрощенный"
> TClientSocket, способный работать с сервером через некий
> socks-прокси в "прозрачном" режиме ?

В целом, верно.

> Откуда ей, этой "мессаге от того же сокета" взяться, если
> recv() ты вызываешь после Application.ProcessMessages ?

Воот, наконец-то вернулись к сути вопроса. Разумеется, я не имел в виду FD_READ. Но ведь событие может быть и другим, например, FD_CLOSE. Тогда-то и случится засада: если я вызову Application.ProcessMessages в начале обработчика FD_READ, будет вызван обработчик FD_CLOSE, который спокойно закроет сокет и вернет управление обработчику FD_READ, который будет безуспешно пытаться прочитать данные из закрытого сокета. Так ведь получается?

> Равно как и очередной вызов send() следует выполнять не
> раньше FD_WRITE-события, если оно явилось следствием отказа
> WSAEWOULDBLOCK при предыдущем вызове send()

Учту...


 
Сергей М. ©   (2008-04-04 14:30) [23]


> если я вызову Application.ProcessMessages в начале обработчика
> FD_READ


Событие готовности гнезда к чтению предполагает чтение из гнезда, а не выкрутасы с польз.интерфейсом !


> FD_CLOSE, который спокойно закроет сокет и вернет управление
> обработчику FD_READ, который будет безуспешно пытаться прочитать
> данные из закрытого сокета


Ну и что ? Первая же попытка чтения вернет отказ, на который твой обработчик должен адекватно отреагировать.


 
SpellCaster   (2008-04-04 17:31) [24]

> Событие готовности гнезда к чтению предполагает чтение из
> гнезда, а не выкрутасы с польз.интерфейсом !

А я что, спорю? Предложи альтернативный способ, я его с радостью рассмотрю.

> Ну и что ? Первая же попытка чтения вернет отказ, на который
> твой обработчик должен адекватно отреагировать.

Да, но пришедшие данные будут утеряны!


 
Сергей М. ©   (2008-04-04 21:57) [25]


> Предложи альтернативный способ, я его с радостью рассмотрю


Альтернатив нет - сначала recv(), а потом выкрутасы.

Не нравится ?

Выноси транспорт с его событиями в доп.поток.


> пришедшие данные будут утеряны


С чего ты так уверен ?


 
SpellCaster   (2008-04-09 14:10) [26]

> С чего ты так уверен ?

Из закрытого сокета вроде как читать не получится...
Хорошо, тогда будет 2 вопроса:
1) Как организовать обработку событий сокета в отдельном потоке, потому что мессаги все равно ведь приходят в Application. Возможно, их надо фильтровать по коду мессаги?

2) Как насчет варианта вместо ProcessMessages сделать свой аналог, который будет извлекать из очереди только сообщения, не относящиеся к сокетам.


 
Сергей М. ©   (2008-04-09 14:24) [27]


> Из закрытого сокета вроде как читать не получится


Чтение-то не из сокета происходит, а из внутреннего буфера приема !

Если там что-то имеется на момент закрытия сокета, то recv вернет это самое "что-то".


> 1)


Ты обработку с выборкой не путаешь ?


> 2)


Никак.
Да и нафих оно не нужно, потому что извращенная это логика.

Но если изврат твоя стихия, то можно извлекать из очереди сообщения, относящиеся только к твоим сокетам и тут же ставить их в хвост очереди сообщений безо всякой обработки.


 
SpellCaster   (2008-04-09 18:15) [28]

> Чтение-то не из сокета происходит, а из внутреннего буфера
> приема !
> Если там что-то имеется на момент закрытия сокета, то recv
> вернет это самое "что-то".

Ну да. Но разве он не освобождается после закрытия? К тому же хэндл уэ точно не будет иметь смысла после Close.

> Ты обработку с выборкой не путаешь ?

Угу... есть такое дело. Так как сделать это в отдельном потоке? Или там нужно будет PostThreadMessage юзать?


 
Сергей М,   (2008-04-09 20:31) [29]


> разве он не освобождается после закрытия?


Смотря с какой стороны его закрыть.


> как сделать это в отдельном потоке?


Сделать что ?
Выборку ?
Обработку ?
И то и другое ?


 
SpellCaster   (2008-04-10 12:59) [30]

Выборку


 
Сергей М. ©   (2008-04-10 14:19) [31]

По умолчанию поток может выбирать сообщения только тем окнам, которые созданы в его собственном контексте.


 
SpellCaster   (2008-04-10 16:06) [32]

Ну и...?


 
Сергей М. ©   (2008-04-10 16:11) [33]

Что "ну и" ?


 
SpellCaster   (2008-04-10 17:38) [34]

В каком смысле > созданы в его собственном контексте.? Функция CreateЦindow была вызвана внутри Execute?
И тогда если в том же Execute я организую выборку сообщений, то она будет распространяться только на эти окна?


 
SpellCaster   (2008-04-10 18:14) [35]

Хотя мне все равно кажется, что это не выход... блин, ну как-то же делают серваки с сотнями клиентов, которые не тормозят... и этот ICS все хвалили, а там ведь такая же система


 
DiamondShark ©   (2008-04-10 20:09) [36]


> ну как-то же делают серваки с сотнями клиентов, которые
> не тормозят

Без окон


> и этот ICS все хвалили

ай да, конечно.


 
Сергей М,   (2008-04-10 20:19) [37]


> В каком смысле > созданы в его собственном контексте.? Функция
> CreateЦindow была вызвана внутри Execute?


Да.
Хотя каким боком к тебе этот Execute ?
Ты вон все апями озабочен да BeginThread"ами)


> если в том же Execute я организую выборку сообщений, то
> она будет распространяться только на эти окна?


По умолчанию - да.


> как-то же делают серваки с сотнями клиентов, которые
> не тормозят


Их по-другому делают.
Ты же не озаботился исследованием, ты сразу ринулся код лепить)
Еще и оффтопом меня попрекнул)

Чудо, блин)


 
SpellCaster   (2008-04-14 13:34) [38]

> Без окон

Может, есть и без окон, но большинтсво все-таки с гуем... виндоус все-таки. Ладно, уйдём от серваков. Возьмем "качалки" с кучей потоков загрузки - они-то не тормозят...

> Ты вон все апями озабочен да BeginThread"ами)

Вот не надо, треды через апи меня пока не привлекают)

> Ты же не озаботился исследованием, ты сразу ринулся код
> лепить)
> Еще и оффтопом меня попрекнул)

Гм. Исследованием чего?
Ну а насчет оффа - все-таки речь не о соксах, согласись.


 
SpellCaster   (2008-04-14 13:36) [39]

> Их по-другому делают

Вот я и хочу узнать, какой там принцип юзается. С нитями понятно, хотя все равно не хочется с ними дело иметь - иначе смысл городить огород, легче все на синхронных оставить...


 
Сергей М. ©   (2008-04-14 14:53) [40]


> большинтсво все-таки с гуем


Причем здей гуй ?


> уйдём от серваков. Возьмем "качалки" с кучей потоков загрузки
> - они-то не тормозят


И у тебя не будет "тормозить", если втанешь на "правильный курс".


> хочу узнать, какой там принцип юзается


Там транспортная и/или прикладная логика вынесена в дополнительные потоки.


> иначе смысл городить огород, легче все на синхронных оставить


Решать тебе.


 
SpellCaster   (2008-04-14 16:46) [41]

То есть "правильный курс" - это дополнительные потоки? И в одном потоке никак?


 
Сергей М. ©   (2008-04-14 16:58) [42]

В одном потоке твои клиенты будут обслуживаться последовательно.
Если тебя это устраивает, делай в одном.

А потоки на то и придуманы, чтобы распараллелить длительные вычислительные алгоритмы, а не выполнять их последовательно.


 
SpellCaster   (2008-04-16 10:39) [43]

Да меня в принципе устраивает и последовательно, проблема-то в том, что за этим обслуживанием теряется возможность пользовательского интерфейса - окошко там переждвинуть, кнопку нажать.


 
Сергей М. ©   (2008-04-16 11:27) [44]

Если теряется, значит не устраивает.



Страницы: 1 2 вся ветка

Текущий архив: 2009.10.18;
Скачать: CL | DM;

Наверх




Память: 0.62 MB
Время: 0.011 c
2-1250445565
alvonen
2009-08-16 21:59
2009.10.18
TOP_MOST окно перекрывается другими


3-1228320501
Tix
2008-12-03 19:08
2009.10.18
Многостраничная запись в QReport


1-1213341676
dmitry_12_08_74
2008-06-13 11:21
2009.10.18
THotKey нормально не работаетс горячими клавишами типа Win...


15-1250312595
Kostya
2009-08-15 09:03
2009.10.18
Параметры запуска программы


15-1250138266
Сергей Давыдов
2009-08-13 08:37
2009.10.18
Оплачю разработку фунции преобразования! (50$)