Indy 10 файл-сервер работает но както....

 
0
 
C++
ava
ochelot | 11.04.2013, 15:39
Смысл кода в передаче файлов по сети от клиентов (в будущем с последующем доступом для клиентов (кому можно а кому нет)...)


<client>       <server>

-> user        <- user Ok
-> sendfile    <- ok; file name
-> 11.mkv    <- ok; file current
-> данные    <- next
....
-> закончили <- ok
-> exit             <- exit ok.

Т.е. в этом коде мы предполагаем что для обработки команд мы сначала отправляем user
и входим в стадию обработки команд для user (в будущем проверка на права).
И поле отправки мы остаемся и ожидаем следующую команду, только после exit вызываем Disconnect.

Но не пойму по всем описаниям в методе void __fastcall TForm1::ServerExecute(TIdContext *AContext)
мы не должны делать внутри цикл, для получения команд (типа пока нет Disconnect(a) Execute постоянно вызывается для клиента).
Но если убрать цикл то сервер обрабатывает первую команду и на остальные уже не реагирует т.е. клиент отправил user получил ответ,  после отправил exit и сервер ни как не отреагировал.

Вопрос почему с циклом работает а без нет?  ???
Можно ли систему команд и по другому реализовать без цикла

:Server

#define READ_BLOCK_SIZE 16384
void __fastcall TForm1::ServerExecute(TIdContext *AContext)
{
if(AContext->Connection->IOHandler->InputBufferIsEmpty())
       {
           AContext->Connection->IOHandler->CheckForDataOnSource(1);
           if (AContext->Connection->IOHandler->InputBufferIsEmpty()) return;
       }


  AnsiString Msg = AContext->Connection->IOHandler->ReadLn().LowerCase();
  if (Msg == "user")
   {
    AContext->Connection->IOHandler->WriteLn("user ok");

    while(1) // BEZ ETOGO CHIKLA RABOTAT NE BUDET
    {
      Msg = AContext->Connection->IOHandler->ReadLn().LowerCase();

      if (Msg == "exit") {AContext->Connection->IOHandler->WriteLn("exit ok.");break; }
      else
      if (Msg == "sendfile")
        {
           AContext->Connection->IOHandler->WriteLn("ok; file name");
           AnsiString FileName = AContext->Connection->IOHandler->ReadLn();
           SetCurrentDir(ExtractFileDir(Application->ExeName)); 
           FileName = ExpandFileName(FileName); 
           AnsiString FilePath = ExtractFileDir(FileName); 
           ForceDirectories(FilePath);
           AContext->Connection->IOHandler->WriteLn("ok; file current");

           int FileHandle = open(FileName.c_str(), O_CREAT | O_TRUNC | O_BINARY | O_RDWR, S_IREAD | S_IWRITE);

           TMemoryStream *Stream = new TMemoryStream();

           while (1)
           {
              Stream->Position = 0;
              Stream->SetSize(0);
              AContext->Connection->IOHandler->ReadStream(Stream);

              write(FileHandle, Stream->Memory, Stream->Size);

              if (Stream->Size >= READ_BLOCK_SIZE)
               {
                  AContext->Connection->IOHandler->WriteLn("next");
                  continue;
               }
              if (Stream->Size < READ_BLOCK_SIZE)
               {
                  AContext->Connection->IOHandler->WriteLn("ok");
                  break;
               }
           }

          delete Stream;
          close(FileHandle);
       }
      else
       AContext->Connection->IOHandler->WriteLn("error command");
    }
     AContext->Connection->Disconnect();
    }
}
Comments (32)
ava
Avazart | 12.04.2013, 15:05 #
Цитата


Вопрос почему с циклом работает а без нет?  ???


Ну а как бы почему должно работать?

Для каждого клиента  создается свой поток TIdThread наследованый от TThread, как я понимаю Execute() это ф-ция каждого потока. А значит и обработка должна вестись аналогично TThread по условию


while( <условие>)
{


}
ava
ochelot | 12.04.2013, 15:40 #
Цитата (Avazart @ 12.4.2013,  15:05)
Цитата



