问题,总结与优化方向

前言

本文章的目的是记录开发过程中的问题与心得体会,文中讲解的功能及流程图请务必基于本项目来理解。

问题

一、Flask服务器部分

1. 同一内网下无法访问服务器

部署好服务器且关闭防火墙后,尝试用在同一内网下的手机访问服务器,访问失败。
原因:Flask服务器在默认情况下只允许本地主机访问,需要设置host=0.0.0.0以接受网络中其它IP的访问(包括局域网和公网)。有的博客是这样设置的,但实际上它不会生效,需要到编译器里设置额外配置host才能生效,具体解决流程参考部署博客。

app.run(host=0.0.0.0)

2. 无法处理跨域请求 / AT+CIPSEND ERROR

访问网址时,组件正常显示,但组件内容不更新。这时打开浏览器的开发者工具(F12),显示控制台,出现如下报错。
在这里插入图片描述
这说明服务器无法处理跨域请求。
原因:web界面发送的api请求被服务器拒绝了,那么开启跨域请求即可,解决方法:添加如下俩行代码

# 导入包
from flask_cors import CORS 

# 开启CORS(跨源资源)以共享处理跨域请求
CORS(app, origins='*', supports_credentials=True) 

3. 服务器报错: AttributeError: ‘NoneType’ object has no attribute ‘read’

在这里插入图片描述
这个问题是在接受一次api调用后产生的,其会导致本次返回数据失败。
原因:经个人排查,报错是因为后台与数据库的连接不稳定,当api调用次数过高时就容易出现,可以通过降低api调用次数来减少这个错误(不同sql语句操作带来的损耗是不同的,复杂的sql语句调用频率升高更容易导致问题)

4. Flask服务器瘫痪问题

这个问题与上一个问题有关,表现如下
在这里插入图片描述
(这是download和query的调用频率都为3s一次)Flask服务器在接受一段时间的api调用后直接瘫痪,会挂起所有请求。为了明确是哪部分的问题,我关闭了qurery的请求调用。结果如下
在这里插入图片描述
(这是download的调用频率都为1s一次)发现运行长时间后服务器仍为瘫痪。且只有俩次调用失败。明确问题后,对代码做出调整,将query修改为30s一次。服务器便稳定了下来,之后的瘫痪时间变为了34.7分钟
在这里插入图片描述

二、前端部分

5. 时间格式错误

数据库里的时间
在这里插入图片描述
前端得到的时间
在这里插入图片描述

问题:当页面通过api获取JSON数据包,提取出的时间相较于标准时间差了八个小时。

原因:这时,我是通过在JavaScript中构造Date对象来接受数据的,它的构造函数创建的日期和时间默认是基于运行JavaScript代码的计算机上的本地时区,所以会自动+8个小时调整到中国时区。
解决

  1. 直接从时间数据里截取HH:mm:ss
//data是你通过api得到的JSON数据包
time=data[0].time.substring(17, 25)
  1. 使用扩展moment.min.js中的方法处理
//data是你通过api得到的JSON数据包
 data.forEach(item => {
time = moment.utc(item.time).format('YYYY-MM-DD HH:mm:ss');
});

成功处理后的时间如下所示
在这里插入图片描述

6. 插入新数据后BoostStrap样式不生效

在这里插入图片描述
问题
上图中的test是我初始时就设置好的表格。可以看到,当我在js中的数据插入新的数据后,它们并没有正确应用bootstrap样式。下面是错误的代码
在这里插入图片描述

原因
浏览器初始化html界面是一个线性的过程,它会逐行根据.html文件里的代码来生成界面(这也是有的开发规范会要求你把js文件放在body的末尾的原因,因为要先生成页面组件)。这就导致了后续添加数据时,如果不重新初始化BootStrap组件,就会丢失样式。
解决
通过AJAX动态加载内容到表格中时,在内容加载完成后重新初始化Bootstrap组件。至于为什么是jQuery,因为BootStrap v3本就是依赖jQuery的,所以通过jQuery插入后会自动初始化,如果你使用别的前端框架或新版的BootStrap,可能就需要调整插入方法。

