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

Вниз

Как правильно написать socks-сервер?   Найти похожие ветки 

 
zsergey   (2008-01-13 21:01) [0]

Привет господа! Есть такая задача: написать простой сокетный сервер, который должен обслуживать несколько подключений одновременно. Я только начал разбирать с этим вопросом, просьба, не ругать. Вот навоял небольшой работающий пример, правда без обработок ошибок:

procedure TForm1.startserverClick(Sender: TObject);
var S,AcceptedSock:TSocket;
   Addr:TSockAddr;
   Data:TWSAData;
   Len:Integer;
   buf : array[0..30] of byte;
begin
 WSAStartup($101,Data);
 S:=Socket(AF_Inet,Sock_Stream,0);
 Addr.sin_family:=PF_Inet;
 Addr.sin_port:=HToNS(5005);
 Addr.sin_addr.S_addr:=InAddr_Any;
 FillChar(Addr.Sin_Zero,SizeOf(Addr.Sin_Zero),0);
 Bind(S,Addr,SizeOf(TSockAddr));
 Listen(S,SoMaxConn);
 Len:=SizeOf(TSockAddr);
 AcceptedSock:=Accept(S,@Addr,@Len);
 Recv(AcceptedSock,buf, sizeof(buf),0);
// дальше обрабатываю данные
end;

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


 
DVM ©   (2008-01-13 21:06) [1]


> в какую сторону хотя бы смотреть...

в сторону либо потоков либо в сторону асинхронных сокетов на сообщениях.

Для блокирующих сокетов Accept крутить в цикле (в идеале цикл в отдельном потоке), при подключении клиента создавать новый поток - передавать туда сокет и работать с конкретным клиентом в этом потоке.


 
DVM ©   (2008-01-13 21:08) [2]

Вот как то так (в коде могут быть ошибки!):


program server;

{$APPTYPE CONSOLE}
uses
 SysUtils,
 Winsock,
 Windows;

type

 ThreadData = record
   Socket: TSocket;
 end;
 PThreadData = ^ThreadData;

var
 WSAData: TWSAData;
 ListenSocket, ClientSocket: TSocket;
 Info: PThreadData;
 SockAddr: TSockAddr;
 ThreadId: THandle;
 hClientThread: Thandle;

const
 Port = word(25);

//------------------------------------------------------------------------------

procedure SocketThread(Info: PThreadData);
var
 SockName: TSockAddr;
 NameLen, OptLen: Integer;
 Buf: PChar;
 RecvSize: integer;
 s: TSocket;
 BuffSize: integer;
begin
 s := Info^.Socket;
 try
   NameLen := SizeOf(TSockAddr);
   if GetPeerName(s, SockName, NameLen) <> 0 then exit;
   Writeln(format("Client accepted, remote address [%s].", [inet_ntoa(SockName.sin_addr)]));
   OptLen := SizeOf(BuffSize);
   if GetSockOpt(s, SOL_SOCKET, SO_RCVBUF, pointer(@BuffSize), OptLen) <> 0 then exit;
   writeln(format("Receive buffer size [%d]", [BuffSize]));
   GetMem(Buf, BuffSize);
   try
     repeat
       RecvSize := recv(s, Buf^, BuffSize, 0);
       sleep(5);
       if RecvSize > 0 then
         writeln(format("Received from client: %s", [strpas(Buf)]));
     until RecvSize <= 0;
   finally
     FreeMem(Buf, BuffSize);
   end;
   Writeln(format("Client disconnected, remote address [%s].",[inet_ntoa(SockName.sin_addr)]));
 finally
   CloseSocket(s);
   Dispose(Info);
 end;  
end;

//------------------------------------------------------------------------------

