2011-01-20 33 views
6

Tôi đang viết bộ giải mã cho giao thức nhị phân (giao thức Javad GRIL). Nó bao gồm khoảng một trăm thư, với dữ liệu theo định dạng sau:Phân tích cú pháp luồng thông điệp nhị phân trong C/C++

struct MsgData { 
    uint8_t num; 
    float x, y, z; 
    uint8_t elevation; 
    ... 
}; 

Các trường là số nhị phân được mã hóa ANSI theo sau không có khoảng trống. Cách đơn giản nhất để phân tích cú pháp các thông báo như vậy là truyền một mảng đầu vào của byte vào loại thích hợp. Vấn đề là dữ liệu trong luồng được đóng gói, tức là không được căn chỉnh.

Trên x86, điều này có thể được giải quyết bằng cách sử dụng #pragma pack(1). Tuy nhiên, điều đó sẽ không hoạt động trên một số nền tảng khác hoặc sẽ phải chịu chi phí hoạt động do tiếp tục làm việc với dữ liệu không được căn lề.

Cách khác là viết chức năng phân tích cú pháp cụ thể cho từng loại thư, nhưng như tôi đã đề cập, giao thức bao gồm hàng trăm thư.

Một cách khác là sử dụng chức năng như Perl unpack() và lưu trữ định dạng thư ở đâu đó. Giả sử, chúng tôi có thể #define MsgDataFormat "CfffC" và sau đó gọi unpack(pMsgBody, MsgDataFormat). Điều này là ngắn hơn nhiều nhưng vẫn dễ bị lỗi và dư thừa. Hơn nữa, định dạng có thể phức tạp hơn vì thông điệp có thể chứa mảng, do đó trình phân tích cú pháp sẽ chậm và phức tạp.

Có giải pháp phổ biến và hiệu quả nào không? Tôi đã đọc this post và Googled xung quanh nhưng không tìm thấy cách nào tốt hơn để làm điều đó.

Có lẽ C++ có giải pháp?

+0

Tôi cho rằng sử dụng các loại tuple để xác định các thông báo, bạn có thể viết các mẫu chức năng lặp lại trên các thành viên tuple và gọi hàm trích xuất thích hợp cho bất kỳ loại nào bạn đang sử dụng. Tuy nhiên, tôi không thể nghĩ ra một ý tưởng để chuyển đổi tự động từ những bộ dữ liệu này sang cấu trúc khác. – sbi

+0

Giả sử bạn đang sử dụng gói MSVC++ '#pragma (1)' nên hoạt động ngay cả trên các nền tảng khác. Việc đóng gói được thực hiện theo các thay đổi bit và mặt nạ, chứ không phải các bản sửa lỗi liên kết của hệ điều hành. –

+0

Dữ liệu của bạn chưa được giải mã, chưa được căn chỉnh. Vì vậy, chỉ có cách chính xác để làm là truy cập byte khôn ngoan như 'giải nén' được gợi ý bởi @larsmans. – 9dan

Trả lời

1

Câu trả lời đơn giản là không, nếu thư là định dạng nhị phân cụ thể không thể đơn giản được đúc, bạn không có lựa chọn nào khác ngoài việc viết một trình phân tích cú pháp cho nó. Nếu bạn có mô tả thư (giả sử xml hoặc một số dạng mô tả được phân tích cú pháp dễ dàng), tại sao bạn không tự động tạo mã phân tích cú pháp từ mô tả đó? Nó sẽ không được nhanh như một diễn viên, nhưng sẽ là cảnh chết tiệt nhanh hơn tạo ra hơn viết mỗi tin nhắn bằng tay ...

3

Giải pháp của tôi để phân tích đầu vào nhị phân là sử dụng lớp Reader, vì vậy mỗi mục nhập tin nhắn bạn có thể xác định những gì được đọc và người đọc có thể kiểm tra overruns, underruns, ....

Trong trường hợp bạn:

msg.num = Reader.getChar(); 
msg.x = Reader.getFloat(); 
msg.y = Reader.getFloat(); 
msg.z = Reader.getFloat(); 
msg.elevation = Reader.getChar(); 

Nó vẫn còn rất nhiều công việc và dễ bị lỗi, nhưng ít nhất nó giúp kiểm tra lỗi.

+3

"Lớp Reader" == 'std :: istream' hoặc' std :: streambuf'. –

+0

