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

Вниз

Проблемы при работе с Indy   Найти похожие ветки 

 
Cyrax ©   (2006-08-26 18:46) [0]

Столкнулся я с такой проблемой. При использовании компонента TIdTCPClient при отсоединении от сервера не освобождается порт, занимаемый клиентом. Отсоединяюсь я методом Disconnect (пробовал и DisconnectSocket). При каждом очередном соединении клиента ОС выделяет ему новый порт. Если же задать порт клиента самому, то при повторном соединении возникает ошибка - порт то остался занятым...
С TIdTCPServer такой проблемы не возникает.

Что самое интересное, пример работы с TIdTCPServer и TIdTCPClient (IdTCPDemo), скачанный с инета, показывает те же результаты.
На форумах читал, как обсуждали глюки некоторых компонентов Indy.
Так вот, может это очередной глюк? Или отсоединяться (клиенту) нужно как то по-другому?

Правильно ли я понимаю механизм соединения клиента с сервером:
При попытке клиента соединиться с сервером (метод TIdPeerThread.Run на клиенте) на стороне сервера вначале создаётся поток для обслуживания этого клиента. Далее происходит попытка установления связи с клиентом. Событие OnExecute на сервере генерится именно после создания потока. При этом возможна ситуация, когда поток создан, а соединение не установлено. При последующем успешном соединении этого же клиента используется тот же поток.
Если я в чём-то ошибаюсь, прошу меня поправить.
Остаётся непонятно, почему в начале обработчика события OnExecute нужно ставить проверку "if not AThread.Terminated". Что, OnExecute генерится ещё и при завершении работы потока?

Есть у меня и другая проблема. При создании объекта TIdTCPServer присвоил полям OnConnect и OnDisconnect имена обработчиков. Напомню, что я пишу модуль, и никакого интерфейса, а тем более визуальных компонентов, нет. Т.е. все объекты я создаю и инициализирую сам... Так вот, в процессе соединения и отсоединения клиентов сервер генерирует события OnConnect и OnDisconnect соответственно и вызывает мои обработчики этих событий (по крайней мере, должен). С коннектом проблем нет. НО: событие OnDisconnect на сервере не генерируется при отсоединении клиента. Это событие генерируется только при завершении работы сервера (IdTCPServer.Active := false). Как я понял из help"а, оно должно генерироваться при каждом отсоединении клиента. Именно так и происходит в случае с сервером и клиентом, созданных визуально путём установки на форму компонентов (в этом случае все необходимые поля инициализируются нужным образом).

Может я не все нужные поля у IdTCPServer проинициализировал?
Вот моя инициализация клиента:

MaxLineLength := DataMax;        
MaxLineAction := maException;
ReadTimeout := 0;
BoundPort := 0;      // чтобы не возникало проблем с занятым портом
Host := "127.0.0.1"; //  пока для проверки
Port := 6002;          


Инициализация сервера:

DefaultPort     := port;
ThreadMgr      := TIdThreadMgrDefault.Create(id_component);
MaxConnections := ClientsMax;
ListenQueue    := 15;
OnExecute      := onexecute;
OnConnect      := onconnect; // мой обработчик
OnDisconnect  := ondisconnect;  // мой обработчик

_______________________
Дополнительные вопросы:
1. Как в винде посмотреть, какие порты в данный момент заняты и какими прогами (средствами винды, без программирования) + как принудительно освободить занятый порт (естественно, с завершением работы проги).
2. Свойство BoundIP клиента указывает IP компа, на котором можно его запускать ?

_______________________
Напоследок хочу узнать мнение тех, кто работал с Indy. Я пишу модуль, который будет обеспечивать связь клиентов с сервером по TCP/IP. Интерфейс представлен 4-мя функциями: инициализация, отправка, получение и завершение. Так вот, целесообразно ли использовать Indy для этих целей. Или проще напрямую работать с winsock, ведь Indy тоже работат через winsock.


 
Dmitrij_K   (2006-08-26 21:02) [1]

Отвечу на то что могу
Клиенту выдается свободный порт, и необязательно он будет такой же как в прошлый раз (мне всегда так казалось Ж))
Механизм ты понял неправильно.


>  Как в винде посмотреть, какие порты в данный момент заняты
> и какими прогами (средствами винды, без программирования)

Файрволом например Outpost FireWall

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

Завершить работу проги

>  Или проще напрямую работать с winsock,

Зависит от твоих знаний


 
Dmitrij_K   (2006-08-26 21:03) [2]

PS такие вопросы задаются в форуме сети


 
Cyrax ©   (2006-08-27 22:26) [3]

Что порт при очередном соединении не обязательно должен быть тем же самым, я согласен. Но не думаю, что винда будет выделять произвольный порт (из свободных). Наиболее вероятный алгоритм - выделение первого свободного (из определённого диапазона). Если это так, то при очередном соединении клиента ему должен быть выделен тот же порт, что и при предыдущем (если за время между соединениями никакие другие проги не занимали порты).
В подтверждение повторю вопрос, заданный в первом сообщении:
Почему при повторном соединении клиента генерится ошибка, связанная с binding"ом сокета? (это в том случае, когда мы прямо укзываем в клиенте порт привязки)


Файрволом например Outpost FireWall

А как насчёт стандартных средств винды (например, брандмауэр Windows).


> Завершить работу проги

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


такие вопросы задаются в форуме сети

Вопросы об освобождении портов и механизме соединения клиента с сервером - да. Но о генерации событий OnExecute и OnDisconnect всё-таки в компонентах...


 
Cyrax ©   (2006-08-27 22:30) [4]

По поводу механизма соединения клиента и сервера.
Прошу меня поправить, если невломы писать...


 
Ketmar ©   (2006-08-27 22:34) [5]

> [3] Cyrax ©   (27.08.06 22:26)
> при очередном соединении клиента ему должен быть выделен
> тот же порт, что и при предыдущем

кто это кому и что должен? можно пошерстить winsock, если не устраивает логика работы. или своё написать. лично я исходников winsock не видел, и предоположения не делаю. в силу чайниковости, наверное.


 
Dmitrij_K   (2006-08-27 23:27) [6]


> А как насчёт стандартных средств винды (например, брандмауэр
> Windows).

В win нет стандартных средств, Outpost FireWall имхо самый инф
ормативный из тех что я видел

> Или таких ситуаций быть не пожет (при завершении проги порт в любом случае будет освобождён виндой?

имхо так


 
Cyrax ©   (2006-08-28 00:09) [7]

кто это кому и что должен?
Уточню формулировку: "При очередном соединении клиента ему выделяется порт с номером... Короче, ясно ведь, что имеется ввиду!  Замечу: при условии, что другие проги за это время не занимали и не освобождали портов...
...если не устраивает логика работы...
О какой логике работы идёт речь?
Пока прозвучала только моя версия, которая подтверждается практически. Она меня вполне устраивает... Вопрос в другом - освобождается ли порт?

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

Кто нибудь скажет, почему ошибка то возникает !!! (моё предыд. сообщение). Тогда и "споры ни о чём" разрешатся...


 
Cyrax ©   (2006-08-28 00:11) [8]

Dmitrij_K, может всё-таки скажешь, в чём я ошибаюсь по поводу механизма...


 
Slym ©   (2006-08-28 06:53) [9]

Клиент вообще не должен думать о своем порте, ему хватает сведений о порте сервера. Порт клиента система сама выделит свободный порт из разрешенного диапазона (>1024)
И зачем прибиндивать клиентский сокет?
при завершении проги, все Хендлы обнуляются, а нулевые освобождаются...


 
Сергей М. ©   (2006-08-28 08:35) [10]


> Cyrax ©   (26.08.06 18:46)


Событие TIdTCPServer.OnExecute возбуждается циклически (а не однократно !) в контексте соответствующего потока TIdPeerThread после успешного установления соединения с клиентом.
Проверка флага Terminated нужна для оперативного реагирования на команду деактивации сервера (IdTCPServer.Active := False).


 
Cyrax ©   (2006-08-28 13:03) [11]

Наконец-то появились нормальные ответы...
[i]
> Slym ©   (28.08.06 06:53)
[/i]
Прибиндивать сокет клиента  - не моя идея, это требование организации (возможность указания порта клиента).
Звучит, конечно, навязчиво... Но всё-таки: когда указываем порт клиента, то при повторном соединении возникает ошибка привязки сокета (без завершения работы проги - соединились, отсоединились, соединились => error). Напрашивается на то, что при отсоединении порт не освобождается... Возможно, отсоединяюсь не так как нужно (см. Cyrax ©   (26.08.06 18:46)). Здесь уже надо знать Indy...
[i]
> Сергей М. ©   (28.08.06 08:35)
[/i]
Циклически после успешного соединения - я так понимаю, генерируется однократно после каждого успешного соединения (в рамках потока).
+ генерируется при завершении потока (после деактивации сервера)?

Как, всё-таки быть с генерацией события OnDisconnect на сервере ? (см. Cyrax ©   (26.08.06 18:46)). Когда это событие должно генерироваться - при отсоединении каждого из клиентов или при завершении работы сервера (при деактивации сервера) ?


 
Сергей М. ©   (2006-08-28 13:19) [12]


> Циклически после успешного соединения - я так понимаю, генерируется
> однократно после каждого успешного соединения (в рамках
> потока).


Нет, не правильно.
После успешного установления соединения создается новый экз-р TIdPeerThread (или берется уже готовый, ранее созданный и ждущий своего использования в пуле тредпул-менеджера), в контексте которого с этого момента циклически вызывается обработчик OnExecute вплоть до самой "смерти" соединения.


 
Cyrax ©   (2006-08-28 15:15) [13]

Что значит "циклически" ? В какие моменты времени ?
И как насчёт дективации сервера ?


 
Сергей М. ©   (2006-08-28 15:40) [14]


> Что значит "циклически" ?


Циклически означает "в цикле".

ты в состоянии заглянуть в исх-ки сервера и проанализировать их ?


> как насчёт дективации сервера ?


А что насчет нее ?
Серверный диспетчер при поступлении "команды" Active := False командует всем работающим своим потокам "Terminate" и ждет пока все созданные им потоки завершатся.


 
Cyrax ©   (2006-08-28 16:16) [15]


Сергей М. ©   (28.08.06 15:40)

Хорошо, гляну в исходники...

Но следующие вопросы остаются открытыми:
1. При отсоединении клиента (disconnect, либо disconnectsocket) порт не освобождается (см. Cyrax ©   (28.08.06 13:03)).
2. Серверные OnDisconnect"ы не вызываются при отсоединении клиентов (только при завершении работы проги / деактивации сервера).


 
Сергей М. ©   (2006-08-28 16:28) [16]


> Cyrax ©   (28.08.06 16:16) [15]
>
>


Не надо месить в одну кучу сервера и клиента.
Либо ты о сервере, либо о клиенте.
Ты о ком ?


 
Cyrax ©   (2006-08-28 16:53) [17]

В первом вопросе - ... лучше переформулирую:
1. При отсоединении клиента (disconnect, либо disconnectsocket на клиенте) порт, занимаемый клиентом не освобождается (см. Cyrax ©   (28.08.06 13:03)).
2. OnDisconnect"ы (на сервере) не вызываются при отсоединении клиентов (только при завершении работы сервера / деактивации сервера).


 
Cyrax ©   (2006-08-28 17:10) [18]

Насчёт события OnExecute, - вроде бы разобрался...
Если кто не знает:
После установления соединения генерируется OnExecute, выполняется и далее генерируется циклически следующим образом:
Проверяется, подсоединён ли клиент, не был ли вызван Disconnect в OnExecute, отсутствуют ли фатальные ошибки, не было ли возбуждено необработанное исключение в OnExecute и активен ли ещё сервер.
Если всё нормально, то событие генерируется снова, и т.д.
Среди перечисленных проверок нет проверки потока на Terminated, поэтому эта проверка необходима вначале обработчика OnExecute...


 
dwar ©   (2006-08-29 07:36) [19]

А какой INDY используется ?


 
Cyrax ©   (2006-08-29 10:18) [20]

9-й.


 
Сергей М. ©   (2006-08-29 10:47) [21]


> 1. При отсоединении клиента (disconnect, либо disconnectsocket
> на клиенте) порт, занимаемый клиентом не освобождается (см.
>  Cyrax ©   (28.08.06


Не знаю, у меня все работает.

при, к примеру, BoundPort = 555555 следующий код

procedure TForm2.Button1Click(Sender: TObject);
begin
 if idtcpclient1.Connected then
   idtcpclient1.Disconnect
 else
   idtcpclient1.Connect();
end;

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


> 2. OnDisconnect"ы (на сервере) не вызываются при отсоединении
> клиентов (только при завершении работы сервера / деактивации
> сервера).


Это нормально, так и должно быть.


 
Cyrax ©   (2006-08-29 21:40) [22]

По поводу освобождения порта - ещё раз всё проверю... Если что, скину исходники для теста...

Теперь насчёт Disconnect"ов. Согласен, что на сервере OnDisconnect не должен вызываться после отсоединения клиента. OnDisconnect генерируется при вызове метода Disconnect сервера.
Сомнения у меня были вызваны примером работы с Indy (http://www.delphi.pnz.ru/components2/Indy9Demos.exe, IdTCPDemo), где факт отсоединения клиента сразу же фиксировался на сервере (на сервере вызывается OnDisconnect). Каким образом, несовсем понятно. Обработчик OnDisconnect (ServerDisconnect) как раз и обрабатывает отсоединение клиента. Но для вызова этого обработчика (через событие OnDisconnect) нужно выполнить метод Disconnect на сервере после очередной проверки AThread.Connection.Connected. Такая проверка стоит только в обработчике OnExecute, причём в случае отсоединения клиента ничего там не делается (в частности, не вызывается метод Disconnect)...

Фрагмент кода для сервера:

procedure TServerFrmMain.ServerExecute(AThread: TIdPeerThread);
var
 ActClient, RecClient: PClient;
 CommBlock, NewCommBlock: TCommBlock;
 RecThread: TIdPeerThread;
 i: Integer;
 F: integer;
begin
 if not AThread.Terminated and AThread.Connection.Connected then
 begin
   AThread.Connection.ReadBuffer (CommBlock, SizeOf (CommBlock));
   ActClient := PClient(AThread.Data);
   ActClient.LastAction := Now;  // update the time of last action

   if (CommBlock.Command = "MESSAGE") or (CommBlock.Command = "DIALOG")
     or (CommBlock.Command = "FILE") then
   begin  // "MESSAGE": A message was send - forward or broadcast it
       ...

       // Запись в файл
       F := FileCreate("2.txt");
       FileWrite(F, CommBlock.Msg, 100);
       FileClose(F);

       with Clients.LockList do
       try
         for i := 0 to Count-1 do  // iterate through client-list
               begin
           RecClient := Items[i];           // get client-object
           RecThread := RecClient.Thread;     // get client-thread out of it
           RecThread.Connection.WriteBuffer(NewCommBlock, SizeOf(NewCommBlock), True);  // send the stuff
         end;
       finally
         Clients.UnlockList;
       end;
     end
     else
     begin  // receiver given - search him and send it to him
       NewCommBlock := CommBlock; // again: nothing to change ;-))
       if CommBlock.Command = "FILE" then
         Protocol.Lines.Add(TimeToStr(Time)+" Sending "+CommBlock.Command+" to ""+CommBlock.ReceiverName
           +": received file " + CommBlock.FileName)
       else
         Protocol.Lines.Add(TimeToStr(Time)+" Sending "+CommBlock.Command+" to ""+CommBlock.ReceiverName+"": ""+CommBlock.Msg+""");

       with Clients.LockList do
       try
         for i := 0 to Count-1 do
         begin
           RecClient:=Items[i];
           if RecClient.DNS=CommBlock.ReceiverName then  // we don"t have a login function so we have to use the DNS (Hostname)
           begin
             RecThread:=RecClient.Thread;
             RecThread.Connection.WriteBuffer(NewCommBlock, SizeOf(NewCommBlock), True);
           end;
         end;
       finally
         Clients.UnlockList;
       end;
     end;
   end
   else
   begin  // unknown command given
     Protocol.Lines.Add (TimeToStr(Time)+" Unknown command from ""+CommBlock.MyUserName+"": "+CommBlock.Command);
     NewCommBlock.Command := "DIALOG";       // the message should popup on the client"s screen
     NewCommBlock.MyUserName := "[Server]";  // the server"s username
     NewCommBlock.Msg := "I don""t understand your command: ""+CommBlock.Command+""";  // the message to show
     NewCommBlock.ReceiverName := "[return-to-sender]"; // unnecessary

     AThread.Connection.WriteBuffer (NewCommBlock, SizeOf (NewCommBlock), true);  // and there it goes...
   end;
 end;
end;

procedure TServerFrmMain.ServerDisconnect(AThread: TIdPeerThread);
var
 ActClient: PClient;

begin
 ActClient := PClient(AThread.Data);
 Protocol.Lines.Add (TimeToStr(Time)+" Disconnect from ""+ActClient^.DNS+""");
 try
   Clients.LockList.Remove(ActClient);
 finally
   Clients.UnlockList;
 end;
 FreeMem(ActClient);
 AThread.Data := nil;
end;

Фрагмент кода для клиента:

 TClientHandleThread = class(TThread)
            private
                   CB: TCommBlock;
                   procedure HandleInput;
            protected
                   procedure Execute; override;
                   end;
var
 ClientFrmMain: TClientFrmMain;
 ClientHandleThread: TClientHandleThread;   // variable (type see above)

implementation
{$R *.DFM}
procedure TClientHandleThread.HandleInput;
begin
 if CB.Command = "MESSAGE" then
 ...
end;

procedure TClientHandleThread.Execute;
begin
 while not Terminated do
 begin
   if not ClientFrmMain.Client.Connected then
     Terminate
   else
   try
     ClientFrmMain.Client.ReadBuffer(CB, SizeOf (CB));
     Synchronize(HandleInput);
   except
   end;
 end;
end;

procedure TClientFrmMain.CBClientActiveClick(Sender: TObject);
begin
 if CBClientActive.Checked then
 begin
   try
     Client.Connect(10000);  // in Indy < 8.1 leave the parameter away

     ClientHandleThread := TClientHandleThread.Create(True);
     ClientHandleThread.FreeOnTerminate:=True;
     ClientHandleThread.Resume;
   except
     on E: Exception do MessageDlg ("Error while connecting:"+#13+E.Message, mtError, [mbOk], 0);
   end;
 end
 else
 begin
   ClientHandleThread.Terminate;
   Client.Disconnect;
 end;

 ButtonSend.Enabled := Client.Connected;
 CBClientActive.Checked := Client.Connected;
end;

procedure TClientFrmMain.ButtonSendClick(Sender: TObject);
var
 ...
begin
 ...
 if CommBlock.Command = "FILE" then
 ...
 Client.WriteBuffer (CommBlock, SizeOf (CommBlock), true);
end;
 ...
end.

Файл .dfm сервера:

 ...
 object Server: TIdTCPServer
   Bindings = <>
   CommandHandlers = <>
   DefaultPort = 47110
   Greeting.NumericCode = 0
   MaxConnectionReply.NumericCode = 0
   OnConnect = ServerConnect
   OnExecute = ServerExecute
   OnDisconnect = ServerDisconnect
   ReplyExceptionCode = 0
   ReplyTexts = <>
   ReplyUnknownCommand.NumericCode = 0
   ThreadMgr = IdThreadMgrDefault1
   Left = 184
   Top = 76
 end
 object IdThreadMgrDefault1: TIdThreadMgrDefault
   Left = 224
   Top = 76
 end
 ...

________________
И другой вопрос...
В моём модуле (см. Cyrax c   (26.08.06 18:46)) обработчик события OnExecute пуст, поскольку все операции по чтению и отправке данных осуществляются через интерфейсные функции. Не будут ли генерироваться исключения, если я не буду обрабатывать OnExecute?
И вообще, неплохо было бы услышать советы о том, что можно реализовать в обработчике OnExecute для модуля...


 
Сергей М. ©   (2006-08-30 08:34) [23]


> Cyrax ©   (29.08.06 21:40) [22]



> факт отсоединения клиента сразу же фиксировался на сервере
> (на сервере вызывается OnDisconnect). Каким образом, несовсем
> понятно


Все довольно просто.

Иллюстрация в псевдокоде:

//тело обработчика события сервера OnExecute

try

SomeRequest := SomeSocketReadMethod(..., SomeReadTimeout);
...
SomeSocketWriteMethod(SomeResponse);
...
except
Один из вызванных методов приема/передачи возбудил исключение.
Если это исключение НЕ вызвано превышением таймаута ожидания чтения, то
  Disconnect; // вод здесь и вызывается обработчик OnDisconnect
end;


> поскольку все операции по чтению и отправке данных осуществляются
> через интерфейсные функции


Это как ?


 
Cyrax ©   (2006-08-30 10:09) [24]

В Demo-примере используется другая схема. Там в обработчике OnExecute вначале стоит проверка
if not AThread.Terminated and AThread.Connection.Connected
и если всё нормально, то происходит чтение/запись. При этом никаких try"ев для операций чтения/записи там нет (только для LockList"а). Если условие не выполняется, то ничего не делается (в этом обработчике), т.е. работа обработчика завершается...
И вообще, на сервере нигде явно не вызывается метод Disconnect...

По поводу интерфейсных функций. Любая операция приёма и отправки данных происходит только по запросу главной программы, которая будет использовать мой модуль. Т.е. чтение/запись осуществляется только в момент вызова главной программой соответствующих функций модуля.
А в этих функциях я читаю и отправляю через WriteBuffer/ReadBuffer и возвращаю данные в главную прогу...


 
Сергей М. ©   (2006-08-30 10:23) [25]


> Cyrax ©   (30.08.06 10:09) [24]
>
> В Demo-примере используется другая схема. Там в обработчике
> OnExecute вначале стоит проверка


Да, стоит. Но она не гарантирует, что исключительные ситуации (в момент собственно приема/передачи) не возникнут.


> в этих функциях я читаю и отправляю через WriteBuffer/ReadBuffer


Эти ф-ции должны вызывать не не иначе как в контексте соответствующего TIdPeerThread, т.е. в oбработчике OnExecute.


 
Cyrax ©   (2006-08-30 14:45) [26]

Да, в примере этот случай не учтён...
Тем не менее OnDisconnect каким-то образом генерируется при отсоединении клиента. Явных вызовов метода Disconnect на сервере нет...
Или в случае невыполнения условия AThread.Connection.Connected (в этот момент уже становится известно, что клиент отсоединился) на сервере вызывается метод Disconnect ?  Где-то же он должен вызываться (неявно)...

Насчёт функций. Главная программа вызывает функцию чтения данных по сети. Эта функция по очереди проверяет потоки всех клиентов (эти потоки у меня хранятся в специальной структуре с данными о клиентах) и ищет те, в контексте которых есть входные данные. Затем читает все входные данные из этого потока и возвращает через параметры размер прочитанных данных, сами данные и id клиента, от которого данные получены. При следующем вызове этой функции просмотр потоков начинается со следующего (в порядке очереди). Главная программа, сформировав ответ, вызывает функцию отправки сообщения и передаёт ей данные, размер и идентификатор клиента.
Таким образом, обработчик OnExecute у меня пока пуст...


 
Сергей М. ©   (2006-08-31 13:16) [27]


> ли в случае невыполнения условия AThread.Connection.Connected
> (в этот момент уже становится известно, что клиент отсоединился)
> на сервере вызывается метод Disconnect ?


Наверно, так и есть.
Посмотри сам в исходниках сервера.


> вызывает функцию отправки сообщения


Мне не понятно, где у тебя вызываются собственно Write-методы объекта Connection ..


 
Cyrax ©   (2006-08-31 22:19) [28]

Функция записи, которую вызывает главная программа, по переданному ей идентификатору клиента из списка TThreadList ищет соответствующий поток AThread (у каждого потока свойство Data указывает на структуру данных с информацией о клиенте, в частноости, id), затем: Athread.Connection.WriteBuffer.
[Если что, могу скинуть исходники...]
Т.е. все операции чтения/записи происходят отдельно и незавсимо от OnExecute. OnExecute мне, получается, и не нужен вообще...

Кстати, хочу уточнить ситуацию с определением размера входных данных.
Читаю я с помощью ReadBuffer, поэтому мне надо знать число байтов на входе. Нашёл я два метода Connection"а, которые позволяют это сделать:
1. ReadFromStack. Функция возвращает число байтов и читает всё во внутренний буфер. Я внутренний буфер не использую - он мне не нужен. Поэтому эту функцию я использую только для получения размера входных данных:

data_len := AThread.Connection.ReadFromStack(true,0,false);  // читает во внутренний буфер, которого я не создавал... (скорее всего буфер создаётся по умолчанию)
if data_len > 0 then AThread.Connection.ReadBuffer(data,data_len)
else ...

Но эту функцию не рекомендуют использовать.

2. CurrentReadBuffer. Функция читает все данные и возвращает их в виде строки. По длине этой строки затем можно определить размер данных. Но в этом случае хошь-нехошь придётся использовать внутренний буфер, поскольку функция читает из внутреннего буфера (ReadFromStack - прямо из стека).

Таким образом, нет функций, определяющих размер входных данных и не использующих внутренний буфер (или я не нашёл...)


 
Сергей М. ©   (2006-09-01 08:46) [29]


> Cyrax ©   (31.08.06 22:19) [28]


Метод Connection.WriteBuffer должен вызываться в контексте обслуживающего данное соединение потока ! Обработчик OnExecute как раз и выполняется в этом контексте.


> хочу уточнить ситуацию с определением размера входных данных


Знать этот размер вовсе не обязательно.


 
Cyrax ©   (2006-09-01 20:00) [30]


>Метод Connection.WriteBuffer должен вызываться в контексте обслуживающего данное соединение потока

Я его вызываю из первичного потока с использованием объекта AThread типа TIdPeerThread. Во всех вторичных потоках (для клиентов) у меня крутится пустой обработчик OnExecute... Доступ к AThread"ам всех клиентов, естественно, имеется из любого потока процесса. Вот я и пользуюсь этими Thread"ами из первичного потока.

Знать этот размер вовсе не обязательно

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


 
Cyrax ©   (2006-09-02 20:36) [31]

По поводу вызова метода Disconnect на сервере. Ни хр он не вызывается в момент проверки AThread.Connection.Connected в обработчике OnExecute, когда клиент отсоединён. И даже AThread.Terminated не приводит к вызову Disconnect.
Поэтому, хочу уточнить (Cyrax ©   (28.08.06 17:10) [18]): проверка, подсоединён ли клиент, перед очередным вызовом OnExecute может дать положительный результат, даже если клиент в настоящий момент отсоединён (в этом случае проверка AThread.Connection.Connected вначале обработчика OnExecute также даст положительный результат). Дело в том, что об отсоединении клиента становится известно только в момент чтения/записи в соответствующем потоке. В случае, если клиент отсоединён, функции чтения/записи вызовут исключение, стандартный обработчик которого как раз и вызывает метод Disconnect сервера (этот вызов генерирует событие OnDisconnected, где выполняются наши действия). После этого проверка, подсоединён ли клиент, перед очередным вызовом OnExecute и AThread.Connection.Connected будут давать уже отрицательный результат.


 
Cyrax ©   (2006-09-02 20:38) [32]

Сергей М., твой пример (Сергей М. ©   (29.08.06 10:47) [21]) я проверил. Результаты те же...
Ниже привожу код сервера и клиента. Попробуй скомпилить и проверить без изменений.

client.dpr

program client;
uses
 Forms,
 main_unit in "main_unit.pas" {Form1};
{$R *.res}

begin
 Application.Initialize;
 Application.CreateForm(TForm1, Form1);
 Application.Run;
end.

main_unit.dfm (для клиента)

object Form1: TForm1
 Left = 305
 Top = 218
 BorderIcons = [biSystemMenu, biMinimize]
 BorderStyle = bsSingle
 Caption = "TCP Client tester"
 ClientHeight = 162
 ClientWidth = 257
 Color = clBtnFace
 Font.Charset = DEFAULT_CHARSET
 Font.Color = clWindowText
 Font.Height = -11
 Font.Name = "MS Sans Serif"
 Font.Style = []
 OldCreateOrder = False
 PixelsPerInch = 96
 TextHeight = 13
 object Label1: TLabel
   Left = 8
   Top = 144
   Width = 39
   Height = 13
   Caption = "status:"
   Font.Charset = DEFAULT_CHARSET
   Font.Color = clWindowText
   Font.Height = -11
   Font.Name = "MS Sans Serif"
   Font.Style = [fsBold]
   ParentFont = False
 end
 object Label2: TLabel
   Left = 56
   Top = 144
   Width = 77
   Height = 13
   Caption = "disconnected"
   Color = clBtnFace
   Font.Charset = DEFAULT_CHARSET
   Font.Color = clBlue
   Font.Height = -11
   Font.Name = "MS Sans Serif"
   Font.Style = [fsBold]
   ParentColor = False
   ParentFont = False
 end
 object Button1: TButton
   Left = 8
   Top = 112
   Width = 105
   Height = 25
   Caption = "Connect"
   TabOrder = 0
   OnClick = Button1Click
 end
 object Memo1: TMemo
   Left = 8
   Top = 16
   Width = 241
   Height = 85
   ReadOnly = True
   TabOrder = 1
 end
 object IdTCPClient1: TIdTCPClient
   MaxLineAction = maException
   ReadTimeout = 0
   OnDisconnected = IdTCPClient1Disconnected
   BoundPort = 55559
   Host = "127.0.0.1"
   OnConnected = IdTCPClient1Connected
   Port = 6002
   Left = 192
   Top = 8
 end
end

main_unit.pas (для клиента)

unit main_unit;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, IdBaseComponent, IdTCPClient,
 StdCtrls, IdTCPConnection, IdComponent;

type
 TForm1 = class(TForm)
   Button1: TButton;
   IdTCPClient1: TIdTCPClient;
   Memo1: TMemo;
   Label1: TLabel;
   Label2: TLabel;
   procedure Button1Click(Sender: TObject);
   procedure IdTCPClient1Connected(Sender: TObject);
   procedure IdTCPClient1Disconnected(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
 end;

var
 Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
 if IdTCPClient1.Connected
 then IdTCPClient1.Disconnect
 else IdTCPClient1.Connect;
end;

procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
 self.Label2.Caption := "connected";
 self.Button1.Caption := "disconnect";
end;

procedure TForm1.IdTCPClient1Disconnected(Sender: TObject);
begin
 self.Label2.Caption := "disconnected";
 self.Button1.Caption := "connect";
end;

end.

server.dpr

program server;

uses
 Forms,
 main_unit in "main_unit.pas" {Form1};

{$R *.res}

begin
 Application.Initialize;
 Application.CreateForm(TForm1, Form1);
 Application.Run;
end.

main_unit.dfm (для сервера)

object Form1: TForm1
 Left = 294
 Top = 245
 BorderIcons = [biSystemMenu, biMinimize]
 BorderStyle = bsSingle
 Caption = "TCP Server tester"
 ClientHeight = 130
 ClientWidth = 258
 Color = clBtnFace
 Font.Charset = DEFAULT_CHARSET
 Font.Color = clWindowText
 Font.Height = -11
 Font.Name = "MS Sans Serif"
 Font.Style = []
 OldCreateOrder = False
 PixelsPerInch = 96
 TextHeight = 13
 object protocol: TMemo
   Left = 8
   Top = 8
   Width = 241
   Height = 113
   ReadOnly = True
   TabOrder = 0
 end
 object IdTCPServer1: TIdTCPServer
   Active = True
   Bindings = <>
   CommandHandlers = <>
   DefaultPort = 6002
   Greeting.NumericCode = 0
   MaxConnectionReply.NumericCode = 0
   OnConnect = IdTCPServer1Connect
   OnExecute = IdTCPServer1Execute
   OnDisconnect = IdTCPServer1Disconnect
   ReplyExceptionCode = 0
   ReplyTexts = <>
   ReplyUnknownCommand.NumericCode = 0
   Left = 216
   Top = 88
 end
end

main_unit.pas (для сервера)

unit main_unit;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, IdBaseComponent, IdComponent, IdTCPServer, IdThreadMgrDefault,
 StdCtrls, IdThreadMgr;

type
 TForm1 = class(TForm)
   IdTCPServer1: TIdTCPServer;
   protocol: TMemo;
   procedure IdTCPServer1Execute(AThread: TIdPeerThread);
   procedure IdTCPServer1Connect(AThread: TIdPeerThread);
   procedure IdTCPServer1Disconnect(AThread: TIdPeerThread);
   procedure FormCreate(Sender: TObject);
   procedure FormDestroy(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
 end;

var
 Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
var num: integer;
begin
 if not AThread.Terminated and AThread.Connection.Connected
 then with AThread.Connection do
   begin
      num := AThread.Connection.ReadInteger(true);
   end;                                          
end;

procedure TForm1.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
 Protocol.Lines.Add(TimeToStr(Time) + " Connection with "" + AThread.Connection.LocalName + "": " + IntToStr(AThread.Connection.Socket.Binding.PeerPort));
end;

procedure TForm1.IdTCPServer1Disconnect(AThread: TIdPeerThread);
begin
 Protocol.Lines.Add (TimeToStr(Time)+" Disconnect");
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 IdTCPServer1.Active := true;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
 IdTCPServer1.Active := False;
end;

end.


При повторном соединении клиента вырабатывается исключение !!!
Глюк: хотя свойство BoundPort имеет тип Integer (4 байта), в конце концов он урезается до word (2 байта). Почему так происходит, непонятно... Поэтому в примере я указал порт клиента 55555.

__________________________________________________________________
Кто знает, как определить число байт на входе или как считать все байты, не замораживая работу потока ?


 
Dmitrij_K   (2006-09-03 19:56) [33]


>  свойство BoundPort имеет тип Integer (4 байта), в конце
> концов он урезается до word (2 байта)

потому что порт это тип Word 2 байта

> Кто знает, как определить число байт на входе или как считать
> все байты, не замораживая работу потока ?

 AThread.Connection.ReadFromStack; - прочитать все данные из сокета (какие есть на данный момент) во внутренний буфер InputBuffer
AThread.Connection.InputBuffer - внутренний буфер, наследник от TMemoryStream со всеми вытекающими

---
 AThread.Connection.ReadFromStack;
 байт на входе := AThread.Connection.InputBuffer.Size;

---


 
Cyrax ©   (2006-09-03 20:24) [34]

> Dmitrij_K   (03.09.06 19:56) [33]
> потому что порт это тип Word 2 байта

Да, но тип свойства BoundPort - Integer (4 байта)...

> AThread.Connection.ReadFromStack;
> байт на входе := AThread.Connection.InputBuffer.Size;

А без внутреннего буфера ?


 
Dmitrij_K   (2006-09-03 21:19) [35]


> Да, но тип свойства BoundPort - Integer (4 байта)...

захотелось им так, в TCP/IP порт это 2 байта т.е. тип Word


> А без внутреннего буфера ?

ioctlsocket(IdTCPClient1.Socket.Binding.Handle, FIONREAD, j);
в j кол-во принятых байт но еще не прочитанных из сокета


 
Сергей М. ©   (2006-09-04 08:45) [36]


> Cyrax ©   (01.09.06 20:00) [30]
> Я его вызываю из первичного потока


Ну и накой ляд тогда, спрашивается, существует TIdPeerThread ?
Это же поток транспортного уровня).. Он для того и существует, чтобы обслуживать транспортные гнездовые операции, не блокируя при этом прочие потоки процесса.


 
Cyrax ©   (2006-09-04 10:05) [37]

Чё то ничего не понял... трудно соображать поутру... наверное, от Ketmar"а пошло...

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


 
Сергей М. ©   (2006-09-04 10:07) [38]


> мне нужно это делать именно по запросу главной программы


Ну и делай) ... в чем проблема-то ? не знаешь как синхронизировать потоки или что ?


 
Сергей М. ©   (2006-09-04 10:14) [39]

with AThread do
while not Terminated and Connection.Connected and GetDataToSend(SomeDataToSend, SomeTimeout) do begin
 try
   Connection.SomeWriteMethod( ..SomeDataToSend ... );
 except
   Connection.Disconnect;
 end;
end;


 
Cyrax ©   (2006-09-04 10:14) [40]

Ну и делай)

Ну а варианты то какие ?  Например, с OnExecute.

в чем проблема-то

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


 
Cyrax ©   (2006-09-04 10:27) [41]

Сергей М. ©   (04.09.06 10:14) [39]

Вообще-то примерчик неплох... стоит попробовать.

А как насчёт освобождения порта ?


 
Сергей М. ©   (2006-09-04 10:30) [42]


> мне нужно обнаружить отсоединение клиента как можно раньше,
>  хотя бы в момент чтения/записи


см. [23]


> как насчёт освобождения порта ?


см. [21]


 
Cyrax ©   (2006-09-04 10:37) [43]

По поводу [23]:
Обработчик OnDisconnect вызывается стандартным обработчиком исключения (см. [31])... (по крайней мере, у меня так работало, без своих except"ов).

> см. [21]

Как насчёт [32]?


 
Сергей М. ©   (2006-09-04 10:50) [44]


> Cyrax ©   (04.09.06 10:37) [43]


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

Если возникло исключение, то оно будет при этом перехвачено и обработано тобой должным образом.

При чем здесь OnDisconnect ?


> Как насчёт [32]?


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


 
Cyrax ©   (2006-09-04 11:08) [45]

Если возникло исключение, то оно будет при этом перехвачено и обработано тобой должным образом.
При чем здесь OnDisconnect ?

Хочу сказать? что я обрабатываю это исключение в обработчике OnDisconnect. А OnDisconnect у меня вызывается стандартным обработчиком этого исключения, даже если не отлавливать try...except"ом...

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

> Как насчёт [32]?

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


 
Сергей М. ©   (2006-09-04 11:21) [46]


> я обрабатываю это исключение в обработчике OnDisconnect.
>  А OnDisconnect у меня вызывается стандартным обработчиком
> этого исключения, даже если не отлавливать try...except"ом


Ну можно и так.


> мой пример, где как раз и использую твой код


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


 
Cyrax ©   (2006-09-04 11:28) [47]


Socket Error # 10048
Address already in use.

Даже после перезагрузки клиента и сервера при соединении клиента сразу же возникает это исключение (с тем же портом клиента)...


 
Сергей М. ©   (2006-09-04 11:35) [48]


> Cyrax ©   (04.09.06 11:28) [47]


Повторил твой код с максимальной точностью

 object IdTCPClient1: TIdTCPClient
   ...
  BoundPort = 55559
  Host = "127.0.0.1"
  ...
end

Никаких "Address already in use" по-прежнему не наблюдаю, хоть без хоть с перезагрузкой клиента.


 
Cyrax ©   (2006-09-04 11:42) [49]

Что за полтергейст... средь бела дня...

А ты пробовал создать файлы с текстом из [32] и скомпилить ?

P.S. Знаю, страшно ломает...


 
Сергей М. ©   (2006-09-04 11:47) [50]


> ты пробовал создать файлы с текстом из [32] и скомпилить
> ?
>


А нафига ?
Если ты утверждаешь, что именно строка IdTCPClient1.Connect приводит к отказу 10048, то сервер тут вообще ни при чем .. Зачем, спрашивается его компилить ?)


 
Cyrax ©   (2006-09-04 11:49) [51]

Глюко всяко случается...


 
Сергей М. ©   (2006-09-04 11:52) [52]


> Cyrax ©   (04.09.06 11:49) [51]


Возьми любую готовую утилитку  (а-ля TCPView) да посмотри, занят ли порт 55559 после первого же дисконнекта клиента.


 
Cyrax ©   (2006-09-05 09:37) [53]

Всё, запарился... Не буду извращаться с Indy...
Сделаю в соответствии с идеологией Indy - все read/write в OnExecute и потоки в OnExecute. А то OnDisconnect достал...

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

Что касается порта клиента, то проверил OutPost"ом - порт все-таки освобождается... но ошибка выходит... Может, руки кривые...
Проверю ещё раз, где именно вылазит.


 
Сергей М. ©   (2006-09-05 10:06) [54]


> Нужно, чтобы эти функции сразу же возвращали результат (код
> ошибки), а для этого нужно подождать, пока выполниться операция
> чтения/записи в соответствующем потоке


При этом теряется весь смысл выполнения трансп.операций в доп.потоке.


> Остановился пока на варианте ждать в цикле, пока не завершится
> операция в потоке


Плохой вариант.


 
Cyrax ©   (2006-09-05 10:12) [55]

Согласен, но по-другому не получится...


 
Сергей М. ©   (2006-09-05 11:23) [56]


> по-другому не получится.


Чтой-то вдруг ?


 
Cyrax ©   (2006-09-05 12:33) [57]

Да-да...


 
Сергей М. ©   (2006-09-05 12:53) [58]


> Да-да


Ну-ну)


 
Cyrax ©   (2006-09-05 13:06) [59]

:)...

lol


 
Cyrax ©   (2006-09-06 12:50) [60]

Сергей М. ©   (05.09.06 10:06) [54]
Я:
> Нужно, чтобы эти функции сразу же возвращали результат (код
> ошибки), а для этого нужно подождать, пока выполниться операция
> чтения/записи в соответствующем потоке

> Остановился пока на варианте ждать в цикле, пока не завершится
> операция в потоке

Плохой вариант.


если ты это серьёзно, то не подскажешь другой...


 
Сергей М. ©   (2006-09-06 12:59) [61]


> Cyrax ©   (06.09.06 12:50) [60]


Зачем основному потоку ждать результат отправки сообщения ?
Зачем основному потоку ждать приема сообщения ?
Ему что, нечем более заняться ?


 
Cyrax ©   (2006-09-06 22:50) [62]

Да это всё начальство. Так что подумать придётся...
Впрочем, можно его и переубедить. Главное - убедительно аргументировать...
Раньше у них был модуль, работающий через IPX. Там функции чтения/записи возвращали результат:
TBrsNetResult = (OK,CRCERROR,TIMEOUT,BUSY,SYSTEMERROR,TERMINATED,CHANNELERROR,SEQUENCEERROR,CFER ROR);

В общем так получается. Ты следишь только за отключением клиента (или разрывом связи) в OnDisconnect... Всё остальное - на совести TCP. (если там что-то случится, получишь OnDisconnect).
________________________
Ещё вот такой глюк у меня:

procedure TTCPServer.onconnect(AThread: TIdPeerThread);
...
begin
...
temp_ptr_1 := PTSysClient(AThread.Data);
temp_client_1 := temp_ptr_1^;
end;

procedure TTCPServer.ondisconnect(AThread: TIdPeerThread);
...
begin
 temp_ptr_2 := PTSysClient(AThread.Data);
 temp_client_2 := temp_ptr_2^;
...
end;


Здесь TSysClient - класс с данными о клиенте. PTSysClient - указатель на такой класс. temp_ptr_1/2 типа PTSysClient, temp_client_1/2 - TSysClient. Все они глобальные. AThread.Data у меня указывает на объект класса TSysClient.

Так вот. Когда клиент соединяется, в temp_client_2 получаю то что ожидается. НО: когда клиент отсоединяется, то в temp_client_2 получаю козяблики вместо класса. Ощущение, что temp_ptr_2 указывает на левую область памяти. И в то же время (проверил) AThread тот же самый, AThread.Data = $то_же_самое (кстати, это значение указателя ?).
Если Data"ы указывают на одну и ту же область памяти и если по значению указателя temp_ptr_1 в момент выполнения OnDisconnect лежит клиент (никуда он не девается), то как по такому же указателю он может отсутствовать ?
Что за аномалия ?

То же самое наблюдается и в OnExecute.

____________________________________________
И насчёт порта клиента, который освобождается:
Сообщение выходит именно на connection"е. Порт при этом освобождается...

______________________________
Dmitrij_K   (03.09.06 21:19) [35]
ioctlsocket(IdTCPClient1.Socket.Binding.Handle, FIONREAD, j);
в j кол-во принятых байт но еще не прочитанных из сокета

Что это за функция. Из какой библиотеки ?


 
Dmitrij_K   (2006-09-06 23:08) [63]


> ioctlsocket(IdTCPClient1.Socket.Binding.Handle, FIONREAD,
>  j);в j кол-во принятых байт но еще не прочитанных из сокетаЧто
> это за функция. Из какой библиотеки ?

ws2_32.dll


 
Сергей М. ©   (2006-09-07 08:37) [64]


> Cyrax ©   (06.09.06 22:50) [62]



> TSysClient - класс с данными о клиенте. PTSysClient - указатель
> на такой класс.


Ну не класс, наверное, а экземпляр (объект) класса  TSysClient.
Объекты в Делфи и так уже представлены указателями, и к чему в таком случае хранить указатель на указатель - не понятно ..


> Когда клиент соединяется, в temp_client_2 получаю то что
> ожидается


А откуда там взялось это самое "то что ожидается" ? В какой момент времени, при каком событии ты устанавливаешь значение AThread.Data ?


> Все они глобальные


Это еще зачем ?


> AThread.Data = $то_же_самое (кстати, это значение указателя
> ?)


Ты про $ что ли ?
Нет , это не "значение указателя", это вообще непонятно что ..

Схема в твоем случае д.б. примерно такая:

В обработчике OnConnect:

var
 SysClient: TSysClientl; //локальная переменная !
 with SysClient do begin
   .. здесь устанавливаешь св-ва объекта SysClient
 end;
 AThread.Data := SysClient; //ассоциируешь объект с транспортной нитью клиента

В обработчике OnDisconnect:
 TSysClient(AThread.Data).Free; //уничтожаешь объект

В прочих обработчиках, где фигурирует AThread, обращаешься к объекту, ассоциированному с трансп.нитью:

 with TSysClient(AThread.Data) do begin
   SomeValue := .SomeProperty;
   SomeMethod(SomeParams);
   ...
 end;


 
Сергей М. ©   (2006-09-07 09:33) [65]

Извиняюсь, самое главное забыл:

var
SysClient: TSysClient; //локальная переменная !
..

SysClient := TSysClient.Create(..); //создаешь объект
with SysClient do begin
  .. здесь устанавливаешь св-ва объекта SysClient
end;
AThread.Data := SysClient; //ассоциируешь объект с транспортной нитью клиента


 
Cyrax ©   (2006-09-07 10:33) [66]

> Ну не класс, наверное, а экземпляр (объект) класса  TSysClient.

Угу...

> Объекты в Делфи и так уже представлены указателями, и к чему в таком
> случае хранить указатель на указатель - не понятно ..

Ухх... Это я от С так соображаю... (там TSysClient - не указатель).
Получается в делфе 2 типа указателей: указатель на объект, который реализован как ссылка (TSysClient) (или это константный указатель ? [*]), и указатель (допустима адресная арифметика), содержащий адрес переменной или объекта.
Да ещё и ссылки...

>> Когда клиент соединяется, в temp_client_2 получаю то что
>> ожидается
>А откуда там взялось это самое "то что ожидается" ? В какой момент >времени, при каком событии ты устанавливаешь значение AThread.Data ?

Athread.Data устанавливаю в OnConnect после заполнения свойств нового клиента (AThread.Data := TObject(SysClient)).

temp_ptr_1 := PTSysClient(AThread.Data);  

AThread.Data - указатель на void, типизирую его к PTSysClient (да, получится тип "указатель на указатель", если [*] верно).

temp_client_1 := temp_ptr_1^;

Здесь temp_ptr_1^ возвратит 4 байта - указатель. И это не должны быть первые 4 байта памяти, где физически расположен объект SysClient... И вообще, объекты лежат в куче, указатели - как локальные переменные - в стеке. Т.е. значением temp_client и должны быть крокозяблики... Так и происходит в OnDisconnect и в OnExecute.  Но в OnConnect temp_client_1 указывает на клиента корректно (т.е. по тому адресу, куда temp_client_1 указывает, действительно лежит клиент)...

>> Все они глобальные
>Это еще зачем ?

Все эти 4 указателя/объекта я ввёл только для проверки рассматриваемой ситуации...

>Ты про $ что ли ?
>Нет , это не "значение указателя", это вообще непонятно что ..

То, что после этого козяблика, всегда одно и то же... :))

