Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Сети";
Текущий архив: 2003.01.13;
Скачать: [xml.tar.bz2];

Вниз

Передача данных при помощи сокетов.   Найти похожие ветки 

 
V_Pavel   (2002-11-01 12:27) [0]

Мне нужно организовать обмен данными между клиентом и сервером при помощи сокетов. Как и с помощью чего это лучше сделать чтоб работало надежно? Предполагаемый размер пакетов колеблется от 10 байт до 500 килобайт. Данные нужно передавать как с клиента на сервер так и обратно. Если у кого есть примеры или ссылки на примеры поделитесь буду рад. Я побывал это делать при помощи стандартных компанент работает но не устойчива, а именно если после большого пакета размером примерно 50 кб. передаю пакет размером 10 байт он кудато теряется.


 
Digitman   (2002-11-01 12:55) [1]


> побывал это делать при помощи стандартных компанент работает
> но не устойчива


Некорректен именно твой код, а не внутренняя работа этих компонентов


 
Song   (2002-11-01 13:03) [2]

http://delphi.mastak.ru/cgi-bin/forum.pl?look=1&id=1036137108&n=4


 
V_Pavel   (2002-11-01 13:31) [3]

да я согласен что мой код некорректен.
Вот посмотрите и скажите что не так. Данный код при приеме склеивает пакет из нескольких кусков в один и при наличии в начале первого куска информации о длине пакета и проверяет куски на наличие склеенных пакетов.
Если после передачи большого пакета сервером проходит немного времени перед передачей маленького пакета то все нормально а вот если сразу передается маленький пакет по он теряется в 3-5 случаях из 10.
procedure TFmain.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var
count :Integer;
buffer: Array [0..MAX_BUF_SIZE] of Char;
p:tstringstream;
i,ln:integer;
str:string;

begin
i:=0;
p:=tstringstream.Create("");
Socket.Lock;
repeat
count:= Socket.ReceiveBuf(buffer,SizeOf(buffer));
if count > 0 then
begin
p.WriteBuffer(buffer,count);
if i=0 then ln:=strtoint(copy(p.DataString,1,10)));
if p.Size>=ln then
repeat
str:=p.DataString;
ln:=strtoint(copy(str,1,10));
decodePacket(copy(str,1,ln+10)); {обработчик пакета}
p.free;
delete(str,1,ln+10);
p:=tstringstream.Create(str);
i:=0;
until (p.Size<10)
else inc(i);
end;
Socket.Unlock;
until (count <= 0);
dePacket(p.DataString);
p.Free;
end;


 
Digitman   (2002-11-01 13:48) [4]

ПРиведи еще код передатчика


 
V_Pavel   (2002-11-03 17:07) [5]

Вот передатчик.
procedure sendpacket(str:string;sh:integer;Socket: TServerWinSocket);
var i,ind:integer;
head:string;
begin
if sh=0 then ind:=0 else
for i:=0 to socket.ActiveConnections-1 do
if socket.Connections[i].Handle=sh then ind:=i;
head:=padl(inttostr(length(str)+10),10,"0");
socket.Connections[ind].SendText(head+str);
end;

Если слать одинаковые пакеты то они не теряются а вот если слать сперва большой пакет а потом маленький то маленький не доходит. Я пробывал ставить задержку между посылками хотябы на 10 ms. тогда вроде рабтает.


 
Digitman   (2002-11-04 08:26) [6]

>V_Pavel

Где анализ результата, возвращаемого ф-цией
socket.Connections[ind].SendText(head+str) ?

Где обработка события OnClientWrite ?


 
V_Pavel   (2002-11-04 09:16) [7]

Digitman © , что ты посоветуешь сделать. Я с сетями работаю не так давно и еще не сильно разбираюсь. Подскажи хоябы идею по обработке результата, возвращаемого ф-цией socket.Connections[ind].SendText(head+str) и что нужно написать в обработчике события OnClientWrite.


 
Digitman   (2002-11-04 10:29) [8]

Я посоветую для начала внимательно почитать хэлп на метод TCustomWinSocket.SendText() и на событие TCustomWinSocket.OnWrite(). Результат вызова метода SendText() (как и прочих иных send-методов) говорит о многом, в зависимости от результата дальнейшие действия могут быть тесно связаны с необходимостью выполнения повторной передачи тех же данных в момент возникновения события OnWrite().

