为 UDP 网络打包消息

Packing messages for UDP networking

我刚刚在我目前正在开发的游戏引擎中遇到以下方法:

bool addmsg(int type, const char *fmt, ...) {
    if (!connected) return false;
    static uchar buf[MAXTRANS];
    ucharbuf p(buf, sizeof (buf));
    putint(p, type);
    int numi = 1, numf = 0, nums = 0, mcn = -1;
    bool reliable = false;
    if (fmt)
    {
        va_list args;
        va_start(args, fmt);
        while (*fmt) switch (*fmt++)
            {
            case 'r': reliable = true; break;
            case 'c':
                {
                    gameent *d = va_arg(args, gameent *);
                    mcn = !d || d == player1 ? -1 : d->clientnum;
                    break;
                }
            case 'v':
                {
                    int n = va_arg(args, int);
                    int *v = va_arg(args, int *);
                    loopi(n) putint(p, v[i]);
                    numi += n;
                    break;
                }

            case 'i':
                {
                    int n = isdigit(*fmt) ? *fmt++ - '0' : 1;
                    loopi(n) putint(p, va_arg(args, int));
                    numi += n;
                    break;
                }
            case 'f':
                {
                    int n = isdigit(*fmt) ? *fmt++ - '0' : 1;
                    loopi(n) putfloat(p, (float)va_arg(args, double));
                    numf += n;
                    break;
                }
            case 's': sendstring(va_arg(args, const char *), p); nums++; break;
            }
        va_end(args);
    }
    int num = nums || numf ? 0 : numi, msgsize = server::msgsizelookup(type);
    if (msgsize && num != msgsize) { fatal("inconsistent msg size for %d (%d != %d)", type, num, msgsize); }
    if (reliable) messagereliable = true;
    if (mcn != messagecn)
    {
        static uchar mbuf[16];
        ucharbuf m(mbuf, sizeof (mbuf));
        putint(m, N_FROMAI);
        putint(m, mcn);
        messages.put(mbuf, m.length());
        messagecn = mcn;
    }
    messages.put(buf, p.length());

    return true;
}

这太可怕了。人们实际上认为像这样通过网络发送消息是个好主意:

addmsg(MY_ACTION, "rii3ii5", 1, 42, 42, 42, e.type, e.attr1, e.attr2, e.attr3, e.attr4, e.attr5)

哦,是的!超级可读。现在,我正试图积极地慢慢重构它。我正在考虑使用模板动态解释消息中的类型并正确编码。有没有人建议如何从这个废话开始?谢谢!

这是 Qt 方式,带有数据流。

class DataStream {
    /* has an internal buffer, can use std::string, std::vector<char> or even uchar [] */

    DataStream &operator << (int i) {
        putint(internal_buffer, i);
        return this;
    }
    /* ... */

    /* Multiple serialization */
    void pack() {
        return;
    }

    template <typename T, typename ...Params>
    void pack(const T &item, Params&&...params) {
        (*this) << item;
        pack(std::forward<Params>(params)...);
    }

    const char* buffer() const {
        return internal_buffer.data();
    }
};

这样,您将拥有:

template<typename ...Params>
bool addmsg(int type, Params &&... params) { 
    DataStream stream;
    stream << type;

    ...

    stream.pack(std::forward<Params>(params)...);

    //Get stream buffer and use that in msg
}

这种事情很危险,你必须确保两个平台上的整数是相同的。使用 uint32_t 之类的类型而不是 int(调用 addmsg 函数时)可能更明智,除非您将 DataStream 限制为仅一种整数。

您可以扩展 DataStream 以支持自定义结构等其他类型:

struct MyStruct {
    int a,b,c;
    float f;
};

DataStream &operator << (DataStream &stream, const MyStruct &s) {
    stream << s.a << s.b << s.c << s.f;
    return stream;
}

这样 e 成员的 5 个参数就可以直接传递给 addmsg 函数:

addmsg(MY_ACTION, 1, 42, 42, 42, e);

你可以只使用函数重载。真的不需要模板。

#include <string>

constexpr size_t MAXTRANS = 128;

enum reliable_t { reliable };

class message
{
public:
    void send() { }

    message& operator<<(reliable_t)
    {
        m_reliable = true;
        return *this;
    }

    message& operator<<(int x)
    {
        // add int to message
        return *this;
    }

    message& operator<<(float x)
    {
        // add float to message
        return *this;
    }

    message& operator<<(const std::string& x)
    {
        // add string to message
        return *this;
    }

private:
    unsigned char m_buf[MAXTRANS];
    bool m_reliable = false;
};

int main()
{
    message msg;
    msg << reliable << 42 << 42 << "ultimate question";
    msg.send();
    return 0;
}

这里是link到coliru的例子。