begin
 Writeln("WSA Initialize...");
 if WSAStartup($101, WSAData) <> 0 then exit;
 try
   Writeln(format("Creating socket on port [%d].", [Port]));
   ListenSocket := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
   if ListenSocket = INVALID_SOCKET then exit;
   try
     FillChar(SockAddr, SizeOf(TSockAddr), 0);
     SockAddr.sin_family := AF_INET;
     SockAddr.sin_port := htons(Port);
     SockAddr.sin_addr.S_addr := INADDR_ANY;
     Writeln("Binding socket...");
     if Bind(ListenSocket, SockAddr, SizeOf(TSockAddr)) <> 0 then exit;
     if listen(ListenSocket, SOMAXCONN) <> 0 then exit;
     Writeln("Socket status: listening...");
     repeat
       Writeln("wait");
       ClientSocket := accept(ListenSocket, nil, nil);
       if ClientSocket <> INVALID_SOCKET then
         begin
           New(Info);
           Info^.Socket := ClientSocket;
           hClientThread := BeginThread(nil, 0, @SocketThread, Info, 0, ThreadId);
           if hClientThread <> 0 then CloseHandle(hClientThread);
         end;  
     until false;
   finally
     CloseSocket(ListenSocket);
   end;
 finally
   WSACleanup;
 end;

//------------------------------------------------------------------------------

end.


 
zsergey   (2008-01-13 21:25) [3]

Cпасибо за пример, буду разбираться.


 
zSergey   (2008-01-14 15:35) [4]

теперь задача немного усложняется: сам сервер я написал в виде библиотеки (*.dll), увы такое требование. Сервер будет запускаться сторонним приложением. Здесь проблем нет. Вопрос в том, как сервер должен передавать данные в вызвавшую его  программу?. Поясняю задачу: сам сервер пишется для связки с 1с v.8. Т.к. у 1с нет стандартных средств для работы с сокетами, приходиться прибегнуть с помошью внешних компонент (dll), где собственно будет находится сам функционал сервера. Далее, приняв данные, сервер должен в реальном времени передать их приложению-хозяину, приложение в это время не находиться в режиме ожидания, а продолжает выполнять весь свой функционал. Я понимаю, что это форум отношения к 1с не имеет, потому я пока рассматриваю вариант  взаимодействия программы и сервера на delphi. Буду благодарен любым идеям.


 
Сергей М. ©   (2008-01-14 16:16) [5]


> zSergey   (14.01.08 15:35) [4]


Какого хрена ты вообще взялся за WinsockAPI ?
Тебе что, компонентов готовых мало ?)


 
zSergey   (2008-01-14 16:31) [6]

> Сергей М.
ну зачем жет так резко, уважаемый? Ничего плохо в том, что я хочу разобрать с WinsockAPI нет. Да и сама библиотечка будет намного легче весить без компонент. Кстати, а как запихнуть компонент в Dll ?  :)Да и вопрос ведь в том, какой должен быть механизм обмена между 2 приложениями, пусть даже с использованием компонент?


 
zSergey   (2008-01-14 16:40) [7]


> Тебе что, компонентов готовых мало ?)

Кстати, а о каких компонентах речь идет?


 
DVM ©   (2008-01-14 22:59) [8]


> теперь задача немного усложняется: сам сервер я написал
> в виде библиотеки (*.dll), увы такое требование. Сервер
> будет запускаться сторонним приложением. Здесь проблем нет.
>  Вопрос в том, как сервер должен передавать данные в вызвавшую
> его  программу?.

А разве сервер запущенный из dll некой сторонней программой не находится в адресном пространстве процесса этой программы? Это передача не между процессами, а между частями одного и того же приложения (в данном случае между потоками).

Самый простой и надежный способ передачи через сообщения Windows.


 
Сергей М. ©   (2008-01-15 08:21) [9]


> о каких компонентах речь идет?


Мало ли таковых существует !

TTCPServer, TServerSocket у тебя прямо под рукой имеются.


> как запихнуть компонент в Dll


Точно также как в exe


 
ketmar ©   (2008-01-15 09:13) [10]

>[6] zSergey (14.01.08 16:31)
>какой должен быть механизм обмена между 2 приложениями
рабочий он должен быть. далее конкретизируется под конкретную задачу.

вот с той же адынцэ — это тебе ActiveX писать. где, кстати, пофигу, компоненты али не компоненты — всё равно будет жирное (если ты, конечно, не хардкорщик, желающий ваять ActiveX «с нуля»).


 
zSergey   (2008-01-15 13:33) [11]


> вот с той же адынцэ — это тебе ActiveX писать. где, кстати,
>  пофигу, компоненты али не компоненты — всё равно будет
> жирное (если ты, конечно, не хардкорщик, желающий ваять
> ActiveX «с нуля»).