Imho, самый удобный, наглядный и наименее проблематичный способ передачи данных в асинхр.неблок.режиме - это передача с использованием поточного класса TStream.

Примерный вариант :


//тело обработчика OnClientConnect()
Socket.Data := nil;

//тело обработчика OnClientDisconnect()
if Assigned(Socket.Data) then
TMemoryStream(Socket.Data).Free;

//в момент необходимости передачи данных клиенту
var Stream: TMemoryStream; //лок.переменная
var CurStreamReadPos: Integer; //лок.переменная
...
Stream := Socket.Data;
if not Assigned(Stream) then
Stream := TMemoryStream.Create;
CurStreamReadPos := Stream.Position;
Stream.Write(...то что нужно передать...);
Stream.Position := CurStreamReadPos;
if not Assigned(Socket.Data) and not Socket.Connections[index].SendStream(Stream) then
begin
Socket.Data := Stream;
Процедура_обработчика_OnClientWrite(ServerSocket, Socket);
end

//тело обработчика OnClientWrite()
var Stream: TMemoryStream; //лок.переменная
...
Stream := TMemoryStream(Socket.Data);
if Assigned(Stream) and Socket.SendStream(Stream) then
Socket.Data := nil;




 
Digitman   (2002-11-04 10:36) [9]

вернее - вот так :


//в момент необходимости передачи данных клиенту
var Stream: TMemoryStream; //лок.переменная
var CurStreamReadPos: Integer; //лок.переменная
...
Stream := Socket.Data;
if not Assigned(Stream) then
Stream := TMemoryStream.Create;
CurStreamReadPos := Stream.Position; //тек.позиция считывания из потока
Stream.Position := Stream. Size; //в конец потока
Stream.Write(...то что нужно передать...); //в хвост потока - новые данные
Stream.Position := CurStreamReadPos; //восстановление тек.позиции
if not Assigned(Socket.Data) and not Socket.Connections[index].SendStream(Stream) then
begin
Socket.Data := Stream;
Процедура_обработчика_OnClientWrite(ServerSocket, Socket);
end


 
Digitman   (2002-11-04 10:43) [10]

и вот еще поправка :

//тело обработчика OnClientDisconnect()
if Assigned(Socket.Data) then
try
TMemoryStream(Socket.Data).Free;
except
end



 
V_Pavel   (2002-11-04 12:23) [11]

Спасибо за пример!
У меня по нему 2 вопроса.
1)
//в момент необходимости передачи данных клиенту
var Stream: TMemoryStream; //лок.переменная
var CurStreamReadPos: Integer; //лок.переменная
...
Stream := Socket.Data;
а если тут сделать так
Stream := Socket.Connections[index].Data;
я думаю будет правильнее или я ощибаюсь.
2)И мне не совсем понятен код

if not Assigned(Socket.Data) and not Socket.Connections[index].SendStream(Stream) then
begin
Socket.Data := Stream;
Процедура_обработчика_OnClientWrite(ServerSocket, Socket);
end






 
Digitman   (2002-11-04 12:32) [12]


> а если тут сделать так
> Stream := Socket.Connections[index].Data;
> я думаю будет правильнее или я ощибаюсь.


Да, так будет правильно.


> И мне не совсем понятен код


Что тебе непонятно ? Конкретно ?


 
V_Pavel   (2002-11-04 13:20) [13]

Может я чтото не понимаю но объясни мне это место
if not Assigned(Socket.Data) and not Socket.Connections[index].SendStream(Stream) then


 
V_Pavel   (2002-11-04 13:30) [14]

>if not Assigned(Stream) then
> Stream := TMemoryStream.Create;
Тут мы создаем поток а где мы его убиваем?


 
Digitman   (2002-11-04 13:46) [15]

Вот так корректней будет.


//в момент необходимости передачи данных клиенту
var Stream: TMemoryStream; //лок.переменная
var Target: TCustomWinSocket; //лок.переменная
var CurStreamReadPos: Integer; //лок.переменная
...
Target := Socket.Connections[index]; // целевое гнездо
Stream := Target.Data;
if not Assigned(Stream) then
Stream := TMemoryStream.Create;
CurStreamReadPos := Stream.Position; //тек.позиция считывания из потока
Stream.Position := Stream.Size; //в конец потока
Stream.Write(...то что нужно передать...); //в хвост потока - новые данные
Stream.Position := CurStreamReadPos; //восстановление тек.позиции
if not Assigned(Target.Data) then // если очередь передачи пуста
begin
Socket.Data := Stream; // ставим поток в очередь
Процедура_обработчика_OnClientWrite(ServerSocket, Target); // инициируем передачу очереди
end