Схема в твоем случае д.б. примерно такая: ...

Всё так и есть, только делал я так:
...PTSysClient(AThread.Data)...  // думал, что TSysClient - объект


 
Сергей М. ©   (2006-09-07 10:59) [67]


> Получается в делфе 2 типа указателей: указатель на объект,
>  который реализован как ссылка (TSysClient) (или это константный
> указатель ? [*]), и указатель (допустима адресная арифметика),
>  содержащий адрес переменной или объекта.


В Делфи любая переменная типа класс представлена в памяти как указатель.
Содержимое этой переменной есть адрес фактического местонахождения объекта соответствующего класса.


 
Cyrax ©   (2006-09-08 08:44) [68]

Интересная вещь получается. Я в первичном потоке два раза запираю clients типа TThreadList (LockList) (эти запоры что - накапливаются ?) и один раз отпираю (там же). Затем во вторичном потоке пытаюcь выполнить LockList и у меня текущая процедура моментально завершается на этом LockList"е, никаких исключений не выбрасывается, в finally даже не попадаю. Т.е. текущая процедура завершается, но прога продолжает работать...
И зачем тогда вообще проверять, заперт ли список или нет, если не могу эту ситуацию отловить...

И ещё. Если эти запоры накапливаются, то UnLock делать придётся для каждого запора. А если запоров много ? - колоссальное загромождение кода try"ами и finally"ами.

