Advertisement

Trying to get an idea of what a move object stream looks like in UDP?

Started by October 25, 2015 11:39 PM
6 comments, last by hplus0603 9 years ago

I have a question about what the stream might look like from client to server and back. I would like to make use of SDL NET, so it basically takes care of most of the low level stuff.

I looked at this example at gpwiki.org

Here is the loop


while (!quit)
	{
		/* Wait a packet. UDP_Recv returns != 0 if a packet is coming */
		if (SDLNet_UDP_Recv(sd, p))
		{
			printf("UDP Packet incoming\n");
			printf("\tChan:    %d\n", p->channel);
			printf("\tData:    %s\n", (char *)p->data);
			printf("\tLen:     %d\n", p->len);
			printf("\tMaxlen:  %d\n", p->maxlen);
			printf("\tStatus:  %d\n", p->status);
			printf("\tAddress: %x %x\n", p->address.host, p->address.port);
 
			/* Quit if packet contains "quit" */
			if (strcmp((char *)p->data, "quit") == 0)
				quit = 1;
		}		
	}

In this example, the server just prints the incoming packet. My first question is this. Do I understand it correctly, that if a packet contains

->quit, then it simply quits? What I mean is, is it simply a message "quit"?

To better ask my question, maybe someone can clarify this.

If I use the above code, but instead of printing it to the screen I make use of the packets, then what would be a message to move a 2D Tile, up, down, left or right.

In other words, I would like to setup a server and client in SDL. They both load a 10x10 tile map. the server would put a monster in tile (0,0)

and the client would put a player in tile (9,9). So if the server moves the monster right in real time, and the player moves up in real time, what would the back and forth messages look like?

I apoligize, but I might not have the concept down correctly yet. If anyone has a simplified explanation, tutorial, or source example in C++, I would really appreciate it if you can share it?

The goal is a tile based multiplayer game. Perhaps 5 - 10 players, with a larger tile map of course.

If I use the above code, but instead of printing it to the screen I make use of the packets, then what would be a message to move a 2D Tile, up, down, left or right.

Whatever you want it to be.

The message is just bytes - in this case, it happens to be the four bytes <tt>['q', 'u', 'i', 't']</tt>.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Advertisement

Ok, makes sense, but just to make sure we are on the same page. I can come up with my own table of bytes such as ->

mov_u

mov_d

mov_l

mov_r

which corresponds to move up, down, left and right. and I basically pass the message such as mov_l around the internal functions of the client and server right? and I parse it?

Yes, a very simple implementation might work something like that.

Are there any good examples out there for how this can be efficient with both client to server and server to client use? For example, the client will have graphics functions to display the scene, but the server will also (except it would be administrative - so all players can be seen and queried).

Also, to go one step further with the byte example above, am I going the right direction if I create a packet like protocol, so messages are smaller? In other words would I want to do that for efficiency (common sense? or no?)

So there might be an action packet like _ac:m:udlrlrudl and that would mean -> action move up down left right left ... etc. and I implement the parser to handle it locally?

lol - which comes to my next question. After this I can start trying to implement it. How can I keep the server in sync with all clients when moving a monster.

Have you browsed the Networking and Multiplayer FAQ? Question 12 seems particularly relevant.


Also, to go one step further with the byte example above, am I going the right direction if I create a packet like protocol, so messages are smaller? In other words would I want to do that for efficiency (common sense? or no?)

Efficiency is relative. Even a fast paced user won't generate too many input commands per unit time, so there probably won't be a huge difference in overall performance in how you encode, transmit and decode such messages. As a counter example, if your game could end up having a lot of active entities on the server, then keeping all the clients up to date with all the entities may warrant much more extra care to avoid performance issues.

I'd recommend starting simple. Probably the best advice is to consider versioning your protocol or packets from the start. Including some kind of version identifier offers you the chance to change how your message work and ensure that new clients or new servers can either reject or interoperate with older versions.


So there might be an action packet like _ac:m:udlrlrudl and that would mean -> action move up down left right left ... etc. and I implement the parser to handle it locally?

You could do that. Remember if you are writing an action game you'll probably want to transmit the input command as soon as possible, so you are unlikely to be in a position to buffer so many commands at once. If your game is less action oriented, then there will be less consequence to the efficiency of the message.