Тоже хорошая идея, навоять собственный ActiveX контрол, чтобы было проще, на основе того же TServerSocket. Как это сделать? Полез я  визард "ActiveX control wizard", там нет родителя для TServerSocket, как я понял, так можно замутить только для визуальный компонент. возможно ли в данном случае сделать свой ActiveX на основе TSocketServer, если да, то как? (просьба не смеяться и не злиться) :)


 
Сергей М. ©   (2008-01-15 13:39) [12]

Сначала поясни, зачем тебе понадобился именно AX-контрол, да еще и невизуальный ?


 
zSergey   (2008-01-15 13:48) [13]


> Сначала поясни, зачем тебе понадобился именно AX-контрол,
>  да еще и невизуальный ?

потому что
> у 1с нет стандартных средств для работы с сокетами, приходиться
> прибегнуть с помошью внешних компонент (dll),

или еще как вариант - ActiveX :)


 
Сергей М. ©   (2008-01-15 13:51) [14]


> zSergey   (15.01.08 13:48) [13]


Зато у одноэсины есть механизм использования надстроек (1C Add-ins)
И она не имеет ни малейшего отношения к AX.


 
zSergey   (2008-01-15 13:56) [15]


> Зато у одноэсины есть механизм использования надстроек (1C
> Add-ins)
> И она не имеет ни малейшего отношения к AX.

это я понимаю! Просто мне интересно, а возжно и проще решить энту задачу через АХ. Итак, не посчитайте меня назойливым, как написать АХ на основе TSockerServer ?


 
zSergey   (2008-01-15 14:02) [16]

> Сергей М. ©
Напиши сам, делов-то на 10 минут. - Здесь вы имели в виду с нуля написать?


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


> мне интересно, а возжно и проще решить энту задачу через АХ


Ходить в гости к соседу по двору через Китай тоже можно.


> как написать АХ на основе TSockerServer


Никак.


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


> с нуля написать?


Почему бы и нет ?


 
zSergey   (2008-01-15 14:11) [19]


> Почему бы и нет ?

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


 
Сергей М. ©   (2008-01-15 14:18) [20]


> где можно про это почитать


Дык в справке стандартной и почитай


 
DVM ©   (2008-01-15 17:33) [21]


> zSergey

Delphi автоматически позволяет создавать ActiveX только на основе потомков TWinControl кажется.


 
zsergey   (2008-01-16 19:53) [22]

Привет знатокам!
Написал сокет-сервер как сом-объект на WinApi (*.dll). Все хорошо, сокет открывается, слушается, данные из него читаются, но само приложение, использующее библиотеку, тормозит и дальше программа не может выполнить остальные свои функции, что не есть хорошо. Используются не блокирующие сокеты. Выкладываю весь исходник:

unit Utestlib;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
 ComObj, ActiveX, testlib_TLB, StdVcl, Dialogs, WinSock, SysUtils, Windows;

type
 TApp = class(TAutoObject, IApp)
 protected
   procedure OpenSocket; safecall;
end;

type
ThreadData = record
  Socket: TSocket;
end;
PThreadData = ^ThreadData;

var
WSAData: TWSAData;
ListenSocket, ClientSocket: TSocket;
Info: PThreadData;
SockAddr: TSockAddr;
ThreadId: THandle;
hClientThread: Thandle;

Arg : u_long;
s: TSocket;
const
Port = word(5555);

implementation

uses ComServ;
//////////////////////////////////////////
procedure SocketThread(Info: PThreadData);
var
SockName: TSockAddr;
NameLen, OptLen: Integer;
buf : array[0..35] of byte;
RecvSize: integer;

BuffSize,k: integer;
str : string;
error : integer;