// 通过jQuery插入
data.forEach(item => {
  time = moment.utc(item.time).format('YYYY-MM-DD HH:mm:ss');
  $('<tr>')
  .append($('<td>').text(item.id))
  .append($('<td>').text(item.temperature))
  .append($('<td>').text(item.humidity))
  .append($('<td>').text(time))
  .appendTo('#history_table tbody');
});

三、设备部分

7. 不能缺少的回车符

在这里插入图片描述
在使用串口调试助手来发送HTTP请求时,要在末尾添加一个换行符,不然无法正常发送。

8. Arduino板连接到服务器后仍然无法发送数据(未解决)

在这里插入图片描述
问题:接下来通过串口调试助手的发送过程来讲解,这样更直观一些
可以看到,明明已经连接上了Flask服务器,但却无法发送数据,原因未知,经过多次重启服务器,重启ESP8266,发现这个问题的发生是随机的,10分钟左右就会恢复正常。不过出于发生频率很低的原因,就不刨根问底了。

总结

一、物联通信的三种方式

  1. 通过设置俩个服务器来实现收发数据
    参考【 ESP8266与Web通信之代码讲解
    该作者通过设置在Node.js环境中运行的服务器端来实现数据传输。本项目没有采用这种方式,原因结合下图来讲
    在这里插入图片描述
    优点:只使用Node.js服务器的话,实现比较简单,只需要本项目的基础上安装Node.js环境和html代码。
    缺点:这种通信方式本质上是使ESP8266同时连接俩个服务器,它俩在同一主机的俩个端口上,Flask服务器负责接收,Node.js负责发送。另外还需要在ESP8266上设置多连接模式,针对俩个不同连接进行处理。
    我不采用这种方式的原因,一是因为同时连俩个服务器感觉优点抽象,另外还要设置ESP8266模块和修改arduino板上的接受数据部分的代码,感觉不如第二种方式来的更合理与可靠。
  2. WebSocket 通信
    优点:WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
    缺点:ESP8266需要在Arduino板上写入WebScocket库才能正常接收服务器端发送来的数据包,太麻烦了。
    实现:本项目使用Flask 扩展:Flask-SocketIO 实现了前端与服务器的通讯,因为WebScocket库有点难用,就没实现ESP8266与服务器的通信。
    接下来讲解如何实现前端与服务器的WebSocket 通信
  • 服务器部分:
# 在python中到入需要的包
from flask_socketio import SocketIO, emit

# 使用SocketIO扩展
socketio = SocketIO(app)

# 服务器连接客户端后发送一个名为response的事件,内容为'Hello Client!'
@socketio.on('connect')
def handle_connect():
    emit('response', 'Hello Client!')

# 服务器接受到事件message后的响应
@socketio.on('message')
def handle_message(message):
    print('received message: ' + message)
    emit('response', 'Hello Client!')

# 以支持 WebSocket通信的方式启动服务器
if __name__ == '__main__':
    socketio.run(app)  
  • 前端部分:
// 导入Socket.IO客户端库,实现全双工通信
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
<script type="text/javascript" charset="utf-8">
      var socket = io.connect('http://' + document.domain + ':' + location.port);
      socket.on('connect', function() {
          console.log('Connected to the server!');
      });
      socket.on('response', function(message) {
          console.log('received response: ' + message);
          // 处理服务器返回的信息
      });
</script>
  • 启动浏览器后的控制台截图:

在这里插入图片描述

  • 通过定期调用api的方式,从数据库中获取信息
  • 优点:这是三种方式种最简单的一种,只需要新建一张表,其余部分全靠复制已有代码就行
  • 缺点:本质上是ESP8266找服务器要数据,这就导致了俩个问题:一、前端操作到ESP8266有俩个时间间隔:前端更新数据库数据,ESP8266找服务器要数据库的数据。二、首先要知道,调用api也是会占用服务器资源的,结合上面的时间间隔问题,间隔太小的话,服务器任务便会太重,间隔太大的话,使用体验便相当糟糕。
  • 总结
    如果觉得项目能跑就行,使用第三种,如果追求稳定性与合理架构,使用第二种。

二、ESP8266的数据传输方式

在这里插入图片描述