It may be important to sent some kind of time identifier with input messages, so the server can attempt to replay the events from multiple clients and resolve unexpected sequences (if player A and B both attempt to move into the same tile at some point). Remember there is no guarantee the clocks on the server and client will match, so some kind of relative timing identifier must be used.


lol - which comes to my next question. After this I can start trying to implement it. How can I keep the server in sync with all clients when moving a monster.

I'd refer you back to the Forum FAQ, which has articles from real games as to how they've handled this. Unfortunately, all those games sounds significantly more complex than the one you're talking about so far, so perhaps there is an element of overkill in some of these approaches.

Ultimately, the big lesson of network game programming is that guaranteeing synchronisation (e.g. lock step) is a very expensive way to solve network games, any latency or connection issues will immediately impact on all users' experience of the game. Thus, most games choose to lose this guarantee and try to do their best to hide latency in various ways. Some games can use design and animation to hide most latency (e.g. Age of Empires, where a "Yes, Sir" sound would complete before the unit would respond to the command). Others accept that the clients and the users will sometimes disagree with the server's interpretation of events, and attempt to reduce the impact of such disagreements (such as the Source Engine "rewinding" the server when a player input packet arrives to resolve the event).

Ultimately, the network is a leaky abstraction, you cannot make it go away, you can just try to choose a trade off that impacts your game's style least.

Advertisement

Ok, makes sense, but just to make sure we are on the same page. I can come up with my own table of bytes such as ->

mov_u

mov_d

mov_l

mov_r

which corresponds to move up, down, left and right. and I basically pass the message such as mov_l around the internal functions of the client and server right? and I parse it?

I would probably refrain from passing text.

Its a novice mistake to see the function declaration for send() and see char *buffer and assume that you must pass in a string.

You could just pass opcodes and have a switch statement which will execute faster.

Your network message would be like

unsigned short opcode (uint16_t) (there could be an opcode for moving, or other actions etc)

signed int x, y, z (or float) relative movement

You allocate a buffer, copy the bytes into it. Then send() will take the pointer to the buffer and its size.

When you recv you check the opcode. If its the move position opcode, then you read the rest of the buffer as you wrote to it.

The gpwiki using a string to compare the payload is bad for two reasons:

1) it calls strcmp on something that may be an uninitialized or unterminated string -- anyone can send you any bytes in a packet
2) text is inefficient to generate, transmit, and parse, for real-time data streams

You're right that you'll want "packets" -- typically, in a protocol, a message is called a "PDU" or "Protocol Data Unit" and there could be multiple of those inside a single on-network packet.
When you receive a UDP datagram, you should start decoding the messages in that datagram. A simple encoding strategy is "TLD" -- type, length, data.
You'd define the first byte as being the type, the second to be the length of the data, and then that many bytes of data.
If you know that you will always control the client and server 100%, you may not need the length, because perhaps each type maps to a specific length, but keeping it in there is usually a good trade-off for debuggability and robustness (as well as being able to tell old clients that they're old without worrying about buffer overflows!)

So, you might define for the first byte:
move_direction = 1
select_weapon = 2
fire_weapon_direction = 3
say_something = 4

The payload of move_direction would then be:
playerid, direction
The payload for select_weapon would be:
playerid, weaponindex
and so on.
Thus, the data for moving player 3 in the "up" direction, and selecting weapon 7, (assuming 0 means up) would look like:
1, 2, 3, 0, 2, 2, 3, 7

If you need more than individual bytes for each field, you can either simply encode a larger integer type (int16, int32, float, etc) or you can use some kind of extended-length encoding, where each byte contains 7 bits of payload, and if the high bit is set, the next byte is continued payload. (This is often named something like "varint" or "variable length int.")

When the length of a message seems to extend past the received buffer, you have received a bad packet, and should not read past the end of the buffer! Same thing with packet types you don't recognize -- you may choose to simply skip them (assuming the other end is of a newer version -- you can skip because you have length) or do something else.
On the server, you may want to simply disconnect this client right away. During debugging, you may also want to keep a log of everything sent and everything received from/to everyone, perhaps with a serial number at the head of the packet, to make debugging easier.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement