Мастера DELPHI, Delphi programming community Рейтинг@Mail.ru Титульная страница Поиск, карта сайта Написать письмо 
| Новости |
Новости сайта
Поиск |
Поиск по лучшим сайтам о Delphi
FAQ |
Огромная база часто задаваемых вопросов и, конечно же, ответы к ним ;)
Статьи |
Подборка статей на самые разные темы. Все о DELPHI
Книги |
Новинки книжного рынка
Новости VCL
Обзор свежих компонент со всего мира, по-русски!
|
| Форумы
Здесь вы можете задать свой вопрос и наверняка получите ответ
| ЧАТ |
Место для общения :)
Орешник |
Коллекция курьезных вопросов из форумов
KOL и MCK |
KOL и MCK - Компактные программы на Delphi
Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / rsdn.org

 
Чтобы не потерять эту дискуссию, сделайте закладку « предыдущая ветвь | форум | следующая ветвь »

Получение размера пакета в ф-ции IOCtlSocket [D7]


GanibalLector ©   (14.07.16 23:52

Добрый день всем мастерам и не только.

Вопрос о поведении ф-ции 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-адреса на совпадения. Я этого не сделал, и в итоге поплатился несколькими неделями поисков мифических ошибок.

Всем спасибо!


версия для печати

Написать ответ

Ваше имя (регистрация  E-mail 







Разрешается использование тегов форматирования текста:
<b>жирный</b> <i>наклонный</i> <u>подчеркнутый</u>,
а для выделения текста программ, используйте <code> ... </code>
и не забывайте закрывать теги! </b></i></u></code> :)


Наверх

  Рейтинг@Mail.ru     Титульная страница Поиск, карта сайта Написать письмо