PythonAnywhere部署Flask视频流服务器:访问时上传停滞问题求助
问题:PythonAnywhere部署Flask视频流服务时的阻塞问题
部署环境:PythonAnywhere
工作机制
- 服务器接收客户端上传的图像并更新至网页
- 其他客户端访问网页即可观看实时视频流
- 服务器不存储图像,仅实时展示收到的帧
问题描述
客户端向服务器上传图像过程流畅,但一旦有用户打开网页,客户端的图像上传就会停滞,服务器无法在有人访问网页时接收图像。
原同步服务器代码
from flask import Flask, Response, render_template from flask import request import cv2 import numpy as np app = Flask(__name__) global current_frame current_frame = None def generate_frames(): while True: if current_frame is not None: ret, buffer = cv2.imencode('.jpg', current_frame) if ret: frame_bytes = buffer.tobytes() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n') @app.route('/') def index(): return render_template('index.html') @app.route('/video_feed') def video_feed(): return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame') @app.route('/upload_frame', methods=['POST']) def upload_frame(): global current_frame frame = request.data nparr = np.fromstring(frame, np.uint8) current_frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR) return "Frame received and displayed" if __name__ == '__main__': app.run('0.0.0.0')
客户端代码
import cv2 import requests import numpy as np import time server_url = "http://danial880.pythonanywhere.com/upload_frame" cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: print("Failed to capture frame") break _, buffer = cv2.imencode('.jpg', frame) frame_bytes = buffer.tobytes() try: # 发送帧到服务器 response = requests.post(server_url, data=frame_bytes, headers={"Content-Type": "image/jpeg"}) if response.status_code == 200: print("Frame uploaded successfully") else: print(f"Frame upload failed with status code {response.status_code}") except requests.exceptions.RequestException as e: print(f"Error: {e}") #cv2.imshow("Webcam Feed", frame) time.sleep(0.1) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
已尝试方案:改为异步模式
尝试将服务器改为异步,但修改后网页无法显示图像;原同步服务器至少能显示最后收到的帧。修改后的代码如下:
from flask import Flask, Response, render_template from flask import request import cv2 import numpy as np import asyncio app = Flask(__name__) global current_frame current_frame = None async def generate_frames(): while True: if current_frame is not None: ret, buffer = cv2.imencode('.jpg', current_frame) if ret: frame_bytes = buffer.tobytes() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n') await asyncio.sleep(0.01) # 增加延迟降低CPU负载 @app.route('/') def index(): return render_template('index.html') @app.route('/video_feed') def video_feed(): return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame') @app.route('/upload_frame', methods=['POST']) async def upload_frame(): global current_frame frame = request.data nparr = np.fromstring(frame, np.uint8) current_frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR) return "Frame received and displayed" if __name__ == '__main__': app.run()
解决方案建议
核心问题分析
原同步服务器的问题在于Flask默认是单线程模式,当有用户访问/video_feed时,该请求会占用唯一的线程,无限循环生成帧,导致/upload_frame的请求无法被处理,出现阻塞。而异步方案无效是因为PythonAnywhere使用WSGI服务器,不支持Flask的异步ASGI模式,无法正确处理异步生成器。
具体修改步骤
针对同步代码进行以下调整,即可解决阻塞问题:
- 启用多线程模式:在
app.run()中添加threaded=True,让Flask能同时处理多个请求。 - 添加线程锁:全局变量
current_frame在多线程下读写会存在竞争问题,用threading.Lock()保护其访问。 - 降低CPU占用:在
generate_frames的循环中添加小延迟,释放CPU资源给其他线程。
修改后的服务器代码:
from flask import Flask, Response, render_template from flask import request import cv2 import numpy as np import threading import time app = Flask(__name__) current_frame = None frame_lock = threading.Lock() # 创建线程锁 def generate_frames(): while True: with frame_lock: # 获取锁读取帧 frame_to_encode = current_frame.copy() if current_frame is not None else None if frame_to_encode is not None: ret, buffer = cv2.imencode('.jpg', frame_to_encode) if ret: frame_bytes = buffer.tobytes() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n') time.sleep(0.01) # 释放CPU,避免占用过高 @app.route('/') def index(): return render_template('index.html') @app.route('/video_feed') def video_feed(): return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame') @app.route('/upload_frame', methods=['POST']) def upload_frame(): global current_frame frame = request.data nparr = np.fromstring(frame, np.uint8) new_frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR) with frame_lock: # 获取锁写入帧 current_frame = new_frame return "Frame received and displayed" if __name__ == '__main__': app.run('0.0.0.0', threaded=True) # 启用多线程
额外说明
- 线程锁的作用是确保同一时间只有一个线程读写
current_frame,避免出现帧数据不完整的情况。 time.sleep(0.01)的延迟可以根据实际情况调整,既保证视频流畅度,又不会占用过多CPU。- PythonAnywhere的WSGI环境支持Flask的多线程模式,无需额外配置即可运行修改后的代码。
内容的提问来源于stack exchange,提问作者Muhammad Danial Khan
相关产品推荐
相关产品推荐

