【问题标题】:Own C++ network protocol: How to define unique binary telegram values自己的 C++ 网络协议:如何定义唯一的二进制电报值
【发布时间】:2014-02-14 10:18:08
【问题描述】:

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

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

如何为“BEGIN”定义一个唯一的二进制值,使接收者能够识别新电报的开头?例如。如果我这样做:

static const int BEGIN = 0x41;

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

【问题讨论】:

  • 看看 MP3 是如何定义它的格式的。它被设计为可嵌入到大多数任何东西中,并且包括内部一致性,因此您可以验证您确实找到了一个 MP3 数据帧,而不是偶然碰巧共享 MP3 帧头字节的东西。

标签: c++ networking protocols


【解决方案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 协议而不是二进制协议。)

【讨论】:

  • 一些领域,包括科学应用和游戏,通常使用二进制协议。二进制协议被认为对程序员来说更难使用,但它们在带宽/cpu 上更容易。
【解决方案2】:

协议定义通常是这样的:

FUNCTIONCODE
LENGTH
DATA
CRC

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

但是,如果您认为附加的“BEGIN”标记对您有帮助,请选择“BEGIN”作为不是 FUNCTIONCODE 的值,然后您就有了

BEGIN
FUNCTIONCODE
LENGTH
DATA
CRC

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

【讨论】:

    猜你喜欢
    • 2012-04-08
    • 2017-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-14
    • 2021-05-25
    • 2021-04-18
    相关资源
    最近更新 更多