WebSocket+OpenCV扫码:连接关闭时终止摄像头读取循环的方案
问题分析
你的核心问题是内层摄像头扫描循环为同步阻塞逻辑,且未主动检查WebSocket连接状态——只有执行await websocket.recv()或await websocket.send()时才会触发ConnectionClosed异常,但内层的cap.read()和cv2.waitKey(1)会持续占用线程,导致连接关闭时无法立即终止循环,必须等下一次条码扫描完成才能退出。
同时,每次收到客户端消息就重新初始化摄像头的做法也存在效率问题,频繁启停摄像头会增加系统开销。
解决方案
1. 主动检查连接状态,即时终止循环
在内层扫描循环的每次迭代中,主动检查WebSocket是否已关闭,一旦检测到关闭状态,立即退出循环并释放资源。
2. 优化摄像头生命周期
将摄像头的初始化移至连接建立阶段(外层循环之前),连接关闭时再统一释放,避免重复初始化的开销。
3. 避免阻塞异步事件循环
OpenCV的cap.read()和cv2.waitKey属于同步操作,会阻塞asyncio事件循环。使用asyncio.to_thread()将同步的摄像头操作放到单独线程中,保证WebSocket任务的响应性。
修改后的代码
import asyncio import websockets import cv2 from pyzbar.pyzbar import decode # 假设你使用pyzbar库的decode函数 def searchProductId(barcode_data): # 保留你的商品ID查询逻辑 return "12345" # 示例返回值 async def handler(websocket, path): cap = None try: # 连接建立时初始化摄像头,仅执行一次 cap = cv2.VideoCapture(0) cap.set(3, 640) cap.set(4, 480) while True: # 等待客户端指令 data = await websocket.recv() if not data: continue scanning = True while scanning: # 检查连接状态,已关闭则终止扫描 if websocket.closed: scanning = False break # 用to_thread封装同步操作,避免阻塞事件循环 _, img = await asyncio.to_thread(cap.read) # 异步执行条码解码 barcodes = await asyncio.to_thread(decode, img) for barcode in barcodes: barcodeData = barcode.data.decode('utf-8') productId = searchProductId(barcodeData) if productId != -1: await websocket.send(productId) await asyncio.sleep(1) productId = -1 # 可选:找到条码后停止扫描,等待下一次客户端指令 scanning = False break # 异步显示画面(不需要可删除) await asyncio.to_thread(cv2.imshow, 'Result', img) # 异步处理按键输入,避免阻塞 key = await asyncio.to_thread(cv2.waitKey, 1) # 按ESC键手动停止扫描(可选) if key == 27: scanning = False except websockets.ConnectionClosed: print("连接已关闭!") finally: # 确保无论正常退出还是异常,都释放摄像头资源 if cap is not None and cap.isOpened(): cap.release() cv2.destroyAllWindows() start_server = websockets.serve(handler, "localhost", 8000) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()
关键改进点说明
- 实时连接状态检查:在内层扫描循环中加入
if websocket.closed:判断,连接关闭时立即终止循环。 - 摄像头复用:连接建立时仅初始化一次摄像头,减少系统资源消耗。
- 异步兼容处理:用
asyncio.to_thread()封装所有同步OpenCV操作,保证WebSocket事件循环不被阻塞。 - 安全资源释放:通过
finally块确保摄像头资源在任何情况下都能被正确释放。
内容的提问来源于stack exchange,提问作者Gabriel Franco
相关产品推荐
相关产品推荐