这是一个HTTP POST请求,其分为请求行(第1行),头部(第2,3,4行),必需空出的一行(第5行),请求体(第6行)。接下来介绍如何计算它的数据长度:
在这里插入图片描述
HTTP POST设定每行都需要一个回车换行符(实际上等于俩个字符),在Conten-Length里设定请求体长度,后跟一个空行与请求体数据。
总长度=请求行字符数+头部字符数+回车符与换行符个数+Conten-Length的值(请求体的大小达不到Content-Length的值也没关系,它会自动用空格补齐上传,但最好还是设置为匹配的值)

接下来介绍俩种传输方式在使用时的区别。

  • 普通传输模式:普通传输模式在发送HTTP请求前需要将其打包,发送时要设置数据包长度,只有发送数据到达长度后才会发送。发送成功后会断开与服务器的TCP连接,这时候就需要重新使用AT+CIPSTART连接上服务器,通过AT+CIPSEND=XX开启下一次传输。
  • 透传模式:透传模式的特点是将原始数据直接从一个系统传输到另一个系统,而不需要进行任何更改,同时只能连接至多一个对象。在实际使用时,只需要使用一次AT+CIPSEND=XX进入传输状态,接下来便不会再退出(哪怕你关闭服务器再重启,数据还是能正常发送)。

三、JSON数据的获取与处理

python与js提取JSON数据的方式是不一样的
在这里插入图片描述

// python提取JSON数据的方式
@app.route('/upload', methods=['POST'])
def upload():
    data = request.get_json()
    temperature = data['temperature']
    humidity = data['humidity']
// js提取JSON数据的方式
    function update() {
      $.ajax({
        url: "http://127.0.0.1:5000/download",
        type: "GET",
        dataType: "json",
        success: function (data) {
          humidity=data[0].humidity;  
          temperature =data[0].temperature;
        }
      })
    }

四、Flask服务器的使用体会

Flask服务器使用方便,同时具有许多扩展,用起来相当顺手。但在接受高频率的api调用时,如果api操作是数据库操作的话,很容易出直接瘫痪。而且它的瘫痪是没有报错提示的,调试初期我还不会使用浏览器的控制台查看请求情况,这就使得我十分痛苦。不过,只要能优化一下数据传输方式的话,Flask服务器还是一个相当值得一用的。

五、三种api调用地址的区别

在本地调试过程中,发现前俩种调用会出现跨区域请求错误,第三项却不会出现,设置网络穿透时也出现了类似的问题。以下是三者的区别:

  • http://127.0.0.1:5000/download
    127.0.0.1是回环地址,用于指代当前设备。5000是Flask应用程序默认监听的端口。download是对应的api方法。
  • http://192.168.230.240:5000/download(本机IP)
    192.168.230.240是主机的地址,同时也是内网地址。它和127.0.0.1只是看起来不同,用起来完全没有区别。
  • /upload
    它是相对于当前域名的相对URL。
    由于使用了Ajax技术,所以实际的调用过程与上图是略有不同的,有兴趣的可以自己去了解
    参考上图中的api调用过程。三者的区别就在于第一步,前俩个api都要在网络中寻找服务器,然后与其请求构建TCP连接,而主机IP对于服务器来说是透明的,因此会被识别为跨域请求。而第三个api则没有这个构建TCP连接的过程,直接发送api请求给后台上。
    如此一来,网络穿透的问题也得以解释:设置网络穿透,其既无法通过127.0.0.1来确定本机地址,也无法在公网中访问内网地址192.168.230.240。只能通过直接访问服务器的方法来调用api。

改进方向

  • 本项目中前端是通过api 的方式来调用数据的,调用频率一高,服务器就要寄了,推荐更换为更健壮的服务器或者修改提取数据库内容的方法。
  • arduino ide中的代码,我没有设置状态检查和状态恢复,这使得设备在上传过程中因意外而发生意外而跳出连接后就无法恢复。这不是因为我不想,而是透传模式下连接断开也是没啥提示的,希望有同学可以给出改进后的代码。
  • 前端使用的是BootStrap v3,是比较老的前端框架,其运行依赖于Jquery,其在前端的主要作用为设置布局。有意者可以更换为BootStrap v5乃至其它前端框架,更换后记得修改原有界面的jQuery代码,另外,我还设置了手机端访问时的合理样式,更改框架后要连同这一部分一起修改。
  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值