MQTT 介绍

MQTT是机器对机器(M2M)/物联网(IoT)连接协议。它被设计为一个极其轻量级的发布/订阅消息传输协议。对于需要较小代码占用空间和/或网络带宽非常宝贵的远程连接非常有用,是专为受限设备和低带宽、高延迟或不可靠的网络而设计。这些原则也使该协议成为新兴的“机器到机器”(M2M)或物联网(IoT)世界的连接设备,以及带宽和电池功率非常高的移动应用的理想选择。例如,它已被用于通过卫星链路与代理通信的传感器、与医疗服务提供者的拨号连接,以及一系列家庭自动化和小型设备场景。它也是移动应用的理想选择,因为它体积小,功耗低,数据包最小,并且可以有效地将信息分配给一个或多个接收器。

  1. MQTT 客户端

    一个使用 MQTT 协议的设备、应用程序等,它总是建立到服务器的网络连接。

    • 可以发布信息,其他客户端可以订阅该信息
    • 订阅其它客户端发布的消息
    • 退订或删除应用程序的消息
    • 断开与服务器连接
  2. MQTT 服务器

    MQTT 服务器也称为 Broker(消息代理),以是一个应用程序或一台设备。它是位于消息发布者 和订阅者之间

    • 接受来自客户端的网络连接
    • 接受客户端发布的应用信息
    • 处理来自客户端的订阅和退订请求
    • 向订阅的客户转发应用程序消息
  3. 主题(Topic)

    连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。

    • 要订阅的主题。一个主题可以有多个级别,级别之间用斜杠字符分隔。例如,/worldemq/emqtt/emqx 是有效的主题。
    • 订阅者的 Topic name 支持通配符 #+
      • # 支持一个主题内任意级别话题
      • + 只匹配一个主题级别的通配符
    • 客户端成功订阅某个主题后,代理会返回一条 SUBACK 消息,其中包含一个或多个 returnCode 参数
  4. 主题筛选器(Topic Filter)

    一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。

  5. QoS(消息传递的服务质量水平)

    服务质量,标志表明此主题范围内的消息传送到客户端所需的一致程度。

    • 值 0:不可靠,消息基本上仅传送一次,如果当时客户端不可用,则会丢失该消息。
    • 值 1:消息应传送至少 1 次。
    • 值 2:消息仅传送一次。
  6. 会话(Session)

    每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。

  7. 订阅(Subscription)

    订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。

    • 客户端在成功建立 TCP 连接之后,发送 CONNECT 消息,在得到服务器端授权允许建立彼此连接的 CONNACK 消息之后,客户端会发送 SUBSCRIBE 消息,订阅感兴趣的 Topic 主题列表(至少一个主题)
    • 订阅的主题名称采用 UTF-8 编码,然后紧跟着对应的 QoS 值
  8. 发布(publish)

    控制报文是指从客户端向服务端或者服务端向客户端传输一个应用消息,MQTT 客户端发送消息请求,发送完成后返回应用程序线程

    • 比如安卓的推送服务,还有一些即时通信软件如微信等也是采用的推送技术。
  9. 负载(Payload)

    消息订阅者所具体接收的内容

简单示例

MQTT 协议主要是根据以下情况设计的:

  • M2M(Machine to Machine),机器或设备间端到端通信,比如传感器之间的数据通讯。
  • 设备(Machine)中,例如传感器,硬件能力很弱,协议要考虑尽量小的资源消耗,比如计算能力和存储等。

根据 MQTT 的基础了解后并结合简单的架构,在这里做一个简单的示例图,可以更直观的理解 MQTT 协议的通信模型。MQTT Broker 就选择 EMQ 作为示范。比如有1个温度传感器(1个 Machine),1个移动设备,1个电脑,一个服务器(3个 Machine),都可以得到或者显示温度传感器的温度值,需要先通过 MQTT 协议 subscribe(订阅)一个比如叫 temperature 的 topic(主题)如下:

图中移动设备,服务器,电脑需要先通过 EMQ subscribe 一个叫 temperature 的 topic,当温度传感器 publish 温度数据,三个设备就可以收到了。

