为 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的例子。
我刚刚在我目前正在开发的游戏引擎中遇到以下方法:
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的例子。