You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask服务端如何处理JS请求解决游戏用户同步的并发问题?

解决Flask服务端与JavaScript客户端的游戏同步问题

需求与现状

需要通过JavaScript定期向Flask服务端发起请求,同步同一游戏实例内玩家的操作:服务端返回最新玩家命令,客户端对比本地记录,若有更新则执行同步。

原客户端JavaScript代码:

function checkUpdate() {
    setInterval(updateState, 1000, document.getElementById('serverId').textContent);
}

function updateState(serverId) {
    var request = new XMLHttpRequest();
    request.onreadystatechange = function() {
        if (request.readyState == XMLHttpRequest.DONE) {
            const response = request.responseText
            if (response != document.getElementById('lastCommand').textContent) {
                document.getElementById('lastCommand').textContent = response
                eval(document.getElementById('lastCommand').textContent)
            }
        }
    }
    request.open('GET', '/updateState?serverId='+serverId, true);
    request.send(null);
}

原Flask服务端代码:

@app.route('/updateState')
def updateState():
    serverId = int(request.args['serverId'])
    for instance in instances:
        try:
            if serverId == instance.id: return instance.lastCommand[-1]
        except IndexError:
            return 'Error'
    return 'Error'

现存问题

短轮询间隔1秒,当玩家命令请求与轮询请求并发时,服务端可能未完成命令处理就返回轮询结果,导致客户端未检测到更新,同步失败。

解决方案:长轮询(Long Polling)

改用长轮询模式:客户端发起请求后,服务端不立即返回结果,等待有新命令或超时后再响应;客户端收到响应后立刻发起下一次请求,维持持续连接,避免并发冲突。

客户端改造

替换setInterval为递归调用,确保每次请求完成后再发起下一次:

function checkUpdate(serverId) {
    // 递归发起长轮询请求
    function poll() {
        var request = new XMLHttpRequest();
        request.onreadystatechange = function() {
            if (request.readyState == XMLHttpRequest.DONE) {
                if (request.status === 200) {
                    const response = request.responseText
                    if (response != document.getElementById('lastCommand').textContent) {
                        document.getElementById('lastCommand').textContent = response
                        eval(document.getElementById('lastCommand').textContent)
                    }
                    // 请求完成后立刻发起下一次轮询(加短延迟避免频繁请求)
                    setTimeout(poll, 100);
                } else {
                    // 请求失败时延迟重试
                    setTimeout(poll, 2000);
                }
            }
        }
        request.open('GET', '/updateState?serverId='+serverId, true);
        request.send(null);
    }
    poll();
}

// 初始化调用
checkUpdate(document.getElementById('serverId').textContent);

服务端改造(异步Flask实现)

Flask 2.0+支持异步路由,结合asyncio.Event实现非阻塞等待新命令:

from flask import Flask, request
import asyncio
from collections import defaultdict

app = Flask(__name__)

# 存储每个游戏实例的状态:key为serverId,value为{event: asyncio.Event, last_command: str}
game_instances = defaultdict(lambda: {"event": asyncio.Event(), "last_command": ""})

# 处理玩家命令的接口(示例)
@app.route('/sendCommand', methods=['POST'])
async def send_command():
    server_id = int(request.form['serverId'])
    command = request.form['command']
    instance = game_instances[server_id]
    instance['last_command'] = command
    # 触发事件,通知等待的轮询请求
    instance['event'].set()
    instance['event'].clear()  # 重置事件,等待下一次命令
    return "OK"

# 长轮询接口
@app.route('/updateState')
async def update_state():
    server_id = int(request.args['serverId'])
    instance = game_instances[server_id]
    current_command = instance['last_command']
    
    try:
        # 等待新命令或超时(15秒)
        await asyncio.wait_for(instance['event'].wait(), timeout=15)
    except asyncio.TimeoutError:
        # 超时返回当前命令,客户端会重新发起请求
        return current_command
    
    # 有新命令时返回最新值
    return instance['last_command']

if __name__ == '__main__':
    app.run(debug=True)

关键说明

  1. 异步路由:用async def定义路由,Flask会自动用异步事件循环处理,避免阻塞服务端主线程。
  2. 事件通知:每个游戏实例维护asyncio.Event,当新命令到达时触发事件,唤醒等待的轮询请求。
  3. 超时处理:设置15秒超时,防止客户端因网络问题一直挂起,超时后返回当前命令,客户端自动重试。
  4. 状态存储:用defaultdict统一管理游戏实例状态,确保多请求间的状态共享。

内容的提问来源于stack exchange,提问作者spots 727

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 16:43:11