搭建本地 MQTT 服务器

EMQX(推荐,免安装)

官网下载(开源版):https://www.emqx.io/zh/downloads

官方文档:https://www.emqx.io/docs/zh/v4.3/

注意:必须在 Windows Shell 中运行

# 解压之后进入目录
# 执行命令脚本
$ ./bin/emqx start
emqx 4.0.0 is started successfully!$ ./bin/emqx_ctl status
Node 'emqx@127.0.0.1' is started
emqx v4.0.0 is running
# 停止 EMQX Broker
$ ./bin/emqx stop
ok

当 EMQ X 成功运行在你的本地计算机上且 EMQ X Dashboard 被默认启用时,你可以访问 http://localhost:18083 来查看你的 Dashboard,默认用户名是 admin,密码是 public。

关于Dashboard的具体使用,可参见:EMQX Dashboard

基本命令

EMQX 提供了 emqx 命令行工具,方便用户对 EMQX 进行启动、关闭、进入控制台等操作。

  • emqx start

    后台启动 EMQX Broker;

  • emqx stop

    关闭 EMQX Broker;

  • emqx restart

    重启 EMQX Broker;

  • emqx console

    使用控制台启动 EMQX Broker;

  • emqx foreground

    使用控制台启动 EMQX Broker,与 emqx console 不同,emqx foreground 不支持输入 Erlang 命令;

  • emqx ping

    Ping EMQX Broker。

mosquitto

官网下载:https://mosquitto.org/download/

可自行在网上了解,不做详细介绍。

购买远程 MQTT 服务器(14天免费试用)

EMQX Cloud 官网:https://www.emqx.com/zh/cloud

可以通过按小时购买或14天免费试用获取服务,控制台界面如下:

Python 程序实例

首先安装相关依赖:pip3 install -i https://pypi.doubanio.com/simple paho-mqtt

发布端 publish.py

import random
import timefrom paho.mqtt import client as mqtt_clientbroker = 'broker.emqx.io'
port = 1883
topic = "/python/mqtt"
# generate client ID with pub prefix randomly
client_id = f'python-mqtt-{random.randint(0, 1000)}'def connect_mqtt():def on_connect(client, userdata, flags, rc):if rc == 0:print("Connected to MQTT Broker!")else:print("Failed to connect, return code %d\n", rc)client = mqtt_client.Client(client_id)client.on_connect = on_connectclient.connect(broker, port)return clientdef publish(client):msg_count = 0while True:time.sleep(1)msg = f"messages: {msg_count}"result = client.publish(topic, msg)# result: [0, 1]status = result[0]if status == 0:print(f"Send `{msg}` to topic `{topic}`")else:print(f"Failed to send message to topic {topic}")msg_count += 1def run():client = connect_mqtt()client.loop_start()publish(client)if __name__ == '__main__':run()

订阅端 subscribe.py

import randomfrom paho.mqtt import client as mqtt_clientbroker = 'broker.emqx.io'
port = 1883
topic = "/python/mqtt"
# generate client ID with pub prefix randomly
client_id = f'python-mqtt-{random.randint(0, 100)}'def connect_mqtt() -> mqtt_client:def on_connect(client, userdata, flags, rc):if rc == 0:print("Connected to MQTT Broker!")else:print("Failed to connect, return code %d\n", rc)client = mqtt_client.Client(client_id)client.on_connect = on_connectclient.connect(broker, port)return clientdef subscribe(client: mqtt_client):def on_message(client, userdata, msg):print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")client.subscribe(topic)client.on_message = on_messagedef run():client = connect_mqtt()subscribe(client)client.loop_forever()if __name__ == '__main__':run()

Vue 程序实例

安装 MQTT 客户端库:

  1. 通过命令行安装,可以使用 npm 或 yarn 命令,二者选一
    npm install mqtt --save
    yarn add mqtt
  2. 通过 CDN 引入
    <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
  3. 下载到本地,然后使用相对路径引入

