Конференция "Сети" » ReceiveBuf. не могу разобраться как приавильно принять данные [D7, WinXP]
 
  • molinero (31.05.11 09:57) [0]
    Клиент отправляет сразу после подключения:

    type
     TPrefix=record
       CMD: Integer;
       Size: Integer;
     end;

    procedure TfMain.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
    var Prefix: TPrefix;
       ClientNo: String[6];
    begin
     if Socket.Connected then
       begin
         Prefix.CMD:=CMD_LOGIN;
         Prefix.Size:=6;
         ClientSocket1.Socket.SendBuf(Prefix,SizeOf(Prefix));
         ClientNo:=C_CLIENTNO;
         ClientSocket1.Socket.SendBuf(ClientNo,Prefix.Size);
       end;
    end;

    сервер принимает:

    procedure TForm1.ServerClientRead(Sender: TObject; Socket: TCustomWinSocket);
    var ClientNo: String[6];
       SocketData: TclAssoc;
       Prefix: TPrefix;
    begin
     Socket.ReceiveBuf(Prefix,Socket.ReceiveLength);
     case Prefix.CMD of
    {CMD_LOGIN} 0:begin
                   Socket.ReceiveBuf(ClientNo,Prefix.Size+1);
                   ShowMessage('CMD_LOGIN');
                   ShowMessage(ClientNo);
                   ShowMessage(IntTosTr(Length(ClientNo)));
                 end;
               2:;
     end;
    end;

    При первовызове SendBuf(Prefix...) доставляется все как задумано, при втором вызове SendBuf(ClientNo...) принимается 0 байт
    и ClientNo=''. А если указать что принимать нужно 8 байт, то длина ClientNo в итоге получится 48.
    Заметил такую штуку. Если установить брекпоинт в том месте где принимается ClientNo то все принимается нормально...
    Помогите разобраться почему? Я в ступоре...
  • han_malign (31.05.11 10:18) [1]

    >  Socket.ReceiveBuf(Prefix,Socket.ReceiveLength);

    - тогда уж Socket.ReceiveBuf(Prefix,sizeof(Prefix));
    т.к. в Socket.ReceiveLength - может быть уже весь пакет(и только чудом у тебя не запоролся стек, при переполнении буфера
    Prefix

    - размещенного на стеке)...

    > Socket.ReceiveBuf(ClientNo,Prefix.Size+1);

    - отправляешь Prefix.Size,  а принимаешь почему то Prefix.Size+1...
    при этом принимаешь и отправляешь ShortString в которой первый(и похоже лишний) символ - длинна строки...
  • molinero (31.05.11 10:38) [2]

    > т.к. в Socket.ReceiveLength - может быть уже весь пакет(и
    > только чудом у тебя не запоролся стек, при переполнении
    > буфера Prefix - размещенного на стеке)...


    Видимых результатов не дало... Я не пойму почему при брекпоинте я вижу то что отправил??
  • FireMan_Alexey © (31.05.11 14:41) [3]
    If Socket.ReceiveLength>=SizeOf(Prefix) Then
     Socket.ReceiveBuf(Prefix,SizeOf(Prefix));
    If Socket.ReceiveLength>=Prefix.Size Then
       {...
       Считываем что надо!!!! Хотя если размер буфера приема меньше передаваемых данных (по умолчанию 4КБ), то надо просто сохранять все что пришло в свой буфер!!!! Иначе ничего не получиться....
       ...}
  • FireMan_Alexey © (31.05.11 14:51) [4]
    Не кидайте тапками... Обшибся :)
    Не 4КБ, а 8 КБ... Давно к сетям не обращался...

    >molinero
    TCP - потоковый протокол, это не значит, что если пришло сообщение/событие о приходе данных, то в буфере 100% будет все что ты послал!!!!
    Т.е., гипотетически, даже твой префикс в 8 байт может приходить по 1 байту!
    И твой код должен быть готовым к принятию фрагментированных/частичных данных!!! В том числе и то, что конец одного ТВОЕГО пакета может быть склеен с началом другого пакета!!!
  • FireMan_Alexey © (31.05.11 15:26) [5]
    Как вариант, если собираешься посылать/принимать пакеты свыше 4 КБ (не рекомендуется посылать больше чем MaxSize(8192) div 2 пакеты с данными, может постоянно выдавать ошибку переполнения буфера...), то стоит сделать примерно так...

    Type
      PClientData=^TClientData;
      TClientData=Record
          Prefix:TPrefix;
          ReadBytes:Integer;
          prefixReady:Boolean;
          <Какой-нибудь буфер на твое усмотрение (Stream/Динамический массив)>
      End;



    В процедуре коннекта:

    var
      CData:PClientData;
    Begin
    ...
     New(CData);
     CData^.PrefixReady:=False;
     CData^.ReadBytes:=0;
     Socket.Data:=CData;
    ...
    End;



    В процедуре приема данных:

    Begin
    ...
      If (not Socket.Data^.PrefixReady)and(Socket.ReceiveLength>=SizeOf(TPrefix)) Then
     <читаем префикс и устанавливаем PrefixReady=True>
    Else
     If Socket.Data^.ReadBytes<Socket.Data^.prefix.Size then
      <Вычисляем кол-во байт, которые необходимо считать, и если считали все необходимое, сбрасываем PrefixReady=False и ReadBytes=0!!!! В принципе УСЕ :), дальше делай с данными все что хочешь ;)>
    ...
    End;
  • FireMan_Alexey © (31.05.11 15:32) [6]
    Да забыл в процедуре дисконнекта подчищаем созданную динам. запись:

    ...
     Dispose(Socket.Data);
    ...

  • FireMan_Alexey © (31.05.11 15:46) [7]
    Еще раз оговорюсь, сам заметил ошибку... :)

    If (Socket.Data^.PrefixReady)and(Socket.Data^.ReadBytes<Socket.Data^.prefix.Size) then

  • molinero (01.06.11 09:07) [8]
    FireMan_Alexey благодарю за участие.
    Я сделал немного подругому. Принимаю в буфер, а потом накладываю на буфер указатель (PTPrefix).

    type
      PTPrefix=^TPrefix;
      TPrefix=Packed record
         CMD: Integer;
         Size: Integer;
      end;

    var Buf: Pointer;
        Prefix: TPrefix;
        ReceiveLength: Integer;

    begin
      ReceiveLength:=0;

      if Socket.ReceiveLength>=SizeOf(TPrefix)then
         Socket.ReceiveBuf(Buf^,SizeOf(TPrefix))

      else
         begin
            while ReceiveLength<=SizeOf(Tprefix)do
               begin
                  Socket.ReceiveBuf(Buf^,Socket.ReceiveLength);
                  ReceiveLength:=+Socket.ReceiveLength;
               end;
        end;

     Prefix:=PTPrefix(Buf)^;

      В Prefix записывается то что я отправил, но в конце процедуры ошибка доступа к памяти. Нужно освободить памаять (Buf)?

      И еще, Если длать так, то Buf будет перезаписваться и указывать на адрес новой порции данных?
    Как мне добавитьв буфер?
  • Anatoly Podgoretsky © (01.06.11 09:25) [9]
    > molinero  (01.06.2011 09:07:08)  [8]

    В конце ReceiveLength становится равным 0
  • molinero (01.06.11 10:09) [10]
    С ошибкой доступа к памяти разобрался.
    Сначала GetMem(Buf,SizeOf(TPrefix))
    Когда буфер больше ненужен FreeMem(Buf)

    Дальше опять непонятно...
    На форуме прочитал, что если данные приходят не все сразу, нужно принимать в цикле.

    Отправляю

    procedure TfMain.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
    var Prefix: PTPrefix;
       ClientNo: String[6];
    begin
     if Socket.Connected then
       begin
         New(Prefix);
         Prefix^.CMD:=CMD_LOGIN;
         Prefix^.Size:=6;
         ClientNo:=C_CLIENTNO;
         ClientSocket1.Socket.SendBuf(Prefix^,SizeOf(TPrefix));//Отправляем размер и команду
         ClientSocket1.Socket.SendBuf(ClientNo,Length(ClientNo));//Отправляем номер клиента
         Dispose(Prefix);
       end;
    end;

    //принимаю

    Prefix:=PTPrefix(Buf)^;
     FreeMem(Buf);
     case Prefix.CMD of
    {CMD_LOGIN} 0:begin
                            //здесь я уже принял Prefix и теперь хочу принять номер клиента
                            GetMem(Buf,Prefix.Size);
                            ReceiveLength:=0;
                            if Socket.ReceiveLength>=Prefix.Size then//здесь ReceiveLength=0
                               Socket.ReceiveBuf(Buf^,Prefix.Size)
                            else
                               while ReceiveLength<=Prefix.Size do//и вот здесь зацикливается
                                  begin
                                     Socket.ReceiveBuf(Buf^,Socket.ReceiveLength);
                                     ReceiveLength:=+Socket.ReceiveLength;
                                  end;
                            s:=PClientNo(Buf)^;
                         end;
                      2:;
     end;

    Собственно вопрос. Как принимать в цикле, если на клиенте был вызван два раза подряд метод отправки данных?
  • Anatoly Podgoretsky © (01.06.11 14:11) [11]
    > molinero  (01.06.2011 10:09:10)  [10]

    А если один раз нажали, уже умеешь и правильно?
  • FireMan_Alexey © (02.06.11 00:12) [12]
    >molinero

    Чего то я ничего не понял...!!! :-\

    Если у тебя в параметре Socket.ReceiveLength не хватает цифры до длины твоего префикса, то забей и не читай ничего до следующего события прихода данных!!!
    Я для чего тебе написал вот это и как заполнять это поле ??????:

    Type
     PClientData=^TClientData;
     TClientData=Record
         Prefix:TPrefix;
         ReadBytes:Integer;
         prefixReady:Boolean;
         <Какой-нибудь буфер на твое усмотрение (Stream/Динамический массив)>
     End;



    Еще если ты собираешься в цикле писать данные из сокета в буфер, то какого лешего ты пишешь в одно и тоже место:

    while ReceiveLength<=SizeOf(Tprefix)do
              begin
                 Socket.ReceiveBuf(Buf^,Socket.ReceiveLength);
                 ReceiveLength:=+Socket.ReceiveLength{вот здесь всегда будет ноль как написал Анатолий};
              end;


    ReceiveBuf и SendBuf - это функции!!!!! Они возвращают кол-во посланных байт!!!!!!
    Даже если ты посылаешь/принимаешь 1 байт это не значит, что эти функции отработают на 100%!!! А как показано в твоем коде, ты не разу даже не поинтересовался ушли ли все данные у тебя или нет!!!!
  • FireMan_Alexey © (02.06.11 00:17) [13]
    Вот правильное принятие данных!
    RecSize:=Socket.ReceiveLength;
    Size:= Socket.ReceiveBuf(Buf^,RecSize);



    В нормальном случае Size=RecSize, а может быть и не равны!!!!
  • FireMan_Alexey © (02.06.11 00:24) [14]
    И зачем тебе динамический префикс?????!!!!!!

    Я тебе вот это показал:

    PClientData=^TClientData;

    Для того, чтобы ты смог использовать свойство Socket.Data: pointer!

    В твоем случае, когда на сервере у тебя будет не 1 клиент, то возникнет проблема как привязать данные к каждому клиенту!!!!!!!! По сути расписал тебе всю схему доставки! Только осталось вписать в обработчики событий!!!
  • FireMan_Alexey © (02.06.11 00:31) [15]
    >molinero

    Не обижайся, но это полная охинея!!!!

    procedure TfMain.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
    var Prefix: PTPrefix;
      ClientNo: String[6];
    begin
    if Socket.Connected then - Он точно законнектился, проверять не надо!!!!
      begin
        New(Prefix);  - Зачем это, сделай свой TPrefix, он все равно умрет в стеке при выходе из процедуры!!!!
        Prefix^.CMD:=CMD_LOGIN;
        Prefix^.Size:=6;
        ClientNo:=C_CLIENTNO;
        ClientSocket1.Socket.SendBuf(Prefix^,SizeOf(TPrefix)); - Где проверка, что данные ушли и сколько их ушло???
        ClientSocket1.Socket.SendBuf(ClientNo,Length(ClientNo));- здесь аналогично!!!
        Dispose(Prefix); - Убрать!!!!! Как и New выше!!!!
      end;
    end;

  • FireMan_Alexey © (02.06.11 00:33) [16]
    Да! Работай вот так!!!!

    Socket.SendBuf(Prefix^,SizeOf(TPrefix));



    а не

    ClientSocket1.Socket.SendBuf(Prefix^,SizeOf(TPrefix));

  • FireMan_Alexey © (02.06.11 00:43) [17]
    Далее на сервере:

    procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
     Socket: TCustomWinSocket);
    var
     CData:PClientData;
    Begin
    ...
    New(CData);
    CData^.PrefixReady:=False;
    CData^.ReadBytes:=0;
    Socket.Data:=CData;
    ...
    End;

    procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
     Socket: TCustomWinSocket);
    Begin
    ...
     If (not Socket.Data^.PrefixReady)and(Socket.ReceiveLength>=SizeOf(TPrefix)) Then
    <читаем префикс и устанавливаем PrefixReady=True>
    Else
    If Socket.Data^.ReadBytes<Socket.Data^.prefix.Size then
     <Вычисляем кол-во байт, которые необходимо считать, и если считали все необходимое, сбрасываем PrefixReady=False и ReadBytes=0!!!! В принципе УСЕ :), дальше делай с данными все что хочешь ;)>
    ...
    End;




    Повторился, но это  только для понимания!!!
  • FireMan_Alexey © (02.06.11 00:48) [18]
    procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject;
     Socket: TCustomWinSocket);
    begin
     Dispose(Socket.Data);
    end;

  • FireMan_Alexey © (02.06.11 01:32) [19]
    Совет! Почитай про динамические массивы!
    Функцию SetLength!
    Потому что, судя из выше написанного тобою, ты слабо разбираешься/понимаешь в работе указателей и адресации!

    Без обид! ;)
  • molinero (03.06.11 07:46) [20]
    FireMan_Alexey ©   (02.06.11 01:32) [19]

    Да какие обиды? Пинок в нужном направлении самое то!
  • FireMan_Alexey © (03.06.11 12:20) [21]
    В принципе принятие и отправку данных можно расписать по следующему алгоритму(во всяком случае я так делал):

    1. Создаешь буферы на прием и отправку!!!
    2. Ждешь события OnWrite потом посылаешь данные до тех пор, пока не получишь SOCKET_ERROR или данные не закончатся!!!
    3. Ждешь событие OnRead и складываешь все в буфер приема, а когда необходимое кол-во данных оказывается в буфере, то проверяешь на корректность и т.д.
    4. При дисконнекте освобождаешь буферы.
    5. ВСЕ!!! :)

    Сейчас поищу пример, только с моим компонентом! Но там должно быть понятно и так...
  • FireMan_Alexey © (03.06.11 13:17) [22]
    Type
     BuffType=Array of Byte;

    procedure TForm1.ClientSocket1Read(Sender: TObject;
     Socket: TCustomWinSocket);

    Var
     BuffTemp:BuffType;
     Size,ReadSize:Integer;
    begin
     Size:=Socket.ReceiveLength;
     SetLength(BuffTemp,Size);
     ReadSize:=Socket.ReceiveBuf(BuffTemp,Size);
     If ReadSize>0 Then BuffRead.Write(BuffTemp,ReadSize);
     SetLength(BuffTemp,0);
     {BuffRead - &#236;&#238;&#233; &#234;&#235;&#224;&#241;&#241;, &#237;&#238; &#236;&#238;&#230;&#237;&#238; &#232;&#241;&#239;&#238;&#235;&#252;&#231;&#238;&#226;&#224;&#242;&#252; &#232; TMemoryStream, &#237;&#238; &#236;&#238;&#233; &#243;&#228;&#238;&#225;&#237;&#229;&#233; ;) }
     {!!!!!!!!Здесь обрабатываешь принятые данные!!!!!!!!!}
    end;

    Const
     MaxWrite=4096;

    procedure TForm1.ClientSocket1Write(Sender: TObject;
     Socket: TCustomWinSocket);
    Var
     BuffTemp:BuffType;
     Size,WriteSize:Integer;
    begin
     While BuffWrite.Size>0 do
       Begin
         SetLength(BuffTemp,MaxWrite);
         Size:=BuffWrite.Read(BuffTemp,MaxWrite);
         WriteSize:=Socket.SendBuf(BuffTemp,Size);
         SetLength(BuffTemp,0);
         If WriteSize>0 Then
           Begin
             BuffWrite.Position:=0;
             BuffWrite.Delete(WriteSize);
           End
         Else
           Break;
       End;
    end;



    Напиши, если нужен исходный код моего класса по типу MemoryStream!
    Он на дин, массивах! :)
  • FireMan_Alexey © (03.06.11 13:22) [23]
    Это то что иероглифами!!!

    {BuffRead - мой класс, но можно использовать и TMemoryStream, но мой удобней ;) }
  • Volkodav SR © (11.01.12 12:17) [24]
    Здравствуйте!
    Тоже бьюсь с сокетами, транспорт впринципе работает, но есть проблеммы.
    Подскажите пож-та кто знает. Проблема такая:
    В событиях по приему данных (ClinetRead или ServerClientRead) идет определение размера пакета данных и следом прием в цикле, пока весь пакет не будет принят. При этом находясь в режиме приема, это событие возникает несколько раз. Если процесс приема уже ведется и еще не закончен, то я выхожу из всех последующих срабатываний ClinetRead ....
    После приема пакета данных выхожу из события ...
    Проблема в том что при поступлении новых данных, событие ClinetRead или ServerClientRead в какой то произвольный момент может не сработать, несмотря на то что в сокете есть данные, оно перестает срабатывать. Если при этом принять данные в какой либо функции - событие продолжает работать.

    Можно ли какой либо командой выйдя из обработчика ClinetRead заставить сработать этот обработчик снова, если в сокете есть данные?
  • Сергей М. © (11.01.12 13:41) [25]
    На то есть три метода - ReceiveLength, ReceiveBuf и ReceiveText.

    Любому из этих методов методу глубоко фиолетово откуда их вызывают - из обработчика OnXXXRead
    или не из обработчика, на их работоспособность это никак не влияет.
  • Volkodav SR © (11.01.12 14:08) [26]

    > Сергей М. ©   (11.01.12 13:41) [25]

    Я использую методы ReceiveLength и ReceiveBuf.
    Смысл в том, что я не выхожу из обработчика OnXXXRead пока не будет принят весь известный мне оъбем данных. При этом событие OnXXXRead срабатывает неоднократно, а потом перестает срабатывать вообще. Прога при этом не висит.

    Ну то есть примерно так:
    1.Например сервер послал клиенту 100кб
    2.на клиенте возникает событие OnXXXRead
      так как не все 100 кб придут клиенту сразу событие OnXXXRead срабатывает у меня несколько раз ... но я принимаю данные (все 100кб) в первом событии, пока не приму все 100кб, а из последующих срабатываний событий выхожу ... после этого покидаю первое событие OnXXXRead, в котором принимал данные .... все хорошо ... работает, принимает, данные не теряет ....
    Когда сервер еще что нибудь пошлет, опять событие срабатывает, принимает ... НО НЕ ВСЕГДА, в какой то момент что то втыкается ...и не смотря на то что на клиент отосланы данные, событие OnXXXRead не срабатывает.

    Что то можно сделать вне обработчика OnXXXRead чтоб он опять сработал если в сокете есть данные?
    Или обязательно менять структуру приема данных?
  • Сергей М. © (11.01.12 15:09) [27]
    > я принимаю данные (все 100кб) в первом событии

    В режиме nonBlocking это неразумное решение.

    Возникло OnRead - обрабатывай его и читай в обработчике не более чем столько сколько в этот момент кажет ReceiveLength. Остальные данные никуда не пропадут - возникнут очередные OnRead и в них ты продолжишь получение очередных принятых от партнера и доступных данных.
  • Volkodav SR © (11.01.12 15:14) [28]
    Замечено, что при срабатывании события OnXXXRead, если не принять данные и выйти из него, то последующих срабатываний события OnXXXRead возникать не будет ...

    Так вот, можно ли сделать что нибудь ("переинициализировать" что ли ), чтоб при такой ситуации, оно сработало?
  • Сергей М. © (11.01.12 15:18) [29]

    > при срабатывании события OnXXXRead, если не принять данные
    > и выйти из него, то последующих срабатываний события OnXXXRead
    > возникать не будет


    Да, не будет, если из очереди поступивших от партнера данных тобой выбраны ВСЕ данные. Если же выбраны не все, то событие обязано возникнуть минимум еще однократно. И если оно у тебя не возникает, значит ты что-то накосячил в коде.
  • Volkodav SR © (11.01.12 15:19) [30]

    > Сергей М. ©   (11.01.12 15:09) [27]

    Ясно ...
    да, у меня nonBlocking ....

    ну тогда, мне предется переделывать схему приема и обработки данных ...
    типа писать все в TFileStream в OnRead, а в другом потоке производить разбор данных ... пойдет по такой схеме?
    только сложности на серверной стороне ... там ведь много клиеннтов может быть, разбор данных усложняется ...
  • Сергей М. © (11.01.12 15:26) [31]

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


    А это уже твоя личная драма что ты сразу не пошел по верному пути.


    > писать все в TFileStream в OnRead, а в другом потоке производить
    > разбор данных ... пойдет по такой схеме?


    Такая схема еще дерьмовей.
    TFileStream тут как корове седло : наверняка вполне сойдет и TMemoryStream.
    Да и зачем другой поток, если ты даже при текущем решении без него обходишься ?
  • Volkodav SR © (11.01.12 15:46) [32]

    > Сергей М. ©   (11.01.12 15:26) [31]

    Большое спасибо за совет и разъяснеия!

    Ну как мне кажется ... в OnRead читать данные, сохранять, и быстро выходить ...
    А обрабатывать и геагировать на принятые данные уже в другом месте, в потоке, дабы не грузить остальную часть приложения ...  а?

    А FileStream вмесо MemoryStream чтоб память не занимать ... может напрасно конечно? :)
  • Сергей М. © (11.01.12 16:00) [33]

    > в OnRead читать данные, сохранять, и быстро выходить


    Именно так и правильно - на то и существует неблокирующий режим.
  • Volkodav SR © (11.01.12 17:29) [34]

    > Сергей М. ©   (11.01.12 16:00) [33]

    ОК - Спасибо!

    А как считаешь, лучше так реализовывать, как сказано выше, в неблокирующем режиме, или попробовать с блокирующим разобраться?

    Работать в блокирующем режиме я не пробовал?
  • Сергей М. © (11.01.12 17:49) [35]
    Тут

    http://www.delphisources.ru/pages/faq/images-indy/indy-in-depth-06.html

    краткий ликбез.

    Полный оригинал статьи Анатолия Подгорецкого:
    http://www.podgoretsky.com/ftp/Docs/Delphi/Podgoretsky/IndyInDepth.pdf

    Попробуй для начала вникнуть самостоятельно.
  • Volkodav SR © (11.01.12 19:23) [36]
    Хорошо!
    Спасибо!
 
Конференция "Сети" » ReceiveBuf. не могу разобраться как приавильно принять данные [D7, WinXP]
Есть новые Нет новых   [134435   +13][b:0][p:0.004]