LINUX.ORG.RU

Преобразование сырых данных в структуру. C.

 


1

2

Добрый день, форумчане. Подскажите куда копать, а то как я понял познания, С у меня особо велики. Интересует работа с потоком сырых данных. Как это реализовано? Как выкристаллизовываются подобные вещи? На входе есть указатель на некоторые сырые данные. Известно какого рода данные передаются, но может быть и мусор. Как определить когда начинается не мусор, и как записать эти данные в некоторую структуру? Какие конструкции языка мне тут помогут? Я почти не нашел ничего, кроме как явно преобразовать указатель сырых данных к структуре общего вида.

Тоесть где-то так:


struct A
{
    unsigned size:16;
    unsigned char* data;
};
void foo(char* src)
{
   A* a = (A)src;
}
Какие исходники предложите посмотреть, где делается подобное? Желательно в упрощенном виде. Я не настолько крут, чтоб разбирать стек tcp в Линуксе. Как вообще с этими данными в си работать? Да что уж там, как создать массив точно заданных байт? Я понимаю, вопрос достаточно общий, но гугление мало чего дало. Буду рад пинку под зад, только бы в правильном направлении.


Структуру можно представить себе в виде проволочной сеточки. Ты накладываешь её на массив байт, и в ячейках сетки видишь какие-то значения.

void *p;
struct my_struct *my;
...
my = (struct my_struct *)p;

Такая конструкция позволяет разобрать область памяти, на которую ссылается p как структуру, и решить, нужные это данные, или нет.

При такой работе со структурами есть засада: выравнивание полей структуры в памяти. Делается это ради оптимизации по скорости. Структура получается с «дырами» между полями, что не подходит, если ты хочешь разобрать в виде структуры массив, поля в который были уложены строго друг за другом.

Запретить выравнивание конкретной структуры:

#pragma pack(push, 1)
struct my_struct
{
};
#pragma pack(pop)

uuwaan ★★ ()
Последнее исправление: uuwaan (всего исправлений: 1)
Ответ на: комментарий от uuwaan

Тааак, то есть я правильно пока всё делал. А как быть с последовательностью «блоков» Вот мой эксперимент:

#include <iostream>
#pragma pack(push, 1)
struct P
{
  
  unsigned a:8;
  unsigned b:8;
  unsigned c:16;
};
#pragma pack(pop)

int main()
{
 
char buf []= {0xFF,0xFF,0,1};
   P* a = (P*)buf;
   std::cout<<a->a<<std::endl;
   std::cout<<a->b<<std::endl;
   std::cout<<a->c<<std::endl;
}

Создав Buf из 4 char-ов (байтов), и присвоив это всё структуре из 3-х значений ( 1 два - по 8 бит, последнее - 16), я ожидаю такое развитие событий.

 11111111 11111111 00000000 00000001  // buf
 |   a   |   b    |       c         | // P* a
   a=255    b=255       c = 1
Первые 2 значения оправдали ожидание а последнее нет
[alex@archalex MODBUS]$ g++ first.cpp
[alex@archalex MODBUS]$ ./a.out 
255
255
256
Тоесть оно в памяти представило его так:

 11111111 11111111 00000000 00000001  // buf
                        --->
                        <---
                   00000001 00000000
 |   a   |   b    |       c         | // P* a
   a=255    b=255       c = 256

Почему?

vsrmis ()
Ответ на: комментарий от anonymous

Ага, тоесть у меня классически интеловский litle-edian byte order. Но это не гарантирует, что на другой машине будет то-же самое. А можно ли явно задать, какой byte order я хочу?

vsrmis ()
Ответ на: комментарий от vsrmis

Читать по-байтово и собирать в слова самому, проблем с Little Endian / Big Endian не будет.

andreyu ★★★★★ ()

Как определить когда начинается не мусор

в таких случаях использую «заголовок», то есть некий байт(может быть даже не один), который говорит что после него начинаются данные. Заголовок так же описывает тип данных и скорее всего размер, чтобы знать, сколько памяти нужно выделить.

Но это не гарантирует, что на другой машине будет то-же самое. А можно ли явно задать, какой byte order я хочу?

Самому кодировать данные. (На самом деле существует уже много библиотек, которые позволяют всё это делать).

Вообще, то что тебе надо называется словами «сериализация», и «маршаллинг».

Собсна весь TCP/IP - это не «rocket science», наждый пакет имеет какой-то формат, имеет заголовок, имеет данные. На каждом уровне ISO/OSI модели к пакету нижнего уровня добавляются дополнительные данные, необходимые для передачи пакета на этом кровне и так далее.

Например: http://www.infocellar.com/networks/images/OSI-2.png

invy ★★★★★ ()
Последнее исправление: invy (всего исправлений: 3)
Ответ на: комментарий от invy

Погуглю, спасибо.Только почему вся информация по этому поводу меня уводит в C#? Я на нём толком не писал и никогда не искал, от куда такой наплыв? Ну это оффтопик. Тем временем я продолжаю эксперимент и снова я чего-то недопонимаю.

#include <iostream>
#pragma pack(push, 1)
struct P
{
  
  unsigned size:16;
  unsigned char data;
  unsigned control:16;
};
#pragma pack(pop)

int main()
{
 
  char buf []= {0x05,0x00,'H','e','l','l','o',0x02,0x01};
  P* a = (P*)buf;
  a->control = *(char *) (buf+2+a->size);
  std::cout<<a->size<<std::endl;
  std::cout<<a->control<<std::endl;
}
------------------------
[alex@archalex MODBUS]$ g++ first.cpp
[alex@archalex MODBUS]$ ./a.out 
5
2

И так. я записал в данных, число 5 по системе litle-edian в 16 битовом числе, после этого 5 байт , посде этого число 258 по системе литл эдиан. Я присвоил указатель, а потом присвоил значению control этот-же указатель, только со смещением на 2( 2 первые 2 байта - size) + размер данных, хранящийся в size. control 16 битный. Я ожидаю, что в control попадёт 258, но оно в него не попадает.В него попадает 2, а это значит, что второй байт игнорируется и вместо него там нули.

vsrmis ()
Последнее исправление: vsrmis (всего исправлений: 1)
Ответ на: комментарий от vsrmis

Вы присвоили control значение только первого байта, когда разыменовывали указатель: *(char *). Сделайте так:

a->control = *(uint16_t *) (buf+2+a->size);

и получите то, что хотели.

А вообще мне непонятно: говорите «C», а программа на C++, - нехорошо обманывать людей.

Sorcerer ★★★★★ ()
Ответ на: комментарий от vsrmis
P* a = (P*)buf;

Здесь a начинает указывать на буфер, который при этом является константным массивом. Далее Вы присваиваете полю данной структуры новые данные, что может повлечь ошибку защиты памяти. Недовольство компилятора пресекается приведением типа в си-стиле.

В целом здесь следует рассматривать буфер как поток данных, который следует последовательно читать и преобразовывать в структуру.

static_lab ★★★★★ ()
Ответ на: комментарий от vsrmis

почему вся информация по этому поводу меня уводит в C#?

Гугл всё понимает.

anonymous ()

Гуглим примеры сериализации для C, вдохновляемся, пишем.

trex6 ★★★★★ ()

Глянь еще такую вещь как union, иногда бывает очень удобно, хотя многие почему-то union-ы не используют.

dvl36 ()

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

defective by design

Как определить когда начинается не мусор

Каждый блок валидных данных должен сопровождаться контрольной суммой. При чтении, если сумма сошлась, то свято веруем, что блок валидный.

Manhunt ★★★★★ ()

Какие исходники предложите посмотреть, где делается подобное?

Вообще это называется «сериализация». Готовая сериализация есть в boost, есть в google protocol buffers.

Manhunt ★★★★★ ()

Про Thrift уже сказали?

anonymous ()
Ответ на: комментарий от static_lab

Здесь a начинает указывать на буфер, который при этом является константным массивом.

Ничего подобного: этот буфер не является константным массивом.

Sorcerer ★★★★★ ()
Ответ на: комментарий от vsrmis

alex@archalex MODBUS
MODBUS

странно, что никто не вспомнил про libmodbus ;) ты ведь для этого всё замутил?!

aol ★★★★★ ()
Ответ на: комментарий от aol

Эта библиотека умеет только TCP и RTU, а мне надо научиться разбирать пакет в независимости от того, от куда он пришел, но спасибо, я буду подсматривать в их код static_lab

Здесь a начинает указывать на буфер, который при этом является константным массивом. Далее Вы присваиваете полю данной структуры новые данные, что может повлечь ошибку защиты памяти. Недовольство компилятора пресекается приведением типа в си-стиле.

В целом здесь следует рассматривать буфер как поток данных, который следует последовательно читать и преобразовывать в структуру.

Я буду очень благодарен, если вы мне покажете как правильней архитектурно это сделать, и как создать указатель на сырые данные и с точностью до байта определить эти данные в нём, а потом это всё записать в структуру, где определить где начинаются и заканчиваются данные. trex6 http://images.gameru.net/image/34c11ae91c.png Вот, я нахожу это издевательством со стороны гугла

Sorcerer