mqtt.vue

<template><div class="home-container"><el-card shadow="always" style="margin:30px 0;"><div class="emq-title">Configuration</div><el-form ref="configForm" hide-required-asterisk size="small" label-position="top" :model="connection"><el-row :gutter="20"><el-col :span="8"><el-form-item prop="host" label="Host"><el-input v-model="connection.host"></el-input></el-form-item></el-col><el-col :span="8"><el-form-item prop="port" label="Port"><el-input v-model.number="connection.port" type="number" placeholder="8083/8084"></el-input></el-form-item></el-col><el-col :span="8"><el-form-item prop="endpoint" label="Mountpoint"><el-input v-model="connection.endpoint" placeholder="/mqtt"></el-input></el-form-item></el-col><el-col :span="8"><el-form-item prop="clientId" label="Client ID"><el-input v-model="connection.clientId"> </el-input></el-form-item></el-col><el-col :span="8"><el-form-item prop="username" label="Username"><el-input v-model="connection.username"></el-input></el-form-item></el-col><el-col :span="8"><el-form-item prop="password" label="Password"><el-input v-model="connection.password"></el-input></el-form-item></el-col><el-col :span="24"><el-buttontype="success"size="small"class="conn-btn"style="margin-right: 20px;":disabled="client.connected"@click="createConnection">{{ client.connected ? 'Connected' : 'Connect' }}</el-button><el-button v-if="client.connected" type="danger" size="small" class="conn-btn" @click="destroyConnection">Disconnect</el-button></el-col></el-row></el-form></el-card><el-card shadow="always" style="margin-bottom:30px;"><div class="emq-title">Subscribe</div><el-form ref="subscription" hide-required-asterisk size="small" label-position="top" :model="subscription"><el-row :gutter="20"><el-col :span="8"><el-form-item prop="topic" label="Topic"><el-input v-model="subscription.topic"></el-input></el-form-item></el-col><el-col :span="8"><el-form-item prop="qos" label="QoS"><el-select v-model="subscription.qos"><el-optionv-for="(item, index) in qosList":key="index":label="item.label":value="item.value"></el-option></el-select></el-form-item></el-col><el-col :span="8"><el-button:disabled="!client.connected"type="success"size="small"class="subscribe-btn"@click="doSubscribe">{{ subscribeSuccess ? 'Subscribed' : 'Subscribe' }}</el-button><el-button:disabled="!client.connected"type="success"size="small"class="subscribe-btn"style="margin-left:20px"@click="doUnSubscribe"v-if="subscribeSuccess">Unsubscribe</el-button></el-col></el-row></el-form></el-card><el-card shadow="always" style="margin-bottom:30px;"><div class="emq-title">Publish</div><el-form ref="publish" hide-required-asterisk size="small" label-position="top" :model="publish"><el-row :gutter="20"><el-col :span="8"><el-form-item prop="topic" label="Topic"><el-input v-model="publish.topic"></el-input></el-form-item></el-col><el-col :span="8"><el-form-item prop="payload" label="Payload"><el-input v-model="publish.payload" size="small"></el-input></el-form-item></el-col><el-col :span="8"><el-form-item prop="qos" label="QoS"><el-select v-model="publish.qos"><el-optionv-for="(item, index) in qosList":key="index":label="item.label":value="item.value"></el-option></el-select></el-form-item></el-col></el-row></el-form><el-col :span="24"><el-button :disabled="!client.connected" type="success" size="small" class="publish-btn" @click="doPublish">Publish</el-button></el-col></el-card><el-card shadow="always" style="margin-bottom:30px;"><div class="emq-title">Receive</div><el-col :span="24"><el-input type="textarea" :rows="3" style="margin-bottom: 15px" v-model="receiveNews"></el-input></el-col></el-card></div>
</template><script>
import mqtt from 'mqtt'export default {name: 'Mqtt',data() {return {connection: {host: 'broker.emqx.io',port: 8083,endpoint: '/mqtt',clean: true, // 保留会话connectTimeout: 4000, // 超时时间reconnectPeriod: 4000, // 重连时间间隔// 认证信息clientId: 'mqttjs_3be2c321',username: 'emqx_test',password: 'emqx_test',},subscription: {topic: 'topic/mqttx',qos: 0,},publish: {topic: 'topic/browser',qos: 0,payload: '{ "msg": "Hello, I am browser." }',},receiveNews: '',qosList: [{ label: 0, value: 0 },{ label: 1, value: 1 },{ label: 2, value: 2 },],client: {connected: false,},subscribeSuccess: false,}},created() {this.createConnection()},methods: {// 创建连接createConnection() {// 连接字符串, 通过协议指定使用的连接方式// ws 未加密 WebSocket 连接// wss 加密 WebSocket 连接// mqtt 未加密 TCP 连接// mqtts 加密 TCP 连接// wxs 微信小程序连接// alis 支付宝小程序连接const { host, port, endpoint, ...options } = this.connectionconst connectUrl = `ws://${host}:${port}${endpoint}`try {this.client = mqtt.connect(connectUrl, options)} catch (error) {console.log('mqtt.connect error', error)}this.client.on('connect', () => {console.log('Connection succeeded!')})this.client.on('error', error => {console.log('Connection failed', error)})this.client.on('message', (topic, message) => {this.receiveNews = this.receiveNews.concat(message)console.log(`Received message ${message} from topic ${topic}`)})},// 订阅主题doSubscribe() {const { topic, qos } = this.subscriptionthis.client.subscribe(topic, { qos }, (error, res) => {if (error) {console.log('Subscribe to topics error', error)return}this.subscribeSuccess = trueconsole.log('Subscribe to topics res', res)})},// 取消订阅doUnSubscribe() {const { topic } = this.subscriptionthis.client.unsubscribe(topic, error => {if (error) {console.log('Unsubscribe error', error)}})},// 发送消息doPublish() {const { topic, qos, payload } = this.publishthis.client.publish(topic, payload, qos, error => {if (error) {console.log('Publish error', error)}})},// 断开连接destroyConnection() {if (this.client.connected) {try {this.client.end()this.client = {connected: false,}console.log('Successfully disconnected!')} catch (error) {console.log('Disconnect failed', error.toString())}}}},
}
</script><style />

Spring Boot 集成 MQTT

依赖 pom.xml

<!-- MQTT -->
<dependency><groupId>org.springframework.integration</groupId><artifactId>spring-integration-mqtt</artifactId>
</dependency>

配置类 MqttConfig.java

Spring 中集成框架,有消息入站通道(用来接收消息)和出站通道(用来发送消息)

import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;@Configuration
public class MqttConfig {// 消费消息/*** 创建MqttPahoClientFactory,设置MQTT Broker连接属性,如果使用SSL验证,也在这里设置。* @return factory*/@Beanpublic MqttPahoClientFactory mqttClientFactory() {DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();MqttConnectOptions options = new MqttConnectOptions();// 设置代理端的URL地址,可以是多个options.setServerURIs(new String[]{"tcp://127.0.0.1:1883"});factory.setConnectionOptions(options);return factory;}/*** 入站通道*/@Beanpublic MessageChannel mqttInputChannel() {return new DirectChannel();}/*** 入站*/@Beanpublic MessageProducer inbound() {// Paho客户端消息驱动通道适配器,主要用来订阅主题MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("consumerClient-paho",mqttClientFactory(), "boat", "collector", "battery", "+/sensor");adapter.setCompletionTimeout(5000);// Paho消息转换器DefaultPahoMessageConverter defaultPahoMessageConverter = new DefaultPahoMessageConverter();// 按字节接收消息//        defaultPahoMessageConverter.setPayloadAsBytes(true);adapter.setConverter(defaultPahoMessageConverter);adapter.setQos(1); // 设置QoSadapter.setOutputChannel(mqttInputChannel());return adapter;}@Bean// ServiceActivator注解表明:当前方法用于处理MQTT消息,inputChannel参数指定了用于消费消息的channel。@ServiceActivator(inputChannel = "mqttInputChannel")public MessageHandler handler() {return message -> {String payload = message.getPayload().toString();// byte[] bytes = (byte[]) message.getPayload(); // 收到的消息是字节格式String topic = message.getHeaders().get("mqtt_receivedTopic").toString();// 根据主题分别进行消息处理。if (topic.matches(".+/sensor")) { // 匹配:1/sensorString sensorSn = topic.split("/")[0];System.out.println("传感器" + sensorSn + ": 的消息: " + payload);} else if (topic.equals("collector")) {System.out.println("采集器的消息:" + payload);} else {System.out.println("丢弃消息:主题[" + topic  + "],负载:" + payload);}};}// 发送消息/*** 出站通道*/@Beanpublic MessageChannel mqttOutboundChannel() {return new DirectChannel();}/*** 出站*/@Bean@ServiceActivator(inputChannel = "mqttOutboundChannel")public MessageHandler outbound() {// 发送消息和消费消息Channel可以使用相同MqttPahoClientFactoryMqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler("publishClient", mqttClientFactory());messageHandler.setAsync(true); // 如果设置成true,即异步,发送消息时将不会阻塞。messageHandler.setDefaultTopic("command");messageHandler.setDefaultQos(1); // 设置默认QoS// Paho消息转换器DefaultPahoMessageConverter defaultPahoMessageConverter = new DefaultPahoMessageConverter();// defaultPahoMessageConverter.setPayloadAsBytes(true); // 发送默认按字节类型发送消息messageHandler.setConverter(defaultPahoMessageConverter);return messageHandler;}}

接口 MqttGateway.java

import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MqttGateway {// 定义重载方法,用于消息发送void sendToMqtt(String payload);// 指定topic进行消息发送void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, byte[] payload);
}

测试

测试方式:使用接口工具,给接口发送消息,从而调用MQTT客户端发布消息

MqttController.java

import com.ioufev.mqtt.domain.MyMessage;
import com.ioufev.mqtt.mqtt.MqttGateway;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@RestController
public class MqttController {@Resourceprivate MqttGateway mqttGateway;@PostMapping("/send")public String send(@RequestBody MyMessage myMessage) {// 发送消息到指定主题mqttGateway.sendToMqtt(myMessage.getTopic(), 1, myMessage.getContent());return "send topic: " + myMessage.getTopic()+ ", message : " + myMessage.getContent();}}

MyMessage.java

public class MyMessage {private String topic;private String content;public String getTopic() {return topic;}public void setTopic(String topic) {this.topic = topic;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}
}

MQTT 协议监听工具

本地下载地址:MQTT X:跨平台 MQTT 5.0 桌面客户端工具

网页版链接:MQTT WebSocket Toolkit | EMQ

MQTT 消息通信工具使用相关推荐

  1. Spark源码阅读02-Spark核心原理之消息通信原理

    Spark消息通信架构 在Spark中定义了通信框架接口,这些接口实现中调用了Netty的具体方法.通信框架使用了工厂设计模式,这种模式实现了对Netty的解耦,能够根据需要引入其他的消息通信工具. ...

  2. Spark详解(四):Spark组件以及消息通信原理

    1. Spark核心基本概念 Application(应用程序):指用户编写的Spark应用程序,包含驱动程序(Driver)和分布在集群中多个节点之上的Executor代码,在执行过程中由一个或多个 ...

  3. SpringBoot 整合MQTT 消息推送

    一: pom文件添加依赖 <!-- mqtt --><dependency><groupId>org.springframework.integration< ...

  4. Java物联网开发(二) —— 开源百万级分布式 MQTT 消息服务器EMQX

    开源百万级分布式 MQTT 消息服务器EMQX 一. 是什么 1. 简介 2. 分类 3. EMQ X 消息服务器功能列表 二. 安装 1. 安装方式 rpm安装 docker安装 免安装 2. 目录 ...

  5. Web技术(七):如何使用并实现MQTT 消息订阅-发布模型?

    文章目录 一.什么是发布-订阅消息模型? 二.订阅-发布消息模型有哪些应用? 2.1 应用于IP 物联网络中的消息传递 2.2 应用于操作系统进程间的消息传递 2.3 应用于MESH 自组网中的消息传 ...

  6. 千万级车联网 MQTT 消息平台架构设计

    在本专题系列文章中,我们将根据 EMQ 在车联网领域的实践经验,从协议选择等理论知识,到平台架构设计等实战操作,与大家分享如何搭建一个可靠.高效.符合行业场景需求的车联网平台. 前言 随着整个汽车出行 ...

  7. 物联网实战-基于开源 MQTT消息服务器EMQ X

    物联网协议对比 对于物联网,最重要的是在互联网中设备与设备的通讯,现在物联网在internet通信中比较常见的通讯协议包括:HTTP.websocket.XMPP.COAP.MQTT HTTP和web ...

  8. java mqtt paho_使用eclipse paho在java端实现MQTT消息的收发(客户端与服务端实例)...

    一.MQTT(消息队列)简介 MQTT(MQ Telemetry Transport)是IBM开发的一种网络应用层的协议,提供轻量级的,支持可发布/可订阅的的消息推送模式,使设备对设备之间的短消息通信 ...

  9. Modbus RTU 通信工具设计

    Modbus 是一个工业上常用的通讯协议.一种通讯约定. ModBus 协议是应用层报文传输协议(OSI 模型第7层),它定义了一个与通信层无关的协议数据单元(PDU),即PDU=功能码+数据域. M ...

  10. MQTT 客户端收发 MQTT 消息

    本文主要介绍如何使用 MQTT 客户端收发 MQTT 消息,并给出示例代码供前期开发测试参考,包括资源创建.环境准备.示例代码.注意事项等. 注意: 本文给出的实例均基于 Eclipse Paho J ...

最新文章

  1. mysql server-存储引擎
  2. 学霸大佬整理,超全 Python 学习路线图(附工具+视频+书籍+面试)
  3. Pycharm最新版本安装教程
  4. python二维向量运算模拟_Python数学基础之向量定义与向量运算(附代码)
  5. 计算机图形(Computer Graphics)经典书籍推荐(1)
  6. linux中vim中文显示乱码
  7. 在 Mac 上的 Pages 文稿中如何添加和替换文本?
  8. 在windows生产环境搭建sphinx的注意事项
  9. rhel5中查询设备上采用的未知文件系统
  10. VM Workstation 12.0+ 参考序列号及linux系统推荐
  11. ESP8285调试 IOT_Demo
  12. 阿里架构师:双十一「大促」,多亏了 Node.js
  13. 动态规划矩阵连乘求最优值和最优解
  14. 巅峰产生虚伪拥护,黄昏见证忠诚信徒
  15. Sphinx武林秘籍(下)
  16. 图鉴| 互联网男女家门口贴什么春联?
  17. 【知识图谱】py2neo基本操作(2021-11-11)
  18. ios仿淘宝商品详情页面粘贴商品规格弹出模板
  19. 洛伦茨方程的运动图像画法,可以控制播放速度
  20. 《28天玩转TensorFlow2》第10天:TensorFlow2项目实战—基于迭代的风格迁移

热门文章

  1. Python面试常见算法题集锦
  2. ubuntu16安装Times New Roma字体 / WPS 安装Times New Roma字体
  3. 基于minio及tus断点续传及断点下载解决方案
  4. 视频教程-java后台+微信小程序 实现完整的点餐系统-微信开发
  5. 有哪些较好用的项目管理软件?
  6. 量化投资学sql还是mysql_新人如何学习量化投资
  7. ArduinoUNO实战-第九章-光敏电阻或亮度传感器
  8. 矩阵分析(2)--正规矩阵、正交矩阵
  9. switch删除用户显示正在维护服务器,Switch即将迎来6.0更新 账号与用户无法再正常解绑...
  10. matlab绘制三维机翼,三维机翼某一断面的压力系数X-Y曲线绘制——使用tecplot的extract功能...