Теперь понятно ?


 
Digitman   (2002-11-04 13:48) [16]


> Тут мы создаем поток а где мы его убиваем?


Хэлп-то читать будем когда-нибудь ?
Читай внимательно описание SendStream() :

Note: The Stream passed as a parameter to SendStream becomes “owned” by the windows socket object. The Windows socket object frees the stream when it is finished with it. Do not attempt to free the stream after it has been passed as a parameter.


 
V_Pavel   (2002-11-04 14:11) [17]

Спасибо за разъяснения. Теперь все понятно. Вечером попробую запихнуть это все в свой проект. А там посмотрим что получиться.
Надеюсь будет работать как надо. Еще раз спасибо.


 
V_Pavel   (2002-11-05 06:13) [18]

Передатчик вроде работает нормально, но теперь придется переписать приемник. Digitman ©, не подскажешь как это правильнее сделать.


 
Digitman   (2002-11-05 08:25) [19]

Приведи спецификацию своего собственного протокола инф.обмена, тогда и разговор будет


 
V_Pavel   (2002-11-05 11:48) [20]

Нужно передать от сервера клиенту абстрактный поток данных (файл, текст, картинка неважно). В начале потока в первых 10 байтах передавать его длину. Клиент считывает длину и все последующие блоки склеивает в один пока длина получившегося блока не равна длине передаваемых данных. Когда клиент получил этот поток он решает что с ним делать.


 
Digitman   (2002-11-05 14:02) [21]

Куда тебе столько - аж 10 байт ?)
10-ю байтами можно описать число до 1208925819614629174706175 включительно ) ... Астрономическое почти число !)

Практически же для любых передаваемых объектов вполне достаточно и 4-х байт



type
TTransportState = (tsDataSizeReceiving, tsDataBlockReceiving);

var
TransportState: TTransportState = tsDataSizeReceiving;
TotalDataSizeBytesRead: DWord = 0;
TotalDataBlockBytesRead: DWord = 0;
TotalBlockSize: DWord = 0;
DataBlock: Pointer;


function DoReadDataSize(Socket: TCustomWinSocket): Boolean;
var
BytesRemains: DWord,
ActualBytesRead : Integer;
buf: Pointer;
begin
BytesRemains := SizeOf(DWord) - TotalDataSizeBytesRead;
if BytesRemains > 0 then
begin
buf := Pointer(DWord(@TotalBlockSize) + TotalDataSizeBytesRead);
ActualBytesRead := Socket.ReceiveBuf(buf^, BytesRemains);
if ActualBytesRead <> - 1 then
Inc(TotalDataSizeBytesRead, ActualBytesRead);
end;
Result := TotalDataSizeBytesRead = SizeOf(DWord);
end;

function DoReadDataBlock(Socket: TCustomWinSocket): Boolean;
var
BytesRemains: DWord,
ActualBytesRead : Integer;
buf: Pointer;
begin
if not Assigned(DataBlock) then
GetMem(DataBlock, TotalBlockSize);
try
BytesRemains := TotalBlockSize - TotalDataBlockBytesRead;
if BytesRemains > 0 then
begin
buf := Pointer(DWord(DataBlock) + TotalDataBlockBytesRead);
ActualBytesRead := Socket.ReceiveBuf(buf^, Min(BytesRemains, 4096));
if ActualBytesRead <> - 1 then
Inc(TotalDataBlockBytesRead, ActualBytesRead);
end;
Result := TotalDataBlockBytesRead = TotalBlockSize;
except
FreeMem(DataBlock);
DataBlock := nil;
raise;
end;
end;

procedure DoProcessDataBlock(DataBlock, TotalBlockSize);
begin
try
// ... обработка блока ...
finally
FreeMem(DataBlock);
DataBlock := nil;
end;
end;

