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) 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 BaertAdministrator |
Comment #2: Thu, 10 Apr 2014, 21:16 (GMT+1, DST) 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) 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 BaertAdministrator |
Comment #4: Thu, 10 Apr 2014, 22:18 (GMT+1, DST) 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) 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 BaertAdministrator |
Comment #6: Thu, 10 Apr 2014, 23:07 (GMT+1, DST) 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) 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 BaertAdministrator |
Comment #8: Thu, 10 Apr 2014, 23:33 (GMT+1, DST) 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) 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 BaertAdministrator |
Comment #10: Fri, 11 Apr 2014, 2:00 (GMT+1, DST) 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) 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 BaertAdministrator |
Comment #12: Fri, 11 Apr 2014, 2:27 (GMT+1, DST) 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) 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 BaertAdministrator |
Comment #14: Fri, 11 Apr 2014, 4:55 (GMT+1, DST) 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) 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 BaertAdministrator |
Comment #16: Fri, 11 Apr 2014, 5:18 (GMT+1, DST) 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. |