news 2026/9/24 5:16:31

ESP32无线图像传输实战:WebSocket实时视频流方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32无线图像传输实战:WebSocket实时视频流方案

1. 为什么要在ESP32上折腾无线图像传输

第一次冒出“用ESP32传图像”这个念头,是在做一个远程监控小车项目的时候。摄像头装在小车上,人坐在电脑前,中间隔了一堵墙,拉线不现实,蓝牙带宽又不够,于是很自然地把目光投向了WiFi加WebSocket这条路。实测下来,这套方案在室内环境下跑个10到15帧的实时画面完全没问题,分辨率控制在160x120或者128x160这个级别,延迟能压到200毫秒以内,对于大多数非专业监控场景已经够用了。

这篇文章想聊的,就是怎么用ESP32把摄像头采集到的图像数据,通过WebSocket协议实时推送到浏览器或者上位机上显示。核心关键词就几个:ESP32WebSocket实时视频流无线图像传输,另外顺带会提到TFT屏幕的本地显示方案,因为很多时候你需要一个本地的预览窗口来确认摄像头到底拍到了什么。

适合谁来读?如果你已经玩过ESP32的基础GPIO和WiFi连接,想进一步做点带图像的项目,比如智能小车图传、远程看护、简易安防监控,那这篇内容应该能帮你省下不少查资料和踩坑的时间。如果你连Arduino IDE或者ESP-IDF都还没装好,建议先把开发环境跑通再来看,不然中间的一些配置细节可能会让你有点懵。

需要提前说清楚的是,ESP32不是专业的视频处理芯片,它的内存和算力都有硬上限。你不可能用它推1080P的流畅视频,那不是它的活。但如果你接受低分辨率、中等帧率、JPEG压缩传输这个前提,它能在成本和功耗之间给你一个相当漂亮的平衡点。

2. 整体方案设计与核心思路拆解

2.1 为什么选WebSocket而不是HTTP轮询或RTSP

图像传输的协议选择,直接决定了整个系统的延迟、带宽占用和实现复杂度。我一开始试过HTTP轮询的方式,就是浏览器每隔几十毫秒发一个GET请求去拉一张JPEG图片。这种做法实现起来最简单,但问题很明显:每次请求都要重新建立TCP连接(即使有Keep-Alive,HTTP头部的开销也不小),而且轮询间隔不好把握——太短了ESP32处理不过来,太长了画面就卡顿。实测下来,HTTP轮询在160x120分辨率下最多跑到5到6帧,再往上服务器端就开始丢请求了。

RTSP是专业的流媒体协议,功能强大,但在ESP32上实现一个完整的RTSP服务器相当复杂,而且浏览器端不能直接播放RTSP流,还得额外搭一个转码服务,整个架构就变重了。

WebSocket的好处在于:它在一次HTTP握手之后,就升级成了一个全双工的TCP长连接。之后服务器可以主动往客户端推数据,不需要客户端反复请求。对于ESP32来说,这意味着它可以在采集完一帧图像后立刻推出去,不用等客户端的请求。而且WebSocket的帧头部开销很小,通常只有2到10个字节,相比HTTP每次几百字节的头部,带宽利用率高得多。

另一个关键优势是浏览器原生支持WebSocket。你不需要安装任何插件,用JavaScript的WebSocket对象就能接收二进制数据,然后通过Blob或者ArrayBuffer转成图片显示。这让整个系统的前端变得非常轻量。

2.2 图像采集与压缩的取舍

ESP32上常用的摄像头模块是OV2640,它自带JPEG压缩功能,可以直接输出JPEG格式的图像数据。这一点非常重要,因为如果让ESP32自己去压缩一张RGB565的原始图像,那点算力根本不够看。OV2640的硬件JPEG编码器可以在采集的同时完成压缩,ESP32只需要把压缩后的数据读出来就行。

分辨率的选择需要权衡。OV2640支持从160x120到1600x1200的多种分辨率。分辨率越高,单帧图像越大,传输时间越长,帧率就越低。我实测过几组数据:在160x120下,一帧JPEG大约3到5KB,ESP32可以跑到15帧左右;在320x240下,一帧大约8到12KB,帧率降到8帧左右;到了640x480,一帧20到30KB,帧率就只有3到4帧了。所以如果你的应用场景对帧率有要求,建议把分辨率控制在320x240以下。

