Конференция "Сети" » Сервер на TIdTCPServer из Indy10 [D7, WinXP]
 
  • RGV © (06.09.11 18:06) [0]
    Доброе время суток!
    На работе потребовался Messager.
    Конечно можно было бы воспользоваться готовыми программами коих тысячи в нете, но мне захотелось попробовать сделать самому.
    На сервере должен быть список зарегистрированных пользователей типа так:

    TClientState = (csOffline,csOnLine,csOut,csDontDistrab,csInVisible,csUser);
    TClient = class(TObject)
       Nick,
       Login,
       Pass,
       ip         : string;
       State      : TClientState;
       Connection : TIdTCPConnection;
       Contacts   : TList;
     public
       Constructor Create;
       Destructor Destroy; override;
     end;

    var
     Clients:TList



    Кроме того существует список
    Действие клиента примерно следующие:
    1.соединение
    2.
     а) если  вход, то отправка логина и пароля
     б) если регистрация, то отправка рег.данных.
    4. обмен данными
    5. disconnect при закрытии формы или если клиент выбрал статус Offline

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

     THeader = record
       ProtocolVersion,
       Command,
       Encrypt,
       Reserved1:byte;
       Reserved2:Dword;
       BufSize:Dword;
     end;


    затем если необходимо посылаются какие то данные. размером в BufSize;

    Вот событие Execute на сервере:

    procedure TForm1.TCPExecute(AContext: TIdContext);
    var
     buffer:TidBytes;
     header:THeader;
     ErrMsg:string;
    begin
     try
       AContext.Connection.Socket.ReadBytes(Buffer,HeaderSize);
     except
       on e: Exception do
       begin
         if (e.message<>'Read timed out.') then
         begin
            //здесь планируется разорвать соединение, незнаю как
            //AContext.Connection.disconnect не разрывает связь.
         end;
         exit;
       end;
     end;
     header:=BytesToHeader(Buffer);
     if not ValidHeader(header,ErrMsg) then
     begin
       //в лог добавляем ошибку
       //здесь планируется разорвать соединени
       exit;
     end;
     if Acontext.Data=nil then//в AContext.data находится указатель на TClient если пользователь уже вошел.
     begin
         UnRegisterResponse(Header,AContext);
     end else
     if TObject(Acontext.Data) is TCLient then
     begin
         Response(TClient(Acontext.Data),Header);
     end;
    end;


    Вот здесь мне нужна помощь.
    Что в привиденном коде неверно?
    Насколько я понимаю процедуры UnRegisterResponse и Response проходят в контексте этого же потока.
    Возможно ли в них обращение к списку Clients (TList) без синхронизации?
    Как правильно завершить соединение с клиентом?

    Например в UnRegisterResponse проходит идентификация пользователя, т.е в цикле перебираются все TClient из списка Clients (TList) на проверку логин/пароль
    в случае, если логин/пароль неверны нужно послать сообщение пользователю и разорвать соединение.
    Заранее благодарен.
  • Медвежонок Пятачок © (06.09.11 20:13) [1]
    OnExecute обрабатывает конкретную нить конкретного подключения.
    Зачем и почему здесь потребовалось

    т.е в цикле перебираются все TClient из списка Clients (TList) на проверку логин/пароль ?

    PS дунотдисторб написано с ошибкой.
    PPS забей, займись чем-нибудь не столь бесполезным.
  • RGV © (07.09.11 01:14) [2]

    > OnExecute обрабатывает конкретную нить конкретного подключения.

    Это я понимаю.


    > Зачем и почему здесь потребовалось

    Не понял вопроса! Что потребовалось?
  • RGV © (07.09.11 07:49) [3]
    А можно ли в OnExecute читать не определенное количество байт, а столько, сколько в буфере есть?
  • Медвежонок Пятачок © (07.09.11 10:45) [4]
    т.е в цикле перебираются все TClient из списка Clients (TList) на проверку логин/пароль

    Вот это нахрена в OnExecute?
  • RGV © (07.09.11 12:24) [5]

    > Вот это нахрена в OnExecute?


    а как быть?
  • Медвежонок Пятачок © (07.09.11 13:15) [6]
    Убрать цикл
  • RGV © (07.09.11 13:28) [7]

    > Убрать цикл


    Я конечно извиняюсь, а как же тогда сделать идентификацию?


    > Вот это нахрена в OnExecute?

    А что тогда .. переводить всё в главный поток? тогда теряется многопоточность программы, если создавать отдельно поток, тогда теряется смысл блокирующего режима
  • Медвежонок Пятачок © (07.09.11 14:24) [8]
    Еще раз.
    OnExecute - он выполняется для каждого подключения.
    Кто-то подключился - у тебя онэкзекут.
    Кто-то еще подключился - у тебя еще один онэкзеккут.
    И каждый конкретный онэкзекут - он для конкретного подключения, конкретного клиента.

    Еще раз вопрос: Нахрена что-то там перебирать среди всех клиентов, если метод вызыван для конкретного, "вот этого" клиента?
  • Сергей М. © (07.09.11 16:58) [9]

    > Возможно ли в них обращение к списку Clients (TList) без
    > синхронизации?


    Невозможно если список может быть изменен в контексте любого иного потока.
  • RGV © (08.09.11 00:54) [10]

    > Еще раз вопрос: Нахрена что-то там перебирать среди всех
    > клиентов, если метод вызыван для конкретного, "вот этого"
    > клиента?


    Или я в танке или вы меня не до конца понимаете.
    естественно после идентификации при последующих обращенениях когда AContext.Data будет присвоен Указатель на TClient мне уже не нужна идентификация.

    А если пользователь только что присоединился и посылает логин и пароль, я же должен проверить логин и пароль, а для этого нужен перебор, может вы знаете другой метод идентификации.
  • Медвежонок Пятачок © (08.09.11 08:59) [11]
    То есть ты хочешь сказать, что у тебя учетные записи живут в листе в виде экземпляров TClient?
    И видимо лист грузится при старте?
    Тогда конечно, перебор нужен. Но естественно защищенный критической секцией.
  • Anatoly Podgoretsky © (08.09.11 09:34) [12]

    > RGV ©   (08.09.11 00:54) [10]

    Только зачем это делать перебором?
  • RGV © (08.09.11 10:44) [13]

    > То есть ты хочешь сказать, что у тебя учетные записи живут
    > в листе в виде экземпляров TClient?
    > И видимо лист грузится при старте?

    да.
    По другому не знаю как... Если подскажете буду весьма признателен.


    > Anatoly Podgoretsky ©   (08.09.11 09:34) [12]

    А как?  До меня пока не доходит ((

    Вобчем насколько я понял onExecute любое обращение (и чтение и запись) к объектам к которым могут иметь доступ одновременно несколько потоков надо осуществлять через Synchronize.   Вот так TidThread(AContext.Yarn).Synchronize(SomeProcedure) ?

    если необходимо послать другому клинту/клиентам сообщение надо
    как то так
    tcp.Contexts.LockList
     TidThread(AContext.Yarn).Synchronize(отправлять данные клиенту/клентам)
     Synchronize - потому что будет обращение к TClient в процедуре отправки
    tcp.Contexts.UnLockList.
    Я правильно понимаю?
  • Медвежонок Пятачок © (08.09.11 10:51) [14]
    если необходимо послать другому клинту/клиентам сообщение надо ....

    классическая ошибка.
    Если необходимо послать клиенту сообщение, надо начинать программировать не с сервера, а с клиента.

    Это клиент должен начинать белыми и запрашивать "нет ли для меня чего?"
    А сервер должен ему ответить есть или нет.

    не наоборот.

    Иначе может получиться так:

    Клиент что-то отправил серверу и ждет ответ на посылку.
    Сервер в это самое время решил что -то наотправлять клиентам.

    И клиент вместо ответа на одну конкретную команду получает от сервера ответ, никак не связанный с отправленным запросом.
    Причем клиент в полной уверенности, что сервер-то ему ответил на его запрос.
  • RGV © (08.09.11 10:59) [15]

    > Медвежонок Пятачок ©   (08.09.11 10:51) [14]

    Хм.... а как же тогда решить такую не хитрую задачу как отправка клиентом сообщения для других клиентов?

    По вашему выходит так:
    1 клиент A посылает клиенту Б сообщение
    2 сервер принял и куда то там сохранил, либо у TCLient делать какой то буфер сообщений и туда сохранять все сообщения.
    3 клиент (наверно по таймеру) спрашивает сервер "А нет ли для меня каких -нибудь сообщений) и тут сервер выдает ему весь буфер

    так?
  • Медвежонок Пятачок © (08.09.11 11:05) [16]
    Это называется "придумать адекватный протокол обмена"
    Без продуманного протокола у тебя ничего не получится.
  • Медвежонок Пятачок © (08.09.11 11:08) [17]
    Для начала можно попробовать свыкнуться с мыслью, что сервер - это обслуживатель запросов клиента.

    Нет клиентского запроса - сервер курит.
    Появился запрос - сервер его обслужил.
    И курит дальше.

    У сервера нет никакой инициативы.
  • RGV © (08.09.11 11:11) [18]
    Мда..... заставили вы меня репу почесать )

    И все же как бы вы решили такую задачу
    > RGV ©   (08.09.11 10:59) [15]
  • RGV © (08.09.11 11:12) [19]
    Мда..... заставили вы меня репу почесать )

    И все же как бы вы решили такую задачу
    > RGV ©   (08.09.11 10:59) [15]
  • RGV © (08.09.11 11:19) [20]

    > У сервера нет никакой инициативы.

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

    Неужели у клиентской программы добавлять таймер и по таймеру узнавать для него сообщения (от клиента, от сервера например об отключении какого то клиента и т.д), а на сервере для каждого клиента делать буфер сообений ?
  • Anatoly Podgoretsky © (08.09.11 11:29) [21]

    > Медвежонок Пятачок ©   (08.09.11 11:08) [17]

    Иначе это не сервер, а спамер.
  • Anatoly Podgoretsky © (08.09.11 11:31) [22]

    > RGV ©   (08.09.11 11:19) [20]

    Надо сменить идеологию, надо что бы клиент стал сервером, а сервер клиентом и всего делов. И никаких таймеров
  • RGV © (08.09.11 11:41) [23]

    > Anatoly Podgoretsky ©   (08.09.11 11:31) [22]


    Издеваетесь?
    А можно по подробней про идеологию?

    Вот бы на исходничек взглянуть "правильного" клиента, и "правильного" сервера, разумеется на Indy.
  • RGV © (08.09.11 11:56) [24]

    > Anatoly Podgoretsky ©   (08.09.11 11:31) [22]

    Я понимаю, что ломать парадигму задача не тривиальная,  все ж таки попробуйте мне объяснить эту вашу идеологию.
  • Anatoly Podgoretsky © (08.09.11 12:03) [25]
    > RGV  (08.09.2011 11:41:23)  [23]

    Считай сам как хочешь, желание что либо отвечать совсем пропало.
  • RGV © (08.09.11 12:06) [26]

    > Anatoly Podgoretsky ©   (08.09.11 12:03) [25]


    Я прошу прощения, если чем вас обидел.
  • DVM © (08.09.11 23:18) [27]

    > Для начала можно попробовать свыкнуться с мыслью, что сервер
    > - это обслуживатель запросов клиента.
    >
    > Нет клиентского запроса - сервер курит.

    Не всегда так. В ряде случаев долбать сервер постоянным опросом со стороны клиентов - накладно слишком, поэтому сервер может уведомлять клиентов сам по собственной инициативе о событиях. Стандартная практика.
    Например, видел такое в интерфейсе Asterisk, в Mailru тоже вроде.


    > Клиент что-то отправил серверу и ждет ответ на посылку.
    > Сервер в это самое время решил что -то наотправлять клиентам.
    >
    >
    > И клиент вместо ответа на одну конкретную команду получает
    > от сервера ответ, никак не связанный с отправленным запросом.
    >

    Каждая посылка серверу должна снабжаться неким уникальным идентификатором, сервер в своих ответах тоже должен приводить тот же идентификатор. Тогда будет ясно какой ответ на какой запрос был.
    Необходимость в последовательной обработке команд отпадает.
    Такой асинхронный протокол обмена тоже часто используется. Для разного рода чатов подходит идеально.
  • DVM © (08.09.11 23:24) [28]
    Т.е в такой асинхронной схеме существует 3 типа посылок:

    1. Команды клиента. Требуют ответа сервера.
    2. Ответы сервера. Высылаются в ответ на команды клиента.
    3. События сервера. Высылаются клиенту. Ответ на них не требуется.
  • DVM © (08.09.11 23:25) [29]
    Да, и Indy для описанного мной не очень подходит. Нужно что-то асинхронное и неблокирующее в идеале. Событийная модель используется.
  • RGV © (09.09.11 04:23) [30]

    > DVM ©   (08.09.11 23:25) [29]

     Спасибо.
     TServerSocket, TClientSocket для этих целей может подойти?
     synapse?
  • DVM © (09.09.11 10:38) [31]

    > RGV ©   (09.09.11 04:23) [30]

    ICS лучше всего
  • Медвежонок Пятачок © (09.09.11 10:47) [32]
    Не всегда так. В ряде случаев долбать сервер постоянным опросом со стороны клиентов - накладно слишком,

    Чем накладно-то?
    У него постоянный коннект клиента и сервер держит его сессию все время пока клиент не выйдет.
    Вся накладность - в количестве байтов гоняемых по сети.
  • DVM © (09.09.11 10:56) [33]

    > Медвежонок Пятачок ©   (09.09.11 10:47) [32]


    > Чем накладно-то?


    > Вся накладность - в количестве байтов гоняемых по сети.

    Сам же ответил. Данные будут гоняться, даже если нет для клиента никаких сообщений. Если клиентов мало - это ерунда конечно, но представь если таким образом сервер будет обслуживать 1000 клиентов. Значительная часть процессорного времени будет уходить только на ответы клиентам что данных нет (причем в большинстве случаев бестолковые). Туда же будет уходить и трафик (иногда критично, например, в случае GPRS). Зачем это все? Не проще ли переломить себя и наделить небольшой инициативой сервер?

    Да, это несколько отличается от поведения классического сервера, но этам модель классического сервера пришла из юникс среды, где все в основном синхронное и блокирующее, так там проще было и прижилось и стало догмой.
  • DVM © (09.09.11 11:01) [34]
    Собственно не зря же придумано множество расширений к стандартным серверам для поддержки так называемых push технологий, когда данные высылаются клиентам по мере их появления, а не по запросам. Типичный пример почта на смартфонах Blackberry. Смартфон всегда на связи с сервером, но постоянных запросов не делает ибо накладно - трафик, сервер сам уведомляет клиента когда данные пришли.
  • Медвежонок Пятачок © (09.09.11 11:18) [35]
    но представь если таким образом сервер будет обслуживать 1000 клиентов.

    Прикинь, не могу.
    В этом кокретном случае.
    Я даже не могу представить, что на предприятии автора когда-то внедрят эту подпорку (если он таки сделает первую альфу)
    Так что ни о каких тысячах речь не идет.
    Речь идет об обмене с его младшей сестрой.
    Пять минут после написания проги.
    Затем сестра снова уйдет во вконтакт со словами "не приставай ко мне со своей ерундой".

    Но так как чел "хочет научиться" - то здесь как раз тот самый случай когда сервер не должен ничего делать кроме обслуживания запросов.
  • DVM © (09.09.11 11:35) [36]

    > Медвежонок Пятачок ©   (09.09.11 11:18) [35]


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

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

    В другой ситуации возможно надо придерживаться схемы запрос-ответ.
  • Anatoly Podgoretsky © (09.09.11 11:57) [37]
    > DVM  (09.09.2011 10:56:33)  [33]

    Да и постоянное подключение тоже в некоторых случаях накладно, когда
    почасовое подключение.
  • Anatoly Podgoretsky © (09.09.11 11:58) [38]
    > DVM  (09.09.2011 11:01:34)  [34]

    Так делается это методом замены ролей, клиент становится сервером и
    наоборот, но автор это не воспринимает.
  • RGV © (10.09.11 03:46) [39]

    > Anatoly Podgoretsky ©   (09.09.11 11:58) [38]


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

    В моем понимании сервер - это некая программа которая в ожидании входящих соединений, клиент - это программа которая "подключается" к серверу, и весь обмен информацией между клиентами идет через сервер.
  • RGV © (10.09.11 06:41) [40]

    > RGV ©   (10.09.11 03:46) [39]

    Может не совсем грамотно выразился, но думаю смысл понятен.


    > DVM ©   (09.09.11 10:38) [31]

    Пытаюсь разобраться в работе TWSocketServer из ICS
    там есть такое свойство MultiThreaded, она включает многопоточность? Т.е все события у TWSocketClient вызываются из их собственного потока? и скорее всего эти события вызываются без Synchroninize.

    Спасибо за вашу помощь.
  • Eraser © (10.09.11 16:06) [41]
    > [29] DVM ©   (08.09.11 23:25)

    очень даже подходит, для клиентов уж точно. для сервера тоже подойдет, если клиентов не тысячи. а касаемо асинхронности - просто нужно четко разделить работу с сетью и остальную логику работы программы, в т.ч. UI. соединение должно быть в отдельном потоке и работать с очередью сообщений, посылаемых из UI и остальной части программы.
  • DVM © (10.09.11 16:29) [42]

    > Eraser ©   (10.09.11 16:06) [41]


    > очень даже подходит

    Выделять цельный поток для каждого из, скажем, 1000 клиентов, большинство из которых 99.99% времени ничего не передают - расточительно.


    > RGV ©   (10.09.11 06:41) [40]

    Там же вроде примеры есть? Многопоточность тебе не нужна.
  • Eraser © (10.09.11 19:46) [43]
    > [42] DVM ©   (10.09.11 16:29)

    если сервер расчитан на тысчи клиентов, то там ни Indy, ни ICS не подойдет. А для клиентов очень даже подходит.
  • DVM © (10.09.11 20:21) [44]

    > Eraser ©


    > если сервер расчитан на тысчи клиентов, то там ни Indy,
    > ни ICS не подойдет.

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


    > А для клиентов очень даже подходит.
    >
    >

    Да подойти то подойдет, но самый примитивный вариант асинхронных сокетов на сообщениях для клиента чата проще в реализации и синхронизация не нужна с интерфейсом.
  • Anatoly Podgoretsky © (10.09.11 21:15) [45]
    > Eraser  (10.09.2011 19:46:43)  [43]

    ICS нормально справляется с десятками тысяч клиентов
  • Eraser © (10.09.11 22:42) [46]
    > [44] DVM ©   (10.09.11 20:21)


    > [45] Anatoly Podgoretsky ©   (10.09.11 21:15)

    обычно просто середины не бывает. либо сервер обслуживает несколько десятков клиентов и не больше, либо сервер должен потянуть и десятки тысяч. в этом случае да - только порты завершения. В ICS увы не используются. Вот если бы Indy или ICS написали хотя бы простенький сервер на базе IOCP - цены бы им не было. Есть неплохой компонент, автором которого является наш форумщик, но к компоненту есть ряд вопросов.
  • DVM © (10.09.11 23:21) [47]

    > Eraser ©   (10.09.11 22:42) [46]


    > Есть неплохой компонент, автором которого является наш форумщик,
    >  но к компоненту есть ряд вопросов.

    Этот?

    THPServerSocket
    Author: Sergey N. Naberegnyh
  • Eraser © (11.09.11 03:07) [48]
    этот.
  • RGV © (11.09.11 08:33) [49]
    Спасибо за советы и помощь.
 
Конференция "Сети" » Сервер на TIdTCPServer из Indy10 [D7, WinXP]
Есть новые Нет новых   [134435   +18][b:0.001][p:0.003]