Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C++ Builder > Indy 10 файл-сервер работает но както....


Автор: ochelot 11.4.2013, 14: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();
    }
}

Автор: Avazart 12.4.2013, 15:05
Цитата

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

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

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

Код

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


}

Автор: ochelot 12.4.2013, 15:40
Цитата(Avazart @ 12.4.2013,  15:05)
Цитата

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

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

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

Код

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


}

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

Автор: Avazart 12.4.2013, 17:38
Цитата

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

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

Автор: ochelot 12.4.2013, 19:33
Цитата(Avazart @ 12.4.2013,  17:38)
Цитата

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

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

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

p.s. Читал Indy deep - так там как то поверхностно коснулись и то не 10 версии ИНДИ 

Автор: Avazart 12.4.2013, 20:56
Документация и Исходники ...

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

Надо пробовать... попробую отпишусь...

Автор: Avazart 12.4.2013, 23:04
Смотрю в Indy in Depth стр 91 приводится пример и утверждается обратное что Execute() выполняется циклично, а соединение прерывается само если что-то не так, либо по команде:
Код

procedure TformMain.IdTCPServer1Execute(AThread: TIdPeerThread);
var
LCmd: string;
begin
with AThread.Connection do
begin
LCmd := Trim(ReadLn);
if SameText(LCmd, 'QUIT') then
begin
WriteLn('200 Good bye');
Disconnect;
end
else if SameText(LCmd, 'DATE') then
begin
WriteLn('200 ' + DateToStr(Date));
end
else
begin
WriteLn('400 Unknown command');
end;
end;
end;

http://s2.ipicture.ru/Gallery/Viewfull/23946450.html

Автор: ochelot 13.4.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 версию... говорят они кардинально отличаются...

Могут ли у них и механизмы работы, что в диаграмме из книги работать по другому?


p.s. Мне тут жена говорит "Ну если и так (как в примере) работает хорошо и как нужно, нафига лезть глубоко и пытаться что то поменять", но хочется же докопаться до истины  smile 

Автор: ochelot 13.4.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;


Автор: Avazart 13.4.2013, 15:32
Цитата

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

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

Автор: 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(); // в общем дикая запись, пытаться освобождать память не надо - само должно удалиться.
}
//---------------------------------------------------------------------------






Автор: ochelot 14.4.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.Какие потенциальные ошибки могут всплыть в моем примере при использовании множеством клиентов?

Автор: Avazart 14.4.2013, 16:36
1. Читайте комментарии в коде - для синхронизации...
2. Возможно потому что 2177,2180  это не порт,  а возможно потому что порт настраивается как то не так.
3. В книге рекомендуется использовать не TIdTCPServer, а TIdCmdTCPServer  он удобнее для команд ...
4. Я не знаю код код писал наугад ...

Автор: ochelot 15.4.2013, 10:42
Спасибо за ответы  smile 

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

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

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

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

Единственное мне приходит в голову сохранять  данные где то о клиенте при прохождении проверки USER xxx , 
при каждом вызове OnExecute проверять проходил ли пользователь проверку и проверять что ему можно а что нет,
при OnDisconnect забываем данные о клиенте.

Автор: Avazart 15.4.2013, 16:26
Лучше 
Код

999 ivanov#123#

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

Цитата

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

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

Автор: ochelot 17.4.2013, 10:09
Цитата(Avazart @ 15.4.2013,  16:26)
Лучше 
Код

999 ivanov#123#

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

Цитата

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

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

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

Код

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

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

OnDisconnect 
Отсылаем сообщение, типа ОК.
Очищаем данные о клиенте.

Автор: Avazart 17.4.2013, 16:00
У TIdContext есть свойство
Код

AContext->Data; //   TObject*  

Вот думаю оно и предназначено для хранения данных о каждом соединении.

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

Автор: Avazart 30.4.2013, 00:37
Цитата

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

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

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

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

Автор: Alca 30.4.2013, 09:19
Цитата

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

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

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


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

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

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

Код

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

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

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


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

P.S. мне по барабану, я инфу для размышления дал, думайте сами

Автор: Avazart 30.4.2013, 18:33
Цитата

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

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

Цитата

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

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

Цитата

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

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

Автор: Alca 30.4.2013, 18:41
Цитата

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

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

Автор: Avazart 30.4.2013, 18:46
Цитата

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

Цитата

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

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


Автор: Alca 30.4.2013, 21:14
Цитата

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

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

Автор: Avazart 1.5.2013, 02:33
Цитата

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

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

Автор: fish9370 1.5.2013, 19:56
Цитата(Avazart @  30.4.2013,  18:33 Найти цитируемый пост)
Наверное в твоем коде только ты разбираешься ...


ха-ха..  smile 
удачи..

Автор: Alca 1.5.2013, 21:32
Цитата

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

SMTP server, в размере буфера для строки, пришлось увеличивать.

Автор: Avazart 1.5.2013, 22:01
Как я помню размер буферов Indy настраиваем, по крайней мере не думаю что перекомпиляция так была необходима

Ну само собой компонент та тоже не простой ...

Цитата(fish9370 @  1.5.2013,  19:56 Найти цитируемый пост)
удачи.. 

И тебе не хворать...

Автор: Alca 2.5.2013, 10:18
Цитата

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

Это как  smile ?? В коде константа.

Добавлено через 7 минут
Вот тема:
http://forum.vingrad.ru/forum/topic-269219.html

Вот исходник:
http://trac.xananews.techtips.com.br/browser/trunk/components/thirdparty/Indy10/Core/IdIOHandler.pas

Цитата

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

Ты прав, там есть такое свойство

Добавлено через 7 минут и 46 секунд
Код

published
...
    property MaxLineLength: Integer read FMaxLineLength write FMaxLineLength default IdMaxLineLengthDefault;

Автор: Avazart 4.5.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.

Автор: Alca 4.5.2013, 00:57
http://trac.xananews.techtips.com.br/browser/trunk/components/thirdparty/Indy10/Core/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;

Автор: Avazart 4.5.2013, 14:29
Я к тому что изначально менять длину было неправильным подходом, так как следовало полагаться на RFC

1024 > 988

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)