![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Проблемы при работе с Indy [D7, WinXP]
Cyrax © (2006-08-26 18:46:00)Столкнулся я с такой проблемой. При использовании компонента 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.
Cyrax (2006-09-11 09:37:00) [81]Сергей М. © (11.09.06 08:25) [75]
Ну и зачем при этом серверу знать размер данных "на входе" ?
Чтобы не блокировать поток, поскольку в OnExecute происходит не только чтение, но и отправка. Попытаемся, например, считать 4Кб, а будет всего 3 - поток заблокируется и мы не сможем отправлять данные, пока не придёт ещё 1 Кб...
Сколько реально будет прочитано
Вот это и нужно знать - сколько их там, байтов...
Сергей М. (2006-09-11 08:25:00) [82]
> сервер принимает всё, что пришло и пересылает без модификации
> аппаратуре. И не разбирает П1-пакеты
Ну и зачем при этом серверу знать размер данных "на входе" ?
Читий равными блоками, например, по 4К. Сколько реально будет прочитано, столько и записывай "на выход".
Cyrax (2006-09-08 23:13:00) [83]...спутал с делфийским модулем...
Dmitrij_K (2006-09-08 22:24:00) [84]библиотеки winsock2.dll не существует
indy работает с WS2_32.DLL
Cyrax (2006-09-08 22:15:00) [85]Т.е. почему бы не использовать winsock2.dll, с которым работает Indy...
Dmitrij_K (2006-09-08 19:57:00) [86]
> А шо ето разнит winsock2.dll
Шо? Я не понимать.
Cyrax (2006-09-08 18:00:00) [87]По поводу ППИО. У меня сервер является промежуточным звеном между клиентами и аппаратурой. Клиенты посылают запросы серверу по TCP/IP, сервер отсылает эти запросы аппаратуре через последовательный порт, затем принимает ответ от аппаратуры и пересылает его нужному клиенту.
ППИО (назовём его П1) организован только между клиентами и аппаратурой, а сервер является лишь промежуточным звеном между ними, т.е. сервер принимает всё, что пришло и пересылает без модификации аппаратуре. И не разбирает П1-пакеты.
Можно, конечно, и "заставить" сервер разбирать П1-пакеты (в моём модуле). Тогда можно будет принимать и отсылать П1-пакеты, например по-одному (и не нужно будет определять число байт на входе). Но в этом случае теряется универсальность модуля из-за ориентации на ППИО П1.
____________________________
> Dmitrij_K (06.09.06 23:08) [63]
> ws2_32.dll
А шо ето разнит winsock2.dll ?
Сергей М. (2006-09-08 10:09:00) [88]
> эти запоры что - накапливаются ?
Для каждой создаваемой крит.секции система создает соотв.сч-к блокировок ("запоров" в твоей терминологии).
Логика работы блокировки примерно такова:
при lock сч-к проверяется на равенство 0, и если равен, то сч-к инкрементируется (поток "захватывает" крит.секцию), в противном случае поток блокируется до момента обнаружения обнуления сч-ка.
Каждый unlock, выполненный захватившим секцию потоком, декрементирует сч-к.
> Если эти запоры накапливаются, то UnLock делать придётся
> для каждого запора. А если запоров много ? - колоссальное
> загромождение кода try'ами и finally'ами
Расставлять "запоры" следует с умом, а не везде где попало, типа "на всякий пожарный". Разумеется, избыток "запоров" не приведет к хорошему.
> в OnExecute у меня происходит сначала отправка данных из
> выходного буфера соответствующего клиента по сети и приём
> данних из сети во входной буфер етого же клиента
И это абсолютно нормальная логика взаимодействия сервера с клиентом, "запрос - ответ", "запрос - ответ" ....
Совершенно очевидно, что сторона, пославшая запрос, должна ждать ответа партнера по соединению столь долго, сколь это предусмотрено прикладнымпротоколом инф.обмена (ППИО), и до этих пор не пытаться отправлять очередные запросы.
Тем же самым ППИО должно быть оговорено, как должны разделяться передаваемые/принимаемые инф.пакеты в сплошном потоке приема/передачи:
1. пакеты предваряются фиксированным (например, 4 байта, содержащие Integer-значение) префиксом размера следующего за ним пакета (если размер пакета не фиксирован протоколом и может варьироваться)
2. пакеты разделяются специальной сигнатурной последовательностью фикс.размера (например, нулевой байт, являющийся символом-терминатором пакета, содержащего строковые данные в ANSI-кодировке)
Если ты в своем ППИО предусмотрел вышеизложенное, то знать размер данных, доступных в очереди приема, совершенно не обязательно, о чем я тебе и говорил.
Cyrax (2006-09-08 08:44:00) [89]Интересная вещь получается. Я в первичном потоке два раза запираю 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-07 10:59:00) [90]
> Получается в делфе 2 типа указателей: указатель на объект,
> который реализован как ссылка (TSysClient) (или это константный
> указатель ? [*]), и указатель (допустима адресная арифметика),
> содержащий адрес переменной или объекта.
В Делфи любая переменная типа класс представлена в памяти как указатель.
Содержимое этой переменной есть адрес фактического местонахождения объекта соответствующего класса.
Cyrax (2006-09-07 10:33:00) [91]> Ну не класс, наверное, а экземпляр (объект) класса 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 09:33:00) [92]Извиняюсь, самое главное забыл:
var
SysClient: TSysClient; //локальная переменная !
..
SysClient := TSysClient.Create(..); // создаешь объект
with SysClient do begin
.. здесь устанавливаешь св-ва объекта SysClient
end;
AThread.Data := SysClient; //ассоциируешь объект с транспортной нитью клиента
Сергей М. (2006-09-07 08:37:00) [93]
> 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;
Dmitrij_K (2006-09-06 23:08:00) [94]
> ioctlsocket(IdTCPClient1.Socket.Binding.Handle, FIONREAD,
> j);в j кол-во принятых байт но еще не прочитанных из сокетаЧто
> это за функция. Из какой библиотеки ?
ws2_32.dll
Cyrax (2006-09-06 22:50:00) [95]Да это всё начальство. Так что подумать придётся...
Впрочем, можно его и переубедить. Главное - убедительно аргументировать...
Раньше у них был модуль, работающий через 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 кол-во принятых байт но еще не прочитанных из сокета
Что это за функция. Из какой библиотеки ?
Сергей М. (2006-09-06 12:59:00) [96]
> Cyrax © (06.09.06 12:50) [60]
Зачем основному потоку ждать результат отправки сообщения ?
Зачем основному потоку ждать приема сообщения ?
Ему что, нечем более заняться ?
Cyrax (2006-09-06 12:50:00) [97]Сергей М. © (05.09.06 10:06) [54]
Я:
> Нужно, чтобы эти функции сразу же возвращали результат (код
> ошибки), а для этого нужно подождать, пока выполниться операция
> чтения/записи в соответствующем потоке
> Остановился пока на варианте ждать в цикле, пока не завершится
> операция в потоке
Плохой вариант.
если ты это серьёзно, то не подскажешь другой...
Cyrax (2006-09-05 13:06:00) [98]:)...
lol
Сергей М. (2006-09-05 12:53:00) [99]
> Да-да
Ну-ну)
Cyrax (2006-09-05 12:33:00) [100]Да-да...