Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect when TCP is congested python twisted socket server

I'm working on a realtime MMO game, and have a working TCP server (along with game client), but now I'm considering using UDP for constantly updating other player's postions(to greatly reduce random game stuffer from TCP congestion control!) I'd love some help from people smarter than me in this stuff (I'm new to python/twisted, and couldn't find this info elsewhere ;) )

Currently, my server accepts connections with a simple Twisted Protocol. Eg.

''' TCP reciever '''
class TCPProtocol(Protocol):

    def connectionMade(self):
        #add to list of connected clients
        factory.clients.append(self)

    def dataReceived(self, data):
        pass


#setup factory and TCP protocol class
factory = Factory()
factory.protocol = TCPProtocol
factory.clients = []
reactor.listenTCP(1959, factory)

@@@@@@@@@@@@@@ UPDATE @@@@@@@@@@@@@ : How can I implement congestion checking for each Protocol instance seperately? Please start me off with something, inside the code sample below: (where it says 'HELP HERE PLEASE'!) Am I thinking of this wrongly? Any guidance would be awesome, thanks!

from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor

KServerIP='127.0.0.1'
KServerPortTCP=8000
#KClientPortUDP=7055

''' TCP reciever '''
class TCPProtocol(Protocol):

    def connectionMade(self):
        #add to list of connected clients
        factory.clients.append(self)

        #set client variables (simplified)
        self.pos_x=100
        self.pos_y=100

        #add to game room (to recieve updates)
        mainGameRoom.clientsInRoom.append(self)

    def dataReceived(self, data):
        pass

    #send custom byte message to client (I have special class to read it)
    def sendMessageToClient(self, message, isUpdate):

''' @@@@@@@@@@@@@@@@@  HELP HERE PLEASE!  @@@@@@@@@@@      
        if isUpdate and (CHECK IF CLIENT IS CONGESTED??? )
            return  (and send next update when not congested)'''
     '''@@@@@@@@@@@@@@@@@  HELP HERE PLEASE!  @@@@@@@@@@@   '''

        #if not congested, write update!
        msgLen = pack('!I', len(message.data))
        self.transport.write(msgLen) #add length before message
        self.transport.write(message.data)

#simplified version of my game room
#this room runs the game, and clients recieve pos updates for 
#everyone in this room (up to 50 people)
dt_gameloop=1.0/60 #loop time difference
dt_sendClientUpdate=0.1 #update intervar
class GameRoom(object):
    #room constants
    room_w=1000
    room_h=1000

    def __init__(self):
        super(GameRoom, self).__init__()
        #schedule main game loop
        l=task.LoopingCall(self.gameLoop)
        l.start(dt_gameloop) # call every X seconds
        #schedule users update loop
        l=task.LoopingCall(self.sendAllClientsPosUpdate)
        l.start(dt_sendClientUpdate) # call every X seconds

    def gameLoop(self):
        #game logic runs here (60 times a second),  EG.
        for anUpdateClient in self.clientsInRoom:
            anUpdateClient.pos_x+=10
            anUpdateClient.pos_y+=10

    #send position update every 0.1 seconds, 
    #send all player positions to all clients
    def sendAllClientsPosUpdate(self):

        message = MessageWriter()
        message.writeByte(MessageGameLoopUpdate) #message type

        #create one byte message containing info for all players
        message.writeInt(len(self.clientsInRoom)) #num items to read
        for aClient in self.clientsInRoom:
            message.writeInt(aClient.ID)
            message.writeFloat( aCell.pos_x )#pos
            message.writeFloat( aCell.pos_y )

        #send message to all clients
        for aClient in self.clientsInRoom:
            aClient.sendMessageToClient(message, True)

#setup factory and TCP protocol class
factory = Factory()
factory.protocol = TCPProtocol
factory.clients = []
reactor.listenTCP(KServerPortTCP, factory)

#simple example, one game room
mainGameRoom=GameRoom()


print "Server started..."
reactor.run()
like image 470
Stan Tatarnykov Avatar asked Jan 18 '26 06:01

Stan Tatarnykov


1 Answers

You probably don't need UDP (yet).

The first thing that you say is that you want to "reduce network congestion ... from TCP". That is not what UDP does. UDP allows you to work around congestion control, which actually increases network congestion. Until you're aware of how to implement your own congestion control algorithms, UDP traffic on high-latency connections is just going to cause packet storms which overwhelm your server and flood your users' network connections, making them unusable.

The important thing about sending movement packets in a real-time game is that you always want to ensure that you don't waste time "catching up" with old movement packets when a new position is already available. In Twisted, you can use the producer and consumer APIs to do that on a TCP connection just fine, like so:

from zope.interface import implementer
from twisted.internet.protocol import Protocol
from twisted.internet.interfaces import IPullProducer

def serializePosition(position):
    "... take a 'position', return some bytes ..."

@implementer(IPullProducer)
class MovementUpdater(Protocol, object):
    def updatePosition(self, newPosition):
        if newPosition != self.currentPosition:
            self.currentPosition = newPosition
            self.needToSendPosition()

    waitingToSend = False

    def needToSendPosition(self):
        if not self.waitingToSend:
            self.waitingToSend = True
            self.transport.registerProducer(self, False)

    def resumeProducing(self):
        self.transport.write(serializePosition(self.currentPosition))
        self.transport.unregisterProducer()
        self.waitingToSend = False

    def stopProducing(self):
        "nothing to do here"

Every time the game needs to send a new position, it can call updatePosition to update the player's current position. updatePosition first updates the current position, then calls needToSendPosition which marks the connection as needing to send a position update. This registers the protocol as a producer for its transport, which will cause resumeProducing to be called each time write-buffer space is available. As soon as resumeProducing is called, we send whatever the latest position is - if updatePosition is called 500 times while the network is congested, only one update will be sent as soon as the congestion alleviates.

This is a bit oversimplified, because each transport can only have one producer at a time, and your game server will probably have lots of different position updates to send to clients, so you will need a multiplexor which aggregates all the position updates from multiple clients, and also some code to order messages so that things other than position updates still get through but that position updates have priority.

This might seem like extra work if you're going to do UDP anyway, but if you're going to do UDP correctly and actually get any benefit from it, you will need to implement something very much like this anyway, so this won't be wasted.

like image 156
Glyph Avatar answered Jan 19 '26 18:01

Glyph



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!