procedure TFmain.ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
var
case TransportState of
tsDataSizeReceiving:
if DoReadDataSize(Socket) then
begin
TotalDataBlockBytesRead := 0;
TransportState := tsDataBlockReceiving;
end;
tsDataBlockReceiving:
if DoReadDataBlock(Socket) then
try
DoProcessDataBlock(DataBlock, TotalBlockSize);
TotalDataSizeBytesRead := 0;
TransportState := tsDataSizeReceiving;
finally
DataBlock := nil;
end;
end;
begin
end;



 
daan_m   (2002-11-06 11:39) [22]

На сколько я знаю:1) Данные разбиваются на пакеты;2) Каждый пакет весом 8 кило3) Вместе с пакетами идут флаги один из них говорит, что пакет последний.4) Разработчики Delphi не сочли нужным анализировать эти флаги:(5) НО на API уровне анализ возможен.


 
Digitman   (2002-11-06 11:56) [23]

> 1) Данные разбиваются на пакеты;

На уровне IP-протокола - да

> 2) Каждый пакет весом 8 кило
Не пакет, а буфер очереди передачи. И - необязательно. Это значение в большинстве реализайий WinSock - по-умолчанию, но делать на это безусловную ставку некорректно.

> 3) Вместе с пакетами идут флаги

Все это на уровне IP-транспорта.

> 4) Разработчики Delphi
> не сочли нужным анализировать эти флаги

Это не их забота. Это - забота разработчиков кода, реализующего TDI и OSI

> (5) НО на API уровне анализ возможен.

На уровне WinsockAPI - нет. Это - высокоуровневая надстройка над низкоуровневыми транспортными сетевыми протоколами


 
V_Pavel   (2002-11-08 16:08) [24]

В финкции DoReadDataSize мне непонятно вот это место
if ActualBytesRead <> - 1 then
Inc(TotalDataSizeBytesRead, ActualBytesRead);
мне кажется должно быть так
if ActualBytesRead <> - 1 then
Inc(TotalDataSizeBytesRead, dword(buf^));


 
V_Pavel   (2002-11-08 17:55) [25]

Digitman © Твой пример приемника тоже не примнмает маленькие пакеты после большого.


 
V_Pavel   (2002-11-09 07:53) [26]

Дело тут не в приемнике а в передатчике который описал Digitman ©. Он после передачи длинного пакета подвисает последующие пакеты становятся в очередь но не передаются. Немогу понять в чем тут дело.


 
V_Pavel   (2002-11-10 06:25) [27]

После передачи большого пакета перестает реботать вот этот кусочек(выделенный жирным) кода в обработчике OnClientWrite()
if Assigned(Stream) and Socket.SendStream(Stream) then
Socket.Data := nil;
Вчем тут дело?



 
Digitman   (2002-11-10 08:13) [28]


> мне кажется должно быть так
> if ActualBytesRead <> - 1 then
> Inc(TotalDataSizeBytesRead, dword(buf^));


Это почему же ? Прокомментируй ход свей мысли.

> перестает реботать вот этот кусочек(выделенный жирным) кода
> в обработчике OnClientWrite()
> if Assigned(Stream) and Socket.SendStream(Stream) then
> Socket.Data := nil;


Что значит "перестает работать" ?


 
V_Pavel   (2002-11-10 12:54) [29]

да первое это я конечно глупость сморозил не до конца разобрался
а вот на счет Socket.SendStream(Stream) при посылке после большого пакета маленького выдает false.


 
Digitman   (2002-11-10 13:06) [30]

>>при посылке после большого пакета маленького выдает false

Ну и что ? False - всего лишь признак того, что полное содержимое потока не может в данный момент поставлено в очередь гнезда на передачу. Как только очередь освободится, возникнет очер.событие OnWrite(), очер.порция из потока будет считана из потока и поставлена в очередь, и , если поток не полностью вычерпан, снова результатом будет False ... и так до полного исчерпания потока... как только поток "кусками" будет полностью вычерпан, результатом будет True, а объект-поток автоматически уничтожен


 
Digitman   (2002-11-10 13:33) [31]

Вот что при этом происходит :

function TCustomWinSocket.SendStreamPiece: Boolean;
var
Buffer: array[0..4095] of Byte;
StartPos: Integer;
AmountInBuf: Integer;
AmountSent: Integer;
ErrorCode: Integer;