Вопрос почему с циклом работает а без нет?  ???




Ну а как бы почему должно работать?



Для каждого клиента  создается свой поток TIdThread наследованый от TThread, как я понимаю Execute() это ф-ция каждого потока. А значит и обработка должна вестись аналогично TThread по условию





while( <условие>)

{





}


Т.е. без цикла, при подключении клиента создается поток в котором вызывается Execute, выполняет его и по завершению закрывает поток (но не обрывает соединение (Disconnect)) и после Execute не вызывается, тем самым игнорируя последующие сообщения от клиента?
Я думал что поток завершается когда происходит Disconnect...
ava
Avazart | 12.04.2013, 17:38 #
Цитата


Т.е. без цикла, при подключении клиента создается поток в котором вызывается Execute, выполняет его и по завершению закрывает поток (но не обрывает соединение (Disconnect)) и после Execute не вызывается, тем самым игнорируя последующие сообщения от клиента?


А  что соединение не разрывается ?
Последующие сообщения недоходят ибо надо заного установить соединение... 
ava
ochelot | 12.04.2013, 19:33 #
Цитата (Avazart @ 12.4.2013,  17:38)
Цитата



Т.е. без цикла, при подключении клиента создается поток в котором вызывается Execute, выполняет его и по завершению закрывает поток (но не обрывает соединение (Disconnect)) и после Execute не вызывается, тем самым игнорируя последующие сообщения от клиента?




А  что соединение не разрывается ?

Последующие сообщения недоходят ибо надо заного установить соединение...

Ну во всяком случае событие TidTCPServer OnDisconnected не срабатывает если только вручную Disconected не вызвать. Следовательно он как бы не отключается.
А вообще где можно узнать как эти механизмы работают?

p.s. Читал Indy deep - так там как то поверхностно коснулись и то не 10 версии ИНДИ 
ava
Avazart | 12.04.2013, 20:56 #
Документация и Исходники ...

Indy depf там только в общем ...

Надо пробовать... попробую отпишусь...
ava
Avazart | 12.04.2013, 23:04 #
Если заглянуть и в другие обработчики  событий компонента

//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPServer1Connect(TIdContext *AContext)
{

}
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPServer1Execute(TIdContext *AContext)
{

}
//---------------------------------------------------------------------------
void __fastcall TForm2::IdTCPServer1Disconnect(TIdContext *AContext)
{

}
//---------------------------------------------------------------------------

Вероятно именно поэтому  нужно делать Disconnect() в ручную.
ava
ochelot | 13.04.2013, 09:41 #
Вот именно, вызывается OnExecute, проверяется соединение (CheckConnection) и снова вызывается OnExecute, и такой цикл для каждого клиента в отдельном потоке.
В моем примере приходится вставлять в OnExecute еще цикл по условию  smile ...

В примере из книги OnExecute состоит из
procedure TformMain.IdTCPServer1Execute(AThread: TIdPeerThread);

а в Builder с indy 10 OnExecute состоит из
void __fastcall TForm1::ServerExecute(TIdContext *AContext)

отличия в параметрах...
Я так понимаю в книге описывается indy 9 а мы используем 10 версию... говорят они кардинально отличаются...

Могут ли у них и механизмы работы, что в диаграмме из книги работать по другому?
ava
ochelot | 13.04.2013, 10:15 #
Вот пример для indy 10 из официальной документации и вроде по описанию, диаграмма работы примерно такая же...


TMyForm.MyServerExecute(AContext: TIdContext);
var
lCmd: string;
begin
lCmd := Trim(AContext.Connection.IOHandler.ReadLn); if AnsiSameText(lCmd, 'HELP') then
begin
          AContext.Connection.IOHandler.WriteLn('HELP');
          AContext.Connection.IOHandler.WriteLn('QUIT');
          AContext.Connection.IOHandler.WriteLn('GETTIMESTAMP');
          AContext.Connection.IOHandler.WriteLn('');
