-
Добрый день всем мастерам и не только.
Вопрос о поведении ф-ции 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 байт) и ф-ция возвращается с упехом.
-
Attempt>5 - слишком мало. TimeOut следует делать порядка 60 секунд, а не как у вас 15 мс.
На отправляющей стороне алгоритм Нагла включён?
-
Я ставил и больше (речь о Attempt) — не помогает.
Сервером выступает устройство. Я не знаю, есть там этот алгоритм или нет. Никаких настроек для Ethernet кроме IP, маски и шлюза нет.
-
Любая техника имеет скажем "запас надёжности". Т.е. ошибки возникают всегда, для TCP это редко от нескольких минут до нескольких суток. Так что закладывать в код обработку ошибок надо всегда.
В вашем случае у вас прямое соединение, ограниченное приделами лаборатории. В лабораторных условиях ошибки крайне редки. Но бывает всякое у меня был опыт с тем что устройство работает месяцами без единого сбоя. А бывает и каждую секунду глюк ловишь.
Запускаешь снифер WireShark и посмотри число сбойных пакетов и ошибок.
> Сервером выступает устройство. Я не знаю, есть там этот > алгоритм или нет.
Берём худший случай, что есть. Тогда минимальный TimeOut = 0,5 секунды.
-
После открытия сокета сделал такую хохму:
nSendBuf:=0;
setsockopt(FCashSocket, SOL_SOCKET, SO_SNDBUF, @nSendBuf, SizeOf(nSendBuf));
Т.е. размер буфера для передачи установил в 0. Данные не накапливаются, а сразу в сеть. При таком подходе ошибки на IoctlSocket не возникает. Возникает на send иногда (даже редко). Причина: Удаленный хост принудительно разорвал существующее подключение. Сдается мне, что с железкой что-то не так.
-
> GanibalLector © (14.07.16 23:52)
а если, в качестве теста, попробовать не самописный вариант работы с сокетами, а фреймворк, например, Indy? каков результат?
-
> не самописный вариант работы с сокетами, а фреймворк, например, > Indy? каков результат?
Только, если там стандартный протокол прикладного уровня.
> GanibalLector © (15.07.16 12:52) [4]
Похоже на то, что просто железка не успевает обработать посылки. Поэтому и разрывает соединение. У уменьшите число send в секунду.
-
> Pavia © (18.07.16 08:54) [6]
> Только, если там стандартный протокол прикладного уровня.
не обязательно, автор указал, что
> Предистория: TCP, блокирующие сокеты.
прикладной уровень не при чем.
> Похоже на то, что просто железка не успевает обработать > посылки
теоретически, такого не должно быть, на то он и TCP. меня код, приведенный автором, смущает, особенно вызов в цикле IoctlSocket. да и в целом что-то не то.
-
> не обязательно, автор указал, что > Предистория: TCP, блокирующие > сокеты.прикладной уровень не при чем.
Если рассматривать TCP, то у инди нет ни какой прослойки. Это мост. Команды напрямую идут на сокеты. Поэтому Инди тут никак не поможет. Вот если бы у него был HTTP, FTP, DNS и тп тогда бы да можно было говорить о том что Инди позволит избежать ошибок начинающего кодера.
> теоретически, такого не должно быть, на то он и TCP.
Теоретически я с вами согласен. Но кодеры встроенных систем жаловались на конкретные реализации.
> меня код, приведенный автором, смущает, особенно вызов в > цикле IoctlSocket. да и в целом что-то не то.
Меня тоже смущает. Код нестандартный - этим и смущает. Код с циклом проще, чем на событиях. Поэтому будь на месте автора я бы также написал. Но возможно мы упускаем что-то из виду. Возможно стоит посмотреть не на этот крошечный кусок, а взять отрывок по больше.
-
GanibalLector © (14.07.16 23:52)
> Он равен 0. Т.е. repeat|until срабатывает по Attempt>5 > и
GanibalLector © (15.07.16 12:52) [4]
> Причина: Удаленный хост принудительно разорвал существующее > подключение.
Скорее всего - это связанные явления. Ноль байт на приеме блокирующего сокета, когда выстрелил select по приему и означает закрытие соединения. Причина не обязательно в неверной работе "железки", возможны и ошибки сети или сетевого оборудования, хотя может быть и в ней.
-
> Pavia © (19.07.16 21:44) [8]
> Если рассматривать TCP, то у инди нет ни какой прослойки. > Это мост. Команды напрямую идут на сокеты.
там не всегда так просто, но в общем случае, да. у меня больше вопрос к рутинной реализации этого транспорта автором вопроса, потому и желательно проверить на 100% работающим решением, чтобы исключить глупые ошибки.
-
> Eraser © (19.07.16 16:15) [7]
> смущает, особенно вызов в цикле IoctlSocket
И да, действительно смысла в цикле нет
-
Но возможно мы упускаем что-то из виду. Возможно стоит посмотреть не на этот крошечный кусок, а взять отрывок по больше.Всегда пожалуйста:
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); 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
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;
-
> меня код, приведенный автором, смущает, особенно вызов в > цикле IoctlSocket. да и в целом что-то не то.
Цикла, естественно, не было в первой версии. Его добавил, когда начались проблемы, чтобы выяснить в чем дело. По большому счету он и сейчас не нужен, т.к. не спасает абсолютно.
-
> GanibalLector © (15.07.16 12:52) [4]
Скорее всего устройство не любит когда запрос приходит не в одном пакете.
Вариант (20.07.16 11:37) [9] > Ноль байт на приеме блокирующего сокета, когда выстрелил > select по приему и означает закрытие соединения.
вот-вот, переподключайся и работай дальше.
Ну и, код сам писал, или поддерживаешь - чем не устроил ov WSARead?
P.S. Работать со строками в качестве буфера - плохая примета ))
-
Вдогонку: прокомментируй эту строчку кода if Ord(Buffer[1])=Length(Buffer)-1 then Result:=True;
-
> Скорее всего устройство не любит когда запрос приходит не > в одном пакете.
Размер запросов до 20 байт. Сниффером смотрел, все отправляется/принимается без делений на несколько пакетов.
> вот-вот, переподключайся и работай дальше.
Не могу. После сбоя девайс не дает коннект около 5 мин.
-
> Вдогонку: прокомментируй эту строчку кода > 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
-
> Сниффером смотрел,
И что он показывает, когда 'сбой'?
> После сбоя девайс не дает коннект около 5 мин.
Интересно, а производитель девайса об этом знает?
-
> Интересно, а производитель девайса об этом знает?
Знает. Сегодня долго общались с производителем. Он предложил протестировать у себя. Протестировал и говорит, что ошибок не возникает в принципе (тестировали на нескольких ПК в сети их приедприятия). Все работает идеально. Предложил мне попробовать вариант работы на прямую (ПК—девайс или ПК-свитч—девайс), в обход моей сети предприятия. Попробовал, тоже работает.
Пока в шоке пребываю. Даже не знаю что делать, куда смотреть и что думать.
-
> Пока в шоке пребываю. Даже не знаю что делать, куда смотреть > и что думать.
Продолжение... Производитель предложил посмотреть на mac-адрес девайса из командной строки (arp -a). Выяснилось, что он не совпадает с mac-адрес самого девайса. Вывод прост: в локальной сети было два устройства с одинаковым ip-адресом. Причем второе устройство (я еще не узнал что это...сканер или принтер), включали хаотично и ошибка моя тоже появлялась хаотично.
Сам дурак, как оказалось. P.S. Надеюсь мой горький опыт кому-то поможет. Нужно тщательней проверять IP-адреса на совпадения. Я этого не сделал, и в итоге поплатился несколькими неделями поисков мифических ошибок.
Всем спасибо!
|