JPEG质量参数也值得调。OV2640的JPEG质量可以从0到63,数值越小质量越高、文件越大。我一般设在12到20之间,这个区间在画质和文件大小之间取得了比较好的平衡。设到10以下,文件大小增加明显但画质提升有限;设到30以上,画面就会出现明显的块状伪影。

2.3 系统架构的两种模式

整个系统的架构可以分成两种模式:直连模式服务器中转模式

直连模式是ESP32自己作为WebSocket服务器,浏览器或者上位机直接连到ESP32的IP地址。这种模式最简单,不需要额外的服务器,适合局域网内使用。缺点是ESP32需要同时处理摄像头采集、JPEG读取、WebSocket服务端逻辑,任务比较重,连接的客户端数量也有限(一般不超过4个)。

服务器中转模式是ESP32作为WebSocket客户端,把图像数据推送到一台中间服务器(比如用Python或者Node.js搭的),再由服务器分发给多个浏览器客户端。这种模式适合需要多客户端同时观看的场景,而且服务器可以做录制、转发、图像处理等额外工作。缺点是架构复杂一些,需要维护一台服务器。

我个人的建议是:如果你只是自己用,或者客户端数量很少,直接用直连模式,省事。如果需要给多人看,或者要做远程访问,那就上服务器中转。

2.4 内存管理的核心考量

ESP32的内存分为内部SRAM和外部PSRAM。如果你用的是ESP32-CAM或者带PSRAM的模组,那内存会宽裕很多。一帧320x240的JPEG大约10KB,双缓冲就需要20KB,再加上WebSocket的发送缓冲区、WiFi协议栈的开销,没有PSRAM的话内部SRAM会比较紧张。

我强烈建议使用带PSRAM的ESP32模组,比如ESP32-WROVER或者ESP32-S3。PSRAM可以通过ps_malloc()来分配帧缓冲区,不占用宝贵的内部SRAM。如果实在没有PSRAM,那就把分辨率降到160x120,并且使用单缓冲加直接发送的方式,减少内存占用。

另外要注意的是,WebSocket发送大帧的时候,不要一次性把整个帧数据拷贝到发送缓冲区。ESP32的WebSocket库(比如arduinoWebSockets)支持分片发送,你可以把一帧图像分成几个片段依次发送,这样每次只需要一小块缓冲区。不过分片发送会增加协议开销,需要根据实际情况权衡。

3. 核心细节解析与实操要点

3.1 硬件选型与接线要点

摄像头模块的选择上,OV2640是最常见也是最容易买到的。它支持JPEG输出,接口是DVP(数字视频端口),需要连接不少引脚。以ESP32-CAM为例,它已经把OV2640和ESP32集成在一块板子上了,用起来最省事。如果你用的是独立的ESP32开发板加独立的OV2640模块,那接线就需要仔细对照引脚定义。

OV2640的DVP接口包括:数据线D0到D7、像素时钟PCLK、行同步HSYNC、场同步VSYNC、主时钟XCLK,另外还有I2C的SDA和SCL用于配置摄像头寄存器。ESP32的I2S接口可以模拟DVP时序,但引脚映射需要根据你的开发板来调整。ESP32-CAM的引脚映射是固定的,直接用现成的例程就行。如果是自己接线,建议参考ESP32的I2S引脚定义,把数据线接到同一组I2S引脚上。

电源方面要特别注意。OV2640在工作时电流波动比较大,尤其是JPEG编码的时候。如果电源不稳,图像会出现花屏或者摄像头直接不工作。建议给摄像头单独加一个100uF的电解电容做滤波,并且确保3.3V电源能提供至少500mA的电流。我遇到过好几次图像随机花屏的问题,最后发现都是电源纹波太大导致的。

如果要用TFT屏幕做本地预览,常见的1.8寸TFT LCD分辨率是128x160,驱动芯片一般是ST7735。它通过SPI接口和ESP32通信,接线比较简单:SCLK、MOSI、CS、DC、RST,再加上背光控制。需要注意的是,TFT和摄像头不要共用SPI总线,否则会互相干扰。如果引脚不够用,可以考虑用I2C接口的OLED屏幕做状态显示,把TFT留给图像预览。

3.2 WebSocket服务端的实现细节

在ESP32上实现WebSocket服务端,我推荐使用arduinoWebSockets这个库,它在Arduino生态里比较成熟,支持服务端和客户端两种模式。安装方式很简单,在Arduino IDE的库管理器里搜索WebSockets就能找到。

服务端初始化的核心代码如下:

