C#与MCGS TCP通信实战:工业级握手、CRC校验与断线重连
简介本资源是一套面向工业自动化开发者的C#与MCGS昆仑通态TCP通信实战范例适用于具备基础C#编程能力及工控通信概念的工程师、PLC上位机开发初学者和高校自动化专业学生解决上位机软件与MCGS嵌入式组态系统之间稳定读取实时数据的核心问题。压缩包共36个文件9个C#源码文件.cs构成完整Windows Forms通信客户端含Socket连接管理、寄存器地址解析与数据解析逻辑3个可执行文件.exe支持即点即试配套.sln工程文件、.csproj项目配置及.mce组态工程文件便于快速复现软硬件协同场景另有txt说明文档与config配置项辅助理解通信协议参数。资源仅92KB轻量易导入已获2505人学习下载。读者可直接运行调试、分析TCP握手流程与MCGS数据区映射关系掌握字节序处理、超时重连机制及异常日志记录等关键工业通信实践细节。1. C#与MCGS通过TCP通信不是“连上就行”而是要稳住心跳、扛住断线、对齐字节序的工业级握手你写完C# TCP客户端用TcpClient.Connect()连上MCGS触摸屏IP和端口NetworkStream.Write()发了一串字节——屏幕没反应再试一次数据乱码第三次连接直接被MCGS主动断开。这不是代码写错了是掉进了工业通信的「三重静默陷阱」MCGS默认启用超时强制断连机制非标准Socket Keep-Alive、严格校验报文头CRC16不校验就丢包、要求小端字节序固定帧结构大端或错位1字节即整包失效。这份范例代码不是教你怎么“发个字符串”而是把某高校自动化实验室调试37台MCGS TP系列设备后沉淀出的可落地通信骨架打包给你含完整握手协议解析、断线自动重连状态机、寄存器地址映射表生成器、以及最关键的——MCGS原厂未公开但实测有效的0x05/0x06单点写入兼容模式。适合正在做设备监控上位机、产线数据采集系统、或需要把老旧MCGS设备接入新MES平台的工程师尤其当你已卡在“能连不能通”超过2小时。2. 协议层拆解为什么MCGS的TCP不是标准Modbus RTU over TCPMCGS昆仑通态的TCP通信协议常被误认为Modbus TCP但实际是其自定义的MCGS-TCP私有协议V6.2版本与Modbus TCP存在三处关键差异直接决定你能否读到真实数据2.1 报文结构4字节头 2字节功能码 N字节数据体 2字节CRC16MCGS要求每帧必须以4字节固定头部起始0x5A 0x5A 0x00 0x00魔数保留位而非Modbus TCP的6字节MBAP头。功能码仅支持0x03读保持寄存器、0x06写单个寄存器、0x10写多个寄存器——注意0x05写单线圈在MCGS中实际等效于0x06但部分固件版本需显式发送0x05才能触发PLC侧动作这是现场踩出的玄学兼容点。2.2 寄存器地址映射MCGS的“40001”不是Modbus的物理地址MCGS将寄存器逻辑地址如“40001”转换为内部偏移量的规则是保持寄存器4xxxx→ 偏移 地址 - 40001输入寄存器3xxxx→ 偏移 地址 - 30001线圈0xxxx→ 偏移 地址 - 00001但关键陷阱在于MCGS默认启用地址偏移校验若你计算出偏移为0x0000却发了0x0001它会静默丢弃整包。范例代码中AddressConverter.cs类内置了双向验证函数输入40001返回ushort0输入0返回40001避免手算失误。2.3 CRC16校验多项式0x8005初始值0x0000无反转MCGS使用CRC16-IBM变种非Modbus标准的CRC16-Modbus多项式0x8005初始值0x0000输入/输出不反转RefInFalse, RefOutFalse示例数据0x03 0x00 0x00 0x00 0x0A读40001起10个寄存器的CRC16为0x9E2F非0x840A。范例中Crc16Calculator.cs已预置该算法且提供在线校验工具入口见Tools/CrcTester.exe。提示MCGS工程中需在“设备窗口”→“MCGS通用TCP/IP设备”属性页勾选“启用TCP协议”并确认“通讯方式”设为“TCP Client”上位机作为客户端或“TCP Server”上位机作为服务端二者通信流程完全不同。本范例默认采用上位机Client模式因90%产线部署场景如此。3. C#核心实现基于Socket的零依赖通信类与状态机设计范例代码摒弃了TcpClient高级封装直接使用Socket底层API确保对连接状态、缓冲区、超时的完全控制。核心类MCGSTcpClient.cs实现了工业场景必需的连接韧性与协议保真度。3.1 连接管理带指数退避的自动重连状态机MCGS设备重启或网络抖动时简单while(!Connected) Connect()会导致雪崩式重连请求。本方案采用有限状态机FSM控制连接生命周期public enum ConnectionState { Disconnected, // 初始态未尝试连接 Connecting, // 正在Connect()阻塞中 Connected, // 已建立Socket可收发 Disconnecting, // 主动关闭中 Reconnecting // 断线后等待重试 } // 指数退避重连逻辑摘录 private async Task ReconnectAsync() { int attempt 0; while (State ConnectionState.Reconnecting attempt MaxReconnectAttempts) { try { await ConnectAsync(); // 尝试连接 State ConnectionState.Connected; OnConnected?.Invoke(this, EventArgs.Empty); return; } catch (SocketException ex) when (ex.SocketErrorCode SocketError.ConnectionRefused || ex.SocketErrorCode SocketError.TimedOut) { attempt; int delayMs (int)Math.Min(1000 * Math.Pow(2, attempt), 30000); // 1s→2s→4s...上限30s await Task.Delay(delayMs); } } }参数说明MaxReconnectAttempts默认5次第5次失败后进入Disconnected态并触发OnConnectionFailed事件delayMs首次延迟1秒后续按2的幂次增长避免网络拥塞某产线实测将重连风暴从200次/分钟压至5次/分钟OnConnected/OnConnectionFailed供UI层绑定例如WinForm中更新连接状态灯颜色3.2 数据收发同步阻塞式读写 超时熔断为规避NetworkStream.ReadAsync在MCGS响应慢时的线程饥饿问题采用同步Socket.Receive独立接收线程并设置硬性超时// 发送报文含4字节头数据CRC public bool SendPacket(byte[] payload) { if (State ! ConnectionState.Connected) return false; // 构建完整帧头(4)载荷CRC(2) var frame new byte[4 payload.Length 2]; Buffer.BlockCopy(new byte[] { 0x5A, 0x5A, 0x00, 0x00 }, 0, frame, 0, 4); Buffer.BlockCopy(payload, 0, frame, 4, payload.Length); var crc Crc16Calculator.Compute(frame, 4, payload.Length); frame[frame.Length - 2] (byte)(crc 0xFF); frame[frame.Length - 1] (byte)(crc 8); try { socket.Send(frame, SocketFlags.None); return true; } catch (SocketException ex) when (ex.SocketErrorCode SocketError.NotConnected) { State ConnectionState.Reconnecting; return false; } } // 接收响应带超时保护 private byte[] ReceiveResponse(int timeoutMs 5000) { var buffer new byte[1024]; int totalRead 0; var stopwatch Stopwatch.StartNew(); while (totalRead 4 stopwatch.ElapsedMilliseconds timeoutMs) // 至少读4字节头 { try { int read socket.Receive(buffer, totalRead, 4 - totalRead, SocketFlags.None); if (read 0) break; // 对端关闭 totalRead read; } catch (SocketException ex) when (ex.SocketErrorCode SocketError.TimedOut || ex.SocketErrorCode SocketError.WouldBlock) { Thread.Sleep(10); // 避免忙等 } } if (totalRead 4 || buffer[0] ! 0x5A || buffer[1] ! 0x5A) throw new ProtocolException(Invalid MCGS frame header); // 读取长度字段MCGS头第3-4字节为数据长度小端 ushort dataLen BitConverter.ToUInt16(buffer, 2); int expectedTotal 4 dataLen 2; // 头数据CRC while (totalRead expectedTotal stopwatch.ElapsedMilliseconds timeoutMs) { int toRead Math.Min(expectedTotal - totalRead, buffer.Length - totalRead); int read socket.Receive(buffer, totalRead, toRead, SocketFlags.None); if (read 0) break; totalRead read; } if (totalRead expectedTotal) throw new TimeoutException($Response timeout: expected {expectedTotal}, got {totalRead}); // 校验CRC ushort receivedCrc BitConverter.ToUInt16(buffer, expectedTotal - 2); ushort calcCrc Crc16Calculator.Compute(buffer, 0, expectedTotal - 2); if (receivedCrc ! calcCrc) throw new ProtocolException($CRC mismatch: expected {calcCrc:X4}, got {receivedCrc:X4}); // 返回有效载荷不含头和CRC var payload new byte[dataLen]; Buffer.BlockCopy(buffer, 4, payload, 0, dataLen); return payload; }关键设计点帧头预检收到前4字节即校验0x5A5A避免后续无效读取动态长度解析MCGS头第3-4字节为dataLen小端据此确定总帧长而非固定长度CRC双重校验接收后立即计算CRC并与末尾2字节比对失败则抛ProtocolException上层可记录日志并重发超时熔断timeoutMs参数可针对不同操作调整读寄存器设5000ms写操作设2000ms4. 避坑指南MCGS TCP通信中90%开发者踩过的5个致命细节工业现场没有“差不多”一个字节错位、一次超时未捕获、一个地址算错都会导致数据飞走或设备假死。以下是某公司产线部署时血泪总结的5条避坑清单每条都附真实现象与根因4.1 现象连接成功但SendPacket()返回trueMCGS无任何响应Wireshark抓包显示TCP ACK正常原因MCGS设备端启用了防火墙或IP白名单但未将上位机IP加入许可列表。MCGS V6.2在“设备窗口”→“TCP/IP设备”属性页中隐藏了白名单开关默认只允许127.0.0.1。解决进入MCGS工程在“设备窗口”双击TCP设备 → “基本属性”页 → 找到“允许访问的IP地址”可能显示为灰色点击右侧“…”按钮 → 添加上位机网段如192.168.1.0/24或具体IP。重启MCGS运行环境生效。4.2 现象读取40001寄存器返回0x0000但MCGS画面中该变量显示为123原因MCGS的“保持寄存器”在工程中需手动关联到内存变量。若仅在设备窗口添加了“40001”但未在“用户窗口”或“策略窗口”中创建对应的数据对象并绑定地址则MCGS不会将PLC值写入该寄存器。解决在MCGS工程中 → “实时数据库” → 新建一个数值型变量如DB_Value1 → 右键该变量 → “属性” → “数据来源”选择“设备0” → “寄存器类型”选“保持寄存器” → “寄存器地址”填40001→ 点击“确定”。此时C#读取的才是真实PLC值。4.3 现象写入单个寄存器功能码0x06成功但MCGS画面中变量值不变改用功能码0x10写多个反而成功原因MCGS对0x06写入有隐式长度校验要求数据域长度必须为2字节即写1个寄存器但部分固件版本会错误地将0x06报文中的“字节数”字段第6字节解释为0x02而你的报文若未显式设置该字节为0x02则MCGS拒绝执行。解决构造0x06报文时严格按格式[0x06][Addr_H][Addr_L][Value_H][Value_L]共6字节数据域。范例中WriteSingleRegister()方法已固化此结构勿自行拼接。4.4 现象同一台PC上C#程序能连MCGS模拟器MCGS Simulator但连不上实体TPC7062K触摸屏原因实体MCGS设备的TCP端口默认为8080而模拟器默认为502Modbus端口。若代码中写死Connect(192.168.1.10, 502)则永远连不上实体机。解决检查实体MCGS设备IP配置通常在“系统设置”→“网络设置”中确认其TCP服务端口。范例代码中MCGSTcpClient构造函数接受port参数默认值为8080调用时显式传入new MCGSTcpClient(192.168.1.10, 8080)。4.5 现象程序运行数小时后突然断连日志显示SocketException: An existing connection was forcibly closed by the remote host原因MCGS设备端空闲超时断连默认120秒。若C#端未发送任何数据MCGS会在120秒后主动RST连接。解决实现应用层心跳每90秒发送一个最小合法报文如读取一个固定寄存器40001。范例中StartHeartbeatTimer()方法启动System.Threading.Timer周期性调用ReadHoldingRegisters(40001, 1)。注意心跳报文也需带正确CRC否则MCGS仍会断连。5. 实战调试用Wireshark精准定位通信故障的3个过滤技巧与1个必装插件当C#程序与MCGS通信异常别急着改代码——先用Wireshark看真实字节流。工业协议调试的核心是让二进制说话以下是某导师带学生调试MCGS产线时总结的高效方法5.1 过滤MCGS通信流量的3个精准表达式在Wireshark过滤栏输入以下任一表达式即可隔离目标流量假设MCGS IP为192.168.1.10端口8080过滤目的Wireshark Display Filter说明只看MCGS设备收发的TCP包ip.addr 192.168.1.10 tcp.port 8080最基础排除其他设备干扰只看MCGS协议有效帧含0x5A5A头ip.addr 192.168.1.10 tcp.port 8080 tcp.len 0 tcp.payload[0:2] 5a:5atcp.payload[0:2]提取TCP载荷前2字节 5a:5a匹配魔数过滤掉TCP握手包和ACK只看MCGS返回的读响应帧ip.addr 192.168.1.10 tcp.port 8080 tcp.payload[0:2] 5a:5a tcp.payload[4] 03tcp.payload[4]是功能码位置头4字节后第1字节 03即0x03读寄存器响应提示启用Wireshark的“Decode As”功能右键任意TCP包 → “Decode As…” → 在TCP端口8080行选择“TCP” → 点击OK。这样Wireshark会将8080端口流量视为纯TCP避免误判为HTTP等协议。5.2 必装插件MCGS协议解析器mcgs_dissector.luaWireshark默认不识别MCGS私有协议需加载自定义解析器。范例包中Tools/Wireshark-Plugin/目录下提供mcgs_dissector.lua安装步骤如下将mcgs_dissector.lua复制到Wireshark插件目录Windows路径C:\Program Files\Wireshark\plugins\3.6\版本号依安装版而定重启Wireshark捕获流量后展开TCP包 → “MCGS Protocol”节点即可看到结构化解析Header Magic:0x5a5aData Length:0x000a10字节Function Code:0x03Read Holding RegistersStart Address:0x0000对应40001Quantity:0x000a10个CRC16:0x9e2f自动高亮红/绿标示校验结果5.3 故障定位三步法从Wireshark到代码修复当通信失败时按此顺序排查90%问题可在5分钟内定位第一步确认连接建立过滤ip.addr 192.168.1.10 tcp.flags.syn 1查看是否有三次握手SYN→SYN-ACK→ACK。若无检查IP/端口/防火墙。第二步确认报文发出且MCGS接收过滤ip.src 192.168.1.100 ip.dst 192.168.1.10 tcp.payload[0:2] 5a:5a192.168.1.100为上位机IP确认你的报文是否发出。再过滤ip.src 192.168.1.10 ip.dst 192.168.1.100 tcp.payload[0:2] 5a:5a确认MCGS是否回包。若你发了但MCGS没回大概率是报文格式错误如CRC错、地址错或MCGS未绑定变量。第三步逐字节比对报文内容右键你的报文 → “Follow” → “TCP Stream”在弹出窗口中点击“Hex Dump”标签页。将Wireshark显示的十六进制如5a 5a 00 0a 03 00 00 00 0a 9e 2f与范例代码中SendPacket()生成的frame数组ToString()输出对比。重点核对第1-2字节5a 5a✅第3-4字节00 0a数据长度10✅第5字节03功能码✅第6-7字节00 00起始地址40001→0✅第8-9字节00 0a读10个✅最后2字节9e 2fCRC✅若某处不符直接定位到代码中payload构造逻辑如AddressConverter.ToBytes(40001)是否返回00 00。从那以后我每次部署新MCGS项目都强制走一遍这三步先用Wireshark确认三次握手再发一个最简读报文40001,1最后用Hex Dump逐字节比对。哪怕只是改了一个寄存器地址也要重走一遍——因为MCGS的“静默失败”太擅长消耗你的调试耐心了。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

