Конференция "Сети" » Получение размера пакета в ф-ции IOCtlSocket [D7]
 
  • GanibalLector © (14.07.16 23:52) [0]
    Добрый день всем мастерам и не только.

    Вопрос о поведении ф-ции IOCtlSocket. Предистория: TCP, блокирующие сокеты.

    Схематично выглядит следующим образом (пишу по памяти):

    // отправляем
    send(Socket,DataBuffer, Length(DataBuffer),0);
    ...
    New(hTime);
    hTime^.tv_sec:=2;
    hTime^.tv_usec:=500000;
    FD_ZERO(SetR);
    FD_Set(Socket,SetR);
    ModeSelect:=select(0,@SetR,nil,nil,hTime);
    Dispose(hTime);
    if ModeSelect>0 then
    begin
     StrLen:=0; Attempt:=0;
     repeat
       if IoctlSocket(HPort,FIONREAD,StrLen)=SOCKET_ERROR then
       begin
         //обрабатываем ошибку
       end
       Sleep(5); //курим...а вдруг не дошло
       Inc(Attempt);
     until (StrLen>0) or (Attempt>5);
     //принимаем
     recv...
    end

    А теперь вопрос. select срабатывает и на функции IoctlSocket я всегда получаю успех (т.е. 0). А вот с размеров принятых данных (StrLen) беда. Он равен 0. Т.е. repeat|until срабатывает по Attempt>5 и читать мне нечего.

    Ошибка возникает хаотично. Т.е. в большинстве случаев ее нет и все читает нормально, но иногда бывает и это очень мешает.
    ЧТо не так? Вторую ночь спать не могу, блин.

    P.S. Может дело в send? Но размер передаваемых данных совсем не большой (до 10 байт) и ф-ция возвращается с упехом.
  • Pavia © (15.07.16 09:37) [1]
    Attempt>5 - слишком мало.  TimeOut следует делать порядка 60 секунд, а не как у вас 15 мс.

    На отправляющей стороне алгоритм Нагла включён?
  • GanibalLector © (15.07.16 09:57) [2]
    Я ставил и больше (речь о Attempt) — не помогает.

    Сервером выступает устройство. Я не знаю, есть там этот алгоритм или нет. Никаких настроек для Ethernet кроме IP, маски и шлюза нет.
  • Pavia © (15.07.16 10:23) [3]
    Любая техника имеет скажем "запас надёжности". Т.е. ошибки возникают всегда, для TCP это редко от нескольких минут до нескольких суток.
    Так что закладывать в код обработку ошибок надо всегда.

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

    Запускаешь снифер  WireShark и посмотри число сбойных пакетов и ошибок.


    > Сервером выступает устройство. Я не знаю, есть там этот
    > алгоритм или нет.

    Берём худший случай, что есть. Тогда минимальный TimeOut = 0,5 секунды.
  • GanibalLector © (15.07.16 12:52) [4]
    После открытия сокета сделал такую хохму:

    nSendBuf:=0;
    setsockopt(FCashSocket, SOL_SOCKET, SO_SNDBUF, @nSendBuf, SizeOf(nSendBuf));



    Т.е. размер буфера для передачи установил в 0. Данные не накапливаются, а сразу в сеть.

    При таком подходе ошибки на IoctlSocket не возникает. Возникает на send иногда (даже редко). Причина: Удаленный хост принудительно разорвал существующее подключение.

    Сдается мне, что с железкой что-то не так.
  • Eraser © (16.07.16 22:19) [5]

    > GanibalLector ©   (14.07.16 23:52) 

    а если, в качестве теста, попробовать не самописный вариант работы с сокетами, а фреймворк, например, Indy? каков результат?
  • Pavia © (18.07.16 08:54) [6]

    > не самописный вариант работы с сокетами, а фреймворк, например,
    >  Indy? каков результат?

    Только, если там стандартный протокол прикладного уровня.


    > GanibalLector ©   (15.07.16 12:52) [4]

    Похоже на то, что просто железка не успевает обработать посылки. Поэтому и разрывает соединение.  У уменьшите число send в секунду.
  • Eraser © (19.07.16 16:15) [7]

    > Pavia ©   (18.07.16 08:54) [6]


    > Только, если там стандартный протокол прикладного уровня.

    не обязательно, автор указал, что

    > Предистория: TCP, блокирующие сокеты.

    прикладной уровень не при чем.

    > Похоже на то, что просто железка не успевает обработать
    > посылки

    теоретически, такого не должно быть, на то он и TCP.
    меня код, приведенный автором, смущает, особенно вызов в цикле IoctlSocket. да и в целом что-то не то.
  • Pavia © (19.07.16 21:44) [8]

    > не обязательно, автор указал, что > Предистория: TCP, блокирующие
    > сокеты.прикладной уровень не при чем.

    Если рассматривать TCP, то у инди нет ни какой прослойки. Это мост. Команды напрямую идут на сокеты. Поэтому Инди тут никак не поможет. Вот если бы у него был HTTP, FTP, DNS и тп тогда бы да можно было говорить о том что Инди позволит избежать ошибок начинающего кодера.


    > теоретически, такого не должно быть, на то он и TCP.

    Теоретически я с вами согласен. Но кодеры встроенных систем жаловались на конкретные реализации.


    > меня код, приведенный автором, смущает, особенно вызов в
    > цикле IoctlSocket. да и в целом что-то не то.

    Меня тоже смущает. Код нестандартный - этим и смущает. Код с циклом проще, чем на событиях.  Поэтому будь на месте автора я бы также написал.
    Но возможно мы упускаем что-то из виду. Возможно стоит посмотреть не на этот крошечный кусок, а взять отрывок по больше.
  • Вариант (20.07.16 11:37) [9]
    GanibalLector ©   (14.07.16 23:52)

    >  Он равен 0. Т.е. repeat|until срабатывает по Attempt>5
    > и

    GanibalLector ©   (15.07.16 12:52) [4]

    > Причина: Удаленный хост принудительно разорвал существующее
    > подключение.

    Скорее всего  - это связанные явления.
    Ноль байт на приеме блокирующего сокета, когда выстрелил select по приему  и означает закрытие соединения. Причина не обязательно в неверной работе "железки", возможны и ошибки сети или сетевого оборудования, хотя может быть и в ней.
  • Eraser © (20.07.16 16:51) [10]

    > Pavia ©   (19.07.16 21:44) [8]


    > Если рассматривать TCP, то у инди нет ни какой прослойки.
    >  Это мост. Команды напрямую идут на сокеты.

    там не всегда так просто, но в общем случае, да. у меня больше вопрос к рутинной реализации этого транспорта автором вопроса, потому и желательно проверить на 100% работающим решением, чтобы исключить глупые ошибки.
  • Вариант (21.07.16 05:26) [11]

    > Eraser ©   (19.07.16 16:15) [7]


    > смущает, особенно вызов в цикле IoctlSocket

    И да, действительно смысла в цикле нет
  • GanibalLector © (01.08.16 15:20) [12]
    Но возможно мы упускаем что-то из виду. Возможно стоит посмотреть не на этот крошечный кусок, а взять отрывок по больше.

    Всегда пожалуйста:

    function IsCompleteTCP(const Buffer: ShortString): Boolean;
    begin
     Result := False;
     if Length(Buffer)>0 then
     begin
      if Ord(Buffer[1])=Length(Buffer)-1  then Result:=True;
     end;
    end;

    procedure EthernetStart(hPort:THandle; LogN:Byte; out Status: TStatusRec);
     var Databuffer, Reply: string;
     Transfer:Integer;
     hTime:PTimeVal;
     SetR:TFDSet;
     StrLen, ModeSelect, Shot:Integer;
    begin
     Databuffer:=Chr($05) + Chr($05) + Chr($01)+ Chr($00) + Chr(LogN) + Chr($00); //query for opening the SET
     //отправить
     Transfer:= Send(HPort,Databuffer[1], Length(Databuffer),0);
     if Transfer<0 then
     begin
       Status.ErrKind := erTCP;
       Status.TCPCode := WSAGetLastError;
       Logs.SaveLog('Command: Start. Error:Send Code:'+IntToStr(Status.TCPCode) +SysErrorMessage(Status.TCPCode));
       Exit;
     end;

     //принять
     Reply:='';
     while not IsCompleteTCP(Reply) do
     begin
       New(hTime);
       hTime^.tv_sec:=2;
       hTime^.tv_usec:=500000;
       FD_ZERO(SetR);
       FD_Set(HPort,SetR);
       ModeSelect:=Select(0,@SetR,nil,nil,hTime);
       Dispose(hTime);

       if ModeSelect>0 then
       begin
         //if FD_IsSet(hPort,SetR) then
         begin
           StrLen:=0;  Shot:=0;
           repeat
             if IoctlSocket(hPort,FIONREAD,StrLen)=SOCKET_ERROR then
             begin
               Logs.SaveLog('Command: Start. Error:IoctlSocket=SOCKET_ERROR');
               Status.ErrKind:=erTCP;
               Status.TCPCode:=WSAGetLastError;
               Exit;
             end;
             if StrLen=0 then Windows.Beep(800,20);
             Inc(Shot);
           until (StrLen>0) or (Shot>=50);

           if StrLen>0 then
           begin
             SetLength(Databuffer, StrLen);
             if ReadFromSocket(hPort, DataBuffer[1],StrLen,Status)>0 then
             begin
               Reply := Reply + DataBuffer;
             end else
             begin
               Logs.SaveLog('Command: Start. Error:ReadFromSocket');
               Status.ErrKind:=erTCP;
               Status.TCPCode:=WSAGetLastError;
               Exit;
             end;
           end else
           begin
             Logs.SaveLog('Command: Start. Error:IoctlSocket answer 0');
             Status.ErrKind:=erTCP;
             Status.TCPCode:=0;
             Exit;
           end;
         end;
       end else
       begin
         Logs.SaveLog('Command: Start. Error:select');
         Status.ErrKind := erTCP;
         Status.TCPCode := WSAGetLastError;
         Exit;
       end;
     end;
     //
     if Length(Reply)>0 then
     begin
       //парсер ответа железки
     end;
    end;

  • GanibalLector © (01.08.16 15:35) [13]

    > меня код, приведенный автором, смущает, особенно вызов в
    > цикле IoctlSocket. да и в целом что-то не то.


    Цикла, естественно, не было в первой версии. Его добавил, когда начались проблемы, чтобы выяснить в чем дело. По большому счету он и сейчас не нужен, т.к. не спасает абсолютно.
  • NoUser © (01.08.16 18:51) [14]
    > GanibalLector ©   (15.07.16 12:52) [4]

    Скорее всего устройство не любит когда запрос приходит не в одном пакете.


    Вариант   (20.07.16 11:37) [9]
    > Ноль байт на приеме блокирующего сокета, когда выстрелил
    > select по приему  и означает закрытие соединения.


    вот-вот, переподключайся и работай дальше.

    Ну и, код сам писал, или поддерживаешь - чем не устроил ov WSARead?

    P.S.
    Работать со строками в качестве буфера - плохая примета ))
  • NoUser © (01.08.16 18:56) [15]
    Вдогонку: прокомментируй эту строчку кода
    if Ord(Buffer[1])=Length(Buffer)-1  then Result:=True;

  • GanibalLector © (02.08.16 00:00) [16]

    > Скорее всего устройство не любит когда запрос приходит не
    > в одном пакете.


    Размер запросов до 20 байт. Сниффером смотрел, все отправляется/принимается без делений на несколько пакетов.


    > вот-вот, переподключайся и работай дальше.


    Не могу. После сбоя девайс не дает коннект около 5 мин.
  • GanibalLector © (02.08.16 00:06) [17]

    > Вдогонку: прокомментируй эту строчку кода
    > if Ord(Buffer[1])=Length(Buffer)-1  then Result:=True;


    Messages format:
    <Len><DATA>

    <Len> The number of next bytes. It is 1 byte less than the length of whole packet; length: 1 byte
  • NoUser © (02.08.16 10:14) [18]
    > Сниффером смотрел,

    И что он показывает, когда 'сбой'?

    > После сбоя девайс не дает коннект около 5 мин.

    Интересно, а производитель девайса об этом знает?
  • GanibalLector © (02.08.16 13:13) [19]

    > Интересно, а производитель девайса об этом знает?


    Знает. Сегодня долго общались с производителем. Он предложил протестировать у себя. Протестировал и говорит, что ошибок не возникает в принципе (тестировали на нескольких ПК в сети их приедприятия). Все работает идеально. Предложил мне попробовать вариант работы на прямую (ПК—девайс или ПК-свитч—девайс), в обход моей сети предприятия. Попробовал, тоже работает.

    Пока в шоке пребываю. Даже не знаю что делать, куда смотреть и что думать.
  • GanibalLector © (02.08.16 23:26) [20]

    > Пока в шоке пребываю. Даже не знаю что делать, куда смотреть
    > и что думать.


    Продолжение...
    Производитель предложил посмотреть на mac-адрес девайса из командной строки (arp -a). Выяснилось, что он не совпадает с mac-адрес самого девайса. Вывод  прост: в локальной сети было два устройства с одинаковым ip-адресом. Причем второе устройство (я еще не узнал что это...сканер или принтер), включали хаотично и ошибка моя тоже появлялась хаотично.

    Сам дурак, как оказалось.
    P.S.  Надеюсь мой горький опыт кому-то поможет. Нужно тщательней проверять IP-адреса на совпадения. Я этого не сделал, и в итоге поплатился несколькими неделями поисков мифических ошибок.

    Всем спасибо!
 
Конференция "Сети" » Получение размера пакета в ф-ции IOCtlSocket [D7]
Есть новые Нет новых   [118666   +35][b:0][p:0.003]