Maarten Baert's website

Game Maker / C++ projects

Home Model Creator ExtremePhysics Game Maker DLLs SimpleScreenRecorder AlterPCB Quadcopters   Recent comments Search

Last modified: Sat, 11 Jun 2016
Refresh

UDP sockets


About UDP sockets

UDP sockets are similar to normal TCP sockets, but there are a few important differences:

  • UDP sockets don't create connections. They simply send messages to each other. Since there's no need to set up a connection, anyone can send messages to anyone with just a single socket on a single port.

  • UDP sockets are unreliable. Messages may arrive in the wrong order, disappear, or arrive multiple times. Normal TCP sockets cover this by reordering data when it is received out of order, and resending data when it disappears.

  • UDP sockets are message-oriented, unlike normal TCP sockets which are stream-oriented. If you're using the built-in message system you won't notice a huge difference though. However, you should realize that with UDP sockets every message actually corresponds to a packet (or sometimes multiple packets), so it's not a good idea to send thousands of messages with just a few bytes of data every second. You should combine them into larger messages.

  • There is a maximum message size. This is typically 65507 bytes, but it might be different on some networks. There's a function to get the maximum message size, but this function only returns the maximum size of your computer, not the maximum size supported by the network. If you want to be absolutely sure that your messages won't exceed the maximum size, then you should make them no larger than 512 bytes. If you want the highest performance, then the best thing you can do is gradually increase the packet size to detect where the limit is. But please realize that the limit may change at any time since the route that your packets will follow is not deterministic.

So why would you want to use UDP sockets if they are so unreliable? The main reason is because packet loss is not always a huge problem. In an online game, it doesn't really matter if the client doesn't receive the new positions of other players for a single step. In those cases it's probably better to just forget it and go on with the next step, instead of waiting until the server resends the data which will take far longer than a single step (so by the time you receive the data it's already outdated). However, you should not rely on UDP for important messages. Typically you would open a TCP connection for the important data, and a separate UDP connection for the unimportant data.

Note

UDP sockets are really hard to use. This functionality is only meant for experts. You should already be familiar with normal TCP sockets and have a good understanding of the way the internet works.

Functions

udpsocket_create

udpsocket_create()

Creates a new UDP socket and returns the id.


udpsocket_destroy

udpsocket_destroy(id)

Destroys a UDP socket.

  • id: The id of the UDP socket.


udpsocket_exists

udpsocket_exists(id)

Returns whether a UDP socket with the given id exists.

  • id: The id of the UDP socket.


udpsocket_get_state

udpsocket_get_state(id)

Returns the current state of the UDP socket.

  • id: The id of the UDP socket.

Return value:

  • 0 = udpsocket_state_notstarted (the UDP socket has not been started)

  • 1 = udpsocket_state_started (the UDP socket has been started)

  • 2 = udpsocket_state_error (an error has occurred)

These constants are only available if you're using the extension (GEX), you have to use the numbers if you're using the scripts and DLL.


udpsocket_reset

udpsocket_reset(id)

Resets the UDP socket to its initial state.

  • id: The id of the UDP socket.


udpsocket_start

udpsocket_start(id, ipv6, port)

Starts the UDP socket. You have to call this function once before sending or receiving messages.

  • id: The id of the UDP socket.

  • ipv6: Whether IPv6 should be used instead of IPv4. If you don't know what this means, you probably shouldn't be using UDP sockets. Anyway, if you don't know what it means, just use IPv4 (set this to false).

  • port: The port the socket will listen to. This is not the destination port! You can set this to 0 to let the operating system pick a random port when you send the first message (recommended for clients).

    If you use a random port, make sure that you send at least one message before you try to receive any messages. Otherwise you won't get a valid port.


udpsocket_set_destination

udpsocket_set_destination(id, address, port)

Sets the destination. It is possible to change the destination as many times as you want.

  • id: The id of the UDP socket.

  • address: The destination address.

  • port: The destination port.