А число байт на входе всё-таки придётся узнавать... Потому что в OnExecute у меня происходит сначала отправка данных из выходного буфера соответствующего клиента по сети и приём данних из сети во входной буфер етого же клиента. Таким образом, если я заблокирую поток, например, чтением из сокета, то не смогу отправить данные по сети етого же клиента (до тех пор, пока клиенту не придёт нужное количество байт и поток не разблокируется, а если не придёт ?)...
Так вот, довольно нехорошо получается, что средствами Indy никак не получить число байт на входе, не трогая внутренний буфер...

Dmitrij_K   (06.09.06 23:08) [63]
ws2_32.dll

А шо ето разнит winsock2.dll ?


 
Сергей М. ©   (2006-09-08 10:09) [69]


> эти запоры что - накапливаются ?


Для каждой создаваемой крит.секции система создает соотв.сч-к блокировок ("запоров" в твоей терминологии).

Логика работы блокировки примерно такова:

при lock сч-к проверяется на равенство 0, и если равен, то сч-к инкрементируется (поток "захватывает" крит.секцию),  в противном случае поток блокируется до момента обнаружения обнуления сч-ка.
Каждый unlock, выполненный захватившим секцию потоком, декрементирует сч-к.


> Если эти запоры накапливаются, то UnLock делать придётся
> для каждого запора. А если запоров много ? - колоссальное
> загромождение кода try"ами и finally"ами


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


> в OnExecute у меня происходит сначала отправка данных из
> выходного буфера соответствующего клиента по сети и приём
> данних из сети во входной буфер етого же клиента


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

Совершенно очевидно, что сторона, пославшая запрос, должна ждать ответа партнера по соединению столь долго, сколь это предусмотрено прикладным протоколом инф.обмена (ППИО), и до этих пор не пытаться отправлять очередные запросы.

Тем же самым ППИО должно быть оговорено, как должны разделяться передаваемые/принимаемые инф.пакеты в сплошном потоке приема/передачи:

1. пакеты предваряются фиксированным (например, 4 байта, содержащие Integer-значение) префиксом размера следующего за ним пакета (если размер пакета не фиксирован протоколом и может варьироваться)
2. пакеты разделяются специальной сигнатурной последовательностью фикс.размера (например, нулевой байт, являющийся символом-терминатором пакета, содержащего строковые данные в ANSI-кодировке)

Если ты в своем ППИО предусмотрел вышеизложенное, то знать размер данных, доступных в очереди приема, совершенно не обязательно, о чем я тебе и говорил.


 
Cyrax ©   (2006-09-08 18:00) [70]

По поводу ППИО. У меня сервер является промежуточным звеном между клиентами и аппаратурой. Клиенты посылают запросы серверу по TCP/IP, сервер отсылает эти запросы аппаратуре через последовательный порт, затем принимает ответ от аппаратуры и пересылает его нужному клиенту.
ППИО (назовём его П1) организован только между клиентами и аппаратурой, а сервер является лишь промежуточным звеном между ними, т.е. сервер принимает всё, что пришло и пересылает без модификации аппаратуре. И не разбирает П1-пакеты.
Можно, конечно, и "заставить" сервер разбирать П1-пакеты (в моём модуле). Тогда можно будет принимать и отсылать П1-пакеты, например по-одному (и не нужно будет определять число байт на входе). Но в этом случае теряется универсальность модуля из-за ориентации на ППИО П1.

____________________________
> Dmitrij_K   (06.09.06 23:08) [63]
> ws2_32.dll

А шо ето разнит winsock2.dll ?


 
Dmitrij_K   (2006-09-08 19:57) [71]


> А шо ето разнит winsock2.dll

Шо? Я не понимать.


 
Cyrax ©   (2006-09-08 22:15) [72]

Т.е. почему бы не использовать winsock2.dll, с которым работает Indy...


 
Dmitrij_K   (2006-09-08 22:24) [73]

библиотеки winsock2.dll не существует
indy работает с WS2_32.DLL


 
Cyrax ©   (2006-09-08 23:13) [74]

...спутал с делфийским модулем...


 
Сергей М. ©   (2006-09-11 08:25) [75]


> сервер принимает всё, что пришло и пересылает без модификации
> аппаратуре. И не разбирает П1-пакеты


Ну и зачем при этом серверу знать размер данных "на входе" ?
Читий равными блоками, например, по 4К. Сколько реально будет прочитано, столько и записывай "на выход".


 
Cyrax ©   (2006-09-11 09:37) [76]

Сергей М. ©   (11.09.06 08:25) [75]
Ну и зачем при этом серверу знать размер данных "на входе" ?

Чтобы не блокировать поток, поскольку в OnExecute происходит не только чтение, но и отправка. Попытаемся, например, считать 4Кб, а будет всего 3 - поток заблокируется и мы не сможем отправлять данные, пока не придёт ещё 1 Кб...

Сколько реально будет прочитано

Вот это и нужно знать - сколько их там, байтов...


 
Сергей М. ©   (2006-09-11 09:51) [77]


> Cyrax ©   (11.09.06 09:37) [76]


> Чтобы не блокировать поток, поскольку в OnExecute происходит
> не только чтение, но и отправка


Блокирование происходит не более чем на время, указанное в таймаут-параметре.


> Вот это и нужно знать - сколько их там, байтов


Объект TIdTCPConnection имеет св-во RecvBufferSize - это и есть размер данных, доступных для чтения в дан.момент.


 
Сергей М. ©   (2006-09-11 09:59) [78]

Вру.

См. св-во Connection.InputBuffer.Size

Кр.того есть метод Connection.CurrentReadBuffer - он вернет строку, длина которой и показывает "сколько их там, байтов"


 
Cyrax ©   (2006-09-11 10:15) [79]

Connection.InputBuffer.Size

Только для этого задействовать буфер...

Connection.CurrentReadBuffer

Затем преобразовывать строку в цепочку байтов - неэффективно.
Да и внутренний буфер опять задействовать придётся...


 
Сергей М. ©   (2006-09-11 10:21) [80]


> Только для этого задействовать буфер..


Что значит "задействовать" ? Он и так задействован в TIdTCPConnection, хочешь тыт того или нет...


> Затем преобразовывать строку в цепочку байтов - неэффективно.
>
> Да и внутренний буфер опять задействовать придётся...


Это еще зачем ?

s := AThread.Connection.CurrentReadBuffer;
if Length(s) > 0 then
 AThread.Connection.WriteBuffer(PChar(s)^, Length(s));


 
Cyrax ©   (2006-09-11 20:07) [81]

Что значит "задействовать" ? Он и так задействован в TIdTCPConnection, хочешь тыт того или нет...

(типа "да-да", "ну-ну" не буду...:) )
По моему, буфера, с которыми работает Connection, - это вовсе не InputBuffer. Причём InputBuffer - только для удобства вывода.
И для работы с InputBuffer его нужно проинициализировать (кажется, Connection.CreateBuffer или что-то типа того).

s := AThread.Connection.CurrentReadBuffer;
if Length(s) > 0 then
AThread.Connection.WriteBuffer(PChar(s)^, Length(s));

А string в Delphi - не объект ?.. чтобы его PChar"ить...
А первым параметром WriteBuffer"а может быть чистый адрес?

И вот ещё два таких вопроса возникло.
1. Что за буфер нужен методу ReadBuffer класса TMemoryStream (первый параметр)? Как в Connection.ReadBuffer не прокатывает. А там у меня массив byte"ов. Передаю имя массива. Pointer проглатывает.
1.5. А нужно мне прочитать в TMemoryStream. Пытаюсь как-то с MemoryStream.Memory, но ни в какую...
2. Как можно без вспомогательного локального буфера считать из Connection в MemoryStream ?


 
Сергей М. ©   (2006-09-12 08:33) [82]


> буфера, с которыми работает Connection, - это вовсе не InputBuffer.
>  Причём InputBuffer - только для удобства вывода.
> И для работы с InputBuffer его нужно проинициализировать
> (кажется, Connection.CreateBuffer или что-то типа того).
>


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


> string в Delphi - не объект ?


Нет, не объект.


> первым параметром WriteBuffer"а может быть чистый адрес?


Что значит "чистый" ?


> 1. Что за буфер нужен методу ReadBuffer класса TMemoryStream
> (первый параметр)?


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


> Как в Connection.ReadBuffer не прокатывает


Что значит "не прокатывает" ?

Прототипы методов обоих классов абсолютно идентичны.


> у меня массив byte"ов


Любой блок памяти, используемый как буфер, можно интерпретировать как массив байт.


> Передаю имя массива


Массив при этом д.б. статический.


> мне прочитать в TMemoryStream. Пытаюсь как-то с MemoryStream.
> Memory, но ни в какую


MemStream.Size := SomeSize;
Connection.ReadBuffer(MemStream.Memory^, MemStream.Size);


> 2. Как можно без вспомогательного локального буфера считать
> из Connection в MemoryStream ?


Никак.
Промежуточный буфер так или иначе (явно или неявно) используется, без него никак не обойтись.


 
Cyrax ©   (2006-09-12 21:44) [83]

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

Да.. Рассказал сказок...

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

... только передавать его можно не по всякому...
А ВАП - это типа процессоптера ?

MemStream.Size := SomeSize;
Connection.ReadBuffer(MemStream.Memory^, MemStream.Size);

Это же и есть без вспомогательного... (из Connection в MemoryStream)

___________________________
А есть ли в Delphi модули для работы с динамическими очередями - типа C"ных queue.h ?
Нужно для целых.

__________________________
А теперь про TMemoryStream.
Материться не буду. Просто приведу фрагмент кода:

