-
Спасайте люди добрые :)
пишу клиент-серверное приложение, клиенты общаются с сервером нормально, но требуется переодически сохранять данные о каждом юзере в базу данных,
проблема в следующем- по таймеру 4 раза в минуту выполняется код:
list:=serv.Contexts.LockList;
for i:=0 to list.count-1 do
begin
q.SQL.Text:='update ClientTable set XXX=:dd where id=:id;';
q.params[0].asinteger:=TMyData(TIdContext(List.Items[i]).Data).xxx;
q.params[1].asinteger:=TMyData(TIdContext(List.Items[i]).Data).id;
q.ExecSQL;
end;
serv.Contexts.UnlockList;
это работает если клиент один.... или подключился второй.. третий и т.д... до тех пор пока один из них не отключится. свойство count выдает непонятное число .. оно иногда не меняется при отключении юзера, просто connection помечается как неактивное.
А иногда при отключении второго юзера (из трех) count становится равным 2, а третий юзер на второе место не передвигается... и моя процедура по таймеру пытается постоянно писать в базу данных данные про неактивного второго юзера, а про третьего забывает..
-
> xxx
А это что за порно ?
-
есть блок данных в который записывается id из базы данных о приконнекченом юзере и еще некоторая инфа.... эта инфа переодически меняется клиентским соединением.... и её надо переодически сохранять в базу данных ... если вы знаете более интересный способ, то предложите
-
этот блок данных сохраняется в памяти (структура TMyData) у каждого нового коннекта есть указатель на данные items[i].Data.. вот эти данные в цикле и надо сохранять, так как они очень важные , и ждать, пока клиент через пару дней отвалится... и по дисконнекту сохранять данные не логично...
-
> при отключении второго юзера (из трех) count становится
> равным 2, а третий юзер на второе место не передвигается
С чего он передвинется-то, если ты сделал LockList списку коннектов ?
-
да нет, вы меня не поняли... клиент отключается гораздо раньше чем возникает эта процедура по таймеру, ставил таймер .. раз в 60 секунд.. все равно непонятки возникают. а как бы вы посоветовали считать данные о всех активных подключениях ?
-
> клиент отключается
А как этот фиксирует твой сервер ?
-
в обработчике ондисконнект я вывожу в лог кто отключился... и больше ничего не делаю.. может в этом и ошибка
-
еще при отключении юзера нужно в базу данных сохранить последние актуальные данные, тут надо использовать походу пул коннектов к базе данных, это я еще пока незнаю как сделать
-
> третий юзер на второе место не передвигается
Чкм это подтверждается ?
-
в этом же цикле я добавляю имя клиента в Memo .. и вижу то, что выводится только первый и второй, который уже отключился... а до третьего дело не доходит, так как count уже=2
-
memo1.lines.clear;
q.SQL.Text:='update ClientTable set XXX=:dd where id=:id;';//вынес из цикла
for i:=0 to list.count-1 do
begin
q.params[0].asinteger:=TMyData(TIdContext(List.Items[i]).Data).xxx;
q.params[1].asinteger:=TMyData(TIdContext(List.Items[i]).Data).id;
q.ExecSQL;
Memo1.lines.add(q.params[0].asinteger);
end;
-
А оно, это имя, откуда берется ?
Из данных, ссылка на которые ты хранишь в Data ?
Тогда это значит что данные об "имени" клиента не соответствуют действительности
-
именно там и храню, а где их хранить ?? когда создается подключение создается нить... к ней прикрепляется кусок данных, я создаю экземпляр в памяти.. и храню там данные.. когда происходит дисконнект , я очищаю память
-
TMyData = class //индивидуальные данные каждого коннекта
public
id:integer;//
logg:boolean;//
xxx : integer;
Name : string;
end;
-
> когда создается подключение создается нить
На сервере только что случился факт подключения, клиент еще НИЧЕГО не сказал серверу, а сервер уже знает имя клиента ?
Чудеса)..
-
procedure TForm1.servConnect(AContext: TIdContext);
begin
memo1.Lines.Add('Connect='+Acontext.Connection.Socket.Binding.PeerIP+' \ '+DateTimeToStr(now));
acontext.Data:=TmyData.Create;
Tmydata(AContext.Data).logg:=false;
Tmydata(AContext.Data).name:='еще не авторизован';
end;
procedure TForm1.servDisconnect(AContext: TIdContext);
begin
memo1.Lines.Add('Disconnect='+Acontext.Connection.Socket.Host);
end;
это две процедуры когда клиент коннектится, и отключается соответсвенно... в дисконнекте экземпляр данных не уничтожаю.. порылся в исходниках ,. вроде компонента сама удаляет экземпляр данных если он существует ( serv: TIdCmdTCPServer;)
-
нет, авторизация происходит следом за коннектом.. если такого юзера нет.. сразу отключение
-
это обработка команды сервера logging user password
procedure TForm1.servCommandHandlers0Command(ASender: TIdCommand);
begin
q.SQL.Text:='select ID,XXX,B from client where nikname=:ni and pass=:pa';
q.Params[0].Value:=asender.Params[0];
q.Params[1].Value:=asender.Params[1];
q.Open;
if not q.Eof then
begin
asender.Response.Add('USERDATA '+intToStr(Q.Fields[1].value)+' '+intToStr(q.Fields[2].value));
Tmydata(asender.Context.Data).id:=Q.Fields[0].value;
Tmydata(asender.Context.Data).xxx:=Q.Fields[1].value;
Tmydata(asender.Context.Data).logg:=true;
memo1.Lines.Add('Login '+asender.Params[0]+'='+Tmydata(asender.Context.Data).ip);
end else
begin// user not found
asender.Response.Add('USERNULL 0');
end;
end;
-
> в дисконнекте экземпляр данных не уничтожаю.. порылся в
> исходниках ,. вроде компонента сама удаляет экземпляр данных
> если он существует
Как это "сама удаляет" ?
Св-во Data - это нетипизированный указатель, откуда серверу знать что за ним кроется и как это "удалять" ?
-
> Как это "сама удаляет" ?
> Св-во Data - это нетипизированный указатель, откуда серверу
> знать что за ним кроется и как это "удалять" ?
>
>
согласен с вами :) , память почищу при дисконнекте методом destroy если не ошибаюсь, но думаю проблему это не решит
-
> TMyData = class //индивидуальные данные каждого коннекта
> public
> id:integer;//
> logg:boolean;//
> xxx : integer;
> Name : string; // а это что ?
> end;
-
procedure TForm1.servCommandHandlers0Command(ASender: TIdCommand);
//Name это имя юзера оно заполняется при авторизации
Tmydata(asender.Context.Data).name:=asender.Params[0];
-
в [18] приведен полный код обработчика команды.
Что-то я в упор не вижу там ни единого обращения к св-ву Name ..
-
procedure TForm1.servCommandHandlers0Command(ASender: TIdCommand);
begin
q.SQL.Text:='select ID,XXX,B from client where nikname=:ni and pass=:pa';
q.Params[0].Value:=asender.Params[0];
q.Params[1].Value:=asender.Params[1];
q.Open;
if not q.Eof then
begin
asender.Response.Add('USERDATA '+intToStr(Q.Fields[1].value)+' '+intToStr(q.Fields[2].value));
Tmydata(asender.Context.Data).id:=Q.Fields[0].value;
Tmydata(asender.Context.Data).xxx:=Q.Fields[1].value;
Tmydata(asender.Context.Data).logg:=true;
Tmydata(asender.Context.Data).name:=asender.Params[0]; // это было добавлено чуть позже... а скопировалось из старого кода
memo1.Lines.Add('Login '+asender.Params[0]+'='+Tmydata(asender.Context.Data).ip);
end else
begin// user not found
asender.Response.Add('USERNULL 0');
end;
end;
-
> в этом же цикле я добавляю имя клиента в Memo
for i:=0 to list.count-1 do
begin
q.params[0].asinteger:=TMyData(TIdContext(List.Items[i]).Data).xxx; // !!!
...
Memo1.lines.add(q.params[0].asinteger); // какое же это "имя клиента", если ты САМ сказал что имя клиента хранится в св-ве Name, а не в порно-свойстве ?
end;
-
Memo1.lines.add(q.params[0].asinteger) - это дописывал от руки (полного кода с собой нет, поэтому ошибся
Memo1.lines.add(TMyData(TIdContext(List.Items[i]).Data).name); //это отладочная строка, потом будет удалена..
-
т.е. ты утверждаешь что значения порносвойства xxx и св-ва Name в каждом объекте класса TMyData одинаковы и содердат имя клиента ?
-
for i:=0 to list.count-1 do
begin
q.params[0].asinteger:=TMyData(TIdContext(List.Items[i]).Data).xxx; //это порносвойство будет хранить количество коннектов :) этого клиента
q.params[1].asinteger:=TMyData(TIdContext(List.Items[i]).Data).id;
q.ExecSQL;
Memo1.lines.add(TMyData(TIdContext(List.Items[i]).Data).name); //это отладочная строка, потом будет удалена..
end;
xxx и name даже разного типа и содержат разную инфу
-
а где у тебя в обработчике servDisconnect() протоколирование имени отключившегося клиента ?
не вижу ..
-
в [8] писал, что не сделал этого еще, но пока это не важно... я сам знаю, что я отключил Васю, почему Count не уменьшается вот это не понятно
-
при отключении юзера ставить флаг неактивности в его блоке данных.
при обработке листа учитывать значение флага.
локлист и анлок лист поместить в try/finally
-
> я сам знаю, что я отключил Васю
А я вот не знаю что отключил ты именно Васю.
Предлагаешь верить тебе на слово и прочим чудесам ?)
> почему Count не уменьшается
Как это не уменьшается, если
> подключился второй.. третий
> при отключении второго юзера (из трех) count становится равным 2
?
И не уменьшаться он НЕ может, если неопровержим факт возникновения события сервера OnDisconnect
-
> Медвежонок Пятачок © (13.10.11 09:16) [31]
>
> при отключении юзера ставить флаг неактивности в его блоке
> данных.
Он нафих не нужен - с тем же успехом для этого достаточно проанализировать LockedList.Connection.Connected.
-
> Медвежонок Пятачок
К тому же автор утверждает что 2-й клиент из 3-х активных отключился еще при царе горохе - аж час назад,- и в течение часа список контекстов якобы не уменьшился.. Якобы как был равен 3-м, так и остался .. А этого не м.б. если при отключении 2-го клиента на сервере гарантированно возникло и было успешно обработано событие OnDisconnect.
Потому что
procedure TIdContext.AfterRun;
begin
if Assigned(OnAfterRun) then begin
OnAfterRun(Self);
end;
if FContextList <> nil then begin
FContextList.Remove(Self);
end;
end;
-
> требуется переодически сохранять данные о каждом юзере в
> базу данных
Непонятная вообще затея..
Для каждого коннекта сервер в ходе его обслуживания возбуждает кучу всякоразных событий, в контексте обработки которых можно легко и без выкрутасов с общим списком коннектов отреагировать на изменение состояния контекста этого коннекта.
-
вот весь код который работает также... ничего не поменялось, исправил все что советовали, список юзеров вывел в отдельный листбокс, в мемо1 идет лог коннектов и дисконнектов с отображением имени клиента
ЧТО ТУТ НЕ ТАК ?
procedure TForm1.servConnect(AContext: TIdContext);
begin
memo1.Lines.Add('Connect='+Acontext.Connection.Socket.Binding.PeerIP);
acontext.Data:=TmyData.Create;
Tmydata(AContext.Data).logg:=false;
Tmydata(AContext.Data).name:='-';
end;
procedure TForm1.servDisconnect(AContext: TIdContext);
begin
memo1.Lines.Add(Tmydata(AContext.Data).name+' Disconnect='+Acontext.Connection.Socket.Binding.PeerIP+' / '+DateTimeToStr(now));
Tmydata(AContext.Data).Free;
AContext.Data:=nil;
end;
procedure TForm1.servCommandHandlers0Command(ASender: TIdCommand);
begin
q.SQL.Text:='select ID,XXX,B from client where nikname=:ni and pass=:pa';
q.Params[0].Value:=asender.Params[0];
q.Params[1].Value:=asender.Params[1];
q.Open;
if not q.Eof then
begin
asender.Response.Add('USERDATA '+intToStr(Q.Fields[1].value)+' '+intToStr(q.Fields[2].value));
Tmydata(asender.Context.Data).id:=Q.Fields[0].value;
Tmydata(asender.Context.Data).xxx:=Q.Fields[1].value;
Tmydata(asender.Context.Data).logg:=true;
memo1.Lines.Add(asender.Params[0]+' = Login');
Tmydata(asender.Context.Data).name:=asender.Params[0];
end else
begin// user not found
asender.Response.Add('USERNULL 0');
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var i,l,p,verrr:integer;
f:double;
begin
lb1.items.Clear;
if serv.Contexts <> nil then
with serv.Contexts.LockList do try
begin
label1.Caption:=inttostr(count); //здесь на форме смотрю сколько активных юзеров сейчас
q.SQL.Text:='update client set XXX=:dd where id=:id;';
if count>0 then
for i:=0 to count-1 do
begin
if TIdContext(Items[i]).Connection.Connected then
begin
q.params[0].asinteger:=TMyData(TIdContext(Items[i]).Data).rur;
q.params[1].asinteger:=TMyData(TIdContext(Items[i]).Data).id;
q.ExecSQL;
lb1.Items.Add(TMyData(TIdContext(Items[i]).Data).name+' '+inttostr(q.params[0].asinteger));
end else lb1.Items.Add('off '+TMyData(TIdContext(Items[i]).Data).name+' '+inttostr(q.params[0].asinteger));
end;
end;
finally
serv.Contexts.UnlockList;
end;
trans.Commit;//завершаем транзакцию
end;
-
Полные тексты протоколов приведи ..
-
LOGGIN username password //команда авторизации
QUIT // disconnect
:) остальное пока не используется приводить смысла нет, да и остальной протокол еще в разработке
-
ответ на LOGGIN
USERDATA xxx // если авторизация успешна
USERNULL 0 // если авторизация не прошла
-
таймер здесь не нужен.
даже для таких странных желаний, что присходят у тебя в онтаймере.
-
вот зачем апдейтить чей-то XXX по таймеру значениями из его TMyData?
этих изменений может и не быть, однако же лочится средлист и делается принудительный апдейт всех.
у тебя процессору делать нечего или в комнате холодно и ты так греешся?
-
согласен, что могу по событию клиента писать в базу данных чтолибо, переделаю, мне нужно, чтобы я на сервере видел, кто сейчас на связи из клиентов, убираю весь код из таймера кроме вывода списка клиентов в листбокс... все равно не работает
-
коннектимся под моим ником... gekanaz, в листбокс добавляется имя по таймеру...
отключаемся... в листбоксе меняется надпись на "off gekanaz"
цепляемся под другим ником... Dima... в лисбоксе ничего не меняется... count =1
затем отключаемся и только после этого видим "off Dima"
procedure TForm1.Timer1Timer(Sender: TObject);
begin
lb1.items.Clear;
if serv.Contexts <> nil then
with serv.Contexts.LockList do try
begin
label1.Caption:=inttostr(count); //здесь на форме смотрю сколько активных юзеров сейчас
if count>0 then
for i:=0 to count-1 do
begin
if TIdContext(Items[i]).Connection.Connected then
lb1.Items.Add(TMyData(TIdContext(Items[i]).Data).name+' '+inttostr(q.params[0].asinteger))
else lb1.Items.Add('off '+TMyData(TIdContext(Items[i]).Data).name+' '+inttostr(q.params[0].asinteger));
end;
end;
finally
serv.Contexts.UnlockList;
end;
end;
-
мне нужно, чтобы я на сервере видел, кто сейчас на связи из клиентов
для этого есть список клиентов и есть онконнект и ондисконнект.
таймер здесь лишний
-
коннектимся под моим ником... gekanaz, в листбокс добавляется имя по таймеру...
отключаемся... в листбоксе меняется надпись на "off gekanaz"
цепляемся под другим ником... Dima... в лисбоксе ничего не меняется... count =1
затем отключаемся и только после этого видим "off Dima"
убери таймер.
-
то есть вы предлагаете по онконнекту в свою структуру данных добавлять нового клиента,,, а по дисконнекту его оттуда удалять ?? да еще и потокобезопасно... зачем изобретать велосипед, когда список уже есть у компонента TIDCMDTCPServer.
-
таймер убери.
-
если просто убрать таймер, тогда получится что два клиента одновременно могут коннектиться.. а листбокс это потоконебезопасный класс... и будет не гуд
-
убери таймер.
а потокобезопасность решается через сендмессадж.
-
Зато TThreadList потокобезопасный. И про какой протокол речь?
-
протокол самописный, экземпляр TThreadList я создам и буду туда при коннекте и дисконнекте добавлять и удалять оттуда юзеров... но непонятно зачем его писать если он есть в LockList-e
-
LockList это не лист а метод
-
это метод, с помощью которого я могу получить список активных клиентов ?? или нет.. это уже просто дело принципа.. разобраться.. то как это сделать
-
если дело принципа - то в одиночку.
а здесь обычно помогают в разработке.
-
Понял, спасибо за помощь, просто непонятно, зачем он тогда нужен :)
-
> убираю весь код из таймера
Вместе с таймером, который нафих не нужен. Ни с какого боку не нужен.
Тебе надо посмотреть в произвольный момент времени кто у тебя онлайн и под каким соусом ?
Пройдись по списку контекстов, отбрасывая элементов Connection.Connected = False, бери свое св-во Data для каждого элемента - вот тебе и список активных клиентов.
-
> это метод, с помощью которого я могу получить список активных
> клиентов ?
Это функуиональный метод, блокирующий доступ к списку активных контекстов (а не клиентов !!!!) со стороны других нитей сервера и возвращающий ссылку на этот список.
-
> Пройдись по списку контекстов, отбрасывая элементов Connection.
> Connected = False, бери свое св-во Data для каждого элемента
> - вот тебе и список активных клиентов.
>
with serv.Contexts.LockList do try
for i:=0 to count-1 do
begin
if TIdContext(Items[i]).Connection.Connected then
lb1.Items.Add(TMyData(TIdContext(Items[i]).Data).name)
end;
finally serv.Contexts.UnlockList; end;
вот это я и пытался сделать... или тут есть ошибка ?
-
> тут есть ошибка ?
Тут ошибки нет.
-
Ошибка здесь дизайнерская. И она в том, что через пять минут после отладки тебе снова захочется прикрутить к этому коду таймер.
-
да нет.. таймер мне не нужен, а этот цикл не выдает список всех активных клиентов :(
-
> тот цикл не выдает список всех активных клиентов
А чего он выдает ?)
Ты соизволишь наконец привести реальные данные протоколов отладочного логирования событий на сервере ?
-
написал класс потокобезопасный там храню копию данных из TMyData, по событию коннекта или дисконнекта обновляю список в листбоксе, все работает.
TO Сергей М.
а цикл который пробегается по локлисту выдает все тоже самое:
connect Вася
Connect Петя
Connect Дима
цикл выдает всех трех юзеров...
делаем дисконнект пети ( то есть он сам отключается)
и теперь цикл выдает
Вася (коннектед)
бывший петя (connected=false) то есть цикл теперь проходит от 1 до 2 и выдает только васю.. так как петя уже не на связи, предполагаю что на 3 месте в листе есть и дима но имея число коннектов 2 до него дело не доходит
-
между
> делаем дисконнект пети ( то есть он сам отключается)
и
> и теперь цикл выдает
должно произойти и быть полностью и успешно обработанным событие OnDisconnect с Петиным контекстом.
Онго было ?
-
а разве в OnDisconnect с контекстом чего-то надо делать ?, сейчас пока только пишется в лог.. что этот клиент отконнектился... , я порылся в примерах там , вроде ничего не делают, только в лог пишут и все
-
> разве в OnDisconnect с контекстом чего-то надо делать ?
А как же !?
Клиент же отвалился, объект Data (читай - твоё прикладное расширение контекста существовавшего до этого коннекта), описывающий параметры клиента (пусть даже и не залогинившегося), более не актуален, его следует тут же в обработчике дисконнекта и уничтожить, потому что после возврата из OnDisconnect индейский объект-контекст будет разрушен, причем в ходе своего разрушения он удалит сам себя из указанного ему списка контекстов (см. [34])
-
Вместо разреженых массивов, в данном случае используются какие либо списки. Достаточно посмотреть примеры по ICS они все построены на списков и объекты идентифицируются без каких либо дополнительных ИД
-
> GekaNaz (13.10.2011 14:08:03) [63]
Если connected=false, то почему же ты его не удалил из списка.
-
> Anatoly Podgoretsky © (13.10.11 16:43) [68]
> почему же ты его не удалил из списка.
зачем его удалять-то, если см. [34] ?