procedure DropStream;
begin
if FDropAfterSend then Disconnect(FSocket);
FDropAfterSend := False;
FSendStream.Free;
FSendStream := nil;
end;

begin
Lock;
try
Result := False;
if FSendStream <> nil then
begin
if (FSocket = INVALID_SOCKET) or (not FConnected) then exit;
while True do
begin
StartPos := FSendStream.Position;
AmountInBuf := FSendStream.Read(Buffer, SizeOf(Buffer));
if AmountInBuf > 0 then
begin
AmountSent := send(FSocket, Buffer, AmountInBuf, 0);
if AmountSent = SOCKET_ERROR then
begin
ErrorCode := WSAGetLastError;
if ErrorCode <> WSAEWOULDBLOCK then
begin
Error(Self, eeSend, ErrorCode);
Disconnect(FSocket);
DropStream;
if FAsyncStyles <> [] then Abort;
Break;
end else
begin
FSendStream.Position := StartPos;
Break;
end;
end else if AmountInBuf > AmountSent then
FSendStream.Position := StartPos + AmountSent
else if FSendStream.Position = FSendStream.Size then
begin
DropStream;
Break;
end;
end else
begin
DropStream;
Break;
end;
end;
Result := True;
end;
finally
Unlock;
end;
end;

function TCustomWinSocket.SendStream(AStream: TStream): Boolean;
begin
Result := False;
if FSendStream = nil then
begin
FSendStream := AStream;
Result := SendStreamPiece;
end;
end;


 
V_Pavel   (2002-11-10 17:17) [32]

Я это все прекрасно понимаю но из за того что SendStream выдает False данные копятся в буфере и не передаются. Я передаю пакет длиной 100 кил. Он передается но если следом передавать к примеру 2 пакета по 7 байт. Вот они остаются в очереди. Я так понимаю что при передаче большого пакета Socket блокируется пока его не передаст. Ток вот не удобнее ли здесь использовать SendBuff и передавать это все кусками.


 
Digitman   (2002-11-10 18:02) [33]

при чем здесь SendBuf() и иже с ними ?
Я тебе для чего привел фрагмент кода от Борланда ? Чтобы ты ВНИК в него и не делал "отфонарных" умозаключений ! Данные потока передаются именно поблочно ! Или , как ты там говоришь, - "кусками")...

Лучше скажи, сколько раз возникает событие OnWrite() при посылке, скажем, фрагмента в 100кб и следом за ним - 7б


 
V_Pavel   (2002-11-11 06:28) [34]

Событие OnClientWrite() возникает в данном случае 2 раза первый раз когда передаетя большой пакет второй раз когда маленький но прередача маленького не осушествляется.


 
Digitman   (2002-11-11 08:26) [35]


> первый раз когда передаетя большой пакет


Быть того не может ! Самое первое событие OnWrite(), ДАЖЕ ЕСЛИ НИЧЕГО НЕ ПЕРЕДАЕТСЯ, должно возникать как минимум однократно. Это - извещение Winsock о готовности буфера передачи к записи в него блока потенциально передаваемых данных.

Каков полный объем переданных и принятых данных ? Я не спрашиваю, сколько раз возникало событие OnRead() у приемника - именно СКОЛЬКО БАЙТ всего получил приемник в результате передачи в общей сложности, например, 100 + 7 = 107кб ?


 
V_Pavel   (2002-11-11 11:36) [36]

На счет OnWrite()ты прав. Приемник получает 100 кил. А вот если передавать маленький пакет после того как большой был принят приемником то все работает.


 
Digitman   (2002-11-11 12:35) [37]


//в момент необходимости передачи данных клиенту
var Stream: TMemoryStream; //лок.переменная
var CurStreamReadPos: Integer; //лок.переменная
...
Application.ProcessMessages // !!!!!!!!!!!!!!!!
Stream := Socket.Data;
if not Assigned(Stream) then
Stream := TMemoryStream.Create;
CurStreamReadPos := Stream.Position; //тек.позиция считывания из потока
Stream.Position := Stream.Size; //в конец потока
Stream.Write(...то что нужно передать...); //в хвост потока - новые данные
Stream.Position := CurStreamReadPos; //восстановление тек.позиции
if not Assigned(Socket.Data) and not Socket.Connections[index].SendStream(Stream) then
begin
Socket.Data := Stream;
Процедура_обработчика_OnClientWrite(ServerSocket, Socket);
end





 
V_Pavel   (2002-11-11 19:02) [38]