var buf: TMemoryStream;
    int i: integer;
    data: array[0..99] of byte;
begin
 for i := 1 to 99 do data[i] := i;
 buf := TMemoryStream.Create;
 buf.WriteBuffer(data,5);  // всё чисто: size = 5, position = 5
 buf.ReadBuffer(data,5);  // ошибка: stream read error (чтение по такому-то адресу)
                                 // ReadBuffer вызывает Read, который возвращает 0 (число прочитанных байтов),
                                 // из-за чего (count <> 0) генерится ошибка. В Read просунуться не смог - нет исходников...
                                 // Пробовал после создания buf Clear"ить, обнулять Position - то же самое. Пробовал с TStringStream - ошибки нет, но читает 0 символов (т.е. ReadString возвращает "").
 buf.Destroy;
end;

... а дальше можно вешаться... или пристрелиться...


 
Dmitrij_K   (2006-09-12 21:54) [84]


> Просто приведу фрагмент кода

Смотрел плакал
 
 buf := TMemoryStream.Create;
 try
   buf.WriteBuffer(data,5);
   ZeroMemory(@data[0], SizeOf(data));
   buf.Position := 0;
   buf.ReadBuffer(data,5);
   if data[0]=1 then
     sleep(1);
 finally
   buf.Free;
 end



> В Read просунуться не смог - нет исходников

Не надо врать. Classes.pas должен быть


 
Cyrax ©   (2006-09-12 22:17) [85]

Я не идиот !  Это я на сях привык.
А там data - указатель на data[0].
Деструктор - не Free, а что-то вроде Destroy.

ZeroMemory(@data[0], SizeOf(data));

Если это чистка памяти, то почему @data[0], а не data ?

buf.Position := 0;

Create - что, Position не обнуляет ?

Кроме того, с connection"ом мой вариант кода прокатывает без проблем !
А finally и sizeof - не использовал, поскольку фрагмент не из моего модуля, а надуманный, только для анализа етого фокуса...
И почему Free, а не Destroy ?  Деструктор же. Не должен тупить...

if data[0]=1 then
    sleep(1);

А ето шо ?  Особенно sleep ?

И чем мой код принципиально отличается от етого. Глюки были только из-за ZeroMemory ?  Не верится...

Не надо врать. Classes.pas должен быть

Я и не вру. Classes.pas есть. Но там только ReadBuffer имплементится, Read - нет. А дальше (в Read) - не пускают...


 
Dmitrij_K   (2006-09-12 22:35) [86]


> Это я на сях привык.

Тогда простительно :)
ZeroMemory и sleep для проверки данных в отладчике, мусор вобщем
В Classes.pas ищи TCustomMemoryStream.Read

> И почему Free, а не Destroy ?

Так принято в Delphi.
Ошибка в Read потому что ты пытался читать с позиции 5, те вверх а не вниз. Вот и получил AV


 
Сергей М. ©   (2006-09-13 09:09) [87]


> Cyrax ©   (12.09.06 21:44) [83]


> только передавать его можно не по всякому


Разумеется не по-всякому.
Это зависит от того как декларированы формальный и фактический параметры.


> ВАП - это типа процессоптера ?


Это Виртуальное Адресное Пространство (процесса).


> Это же и есть без вспомогательного


А SomeSize тебе с потолка свалился ?


> есть ли в Delphi модули для работы с динамическими очередями
> - типа C"ных queue.h ?
> Нужно для целых.


Есть.
См. класс TQueue в Contnrs.pas


>  buf.WriteBuffer(data,5);  // всё чисто: size = 5, position
> = 5
>  buf.ReadBuffer(data,5);  // ошибка: stream read error


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


> В Read просунуться не смог - нет исходников


Есть исходники !

cм. TCustomMemoryStream.Read (Classes.pas)


> я на сях привык.
> А там data - указатель на data[0]


И в данном случае тоже самое - 1-м параметром в ReadBuffer() фактически передается адрес data[0]


> почему @data[0], а не data ?


Потому что формальный параметр Destination объявлен как pointer и тербует передачи в кач-ве фактического параметра нач.адреса блока памяти.
А нач.адрес стат.массива data и есть адрес его младшего элемента, т.е. @data[0]

Вот если бы Destination был объявлен как нетипизированный параметр, передаваемый по ссылке (т.е. var Destination или const Destination), факт.параметр data можно было бы передать так как ты привык на Сях:

ZeroMemory(data, sizeof(data));

В этом случае заботу о вычислении нач.адреса блока памяти, распределенного под факт.параметр, берет на себя сам компилятор.


 
Cyrax ©   (2006-09-13 22:11) [88]

Dmitrij_K   (12.09.06 22:35) [86]
В Classes.pas ищи TCustomMemoryStream.Read

Всё чих-пых, только вот в режиме отладки в библиотечные функции не пускают... В release-варианте, наверное, скомпилен (Delphi)...
Да ещё по Ctrl в ReadBuffer пускают, а дальше - в Read (это который от TCustomMemory) - никак...

Блин, я везде в своих классах Destroy вызывал...
(The Destroy destructor should never be called directly. To destroy a component created with Create, call the Free method.)

Сергей М. ©   (13.09.06 09:09) [87]
>> Это же и есть без вспомогательного
>А SomeSize тебе с потолка свалился ?

Это не буфер.

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

Всё понятно. Мне нужен был поток, который бы при чтении декрементировал position и уменьшал размер. Подобно Connection"у. Оных не нашёл - пришлось наследовать самому...

> почему @data[0], а не data ?
Потому что формальный параметр Destination объявлен как pointer и тербует передачи в кач-ве фактического параметра нач.адреса блока памяти...

Адрес или указатель на этот адрес ?
Я думаю указатель (нетипизированный) на этот адрес (pointer).

Вот если бы Destination был объявлен как нетипизированный параметр, передаваемый по ссылке (т.е. var Destination или const Destination), факт.параметр data можно было бы передать так как ты привык на Сях:
        ZeroMemory(data, sizeof(data));
В этом случае заботу о вычислении нач.адреса блока памяти, распределенного под факт.параметр, берет на себя сам компилятор.

Т.е. data здесь воспринимается не как указатель на первый элемент массива (как в C), а как сам массив, адрес которого ещё нужно вычислить ?

Предположим, я передам data там, где требуется указатель на адрес (pointer). Тогда компилятор возмёт первые 4 байта массива и интерпретирует их как адрес данных, с которыми он будет работать ?

_____________________________________________________________
Кстати, длина статического массива хранится в байтах перед первым элементом массива ?  И сколько байт выделяется под эту длину ?


 
Сергей М. ©   (2006-09-14 09:34) [89]


> Cyrax ©   (13.09.06 22:11) [88]


> Это не буфер


Разумеется.
Это то самое значение, которое ты читаешь из InputBuffer.Size.
Так вот этот самый InputBuffer и есть ничто иное как "вспомогательный буфер", аккумулирующий принимаемый из гнезда вх.поток данных.


> Мне нужен был поток, который бы при чтении декрементировал
> position и уменьшал размер. Подобно Connection"у. Оных не
> нашёл - пришлось наследовать самому


Нормальное решение.
За образец класса, реализующего два независимых позиционных стрим-маркера (тек.позиция записи и тек.позиция чтения) можно было взять класс TDataBlock (sconnect.pas).


> Адрес или указатель на этот адрес ?


Данные указ.типа (pointer) предназначены для хранения адресов.

Следующие высказывания эквивалентны по смыслу:
Содержимое переменной (параметра) типа pointer
a) содержит адрес неких данных некоего типа.
b) указывает на некие данные некоего типа
с) ссылается на некие данные некоего типа


> data здесь воспринимается не как указатель на первый элемент
> массива (как в C), а как сам массив, адрес которого ещё
> нужно вычислить ?


Да, верно.

Для вычисления адреса переменной data в дан.случае как раз и применяется префикс @. Вычисленный таким образом адрес трактуется компилятором как данные указ.типа, что не противоречит типу форм.параметра, объявленного как pointer.


> Предположим, я передам data там, где требуется указатель
> на адрес (pointer)


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


> компилятор возмёт первые 4 байта массива и интерпретирует
> их как адрес данных, с которыми он будет работать ?


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


> длина статического массива хранится в байтах перед первым
> элементом массива ?


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


 
Cyrax ©   (2006-09-14 23:14) [90]

1. Насчёт Free. Что-то сразу не подумал и понаставил для своих классов Free вместо Destroy. Естественно, в этом случае будет вызываться Free базового класса (TMemoryStream.Free), который о моих Destroy"ях даже и не догадывается...

2. Очередь TQueue. Довольно тонкий вопрос совмещения очереди Queue (типа TQueue или TObjectQueue) и разделяемого потоками списка ThreadList (типа TThreadList), содержащего указатели на данные клиентов.
    Дело в том, что если взять очередь указателей (TQueue), то воле-неволей попытаешься направить их на те же участки памяти, куда указывают элементы списка ThreadList. Но в момент доступа к данным клиентов с помощью этой очереди никаких блокировок не происходит (как в случае доступа через TThreadList.LockList.Items...). Т.е. этот вариант отпадает из-за невозможности заблокировать ThreadList (доступ к клиентским данным осуществляется, минуя его)...
    Если же взять очередь объектов (TObjectQueue), то единственно эффективным вариантом будет хранить в этой очереди, например, идентификаторы (целое число) клиентов (сейчас я так и сделал), которые являются одним из полей структуры, содержащей клиентские данные.
    Минусом такой организации очереди является необходимость при каждом очередном доступе к какому-либо клиенту по полученному из очереди идентификатору искать соответствующие данные по списку ThreadList.
    Другой минус - необходимость синхронизации очереди ObjectQueue и списка ThreadList при каждом соединении/отсоединении клиента.
    Плюс очереди объектов - каждая операция доступа к клиентским данным происходит через список ThreadList, что влечёт за собой обязательную блокировку списка.

3. Требуется ограничить число одновременно подключенных клиентов (const ClientsMax). Подключение обрабатывается в OnConnect, т.е. ничего передать в использующую модуль программу не получится. Поле ListenQueue сервера (IdTCPServer) - как раз для этого. Допустим, я установлю его в 256. Что произойдёт, если попытается подключиться 257-й клиент ?  Полный игнор ?  Не вызовется даже OnConnect ?  Или возникнет исключение (если так, то где, в каком месте) ?  В любом случае мне нужно зафиксировать такую попытку соединения, чтобы сообщить при первой возможности использующей модуль программе.

4. В дочернем классе объявлен метод, одноимённый с методом родительского класса (ReadBuffer, WriteBuffer) без перегрузки. Как вызвать метод родительского класса из дочернего ?

5. Не совсем удобно получается с поиском или перебором клиентских данных с помощью цикла for. Дело в том, что в заголовке цикла указывается верхний предел, который получаем из ThreadList.LockList.Count. Но здесь происходит блокировка списка. Затем в теле цикла осуществляется доступ к клиентским данным (ThreadList.LockList.Items...). Каждая такая операция сопровождается очередной блокировкой. Сколько таких блокировок произойдёт внутри цикла - заранее неизвестно (придётся столько же раз разблокировывать). Единственный выход из ситуации - загнать в локальную переменную величину ThreadList.LockList.Count, разблокировать список (через finally), записать заголовок цикла с помощью этой локальной переменной, а в теле цикла каждую операцию доступа к списку ThreadList завершать (через finally) UnLock"ом.
При этом происходит загромождение кода этими try"ами и finally"ами (от 2 пар на 1 цикл for). Гораздо эффективнее было бы, например, если бы имелась возможность получать доступ к элементам списка неблокирующим способом (ну можно, конечно, дополнительно хранить указатель на список, получаемый через ThreadList.LockList.Items..., но это уже слишком). Тогда достаточно было бы заблокировать список (а точнее, объект ThreadList) перед циклом и разблокировать после него. А в заголовке и в теле цикла использовать неблокирующие операции доступа к списку.

6. Не совсем понятно поведение программы при попытке получения доступа к списку другим потоком (поток, который запер список, как я понял, может без проблем получить к этому списку доступ, даже если он его не разблокировал). Что в этот момент происходит - ожидание, когда список окажется разблокированным, или выбрасывается исключение ?  Или участок кода игнорируется ?
В коде у меня были ошибки подобного характера и не только, которые, к счастью, все исправлены. Намучался не хило, так что экспериментировать больше не хочется... :)

____________________________________________________________________
Сергей М. ©   (14.09.06 09:34) [89]
За образец класса, реализующего два независимых позиционных стрим-маркера (тек.позиция записи и тек.позиция чтения) можно было взять класс TDataBlock (sconnect.pas).

Да, можно и так. Но всё же оставлю прежний вариант с одним указателем.
(Всё равно придётся самому перемещать указатели)

Следующие высказывания эквивалентны по смыслу:
Содержимое переменной (параметра) типа pointer
a) содержит адрес неких данных некоего типа.
b) указывает на некие данные некоего типа
с) ссылается на некие данные некоего типа

Если в последнем пункте под словом "ссылается" понимается не ссылка на
данные, а указатель, то да, соглашусь.
Моё ИМХО:
В Object Pascal, как и в C, ссылки и указатели различаются следующим образом. По поведению в программе значением ссылки являются данные, а указателя - адрес, хотя на уровне реализации и те, и другие представлены адресами данных, на которые ссылаются/указывают.

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

Ну, в C компилятор тоже выдаст ошибку, если вместо указателя передать данные (имя массива там не относится к таким данным). А неоднозначностью передачу имени массива я не считаю, поскольку там нет таких случаев, где бы имя массива интерпретировывалось не как указатель на первый его элемент... :)

Кстати, понравилась такая запись нескольких действий в одну строку:

InputBuffer := get_client(Integer(id_queue.push(id_queue.pop))).InputBuffer;

(Сильно напоминает C++...)

____________________________________________________________________
Dmitrij_K   (12.09.06 22:35) [86]
В Classes.pas ищи TCustomMemoryStream.Read

Всё хорошо, только вот в режиме отладки в библиотечные функции не пускают... В release-варианте, наверное, скомпилен (Delphi)...
Да ещё по Ctrl в ReadBuffer пускают, а дальше - в Read (это который от TCustomMemory) - никак...


 
Сергей М. ©   (2006-09-15 09:07) [91]


> Cyrax ©   (14.09.06 23:14) [90]


1. Правильно сделал, что "понаставил .. Free".


> в этом случае будет вызываться Free базового класса (TMemoryStream.
> Free), который о моих Destroy"ях даже и не догадывается


Еще как догадывается.

2. TThreadList вполне подходит для организации очереди.
А если уж удобней использовать T[Object]Queue, придется организовать защиту обращений к очереди с пом. крит.секции (сделать класс TThread[Object]Queue по аналогии с TThreadList совсем не трудно)


> Что произойдёт, если попытается подключиться 257-й клиент
> ?


