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

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模式,无法正确处理异步生成器。

具体修改步骤

针对同步代码进行以下调整,即可解决阻塞问题:

  1. 启用多线程模式:在app.run()中添加threaded=True,让Flask能同时处理多个请求。
  2. 添加线程锁:全局变量current_frame在多线程下读写会存在竞争问题,用threading.Lock()保护其访问。
  3. 降低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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 13:44:51