#include <WiFi.h> #include <WebSocketsServer.h> WebSocketsServer webSocket = WebSocketsServer(81); void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { switch(type) { case WStype_DISCONNECTED: Serial.printf("[%u] Disconnected!\n", num); break; case WStype_CONNECTED: { IPAddress ip = webSocket.remoteIP(num); Serial.printf("[%u] Connected from %d.%d.%d.%d\n", num, ip[0], ip[1], ip[2], ip[3]); } break; case WStype_TEXT: Serial.printf("[%u] get Text: %s\n", num, payload); break; } } void setup() { Serial.begin(115200); WiFi.begin("SSID", "PASSWORD"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(WiFi.localIP()); webSocket.begin(); webSocket.onEvent(webSocketEvent); } void loop() { webSocket.loop(); }

这段代码建立了一个监听81端口的WebSocket服务端。注意端口选择,80端口通常被HTTP服务器占用,如果你同时要提供网页服务,WebSocket可以用81或者其他端口。

发送图像数据的核心逻辑是:当摄像头采集完一帧JPEG后,调用webSocket.broadcastBIN(frameBuffer, frameLength)把数据推送给所有连接的客户端。broadcastBIN会遍历所有客户端并逐个发送,如果客户端数量多,发送时间会线性增加。

这里有一个关键细节:broadcastBIN是阻塞式的,发送期间ESP32不能做其他事情。如果一帧图像是10KB,WiFi带宽是10Mbps,那发送一帧大约需要8毫秒。这个时间不算长,但如果帧率很高,累积起来就会影响摄像头采集的时序。解决办法是使用双缓冲:一个缓冲区在采集,另一个在发送,采集和发送交替进行。

3.3 摄像头初始化的关键参数

OV2640的初始化涉及大量寄存器配置,好在esp32-camera库已经封装好了。你只需要在camera_config_t结构体里填几个关键参数:

#include "esp_camera.h" camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = 5; config.pin_d1 = 18; config.pin_d2 = 19; config.pin_d3 = 21; config.pin_d4 = 36; config.pin_d5 = 39; config.pin_d6 = 34; config.pin_d7 = 35; config.pin_xclk = 0; config.pin_pclk = 22; config.pin_vsync = 25; config.pin_href = 23; config.pin_sscb_sda = 26; config.pin_sscb_scl = 27; config.pin_pwdn = 32; config.pin_reset = -1; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; config.frame_size = FRAMESIZE_QVGA; config.jpeg_quality = 12; config.fb_count = 2;

xclk_freq_hz设为20MHz是OV2640的典型值,设太高会导致图像不稳定,设太低帧率上不去。frame_size我一般用FRAMESIZE_QVGA也就是320x240,如果内存紧张就降到FRAMESIZE_QQVGA即160x120。fb_count是帧缓冲数量,设为2可以实现双缓冲,但会占用双倍内存。如果PSRAM够大,设2没问题;如果内存紧张,设1也行,但帧率会受一些影响。

jpeg_quality的范围是0到63,数值越小质量越高。我前面说过12到20是比较好的区间,但具体值要根据你的场景调。如果是监控类应用,画面细节不是特别重要,可以设到20甚至25,文件能小不少。

3.4 前端接收与显示的实现

浏览器端的代码不复杂,核心就是创建一个WebSocket连接,然后在onmessage事件里把收到的二进制数据转成图片显示:

const ws = new WebSocket('ws://192.168.1.100:81'); ws.binaryType = 'arraybuffer'; ws.onmessage = function(event) { const blob = new Blob([event.data], {type: 'image/jpeg'}); const url = URL.createObjectURL(blob); const img = document.getElementById('videoFrame'); img.onload = function() { URL.revokeObjectURL(url); }; img.src = url; };

这里有一个性能陷阱:每次收到新帧都创建BlobObjectURL,如果帧率很高,浏览器会频繁进行内存分配和垃圾回收,导致页面卡顿。优化方法是复用同一个Image对象,并且在onload之后立刻释放上一个URL。另外,如果不需要显示每一帧,可以在onmessage里做一个简单的丢帧逻辑,比如每两帧只显示一帧。

还有一个细节:ws.binaryType必须设为arraybuffer,否则收到的数据是Blob类型,处理起来会多一层异步转换。设成arraybuffer之后,event.data就是ArrayBuffer,可以直接构造Blob

