Skip to main content
gamedev.net
🔒 Locked

UDP Socket Usage

Started by Figgles Mar 13, 2007 at 12:57 PM 4 replies 1.1k views
Original Post
Figgles
Figgles
Hello everyone. I have a question about the usage of UDP sockets in an online game. I currently have the design where client has a non-blocking socket assigned to it. I know, you don't need a socket-per-client, as you can always use recvfrom(), but I used connect() to bind the socket to a IP/port combination so that all send() calls went to the correct address, and all recv() calls would receive data only from that IP/port combination. I have multiple threads, usually three or four that handle all of the clients, and they load balance themselves. Each thread does a recv() on its respective clients' sockets, and processes the data. And right now, it works just fine. However, I had heard that this was a *very* poor architecture, and that using a single UDP socket for the entire server was the best plan, and have recvfrom() return the IP/port, then find the associated client, perhaps then shift the load to a different thread to utilize > 1 CPU. I honestly can't understand why the first wouldn't be better than the second. It seems to be that being able to use multiple processors on the server and not have to sort through messages from other users (because recv() after connect() filters UDP traffic so that only UDP traffic from that IP/port can be read()) would be a win. Moreover, there is no synchronization required between the threads in the first to merely recv() a packet, which should improve scalability, since otherwise, each packet would need to be synchronized with another thread. The only thing I think my architecture (the first one) would not scale at is the large number of system calls (i.e. recv() being 99% of them). But... Couldn't you use epoll() (Linux) or /dev/poll (Solaris) to pack all of a worker thread's information into a single test for data, THEN recv() if needed to reduce the number of system calls? (Non-blocking sockets would be pretty useless then, but that can be turned on/off easily with a single line of code.) Anyways, if there is something glaringly obvious that I am missing, please help me! I would appreciate as detailed an explaination as possible. If someone has a better architecture that they think might work, then feel free to explain it too!
jeremymm
jeremymm
I will give you my method you can ignore if you want ;)

I open two sockets, one that recvs and one that sends. I do this so that I can have two threads that do not block when reading/writing to a single socket. This is really not needed.

Some of my optimization algorithms use sendto to send certain data to certain clients.

"Connecting" an UDP socket limits its function IMHO. The nice thing about UDP sockets is their flexiblity.

Hope that helps

hplus0603
hplus0603
If you have more than one socket, you need to make really sure that you are properly NAT compliant. I'm sure you can make it work, but it does waste system resources. The number of calls to recv() are roughly the same (as there should be one per incoming message in each case), but each socket uses memory in the kernel, and grows the table that needs to be searched for each incoming message, so many sockets will run slower than a single socket.
enum Bool { True, False, FileNotFound };
Anon Mike
Anon Mike
Somebody has to do the per-client fanout. If you have a socket per-client then it's the network stack doing it. If you have a single socket it's you doing it. The code to do it yourself is not particularly complicated. So it's basically a wash.

I'm not sure what you're trying to say with you threading arguments. It sounds like you are taking advantage of threads but locking a client to a single thread (which is fine as long as the workload doesn't become to asymetrical) or it's another case of if you're not doing it somebody else is. You may be able to argue that inter-thread syncronization would be complicated enough that it's better to let the (presumably well-tested and optimized) OS stack do it for you.

The arguments in a favor of the single socket approach usually have to do with saving system resources (sockets are not free), making the low-level code simpler (managing one socket is easier than managing n sockets), and making life a lot easier if you want to integrate the more high-powered OS features into your net code (e.g. completion ports in Windows).
-Mike
Figgles
Figgles
Ah, thanks guys.

I had forgot about the kernel resources involved with sockets. That is a big turn-off when you talk about an ever increasing number of clients.

I knew at some level the sorting would be done, but I suppose doing it in userspace rather than kernelspace would be better since the data is immediately needed after it is sorted.

Does anyone have any suggestions on how to implement this model effectively? I am still a bit worried that I will have a lot of idle CPUs.
hplus0603
hplus0603
One thread that runs at high priority, reading from the socket, looking up the IP address in a hashtable, and dispatch the buffer to the registered client. For threading, you can do that with a thread pool. One implementation of thread pool is using a counting semaphore.
enum Bool { True, False, FileNotFound };

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.