end
else if AnsiSameText(lCmd, 'QUIT') then begin
          AContext.Connection.IOHandler.WriteLn('Goodbye...');
          AContext.Connection.IOHandler.WriteLn('');
          AContext.Connection.Disconnect;
end
else if AnsiSameText(lCmd, 'GETTIMESTAMP') then begin
          AContext.Connection.IOHandler.WriteLn(
            FormatDateTime(Now, 'yyyy-mm-ddThh:nn:ss.zzz'));
AContext.Connection.IOHandler.WriteLn(''); end;
end;

ava
Avazart | 13.04.2013, 15:32 #
Цитата


В моем примере приходится вставлять в OnExecute еще цикл по условию 


Да скорее всего у вас код не верный, читаете записываете неправильно ...
ava
Avazart | 13.04.2013, 17:57 #
Вот мой пример сервера на скорую руку :
( без обработки команд, всегда отвечает клиенту "what?", добавил лог серверу но как-то криво вроде)
Код C++


#include <IdSync.hpp>
//---------------------------------------------------------------------------
class TMyNotify: public TIdNotify
{
    String FLine;

    virtual void __fastcall DoNotify()
     {
       Form1->Memo1->Lines->Add(FLine);
     }

  public:
    __fastcall TMyNotify(String Line):TIdNotify(),FLine(Line) {}
};
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
   IdTCPServer1->DefaultPort= 2013;
   IdTCPServer1->Active= true;

   IdTCPServer1->Intercept= IdServerInterceptLogEvent1;
   IdServerInterceptLogEvent1->LogTime= true;
   IdServerInterceptLogEvent1->ReplaceCRLF= false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)
{
   AContext->Connection->IOHandler->WriteLnRFC("What ?");
   String Line=  AContext->Connection->IOHandler->ReadLn(); // Line не используем - у нас есть лог для просмотра.
}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdServerInterceptLogEvent1LogString(TIdServerInterceptLogEvent *ASender,
          const UnicodeString AText)
{
  (new TMyNotify(AText))->Notify();
}
//---------------------------------------------------------------------------





ava
ochelot | 14.04.2013, 10:58 #
Цитата (Avazart @ 13.4.2013,  17:57)
Вот мой пример сервера на скорую руку :

( без обработки команд, всегда отвечает клиенту "what?", добавил лог серверу но как-то криво вроде)

Код C++




#include <IdSync.hpp>

//---------------------------------------------------------------------------

class TMyNotify: public TIdNotify

{
  String FLine;


  virtual void __fastcall DoNotify()
   {
     Form1->Memo1->Lines->Add(FLine);
   }


   public:
  __fastcall TMyNotify(String Line):TIdNotify(),FLine(Line) {}

};

//---------------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender)

{
    IdTCPServer1->DefaultPort= 2013;
    IdTCPServer1->Active= true;


    IdTCPServer1->Intercept= IdServerInterceptLogEvent1;
    IdServerInterceptLogEvent1->LogTime= true;
    IdServerInterceptLogEvent1->ReplaceCRLF= false;

}

//---------------------------------------------------------------------------

void __fastcall TForm1::IdTCPServer1Execute(TIdContext *AContext)

{
    AContext->Connection->IOHandler->WriteLnRFC("What ?");
    String Line=  AContext->Connection->IOHandler->ReadLn(); // Line не используем - у нас есть лог для просмотра.

}

//---------------------------------------------------------------------------

void __fastcall TForm1::IdServerInterceptLogEvent1LogString(TIdServerInterceptLogEvent *ASender,
     const UnicodeString AText)

{
   // Судя из написанного в книге нужна синхронизация с основным потоком, а для сервера лучше всего подходит через IdNotify
   (new TMyNotify(AText))->Notify(); // в общем дикая запись, пытаться освобождать память не надо - само должно удалиться.

}

//---------------------------------------------------------------------------




Да, Вы правы... пробовал Ваш пример.. работает...  smile 

Возникли вопросики  smile 
1. А для чего нужен TIdNotify?

2. Почему порт выдает при коннекте не 2013 (как порт сервера) а в логе пишется что то типа

127.0.0.1:2177 Recv 14.04.2013 11:51:22: USER