如果要在TFT屏幕上做本地预览,那就需要在ESP32端把JPEG解码成RGB565,然后通过SPI推送到屏幕。JPEG解码可以用TJpg_Decoder库,它支持从内存中解码JPEG。解码后的RGB数据通过TFT_eSPI库的pushImage方法写入屏幕。这个过程比较耗时,320x240的JPEG解码大约需要100到200毫秒,所以本地预览的帧率会比较低,大概3到5帧。如果只是用来确认摄像头工作状态,这个帧率足够了。

4. 完整实操流程与核心环节实现

4.1 开发环境搭建与库依赖安装

我用的开发环境是Arduino IDE 2.x加上ESP32开发板支持包。安装步骤不复杂,但有几个地方容易卡住。

首先在Arduino IDE的“首选项”里,把开发板管理器地址加上乐鑫的官方地址。然后在开发板管理器里搜索esp32,安装最新版本。安装过程需要下载几百MB的文件,网络不好的话可能会失败,多试几次或者换个时间段。

库依赖方面,需要安装以下几个:

  • esp32-camera:摄像头驱动,乐鑫官方维护的
  • arduinoWebSockets:WebSocket服务端和客户端
  • TFT_eSPI:TFT屏幕驱动(如果要用本地预览)
  • TJpg_Decoder:JPEG解码(如果要用本地预览)

安装方式都是在库管理器里搜索名称然后点安装。注意esp32-camera在库管理器里可能搜不到,需要从GitHub手动下载然后放到libraries目录下。

开发板选择上,如果你用的是ESP32-CAM,选“AI Thinker ESP32-CAM”;如果是ESP32-S3,选对应的S3型号。分区方案要选“Huge APP”或者带PSRAM的方案,否则编译出来的固件可能超出默认分区大小。

4.2 摄像头采集任务的独立化

在Arduino环境下,loop()函数是单线程的,如果摄像头采集、WebSocket发送、TFT显示都放在loop()里顺序执行,任何一个环节卡住都会影响其他环节。我的做法是把摄像头采集放到一个独立的任务里,用FreeRTOS的xTaskCreatePinnedToCore把它固定到另一个核心上。

ESP32有两个核心,核心0通常跑WiFi协议栈,核心1跑应用逻辑。我把摄像头采集任务固定在核心1上,WebSocket发送放在loop()里也就是核心1的默认任务里,这样采集和发送可以并行。具体代码结构如下:

