Sending char strings in structs

Started by
8 comments, last by zfvesoljc 11 years, 6 months ago
Hey,
I've got a lobby server and would like to send to it some info. I thought that I would make structure which contains nickname and user's message text. However I dont want to send a structure like:

struct data
{
char nickname[20];
char text[255];
}

becouse it's not efficient. Users might have less letters in nick than 20 or type less text than 255 chars and that would be a waste of time sending such a structure everytime... I guess it's impossible to send a pointer becouse it doesn't make any sense... I know I could merge those 2 arrays into 1 and send not a sturct but just char string but there will probably be some more elements to send for each user and I would like to have it all in one structure

I would be evry grateful for help!
Advertisement
Just send the length of the strings before each string. In my case, I know the receiving format, and thus i assume that if there are 3 strings in the struct, the first 3 bytes are the sizes of each string. Then use these numbers to help slice it up.
You need a form of serialization," or "marshalling."

Built-in functions to do this in most Java, C# and similar libraries aren't very efficient (they encode type information, versioning, and other unnecessary-for-games data.)

There are various articles (mostly in C++, some in C#) about this on my blog: http://www.enchantedage.com/taxonomy/term/17
enum Bool { True, False, FileNotFound };
thanks a lot for your articles hplus! I find them very useful. I have one more question though. So far I've been sending only 1 type of struct to server and 1 type from server to client. So I always knew the received structure type but I've read on one article sth like this:
Note that I'm assuming that you know what the packet is through some data that comes before the packet. And, if you're on TCP, I'm assuming you know how big the data is, again through some data before the packet.[/quote]
In UDP messages don't come in order so I guess I can't realize what's incoming data by some data that arrived before? I thought that I could recognize structure type by structure's size? and then convert it to proper structure types in my program.
And everytime I send a struct with game data there is a header data as well, which is char header[16]. I'm getting quite confused... The header data is used only to recognize that incoming packet comes from good source?(my server) or not? And is that a good approach or a waste of bandwidth to send such a header data with every sturct I'm sending?
Use a network library, i.e. enet, which works on udp protocol but also supports reliable packets. That way you can send important messages as reliable (and ordered) and state changes like position, rotation as unreliable messages (spam like, multiple times per seconds). I wouldn't use size as a message descriptor, just use header ID for that.
Thanks a lot for reply zfvesoljc! I'm so glad I don't have to do all that by myself. From the description I can tell that this library suits me almost perfectly. The only problem might be(it obviously will be) Nat punch through... But wow, why haven't I found this library earlier? The only libs I was finding were those commercial and very expensive(raknet etc).
Anyway I still don't quite get the idea of sending different sturctures and recognizing their types. The variable containing header ID must be sent in one the same struct in which I have all other information, right?(position, rotation etc) I first need to convert the incoming data to existing structure type in order to access its elements so if this header ID already belongs to some certain struct how can I access it from unknown structure to get info about structure type?
Typically, you send more than one message in a single UDP datagram.
Typically, you define your message format as something like:
<type> <length> <... data ...>
One byte each for "type" and "length" gets you pretty far.

Thus, your actual UDP datagram would look like:

<type> <length> <data * length> <type> <length> <data * length> ...


This allows you to figure out what each message is, and how long it is (so you can find the next message.)

The header typically will contain an application-specific identifier, serial number (for de-duplication, etc) and perhaps server time information and maybe a checksum, if you don't trust the pretty dumb UDP checksum algorithm, or want to do cryptographic signatures or whatever.

For example, here's one possible format:

<id0> <id1> <serial> <acklast> <ackseq> <timebyte>

id0 and id1 are bytes that you choose, that mean that you can tell your packets apart from random packets that some application might send on your particular port. (This is most important for broadcast, but useful for regular UDP as well)
"serial" is simply incremented by 1 for each UDP packet sent by the sender, and wraps around from 255 to 0.
"acklast" is the serial number of the last packet received from the other end, used for knowing which packets made it. <ackseq> is how many packets before <acklast> were received without any drops, capped at 255.
Note that this does not allow you to positively identify packet sequence numbers that were NOT received, but you typically don't need that.
Finally, "timebyte" is perhaps something like the current server clock in units of ten milliseconds, again wrapping around from 255 to 0. This can be used for clock synchronization, etc.
enum Bool { True, False, FileNotFound };
I've use variable width encoding for bitpacking size types. for example use the first 7 bits of a byte for size, and if the number is greater than 127, encode the rest of that number into another byte, and so on.

There are many ways to read/write data into a packet. In any case, it will almost always be based around a serialisation / deserialisation layer that encode/decode your objects properties into bitstreams / bytestreams, which are then passed to the transport layer to be sent off.

Simply dumping raw objects into packets is highly inefficient, does not provide scope for error checking (value range, value types, versioning, ...), object aggregation (basically, objects containing pointers), and does not support cross-platform communications (which isn't a big deal, but for the sake of completion...).

would look something like this


struct data
{
char nickname[20];
char text[255];

virtual bool serialise(bistream& out) const
{
bool ok = out.write_string(nickname, sizeof(nickname));
ok = ok && out.write_string(text, sizeof(text));
return ok;
}
virtual bool deserialise(const bistream& in)
{
bool ok = in.read_string(nickname, sizeof(nickname));
ok = ok && in.read_string(text, sizeof(text));
return ok;
}
};


Then transmitting your packet to all clients in the game could look like...

socket.broacast_packet(out.begin(), out.size());


stuff like that. Really look up some serialisation as it's really handy for what you want to do.

Everything is better with Metal.

thanks a lot for answers, everything looks clearer now!

The only problem might be(it obviously will be) Nat punch through...


Yes, you need "someone" on the outside to do that. But if you'll be using a matchmaking service (like steam, or live on xbox) you can use that.


Anyway I still don't quite get the idea of sending different sturctures and recognizing their types.


Every message I send/receive contains a message ID, which is basically LAYER + MESSAGETYPE, where layer denotes "code" layer that handles it (like network for connections, session for client/member changes, game for well, game stuff) and messagetype denotes what that message is. Based on that info, a proper message handler is selected. Just simple routing basically...

This topic is closed to new replies.

Advertisement