PDA

Просмотр полной версии : СПК1хх & Сокеты



lazy
26.11.2019, 17:05
Добрый день!

Добрался до сокетов. Пытаюсь поднять сервер.

Открываю сокет:
m_hSocket := SysSockCreate( SOCKET_AF_INET, SOCKET_STREAM, SOCKET_IPPROTO_TCP, ADR( m_hRes ) );
все ок.

Далее SysSockBind:

m_SAddress.sin_family := SOCKET_AF_INET;
//m_SAddress.sin_addr := SOCKET_INADDR_ANY; - пока закоментировал так как компилятор ругается если скармливать сюда SOCKET_INADDR_ANY
m_SAddress.sin_port := SysSockHtons( i_rOptions.wPort );
m_hRes := SysSockBind( m_hSocket, ADR( m_SAddress ), SIZEOF( m_SAddress ) );
IF 0 = m_hRes THEN
o_eCondition := ST_CONNECT;
ELSE
o_eCondition := ST_CLOSE;
END_IF

SysSockBind возвращяет ERR_SOCK_ADDRINUSE 16#207 - The provided address is already in use. Куда копать? и полцарства за примерчик с сокетами на СПК.

Евгений Кислов
26.11.2019, 18:55
Добрый день.

1. По фрагменту кода сложно дать комментарии - могу только предположить, что bind один раз все же отработал, а SysSocketClose после этого не вызывался.
Проще всего перезагрузить контроллер, чтобы освободить все ресурсы, и проверить еще раз.

2. Если нет веских причин работать именно с SysSocket - то проще использовать CAA NetBaseServices.
Документация и примеры:
https://ftp.owen.ru/CoDeSys3/11_Documentation/03_3.5.11.5/CDSv3.5_Sockets_v2.0.pdf

lazy
27.11.2019, 12:16
Добрый день.

могу только предположить, что bind один раз все же отработал, а SysSocketClose после этого не вызывался.


действительно, так и есть )

Еще вопрос по сокетам. В неблокирующий режим нужно сокеты переводить и собственно как? похоже что на SysSockAccept все вистнет.

Евгений Кислов
27.11.2019, 12:24
действительно, так и есть )

Еще вопрос по сокетам. В неблокирующий режим нужно сокеты переводить и собственно как? похоже что на SysSockAccept все вистнет.

Вы не могли бы все же уточнить, в чем цель использования SysSocket вместо более простых вариантов?

lazy
27.11.2019, 12:34
Вы не могли бы все же уточнить, в чем цель использования SysSocket вместо более простых вариантов?

Просто написано уже для 110го М2, отлажено и работает. Как по мне, проще этот код на третий кодесис перенести, чем по новой в чем то разбирацо и с нуля ваять )

Евгений Кислов
27.11.2019, 12:44
Просто написано уже для 110го М2, отлажено и работает. Как по мне, проще этот код на третий кодесис перенести, чем по новой в чем то разбирацо и с нуля ваять )

Реализация библиотеки сильно отличается для Cds 2.3 (с учетом нюансов конкретной реализации в ПЛК) и 3.5.
Я бы рекомендовал все же посмотреть CAA NBS - там сервер строится из 4 ФБ, которые имеют минимум входов и выходов.

Если конкретно по вашему вопросу - то ответ есть в этой статье (http://support.fastwel.ru/AppNotes/AN/AN-0001.html).

lazy
27.11.2019, 13:01
ну конект уже есть )
Оказываецо в SysSockAccept указатель на размер SOCKADDRESS а не размер )

lazy
27.11.2019, 16:58
Евгений, спасибо, заработало )

Кому вдруг интересно перевод в неблокирующий режим:
m_dnParam := 1; // DINT
IF 0 = SysSockIoctl( m_hSocket, SOCKET_FIONBIO, ADR( m_dnParam ) ) THEN "Ok"