语音消息接口开发实战:TTS合成与API推送全攻略

语音消息接口开发实战:TTS合成与API推送全攻略

做语音消息接口应用开发这件事,核心是把一段个性化音频或TTS语音内容通过API稳定地推送出去,同时兼顾音质、延迟和成本。我最近刚把一个语音通知系统跑上线,从最原始的"拿现成MP3文件上传再发送",到后面接服务商TTS合成…

2026/10/16 9:22:15 阅读更多 →
随机森林稳定性工程:从偏差-方差控制到生产级落地

随机森林稳定性工程:从偏差-方差控制到生产级落地

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

2026/10/16 9:22:15 阅读更多 →
FPGA上稳定实现卡尔曼滤波:Verilog定点化与流水线设计

FPGA上稳定实现卡尔曼滤波:Verilog定点化与流水线设计

简介:本资源是面向数字信号处理与FPGA硬件开发者的卡尔曼滤波器实战实现方案,适用于通信、导航、实时图像处理等对低延迟与高并行性有严苛要求的嵌入式场景,适合具备Verilog基础和数字电路设计经验的中高级工程师及研究生学习。压缩包共475个…

2026/10/16 9:21:04 阅读更多 →

最新新闻

LabVIEW波峰波谷检测实战:从Peak Detector到数组遍历及参数调优

