Performance issues with UDP network [SOLVED]

Hi!

Im working on a small online project right now, and am having trouble with my method of UDP server/client connection.

When i run my client alone, and put in a hack so that the client loads a terrain tile/player without the server, I get about 25 FPS.

But if i comment out the hack, and launch the server and then run the client, i will get less that 1 frame per second.

I had this same problem with a TCP connection method i was working with before, and thought the performance drop was due to TCP’s inherently slow nature.

I was wondering if someone could shed some light on what im doing wrong. (aside from my re-inventing-the-wheel UDP Server/Client code in /net/_udp.py)

http://rapidshare.com/files/447340613/laggy.rar

To disable the server-less hack, open client/main.py, and comment out:

main.world = world.Map(main)
main.world.loadTerrain(1)

near the bottom.

Thanks in advance.

Hi Alviss, regardless of if you’re using TCP or UDP, any call to recv (TCP) or recvfrom (UDP) will block until socket.timeout has been raised.

for example, in your code, “client_udp.py” line 35:

#- Replace this line
#pack, server = self.con.recvfrom( 1024 )
#- With this line
pack, server = None, None

Granted that stops you from receiving data, you see my point that self.con.recvfrom(1024) will block until your socket.timeout.

You have two options:
Look into Python’s select module
You would use select like this in your case:
(basically)

from select import select
...
readable, write_to_delete, error_to_delete = select([self.con], [], [])
del write_to_delete
del error_to_delete
if len(readable) == 1:
    #There is data to be read from the socket
    pack, server = self.con.recvfrom(1024)
    ...
    do other stuf
    ...

(Optionally you could set the socket’s timeout to lower, but that will still lose frame rate and it’s generally much much better to use select, so just use select, please)

Hopefully you understand that, tell me otherwise and I’ll fix up your code for you.
~powerpup118

Panda3d provides build in networking support so that you don’t have to do thous things.

I thought that UDP doesnt block, which is why it is better regarding games.

@hobbit

There is a statement you can make that will set the socket to nonblocking, by:

socket.setblocking(0)

@powerpup118

Thanks pup, ill do that. I didnt realize I/O blocking was causing my lag. If that is the case, i dont understand why my Panda TCP server/client was lagging that badly.

@treeform

I switched from using the panda supplied TCP server/client network because i was getting this same FPS issue. I dont know if it was lagging because of a blocking I/O.
I figured it was because the Panda supplied module was inherently slower.

Do you think the Panda module is as fast the Python default?