Клиент успешно подключится, но сервер втихомолку тут же сделает ему
disconnect, никаких событий и /или исключений сервер при этом не возбудит.


> 4. В дочернем классе объявлен метод, одноимённый с методом
> родительского класса (ReadBuffer, WriteBuffer) без перегрузки.
>  Как вызвать метод родительского класса из дочернего ?


К чему создавать себе трудности ?
Есть методы Read/Write, они виртуальные. Перекрой их в своем наследнике, тогда соотв.методы предка легко вызываются с пом. inherited.
Методы же ReadBuffer/WriteBuffer не виртуальные, и все что они делают - вызывают соответствующие вирт.методы Read/Write.


> 6. Не совсем понятно поведение программы при попытке получения
> доступа к списку другим потоком


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


 
Cyrax ©   (2006-09-18 21:34) [92]

Сергей М. ©   (15.09.06 09:07) [91]
> в этом случае будет вызываться Free базового класса (TMemoryStream.
> Free), который о моих Destroy"ях даже и не догадывается
Еще как догадывается.

Ну да...

2. Слепил свой класс TClientThreadQueue, унаследовав TQueue и TThreadList. Получил комбайн высшего класса. :)

3 Клиент успешно подключится, но сервер втихомолку тут же сделает ему
disconnect, никаких событий и /или исключений сервер при этом не возбудит.

Как тогда зафиксировать факт такой попытки ?

4. Я унаследовал класс TTCPServer от TIdTCPServer. Далее пытаюсь проинициализировать обработчики событий:
inherited OnExecute      := onexecute;
nherited OnConnect      := onconnect;
inherited OnDisconnect   := ondisconnect;
Почему после наследования приходится приписывать к ним inherited.
Без них - ошибка.

5. Почему, когда ты в производном классе определяешь конструктор и деструктор, при их вызове (а вернее, до и после, соответственно) автоматически не вызываются конструкторы и деструкторы базового класса ?

6. Как в Delphi перегрузить глобальную функцию ?

7. При организации приёма/передачи данных на сервере с помощью входных и выходных (для каждого клиента) буферов возникает "проблема" получения результата операции, поскольку сервер отправляет в буфер выходные данные, а сам занимается другими делами. А OnExecute"ы занимаются обработкой входных и выходных буферов. Так вот, смущает тот факт, что на сервере мы не получаем оперативной информации о выполнении операции отправки/приёма, а на клиенте (там уже наших входных/выходных буферов нет, OnExecute"ов нет) мы эту информацию получаем сразу по исключению при выполнении метода TIdTCPClient.Connection.ReadBuffer.
Хотелось бы, чтобы всё-таки и на сервере, и на клиенте это дело одинаково было. Поскольку в модуле мне приходится объявлять константу ConnectionError (в составе перечислимого типа TNetResult), которая будет формироваться только клиентом, тогда когда сервер тоже имеет дело с соединениями...

8. Отключаю сервер при подсоединённом клиенте. Для всех клиентов вызываются disconnect"ы. Тем не менее после вызова этих disconnect"ов генерируется исключение (именно из-за disconnected, 1-й параметр - true) на ReadFromStack (сейчас я эту ф-цию не использую, но вопрос стоит) в OnExecute, хотя в OnExecute вначале у меня стоит проверка на Connected.
Если OnDisconnect вызывается в том же потоке (т.е. одно вызывается после другого), что и OnExecute (что и должно быть), то как можно проскочить проверку на Connected вначале OnExecute ?

9. Не совсем понятен смысл свойства ListenQueue объекта класса TIdTCPServer (а именно "unresolved connections").

10. Задача такова. Нужно считать из стека TCP данные без блокировки. Если входных данных нет - не ждать пока они придут, вернуть 0. Если есть - вернуть их количество.
Так вот, ReadFromStack, если на входе нет данных, ждёт пока не придёт хотя бы 1 байт, после возвращает их количество. Можно задать timeout = 0, тогда сразу вернёт 0, если на входе нет данных.
Функция CurrentReadBuffer вызывает ReadFromStack, если Connected. Т.е. её поведение аналогично ReadFromStack. НО: вызывает она ReadFromStack с одним параметром - true. Т.е. 2-й параметр по ум. = -1 (ждать вечность, пока не придёт хотя бы 1 байт). Получается, если использовать функцию CurrentReadBuffer, то, если на входе нет ни байта, то будем ждать вечность. Как этого избежать, не используя ReadFromStack с нулевым вторым параметром.

11. Не могу понять, как можно получить данные по Pointer"у без приведения его к какому-нибудь типизированному указателю:
buffer^, где buffer типа Pointer...

12. Free можно вызвать даже через объект (котороый является указателем, т.к. объекты - это указатели), который = nil.
Парадокс...


 
Сергей М. ©   (2006-09-19 08:53) [93]


> Cyrax ©   (18.09.06 21:34) [92]


> Ну да...


Вот тебе и "ну да")


> Слепил свой класс TClientThreadQueue, унаследовав TQueue
> и TThreadList. Получил комбайн высшего класса


В Делфи у класса-наследник может быть только один класс-предок.
Думаю, тебя ждет шнобелевская премия, поскольку ты умудрился унаследовать аж два класса)
К тому же оба этих класса оперируют каждый своим отдельным списком)


> Как тогда зафиксировать факт такой попытки ?


А зачем ?


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


Потому что у тебя ошибка в декларации/реализации/использовании конструктора и деструктора "производного" класса (*)


> Почему после наследования приходится приписывать к ним inherited.


См. (*)


> Как в Delphi перегрузить глобальную функцию ?


Вот так:

function SomeFunction(SomeParams): SomeResultType; overload;
function SomeFunction(SomeOtherParams): SomeResultType; overload;


> 7.
> 8.


Ерунда какая-то ..
Иллюстрируй свою мысль в коде  ..


> 9.


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


> Функция CurrentReadBuffer вызывает ReadFromStack, если Connected.
>  Т.е. её поведение аналогично ReadFromStack. НО: вызывает
> она ReadFromStack с одним параметром - true


см. параметр метода TIdTCPClient.Connect()


> как можно получить данные по Pointer"у без приведения его
> к какому-нибудь типизированному указателю


Никак.


> Free можно вызвать даже через объект (котороый является
> указателем, т.к. объекты - это указатели), который = nil.
> Парадокс...


Никакого парадокса.

Читай справку:

Every object inherits a Destroy method (called a destructor
) from TObject. To destroy an object, however, you should call the Free method (also inherited from TObject), because Free checks for a nil reference before calling Destroy


 
Cyrax ©   (2006-09-20 09:56) [94]

>Сергей М. ©   (19.09.06 08:53) [93]
>> Cyrax ©   (18.09.06 21:34) [92]
>> Ну да...
>Вот тебе и "ну да")

Я хочу сказать, что в моём классе не объявлен Free, поэтому, вызывается Free базового класса, который ну просто никак не обязан шерстить своих потомков в поисках Destroy...

>> Слепил свой класс TClientThreadQueue, унаследовав TQueue
>> и TThreadList. Получил комбайн высшего класса

Я провёл цептографическое наследование классов с генерацией fusion-объектов...
Так-что проблема отдельных списков решена...

> Как тогда зафиксировать факт такой попытки ?
А зачем ?

Например, для информирования сервером оператора...

> Почему, когда ты в производном классе определяешь конструктор
> и деструктор, при их вызове (а вернее, до и после, соответственно)
> автоматически не вызываются конструкторы и деструкторы базового
> класса ?
Потому что у тебя ошибка в декларации/реализации/использовании конструктора и деструктора "производного" класса (*)

Я так не думаю...

> Почему после наследования приходится приписывать к ним inherited.
Ну уж не может быть так, чтобы всё наследовалесь от TIdTCPServer, а
OnExecute"ы и т.п. - нет...

> 7.
> 8.
Такие вопросы кодом сложно.

> 9.
Что конкретно не понятно ?

Какие соединения относятся к неразрешённым.. %-|

> Функция CurrentReadBuffer вызывает ReadFromStack, если Connected.
>  Т.е. её поведение аналогично ReadFromStack. НО: вызывает
> она ReadFromStack с одним параметром - true
см. параметр метода TIdTCPClient.Connect()

Это не есть 2-й параметр ReadFromStack. И это только для initiate connection.
Мне же нужно для операций чтения/записи...

>> как можно получить данные по Pointer"у без приведения его
>> к какому-нибудь типизированному указателю
>Никак.

Фрагмент корректно работающего кода:
...
buffer: Pointer;
...
AThread.Connection.ReadBuffer(buffer^,data_len);
...

Речь идёт о безошибочности операции разыменования...

Dmitrij_K   (12.09.06 22:35) [86]
В Classes.pas ищи TCustomMemoryStream.Read

Всё хорошо, только вот в режиме отладки в библиотечные функции не пускают... В release-варианте, наверное, скомпилен (Delphi)...
Да ещё по Ctrl в ReadBuffer пускают, а дальше - в Read (это который от TCustomMemory) - никак...


 
Dmitrij_K   (2006-09-20 10:53) [95]


> Всё хорошо, только вот в режиме отладки в библиотечные функции
> не пускают..

Project-Options-Compiler- On Use Bebug DCUs


 
Сергей М. ©   (2006-09-20 11:05) [96]


> в моём классе не объявлен Free, поэтому, вызывается Free
> базового класса, который ну просто никак не обязан шерстить
> своих потомков в поисках Destroy


Обязан.
Потому что Destroy - виртуальный метод.


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


Зачем серверу оператор ? Зачем оператору инф-ция об отверженном соединении ?


> Я так не думаю


Ну тогда продолжай в том же духе.


> Такие вопросы кодом сложно


Ты считаешь более простым объяснять тебе на пальцах неизвестно что и неизвестно почему происходящее в неизвестно каком коде ?


> Какие соединения относятся к неразрешённым


Те которые ждут акцептирования, т.е. потенциальные.


> Мне же нужно для операций чтения


Тогда см. св-во TIdTCPConnection.ReadTimeout


> Фрагмент корректно работающего кода:
> ...
> buffer: Pointer;
> ...
> AThread.Connection.ReadBuffer(buffer^,data_len);


Разыменование здесь нужно потому что переменная buffer объявлена именно как pointer, в то время как соотв.форм.параметр объявлен как нетипизированный и передаваемый по ссылке, а не по значению.

Конструкция buffer^ в этом случае означает предписание компилятору сгенерировать код, передающий фактическим параметром не адрес переменной buffer, а адрес, хранящийся в этой переменной.

Вот если бы ты объявил, к примеру,
buffer: array[...] of sometype
то тогда разыменование не потребовалось бы, потому что по ссылке будет передана сама переменная, в которую будет осуществляться чтрение, что собственно и требуется.


 
Сергей М. ©   (2006-09-20 14:08) [97]

По поводу Free ..

Если ты имеешь

TMyClass = class(...)
...
 destructor Destroy; override;
end;

TMyClass1 = class(TMyClass)
...
 destructor Destroy; override;
end;

TMyClass2 = class(TMyClass1)
...
 destructor Destroy; override;
end;

и создаешь объект класса TMyClass2

var MyObject: TMyClass;
MyObject := TMyClass2.Create(..);

то при вызове MyObject.Free (или непосредственно MyObject.Destroy, что несущественно в дан.случае) самым первым будет вызван метод TMyClass2.Destroy, поскольку этот метод перекрывает соотв.виртуальный метод предка, который точно так же перекрывает соотв.виртуальный метод своего предка, который... и так далее вплоть до TObject.Destroy, который собственно и объявлен как виртуальный метод, допускающий перекрытие (override) в своих потомках.


 
Cyrax ©   (2006-09-21 23:04) [98]

У меня механизм обработки входных данных следующий.
При инициализации клиента/сервера указывается процедура, которая будет вызываться, когда приходят данные. Вызов происходит в OnExecute соответствующего дочернего потока. При этом не допускается одновременных выполнений этой процедуры в нескольких потоках. Т.е. процедура должна полностью завершиться до начала следующего её вызова.
Имеем 2 варианта реализации вызова:
1. Делаем что-то типа lock"ов для чего-то...
2. Вызываем эту процедуру на выполнение в главный (первичный) поток с помощью synchronize(метод без парам). Но здесь возникает проблема: процедура у меня с параметрами. Как ето самое уделать ?

з.ыз. В случае 2 как раз и будет неявно организована очередь обработки запросов клиентов.


 
Сергей М. ©   (2006-09-22 08:17) [99]


> Вызов происходит в OnExecute соответствующего дочернего
> потока. При этом не допускается одновременных выполнений
> этой процедуры в нескольких потоках


Ну и нафиг тогда тебе Инди со всеми его наворотами ?
Индейский сервер изначально подразумевает его использование для параллельной обработки клиентских запросов.
Бзял бы обычный TServersocket или TTCPServer в режиме non-blocking - там события возбуждаются последовательно в одном-единственном потоке (например, в основном), и никакие заморочки с крит.секциями и прочей синхронизацией изобретать совершенно не нужно.


> synchronize(метод без парам). Но здесь возникает проблема:
>  процедура у меня с параметрами. Как ето самое уделать ?
>


Вот так:

TMyThread = class(TSomeThreadAncestor)
 ..
 FSomeParam: SomeType;
 ..
 procedure SomeSyncMethod;
 procedure SomeProc(SomeParam: SomeType);
end;

....

procedure TMyThread.SomeSyncMethod;
begin
  SomeProc(FSomeParam);
end;

...

FSomeParam := SomeValue;
Syncronize(SomeSyncMethod);


 
Cyrax ©   (2006-09-24 23:04) [100]

Сергей М. ©   (22.09.06 08:17) [99]
Ну и нафиг тогда тебе Инди со всеми его наворотами ?

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

11. Задача такова. Есть функция, которая принимает параметр типа PData.
Тип PData - какой-либо типизированный указатель, включая PChar.
При вызове функции в качестве фактического параметра передаётся PData(параметр_типа_Pointer). Но в случае с PChar генерируется статическая ошибка.
Как этого избежать ?  Почему PChar не приводится к Pointer ? Ведь это просто указатель на Char...
Дело в том, что среди возможных типов PData должен быть такой, чтобы доступ к элементам осуществлялся путём индексации (как в случае с PChar).

Обязан.
Потому что Destroy - виртуальный метод.

Тогда в своих деструкторах и конструкторах придётся самому вызывать деструкторы и конструкторы базового класса. Не очень то удобно...

Зачем серверу оператор ? Зачем оператору инф-ция об отверженном соединении ?

Допустим, сервер ведёт статистику, собирает и обрабатывает подобную (и не только) информацию. В любой момент можно быдет посмотреть, какие клиенты подключены, каким соединениям и когда было отказано и т.п.
Эта информация в любом случае не помешает...

>> Почему, когда ты в производном классе определяешь конструктор
>> и деструктор, при их вызове (а вернее, до и после, соответственно)
>> автоматически не вызываются конструкторы и деструкторы базового
>> класса ?
>Потому что у тебя ошибка в декларации/реализации/использовании
>конструктора и деструктора "производного" класса (*)

Я так не думаю...
>Ну тогда продолжай в том же духе.
А по моему, виртуальный метод не должен вызывать соответствующий метод базового класса...

Ты считаешь более простым объяснять тебе на пальцах неизвестно что и неизвестно почему происходящее в неизвестно каком коде ?

Дело в том, что для моделирования подобных ситуаций и представлении их в ярко выраженной форме потребуется потратить время на выдумывание такого кода. А в моей программе такие ситуации имеют место, но не в такой выраженной форме, чтобы только по коду можно было понять вопрос, да и части кода будут из разных мест программы + всё-равно потребуются письменные комментарии и разьяснения...

Те которые ждут акцептирования, т.е. потенциальные.

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

4. Я унаследовал класс TTCPServer от TIdTCPServer. Далее пытаюсь проинициализировать обработчики событий:
inherited OnExecute      := onexecute;
nherited OnConnect      := onconnect;
inherited OnDisconnect   := ondisconnect;
Почему после наследования приходится приписывать к ним inherited.
Без них - ошибка.

Разыменование здесь нужно потому что переменная buffer объявлена именно как pointer, в то время как соотв.форм.параметр объявлен как нетипизированный и передаваемый по ссылке, а не по значению.

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

12. Чем отличается тип Pointer от PVariant ?
13. И почему в сигнатуре методов пишут var имя_парам, а не var Vaiant. Т.е., фактически, пропускают тип параметра...


 
Сергей М. ©   (2006-09-25 08:13) [101]

Дискуссия перешла в русло абстрактной "болтологии" на тему Паскаля, к сетям не имеющей отношения.

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


 
Cyrax ©   (2006-10-09 23:14) [102]

Что-то я забросил ветку...
Всё-таки половина вопросов была по сетям.

1. Сейчас у меня такая ситуация. Клиент отсылает запросы серверу. 1 запрос - 1 прикладной пакет (10-30 байтов). Сервер поддерживает как SPX/IPX-протокол, так и TCP/IP. На стороне сервера, как только приходят данные (хотя бы 1 байт), вызывается функция, их обрабатывающая. Проверка появления входных данных на серверном сокете происходит в отдельном потоке в цикле OnExecute, т.е. практически непрерывно. В случае с TCP/IP наблюдается ситуация, когда эта функция получает блок данных, соответствующий нескольким запросам клиента (*). Запросы же клиент отсылает отдельно, друг за другом, с некоторыми промежутками. В случае с SPX/IPX функция обрабатывает входные данные отдельными прикладными пакетами, т.е. ситуации, когда функция вызывается после прихода на серверный сокет нескольких клиентских прикладных пакетов, не наблюдается... (для SPX/IPX вызывается другая функция, но выполняющая практически те же действия)
Конечно, такие ситуации в общем случае вполне могут быть. Но настораживает то, что проверка в OnExecute происходит практически непрерывно и появление на серверном сокете очередного прикладного пакета должно быть сразу же зафиксировано и вызвана соответствующая функция, не дожидаясь, пока придёт ещё один прикладной пакет...

з.ы. не объединяются ли прикладные пакеты в один IP-пакет (по "вине" ОС) на клиентской стороне (стороне отправителя), поскольку прикладной пакет намного меньше среднего размера IP-пакета ?
И какие задержки могут привести к таким результатам (*)

Рассматриваемые ситуации с TCP и IPX полностью идентичны за исключением протокола...

2. Какие проги можете посоветовать для слежки за передачей IP-пакетов в винде. Желательно небольшие, чтоб можно было закачать по dialup"у...


 
Ketmar ©   (2006-10-09 23:18) [103]

естественно, аккумулируется в буфере винсока.

Ethereal.


 
Cyrax ©   (2006-10-09 23:21) [104]

И как ету аккумуляцию придушить ?


 
Ketmar ©   (2006-10-09 23:32) [105]

а зачем? ну, положим, можно. но -- зачем? лучше код напиши правильно.


 
Cyrax ©   (2006-10-09 23:44) [106]

Ketmar ©   (09.10.06 23:32) [105]
а зачем? ну, положим, можно. но -- зачем? лучше код напиши правильно.

На основе процессоптеров чтоль...
У меня сервер, который принимает данные от клиента, всего лишь пересылает их аппаратуре, не влезая в прикладной протокол, чтобы разделять прикладные пакеты друг от друга...


 
Ketmar ©   (2006-10-10 00:16) [107]

ну и "перепосылай". или сделай разборщик на простом автомате. а буфера -- не трогай. их не зря сделали. %-)


 
Cyrax ©   (2006-10-10 08:04) [108]

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


 
Cyrax ©   (2006-10-10 08:07) [109]

И насчёт аккумуляции. До каких пор данные будут буферизоваться (до какого размера). И через какое время они начинают передаваться (если данных для буферизации больше не поступает)...


 
Сергей М. ©   (2006-10-10 08:49) [110]


> До каких пор данные будут буферизоваться (до какого размера)


Это определяется алгоритмом Нагеля.

см. SetSockOpt(TCP_NODELAY)


 
Cyrax ©   (2006-10-10 16:17) [111]

Не пашет:

const Id_SO_True: boolean = true;
     Id_SO_False: boolean = false;
begin
 Self.Socket.Binding.SetSockOpt(Id_SOL_SOCKET, Id_TCP_NODELAY, Char(@Id_SO_False), SizeOf(Id_SO_False));

Ошибка типа доступ по такому-то адресу...
Превым параметром ставил и Id_IPPROTO_TCP, и Id_IPPROTO_IP - то же самое...


 
Сергей М. ©   (2006-10-10 16:41) [112]


> по такому-то адресу


Вот по именно такому-то адресу и ищи ошибку.
см. Search -> Find Error ..

Надеюсь, вся эта катавасия у тебя происходит для гнезда-передатчика.


 
Cyrax ©   (2006-10-11 17:31) [113]

Надеюсь, вся эта катавасия у тебя происходит для гнезда-передатчика.

Да. Но этот же сокет передатчика является и сокетом приёмника - двусторонняя связь...

Вот по именно такому-то адресу и ищи ошибку.
см. Search -> Find Error ..

И что я должен высмотреть в этих регистрах и дампах...


 
Сергей М. ©   (2006-10-11 17:43) [114]


> Cyrax ©   (11.10.06 17:31) [113]



> этот же сокет передатчика является и сокетом приёмника


"Приемнику" алгоритм Нанеля по барабану - этот алгоритм актуален лишь для передающей стороны.


> что я должен высмотреть в этих регистрах и дампах


Справку читай, там все описано.


 
Cyrax ©   (2006-10-11 23:00) [115]

Справку читай, там все описано.

Уж не особо я полагаюсь на справку в Delphi...
Не особо ей доверяю...
Она не достаточно практична... Приходится рыться в инете.
(ошибаюсь ?)

Да и вообще, ассемблерным дебаггером в своё время активно пользовался.
В данном же случае меня интересует вопрос, касающийся ошибки...
Не думаю, что из Search -> Find Error что-то толковое выйдет. По-моему, достаточно вызова функции, которую я привёл выше, и парочку уточнений по моему модулю - и проблема будет решена (естественно, с помощью тех, кто работал с SetSockOpt)...


 
Сергей М. ©   (2006-10-12 08:28) [116]


> Приходится рыться в инете.
> (ошибаюсь ?)
>


В дан.случае ошибаешься.
Эта функциональность специфична именно для Delphi IDEб и где еще как не в "родной" справке к IDE искать ее описание ?


> Не думаю, что из Search -> Find Error что-то толковое выйдет


Если ошибка именно в твоем коде, Search -> Find Error покажет тебе строчку, при выполнении которой произошла эта ошибка.


 
Cyrax ©   (2006-10-15 19:15) [117]

При вводе адреса в моей программе, где произошла ошибка, ничего не происходит, окно "CPU" не открывается. Но по этому адресу мне делать нечего...
При вводе адреса, к которому произошло обращение (00000068), окно "СPU" открывается. Меня смущает этот адрес - 00000068...

const Id_SO_True: boolean = true;
       Id_SO_False: boolean = false;
begin
Self.Socket.Binding.SetSockOpt(Id_SOL_SOCKET, Id_TCP_NODELAY, PChar(@Id_SO_False), SizeOf(Id_SO_False));

Ну так кто-нибудь отключал буферизацию или нет !?
Если да, то как...


 
Ketmar ©   (2006-10-15 19:39) [118]

вот же прикопался к буферизации. чую, когда отключишь, будет пост типа "отключил. не помогло. что дальше?" %-)


 
Cyrax ©   (2006-10-15 20:09) [119]

Ketmar ©   (15.10.06 19:39) [118]
вот же прикопался к буферизации...

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

чую, когда отключишь, будет пост типа "отключил. не помогло. что дальше?"

Если не поможет, буду отслеживать движение IP-пакетов и проводить всякие извращенские опыты...


 
Ketmar ©   (2006-10-15 21:26) [120]

>[119] Cyrax(c) 15-Oct-2006, 20:09
>Если не поможет, буду отслеживать движение IP-пакетов и
>проводить всякие извращенские опыты...
ага. вместо чтобы написать простейший автомат... или по ходу обсуждения задача уже поменялась? лень перечитывать.


 
Cyrax ©   (2006-10-15 23:10) [121]

Задача никак не менялась.
Могу написать простейший автомат и реализовать его в своём модуле. Будет некоторая надстройка (реализованная только в моём модуле и не выходящая за его пределы) над прикладным протоколом (неизвестным в общем случае). Ещё подумаю...
Но это всё будет после того, как разберусь с текущим глюком с отключением буферизации... - это садомазо такое (похоже на ситуацию с языковой панелью, которую я всё-таки вытащил наружу)...


 
Ketmar ©   (2006-10-15 23:19) [122]

жениться бы вам, барин... (ц)


 
Сергей М. ©   (2006-10-16 08:33) [123]


> Cyrax ©   (15.10.06 19:15) [117]


> Self.Socket.Binding.SetSockOpt(Id_SOL_SOCKET, Id_TCP_NODELAY,
>  Char(@Id_SO_False), SizeOf(Id_SO_False));


Кто такой Self в данном контексте ?

Нагель отключается на уровне  Id_IPPROTO_TCP.
Справку-то когда читать наконец научимся ?


 
Cyrax ©   (2006-10-16 22:07) [124]

Ketmar ©   (15.10.06 23:19) [122]
жениться бы вам, барин... (ц)

Тогда останется один путь - повеситься...
Сейчас есть приоритеты и поважнее...

Сергей М. ©   (16.10.06 08:33) [123]
Кто такой Self в данном контексте ?

Ето наследник TIdTCPClient. Self я уже давно убрал. Он мне нужен был только чтоб добраться до SetSockOpt...

Нагель отключается на уровне  Id_IPPROTO_TCP.

И на уровне Id_IPPROTO_TCP отключал, и на уровне... в общем, все уровни перепробовал... Везде одна и та же ошибка...

Справку-то когда читать наконец научимся ?

Где в справке указано, что... Вообще, где в справке хоть что-то описано об этих уровнях. Например, всё, что написано про Id_IPPROTO_TCP:
Id_IPPROTO_TCP = IPPROTO_TCP;

Unit
IdStackConsts
Description
Id_IPPROTO_TCP

И так про все константы, используемые в SetSockOpt...

_________________________________________________________________
Кто-нибудь отключал буферизацию TCP-пакетов с помощью SetSockOpt ?


 
Ketmar ©   (2006-10-16 22:14) [125]

>[124] Cyrax(c) 16-Oct-2006, 22:07
>Кто-нибудь отключал буферизацию TCP-пакетов с помощью
>SetSockOpt ?
ну, я отключал. только как -- убей, не помню. и вспоминать лениво. давно было. где-то на "королевстве" была статья по работе с сокетами, там в том числе и об этом сказано.


 
Cyrax ©   (2006-10-16 23:15) [126]

Ketmar ©   (16.10.06 22:14) [125]
ну, я отключал.

Ну вот, самому ведь приходилось, а говоришь код пиши правильно, буфера не трогай... $\g

где-то на "королевстве" была статья...

паоищу.. Глядел какой-то форум по этой функции, но на C++ - на паскале не прокатывает...


 
Ketmar ©   (2006-10-16 23:35) [127]

>[126] Cyrax(c) 16-Oct-2006, 23:15
>Ну вот, самому ведь приходилось, а говоришь код пиши
>правильно, буфера не трогай...
я это делал "из спортивного интереса". в реальных задачах не приходилось ни разу.


 
Сергей М. ©   (2006-10-17 08:33) [128]


> Где в справке указано, что... Вообще, где в справке хоть
> что-то описано об этих уровнях


Не ту справку ты куришь.

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

Следует курить как минимум справку MS Windows Sockets 2 Reference (поставляется штатно), где сказано:

level = IPPROTO_TCP*

TCP_NODELAY BOOL Disables the Nagle algorithm for send coalescing.
* - included for backward compatibility with Windows Sockets 1.1  


BSD options not supported for setsockopt are:

...
TCP_NODELAY

The TCP_NODELAY option is specific to TCP/IP service providers. Enabling the TCP_NODELAY option disables the TCP Nagle Algorithm (and vice versa). The Nagle algorithm (described in RFC 896) is very effective in reducing the number of small packets sent by a host by essentially buffering send data if there is unacknowledged data already "in flight" or until a full-size packet can be sent ....

А еще лучше искать инф-цию в первоисточнике - MSDN.


> Везде одна и та же ошибка


В смысле "Access violation at address XXXXXXXX ...." ?

И чему равен XXXXXXXX ? Входит ли XXXXXXXX в диапазон адресов, распределенных под образ твоего модуля в ВАП процесса ?
Если входит, то в [117] - вранье, либо ты не умеешь пользоваться встроенным отладчиком.
Если не входит, то обязательно входит в диапазон адресов для образа какого-либо иного модуля, и эта инф-ция (т.е. какого конкретно модуля) не менее важна при поиске причин отказа.


 
Verg ©   (2006-10-17 09:49) [129]


> Отсоединяюсь я методом Disconnect (пробовал и DisconnectSocket).
>  При каждом очередном соединении клиента ОС выделяет ему
> новый порт. Если же задать порт клиента самому, то при повторном
> соединении возникает ошибка - порт то остался занятым...
>  
> С TIdTCPServer такой проблемы не возникает.
>
> Что самое интересное, пример работы с TIdTCPServer и TIdTCPClient
> (IdTCPDemo), скачанный с инета, показывает те же результаты.
>  
> На форумах читал, как обсуждали глюки некоторых компонентов
> Indy.
> Так вот, может это очередной глюк? Или отсоединяться (клиенту)
> нужно как то по-другому?


Это не от Indy.
см. http://book.itep.ru/4/44/tcp_443.htm
Состояние соединения TIME_WAIT.


 
Cyrax ©   (2006-10-18 11:20) [130]