begin
s := Info^.Socket;
try
  NameLen := SizeOf(TSockAddr);
  if GetPeerName(s, SockName, NameLen) <> 0 then exit;
  ShowMessage("Client accepted");
  OptLen := SizeOf(BuffSize);
  if GetSockOpt(s, SOL_SOCKET, SO_RCVBUF, pointer(@BuffSize), OptLen) <> 0 then exit;
  try
    repeat
      RecvSize := recv(s, Buf[0], Length(Buf), 0);
      if  RecvSize = SOCKET_ERROR then
         Begin
           error := WSAGetLastError();
           if (error<>WSAEWOULDBLOCK) then
               continue
           else RecvSize := 1
         end
      else
      sleep(5);
      if RecvSize > 1 then
         ShowMessage("Received from client ...");
    until RecvSize <= 0;
  finally
end;
  ShowMessage("Client disconnected ...");
finally
  CloseSocket(s);
  Dispose(Info);
end;
end;
//////////////////////////////////////////

procedure TApp.OpenSocket;
begin
ShowMessage("WSA Initialize ...");
if WSAStartup($101, WSAData) <> 0 then exit;
try
  ListenSocket := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
  // non bloking
   Arg:=1;
   IOCtlSocket(ListenSocket,FIONBIO,Arg);
  //
  if ListenSocket = INVALID_SOCKET then exit;
  try
    FillChar(SockAddr, SizeOf(TSockAddr), 0);
    SockAddr.sin_family := AF_INET;
    SockAddr.sin_port := htons(Port);
    SockAddr.sin_addr.S_addr := INADDR_ANY;
    if Bind(ListenSocket, SockAddr, SizeOf(TSockAddr)) <> 0 then exit;
      if listen(ListenSocket, SOMAXCONN) <> 0 then exit;
        repeat
           
          ClientSocket := accept(ListenSocket, nil, nil);
          if ClientSocket <> INVALID_SOCKET then
            begin
              New(Info);
              Info^.Socket := ClientSocket;
              hClientThread := BeginThread(nil, 0, @SocketThread, Info, 0, ThreadId);
              if hClientThread <> 0 then CloseHandle(hClientThread);
            end;
        until false;
  finally
  Arg:=0;
  IOCtlSocket(ListenSocket,FIONBIO,Arg);
  CloseSocket(ListenSocket);
  end;
finally
  WSACleanup;
end;
end;

initialization
 TAutoObjectFactory.Create(ComServer, TApp, Class_App,
   ciMultiInstance, tmApartment);
end.


что я не так делал?


 
Slym ©   (2008-01-17 04:28) [23]

zsergey   (16.01.08 19:53) [22]
не блокирующие сокеты

1. и нафега BeginThreadв твоем не блокирующем сокете?
2. чтобы он стал полноценно неблокирующим нужно вызвать Select функцию и в нее передать дескриптор окна в которое будут приходить сообщения об событиях сокета
3. неблокирующий у тебя только слушающий сокет (listen), остальные (accept) блокирующие т.к. становится им неблокирующими ты им не сказал


 
Slym ©   (2008-01-17 04:32) [24]

ты понацеплял ComObj, ActiveX, testlib_TLB, StdVcl, Dialogs и тебе жалко 10кб на нормальный сокет? ты еще сом-объект руками сваяй...
Slym ©   (17.01.08 4:28) [23]
4. в com объекте с потоками огребеш не зная предметной области


 
Сергей М. ©   (2008-01-17 10:11) [25]


> ShowMessage


Что еще за VCL-выкрутасы в дополнительном потоке ?


 
Сергей М. ©   (2008-01-17 10:21) [26]


> 3. неблокирующий у тебя только слушающий сокет (listen),
>  остальные (accept) блокирующие т.к. становится им неблокирующими
> ты им не сказал
>


Угу.
Но при этом в слушающем потоке, работающем с неблокирующим ListenSocket, почему-то нет ни намека на WSAEWOULDBLOCK, зато в клиентских потоках, работающих с блокирующими ClientSocket, WSAEWOULDBLOCK с какого-то перепугу фигурирует.


 
DVM ©   (2008-01-17 10:34) [27]


> zsergey   (16.01.08 19:53) [22]

Лучше бы ты на асинхронных неблокирующих сокетах сделал. На сообщениях. Ей богу проще для твоего случая. И доп потоки не нужны.


 
Сергей М. ©   (2008-01-17 10:42) [28]


> if GetSockOpt(s, SOL_SOCKET, SO_RCVBUF, pointer(@BuffSize),
>  OptLen) <> 0 then exit;


