user3176878
user3176878

Reputation: 83

Sending structs over a UDP winsock connection, size of struct?

I'll try to keep this short. So I'm making a network interface which, inherently will be used for creating simple video games. I initially began designing it in TCP but I found it to be too slow so I switched to UDP.

My current idea is this: I want to send structs, where each struct corresponds to a specific message. Of course, I need to know the size of the struct when the server or client receives it. I fix this by first sending a standardized struct which contains the type of struct which is coming next, and then handle that accordingly. So I'll have something like a shared header file between the client and server which would look like this:

enum data_type { PLAYER_MESSAGE, PLAYER_LOCATION};

struct enum_type {
int nClientID;
data_type data_identifier;
};

struct player_message {
char message[128];
};

struct player_location {
int x, y;
};

Then, in the function that receives data sent by the other client I would do something like this:

enum_type enum_check;
recvfrom(socket, (char*)&enum_check, sizeof(enum_type), 0, (struct sockaddr*)&client, &client_length);

switch(enum_check.data_identifier)
{

    case: PLAYER_MESSAGE
    player_message temp_message;
    recvfrom(socket, (char*)&temp_message, sizeof(player_message), 0, (struct sockaddr*)&client, client_length);
    //Print message
    break;

    case: PLAYER_LOCATION
    player_location temp_location;
    recvfrom(socket, (char*)&temp_location, sizeof(player_location), 0, (struct sockaddr*)&client, &client_length);
    //move player
    break;
}

The system works pretty well with all sorts of messages and also behaves decently under (some) stress. The problem of course is that every single client would be using the same recvfrom to first check the message type and then to handle it accordingly. In short, I don't trust this system one bit because it makes too many assumptions. It makes the assumption that a receive giving a message type is always followed by the correct message. With UDP, you can never really guarantee this is the case, which is leaving me stumped. (I am also aware of the problem with endianness and computer architectures with this approach, but I'm side-lining that for now.)

I figured a quick and easy approach would be to have only one type of struct, including the client ID, how to use its data (enum), room for around 10 numbers as well as some text. This way I would not have to make assumptions but would of course send tons of needless data constantly (which wouldn't be a problem if bandwidth isn't really an issue, but it seems like a pretty dumb solution nonetheless).

Any suggestions here? I've contemplated just using a network library such as ENET (then again, wouldn't I get the same problems trying to send structs?) but that seems like such a defeat. I'm only doing this to learn after all.

Upvotes: 3

Views: 841

Answers (3)

Non-maskable Interrupt
Non-maskable Interrupt

Reputation: 3911

I initially began designing it in TCP but I found it to be too slow so I switched to UDP.

I recommend reading this before you come into such conclusion: udp-vs-tcp-how-much-faster-is-it. In most situration TCP is fairly good. I'm getting 400K packets per second for my TCP-based server running ping-pong test on entry level xeon i7 with dual NIC, and pretty quick latency.

How fast do you need?

Upvotes: 0

camelccc
camelccc

Reputation: 2992

Why not use a union, of structs, where the 1st member gives the type of the struct and is common to all structs in the union. Your quick and easy approach isn't far off, since if all you have is 10 numbers the headers of the packets are going to dominate your bandwidth usage anyway. You really don't want one packet to check the type on the next - that is asking for trouble sinbce in UDP there is no guarantee that the packets arrive in the same order they were sent.

Upvotes: 1

Deduplicator
Deduplicator

Reputation: 45684

Good thing you already caught on to UDP sometimes reordering messages in arbitrary ways.

Your solution of always sending the same struct containing all possible info is nearly correct:

Define yourself a base message, like e.g.:

struct msg_common{uint8_t sender, receiver; uint16_t message_id;};
// Take note that I used fixed-width types here!

And make all specific messages begin with that, reserving non-overlapping ranges of the common prefix for each one.

You won't get around validating that the message is valid, which means:

  • It is complete, aka. the UDP packet contained the whole message.
  • It should be processed now, aka. there is no previous message which has to be processed first.
    • Either include all data neccessary for reconstructing previous non-acknowledged messages.
    • Or accept dropping an arbitrary amount of messages.
    • Or allow for requesting a resend.
  • It does not violate some other semantic constraints of your game.

Upvotes: 1

Related Questions