LabVIEW波峰波谷检测实战:从Peak Detector到数组遍历及参数调优

最近在调一套气动检测平台的采集程序,甲方要的指标很直接:每次气缸动作,上位机必须自动把压力波形的冲击峰值时刻标出来,漏一个峰都不行。这种需求在LabVIEW的日常项目里太常见了——振动监测要看极值区间,疲劳测试要统…

2026/10/16 10:42:49 阅读更多 →
计算机考研真题背后的系统级思维:从栈共享到TCP握手的底层逻辑

计算机考研真题背后的系统级思维:从栈共享到TCP握手的底层逻辑

简介:本资源为武汉理工大学计算机专业硕士研究生复试真题汇编,面向备考该校计算机学院考研复试的考生,系统覆盖近四年(2004–2007)复试试题及核心知识点解析,直击操作系统、数据结构、计算机网络、数据库原…

2026/10/16 10:42:49 阅读更多 →
Flink Exactly Once与At Least Once落地实战指南

Flink Exactly Once与At Least Once落地实战指南

1. 这不是理论考试,是生产环境里每天要签的“生死状”Flink 的 Exactly Once 和 At Least Once,这两个词在面试题里出现频率高得离谱,但真正让一线开发者头皮发紧的,从来不是背定义,而是某天凌晨三点收到告警&#xff…