udpsocket_receive

udpsocket_receive(id, buffer_id)

Receives a new message, i.e. copies the contents of the message to the buffer. Returns whether there was a message available.

  • id: The id of the UDP socket.

  • buffer_id: The id of the buffer that should be used to store the contents of the message.

You should use a while loop to read all messages:

while udpsocket_receive(socket, buffer) {
    // read the buffer
}

udpsocket_send

udpsocket_send(id, buffer_id)

Sends a message.

  • id: The id of the UDP socket.

  • buffer_id: The id of the buffer that holds the contents of the message.


udpsocket_get_last_address

udpsocket_get_last_address(id)

Returns the IP address the last message came from. Since UDP sockets are connectionless, this can be any IP, not just your destination address. Note that the IP can be spoofed: a hacker can send a UDP message pretending to be from a different IP. You should never rely on the IP address alone in situations where security is important.

  • id: The id of the UDP socket.


udpsocket_get_last_port

udpsocket_get_last_port(id)

Returns the port the last message came from. You should usually send the response to this port (this should work even with local area networks).

  • id: The id of the UDP socket.


udpsocket_get_max_message_size

udpsocket_get_max_message_size(id)

Returns the maximum message size supported by the computer. This is usually 65507. The maximum size of the network may be much lower though. If you want to be safe, don't make your messages larger than 512 bytes.

  • id: The id of the UDP socket.


Comments

Kbjwes77

Comment #1: Thu, 10 Apr 2014, 21:03 (GMT+1, DST)

Quote


Hey there, it's me again. Sorry to bother you, but I had a simple problem that is turning out to be difficult to fix.

I am testing a UDP connection on LAN, sending packets from host to client. However when the client sends a packet to the host, instead of the host receiving it, the client steals the packet it just sent! The server has a defined port (7777) and I am giving the client a random port (I set it to 0 so the OS chooses it randomly for the client), but the host and client have the same IP address ("localhost" or "127.0.0.1").

How would I go about making sure the client doesn't receive it's own packets? It works fine over a LAN when the host and client aren't on the same machine. Any ideas? Thanks in advance!

Maarten Baert

Administrator

Comment #2: Thu, 10 Apr 2014, 21:16 (GMT+1, DST)

Quote


Quote: Kbjwes77

Hey there, it's me again. Sorry to bother you, but I had a simple problem that is turning out to be difficult to fix.

I am testing a UDP connection on LAN, sending packets from host to client. However when the client sends a packet to the host, instead of the host receiving it, the client steals the packet it just sent! The server has a defined port (7777) and I am giving the client a random port (I set it to 0 so the OS chooses it randomly for the client), but the host and client have the same IP address ("localhost" or "127.0.0.1").

How would I go about making sure the client doesn't receive it's own packets? It works fine over a LAN when the host and client aren't on the same machine. Any ideas? Thanks in advance!

I'm going to assume that you meant 'server' instead of 'host'.

What you're seeing is not normal. You are probably using my DLL incorrectly. If you can post your code here, I will take a look at it.

Kbjwes77

Comment #3: Thu, 10 Apr 2014, 21:40 (GMT+1, DST)

Quote


Your replies are always very timely, I really appreciate it! And yes I did mean server, not host. Also, for clarification, I am using the Studio extension.

As for the code:

I have two buffers, "global.buffi" for the incoming stuff, "global.buffo" for the outgoing stuff.

Here is the client's UDP socket creation code:

udpclient = udpsocket_create();
udpsocket_set_destination(udpclient,ip,port); // here, ip = "127.0.0.1"; port = 7966;
udpsocket_start(udpclient,false,0); // use IPv4, select random listening port

Here is the client's code for receiving packets:

while(udpsocket_receive(udpclient,global.buffi))
    {
    lastip = udpsocket_get_last_address(udpclient);
    bytesin += hbuffer_get_length(global.buffi);
    udppackets++;
    
    scr_client_udp_in();
    }