The panda networking modules are just as fast (in some cases may be faster. Use whichever one you are most comfortable with.

In reality, the performance difference between the two is nearly none. Think of them as “both performing at the same speed”, unless you’re using them wrong that is.

~powerpup118

@Pup

Thanks a lot pup. Luckily i saved my old Panda TCP server framework, so it was easy.

I ran my server with a client for a test and i had my client running at 22-26 FPS, and for every client i ran in succession the FPS was halved.

Is there anyway i can improve my performance, just off the top of your head?

Sounds to me like you’re sending data much faster than you need to (or can really). Keep in mind TCP does block to a point when sending.
Are you sending “Position updates for players” or something like that very quickly? perhaps you’re sending it too often.

That’s the only thing that comes to mind.
Perhaps you could explain what you’re mostly doing?
~powerpup118

Im sending position and vector updates using PyDatagrams.

I put the position/vector tuples into strings and add them to the Datagram.

a example would be:

Pkg = PyDatagram()
pkg.addString( str(player.vector) )
pkg.addString( str(player.position) )
# player vec/pos are stored on the Serverside Player
# object after being checked, and the client updates 
# them

Of course i do rounding for the tuples.
i update every client about 3 times a second, i havent actually gotten to the point where a client can see any other clients, but when i do, ill change how often the updates happen so it’s only when necessary.

Does a UDP connection with blocking disabled block less than TCP?

Main problem i found is that pythons TCP module is not 100% same on all platforms while panda3d is i forgot what though, maybe need to reavalute. You also get the pstats network anlyzer view for free if you use the panda3d built int one. Build in panda3d networking is crazy fast.

I’ve used the python TCP module for over a year now, and have found no platform specific things at all (Other than maybe backlog size, that’s relevant for all networking modules though, and never really a problem) so I think perhaps that’s irrelevant now? I could be wrong though, dunno what you where using specifically that you ran into platform-specific problems with

No problems should be arising. You’re using panda’s built in networking library, correct? If so I think panda auto-manages the blocking for you (so that it never blocks, is it’s goal)

UDP with blocking disabled never blocks at all. UDP packets are ‘fired’ off, no checking occurs.

TCP with blocking disabled blocks ONLY when sending. TCP packets have a small delay when sending to assure that the packet arrived on the other end.

I find it best to use UDP with blocking disabled for position updates, and non-important rapid packets.
Then use TCP with blocking disabled (Check the socket for reading and writing using select) for critical updates such as purchases, kills, anything important that is never sent “tons”

Here is how you would use select in the case of a TCP socket (store anything you want to send over TCP in, say, self.dataToSend):

import select
writeAbleSockets, readAbleSockets, errorTrashSockets = select.select([self.tcpSocket], [self.tcpSocket], [])
del errorTrashSockets
if len(writeAbleSockets) == 1:
    self.tcpSocket.send(self.dataToSend)
if len(readAbleSockets) == 1:
    data = self.tcpSocket.recv(1024)
    ...do stuff with data

So yeah, you get the idea… seems strange that you’d experience lag though, (Out of odd random-ness, are the all 10 clients started on the same PC? Is it just your PC slowing down due to running the game that many consecutive times?)

Best of luck,
~powerpup118

@ treeform

Thanks for all the info. I’ve switched back over to the Panda Network Library, which is making things a little easier.

@ pup

Yeah, all the clients are being run off one machine (so yeah, of course,lag) but if a run a client without networking, i get about 60FPS, with networking 20-25FPS.

[crossout]I’ll reply more once i get home from work.[/crossout]

Ok, so i’ve decided to switch all my pos/vec to a UDP message pump with the select example you supplied, but it’s blocking.

This is pretty much what i have. (of course socket/select is imported)

    self.UDPsock = socket(AF_INET, SOCK_DGRAM ) 
    self.UDPsock.bind(("", PORT))
    self.UDPsock.setblocking(0)
    #self.UDPsock.settimeout(0.0) 
    
    taskMgr.add(self.listenUDPTask, "serverlistenUDPTask") 
            
    def listenUDPTask(self, task):
        """ 
        Listen for client pos/vec/hpr broadcast 
        """         
        print "listenUDPTask" 
        readable, write_to_delete, error_to_delete =  select([self.UDPsock], [], []) 
        print readable 
        del write_to_delete 
        del error_to_delete 
        if len(readable) == 1: 
           #There is data to be read from the socket 
           pack, addr = self.UDPsock.recvfrom(1024) 
           print pack
           print addr
           
        return task.cont

Alviss I want to apologize, I was sitting down going “How could he still be having trouble, he’s doing everything right”, I informed you the improper way to use select.
I am so terribly sorry.
To use select, you must say:

read, write, error = select([mySocket], [mySocket], [], 0)

Note the ending argument of 0, that is the time for select to block before moving on. I am SO terribly sorry.

Here is an example showing how to use UDP sockets in panda3d without them blocking.

from direct.directbase import DirectStart

from socket import *
from select import select

class socketServer:
	def __init__(self):
		self.socket = socket(AF_INET, SOCK_DGRAM)
		self.socket.bind(('', 9000))
		taskMgr.add(self.checkForData, 'socketClient.checkForData')

	def checkForData(self, task):
		read, write_trash, error_trash = select([self.socket], [], [], 0) #- you MUST pass 0 at the end, SO SORRY
		if len(read) == 1:
			print 'Got data from client', self.socket.recvfrom(1024)
		#- There is no need for the line below, once we leave this scope python auto-removes any unheld variables
		#del write_trash, error_trash
		return task.cont

class socketClient:
	def __init__(self):
		self.socket = socket(AF_INET, SOCK_DGRAM)
		taskMgr.doMethodLater(1.0, self.contactServer, 'socketClient.contactServer')
		taskMgr.add(self.checkForData, 'socketClient.checkForData')

	def contactServer(self, task):
		print 'Sending update..'
		self.socket.sendto('Hello world, I love you', ('localhost', 9000))
		return task.again

	def checkForData(self, task):
		read, write_trash, error_trash = select([self.socket], [], [], 0) #- you MUST pass 0 at the end, SO SORRY
		if len(read) == 1:
			print 'Got data from client', self.socket.recvfrom(1024)
		#- There is no need for the line below, once we leave this scope python auto-removes any unheld variables
		#del write_trash, error_trash
		return task.cont

base.setFrameRateMeter(True)
server = socketServer()
client = socketClient()
run()

Again I’m very sorry about this, I was stupid.
Once again, the proper way to use select:

read, write, error = select([mySocket], [mySocket], [], 0)

Instead of:

read, write, error = select([mySocket], [mySocket], [])

Terribly sorry about that.
Cheers,
~powerpup118

Oh no problem! That puts me pretty well at ease. I was worried it was some serious problem.

I probably should have read the select documentation link you provided, and i would have figured out the problem on my own.

Thanks for all the assistance, Pup. You’ve helped me progress my project alone wonderfully in a very short period of time! :slight_smile: