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本身还是同步的。
额外注意点
- 原代码中
if not success: pass会导致无限空转,建议改成continue或添加重试/退出逻辑。 - 视频流传输需要确保缓冲区及时刷新,Tornado的
await self.flush()是关键。
内容的提问来源于stack exchange,提问作者张星星
相关产品推荐
相关产品推荐