иначе все виснет на обмене, так как SysSockRecv ждет пакетов от клиентов, а их может и не быть )
при разрыве связи SysSockRecv и(или) SysSockSend в pResult возвратит "ERR_SOCK_CLOSED 16#211 Socket has been gracefully closed. No more send/receives allowed"
и клиентский сокет можно закрывать.

lazy
29.11.2019, 09:11
Тестирую обмен модбаспулом и вот, что обнаружилось.

Конект есть, обмен идет. Если отключить кабель сети (приблизительно) на пару секунд модбаспул дает ошибки таймаута (как и положено), подтыкаем кабель обратно и связь восстанавливается, при этом на СПК все проходит без ошибок, то есть SysSockRecv в pResult возвращает 0. Но если кабель отключить на дольшее время модбаспул начинает выдавать (как и всегда)writeerror или readerror, а программа на СПК просто виснет.

Пробовал читать и писать с флагом SOCKET_MSG_DONTWAIT, SysSockRecv каждый раз в pResult возвращает: "ERR_SOCK_WOULDBLOCK 16#206 Socket is in nonblocking mode but THIS call would block", игнорирую эту ошибку - обмен идет. Отключаем кабель на пару секунд - все как и с чтением без флагов (diFlags := 0 ). Отключаем кабель дольше чем на пару секунд - СПК больше не виснет, но SysSockRecv в pResult других ошибок не возвращает, поэтому при присоединении кабеля связь восстанавливается путем новых подключений. Создаются новые клиентские сокеты, а старые не закрываются, так как о разрыве связи так ничего и не узнали.

lazy
29.11.2019, 12:42
Ого. поставил в модбаспуле сканрейт в 5 сек. То есть пакеты шлюцо раз в пять секунд. У меня цикл выполнения программы на СПК стал 5 сек. Не работает SysSockRecv в неблокирующем режиме что ли? Или через SysSockSelect обмен ваять?

lazy
29.11.2019, 14:23
В общем сокеты нормально работают только при переводе их в неблокирующий режим и при чтении и записи с флагом SOCKET_MSG_DONTWAIT, иначе цикл исполнения программы увеличивается т.к SysSockRecv ждет пакетов. Но вопрос про то, как узнать что кабель оторван остается открытым.
Можно, конечно закрывать сокеты по таймауту, мол не обращаются к нам какое то время - закрываем клиентский сокет. попробую отпишусь.

lazy
29.11.2019, 15:25
С таймаутами работает. Отключаем кабель, включаем через любое время - связь восстанавливается.
Но все же хотелось бы знать точно, это что-то со связью (кабель итд) или просто клиент молчит.

DENth
19.12.2019, 19:29
А вот еще вопрос по теме. Используя библиотеку NBS CAA ведь можно с ПЛК в ПЛК передавать любые данные, используя ФБ send и recieve, а не только строковые, как приведено в примере. Но вот у меня есть необходимость реализовать связь ПК - ПЛК, и ПК нужно знать структуру передаваемого UDP пакета, а в документации "Реализация обмена через сокеты" об этом нигде не сказано. Есть только сноска, что "В рамках примера рассматривается обмен данными между сервером и клиентом с помощью обычных текстовых строк. Для реализации конкретного протокола потребуется его спецификация, описывающая форматы и последовательности запросов и ответов."

Если по другому и глупо, то ПЛК используя библиотеку легко примет переменную типа WORD, а как ее сформировать на ПК?

ВладОвен
06.12.2022, 09:34
Я бы рекомендовал все же посмотреть CAA NBS - там сервер строится из 4 ФБ, которые имеют минимум входов и выходов.

Привет.
А в этой библиотеке CAA NBS есть ФБ, который закрывает соединение?
Мне нужно 1 раз в полчаса отправлять сообщение на сервер и не хотелось бы держать соединение постоянно открытым.