127.0.0.1:2177 Stat Disconnected.

0.0.0.0:0 Stat Disconnected.

127.0.0.1:2180 Stat Connected.

3. Как можно организовать проверку пользователя типа отправили user XXX и далее можно остальные команды и что по новой проверка только после Disconnect?

4.Какие потенциальные ошибки могут всплыть в моем примере при использовании множеством клиентов?
ava
Avazart | 14.04.2013, 16:36 #
1. Читайте комментарии в коде - для синхронизации...
2. Возможно потому что 2177,2180  это не порт,  а возможно потому что порт настраивается как то не так.
3. В книге рекомендуется использовать не TIdTCPServer, а TIdCmdTCPServer  он удобнее для команд ...
4. Я не знаю код код писал наугад ...
ava
ochelot | 15.04.2013, 10:42 #
Спасибо за ответы  smile 

А все таки если использовать OnExecute для верификации клиента, как теоретически организовать процедуру?

При соединении клиент отправляет USER ivanov#123# пользователь и пароль.
Сервер проверяет что такому пользователю разрешено выполнять такие то команды (например TIME).

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

DATE - Разрешено только клиенту ivanov 
TIME -  Разрешено только клиенту petrov  
LIST - Разрешено всем
USER - Разрешено всем

Единственное мне приходит в голову сохранять  данные где то о клиенте при прохождении проверки USER xxx ,
при каждом вызове OnExecute проверять проходил ли пользователь проверку и проверять что ему можно а что нет,
при OnDisconnect забываем данные о клиенте.
ava
Avazart | 15.04.2013, 16:26 #
Лучше

999 ivanov#123#

Тогда не придется парсить и в коде можно использовать switch/case

Цитата


Единственное мне приходит в голову сохранять  данные где то о клиенте


Ну да, а еще видимо нужна будет БД содержащая список пользователей их права, - тут нужно будет обеспечить потокобезопасность.
ava
ochelot | 17.04.2013, 10:09 #
Цитата (Avazart @ 15.4.2013,  16:26)
Лучше 



999 ivanov#123#



Тогда не придется парсить и в коде можно использовать switch/case



Цитата



Единственное мне приходит в голову сохранять  данные где то о клиенте




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

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


Onconnect
Отсылаем клиенту приветствие.

OnExecute
Если Клиент еще не проходил проверку ТО
{
   Ожидаем комманду USER
  Если Получили USER
    Сохраняем ГДЕ ТО
  Иначе
    Disconnect
}
Иначе
{
  Получаем команду клиента и выполняем
}

OnDisconnect
Отсылаем сообщение, типа ОК.
Очищаем данные о клиенте.
ava
Avazart | 17.04.2013, 16:00 #
У TIdContext есть свойство

AContext->Data; //   TObject*  

Вот думаю оно и предназначено для хранения данных о каждом соединении.
ava
fish9370 | 29.04.2013, 20:31 #
я боюсь быть забросанным гнилыми помидорами, но вам не кажется, что все можно сделать намного проще с помощью обычных сишных функций?  smile 
ava
Avazart | 30.04.2013, 00:37 #
Цитата


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


А ты попробуй, во что превратится твой код Си-шный код ?

Что может проще чем кинуть пару компонентов ?
ava
Alca | 30.04.2013, 09:19 #
Цитата


Что может проще чем кинуть пару компонентов ?


А потом думать, почему это работает, но как-то криво  smile 
ava
fish9370 | 30.04.2013, 09:51 #
Цитата (Avazart @  30.4.2013,  00:37 findReferencedText)
А ты попробуй, во что превратится твой код Си-шный код ? Как его будет удобно поддерживать ?


а ты видел как он выглядит?  smile 

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

в Си дело сводится примерно к следующему:


thread_function() {
     /*  в этой функции идет взаимодействие с подключившимся клиентом, она запускается для каждого соединения */
     /* как правило используют функции poll, fgets, read, write */
}

make_socket() {
     /* создание и инициализация сокета (socket(), bind()) */
     /* одноразовая функция, запустил в самом начале и забыл */
}

