【编程实践/嵌入式比赛】嵌入式比赛学习记录(一):TCP服务器和web界面的建立

0. 前言

最近找同学参加了嵌入式比赛,为了让自己的简历不显得一穷二白,可惜我本人是计算机专业的学生,因此大部分的工作是上位机开发,可能也会搞一下WIFI模块。
由于在此过程中还是学到了一些东西,因此我打算以博客的形式记录之,以便后续浏览,也希望大家可以从我的博客中有所收获。
本人争取按时更新。

1. 本次任务介绍

目前接到的需求是上位机使用Web界面(最早的设想是GUI),将下位机传来的数据接收(使用TCP传输,后续可能改为UDP),并做处理后放到web网页上。因此我需要的工作是:
1.TCP server的建立(上位机做服务器,下位机做客户端)
2. Web界面的实现
3. TCP server向web server发送数据。

考虑到本人贫瘠的编程能力和web开发的需求,因此我打算使用Python开发。

TCP server使用socket库实现,web界面使用flask实现,通信则用requests由TCP server向web server的指定URL发送POST请求实现。

以下我将会介绍具体内容

2. TCP server的建立

Python中,可以使用socket库构建TCP server。
本人使用的是socketserver模块,其实也是基于socket的,使用这个模块的好处是可以更加方便的建立TCP server,甚至省去了创建时的绑定IP端口,调用accept阻塞的方法。

调用时的方法如下:

# 建立TCP server
def start_TCP(host,port):
    myserver=socketserver.ThreadingTCPServer((host,port),MyHandler)
    print("You have start server,address is: {}:{}".format(host,port))
    #持续服务
    myserver.serve_forever()

我们注意到,在构造类ThreadingTCPServer时的第二个参数是MyHandler,这个是自定义的类,继承了socketserver.BaseRequestHandler类,需要重写handle函数,具体如下:

class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            #接收数据,处理
            data=self.request.recv(1024)
            try:
                data=data.decode("gbk")
            except:
                #主要是防止编码错误问题
                data="cannot show normally!"
            #稍作包装
            object={"data":time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())+" "+data}
            #没有接收到数据,不向web server转发
            if not data:
                break
            else:
                #本来打算做成线程间通信的,后来一想是web server,直接发post请求好了
                print("Got: {}".format(data))
                self.request.send("You have successfully sent data!".encode("ascii"))
                with requests.post("http://192.168.71.1:5000/",json=object) as r:
                    pass

具体的处理过程是:

  1. 调用self.requests.recv,接收数据,参数为接收长度
  2. 对数据进行处理,可以调用self.request.send向客户端回送消息

至于向web server转发的内容,后文会介绍。

3. Flask web的建立

flask是一个开源web框架,学起来非常简单,只要有一定的web基础(甚至是常识)就能学会。
具体的代码如下:

from flask import *
import threading

app=Flask(__name__)
#一些全局变量
messages=[]
length=0

#主页面
@app.route("/",methods=["GET","POST"])
def index_page():
    global messages
    #接收TCP server发送的数据
    data=request.get_json()
    #若有数据,则相应操作,这里是加入到全局列表中
    if data is not None:
        messages.append(data["data"])
    #渲染模板
    return render_template("index0.html",messages=messages)

#ajax会向此路由发送post请求轮询,用于异步更新路由
@app.route("/get_data",methods=['GET','POST'])
def get_data():
    #发现有新数据加入的时候,要求前端进行相应动作,例如更新路由
    global length
    if(length<len(messages)):
        length=len(messages)
        return "flash"
    return ""

其中前端代码如下
首先是index0.html,因为是学习部分,就放了几个数据

<html>
    <head>

    </head>
    <body>
        <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
        <script type="text/javascript" src="{{url_for('static', filename = 'js/update.js')}}"></script>
        <h2>历史信息</h2>
        {% for each in messages %}
        <p>{{each}}</p><br>
        {% endfor %}
    </body>

</html>

这是比较简单的flask模板的编写,这里不再进行叙述。
然后是用到的JavaScript代码。很抱歉,本人对js并不熟悉,这些代码也是从网上找+自己改的,因此不能给出具体的解释。

var script=document.createElement("script");  
script.type="text/javascript";  
script.src="jquery.js";  
//ajax轮询,每100ms调用一次函数,即ajax请求
setInterval(
    function(){
        $.ajax({
            url:"/get_data",
            async:true,
            type:'post',    //貌似post才行,我很疑惑
            timeout:10000,
            success:function(data){
                if(data=="flash"){
                    //your operation
                    window.location.reload();//刷新路由,也可以是其他操作
                }
            },
            error:function(xhr,type){
                window.alert("error!");
            }
        })
    },100
)

4. TCP server 向web server通信

这里就用到了一行,即requests.post请求
并且前面也已经展示了

# 本实验中,web server的ip和端口为192.168.71.1:5000
with requests.post("http://192.168.71.1:5000/",json=object) as r:
	pass

5. 总程序

总程序用来启动web server和TCP server,由于两个server都是持久服务,因此在主进程中启动一个就会陷入死循环,不能启动另一个,因此我将这两个服务器启动放到了两个线程上,并且设置了不同的端口号防止冲突。
(其实这里我一直陷入困惑,因为按照我浅显的计网知识,我觉得两个server应该在两个进程上,可放到两个线程上貌似也可以,反而是多进程失败了……后续有时间会探求一下)

### index.py
from flask import *
import threading
import socketserver
import time
# web服务器部分
from app_views import *
#TCP服务器部分
from lib.MyServer import *



def start_TCP(host,port):
    myserver=socketserver.ThreadingTCPServer((host,port),MyHandler)
    print("You have start server,address is: {}:{}".format(host,port))
    myserver.serve_forever()

def start_flask(host,port):
    print("you have start flask server,address is: {}:{}".format(host,port))
    app.run(host=host,port=port)

def main():
    th1=threading.Thread(target=start_flask,args=("192.168.71.1",5000))
    th2=threading.Thread(target=start_TCP,args=("192.168.71.1",5001))
    th1.start()
    th2.start()

if __name__=="__main__":
    main()

6.验收

启动上述代码,打开网络调试助手,向192,168.71.1:5001发送数据,可以看到在前端已正常显示,证明实验成功。

在这里插入图片描述
在这里插入图片描述

7. 可改进的地方

  1. 可不可以直接向web server发送数据?我不好说,因为感觉还是要建立TCP连接才行
  2. 有没有其他转交方式?可以线程间共享变量,使用信号量控制发送接收关系,但可能很麻烦
  3. 前端更新数据除了ajax轮询还有没有更好的方式?可不可以实现真正的异步通讯,以免不必要的负载?

本篇文章到此,感谢收看!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值