На LocalHost-е работает если разношу клиент и сервер по разным машинам ситуация повторяется.


 
Digitman   (2002-11-12 08:57) [39]

давай-ка еще раз вникни и посмотри, что не так, как ожидается


//тело обработчика OnClientWrite()
var Stream: TMemoryStream; //лок.переменная
...
Stream := TMemoryStream(Socket.Data);
// если ссылка на экземпляр исх.потока ассоциирована с гнездом
if Assigned(Stream)
// и в момент нижеследующего вызова ВСЕ содержимое потока
// успешно поставлено в очередь на передачу
and Socket.SendStream(Stream) then
// то экз-р исх.потока будет автоматически уничтожен
// в private-методе TCustomWinSocket.SendStreamPiece (DropStream)
// и ассоциация с экз-ром разорвана, чтобы известить внешний код
// о том, что экз-р больше не существует
Socket.Data := nil;


//в момент необходимости передачи данных клиенту
var Stream: TMemoryStream; //лок.переменная
var CurStreamReadPos: Integer; //лок.переменная
...
// первым делом даем возможность системе обработать тек.события, в т.ч. - OnWrite(), если оно возникнет в этот момент
//а если возникнет, то в его обработчике в случае успеха
// поток будет уничтожен, а ассоциация разорвана
Application.ProcessMessages;

// считываем состояние ассоциации
// изначально в OnConnect() она сброшена в nil
Stream := Socket.Data;

// если на этот момент еще ничто не передавалось
// или ранее осуществленные попытки передачи завершились успешно
// и предудущий экз-р потока уже не существует (уничтожен в DropStream)
if not Assigned(Stream) then

// создадим новый экз-р потока
Stream := TMemoryStream.Create;

// фиксируем тек.позицию считывания из потока
// ибо не делаем никаких предположений о том,
// новый это поток или уже существующий и ассоциированный с гнездом
CurStreamReadPos := Stream.Position;

//смещаем тек.позицию в конец потока
Stream.Position := Stream.Size;

//в хвост потока записываем то, что необходимо передать
Stream.Write(...то что нужно передать...);

//восстанавливаем тек.позицию в потоке
// для того, чтобы метод SendStreamPiece() мог начать продолжить
// отложенную передачу именно с того места, с которого при его предыд.вызове была неуспешной попытка передачи очер.блока
Stream.Position := CurStreamReadPos;

//если ассоциации на дан.момент нет, т.е. экз-р потока вновь создан
if not Assigned(Socket.Data) then
begin

// фиксируем ассоциацию гнезда с новым экз-ром потока
Socket.Data := Stream;

// стартуем потытку передачи потока
Процедура_обработчика_OnClientWrite(ServerSocket, Socket.Connections[index]);
end
// иначе ничего делать не надо -
// ассоциация есть, значит, ранее уже тем или иным образом
// OnWrite() вызывалась и на сей момент ассоциированный поток
// находится в стадии отложенной передачи;
// для продолжения/завершения отложенной передачи
// полагаемся на событие OnWrite(), которое будет вызвано
// автоматически, как только буфер гнезда освободится;
// здесь, в принципе, можно еще раз вызвать Application.ProcessMessages;




 
V_Pavel   (2002-11-12 12:15) [40]

Вроде все так но на самом деле чтото не так. У меня вот какое предположение. Вызвали мы метод SendStream он вернул True но на самом деле физически часть пакета еще находится на сервере. И скорее всего пока они не передадутся сокет заблокирован. Если это так то отсюда вопрос как узнать передан пакет или нет.



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

Форум: "Сети";
Текущий архив: 2003.01.13;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.58 MB
Время: 0.01 c
1-37117
MadGhost
2003-01-04 07:41
2003.01.13
Есть какой нить способ посмотреть события компонента?


14-37330
Дмитрий К.К.
2002-12-24 06:18
2003.01.13
Именинники 24 декабря


14-37369
Jeka
2002-12-24 16:19
2003.01.13
IDE command-line options


1-37156
Boger
2002-12-29 14:45
2003.01.13
thread и synchronize


1-37006
ivlex
2003-01-01 04:06
2003.01.13
О блокировке заставки





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский