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

Flask传输RTSP流正常,Tornado托管后网页无法加载求助

问题分析

你的问题核心在于Tornado的WSGIContainer是同步运行WSGI应用的,而Flask接口里的gen_frames生成器包含阻塞IO操作(camera.read()),会完全占用Tornado的单IO线程,导致整个服务无法响应任何请求(包括视频流的持续推送和页面加载)。

Flask调试模式用的是Werkzeug多线程服务器,每个请求单独开线程,阻塞操作不会影响全局,但Tornado的WSGI容器是单线程同步执行,阻塞操作会直接卡死整个IO循环。

解决方案

推荐采用Tornado原生异步接口处理视频流,彻底避免阻塞IO循环,同时保留原有Flask应用的其他路由。

方案一:改用Tornado原生异步实现视频流

直接用Tornado的异步RequestHandler重写视频流接口,将阻塞的摄像头读取操作放到线程池执行,不占用IO循环:

import sys
import asyncio
import cv2
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application
from ZYBpowercfg import app  # 保留你的Flask原有应用

rtspUrl = 'rtsp://user:password@10.238.1.1/h264/ch1/sub/av_stream'

class VideoStreamHandler(RequestHandler):
    def set_default_headers(self):
        self.set_header('Access-Control-Allow-Origin', '*')
        self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
        self.set_header('Access-Control-Allow-Headers', '*')
        self.set_header('Content-Type', 'multipart/x-mixed-replace; boundary=frame')

    async def get(self):
        camera = cv2.VideoCapture(rtspUrl)
        try:
            while True:
                # 将阻塞的camera.read()放到线程池执行,不阻塞IO循环
                success, frame = await IOLoop.current().run_in_executor(None, camera.read)
                if not success:
                    continue
                ret, buffer = cv2.imencode('.jpg', frame)
                frame_bytes = buffer.tobytes()
                # 推送帧数据并立即刷新缓冲区
                self.write(b'--frame\r\n')
                self.write(b'Content-Type: image/jpeg\r\n\r\n')
                self.write(frame_bytes)
                self.write(b'\r\n')
                await self.flush()
                # 控制帧率,避免过度占用资源
                await asyncio.sleep(0.03)
        finally:
            camera.release()

class FlaskProxyHandler(RequestHandler):
    def set_default_headers(self):
        self.set_header('Access-Control-Allow-Origin', '*')
        self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
        self.set_header('Access-Control-Allow-Headers', '*')

    def handle_flask_request(self):
        # 将Tornado请求转发给Flask应用
        environ = self.request.environ
        response = app(environ, lambda status, headers, exc_info=None: None)
        self.set_status(int(response.status.split()[0]))
        for header, value in response.headers.items():
            self.set_header(header, value)
        self.write(response.get_data())

    def get(self):
        self.handle_flask_request()

    def post(self):
        self.handle_flask_request()

if __name__ == '__main__':
    if sys.platform == 'win32':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    # 路由配置:视频流用Tornado原生处理,其他请求转发给Flask
    tornado_app = Application([
        (r'/playvideo', VideoStreamHandler),
        (r'.*', FlaskProxyHandler),
    ])
    http_server = HTTPServer(tornado_app)
    http_server.listen(8082)
    IOLoop.instance().start()

方案二:给Flask阻塞操作添加线程池(兼容原有代码)

如果不想重写接口,可以将摄像头读取操作放到线程池,避免阻塞Tornado的IO循环:

修改Flask代码:

from concurrent.futures import ThreadPoolExecutor
import cv2
import asyncio

executor = ThreadPoolExecutor(max_workers=2)
rtspUrl = 'rtsp://user:password@10.238.1.1/h264/ch1/sub/av_stream'

def read_camera_frame(camera):
    return camera.read()

def gen_frames():    
    camera = cv2.VideoCapture(rtspUrl)    
    loop = asyncio.get_event_loop()
    while True:        
        # 把阻塞的读取操作放到线程池
        success, frame = loop.run_in_executor(executor, read_camera_frame, camera)
        if not success:
            continue  # 替换原有的pass,避免空转
        ret, buffer = cv2.imencode('.jpg', frame)
        frame = buffer.tobytes()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')  

@app.route('/playvideo',methods=['GET'])
def playvideo():
    return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

Tornado代码保留原有逻辑即可,但这种方式效率不如方案一,因为WSGIContainer本身还是同步的。

额外注意点
  1. 原代码中if not success: pass会导致无限空转,建议改成continue或添加重试/退出逻辑。
  2. 视频流传输需要确保缓冲区及时刷新,Tornado的await self.flush()是关键。

内容的提问来源于stack exchange,提问作者张星星

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 04:08:13