Совершенно непонятно, в каких случаях GetSockOpt может вернуть ошибку и для чего вообще запрашивается размер буфера приема, если этот полученный размер далее по ходу дела нигде не используется


 
Сергей М. ©   (2008-01-17 10:56) [29]


> Slym ©   (17.01.08 04:28) [23]


Впрочем ни ты ни я не правы  - ClientSocket у автора работает именно в неблок.режиме.

Цитата из справки по accept():

newly created socket has the same properties as s including asynchronous events registered with WSAAsyncSelect or with WSAEventSelect


 
Сергей М. ©   (2008-01-17 10:59) [30]

Но этот факт не меняет сути - логика repeat-цикла в кл.потоке с учетом неблок.режима не верна в принципе


 
Slym ©   (2008-01-17 11:08) [31]

Сергей М. ©   (17.01.08 10:56) [29]
Select

я то как раз прав...
ниодного select я не увидел...
если бы и увидел то было выглядело бы так:
WSAAsyncSelect(ListenSocket,hWnd,FD_ACCEPT,WM_USER);
этого для слушателя достаточно, а для ацепнутого сокета никаких Эвентов не прописано...


 
zSergey   (2008-01-17 11:38) [32]


> 2. чтобы он стал полноценно неблокирующим нужно вызвать
> Select функцию


> ниодного select я не увидел...
> если бы и увидел то было выглядело бы так:
> WSAAsyncSelect(ListenSocket,hWnd,FD_ACCEPT,WM_USER);

Хорошо, вот переделанный сервер с селектом (не СОМ)

program Project1;
uses
 SysUtils,
 WinSock;

{$APPTYPE CONSOLE}
var Sockets:array of TSocket;
   Addr:TSockAddr;
   Data:TWSAData;
   Len,I,J:Integer;
   FDSet:TFDSet;
   arrBuf : array [0..35] of byte;
   Arg:u_long;
   tv : Ttimeval;

begin
 WSAStartup($101,Data);
 SetLength(Sockets,1);
 Sockets[0]:=Socket(AF_Inet,Sock_Stream,0);
 Addr.sin_family:=AF_Inet;
 Addr.sin_port:=HToNS(21001);
 Addr.sin_addr.S_addr:=InAddr_Any;
 FillChar(Addr.Sin_Zero,SizeOf(Addr.Sin_Zero),0);
 Bind(Sockets[0],Addr,SizeOf(TSockAddr));
 Listen(Sockets[0],SoMaxConn);
 while True do
  begin
   FD_Zero(FDSet);
   for I:=0 to High(Sockets) do
   Arg:=1;
   IOCtlSocket(Sockets[I],FIONBIO,Arg);
   FD_Set(Sockets[I],FDSet);

   tv.tv_sec := 5;
   tv.tv_usec := 0;

   Select(0,@FDSet,nil,nil,@tv);

   if FDSet.fd_count=0 then Continue;
   sleep(100);
   writeln("wait");
   
   I:=1;
   while I<=High(Sockets) do
    begin
     if FD_IsSet(Sockets[I],FDSet) then
      if Recv(Sockets[I],arrBuf, sizeOf(arrBuf),0) <=0 then
       begin
       
        writeln("disconnect, socket close");
        CloseSocket(Sockets[I]);
        for J:=I to High(Sockets)-1 do
         Sockets[J]:=Sockets[J+1];
        Dec(I);
        SetLength(Sockets,Length(Sockets)-1)
       end
      else
       begin
        writeln("Receive data");

       end;
     Inc(I)
    end;
   

   if FD_IsSet(Sockets[0],FDSet) then
    begin
   
     writeln("new client connected");
     SetLength(Sockets,Length(Sockets)+1);
     Len:=SizeOf(TSockAddr);
     Sockets[High(Sockets)]:=Accept(Sockets[0],@Addr,@Len)
    end
  end;
end.


Но почему-то при тестировании select не возвращает сокеты для чтения, чего не должно быть. Где ошибка?


 
Сергей М. ©   (2008-01-17 11:41) [33]


> Slym ©   (17.01.08 11:08) [31]


> ниодного select я не увидел


Причем здесь select ?
Ну нет его и нет - и фиг с ним, я о другом сейчас.

Я о твоей ремарке

> остальные (accept) блокирующие т.к. становится им неблокирующими
> ты им не сказал


и о своей


> в клиентских потоках, работающих с блокирующими ClientSocket


А это неверно, поскольку слушающий сокет перед вызовом accept() переведен в неблок.режим (т.е. для него явно установлено опция-"свойство" nonblocking), что, с учетом цитаты в [29], при успешном вызове accept()  порождает новое неблокирующее гнездо.


 
Сергей М. ©   (2008-01-17 11:45) [34]


> select не возвращает сокеты для чтения


И не возвратит.

Куда у тебя accept()-то пропал ? Без него select как мертвому припарка)


 
zSergey   (2008-01-17 14:43) [35]


> И не возвратит.

нашел в чем ошибка:
FD_Set(Sockets[I],FDSet); в цикле не в операторских скобках;
для чего Arg:=1;
  IOCtlSocket(Sockets[I],FIONBIO,Arg);
перенес под Accept(); теперь заработало, НО само приложение в "замороженном" состоянии, будь там блокирующие сокеты или не блокирующие, будут там нити или нет. Я почти все перепробовал. Повторюсь, сервер должен не только обрабатывать подключения, но и одновременно сохранять другие свои ф-ции, короче, чтобы все контролы на форме были доступными. Вроде понятно объяснил.
> Лучше бы ты на асинхронных неблокирующих сокетах сделал.
>  На сообщениях. Ей богу проще для твоего случая. И доп потоки
> не нужны.

Господа, дайте пож-ста простой примерчик, на который мне можно было опереться в данном случае.


 
Сергей М. ©   (2008-01-17 15:13) [36]

ты все-таки определись, тебя на WSAPI заклинило окончательно и бесповоротно или только блажь ?


 
zSergey   (2008-01-17 15:32) [37]


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

ага мне вот делать нехер, низнаю чем заняться, WSAPI или еще чем. Уважаемый Сергей М, напоминаю, для чего мне все это нужно:

Поясняю задачу: сам сервер пишется для связки с 1с v.8. Т.к. у 1с нет стандартных средств для работы с сокетами, приходиться прибегнуть с помошью внешних компонент (dll), где собственно будет находится сам функционал сервера. Далее, приняв данные, сервер должен в реальном времени передать их приложению-хозяину, приложение в это время не находиться в режиме ожидания, а продолжает выполнять весь свой функционал. Я понимаю, что это форум отношения к 1с не имеет, потому я пока рассматриваю вариант  взаимодействия программы и сервера на delphi. Буду благодарен любым идеям.

ни внешних компонент, ни  бесплатных ActiveX контролов у меня нет, чтоб решить эту проблему, потому я вынужден ковырять wsapi ибо другого выхода я не вижу :)


 
Сергей М. ©   (2008-01-17 15:34) [38]


> мне вот делать нехер, низнаю чем заняться


Я вот тоже так думаю - тебе нечем более заняться)

ЧЕМ не устроили TServerSocket, TTCPServer, ICS-компоненты ?
Вразумительно ты так и не объяснил)


 
zSergey   (2008-01-17 15:41) [39]


> ЧЕМ не устроили TServerSocket, TTCPServer, ICS-компоненты
> ?
> Вразумительно ты так и не объяснил)

я не знаю как эти компоненты засунуть в dll :)


 
Сергей М. ©   (2008-01-17 15:47) [40]

А в ехе знаешь ?)
рассказывай) ..



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

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

Наверх




Память: 0.6 MB
Время: 0.011 c
2-1280922687
mylasthit
2010-08-04 15:51
2010.10.31
Управление скролом в RichEdit


2-1281406808
03111978
2010-08-10 06:20
2010.10.31
Помогите посчитать и написать запрос ......


2-1281117985
Игорь
2010-08-06 22:06
2010.10.31
Загрузка драйвера в Windows 7 x64


2-1280974168
Soi
2010-08-05 06:09
2010.10.31
Цикл.Сохранение в цикле предыдущего значения


2-1281113383
nik9632
2010-08-06 20:49
2010.10.31
Ищу удачный вариант участка кода программы