1. 项目概述:为什么S7-1200做Modbus TCP客户端不是“选修课”,而是现场刚需
在自动化产线调试现场,我见过太多次这样的场景:一台西门子S7-1200 PLC要读取四台第三方温控仪表的数据,每台仪表都支持Modbus TCP协议;工程师花两天时间反复改IP、调端口、查TIA Portal报错代码,最后发现是MB_CLIENT块的“连接超时时间”设成了500毫秒——而实际网络延迟波动在380~620ms之间,导致轮询失败率高达47%。这不是个例,而是当前中小规模产线集成中最常踩的坑之一。S7-1200 Modbus TCP客户端配置,表面看只是拖一个MB_CLIENT指令、填几个参数,背后却牵扯到TIA Portal工程架构逻辑、TCP连接状态机管理、PLC扫描周期与通信周期的耦合关系、以及工业现场真实网络抖动的容错设计。它既不是纯软件开发,也不是传统继电器逻辑,而是一条横跨电气、网络、编程三领域的交叉技能。你不需要成为网络协议专家,但必须清楚:当MB_CLIENT的“DONE”位始终不置位时,问题90%不在PLC程序里,而在你没意识到的三个隐性环节——IP地址掩码是否匹配、防火墙是否放行502端口、以及TIA Portal中“优化访问”开关是否误关。这篇文章不讲理论堆砌,只拆解我在17个真实项目中验证过的实操路径:从新建项目那一刻起,如何避开TIA Portal v14/v15/v17版本差异带来的陷阱,怎样用最简方式实现4台设备轮询(不是靠写4个MB_CLIENT块硬拼),以及最关键的——当数据偶尔跳变时,怎么快速定位是线缆干扰、仪表固件Bug,还是PLC内部缓冲区溢出。适合刚接手产线集成的电气工程师、想补全通信能力的PLC程序员,以及被客户临时拉去调试第三方设备的售后技术员。所有步骤均基于TIA Portal V17 SP1实测,兼容V14/V15,但会明确标注各版本关键差异点。
2. 整体设计思路:为什么不用“轮询+定时器”而用“状态机驱动”
2.1 传统轮询思维的致命缺陷
很多工程师拿到需求第一反应是:“写个循环,每隔100ms读一次设备A,再隔100ms读B……”这种思路在仿真环境里跑得飞快,一上现场就崩。原因有三:
第一,S7-1200的扫描周期本身就有波动(典型值2~8ms),叠加通信块执行时间后,实际轮询间隔根本不可控。我曾用示波器抓过某产线PLC的MB_CLIENT触发沿,发现相邻两次触发间隔在92ms到137ms之间跳变,直接导致温控仪表采样数据被漏读。
第二,Modbus TCP是请求-响应模式,每个请求必须等响应返回才能发下一个。如果设备响应慢(比如某些国产仪表固件处理Modbus请求需200ms),强行轮询会导致后续请求全部堆积在发送队列,最终触发TIA Portal的“通信超时中断”并复位MB_CLIENT块。
第三,也是最容易被忽视的:TIA Portal默认启用“优化块访问”,这会让MB_CLIENT的输入/输出参数地址被编译器重排。当你在DB块里定义了结构体stModbusData: STRUCT,其中nValue1: INT; nValue2: INT,编译后实际内存布局可能变成nValue2在前、nValue1在后——而MB_CLIENT块严格按字节偏移读写,结果就是数据错位,读到的永远是“乱码”。
2.2 状态机驱动的核心逻辑
我们改用“单次触发+状态反馈”模式:
- 每个MB_CLIENT块只负责与一台设备通信,不轮询;
- 用一个全局状态字(如
gbState: BYTE)控制通信流程:0=空闲,1=向设备A发请求,2=等待A响应,3=向设备B发请求……; - 关键触发条件不是定时器,而是MB_CLIENT的
DONE信号上升沿——只有确认上一次通信成功完成,才推进状态机; - 如果
ERROR置位,则根据STATUS字的低8位查错(如16#0004=连接超时,16#0005=目标设备无响应),进入错误处理分支而非简单重试。
这个设计把通信从“时间驱动”转为“事件驱动”,彻底规避扫描周期波动影响。更重要的是,它让调试变得可追踪:你在监控表里看到gbState=2卡住超过500ms,立刻知道是设备A没回包,而不是怀疑整个轮询逻辑有问题。
2.3 为什么坚持用MB_CLIENT而非自定义TCP
TIA Portal提供两种Modbus TCP实现路径:官方MB_CLIENT指令,或用TCON/TSEND/TRCV指令自己组Modbus报文。后者看似灵活,实则暗坑密布。我曾帮一家包装机械厂重构通信模块,他们原方案用TRCV接收原始TCP数据,再用MOVE指令逐字节解析Modbus ADU(应用数据单元)。问题出在字节序上:西门子PLC默认小端序,而多数Modbus设备要求大端序。他们花了3天调试,最后发现nRegisterValue:=WORD_TO_INT(WORD_SWAP(#rawData))这行代码漏了WORD_SWAP——因为工程师以为INT类型自动处理字节序。而MB_CLIENT块内部已固化处理大小端转换、CRC校验(虽然TCP层不校验,但块内会校验功能码合法性)、异常响应解析,你只需关注DATA区映射。省下的时间,足够你多检查两遍网线水晶头压接质量。
2.4 四台设备轮询的工程化实现
针对热搜词“s7-1200与4台modbus tcp轮询”,很多人直接复制4个MB_CLIENT块,每个块配不同IP。这在小型项目里可行,但存在扩展性灾难:
- 每增加一台设备就要新增DB块、FC块、网络配置,工程文件体积指数级增长;
- 所有块的超时时间、重试次数必须手动同步,漏改一个就导致某台设备通信异常;
- 无法统一监控通信质量(比如统计每台设备的平均响应时间)。
我们采用“参数化实例化”方案:
- 创建一个通用FC(如FC100 "Modbus_Read"),输入参数包括
ipAddr: ARRAY[0..3] OF BYTE(目标IP)、port: WORD(端口,默认502)、startAddr: WORD(起始寄存器地址)、numRegs: WORD(读取数量); - 输出参数
dataBuffer: ARRAY[0..99] OF WORD(接收缓冲区)和commStatus: WORD(状态码); - 在OB1中调用FC100四次,每次传入不同IP和寄存器地址,但共用同一个背景DB(DB100);
- 关键技巧:在DB100中定义
arrDeviceCfg: ARRAY[0..3] OF STRUCT,每个元素包含IP、端口、寄存器范围、上次响应时间戳。这样所有设备配置集中管理,增删设备只需改数组长度和元素值,无需动程序逻辑。
提示:TIA Portal V17支持“数组类型DB块”,但V14/V15不支持。若用老版本,需将
arrDeviceCfg拆成4个独立STRUCT变量(如stDev0,stDev1),并在FC100中用指针索引(P#DB100.DBX0.0),虽稍繁琐但完全可行。
3. 核心细节解析:TIA Portal配置中的12个隐形开关
3.1 项目创建阶段的“三禁三必”
新建项目时,以下设置必须在第一步完成,否则后期修改代价极大:
禁用“优化块访问”:在项目树→CPU属性→常规→“优化的块访问”前打钩取消。这是MB_CLIENT能正确映射DB块字段的前提。很多工程师直到数据错位才想起查这个,但此时已建好几十个DB块,重新启用优化访问会导致所有地址重排,必须逐个核对。
禁用“启用保护”:CPU属性→保护→“启用保护”设为“未保护”。Modbus通信调试阶段频繁下载程序,启用保护会导致每次下载都要输密码,打断调试节奏。正式投运前再开启即可。
禁用“启用运行时诊断”:CPU属性→诊断→“启用运行时诊断”关闭。该功能会占用约15%的扫描周期资源,对通信密集型项目影响显著。我们实测过:开启状态下,4台设备轮询周期从120ms延长至180ms。
必设IP地址掩码:在CPU属性→以太网接口→IP地址中,掩码必须设为255.255.255.0(除非你明确需要跨网段通信)。曾有个项目因掩码误设为255.255.0.0,导致PLC能ping通设备但MB_CLIENT始终报16#0005错误——因为ARP广播包被限制在/16网段,而设备实际在/24子网内。
必开502端口防火墙:Windows防火墙默认拦截502端口。在TIA Portal安装目录下找到
Automation License Manager,右键→“以管理员身份运行”,然后在“许可证”选项卡中勾选“允许通过防火墙”。别信网上说的“PLC自带防火墙”,那是针对PROFINET的,Modbus TCP走的是标准TCP/IP栈。必装最新固件:S7-1200 CPU固件低于V4.4时,MB_CLIENT块存在内存泄漏Bug(连续运行72小时后通信中断)。官网固件下载页明确标注:“V4.4及以上修复Modbus TCP客户端稳定性问题”。升级固件只需5分钟,比排查三天Bug强百倍。
3.2 MB_CLIENT块参数的“反直觉”设定
MB_CLIENT指令有12个输入参数,但真正决定成败的只有4个:
REQ:触发信号。必须用R_TRIG边沿检测,而非直接接M点。因为MB_CLIENT是上升沿触发,如果REQ持续为1,块会反复初始化,导致连接重置。我见过最典型的错误是把REQ接到一个保持型M点,结果每次扫描都触发,PLC日志里全是“连接已建立”“连接已断开”的刷屏。CONNECT:连接参数结构体。其中IP_ADDRESS必须用ARRAY[0..3] OF BYTE类型,不能用字符串!例如目标IP是192.168.1.100,要写成[192,168,1,100]。字符串格式(如'192.168.1.100')在V14中编译报错,V17虽兼容但会隐式转换,增加不可控风险。MB_MODE:功能码。读线圈用1,读输入寄存器用4,读保持寄存器用3,写单个寄存器用6。注意:功能码6(Write Single Register)的DATA区只能放1个WORD,放多了会触发STATUS=16#000A(非法数据值)。DATA:数据缓冲区。必须是ARRAY[0..n] OF WORD类型,且长度≥所需寄存器数。例如读10个保持寄存器,DATA数组至少定义ARRAY[0..9] OF WORD。少定义会导致越界写入,轻则数据错乱,重则PLC崩溃重启。
注意:
MB_DATA_ADDR参数填的是“寄存器地址”,不是“字节偏移”。比如你要读保持寄存器40001,这里填40001,不是0(因为Modbus协议规定保持寄存器地址从40001开始编号,内部映射为0偏移)。填错会导致读到完全无关的数据。
3.3 DB块数据结构的“黄金模板”
MB_CLIENT的DATA区必须映射到DB块,而DB块结构直接影响数据读取效率。我们采用三层嵌套结构:
TYPE stModbusDevice : STRUCT // 设备基础信息 ipAddr : ARRAY[0..3] OF BYTE; port : WORD := 502; // 通信状态 lastCommTime : DINT; // 上次通信时间戳(ms) commErrorCount : INT; // 连续错误次数 // 数据区(按功能码分组) coilData : ARRAY[0..127] OF BOOL; // 功能码01/05/15用 inputRegData : ARRAY[0..127] OF WORD; // 功能码02用 holdRegData : ARRAY[0..127] OF WORD; // 功能码03/06/16用 // 配置参数(可在线修改) readInterval_ms : TIME := T#100MS; // 轮询间隔 timeout_ms : TIME := T#1000MS; // 单次超时 END_STRUCT这个结构的优势在于:
coilData用BOOL数组,节省空间(128个线圈仅占16字节);holdRegData用WORD数组,直接对应Modbus寄存器16位宽度;lastCommTime和commErrorCount为后续做通信质量分析留出接口;readInterval_ms和timeout_ms设为TIME类型,可在HMI上直接修改,无需下载程序。
特别提醒:holdRegData数组索引从0开始,但Modbus协议中寄存器地址从40001开始。所以当你读40001~40010共10个寄存器时,holdRegData[0]对应40001,holdRegData[9]对应40010。千万别用holdRegData[40001]这种错误索引!
3.4 TIA Portal版本差异的“避坑清单”
| 版本 | 关键差异 | 应对方案 |
|---|---|---|
| V14 | 不支持数组类型DB块;MB_CLIENT块无“重试次数”参数 | 用4个独立DB块;手动在FC中实现重试逻辑(计数器+TON定时器) |
| V15 | 增加RETRY参数(最大重试次数),但默认值为0(不重试) | 必须显式设为1~3,否则单次失败即终止 |
| V17 | CONNECT结构体新增LOCAL_PORT字段(本地端口),默认0(系统自动分配) | 如需固定端口(如防火墙策略要求),填入1024~65535间未用端口 |
最易被忽略的版本陷阱:V14/V15中,MB_CLIENT的STATUS输出是WORD类型,而V17升级为DWORD。如果你在V14工程里写了IF #mbClient.STATUS = 16#0000 THEN...,升级到V17后必须改为IF #mbClient.STATUS = 16#00000000 THEN...,否则永远进不了成功分支。建议在所有版本中统一用STATUS <> 0判断错误,而非匹配具体值。
4. 实操过程详解:从零开始搭建四设备轮询系统
4.1 硬件与网络准备(30分钟)
在动手编程前,必须完成物理层验证,否则90%的问题都出在这里:
- 网线质量检查:用FLUKE DSX-5000测试仪测四条网线,重点看“插入损耗”和“近端串扰(NEXT)”。我们曾遇到一条网线NEXT值超标,导致Modbus TCP丢包率12%,但Ping测试显示100%连通——因为Ping用ICMP协议,而Modbus TCP用TCP,对线缆质量更敏感。
- IP规划表:制作一张表格,明确每台设备的IP、子网掩码、网关(即使不跨网段也填上,避免后续扩展麻烦):
| 设备 | IP地址 | 子网掩码 | 网关 | Modbus端口 | 寄存器范围 |
|---|---|---|---|---|---|
| 温控仪A | 192.168.1.100 | 255.255.255.0 | 192.168.1.1 | 502 | 40001~40010 |
| 温控仪B | 192.168.1.101 | 255.255.255.0 | 192.168.1.1 | 502 | 40001~40010 |
| 压力变送器 | 192.168.1.102 | 255.255.255.0 | 192.168.1.1 | 502 | 40001~40005 |
| 流量计 | 192.168.1.103 | 255.255.255.0 | 192.168.1.1 | 502 | 40001~40003 |
- 设备端验证:用ModScan32软件(免费)分别连接四台设备,确认:
- 能正常读取寄存器数据;
- 功能码03(读保持寄存器)返回数据与设备手册一致;
- 修改寄存器值(功能码06)能实时生效。
这一步省不得。曾有个项目,压力变送器固件Bug导致40005寄存器始终返回0,但ModScan32显示正常——因为软件缓存了上次成功读取的值。必须用“强制刷新”按钮验证实时性。
4.2 TIA Portal工程搭建(45分钟)
步骤1:创建新项目
- 软件:TIA Portal V17 SP1(兼容V14/V15,但以V17为准);
- CPU型号:S7-1214C DC/DC/DC(固件V4.4.1);
- 项目名称:
Modbus_Client_4Devices; - 关键操作:在“添加新设备”向导中,取消勾选“优化的块访问”和“启用保护”。
步骤2:配置CPU以太网接口
- 双击CPU→属性→以太网接口→IP地址;
- 输入IP:192.168.1.200,子网掩码:255.255.255.0;
- 点击“下载到设备”前,先勾选“仅下载硬件组态”,避免程序覆盖。
步骤3:创建DB块(DB100)
- 右键“PLC标签”→“添加新块”→“数据块”→名称
DB100_ModbusConfig; - 类型选“全局DB”;
- 在DB编辑器中定义:
// 设备配置数组(4台) arrDevices : ARRAY[0..3] OF stModbusDevice; // 全局状态 gbCommState : BYTE; // 0=空闲,1=设备0通信,2=设备1通信... gbLastError : WORD; // 最近一次错误码步骤4:编写FC100 "Modbus_Read"
- 新建函数块FC100,接口参数:
// 输入 ipAddr : ARRAY[0..3] OF BYTE; port : WORD := 502; startAddr : WORD; numRegs : WORD; // 输出 dataBuffer : ARRAY[0..127] OF WORD; commStatus : WORD;- 主体逻辑:
// 初始化MB_CLIENT连接参数 #stConnect.ip_address := #ipAddr; #stConnect.rack := 0; #stConnect.slot := 0; #stConnect.port := #port; // 触发通信(上升沿) #rTrig(CLK := #trigger); #mbClient( REQ := #rTrig.Q, CONNECT := #stConnect, MB_MODE := 3, // 读保持寄存器 MB_DATA_ADDR := #startAddr, MB_DATA_LEN := #numRegs, DATA := #dataBuffer, DONE := #done, ERROR := #error, STATUS := #commStatus ); // 状态反馈 IF #done THEN #commStatus := 0; // 成功 ELSIF #error THEN #commStatus := #mbClient.STATUS; // 返回错误码 END_IF;4.3 四设备轮询主程序(OB1)实现(20分钟)
在OB1中编写状态机主循环:
// 1. 初始化(首次扫描) IF #firstScan THEN #DB100.gbCommState := 0; #firstScan := FALSE; END_IF; // 2. 状态机处理 CASE #DB100.gbCommState OF 0: // 空闲状态,启动设备0通信 #FC100( ipAddr := #DB100.arrDevices[0].ipAddr, startAddr := 40001, numRegs := 10, dataBuffer := #DB100.arrDevices[0].holdRegData, commStatus := #status0 ); IF #status0 = 0 THEN #DB100.gbCommState := 1; // 成功,进入设备1 ELSIF #status0 <> 0 THEN #DB100.gbLastError := #status0; #DB100.gbCommState := 0; // 错误,重试 END_IF; 1: // 设备0成功,启动设备1 #FC100( ipAddr := #DB100.arrDevices[1].ipAddr, startAddr := 40001, numRegs := 10, dataBuffer := #DB100.arrDevices[1].holdRegData, commStatus := #status1 ); IF #status1 = 0 THEN #DB100.gbCommState := 2; END_IF; 2: // 设备1成功,启动设备2... // 同理,略 3: // 设备3成功,回到空闲 #DB100.gbCommState := 0; END_CASE;关键点:每个状态只调用一次FC100,且仅在commStatus=0时推进状态。这样确保四台设备严格串行通信,避免TCP连接冲突。
4.4 下载与在线调试(15分钟)
下载前必做三件事:
- 编译检查:点击“编译”→“全部编译”,确认无警告(尤其注意“优化访问”相关警告);
- 在线连接:右键CPU→“在线”→“在线与诊断”,确认PLC状态为“RUN”;
- 监控表设置:新建监控表,添加以下变量:
DB100.gbCommState(观察状态流转)DB100.arrDevices[0].holdRegData[0](设备A第一个寄存器)DB100.gbLastError(错误码)MB_CLIENT.STATUS(实时状态)
下载后,观察gbCommState是否在0→1→2→3→0循环。如果卡在某个状态,立即看gbLastError:
- 若为16#0004,检查设备IP是否可达(在PLC上用“诊断”→“网络”→“Ping”测试);
- 若为16#0005,检查设备是否开机、Modbus服务是否启用;
- 若为16#000A,检查
startAddr和numRegs是否超出设备支持范围(如设备只开放40001~40005,却读40001~40010)。
5. 常见问题与排查技巧实录:17个项目积累的“血泪清单”
5.1 数据错位:90%源于字节序与地址映射
现象:读取温度值,HMI显示-27315(即0x8000),但设备实际为25℃。
根因分析:
- Modbus保持寄存器是16位无符号整数(0~65535),25℃对应0x0019;
- 但PLC将0x0019解释为有符号INT,最高位为0,应为25;
- 显示-27315说明实际收到的是0x8000,即32768。
排查路径:
- 用Wireshark抓包,过滤
tcp.port==502,看PLC发出的请求帧和设备返回的响应帧; - 查响应帧数据区:若返回
00 19,说明设备发对了,问题在PLC解析; - 检查DB块定义:
holdRegData是否定义为WORD?如果是INT,则PLC会把0x0019当作有符号数,但-27315对应0x8000,不符; - 最终发现:
holdRegData定义为ARRAY[0..9] OF INT,而设备返回的是00 00(高位在前),PLC按小端序读成00 00→0,但监控表显示为-27315——这是TIA Portal监控表的显示Bug!实际数据正确,只需在HMI中用WORD_TO_INT转换。
实操心得:永远用Wireshark验证原始数据,不要信监控表。Wireshark中Modbus TCP帧结构:前6字节TCP头,后2字节事务标识符,2字节协议标识符,2字节长度,1字节单元标识符,1字节功能码,2字节起始地址,2字节寄存器数量,N字节数据。
5.2 连接频繁断开:被忽略的“心跳包”机制
现象:MB_CLIENT能连上设备,但10秒后自动断开,STATUS返回16#0005。
真相:Modbus TCP没有内置心跳机制,TCP连接空闲超时后被设备侧主动关闭。某些国产仪表默认空闲超时为15秒。
解决方案:
- 在状态机中加入“保活逻辑”:每8秒向任意一台设备发一次最小请求(如读1个线圈),维持TCP连接;
- 或在设备端配置:查找仪表Web界面或拨码开关,将“TCP Keepalive Time”设为0(禁用)或300秒(5分钟)。
我们曾为某药厂改造温控系统,四台仪表来自不同厂商,其中一台要求必须每30秒发心跳,否则断连。最终在FC100中增加判断:IF #lastCommTime - #currentTime > T#25S THEN #sendHeartbeat := TRUE;,用功能码01读一个虚拟线圈(地址0)。
5.3 轮询周期不稳定:扫描周期与通信块的“时间战争”
现象:四台设备轮询总时间在120ms~210ms之间波动,无法满足产线100ms控制周期要求。
深度归因:
- S7-1200的扫描周期受程序复杂度影响,而MB_CLIENT块执行时间又受网络延迟影响;
- 当网络延迟高时,MB_CLIENT阻塞PLC扫描,导致后续逻辑延后执行。
破局方案:
- 降低通信优先级:在CPU属性→常规→“循环时间监视”中,将“最大循环时间”设为500ms(默认100ms),避免因通信延迟触发看门狗复位;
- 异步化处理:将MB_CLIENT调用移到OB35(100ms定时中断组织块)中,而非OB1。这样通信与主逻辑解耦,即使通信耗时200ms,也不影响OB1的100ms周期;
- 硬件加速:为S7-1200加装CM1241通信模块(RS485),用Modbus RTU协议替代TCP。实测RTU轮询4台设备稳定在85ms,且抗干扰能力提升3倍。
注意:OB35需在CPU属性→循环中断中使能,并设“时间间隔”为100ms。调用MB_CLIENT时,务必用静态变量保存状态,避免每次中断都重置。
5.4 多设备并发通信:何时该放弃“单线程轮询”
决策树:
- 设备数≤4,且单次响应时间<100ms → 用状态机轮询(本文方案);
- 设备数5~12,或响应时间>150ms → 改用“多实例并行”,每个MB_CLIENT块独立运行,用
TP定时器错开触发时间(如设备0在T0触发,设备1在T20ms触发); - 设备数>12,或需实时性<10ms → 必须换方案:用S7-1500 + PN/PN Coupler做分布式IO,或上工业物联网网关(如HMS Anybus)聚合Modbus数据后转PROFINET。
我们有个汽车焊装线项目,需读取23台传感器,最终采用“4组轮询+1个网关”混合架构:前16台分4组轮询,剩余7台接入Anybus网关,网关输出PROFINET IO,PLC只需读取网关的一个数据块。总成本比全PLC方案低37%,调试时间缩短60%。
5.5 TIA Portal常见报错速查表
| STATUS值(16进制) | 含义 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 0000 | 成功 | — | 无需操作 |
| 0004 | 连接超时 | 1. Ping设备IP 2. 检查设备是否开机 3. 查防火墙是否放行502端口 | 1. 延长timeout_ms2. 检查网线质量 3. 关闭Windows防火墙 |
| 0005 | 目标设备无响应 | 1. 确认设备Modbus服务启用 2. Wireshark抓包看请求是否发出 | 1. 设备端重启Modbus服务 2. 检查 CONNECT结构体IP地址格式 |
| 000A | 非法数据值 | 1. 核对startAddr和numRegs2. 查设备手册支持的寄存器范围 | 修改参数,确保在设备开放范围内 |
| 000D | 无效参数 | 1. 检查DATA数组长度2. 确认 MB_MODE与DATA类型匹配 | MB_MODE=3时DATA必须为WORD数组,且长度≥numRegs |
| 0010 | 连接已存在 | 多个MB_CLIENT块用相同CONNECT参数 | 为每个块使用独立CONNECT结构体变量 |
最后分享一个真实案例:某食品厂PLC与4台流量计通信,STATUS始终为0005。我们按表排查,Ping通、设备开机、防火墙关闭,最后发现流量计IP设为192.168.1.100,而PLC网关设为192.168.0.1——子网掩码255.255.255.0下,两者不在同一网段。改PLC网关为192.168.1.1,问题瞬间解决。所以,永远先查网络基础,再碰代码。