Вы присвоили control значение только первого байта, когда разыменовывали указатель: *(char *). Сделайте так:

a->control = *(uint16_t *) (buf+2+a->size);

и получите то, что хотели.

А вообще мне непонятно: говорите «C», а программа на C++, - нехорошо обманывать людей.

Так не сработало( ошибка компиляции), но зато сработало так.

a->control = *(short *) (buf+3+a->size);
Но я не особо хочу использовать short, что знает, какой он будет размерности на другой платформе, да и не совсем правильно это. Я ведь не объявлял свою переменную control как short, я её объявил как unsigned:16, и хочу записать в неё 16 бит и не short.

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

#include <iostream>
#pragma pack(push, 1)
struct P
{
  
  unsigned size:16;
  char* data;
  unsigned control:16;
};
#pragma pack(pop)

int main()
{
 
  char buf []= {0x05,0x00,'H','e','l','l','o',0x00,0x02,0x01};
  P* a = (P*)buf;
  
  a->control = *(short *) (buf+3+a->size);
  a->data = (char*)(buf+3);
  std::cout<<a->size<<std::endl;
  std::cout<<a->control<<std::endl;
  std::cout<<a->data[0]<<std::cout;
}
---------------------------
[alex@archalex MODBUS]$ g++ ./first.cpp
[alex@archalex MODBUS]$ ./a.out 
5
258
�0x6012c8[alex@archalex MODBUS]$ 
Я Создал массив char-ов, в котором в первых 2 байтах записал длинну строки - 5, после строки поставил 0 (меня учили на лабах по асму, что признак окончания строки - 0). Со скрипом в сердце присвоил control значение ( дурацкий short), и зпхотел это всё вывести на экран. Что я сделал не так?

vsrmis ()
Последнее исправление: vsrmis (всего исправлений: 1)
Ответ на: комментарий от Sorcerer

Ничего подобного: этот буфер не является константным массивом.

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

static_lab ★★★★★ ()
Ответ на: комментарий от vsrmis

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

указатель на char замени фиксированным массивом. убери присваивания элементам структуры.

Твоя беда в понимании указателей. Подумай, что на самом деле хранится в структуре? Ты сам определяешь структуру, считывая её по байтам.

Если речь о мадбасе, то что реализуешь-то хоть: мастера/слейв?

ziemin ★★ ()

Какую задачу ты решаешь? Если образование или нужно парсить существующий поток данных, тогда ок. Если сам хочешь обмениваться данными между своими приложениями или сохранять/загружать с диска и на диск, тогда погугли Google Protobuf. Там с форматом не накосячишь, формат очень компактный, сможешь читать и записывать из разных ЯП, автоматически конвертирует между endianess.

vertexua ★★★★☆ ()
Ответ на: комментарий от vsrmis

В двух словах: если данных не много (меньше 10MB/s), то проще всего их ковертировать в текстовый вид, а на другой стороне потом разбирать. И тестрировать будет в разы проще - берем telnet и вперед.

trex6 ★★★★★ ()
Ответ на: комментарий от vsrmis

Так не сработало( ошибка компиляции)

#include <inttypes.h>

Sorcerer ★★★★★ ()
Ответ на: комментарий от vsrmis

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

Вы сделали слишком много ошибок. Воспользуйтесь gdb, чтобы понять, что происходит.

Sorcerer ★★★★★ ()
Ответ на: комментарий от ziemin

Пока разбираю протокол, потом буду думать о мастерах и слейвах.
vertexuaК сожалению нужно именно modbus и именно С.
trex6 В некстовый нельхя :-( Именно из-за прогнозируемой скорости. Нужно бинарный. Sorcerer А #include <inttypes.h> входит в си стандарт? Я смогу его использовать например на армах?
Сейчас сижу с отладчиком

21	int main()
22	{
23	 
24	   char buf []= {0x05,0x00,'a','b','c','d','e',0x00,0x02,0x01};
25	   foo(buf);
(gdb) print buf
$1 = "\005\000abcde\000\002\001"

Я создал массив и отправил его функцию

int foo(void * data)
{
  std::cout<<(char*)(data+2)<<std::endl;
  P* a = (P*)calloc(1,sizeof(P));
  a->size = *(uint16_t*)data;
  a->data = (char*)calloc(a->size,sizeof(char));
  a->control = *(uint16_t*)(data+2+a->size);
  memcpy(data+2, a->data, a->size);
  std::cout<<a->size<<std::endl;
  std::cout<<a->data<<std::endl; 
  std::cout<<a->control<<std::endl;
}
__________________________________
[alex@archalex MODBUS]$ g++ ./first.cpp -g
./first.cpp: В функции «int foo(void*)»:
./first.cpp:17:27: предупреждение: в арифметическом выражении использован указатель «VOID *» [-Wpointer-arith]
   std::cout<<(char*)(data+2)<<std::endl;
                           ^
./first.cpp:21:34: предупреждение: в арифметическом выражении использован указатель «VOID *» [-Wpointer-arith]
   a->control = *(uint16_t*)(data+2+a->size);
                                  ^
./first.cpp:21:39: предупреждение: в арифметическом выражении использован указатель «VOID *» [-Wpointer-arith]
   a->control = *(uint16_t*)(data+2+a->size);
                                       ^
./first.cpp:22:15: предупреждение: в арифметическом выражении использован указатель «VOID *» [-Wpointer-arith]
   memcpy(data+2, a->data, a->size);
               ^
[alex@archalex MODBUS]$ ./a.out 
abcde
5

512
Во первых - предупреждение, что void* смещается,я понимаю, что ему тяжело понять на сколько ему смещать,но как это сделать явно? Потом, я всё делаю правильно, читая в control со смещением, и копируя память из data со смещением? И если да, то почему сначала, он умпешно выводит abcd ( до копирования), а потом не выводит ничего?

vsrmis ()
Последнее исправление: vsrmis (всего исправлений: 1)
Ответ на: комментарий от vsrmis

Моя невнимательность меня погубит. Изменил

 memcpy(a->data, data+2, a->size);
Заработало. Но мне нужен ответ правильно ли я делаю всё? Где мне надо дать по рукам? И как избавиться от предупреждения о смещении указателя void?

vsrmis ()
Ответ на: комментарий от vsrmis

Я обычно делаю так

// MOCK потока ввода
char buf []= {0x02,0x03,0x00,0x00,0x00,0x03};
int bufc = 0;
char getByte() {
  return buf[bufc++];
}
char getWord() {
  return getByte()*256+getByte();
}

// MOCK потока вывода
char obuf []= {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
int obufc = 0;
void putByte(char b) {
  return obuf[obufc++];
}
void putWord(uint b) {
  putByte(b >> 8);
  putByte(b & 0xFF);
}


int main() {
// Разбираем модбас
  // Наши регистры
  int regs[] = {123, 234, 345, 456};
  // Наш адрес
  char addr = 2;
  // *** Понеслась ***
  // Проверяем адрес
  if(getByte() == addr) {
    //Проверяем функцию. Будем работать только с 3
    if (getByte() == 3) {
      uint regAddr = getWord();
      // Допустим у нас 5 регистров
      if (regAddr < 5) {
        uint regCount = getWord();
        if (regCount + regAddr < 5) {
          // Формируем ответ
          putByte(addr);
          putByte(3);
          putByte(regCount*2);
          for(int i = regAddr; i < regCount + regAddr; i++) {
            putWord(regs[i]);
          }
        } else {/* Формируем исключение */}
      } else {/* Формируем исключение */}
    } else {/* Формируем исключение */}
  } // ничего не делаем - запрос не нам  
}

Нафига тебе структуры для модбаса?

ziemin ★★ ()
Последнее исправление: ziemin (всего исправлений: 2)
Ответ на: комментарий от vsrmis

А #include <inttypes.h> входит в си стандарт?

Вам почитать стандарт?

Sorcerer ★★★★★ ()

про байт ордер в дополнение к вышеперечисленному посмотри статьи Пайка и Томпсона ( они utf8 ага) - там показанно как инкапсулировать проблему порядка байт .

вообще просто считай (как ты уже и сделал) как char* (ну или byte* или shortint* если есть :) )

и уже анализируй поток байт по «теговым» байтам али «последовательностям бит» ну или по байтам размерам кадров.

т.е *(тип_поля*)((char*)база+смещение_поля)

qulinxao ★★☆ ()
Ответ на: комментарий от ziemin

:( поменяй ветки(т.е интвертируй условия)

сделай в ветке ошибки return

в результате получиш набор одноуровневых отсичений

if(!A){
// sldkfjsldkjf
  return errorNotA;
}

if(!B){...

}
.
.
.

а тут жара.

короче читай код Люк

qulinxao ★★☆ ()
Ответ на: комментарий от qulinxao

Это же прототип! Ты лучше выясни чем ТС кончил.

ziemin ★★ ()

ну обычно делают тако непереносимый быдлокод:

union
{
  struct
  {
    uint8_t raw_data[8];
  };
  struct
  {
    uint32_t x;
    uint32_t y;
  }
};
с локалхоста в локалхост(тот же) работает. Дальше надо учитывать порядок байт, и возможно ещё что-то.

emulek ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.