@Billy: đúng vậy. Tôi đã sử dụng lớp Reader một thời gian, vì vậy tôi chưa bao giờ sử dụng cho một hệ thống chuẩn hơn. Vâng phát hiện. – stefaanv

+1

Vâng, nhưng đây là những gì tôi gọi là "viết thường trình phân tích cú pháp specicfic cho mỗi thư") – gaga

7

Ok, sau đây biên dịch cho tôi với VC10 và với GCC 4.5.1 (on ideone.com). Tôi nghĩ rằng tất cả các nhu cầu này của C++ 1x là <tuple>, nên có sẵn (như std::tr1::tuple) trong các trình biên dịch cũ hơn là tốt.

Nó vẫn cần bạn nhập một số mã cho mỗi thành viên, nhưng đó là mã rất nhỏ. (Xem giải thích của tôi ở cuối.)

#include <iostream> 
#include <tuple> 

typedef unsigned char uint8_t; 
typedef unsigned char byte_t; 

struct MsgData { 
    uint8_t num; 
    float x; 
    uint8_t elevation; 

    static const std::size_t buffer_size = sizeof(uint8_t) 
             + sizeof(float) 
             + sizeof(uint8_t); 

    std::tuple<uint8_t&,float&,uint8_t&> get_tied_tuple() 
    {return std::tie(num, x, elevation);} 
    std::tuple<const uint8_t&,const float&,const uint8_t&> get_tied_tuple() const 
    {return std::tie(num, x, elevation);} 
}; 

// needed only for test output 
inline std::ostream& operator<<(std::ostream& os, const MsgData& msgData) 
{ 
    os << '[' << static_cast<int>(msgData.num) << ' ' 
     << msgData.x << ' ' << static_cast<int>(msgData.elevation) << ']'; 
    return os; 
} 

namespace detail { 

    // overload the following two for types that need special treatment 
    template<typename T> 
    const byte_t* read_value(const byte_t* bin, T& val) 
    { 
     val = *reinterpret_cast<const T*>(bin); 
     return bin + sizeof(T)/sizeof(byte_t); 
    } 
    template<typename T> 
    byte_t* write_value(byte_t* bin, const T& val) 
    { 
     *reinterpret_cast<T*>(bin) = val; 
     return bin + sizeof(T)/sizeof(byte_t); 
    } 

    template< typename MsgTuple, unsigned int Size = std::tuple_size<MsgTuple>::value > 
    struct msg_serializer; 

    template< typename MsgTuple > 
    struct msg_serializer<MsgTuple,0> { 
     static const byte_t* read(const byte_t* bin, MsgTuple&) {return bin;} 
     static byte_t* write(byte_t* bin, const MsgTuple&)  {return bin;} 
    }; 

    template< typename MsgTuple, unsigned int Size > 
    struct msg_serializer { 
     static const byte_t* read(const byte_t* bin, MsgTuple& msg) 
     { 
      return read_value(msg_serializer<MsgTuple,Size-1>::read(bin, msg) 
          , std::get<Size-1>(msg)); 
     } 
     static byte_t* write(byte_t* bin, const MsgTuple& msg) 
     { 
      return write_value(msg_serializer<MsgTuple,Size-1>::write(bin, msg) 
           , std::get<Size-1>(msg)); 
     } 
    }; 

    template< class MsgTuple > 
    inline const byte_t* do_read_msg(const byte_t* bin, MsgTuple msg) 
    { 
     return msg_serializer<MsgTuple>::read(bin, msg); 
    } 

    template< class MsgTuple > 
    inline byte_t* do_write_msg(byte_t* bin, const MsgTuple& msg) 
    { 
     return msg_serializer<MsgTuple>::write(bin, msg); 
    } 
} 

template< class Msg > 
inline const byte_t* read_msg(const byte_t* bin, Msg& msg) 
{ 
    return detail::do_read_msg(bin, msg.get_tied_tuple()); 
} 

template< class Msg > 
inline const byte_t* write_msg(byte_t* bin, const Msg& msg) 
{ 
    return detail::do_write_msg(bin, msg.get_tied_tuple()); 
} 