2026/10/16 10:42:49 阅读更多 →
NSGA-III 的 MATLAB 实现:参考点引导的多目标优化代码解析

NSGA-III 的 MATLAB 实现:参考点引导的多目标优化代码解析

简介:这是一份可在 MATLAB 中直接运行的 NSGA-III 多目标进化算法代码,面向需要处理多目标优化问题、熟悉遗传算法但希望快速获得可复现基线的研究人员与高年级学生。资源实现了参考点生成、非支配排序、模拟二进制交叉与多项式变异、种群归一化、关联参…

2026/10/16 10:42:49 阅读更多 →
内置修改器1.67版版本匹配与安装排错指南

内置修改器1.67版版本匹配与安装排错指南

简介:GTA5中文内置修改器1.67版本是为GTA5线下单机玩家制作的游戏插件,由3DM-DJ小良汉化,支持2023年10月更新的1.67/3028版游戏,提供简体与繁体两套界面,具备一键刷车、武器获取、传送、保镖雇佣等常用功能&#xff0c…

2026/10/16 10:42:49 阅读更多 →
C#面向对象:Person类、Student类继承与构造函数全解析

C#面向对象:Person类、Student类继承与构造函数全解析

1. 面向对象的第一步:从Person类的设计说起很多人学C#敲的第一段面向对象代码,就是从定义人类Person开始的。这个题目看着简单,但背后其实藏着一堆值得掰扯清楚的细节——构造函数怎么写、字段和属性有什么区别、继承之后构造函数怎么联动。我…

2026/10/16 10:41:46 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

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

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/15 9:49:14 阅读更多 →