Twisted中的多个响应

2 投票
1 回答
564 浏览
提问于 2025-04-17 14:05

我正在尝试用 Twisted 和 Pygame 开发一个简单的 TCP 客户端/服务器游戏,但在向客户端发送数据时遇到了困难。Twisted 不允许我连续发送多个响应。这是我想做的事情:

我有一个方法来处理玩家状态的变化,并把这些变化重新发送给其他客户端:

def handle_stateChanged(self, data):
    #get playerState from client and override local copy of player
    #check if all players are ready
    #if needed, change gameState form 'inLOBBY' to 'inGAME'
    #if gameState == 'inGAME', start 'proceed' method (see below)
    #send message about player and game state to others

还有一个 proceed 方法(每秒调用一次,或者每 30 次使用 LoopingCall),这个方法简单地计算所有游戏的内容并发送给玩家。这两个函数互相阻塞,单独运行时都没问题,但一起工作时只有一个函数的数据能到达目的地。类似这样的代码也不行:

def dataRecived(self, data):
    ...
    sendData(data1) #only data1 is delivered
    sendData(data2)
    ...  

我不知道这是因为 TCP 的工作方式,还是我对 Twisted 的理解不够。应该如何向客户端发送更新,同时在后台处理用户输入呢?

编辑:

class Server(Protocol):
    def __init__(self, factory):
        self.factory = factory
        self.player = None #when connection is made here goes my player class
        self.world = factory.world
        ...
        self.factory.loop = LoopingCall(self.proceed)

    def dataReceived(self, data):
        data = pickle.loads(data)
        #this calls 'handle_stateChanged'
        getattr(self, 'handle_{}'.format(data['header']))(data) #data[header] = 'stateChanged'

    def handle_stateChanged(self, data):      
        isReady = data['isReady']
        self.player.isReady = isReady

        if isReady:
            count = 0
            for connection in self.factory.connections.values():
                if connection.player.isReady:
                    count += 1

            if count == len(self.factory.connections) and count > 1 and self.world.state == 'inLOBBY':
                self.world.state = 'inGAME'
                self.world.playersDistribution(self.factory.connections)
                self.factory.loop.start(1 / 30)

        data['state'] = self.world.state
        data['players'] = self.getPlayers()        
        self.sendToOthers(data)

    def sendToOthers(self, data, omitId = None):
        connections = self.factory.connections

        for key in connections.keys():
            if key != omitId:
                connections[key].sendData(data)

    def proceed(self):
        #It's only a prototype of my method. 
        #The point is that the client keep getting
        #'test' and data from self.sendToOthers(data) in handle_stateChanged
        #is not being delivered even if the method (handle_stateChanged) is called

        if self.world.state != 'inGAME':
            return

        data = {'header' : 'message', 'body' : 'test'}
        #When i comment this out, handle_stateChanged works fine and sends data
        self.sendToOthers(data)

class ServerFactory(Factory):

    def __init__(self, world):
        self.world = world
        self.connections = {}

    def buildProtocol(self, addr):
        return Server(self)

1 个回答

4

你的服务器存在一个远程任意代码执行的漏洞。

几乎没有什么情况下你应该从网络接收的数据中解压(unpickle)数据。这样做会让任何人都能控制你的服务器,可能会用来做一些恶意的事情。请注意pickle文档中的大红框

除了这个严重的安全问题之外,你遇到的第一个数据包被解读的问题,可能是因为在网络传输过程中,两个数据包被连在一起了。你的接收代码没有正确的分帧支持,所以它无法识别出有两个消息。结果,pickle会从第一个消息中加载数据,而忽略后面代表第二个消息的额外数据,导致那部分数据被丢掉了。

如果你换用一种更灵活的协议(比单纯的TCP传输没有分帧的pickle字符串更灵活),比如AMP,就可以解决安全问题和分帧问题。

撰写回答