Тут такая задача подвернулась. Надо извратиться и отправить широковещательный TCP-пакет для поиска сервера...
А вообще, нужно максимально быстро найти сервер (с неизвестным IP-адресом и неизвестным портом из известных диапазонов), пользуясь только протоколом TCP/IP...


 
Сергей М. ©   (2006-10-18 11:25) [131]


> нужно максимально быстро найти сервер



> пользуясь только протоколом TCP/IP...


Это что, блажь ? Чем это обосновано ? Чем UDP помешал ?


 
Cyrax ©   (2006-10-18 11:31) [132]

Так извращеннее всего...
Ну, модуль у меня TCP-шный, кроме того, сервер может не поддерживать UDP...


 
Сергей М. ©   (2006-10-18 11:52) [133]


> сервер может не поддерживать UDP


Что мешает реализовать в нем такую поддержку ?


 
Cyrax ©   (2006-10-18 12:11) [134]

Допустим, сервер работает на основе сторонних модулей (а не моего), не реализующих поддержку UDP...


 
Сергей М. ©   (2006-10-18 12:33) [135]

Да по барабану, на основе чего он там сейчас работает)
Если ты причастен к его разработке, то ты всегда волен реализовать сервер так как тебе нужно.


 
Cyrax ©   (2006-10-18 12:45) [136]

Если ты причастен к его разработке, то ты всегда волен реализовать сервер так как тебе нужно.

Ну, предположим, так...
И какой из всего этого можно сделать вывод ?

з.ы. Видно, вы никогда не отправляли групповые TCP-пакеты...


 
Сергей М. ©   (2006-10-18 12:52) [137]


> предположим, так...
> И какой из всего этого можно сделать вывод ?


реализуй в серверном приложении UDP-сервер на фикс.порту - он будет принимать UDP-бродкаст-запросы (простейшие в реализации !) клиентов и отвечать, на каком порту в дан.момент "слушает" соотв. TCP-сервер.


> вы никогда не отправляли групповые TCP-пакеты


Я много чего еще не делал)
А тебе посоветую не заниматься демагогией, а иззучить для начала хотя бы это

http://www.google.ru/search?q=Winsock+multicast&start=0&ie=utf-8&oe=utf-8&client=firefox-a&rls=org.mozilla:ru:official


 
Cyrax ©   (2006-10-19 21:24) [138]

Сергей М. ©   (18.10.06 12:52) [137]
реализуй в серверном приложении UDP-сервер на фикс.порту - он будет принимать UDP-бродкаст-запросы (простейшие в реализации !) клиентов и отвечать, на каком порту в дан.момент "слушает" соотв. TCP-сервер.

На фиксированном нельзя - может быть занят.
Если же брать диапазон - то придётся отправлять б-касты на все порты этого диапазона. Т.е., фактически клиент ищет UDP-сервер, отправляя б-касты на все порты диапазона, чтобы узнать IP-ник TCP-сервера...

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

А тебе посоветую не заниматься демагогией, а иззучить для начала хотя бы это
http://www.google.ru/search?q=Winsock+multicast&start=0&ie=utf-8&oe=utf-8&client=firefox-a&rls=org.mozilla:ru:official

Это всё, я так понимаю, для глубоких ламёров...
А что надо изучать ламёрам, пытающимся вырваться в юзёры ?.. :)

з.ы. Всё-таки TCP-мультикасту раскидать было бы проще...
Просто никто этого не хочет...
...или не может...


 
Сергей М. ©   (2006-10-20 08:36) [139]

http://citforum.utmn.ru/programming/delphi/sockets-2/#08


 
Lamer@fools.ua ©   (2006-10-20 08:56) [140]

>1. Как в винде посмотреть, какие порты в данный момент заняты и какими прогами (средствами винды, без программирования)

netstat — показывает активные соединения
Process Explorer (3rd party утилита) — показывает... много чего показывает :o)


 
Cyrax ©   (2006-10-23 23:38) [141]

У меня такая проблема.
Отсылаю широковещательный UDP-пакет серверу. Тот его принимает и отсылает клиенту ответ (TCP-порт). На клиенте я принимаю данные:
UDPBaseClient.RecieveData(buf,buflen). Но при выполнении этой команды выбрасывается исключение Socket error, не дожидаясь, пока пройдёт timeout мс, указанные в параметрах функции.
Пробовал принимать и через ReceiveString - то же самое...
Убрал отсылку ответа на сервере - то же самое...
Вообще убрал на клиенте отправку запроса серверу. Оставил только приём - всё нормально. Функция приёма ждёт timeout мс и успешно завершается безо всяких socket error...
Выходит, на клиенте при отправке запроса серверу (а эта операция происходит без ошибок) происходит что-то такое, что заставляет выбрасывать исключение на процедуре приёма данных на клиенте...
UDP-сервер у меня всегда активен при выполнении всех этих операций.

Теперь по поводу поиска сервера через UDP. 3 довода против такого подхода:
1. UDP-сервер никак нельзя сажать на заранее определённый порт - он может быть занят. Даже небольшой диапазон портов (10-20) в общем случае тоже может быть занят...
2. Если взять заранее определённый диапазон портов для UDP-сервера (скажем, 500 или 1000 номеров), то клиенту придётся их перебирать широковещательными UDP-пакетами. Здесь уже встаёт вопрос, стоит ли задействовать UDP, когда можно перебрать этот же диапазон портов (или чуть уже) TCP-пакетами, просто в N (число компов в локальной сети) раз дольше, чем в случае с UDP. Но если компов в локальной сети N немного, то поиск TCP-пакетами может оказаться ненамного дольше, чем поиск UDP-пакетами.
3. И вообще из этого может получиться следующее:
...покажу на примере живого анекдота про вирусную атаку:

Сижу на работе, отсылаю UDP-broadcast"ы по диапазону адресов. У одного начинает ругаться антивирус, потом у другого... Через некоторое время подходит начальство, говорит: "Ты Scada3 ?" - "Да". "От тебя вирусы лезут"...

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


 
Сергей М. ©   (2006-10-24 11:09) [142]


> выбрасывается исключение Socket error


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


> 1. UDP-сервер никак нельзя сажать на заранее определённый
> порт - он может быть занят. Даже небольшой диапазон портов
> (10-20) в общем случае тоже может быть занят


Это оговорено в ТЗ ?


> Но если компов в локальной сети N немного, то поиск TCP-
> пакетами может оказаться ненамного дольше, чем поиск UDP-
> пакетами


Выбирать тот или иной протокол и принимать решение придется в любом случае тебе, если это не оговорено в ТЗ.


> нужно, чтобы такого "вирусного" эффекта не было, когда клиент
> будет искать сервер


На то есть сетевой администратор - он обязан привести сет.настройки раб.станций в соответствие с особенностями и требованиями твоего софта.


> Способов организации взаимодействия компов в сети, к счастью,
>  не мало


Одна из них - NamedPipes, в условиях ЛВС имеющая ряд ощутимых преимуществ перед технологиями/механизмами, ориентированными на Internet.


 
Cyrax ©   (2006-10-24 11:57) [143]

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

Нет, мне надо всё быстро и сразу...
В справке есть эти коды ?

Это оговорено в ТЗ ?

В общем-то да...

Выбирать тот или иной протокол и принимать решение придется в любом случае тебе, если это не оговорено в ТЗ.

... а делиться опытом ?

На то есть сетевой администратор - он обязан привести сет.настройки раб.станций в соответствие с особенностями и требованиями твоего софта.

Какие именно настройки ?
Отслеживание антивирусами сканирования портов блокировать нежелательно...

Одна из них - NamedPipes, в условиях ЛВС имеющая ряд ощутимых преимуществ перед технологиями/механизмами, ориентированными на Internet.

Насчёт NamedPipes гляну...
А вот по поводу MailSlot"ов: целесообразно ли их использовать для поиска сервера ?


 
Сергей М. ©   (2006-10-24 12:40) [144]


> мне надо всё быстро и сразу


Ишь ты, О.Бендер нашелся) ...


> В справке есть эти коды ?


В справке есть все.


> В общем-то да...


Что значит "в общем-то" ?
Это, извини уж, похоже на "чуть-чуть беременна")
Сдается мне при этом, что ТЗ как такового не существует ..


> а делиться опытом ?


Делюсь - UDP быстр, но ненадежен, TCP надежен, но не быстр.


> Какие именно настройки ?


Профессионалу-сисадмину они д.б. известны.
И это вообще не твое (как разработчика) дело.
Твое дело - описать то что ты реализовал в ТР и представить его заказчику для согласования со специалистами по сет.администрированию.


> целесообразно ли их использовать для поиска сервера ?


Столь же целесообразно, сколь целесообразно использовать UDP - быстро, но ненадежно.


 
Anatoly Podgoretsky ©   (2006-10-24 13:18) [145]


> мне надо всё быстро и сразу...

Быстро только кошки родятся.


 
Cyrax ©   (2006-10-24 13:24) [146]

В справке есть все.

Гляну. Если что - буду ругаться...

Что значит "в общем-то" ?
Это, извини уж, похоже на "чуть-чуть беременна")
Сдается мне при этом, что ТЗ как такового не существует ..

Блин, ушли хрен знает куда... рожать...
Речь идёт о диапазоне адресов, с чего вопрос-то и начался. Ну так как быть с диапазоном портов для UDP-сервера...
Как вообще люди нормальный поиск сервера реализуют (самый распространённый вариант и чуть менее распространённые) ?

Делюсь - UDP быстр, но ненадежен, TCP надежен, но не быстр.

И это весь опыт-то ?

Профессионалу-сисадмину они д.б. известны.
И это вообще не твое (как разработчика) дело.
Твое дело - описать то что ты реализовал в ТР и представить его заказчику для согласования со специалистами по сет.администрированию.

Ну, допустим. С каким вопросом я должен обращаться к "специалистам по системному администрированию" - что-то вроде "Нужно как-то сделать что-то, чтобы антивирусники не ругались..." ?
Ну уж нет. Предпочитаю что-то поконкретнее...

Столь же целесообразно, сколь целесообразно использовать UDP - быстро, но ненадежно.

Зато перебирать порты не придётся... Чем не преимущество перед UDP ?
И вообще, обычно реализуют поиск сервера через MailSlot"ы ?


 
Anatoly Podgoretsky ©   (2006-10-24 14:24) [147]

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


 
Сергей М. ©   (2006-10-24 14:38) [148]


> Гляну. Если что - буду ругаться...
>


Только не говори потом, мол, искал и не нашел, потому что не туда, мол, смотрел)

Ты инженер или где ?)


> Блин, ушли хрен знает куда... рожать...


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


> как быть с диапазоном портов для UDP-сервера...
> Как вообще люди нормальный поиск сервера реализуют


Они его никак не реализуют.
Они , эти самые люди, говорят так: вот вам, к примеру, очередной шедевр в виде такого-то POP3-сервера, оной по таким-то известным соглашениям "сидит" на 110-м порту, а ежели у вас 110-й порт занят, то сервер можно посадить и на любой иной порт, но это уже ваши проблемы.


> С каким вопросом я должен обращаться к "специалистам по
> системному администрированию" - что-то вроде "Нужно как-
> то сделать что-то, чтобы антивирусники не ругались..." ?
>


Да какое тебе нафих дело до антивирусников и прочей не относящиейся к твоей компетенции софтовой тряхомудии ?)

Ты - разработчик, а не кто-то иной !
Да мало ли какая гадость на компе заказчика может мешать работе твоего софта ! В известный момент времени это его, заказчика, проблемы)

В ответ на ТЗ ты сотворил свое подробное ТР и отправил его на согласование представителю Заказчика. Заказчик же тебе, к примеру, вернул отлуп, мол, ТР не годится потому-то и потому-то. Далее ты волен отказаться от реализации проекта, потому как не знаешь иного решения, либо предложить это иное решение.


> Зато перебирать порты не придётся


Что тебя на портах заклинило ?)

Ты софт свой для ЛВС разрабатываешь или для глоб.сети ?
В ТЗ оговорена обязательная реализация трансп.подсистемы на базе IP или это твоя отсебячина ?

С чего ты взял, что в случае mailslots никаких "переборов" нет и в помине ?
"Ты суслика видишь ? И я не вижу. А он есть !" (С)


 
Anatoly Podgoretsky ©   (2006-10-24 15:10) [149]


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

Предлагаешь убить? Или все таки подчиняться корпоративным или другим правилам.
А то тут получается, какая то гадость постоянно сканирует сеть. Не так не живут в корпоративных сетях и убивается не на гадость которая мешает, а эта программа, как вредная для корпоративной сети, хорошо если автор выживет, а то улица широкая.


 
Сергей М. ©   (2006-10-24 15:15) [150]


> Предлагаешь убить?


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


 
Anatoly Podgoretsky ©   (2006-10-24 16:00) [151]


> Я предлагаю автору представить заказчику подробное ТР

Это обязательно и если заказчик подпишет, то посылать лесом со всеми претензиями по безопасности, можно будет и отменить, но работу все равно оплатить и отдельно оплатить переделку, но я бы на месте заказчика серьезно задумался бы над подобным и послал бы автора по дальше и далее в соотвествии с договорами.
А если эта штука делается не по заказу, то сам понимаешь куда надо автора посылать?

Пока я не вижу ни одного разумного довода для подобной реализации.


 
Сергей М. ©   (2006-10-24 16:06) [152]


> если эта штука делается не по заказу, то сам понимаешь куда
> надо автора посылать?


Конечно понимаю)

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


 
Anatoly Podgoretsky ©   (2006-10-24 16:17) [153]


> в "начинающие" - в самый раз

Моя ошибка, исправлю


 
Cyrax ©   (2006-10-24 19:03) [154]

Удалено модератором
Примечание: Обсуждение модерирования, а насчет угроз пока будут считать, что шутишь, но забанить всегда успеем


 
Leonid Troyanovsky ©   (2006-10-24 19:10) [155]


> Cyrax ©   (24.10.06 19:03) [154]

> Желательно перекинуть эту ветку назад, либо меня забаннить
> (только 2 варианта)...


В качестве третьего предлагаю и то и другое.

--
Regards, LVT.


 
Cyrax ©   (2006-10-24 19:44) [156]

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



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

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

Наверх





Память: 1.15 MB
Время: 0.062 c
15-1161405325
SerJaNT
2006-10-21 08:35
2006.11.12
Не могу писать и отвечать. RO?


2-1161187684
Благодарный слушатель
2006-10-18 20:08
2006.11.12
Помогите работать с TImage


4-1151253289
pizz_pizz
2006-06-25 20:34
2006.11.12
работа с сертификатами


15-1161803854
PHPDeveloper
2006-10-25 23:17
2006.11.12
payPal


6-1151011930
Rembo
2006-06-23 01:32
2006.11.12
WebBroker и post





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
Английский Французский Немецкий Итальянский Португальский Русский Испанский