unluckyyy 2024-04-04 16:27 采纳率: 100%
浏览 38
已结题

Flask转换为FastAPI后,Socketio客户端无法连接服务端问题

背景:
原本的项目使用Flask和Socket.IO进行开发,现在需要将Flask转换为FastAPI。在转换过程中遇到了一些问题。

问题现象:
在Python中的客户端无法与服务端建立连接。客户端代码看起来没有明显的问题,命名空间和服务端的地址看起来都没问题,并且在原来的Flask中是正常使用的。但是转换之后似乎连接不上。因为服务端没有接收到客户端发送的消息。服务端代码如下:

app = FastAPI()
logging.basicConfig(level=logging.INFO)
#设置允许的跨域源
origins = [
    "http://localhost:8080",  # Vue.js 前端地址
    "http://127.0.0.1:8080",  # Vue.js 前端地址
]

# 添加中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
# 其他代码
io = socketio.AsyncServer(async_mode='asgi',cors_allowed_origins=[],namespaces=['/'+token])
handle_websocket_events(io,token)

def handle_websocket_events(sio,namespace):
    #断开连接事件
    @sio.on('disconnect', namespace='/'+namespace)
    async def handle_disconnect():
        # 断开连接自动关闭子进程
        if namespace in user_process:
            user_process[namespace].terminate()

    #接收网页端选择数据,并发送给子进程
    @sio.on('updateUIState', namespace='/'+namespace)
    async def handle_ui_state(data):
        print('\n用户 '+namespace+' 的连接:\n接收到网页端选择的数据 '+data+'\n以上数据将要发送给子进程 \n')
        sio.emit('ui_state', data)
    
    #接收网页端输入数据,并发送给子进程
    @sio.on('updateUIInput', namespace='/'+namespace)
    async def handle_ui_input(data):
        print('\n用户 '+namespace+' 的连接:\n接收到网页端输入的数据 '+data+'\n以上数据将要发送给子进程 \n')
        sio.emit('input_text', data)
    
    #接收子进程输入数据,并发送给网页端
    @sio.on('childInputData', namespace='/'+namespace)
    async def get_child_data(data):
        print('\n用户 '+namespace+' 的连接:\n接收到子进程返回输入的数据 '+json.dumps(data)+'\n以上数据将要发送给网页端 \n')
        sio.emit('inputResponse', data)
    
    # 接收子进程选项数据,并发送给网页端
    @sio.on('childData', namespace='/'+namespace)
    async def get_child_data(data):
        print('\n用户 '+namespace+' 的连接:\n接收到子进程返回选择的数据 '+json.dumps(data)+'\n以上数据将要发送给网页端')
        sio.emit('uiResponse', data)
    
    # sio.attach(app,socketio_path='/game/socket.io')
    app.mount('/', socketio.ASGIApp(socketio_server=sio))

在项目中还有一个网页客户端,而在FastAPI中,Socket.IO服务似乎默认支持跨域(不明白原理)。如果不将cors_allowed_origins设置为空列表[],则在网页端的响应头中会存在重复的Access-Control-Allow-Origin和Access-Control-Allow-Credentials。

只有将cors_allowed_origins设置为空列表[],网页客户端才可以正常连接上Socket。会不会是因为cors_allowed_origins为空列表[],导致Python的客户端无法连接?

python客户端socket相关的代码如下:

def __init__(self,options,context, userName=''):
        self.userName = userName
        self.sio = socketio.AsyncClient()
        self.sio.connect('http://127.0.0.1:5000',namespaces=['/'+userName])
        self.sioEvent()
 
def sioEvent(self):
        @self.sio.on('ui_state',namespace='/'+self.userName)
        async def ui_state(state):
            #print('接收选择数据')
        
        @self.sio.on('input_text',namespace='/'+self.userName)
        async def input_text(text):
            #print('接收输入数据')

def run(self):
        format = self.changeFormat(self.options,self.context)
        #print('用户'+self.userName+'子进程发送选项数据')
        # 向主进程发送数据
        self.sio.emit('childData', format,namespace='/'+self.userName)

        while True:
            if self.state:
                break

        return self.state

由于不熟悉异步以及FastAPI,不确定是不是关于异步的问题,看了官方文档以及各种搜索都找不到解决方法,被这个问题卡了两天,实在没办法了o(╥﹏╥)o

  • 写回答

4条回答 默认 最新

  • 关注

    下午好🌅🌅🌅
    本答案参考ChatGPT-3.5

    在将Flask转换为FastAPI后,SocketIO客户端无法连接到服务端的问题可能有以下几个原因:

    1. 异步问题: FastAPI是基于异步操作的框架,而Flask是基于同步操作的框架。可能存在在转换过程中未正确处理异步操作的情况,导致客户端无法连接。需要确保在使用AsyncServer时,将socketio的事件处理函数定义为异步函数。

    2. CORS设置问题: FastAPI提供了CORS(跨域资源共享)中间件,用于处理跨域请求。在转换过程中可能存在CORS设置问题,导致客户端无法连接。需要确保在使用CORS中间件时,将允许的跨域源正确配置,例如设置origins为客户端的地址,如"http://localhost:8080"。

    解决以上问题的步骤如下:

    1. 确认异步处理正确: 确保所有的SocketIO事件处理函数都定义为异步函数,例如使用async def关键字定义函数,并使用异步版本的装饰器@sio.on来注册事件处理函数。

    2. 检查CORS设置: 确保在使用CORS中间件时,将允许的跨域源正确配置。可以将客户端地址添加到origins列表中,以允许跨域请求。

    3. 调试客户端代码: 可以使用打印语句或日志记录来调试客户端代码,检查是否能够正确连接到服务端,并接收到事件。确保客户端代码中的地址和命名空间与服务端配置匹配。

    修改后的代码示例如下(主要修改sioEvent函数中的事件处理函数定义):

    def __init__(self, options, context, userName=''):
        self.userName = userName
        self.sio = socketio.AsyncClient()
        self.sio.connect('http://127.0.0.1:5000', namespaces=['/' + userName])
        self.sioEvent()
    
    def sioEvent(self):
        @self.sio.on('ui_state', namespace='/' + self.userName)
        async def ui_state(state):
            print('接收选择数据')
    
        @self.sio.on('input_text', namespace='/' + self.userName)
        async def input_text(text):
            print('接收输入数据')
    
    def run(self):
        format = self.changeFormat(self.options, self.context)
        self.sio.emit('childData', format, namespace='/' + self.userName)
    
        while True:
            if self.state:
                break
    
        return self.state
    

    希望以上解决方案能够帮助到你解决问题。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论 编辑记录
查看更多回答(3条)

报告相同问题?

问题事件

  • 系统已结题 4月22日
  • 已采纳回答 4月14日
  • 创建了问题 4月4日

悬赏问题

  • ¥15 35114 SVAC视频验签的问题
  • ¥15 impedancepy
  • ¥15 在虚拟机环境下完成以下,要求截图!
  • ¥15 求往届大挑得奖作品(ppt…)
  • ¥15 如何在vue.config.js中读取到public文件夹下window.APP_CONFIG.API_BASE_URL的值
  • ¥50 浦育平台scratch图形化编程
  • ¥20 求这个的原理图 只要原理图
  • ¥15 vue2项目中,如何配置环境,可以在打完包之后修改请求的服务器地址
  • ¥20 微信的店铺小程序如何修改背景图
  • ¥15 UE5.1局部变量对蓝图不可见