Евгений Кислов
06.12.2022, 09:35
Привет.
А в этой библиотеке CAA NBS есть ФБ, который закрывает соединение?
Мне нужно 1 раз в полчаса отправлять сообщение на сервер и не хотелось бы держать соединение постоянно открытым.

Добрый день.
Вы планируете реализовать клиент или сервер?

ВладОвен
06.12.2022, 10:05
Я планирую сделать клиент.

Евгений Кислов
06.12.2022, 10:11
Я планирую сделать клиент.

По заднему фронту на входе xEnable блок TCP_Client закрывает соединение.

ВладОвен
06.12.2022, 13:49
Подскажите, а как сформировать очень длинную строку для отправки? Например нужно 10кБ, т.е. 10 тыс. символов.
Можно ли использовать функцию StrConcatA из библиотеки StringUtils для этого?

Евгений Кислов
06.12.2022, 13:54
Подскажите, а как сформировать очень длинную строку для отправки? Например нужно 10кБ, т.е. 10 тыс. символов.
Можно ли использовать функцию StrConcatA из библиотеки StringUtils для этого?

Да, можно использовать эту функцию.

ВладОвен
06.12.2022, 14:04
Супер! Теперь осталось понять, почему она хреново работает.:)

Я складываю свою строку из многих компонентов.
Вот код:


sDataSend := '{"calibr_unit":10,"data":{"cmd":"add_launch","algoritm":"'; // уникальный номер стенда
sDataTEMP := 'Test'; // уникальное имя алгоритма
STU.StrConcatA(ADR(sDataTEMP), ADR(sDataSend), 10000); // сначала кусок для прибавления, потом то, к чему нужно прибавить.


Получаю нормальную склейку:
64330

А далее:


sDataTEMP := '2022-12-05 12:00:00'; // дата/время
STU.StrConcatA(ADR(sDataTEMP), ADR(sDataSend), 10000);


Что тут:
64331

Почему так?

Евгений Кислов
06.12.2022, 14:16
Выложите простейший проект (без сервера, CASE и т.д.), в котором я смогу повторить проблему.

ВладОвен
06.12.2022, 14:29
Проблема создать простейший проект.
Удалил визуализацию и получил кучу ошибок. Мол, нету визуализации.
Ну неужели нельзя сделать проект для СПК без использования визуализации?

Буду делать еще раз.

ВладОвен
06.12.2022, 14:40
Готово: DropMeFiles – бесплатный файлообменник без регистрации (https://dropmefiles.com/adJbi)

Проблема проявляется и в этом проекте.

Евгений Кислов
06.12.2022, 15:58
Вы указываете функции, что у вас размер буфера - 10000 байт, но в реальности под указатель подкладываете переменную типа STRING, которая занимает 81 байт
(по умолчанию - если макс. размер строки не указан - выделяется память под 80 символов + терминирующий ноль).
Поэтому у вас переполнение буфера происходит.

Сделайте так:



...
sDataSend : STRING(10000);
...

ВладОвен
06.12.2022, 16:34
Да. Действительно заработало. Кто бы мог подумать про гребанную строку длинной в 10к символов?!

Теперь другая проблема: у меня не открывается клиент.
Но тут, наверное, мне нужно более фундаментально подойти к проблеме.
Мне кажется, что я вообще не догоняю как строить эти программы.

ВладОвен
06.12.2022, 16:42
Вот можно открыть сокет так:





CASE bState OF
05: // Формируем строку для отправки
// Тут мы уже разобрались, оказывается надо строку делать с длинной в 10к символов.
bState := 10;
10: // создаем TCP-клиента
fbTcpClient(xEnable:=TRUE, udiTimeOut:=1000, ipAddr:=stIpServer, uiPort:=uiPortServer);
IF fbTcpClient.xActive THEN
bState := 20;
ELSIF bTries > 10 OR fbTcpClient.xError THEN
bError := 10; // код шага, где произошла ошибка
xError := TRUE; // флаг ошибки
bState := 50; // Выход из автомата шагов
END_IF
20: ... // Другие шаги, через неделю до них дойдем.

Евгений Кислов
06.12.2022, 16:47
Да, можно.

ВладОвен
06.12.2022, 17:06
Вот такой вопрос.
Я открываю сокет и даю время на его открытие 10 секунд.
Но программа сразу же мне выдает ошибку TIME_OUT.
Что я делаю не так?
64343

Получается, что ФБ TcpClient подымает флаг xError и причина этого флага лежит в eError? Их надо контролировать?

Евгений Кислов
06.12.2022, 17:16
Я открываю сокет и даю время на его открытие 10 секунд.

Вы ошибаетесь. Таймаут задается в микросекундах.

ВладОвен
06.12.2022, 17:26
А как в отладке увидеть реальное (фактическое) время открытия?

Евгений Кислов
06.12.2022, 17:29
А как в отладке увидеть реальное (фактическое) время открытия?

Убрать таймаут и запустить таймер от xEnable до xActive.

ВладОвен
06.12.2022, 17:35
А вот еще вопрос.
Тот, кто писал эту библиотеку (NBS), почему он сделал ФБ TCP_Client с параметром udiTimeOut,
а ФБ TCP_Read без этого параметра?
Ну было бы удобно задавать тайм-аут параметром, а не доп.таймером при чтении данных от сервера!
Разве нет?

ВладОвен
06.12.2022, 17:43
Ладно. Вот такой вопрос.
Сервер не отвечает, потому что нету в моей строке завершающего символа с кодом 0.
Как его туда поставить? Думаю так:
1. Раз за разом перед началом чистить мою 10к-строку, чтобы в ней все символы были равны 0.
2. Формировать мою строку на сервер.
3. Непосредственно при отправке в ФБ fbTcpWrite вписывать так:

fbTcpWrite(xExecute:=TRUE, hConnection:=fbTcpClient.hConnection, pData:=ADR(sDataSend), szSize:=SIZEOF(sDataSend) +1 );

Так получится?
Буфер будет на один символ длиннее, а там 0 (см. п1.).

Евгений Кислов
06.12.2022, 17:59
Ну было бы удобно задавать тайм-аут параметром, а не доп.таймером при чтении данных от сервера!
Разве нет?

Мы в OwenCommunication в ФБ UNM_TcpRequest так и сделали.
Так что наши клиенты могут обойтись и без доп. таймера.

Евгений Кислов
06.12.2022, 18:03
Если вы делаете SIZEOF от строки - то отправляется и "завершающий символ с кодом 0".
Потому что в CODESYS строки нуль-терминированные.
https://ru.wikipedia.org/wiki/%D0%9D%D1%83%D0%BB%D1%8C-%D1%82%D0%B5%D1%80%D0%BC%D0%B8%D0%BD%D0%B8%D1%80%D 0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%B0%D1%8F_%D1%81%D1 %82%D1%80%D0%BE%D0%BA%D0%B0

Более того - отправляется ровно столько байт, сколько выделено под строку (потому что вы используете SIZEOF).
Т.е. если в вашей STRING(10000) реально всего 100 символов - то будут отправлены эти 100 символов и еще 9901 нулевых байт следом.


Буфер будет на один символ длиннее, а там 0 (см. п1.).

Буфер не будет "на один символ длиннее".
Вы просто пытаетесь убедить блок в том, что размер вашей переменной на один байт больше, чем на самом деле.
Результат вы уже видели.

ВладОвен
06.12.2022, 18:08
Т.е. строка в таком случае всегда вылетает с этим символом 0? Даже, если я напишу "МАМА", то вылетят не 4, а 5 символов?
Т.е. я понял смысл нуль-терминальной строки, но как происходит выход такой строки в порт?

Евгений Кислов
06.12.2022, 18:36
Т.е. строка в таком случае всегда вылетает с этим символом 0? Даже, если я напишу "МАМА", то вылетят не 4, а 5 символов?
Т.е. я понял смысл нуль-терминальной строки, но как происходит выход такой строки в порт?

Если szSize := SIZEOF(sMama), где sMama имеет тип STRING(4) - то да.
С точки зрения порта - вы передаете указатель на начальный байт буфера и число байт в нем.
Про строки буфер вообще не в курсе - представление данных его не касается.

Евгений Кислов
06.12.2022, 18:57
Линукс линуксом, но мне кажется как и во второй версии в кдс нет фрагментации и по сети передается только не больше полутора килобайт

Ваши предположения неверны.

64346

ВладОвен
07.12.2022, 08:38
Т.е. если в вашей STRING(10000) реально всего 100 символов - то будут отправлены эти 100 символов и еще 9901 нулевых байт следом.
Значит надо перед отправкой посчитать количество реальных байт (искать до первого символа с кодом 0 и его тоже захватить с собой) и вставить в sizeof это значение.
Так?

Евгений Кислов
07.12.2022, 08:40
Значит надо перед отправкой посчитать количество реальных байт (искать до первого символа с кодом 0 и его тоже захватить с собой) и вставить в sizeof это значение.
Так?

Да, именно.
Можно сделать так: szSize:=TO_DWORD(LEN(sDataSend) + 1)

ВладОвен
07.12.2022, 11:23
Вы уверены? Мне кажется, что LEN не умеет отдавать больше, чем 255.
См.рис:

64378

ПС: bOlolo объявлена как dword. Тупо забыл имя поменять на dwOlolo.

Евгений Кислов
07.12.2022, 11:29
Вы уверены? Мне кажется, что LEN не умеет отдавать больше, чем 255.
См.рис:

64378

ПС: bOlolo объявлена как dword. Тупо забыл имя поменять на dwOlolo.


Да, вы правы, я ошибся - забыл, что у вас строки по 10000 символов.
В вашем случае надо использовать функцию StrLenA из библиотеки StringUtils.

ВладОвен
09.12.2022, 15:09
Да, можно использовать функцию StrConcatA из библиотеки StringUtils

Скажите, а какую функцию можно использовать для супер-длинных строк типа wString?

Евгений Кислов
09.12.2022, 17:50
Скажите, а какую функцию можно использовать для супер-длинных строк типа wString?

StrConcatW

ВладОвен
12.12.2022, 10:24
StrConcatW

Странно. Не заметил ее сразу.

Протестировал.
Она работала бы. Но вот длину строки 16000 символов она не принимает. Возвращает пустой результат.
А при длине строки 50 символов - работает нормально.
Может это как-то связано с тем, что используется не срока, а указатель на буфер?

capzap
12.12.2022, 10:30
Может это как-то связано с тем, что используется не срока, а указатель на буфер?

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

Евгений Кислов
12.12.2022, 10:50
Странно. Не заметил ее сразу.

Протестировал.
Она работала бы. Но вот длину строки 16000 символов она не принимает. Возвращает пустой результат.
А при длине строки 50 символов - работает нормально.
Может это как-то связано с тем, что используется не срока, а указатель на буфер?

Вообще, размер буфера при вызове функции задается в виде переменной типа INT, так что его максимальное значение 32767 байт.
Ваши строки по размеру близки к граничным значениям - например, склеить две строки по 16000 символов с помощью этой функции не получится.

Один из вариантов решения проблемы предложил выше capzap.

Альтернативный - использовать эту библиотеку:
https://dropmefiles.com/3g5BL

В ней есть своя версия функции StrConcatW, где буфер уже типа UDINT и есть ФБ StringWriter, который предназначен как раз для склеивания длинных строк.

ВладОвен
12.12.2022, 14:07
Ваши строки по размеру близки к граничным значениям - например, склеить две строки по 16000 символов с помощью этой функции не получится.

Сейчас детально посмотрел. Вы правы: при 16000 строка еще работает, а при 20000 - уже не работает.

ВладОвен
12.12.2022, 14:31
я бы советовал не заниматься строками на прямую, а брать массив байт...

Хорошо. Попробую этот метод.

Создаю массив и указатель:

arrDataSend : ARRAY[1..20000] OF BYTE; // Буфер для отправки
wPointer : WORD := 0; // Указатель для накидывания новых строк в буфер

wsLine01 : WSTRING := "Строка с кириллицей номер 01"; // Наша первая строка
wsLine02 : WSTRING := "Строка с кириллицей номер 02"; // Наша вторая строка
wsLine03 : WSTRING := "Строка с кириллицей номер 03"; // Наша третья строка

Программа по склеиванию:

// Первая строка:
MEM.MemMove(ADR(wsLine01), ADR(arrDataSend[wPointer]), len(wsLine01));
wPointer := wPointer + (len(wsLine01) * 2);

// Вторая строка:
MEM.MemMove(ADR(wsLine02), ADR(arrDataSend[wPointer]), len(wsLine02));
wPointer := wPointer + (len(wsLine02) * 2);

// Третья строка:
MEM.MemMove(ADR(wsLine03), ADR(arrDataSend[wPointer]), len(wsLine03));
wPointer := wPointer + (len(wsLine03) * 2);

// Отправка на сервер:
...
fbTcpWrite(xExecute:=TRUE, hConnection:=fbTcpClient.hConnection, pData:=ADR(arrDataSend), szSize:=wPointer);
...

Указатель каждый раз инкрементируется на len*2 (из-за WSTRING) и каждая следующая строка начинает ложиться в буфер по новому значению.
Тут есть вопрос: len не хочет отдавать длину строки, если она wstring. Видимо, len работает только со string. Какая функция отдаст длину wstring?

И вообще, я правильно понял предложенную реализацию?
Спасибо.

capzap
12.12.2022, 14:38
сложно это передать словами, проектом тоже конкретно надо заниматься чтоб сделать его обучающим, ловите мою тестовую если разберетесь хорошо

Я не пользуюсь wstring потому что в КДС2 их нет, но суть в том что каждую строку и указатель на неё надо задавать с фиксированным количеством символов, тогда в массив они будут попадать в нужные места занимая столько байт сколько нужно

ВладОвен
12.12.2022, 16:38
Использовать массив вместо строки получилось. И массив этот склеивается нормально (по указателю раз за разом).
Я проверяю это: запускаю обратную функцию MEM.MemMove() и собираю из массива переменную. В этой переменной вижу склеенные части. (в отладке. Строка видна и читабельна. А массив - это набор кодов: там хрен-читаемо...)
В массиве каждый байт занимает две ячейки. Это ведь аналог wstring. Поэтому так.

Но вот на сервер этот массив не отправляется. Или отправляется неправильно.
Я сделал тестовую короткую строку (около 80 символов), на которую сервер гарантированно отвечает коротким тестовым ответом.
Этот ответ не приходит. Увы.

Тут загвоздка:

fbTcpWrite(xExecute:=TRUE, hConnection:=fbTcpClient.hConnection, pData:=ADR(arrDataSend[1]), szSize:=wPointer+2);

Раньше там была ссылка на строку и это работало:

fbTcpWrite(xExecute:=TRUE, hConnection:=fbTcpClient.hConnection, pData:=ADR(sDataSend), szSize:=TO_DWORD(STU.StrLenA(ADR(sDataSend)) + 1));

capzap
12.12.2022, 17:09
Ну откуда же нам знать просто по тексту, хотя бы онлайн показывали. А так зачем к длине прибавляет двойку, не выходит ли это за диапазон массива и соответственно ошибочная отправка.

capzap
13.12.2022, 09:47
не встретил проблем и с wstring
на скрине то что получает ПК с контроллера и сам проект во вложении
64494

ВладОвен
13.12.2022, 10:39
Спасибо. Буду разбираться.