int main() 
{ 
    byte_t buffer[MsgData::buffer_size]; 

    std::cout << "buffer size is " << MsgData::buffer_size << '\n'; 

    MsgData msgData; 
    std::cout << "initializing data..."; 
    msgData.num = 42; 
    msgData.x = 1.7f; 
    msgData.elevation = 17; 
    std::cout << "data is now " << msgData << '\n'; 
    write_msg(buffer, msgData); 

    std::cout << "clearing data..."; 
    msgData = MsgData(); 
    std::cout << "data is now " << msgData << '\n'; 

    std::cout << "reading data..."; 
    read_msg(buffer, msgData); 
    std::cout << "data is now " << msgData << '\n'; 

    return 0; 
} 

Đối với tôi đây in

 
buffer size is 6 
initializing data...data is now [0x2a 1.7 0x11] 
clearing data...data is now [0x0 0 0x0] 
reading data...data is now [0x2a 1.7 0x11] 

(Tôi đã rút ngắn kiểu MsgData của bạn để chỉ chứa ba thành viên dữ liệu, nhưng điều này chỉ là để thử nghiệm.)

Đối với mỗi loại thư, bạn cần xác định hằng số tĩnh buffer_size và hai hàm get_tied_tuple() thành viên, một const và một số không const, cả hai được triển khai theo cùng một cách. (Tất nhiên, những người này cũng có thể không phải là thành viên, nhưng tôi đã cố gắng giữ họ gần với danh sách các thành viên dữ liệu mà họ liên kết.)
Đối với một số loại (như std::string), bạn sẽ cần phải thêm quá tải đặc biệt các chức năng detail::read_value()detail::write_value().
Phần còn lại của máy móc vẫn giữ nguyên cho tất cả các loại tin nhắn.

Với hỗ trợ đầy đủ C++ 1x, bạn có thể loại bỏ hoàn toàn việc loại bỏ các loại trả về rõ ràng của các hàm thành viên get_tied_tuple(), nhưng tôi chưa thực sự thử điều này.

+0

Chà. Đó là mát mẻ, tôi cần một thời gian để suy nghĩ về nó) – gaga

+0

ví dụ tốt đẹp cho việc sử dụng tuple ... làm cho một cú pháp khá tốt đẹp. C++ 11 rocks.All tốt hơn là bạn cung cấp toàn bộ nguồn trên ideone.com! – oliver

1

Tôi không nghĩ rằng bạn có thể tránh viết thường trình phân tích cú pháp cụ thể cho mọi thư trong C++ thuần túy (không sử dụng pragma). Nếu tất cả các thông điệp của bạn là đơn giản, POD, cấu trúc giống như C, tôi nghĩ giải pháp đơn giản nhất là viết trình tạo mã: đặt cấu trúc của bạn trong tiêu đề mà không có công cụ C++ khác và viết một trình phân tích cú pháp đơn giản (perl/python/bash script bằng cách sử dụng một vài biểu thức thông thường là đủ) - hoặc tìm kiếm một - có thể tìm thấy tên biến trong bất kỳ thư nào; sau đó sử dụng nó để tự động tạo ra một số mã cho bất kỳ thông báo để đọc nó, như thế này:

YourStreamType & operator>>(YourStreamType &stream, MsgData &msg) { 
    stream >> msg.num >> msg.x >> msg.y >> msg.z >> msg.elevation; 
    return stream; 
} 

chuyên YourStreamType 's operator>> cho bất kỳ loại hình cơ bản thông điệp của bạn có chứa và bạn nên thực hiện:

MsgData msg; 
your_stream >> msg; 
0

Bạn luôn có thể căn chỉnh bộ nhớ của mình:

uint8_t msg[TOTAL_SIZE_OF_THE_PARTS_OF_MsgData]; 

sizeof(MsgData) trả về kích thước của MsgData + đệm padding, bạn có thể tính toán

enum { TOTAL_SIZE_OF_THE_PARTS_OF_MsgData = 
    2*sizeof(uint8_t)+ 
    3*sizeof(float)+sizeof(THE_OTHER_FIELDS) 
} 

Sử dụng enums cho các hằng số như vậy là một khái niệm đã được chứng minh trên một số máy.

đọc thông báo nhị phân vào mảng msg. Sau đó bạn có thể đúc các giá trị vào các giá trị MsgData:

unsigned ofs = 0; 
MsgData M; 
M.num = (uint8_t)(&msg[ofs]); 
ofs += sizeof(M.num); 
M.x = (float)(&msg[ofs]); 
ofs += sizeof(M.x); 

và vân vân ...

hoặc sử dụng memcpy nếu bạn không thích loại phôi:

memcpy(&M.x,&msg[ofs],sizeof(M.x)); ... 
Các vấn đề liên quan