C#与Fast API WebSocket通信帧率极低问题排查
问题描述
我实现了一套C#客户端与FastAPI服务端的WebSocket通信程序:
C#客户端捕获摄像头实时帧,发送至FastAPI服务端,服务端将图像回传给C#客户端并在PictureBox中显示。本示例中FastAPI未对图像做处理,仅用于测试通信流程,但实际场景中会进行图像处理。
C#(客户端)代码
private void CaptureCameraCallback(int camInd, PictureBox pictureBox) { // 创建WebSocket连接 using (var ws = new WebSocketSharp.WebSocket("ws://localhost:8000/ws")) { // 连接到WebSocket服务器 ws.Connect(); // 创建视频捕获对象 using (var capture = new VideoCapture(camInd)) { // 循环捕获帧并发送到服务端 while (true) { // 创建Mat对象存储帧 using (var frame = new Mat()) { // 读取摄像头下一帧 capture.Read(frame); // 检查帧是否为空 if (!frame.Empty()) { // 将帧编码为JPEG var buffer = new byte[frame.Width * frame.Height * frame.ElemSize()]; Cv2.ImEncode(".jpg", frame, out buffer); // 发送图像到服务端 ws.Send(buffer); // 监听服务端响应 ws.OnMessage += (sender, e) => { // 将服务端响应转换为图像 var responseBytes = e.RawData; using (var responseStream = new MemoryStream(responseBytes)) { var responseImage = Image.FromStream(responseStream); // 在UI线程显示图像 pictureBox.Invoke((MethodInvoker)(() => { pictureBox.Image = responseImage; })); } }; } else { // 帧为空时记录错误并继续 Debug.WriteLine("无法捕获帧"); continue; } } } } } }
FastAPI(服务端)代码
from fastapi import FastAPI, WebSocket import cv2 import numpy as np class WebSocketServer: def __init__(self): self.app = FastAPI() @self.app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): print("接受连接") await websocket.accept() print('已接受') while True: try: image_bytes = await websocket.receive_bytes() nparr = np.frombuffer(image_bytes, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # 此处可添加图像处理逻辑 # ... # 回传处理后的图像 _, buffer = cv2.imencode('.jpg', img) response_bytes = buffer.tobytes() await websocket.send_bytes(response_bytes) except: pass break def run(self, host: str = '0.0.0.0', port: int = 8000): import uvicorn uvicorn.run(self.app, host=host, port=port) if __name__ == '__main__': server = WebSocketServer() server.run() # 启动命令:uvicorn fastApi:app --reload
图像能成功收发,但帧率极低(不足1FPS),我怀疑是PictureBox的图像处理或显示方式有误,请问问题出在哪?
问题分析与解决方案
核心问题1:重复绑定OnMessage事件
每一次循环捕获帧时都重新绑定ws.OnMessage事件,导致同一个事件被注册几十上百次。每次服务端返回响应,所有绑定的事件处理函数都会执行,大量冗余UI更新直接拖慢帧率。
核心问题2:UI线程阻塞与低效更新
- 循环捕获帧的逻辑在主线程执行,未使用异步或多线程,导致UI线程被阻塞,无法及时响应刷新请求。
- 每次直接替换
pictureBox.Image,未释放旧图像资源,易引发内存泄漏,间接影响性能;且Image.FromStream依赖的流关闭后图像可能失效。
核心问题3:WebSocket同步发送阻塞
ws.Send(buffer)是同步调用,等待服务端响应时当前线程被挂起,无法继续捕获下一张帧,这是帧率低的关键原因之一。
修复后的C#客户端代码
private async void CaptureCameraCallback(int camInd, PictureBox pictureBox) { using (var ws = new WebSocketSharp.WebSocket("ws://localhost:8000/ws")) { // 仅绑定一次OnMessage事件 ws.OnMessage += (sender, e) => { var responseBytes = e.RawData; using (var responseStream = new MemoryStream(responseBytes)) { // 使用Bitmap并克隆图像,避免流关闭后图像失效 using (var responseBitmap = new Bitmap(responseStream)) { var displayBitmap = (Bitmap)responseBitmap.Clone(); pictureBox.Invoke((MethodInvoker)(() => { // 释放旧图像资源,避免内存泄漏 pictureBox.Image?.Dispose(); pictureBox.Image = displayBitmap; })); } } }; ws.Connect(); using (var capture = new VideoCapture(camInd)) { // 将捕获逻辑放到后台线程,避免阻塞UI await Task.Run(() => { while (ws.IsAlive) { using (var frame = new Mat()) { if (!capture.Read(frame)) { Debug.WriteLine("无法捕获帧"); continue; } // 设置JPEG质量参数,平衡传输速度与图像质量 var encodeParams = new List<int> { (int)ImwriteFlags.JpegQuality, 70 }; Cv2.ImEncode(".jpg", frame, out var buffer, encodeParams); // 使用异步发送,避免阻塞捕获线程 ws.SendAsync(buffer, (success) => { if (!success) { Debug.WriteLine("发送失败"); } }); // 控制捕获频率,避免发送过快导致拥塞 Thread.Sleep(30); // 约30FPS } } }); } } }
额外优化建议
- 服务端优化:
- 移除未使用的
StreamingResponse导入。 - 捕获异常时添加日志记录,不要直接
pass,方便排查连接断开问题。
- 移除未使用的
- 图像传输优化:
- 根据实际需求降低摄像头分辨率,减少单帧数据量。
- 调整JPEG质量参数,在可接受的画质范围内尽量减小传输体积。
- 线程管理:
- 使用
CancellationToken控制循环终止,避免强制退出导致资源泄漏。
- 使用
内容的提问来源于stack exchange,提问作者Captain C
相关产品推荐
相关产品推荐

