Modbus相关知识点

一、基本概念

1.1 Modbus基本概念

1.1.1 什么是Modbus?

Modbus是Modicon(施耐德)公司于1979年开发的串行通信协议。它最初设计用于公司的可编程逻辑控制器(PLC)。Modbus是一种开放式协议,支持使用RS232/RS485/RS422协议的串行设备,同时还支持调制解调器。它的简单性以及制造商可以免费将其纳入其产品的事实使其成为连接工业电子设备的最流行的方法。Modbus比其他通信协议使用的更广泛的主要原因有以下几点:

  • 公开发表并且无著作权要求;
  • 易于部署和维护;
  • 对供应商来说,修改移动本地的比特或字节没有很多限制;

Modbus通过设备之间的串行线进行数据传输。最简单的设置是使用一根串行电缆连接两个设备(主设备和从设备)上的串行端口。数据以称为比特的1和0的序列发送。每个位都作为电压发送。0被发送为正电压,1被发送为负电压。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-evUMELhb-1631626547711)(https://i.loli.net/2021/08/11/ZgVDlzY2uyk95UL.png)]

1.1.2 主从模式

Modbus解决了通过串行线路在电子设备之间发送信息的问题。该协议在遵循该协议的体系结构中实现主/从模型。Modbus主站(Master)负责从其他设备(Slave)请求信息。标准Modbus网络中有一个Modbus主站。具体如下图所示;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wH2nBQA6-1631626547713)(https://i.loli.net/2021/08/11/GtmOi1xVpfacUIh.png)]

主设备向从设备请求信息,最多大约可达到240个 . 每个从设备都有自己唯一的从设备地址标识(Slave Address)。除了从从设备请求信息之外,主设备还可以写入从设备的内部寄存器

1.2 应用场景

modbus是专为工业开发的协议,所以主要用于工业场合,也可以用于基础设施,如:住宅、商业中心、机场、水处理、电厂等。

modbus RTU协议紧凑,可以使用RS232/RS485、无线、等介质,用于速度要求不高的场合,如:楼宇、工业现场、管道输送、远程泵站等;

modbusASCII协议比较宽松,时序要求不高,可以用于条形码阅读器、打印机、仪器仪表读取等;

modbusTCP速度很高,可以用于实时控制、时钟对时、全局数据、发送邮件、故障设备替换、网络管理、用户网页制定和浏览、固件更新等多种服务。

1.3 三种通信模式

最常用Modbus协议总共有以下三种:Modbus ASCIIModbus RTUModbus TCP

Modbus TCP基于以太网和TCP/IP协议

Modbus RTU和Modbus ASCII则是使用异步串行传输(通常是RS-232/422/485)

1.3.1 Modbus ASCII模式

起始 地址码 功能码 数据 校验 回车换行
字符 ‘:’(冒号) 两字节 两字节 0到2 * 252字节 两字节(LRC校验) 两字节(CR,LF)

帧的起始一字符 ’ : '冒号开始,结束为回车换行,其对应的16进制可以到ASCII表中进行查询。字节间传输的间隔时间不能大于1s,大于1s认为这一帧数据丢失.同样我们可以计算出来ASCII帧的最大长度是513字节。

RTU使用CRC校验ASCII使用LRC校验。

当设备设置为使用ASCII(美国信息交换标准代码)模式在MODBUS串行线上进行通信时,消息中的每个8位字节将作为两个ASCII 4位字符发送。当物理通信链路或设备的功能不允许符合RTU计时器管理要求时,使用此模式。所以此模式的效率不如RTU,因为每个字节需要两个字符。示例:字节0x7D编码为两个字符:0x35和0x42(在ASCII表中为0x37 =‘7’,而0x44 =‘D’)。

1.3.2 Modbus RTU模式

地址码 功能码 数据 校验码
一字节 一字节 n字节 两字节(CRC)

从机都有相应的地址码,便于主机识别,从机地址为0到255,0为广播地址,248-255保留。总线上只能有一个主设备,但可以有一个或者多个(最多247个 ip地址1-247)从设备。

广播模式:主设备向所有的从设备发送请求指令,从设备收到指令后,各自处理,不要求返回应答;这种模式下,请求指令必须是Modbus标准功能中的写指令;比如 0x06,0x10 功能码。

其中数据以帧为单位进行数据传输,每帧最长为252字节,最短为0字节。如果一byte数据的传输时间为T,那么每两帧之间的间隔最小应该要大于3.5T,否则从机不能分辨这是两帧。第二,同一帧连续的两个数据之间的间隔时间不能超过1.5T,否则节点会认为这一帧数据不完整,这说明我们在modbus传输的时候要使能一个定时器的工作。

例子:

发送:09 03 00 04 00 03 XX

主站告诉从站09,我要读取的地址偏移为4、5、6的Holding Register的数值。其中"03"是读Holding Register的功能码,"00 04 00 03"是数据区,"00 04"是寄存器的地址,"00 03"说明要连续读三个寄存器的值。"XX"代表最后的校验位。

接收:09 03 06 02 2B 00 01 00 64 XX

从站回应该地址偏移为4的寄存器值为02 2B,地址偏移为5的寄存器值为00 01,地址偏移为6的寄存器值为00 64。其中"09 03"是复制了主站发来的地址和功能码,"06"代表接下来的数据共有6个字节。

Modbus RTU是一种紧凑的,采用二进制表示数据的方式;因为使用二进制编码和CRC错误检查的结合使得Modbus RTU适用于工业应用,因为它比ASCII字符的替代方案更有效地传输。在Modbus RTU与ASCII之间进行选择时,如果考虑性能,则RTU是首选。

1.3.3 Modbus TCP模式

ModbusTCP的数据帧可分为两部分:MBAP+PDU。

主站为client端,主动建立连接;从站为server端,等待连接。

1.报文头MBAP

MBAP为报文头,长度为7字节,组成如下:

事务处理标识 协议标识 长度 单元标识符
2字节 2字节 2字节 1字节

事务处理标识:可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文
协议标识符:00 00表示ModbusTCP协议
长度:表示接下来的数据长度,单位为字节
单元标识符:可以理解为设备地址

2.帧结构PDU

PDU由功能码+数据组成。功能码为1字节,数据长度不定,由具体功能决定。

例子:

发送:01 c8 00 00 00 06 01 03 00 14 00 0a

序列号:01 c8 ,协议标识符:00 00 ,长度:00 06 ,单元标识符/服务器地址:01,功能码:03,寄存器地址:00 14 ,读取几位数据:00 0a。

接收:01 c8 00 00 00 17 01 03 14 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 03 00 00

序列号:01 c8 ,协议标识符:00 00 ,长度:00 17 ,单元标识符/服务器地址:01 ,功能码:03,数据长度:14 ,数据:就是后面的在第6位和第9位有数据。

下一条数据序,列号就会加一,变为01 c9。

Modbus TCP TCP/IP网络上运行的Modbus的实现,旨在允许Modbus ASCII / RTU协议在基于TCP / IP的网络上传输。Modbus / TCP将Modbus消息嵌入TCP / IP帧内。尽管实现起来非常简单,但是与网络相关的特性增加了一些挑战。例如,由于Modbus主机期望并要求在一定时间范围内对其轮询做出响应,因此必须考虑TCP / IP网络的不确定性(和其他方面)。Modbus ASCII和Modbus TCP之间的主要区别在于,Modbus ASCII所需的LRC错误检查由IP层执行。

对于以上TCP/RTU/ASCII的这三种通信协议在数据模型和功能调用上都是相同的,只有封装方式是不同的。

1.4 广播模式与单播模式

在串行链路的主从通信中,Modbus主设备可以连接一个或N(最大为247)个从设备,主从设备之间的通信包括单播模式和广播模式。

广播模式中,Modbus主设备可同时向多个从设备发送请求(设备地址0用于广播模式),从设备对广播请求不进行响应。

所谓广播模式,是主节点向所有的子节点发送请求,当主节点发送的请求报文的地址域值为0时,代表广播请求,所有的从节点都需要接受处理,但不需要向主节点返回报文。

单播模式中,主设备发送请求至某个特定的从设备(每个Modbus从设备具有唯一地址),请求的消息帧中会包含功能代码和数据,比如功能代码“01”用来读取离散量线圈的状态。从设备接到请求后,进行应答并把消息反馈主设备。

所谓单播模式就是主节点给某个指定的节点发送消息(通过ADU中的地址域指定),从节点收到并处理完请求后,从节点向主节点返回一个应答报文,在这种模式下,一个Modbus事务包含两个报文,一个来自主节点的请求,一个来自子节点的应答

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-riMsj0KM-1631626547715)(https://i.loli.net/2021/08/11/NEkywuR4l1TdUhP.png)]

其实在物理层所有设备都会收到所有的请求,但地址域不为0时,从机判断当前为单播模式,只有地址域和自身地址号相同的从机才会响应请求,当地址域为0时,从机判断为广播消息,所有的从机都会执行指令,所有收到指令的设备都会运行,只不过不回应指令。

1.5 事务处理流程

​ 启动MODBUS 事务处理的客户机创建 MODBUS 应用数据单元。当从客户机向服务器设备发送报文时,功能码向服务器指示将执行哪种操作。

​ **正常事务处理流程:**从客户机向服务器设备发送的报文数据域包括附加信息,服务器使用这个信息执行功能码定义的操作。如果在一个正确接收的 MODBUS ADU 中,不出现与请求 MODBUS 功能有关的差错,那么服务器至客户机的响应数据域包括请求数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCOKYFWM-1631626547717)(https://i.loli.net/2021/08/11/oMjFgJln7fQdSxz.png)]

**异常事务处理流程:**如果出现与请求 MODBUS 功能有关的差错,那么域包括一个异常码,服务器应用能够使用这个域确定下一个执行的操作。当服务器对客户机响应时,它使用功能码域来指示正常(无差错)响应或者出现某种差错(称为异常响应)。对于一个正常响应来说,服务器仅对原始功能码响应。

1.6 寄存器相关概念

modbus完整支持很多功能码,但是实际在应用的时候常用的也就那么几个。具体如下:

0x01: 读线圈寄存器

0x02: 读离散输入寄存器

0x03: 读保持寄存器

0x04: 读输入寄存器

0x05: 写单个线圈寄存器

0x06: 写单个保持寄存器

0x0f: 写多个线圈寄存器

0x10: 写多个保持寄存器

如上所示一共8种功能码。这其中有涉及到线圈、离散输入、保持、输入四种寄存器

线圈寄存器:实际上就可以类比为开关量,没一个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是:0x01 0x05 0x0f。

离散输入寄存器:如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02。

保持寄存器:这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写,所以功能码有对应的三个:0x03 0x06 0x10。

输入寄存器:只剩下这最后一个了,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 0x04。

二、数据帧格式

功能码 描述 访问类型 PLC地址 数据类型 操作数量
01H 线圈寄存器 00001-09999 单/多
02H 离散输入寄存器 只读 10001-19999 单\多
03H 保持寄存器 40001-49999 单\多
04H 输入寄存器 30001-39999 单\多
05H 线圈寄存器 00001-09999
06H 保持寄存器 40001-49999
0FH 线圈寄存器 00001-09999
10H 保持寄存器 40001-49999

2.1 0X01功能码

0X01:读线圈状态

描述:读取从机线圈寄存器,位操作,可读单个或者多个;

2.1.1 发送指令

假设从机地址位0x01,线圈寄存器开始地址0x0023,总共读取21个线圈寄存器:

从机地址 功能码 起始地址高八位 起始地址低八位 寄存器数量高八位 寄存器数量低八位 CRCH CRCL
0x01 0x01 0x00 0x23 0x00 0x15 0xXX 0xXX

从机地址:要通信的设备

功能码:实现的功能

起始地址:从哪个线圈寄存器地址开始读

线圈数量:从起始地址往后读多少个线圈寄存器

2.1.2 正常响应

返回数据的每一位对应线圈状态,1-ON,0-OFF,如下图;

从机地址 功能码 返回字节数 data1 data2 data3 CRCH CRCL
0x01 0x01 0x03 0xa5 0xd4 0x18 0xXX 0xXX
  • 返回字节数 = 线圈寄存器数量/8 (线圈寄存器数量/%8还有余数,则字节数+1)(1个字节最多可以表示8个线圈寄存器)
  • data1的最低位代表最低地址的线圈寄存器状态,data3的最高位代表最高地址的线圈寄存器状态,可以理解为小端模式
  • data1表示地址0x0023-0x002a的线圈寄存器状态
  • data2表示地址0x002b-0x0032的线圈寄存器状态
  • data3表示地址0x0033-0x0037的线圈寄存器状态(不够8位,字节高位填充为0

data1:

0x2a 0x29 0x28 0x27 0x26 0x25 0x24 0x23
1 0 1 0 0 1 0 1

data2:

0x32 0x31 0x30 0x2f 0x2e 0x2d 0x2c 0x2b
1 1 0 1 0 1 0 0

data3:

0xxx 0xxx 0xxx 0x37 0x36 0x35 0x34 0x33
0 0 0 1 1 0 0 0

2.1.3 异常码响应

假设发生了01异常

从机地址 功能码0x01|0x80 异常码 CRCH CRCL
01 81 01 0xXX 0xXX
  • 01异常码:功能码错误

  • 02异常码:起始地址超出范围(或起始地址+输出线圈数量超出范围)

  • 03异常码:输出线圈数量小于0x0001或者大于0x07d0

2.1.4 不响应

  • 从机地址错误

  • 接收到的数据位数不对,例如少了功能码、或者少了线圈寄存器地址

  • CRC校验错误

2.2 0X03功能码

0X03-读保持寄存器

描述:读保持寄存器,字节指令操作,可读单个或者多个;

2.2.1 发送指令

从机地址0x01,保持寄存器起始地址0x0023,读2个保持寄存器

从机地址 功能码 起始地址高八位 起始地址低八位 寄存器数量高八位 寄存器数量低八位 CRCH CRCL
0x01 0x03 0x00 0x23 0x00 0x02 0xXX 0xXX

从机地址:要通信的设备

功能码:实现的功能

寄存器起始地址:从哪个寄存器地址开始读

寄存器数量:从起始地址往后读多少个寄存器

2.2.2 正常响应

从机地址 功能码 返回字节数 Data1H Data1L Data2H Data2L CRCH CRCL
0x01 0x03 0x04 0xA5 0xA4 0x18 0x12 0xXX 0xXX
  • 返回字节数=寄存器数量*2 (1个寄存器返回一个寄存器高8位,一个寄存器低8位,共两个字节)

  • Data1H = 第一个寄存器里面高8位

  • Data1L = 第一个寄存器里面低8位

  • Data2H = 第二个寄存器里面高8位

  • Data2L = 第二个寄存器里面低8位

Data1:04 B1(十六进制)–>1201(十进制)

Data2:08 FF(十六进制)–>2303(十进制)

数据存储顺序

0x0026 0x0025 0x0024 0x0023
0x04 0xB1 0x08 0xFF

2.2.3 异常码响应

假设发生了02异常

从机地址 功能码0x03|0x80 异常码 CRCH CRCL
01 83 02 0xXX 0xXX
  • 01异常码:功能码错误

  • 02异常码:起始地址超出范围(或起始地址+寄存器数量超出范围)

  • 03异常码:输出数量小于0x0001或者大于0x007D

2.2.4 不响应

  • 从机地址错误

  • 接收到的数据位数不对,例如少了功能码、或者少了寄存器地址

  • CRC校验错误

2.3 0X0F功能码

0X0F-写多个线圈

描述:写多个线圈寄存器。若数据区的某位值为“1”表示被请求的相应线圈状态为ON,若某位值为“0”,则为状态为OFF。

2.3.1 发送指令

线圈寄存器地址为0x0023,写10个线圈寄存器:

从机地址 功能码 起始地址高八位 起始地址低八位 寄存器数量高八位 寄存器数量低八位 字节数 DATA1 DATA2 CRCH CRCL
0x01 0x0F 0x00 0x23 0x00 0x0A 0x02 0x0C 0x02 0xXX 0xXX
  • 字节数 = 线圈寄存器数量/8 (线圈寄存器数量/%8还有余数,则字节数+1)(1个字节最多可以表示8个线圈寄存器)

  • data1的最低位代表最低地址的线圈寄存器状态,data2的最高位代表最高地址的线圈寄存器状态,可以理解为小端模式

  • DATA1:表示地址0x0023-0x002a的线圈寄存器状态

  • DATA2:表示地址0x002B-0x002C的线圈寄存器状态(不够8位,字节高位填充为0

DATA1:

0x002A 0x0029 0x0028 0x0027 0x0026 0x0025 0x0024 0x0023
0 0 0 0 1 1 0 0

DATA2:

0xxx 0xxx 0xxx 0xxx 0xxx 0xxx 0x002C 0x002B
0 0 0 0 0 0 1 0

2.3.2 正常响应:

从机地址 功能码 线圈寄存器地址高八位 线圈寄存器地址低八位 寄存器数量高八位 寄存器数量低八位 CRCH CRCL
0x01 0x0F 0x04 0x05 0x00 0x0d 0xXX 0xXX

2.3.3 异常码响应

假设发生了03异常

从机地址 功能码0x0F|0x80 异常码 CRCH CRCL
01 8F 03 0xXX 0xXX
  • 01异常码:功能码错误

  • 02异常码:起始地址超出范围(或起始地址+寄存器数量超出范围)

  • 03异常码:输出数量小于0x0001或者大于0x07B0(或字节数位与后面的字节数不对应)

2.3.4 不响应

  • 从机地址错误
  • 接收到的数据位数不对,例如少了功能码、或者少了寄存器地址
  • CRC校验错误

2.4 0X10功能码

0X10-写多个保持寄存器

描述:写多个保持寄存器,字节指令操作,可写多个;

2.4.1 发送指令

线圈寄存器地址为0x0023,写10个线圈寄存器:

例如:保持寄存器起始地址为0x0023,写2个寄存器,共4个字节的数据;

从机地址 功能码 起始地址高八位 起始地址低八位 寄存器数量高八位 寄存器数量低八位 字节数 DATA1H DATA1L DATA2H DATA2L CRCH CRCL
0x01 0x10 0x00 0x23 0x00 0x02 0x04 0x04 0xB1 0x08 0xFF 0xXX 0xXX
  • 字节数=寄存器数量*2 (1个寄存器返回一个寄存器高8位,一个寄存器低8位,共两个字节)

  • Data1H = 第一个寄存器里面高8位

  • Data1L = 第一个寄存器里面低8位

  • Data2H = 第二个寄存器里面高8位

  • Data2L = 第二个寄存器里面低8位

Data1:04 B1(十六进制)–>1201(十进制)

Data2:08 FF(十六进制)–>2303(十进制)

3.4.2正常响应

从机地址 功能码 起始地址高八位 起始地址低八位 寄存器数量高八位 寄存器数量低八位 CRCH CTCL
0x01 0x10 0x00 0x34 0x00 0x02 0xXX 0xXX

2.4.3 异常码响应

假设发生了03异常

从机地址 功能码0x10|0x80 异常码 CRCH CRCL
01 90 03 0xXX 0xXX
  • 01异常码:功能码错误

  • 02异常码:起始地址超出范围(或起始地址+寄存器数量超出范围)

  • 03异常码:输出数量小于0x001或者大于0x007B(或字节数不等于寄存器数*2)

2.4.4 不响应

  • 从机地址错误

  • 接收到的数据位数不对,例如少了功能码、或者少了寄存器地址

  • CRC校验错误

Modbus的基础学习相关推荐

  1. 【转】oracle PLSQL基础学习

    [转]oracle PLSQL基础学习 --oracle 练习: /**************************************************PL/SQL编程基础****** ...

  2. python创建对象的格式为_Python入门基础学习(面向对象)

    python基础学习笔记(四) 面向对象的三个基本特征: 封装:把客观事物抽象并封装成对象,即将属性,方法和事件等集合在一个整体内 继承:允许使用现有类的功能并在无须重新改写原来的类情况下,对这些功能 ...

  3. 虚幻引擎虚拟现实开发基础学习教程

    流派:电子学习| MP4 |视频:h264,1280×720 |音频:AAC,44.1 KHz 语言:英语+中英文字幕(根据原英文字幕机译更准确)|大小解压后:3.93 GB |时长:5h 15m 了 ...

  4. 动画产业基础学习教程 Rad How to Class – Animation Industry Fundamentals

    如何分类--动画产业基础 大小解压后:6.2G 含课程素材 1920X1080 mp4 语言:英语+中英文字幕(根据原英文字幕机译更准确) 信息: 绘画技巧.解剖学.角色设计.透视和整体讲故事--这门 ...

  5. Blender纹理基础学习视频教程 CGCookie – Fundamentals of Texturing in Blender

    Blender纹理基础学习视频教程 CGCookie – Fundamentals of Texturing in Blender Blender纹理基础学习视频教程 CGCookie – Funda ...

  6. ue5新手零基础学习教程 Unreal Engine 5 Beginner Tutorial - UE5 Starter Course

    ue5新手零基础学习教程 Unreal Engine 5 Beginner Tutorial - UE5 Starter Course! 教程大小解压后:4.96G 语言:英语+中英文字幕(机译)时长 ...

  7. 0基础学好python难不难_零基础学习Python难不难?Python有什么优势?

    原标题:零基础学习Python难不难?Python有什么优势? Python是一种计算机程序设计语言.首先,我们普及一下编程语言的基础知识.用任何编程语言来开发程序,都是为了让计算机干活,比如下载一个 ...

  8. 计算机一级ps2019,2019年计算机一级考试PS基础学习点子:PS菜单中英文对照表.docx...

    2019 年计算机一级考试 PS 基础学习点子: PS 菜单中英文对照表 PS菜单中英文对照表 一.File New 2.Open 3.Open As 4.Open Recent Close 6.Sa ...

  9. Java零基础学习难吗

    java编程是入行互联网的小伙伴们大多数的选择,那么对于零基础的小伙伴来说Java零基础学习难吗?如果你是初学者,你可以很好的理解java编程语言.并不困难.如果你的学习能力比较高,那么你对Java的 ...

最新文章

  1. jquery通知插件toastr
  2. Python/Anaconda-python2.x代码转为python3.x代码
  3. 关于UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte 0xa6 in position 9737: 的解决
  4. qperf测量网络带宽和延迟
  5. echart 高度 不用 不撑满_高度、长度可调节的输送机,能延伸至货车内部,堪称装卸神器...
  6. larvel nginx 配置
  7. VMware Esxi-5.1 简介与安装
  8. 实用的 Python —— base64
  9. python入门经典 财务-财务方面的学生如何学习python?
  10. Linux的基本权限和特殊权限
  11. Iphone 开发常用代码
  12. 如何将Win7、Win10笔记本,台式机系统C盘软件搬家? 只需3个步骤!!!
  13. 小学认识计算机评课,小学信息技术评课.doc
  14. 百度C语言面试题2017,百度C语言面试题
  15. Ubuntu下deb文件安装方法图文详解
  16. Codewars-Java编程刷题学习4-Jaden Casing Strings
  17. c语言 二分查找法 及二分查找法的时间复杂度。
  18. vuex的基本应用(vuex的购物车案例)
  19. cesium实时获取卫星的动态信息,包括经纬度和名称(onTick)
  20. android五大布局的作用,Android五大布局与实际应用详解

热门文章

  1. HTTP请求中POST与GET的区别
  2. redis在windows下的安装(包含官网下载安装包)
  3. 行云集团独家冠名纪录片《风从东风来》,讲述中国品牌故事
  4. 视频教程-【吴刚】iOS原生图标设计原理与绘制技巧标准教程-UI
  5. e代理与和合首创达成战略合作,共创WealthTech生态圈
  6. git extensions 记住用户名和密码
  7. “33岁转行软件测试还来得及吗?”怎么去转行软件测试?
  8. maven-assembly-plugin插件
  9. Linux/Openwrt路由安装配置UPNP服务提高迅雷下载速度
  10. 太阳同步轨道卫星 理解内容