Client sending code sample (yep that's it!):

hbuffer_clear(global.buffo);
hbuffer_write_uint8(global.buffo,0); // message case id
hbuffer_write_int8(global.buffo,xmove-obj_player.x);
hbuffer_write_int8(global.buffo,ymove-obj_player.y);
hbuffer_write_uint8(global.buffo,floor(obj_player.dir*0.5));
udpsocket_send(udpclient,global.buffo);

It uses the same UDP socket that sends information to the server. I could create a second UDP socket, use it only for sending, but how would the server know which client it was sent from?

Last modified: Fri, 11 Apr 2014, 2:30 (GMT+1, DST)

Maarten Baert

Administrator

Comment #4: Thu, 10 Apr 2014, 22:18 (GMT+1, DST)

Quote


That code is fine, the problem is most likely how you are sending messages, and you didn't post that part. I also need to see the server code as well, not just the client.

The server should use udpsocket_get_last_address and udpsocket_get_last_port to find out who the client is, and then use those as the destination when it sends replies. The client doesn't need to do this, since it already knows the address and port of the server.

Kbjwes77

Comment #5: Thu, 10 Apr 2014, 22:43 (GMT+1, DST)

Quote


I don't think it's the sending code. [Edited so the page wasn't cluttered with useless info!]

Last modified: Fri, 11 Apr 2014, 2:29 (GMT+1, DST)

Maarten Baert

Administrator

Comment #6: Thu, 10 Apr 2014, 23:07 (GMT+1, DST)

Quote


That looks okay. Are you absolutely sure that the server is not receiving the messages, and that the client is receiving its own messages? It could also be a different problem that makes it look like this is happening.

By the way, it's probably not the cause of your problems, but this:

string(lastip)+string(lastport)

... is not a good key. The IP 1.2.3.4 with port 56 will get the same key as 1.2.3.45 with port 6. Use this instead:

string(lastip)+","+string(lastport)
Kbjwes77

Comment #7: Thu, 10 Apr 2014, 23:21 (GMT+1, DST)

Quote


I'm positive. When I comment out the client's receiving code, the server receives things just fine.

I fixed the issue with the string(lastip)+string(lastport) collisions as you suggested. I didn't even think of that one. Unfortunately, it wasn't the cause of my greedy-client issue. I'll get back to you on that if I do find a fix; at least someone can use this as a reference if they get stuck with the same issue.

Last modified: Thu, 10 Apr 2014, 23:22 (GMT+1, DST)

Maarten Baert

Administrator

Comment #8: Thu, 10 Apr 2014, 23:33 (GMT+1, DST)

Quote


Quote: Kbjwes77

I'm positive. When I comment out the client's receiving code, the server receives things just fine.

Then the problem is somewhere else in the client. Commenting out code in the client can't possibly affect the server.

What happens when you keep the client's receiving code active, but you comment out the line scr_client_udp_in(); in the receive loop?

Kbjwes77

Comment #9: Fri, 11 Apr 2014, 1:32 (GMT+1, DST)

Quote


Sorry for the delay, I had to get to class.

If I just comment out the script scr_client_udp_in() inside of the while statement, it still behaves as if it is reading the packet before it gets sent out to the server.

Anytime udpsocket_receive() is called after it sends a message and before the next step, it seems to do this.

Last modified: Fri, 11 Apr 2014, 1:37 (GMT+1, DST)

Maarten Baert

Administrator

Comment #10: Fri, 11 Apr 2014, 2:00 (GMT+1, DST)

Quote


Quote: Kbjwes77

Sorry for the delay, I had to get to class.

If I just comment out the script scr_client_udp_in() inside of the while statement, it still behaves as if it is reading the packet before it gets sent out to the server.

Anytime udpsocket_receive() is called after it sends a message and before the next step, it seems to do this.

I still don't understand what's happening. Are you checking the state of the sockets to make sure that no errors are happening?

Kbjwes77

Comment #11: Fri, 11 Apr 2014, 2:24 (GMT+1, DST)

Quote


The state of the socket is always 1, or the constant socket_started. No errors anywhere. Data is sent, but it never reaches the server. The client actually accepts it. Like I can send a packet to myself from the same UDP socket. Everything is working properly, though! Haha I am just so confused as to why the client reads it's own messages, when the destination (outgoing) port it 7966 and the receiving (incoming) port is set to 0 (random).

This works, and the server receives the packets:

/*
while(udpsocket_receive(udpclient,global.buffi))
    {
    lastip = udpsocket_get_last_address(udpclient);

    scr_client_udp_in();
    }
*/

However, this doesn't, and the client picks up it's very own messages:

while(udpsocket_receive(udpclient,global.buffi))
    {
    lastip = udpsocket_get_last_address(udpclient);
    
    scr_client_udp_in();
    }

Last modified: Fri, 11 Apr 2014, 2:35 (GMT+1, DST)

Maarten Baert

Administrator

Comment #12: Fri, 11 Apr 2014, 2:27 (GMT+1, DST)

Quote


Quote: Kbjwes77

The state of the socket is always 1, or the constant socket_started. No errors anywhere. Data is sent, but it never reaches the server. The client actually accepts it. Like I can send a packet to myself from the same UDP socket. Everything is working properly, though! Haha I am just so confused as to why the client reads it's own messages, when the destination (outgoing) port it 7966 and the receiving (incoming) port is set to 0 (random).

Yes, that really makes no sense, it's as if Windows is binding both sockets to the same port :S. Can you try to set the client port explicitly to some value other than 7966, e.g. 7967, and see if that fixes the problem? It's not a good solution of course, but it helps to track down the cause.

EDIT: You are starting the server before you start the client, right?

Last modified: Fri, 11 Apr 2014, 2:28 (GMT+1, DST)

Kbjwes77

Comment #13: Fri, 11 Apr 2014, 2:33 (GMT+1, DST)

Quote


I am starting the server before the client.

Wow! I changed the receiving port for the client from 0 (the supposedly randomly selected by the OS free port thing) to 7965 which is one below the sending port. It goes through! Why does windows do that? It's not the DLL right?

So, I have to select a receiving port manually, and not set it to 0 I think. Because it's not actually giving me a random port.

Maarten Baert

Administrator

Comment #14: Fri, 11 Apr 2014, 4:55 (GMT+1, DST)

Quote


Quote: Kbjwes77

I am starting the server before the client.

Wow! I changed the receiving port for the client from 0 (the supposedly randomly selected by the OS free port thing) to 7965 which is one below the sending port. It goes through! Why does windows do that? It's not the DLL right?

So, I have to select a receiving port manually, and not set it to 0 I think. Because it's not actually giving me a random port.

If you set the port to 0, the DLL will simply leave the socket unbound. As soon as you try to send something, Windows should pick a random port for you.

Could it be that you were trying to receive data before you had sent anything from that socket? Can you try sending something first (e.g. in the create event) to see whether that makes any difference?

Last modified: Fri, 11 Apr 2014, 4:54 (GMT+1, DST)

Kbjwes77

Comment #15: Fri, 11 Apr 2014, 5:09 (GMT+1, DST)

Quote


That worked! It worked, it worked, it worked! Yes! Thank you so much, I just needed to send something to the server before receiving something. So simple, but completely game breaking. Thank you!

Maarten Baert

Administrator

Comment #16: Fri, 11 Apr 2014, 5:18 (GMT+1, DST)

Quote


Quote: Kbjwes77

That worked! It worked, it worked, it worked! Yes! Thank you so much, I just needed to send something to the server before receiving something. So simple, but completely game breaking. Thank you!

It was just a guess, I had no idea either. I've added it to the documentation.

Write a comment