自己的C ++网络协议:如何定义唯一的二进制报文值

时间:2014-02-14 10:18:09

标签: c++ networking protocols

我希望通过编写自己的小二进制协议,通过TCP / IP与C ++交换数据。我的想法是通过以下列字节的形式定义值来实现协议:

BEGIN
LENGTH
FUNCTIONCODE
[data bytes] (optional)
CRC
END

如何定义“BEGIN”的二进制值,该值是唯一的,允许接收者识别新电报的开头?例如。如果我这样做:

static const int BEGIN = 0x41;

并且可选的数据字节也随机包含0x41值,这可能是接收器的问题。还是我错了?如果没有,我如何定义唯一的BEGIN和END值?

2 个答案:

答案 0 :(得分:1)

我通常会为简单的C ++网络编写类似的东西。 (但这次我从头开始编写这段代码并且根本没有测试过它!)

class Packet : public std::string {
public:
    struct __attibute__((__packed__)) Header {
        static const int BEGIN = 0x41;
        uint32_t begin;
        uint16_t length;
        uint16_t funcode;
        void init(uint16_t len) { begin = BEGIN; length = len; }
        bool verify() { return (BEGIN == begin); }
        char *buffer() { return reinterpret_cast<char *>(this); }
    };
    class __attibute__((__packed__)) Footer {
        uint32_t crc;
        char term;
        char newline;
        void init() { term = ';'; newline = '\n'; }
        bool verify() { return (';' == term && '\n' == newline); }
        char *buffer() { return reinterpret_cast<char *>(this); }
    };
public:
    void init(size_t n = 0) {
        resize(sizeof(Header) + n + sizeof(Footer));
        Header * const h = header();
        h->init(n);
        Footer * const f = footer();
        f->init();
    }

    // these two are valid only after init()
    Header *header() { return reinterpret_cast<Header*>(&(*this)[0]); }
    Footer *footer() { return reinterpret_cast<Footer*>(&(*this)[size() - sizeof(Footer)]); }
    template <typename T>
    T *buffer() { return reinterpret_cast<T *>(&(*this)[sizeof(Header)]); }

    void extend(size_t n) { resize(size() + n); }
};

int sendMessage(int funcode) {
    Packet p;
    switch (funcode) {
        case TIME: {
            timeval tv;
            gettimeofday(&tv, NULL);
            p.init(sizeof tv);
            Packet::Header * const h = p.header();
            h->funcode = funcode;
            timeval * const dst = p.buffer<timeval>();
            *dst = tv;
        } break;
        case PID: {
            const pid_t pid = getpid();
            p.init(sizeof pid);
            Packet::Header * const h = p.header();
            h->funcode = funcode;
            pid_t * const dst = p.buffer<pid_t>();
            *dst = pid;
        } break;
        ...
    }
    Packet::Footer * const f = p.footer();
    f->crc = ...;

    const ssize_t nSent = send(sock, p.data(), p.size(), 0);
    ...
}

Packet receiveMessage() {
    Packet ret;
    ret.init();
    Packet::Header * const h = ret.header();
    ssize_t nRecv = recv(sock, h->buffer(), sizeof *h, 0);
    ...
    if ( ! h->verify()) ...

    p.extend(h->length);
    nRecv = recv(sock, p.buffer<char>(), h->length, 0);
    switch (h->funcode) {
        case TIME: {
            timeval tv = *p->buffer<timeval>();
            tm theTm;
            localtime_r(&tv.tv_sec, &theTm);
            char buf[128];
            strftime(buf, sizeof buf, "%x %X", &tv);
            cout << "Server time == " << buf << endl;
        } break;
        case PID: {
            const pid_t pid = *p.buffer<pid_t>();
            cout << "Server PID == " << pid << endl;
        } break;
        ...
    }

    Packet::Footer * const f = ret.footer();
    nRecv = recv(sock, f->buffer(), sizeof *f, 0);
    if ( ! f->verify() || f->crc != ...) ...

    return ret; // inefficient data copy, only for a sample code
}

我建议您将BEGIN代码定义为类似0x48454c4f的内容,可以读取为ASCII "HELO",而不是像0x41这样的随机整数。我向页脚添加了两个额外的字节';''\n';当您将协议的数据包捕获转储到控制台时,您会发现它们很有用。请记住,可以始终使用sedperlpython或其他任何内容(但不是grep;它不会接受任意十六进制搜索字符串)分析包括数据包转储在内的任意二进制数据。如果您很好地设计协议,您的调试将更容易。 (首先,您应该考虑使用ASCII协议而不是二进制协议。)

答案 1 :(得分:0)

协议定义通常如下:

FUNCTIONCODE
LENGTH
DATA
CRC

并且没有必要钓“BEGIN”。读取通过使用固定长度读取FUNCTIONCODE和LENGTH,然后根据LENGTH读取数据+ CRC,然后再次...

但是,如果您认为额外的“BEGIN”标记可以帮助您,请选择“BEGIN”作为不是FUNCTIONCODE的值,然后您有

BEGIN
FUNCTIONCODE
LENGTH
DATA
CRC

对于BEGIN等于BEGIN之后的每个字节,发送BEGIN两次。但是,在这种情况下,阅读要复杂得多......