TaskHandle_t cameraTaskHandle; void cameraTask(void *parameter) { while (1) { camera_fb_t *fb = esp_camera_fb_get(); if (fb) { // 把帧数据放入队列或者直接发送 webSocket.broadcastBIN(fb->buf, fb->len); esp_camera_fb_return(fb); } vTaskDelay(1); } } void setup() { // ... 初始化代码 ... xTaskCreatePinnedToCore( cameraTask, "CameraTask", 4096, NULL, 1, &cameraTaskHandle, 1 ); }

这里有一个坑:webSocket.broadcastBIN不是线程安全的,如果多个任务同时调用会出问题。所以要么只在摄像头任务里调用,要么加互斥锁。我选择只在摄像头任务里调用,loop()里只跑webSocket.loop()处理连接事件。

4.3 双缓冲与帧率控制

双缓冲的实现思路是:准备两个帧缓冲区,摄像头采集时写入缓冲区A,采集完成后把缓冲区A标记为“待发送”,然后切换到缓冲区B继续采集。发送任务从“待发送”的缓冲区读取数据发送,发送完成后标记为“空闲”。

esp32-camera库里,fb_count设为2时,esp_camera_fb_get()会自动在双缓冲之间切换。但要注意,esp_camera_fb_get()返回的帧缓冲在调用esp_camera_fb_return()之前不能被复用。所以如果你在发送期间持有帧缓冲,摄像头就没有缓冲区可用了,会阻塞采集。

解决办法是:拿到帧缓冲后,先把数据拷贝到一个独立的发送缓冲区,然后立刻调用esp_camera_fb_return()释放帧缓冲。发送任务从发送缓冲区读数据。这样摄像头采集和WebSocket发送就完全解耦了。代价是多了一次内存拷贝,但10KB的拷贝在ESP32上只需要几十微秒,可以接受。

帧率控制方面,我一般会在摄像头任务里加一个vTaskDelay,根据目标帧率计算延迟时间。比如目标15帧,每帧间隔约66毫秒,扣除采集和发送的时间,延迟设20到30毫秒比较合适。如果不加延迟,摄像头会全速采集,帧率可能跑到20帧以上,但WiFi带宽和客户端处理能力可能跟不上,反而导致卡顿。

4.4 客户端连接管理与异常处理

WebSocket客户端断开连接时,arduinoWebSockets库会自动从客户端列表里移除。但有时候客户端异常断开(比如浏览器直接关闭),库可能不会立刻检测到,需要靠心跳机制来清理。

我一般会在ESP32端定期发送一个小的Ping帧,客户端收到后回复Pong。如果连续几次没有收到Pong,就主动断开该客户端。arduinoWebSockets库支持enableHeartbeat方法,可以设置心跳间隔和超时次数:

webSocket.enableHeartbeat(15000, 3000, 2);

这表示每15秒发送一次Ping,等待3秒,如果连续2次没有响应就断开。这个参数可以根据你的网络状况调整。局域网内可以设长一点,减少开销;网络不稳定的话设短一点,及时清理死连接。

还有一个常见问题是客户端连接数过多导致内存不足。每个WebSocket连接都会占用一定的内存(大约几KB),ESP32的内部SRAM有限,连接数太多会触发内存分配失败。我一般限制最多4个客户端,在webSocketEventWStype_CONNECTED事件里检查当前连接数,超过就拒绝新连接。

5. 常见问题与排查技巧实录

5.1 图像花屏、条纹、颜色异常的排查

图像质量问题是最常见的,原因也最多。我整理了一个排查表,按可能性从高到低排列:

现象可能原因排查方法解决方法
随机花屏电源纹波大用示波器看3.3V电源加100uF电容,换LDO
固定条纹数据线接触不良检查D0-D7焊接重新焊接或换线
颜色偏绿/偏紫时钟极性错误检查XCLK频率调整xclk_freq_hz
图像上下颠倒摄像头方向确认模块安装方向软件里设置翻转
图像模糊镜头焦距不对手动旋转镜头调焦至清晰
亮度异常曝光参数检查sensor设置调整曝光寄存器

电源问题是最容易被忽略的。我遇到过好几次图像随机花屏,换了摄像头、改了代码都没用,最后用示波器一看,3.3V电源上有200mV的纹波。加了一个100uF的电解电容和一个0.1uF的陶瓷电容之后,问题立刻消失。所以如果你遇到莫名其妙的图像问题,先查电源。

数据线接触不良也会导致花屏,但通常是固定位置的条纹或者色块。这种情况重新焊接或者换一根短一点的排线就能解决。排线太长(超过10厘米)也容易引入干扰,建议尽量短。

5.2 WebSocket连接失败与断连的排查

WebSocket连不上的原因也很多,按排查顺序来:

首先确认ESP32的IP地址。在串口监视器里看启动时打印的IP,确保和浏览器访问的地址一致。如果IP是0.0.0.0,说明WiFi没连上,检查SSID和密码。

然后确认端口。ESP32的WebSocket服务端监听81端口,浏览器里要写ws://IP:81。如果写成了http://或者端口不对,肯定连不上。

防火墙也是一个常见问题。有些路由器默认开启AP隔离,同一WiFi下的设备不能互相通信。需要在路由器设置里关闭AP隔离。另外电脑的防火墙也可能拦截WebSocket连接,临时关闭防火墙测试一下。

如果连接建立后频繁断连,先看串口日志里有没有内存分配失败的报错。ESP32内存不足时,WebSocket库可能无法维持连接。降低分辨率、减少客户端数量、关闭不必要的功能都能缓解。

还有一个隐蔽的问题:WiFi信道拥堵。如果周围WiFi网络很多,2.4GHz频段会非常拥挤,导致丢包和断连。可以在路由器里把信道固定到1、6、11中的一个,避开自动选择的拥挤信道。或者改用5GHz频段,但ESP32只支持2.4GHz,所以只能优化2.4GHz的环境。

5.3 帧率上不去的优化思路

帧率低是另一个高频问题。影响帧率的因素按影响程度排序:分辨率、JPEG质量、WiFi带宽、客户端处理速度。

分辨率的影响最大。从320x240降到160x120,帧率大概能翻倍。如果帧率实在上不去,先降分辨率。

JPEG质量的影响次之。从12调到20,文件大小能减少30%左右,帧率相应提升。画质会有所下降,但监控场景通常可以接受。

WiFi带宽方面,ESP32的WiFi理论速率是72Mbps,但实际TCP吞吐量也就10到20Mbps。一帧10KB,15帧就是150KB/s,也就是1.2Mbps,远没到带宽上限。所以带宽通常不是瓶颈,除非周围干扰严重。

客户端处理速度容易被忽略。浏览器收到JPEG数据后要解码显示,如果电脑性能差或者浏览器标签页太多,解码速度跟不上,就会导致画面卡顿。可以在浏览器端做丢帧处理,只显示最新的帧,跳过积压的旧帧。

还有一个ESP32端的优化:使用webSocket.broadcastBIN时,如果客户端数量多,发送是串行的。可以改成多线程发送,每个客户端一个发送任务。但这样内存开销会增大,需要权衡。

5.4 内存不足的典型表现与解决

内存不足的表现包括:摄像头初始化失败、WebSocket连接建立后立刻断开、程序随机重启、串口打印malloc failed

排查方法是打印ESP.getFreeHeap()ESP.getFreePsram(),看看剩余内存有多少。正常情况下,内部SRAM应该至少有50KB空闲,PSRAM至少有1MB空闲。

如果内存不足,按以下顺序优化:

  1. 降低分辨率,从QVGA降到QQVGA,帧缓冲从10KB降到3KB
  2. 减少帧缓冲数量,fb_count从2降到1
  3. 关闭TFT本地预览,省下TFT驱动和解码器的内存
  4. 减少WebSocket客户端数量上限
  5. 使用PSRAM分配帧缓冲,把内部SRAM留给WiFi协议栈

如果用了PSRAM还是内存不足,那可能是内存泄漏。检查代码里有没有忘记freemalloc,或者WebSocket发送后没有释放缓冲区。arduinoWebSockets库在发送大帧时会在内部申请缓冲区,如果发送失败可能没有释放,需要检查库的版本是否有相关修复。

5.5 实操心得与避坑清单

最后分享几条我踩过坑之后总结的经验:

不要用ESP32的ADC引脚接摄像头数据线。ADC引脚有内部电路,会影响数字信号质量。尽量用纯数字GPIO。

摄像头和WiFi同时工作时,ESP32的发热会比较明显。如果长时间运行,建议加一个小散热片,或者降低发射功率。

WebSocket发送二进制数据时,确保客户端设置了binaryType = 'arraybuffer',否则收到的数据格式不对,解析会出错。

如果要用TFT屏幕显示,SPI时钟频率不要超过40MHz,否则屏幕可能出现雪花点。我一般设在27MHz,稳定且够快。

调试阶段先在串口打印每一帧的大小和发送耗时,这样能快速定位是采集慢还是发送慢。

如果客户端是手机浏览器,注意手机在锁屏或者切到后台时,WebSocket连接可能会被系统挂起。需要在页面可见性变化时重新建立连接。

电源一定要给足。ESP32-CAM在WiFi传输时峰值电流能到300mA以上,加上摄像头和TFT,总电流可能超过500mA。用电脑USB口供电有时候不够,建议用独立的5V/2A电源。

这套方案我前后调了大概两周,从最开始的一堆花屏和断连,到后来能稳定跑15帧、延迟200毫秒以内,中间踩的坑基本都写在上面的。如果你也在做类似的项目,希望这些经验能帮你少走点弯路。后面如果要做远程访问,可以在服务器中转模式上再下点功夫,那个又是另一个话题了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 5:05:11

Wandb Core 中的 gax-go v2:从 2.4 到 2.25 的能力演进与源码级解析

机器学习深度学习数据可视化可观测性 【免费下载链接】wandb The AI developer platform. Use Weights & Biases to train and fine-tune models, and manage models from experimentation to production. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/wa/wandb 点…

作者头像 李华
网站建设 2026/9/24 5:03:00

谢希仁《计算机网络》课后答案使用指南:版本对比与高效刷题法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 5:01:45

C语言格式化输入输出与通讯录持久化

C 语言课堂练习&#xff1a;几组输入输出函数&#xff0c;以及通讯录怎么存档 这次课的作业&#xff0c;一部分是比较 scanf、fscanf、sscanf 这些函数&#xff0c;另一部分是给之前写的动态通讯录加上文件保存。我刚看题时觉得它们的名字太像了&#xff0c;背函数名很容易背混…

作者头像 李华
网站建设 2026/9/24 4:59:42

Tinkercad Circuits零基础电路仿真实战:从LED点亮到Arduino控制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 4:45:47

Jetson Orin NX USB3.0接口配置实战:从硬件映射到设备树

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华