main_loop() {
     /* основной цикл, в нем как правило в цикле вызывается функция accept(), после идет вызов создания потока (см. выше) */
}


еще раз, я не призываю отказаться от всех ваших примочек, AnsiString, StringGrid и прочей всякой хрени, но не доводите до адсурда
работать будет быстрее, прозрачнее и на мой взгляд с этим разобраться проще  smile 

P.S. мне по барабану, я инфу для размышления дал, думайте сами
ava
Avazart | 30.04.2013, 18:33 #
Цитата


А потом думать, почему это работает, но как-то криво


Опыт показывает что не работает не из-за Indy, а из-за кривых рук...
Читать доку надо, смотреть в исходники ...

Цитата


в Си дело сводится примерно к следующему:


Земля пухом такому коду ... ( ну хотя бы потому что он си )

Цитата


еще раз, я не призываю отказаться от всех ваших примочек, AnsiString, StringGrid и прочей всякой хрени, но не доводите до адсурда

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


Наверное в твоем коде только ты разбираешься ...
А выигрыш в скорости копеечный.
ava
Alca | 30.04.2013, 18:41 #
Цитата


Опыт показывает что не работает не из-за Indy, а из-за кривых рук...

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


В моем случае это было наоборот.
ava
Avazart | 30.04.2013, 18:46 #
Цитата


В моем случае это было наоборот.


Цитата


А потом думать, почему это работает, но как-то криво  


Ну так не нашли  ведь почему  ?

ava
Alca | 30.04.2013, 21:14 #
Цитата


В моем случае это было наоборот.


Я нашел, но потом пришлось самому пересобирать 10-ых индейцев.
А это еще то геморой.
ava
Avazart | 01.05.2013, 02:33 #
Цитата


Я нашел, но потом пришлось самому пересобирать 10-ых индейцев. 


А можно поинтересоваться в чем проблема была ?
ava
fish9370 | 01.05.2013, 19:56 #
Цитата (Avazart @  30.4.2013,  18:33 findReferencedText)
Наверное в твоем коде только ты разбираешься ...


ха-ха..  smile 
удачи..
ava
Alca | 01.05.2013, 21:32 #
Цитата


А можно поинтересоваться в чем проблема была ?


SMTP server, в размере буфера для строки, пришлось увеличивать.
ava
Avazart | 01.05.2013, 22:01 #
Как я помню размер буферов Indy настраиваем, по крайней мере не думаю что перекомпиляция так была необходим

Цитата (fish9370 @  1.5.2013,  19:56 findReferencedText)
удачи.. 

И тебе не хворать...
ava
Alca | 02.05.2013, 10:18 #
Цитата


Как я помню размер буферов Indy настраиваем


Это как  smile ?? В коде константа.
ava
Avazart | 04.05.2013, 00:21 #
Кстати а что в  компоненте  мах длина строки была изначально меньше чем указана в RFC ?

Цитата


2.1.1. Line Length Limits


    There are two limits that this standard places on the number of
    characters in a line. Each line of characters MUST be no more than
    998 characters, and SHOULD be no more than 78 characters, excluding
    the CRLF.

ava
Alca | 04.05.2013, 00:57 #
http://trac.xananews.techtips.com.br/brows...IdIOHandler.pas


const
      GRecvBufferSizeDefault = 32 * 1024;
      GSendBufferSizeDefault = 32 * 1024;
      IdMaxLineLengthDefault = 16 * 1024;
      // S.G. 6/4/2004: Maximum number of lines captured
      // S.G. 6/4/2004: Default to "unlimited"
      Id_IOHandler_MaxCapturedLines = -1;
ava
Avazart | 04.05.2013, 14:29 #
Я к тому что изначально менять длину было неправильным подходом, так как следовало полагаться на RFC

(16 *) 1024 > 988
Please register or login to write.
Firm of day
Вы также можете добавить свою фирму в каталог IT-фирм, и публиковать статьи, новости, вакансии и другую информацию от имени фирмы.
Подробнее
Contributors
advanced
Submit