本文讲解4.6版jxTMS中如何使用webSocket,整个系列的文章请查看:4.6版升级内容
docker版本的使用,请查看:docker版jxTMS使用指南
4.0版jxTMS的说明,请查看:4.0版升级内容
4.2版jxTMS的说明,请查看:4.2版升级内容
4.4版jxTMS的说明,请查看:4.4版升级内容
4.6版jxTMS的核心是解决分布式问题,但之所以要考虑分布式处理,诱因就是一个高频数据处理问题。这个问题需要对采集到的数据进行分析后建立一个模型。
由于对此问题还没有充分的认识,所以想先做一个简单的数据分析并显示到页面上,好先建立起一个感性的认识。
过程比较简单,就是抓数据然后做成一个四分位的图来显示一下,这个用matplotlib来做很容易。但问题在于数据频次要求比较高,用javascript轮询来显示,0.1秒一次就已经开始丢数据了【报ns_binding_aborted错误】,频率再高一点根本就不显示了。
所以就想到使用webSocket来做。干起来!
实现起来并不难,关键是要无缝的嵌入到我们之前的web体系中。所以整个解决方案是:
1、增加了两个和websocket相关的uri:
/openWS:用来打开webSocket
/ws:用来处理webSocket的数据交换
2、webSocket功能是动态的,通过命令行开关【–webSocket】进行控制,即命令行中给出了开关【–webSocket】,就注册并使能上面的两个路由,否则webSocket功能是关闭的
3、webSocket功能是可定制、可扩展的
即webSocket功能可以灵活的嵌入到任何一个页面中,使用纯javascript编写前端,可以非常容易的就为任何一个页面提供webSocket功能。
后端可支持多个具有webSocket功能的不同页面的不同用户的同时访问,增加后端对应功能也非常简单。
我们想实现在浏览器中显示一个图片,该图片是由后台实时刷新的,每接收通过webSocket发送过来的一帧图像数据,我们就动态的刷新到该图片中。
1、定义一个需要启用webSocket功能的页面,我们直接上代码:
def webPage():
'''
test
'''
pass
上述代码定义了一个webPage函数,其__doc__是一个用html描述的web页面,该页面的核心就是【{{WSJSCript}}】,除了这个之外的都是用户根据自己的需要进行定义就好,然后在需要启用webSocke功能前增加此语句即可。
jxTMS启用webSocke功能后,对所有访问路由【/openWS】的请求,都会直接返回上述定义的html,并将其中的{{WSJSCript}}替换为dsWebSocket类的定义。
dsWebSocket类的对象在初始时有5个参数:
name:该页面名,也是该webSocke功能的名字,前后端要统一起来,否则会无法显示
cid:用来区分不同的用户id
wsRecv:接收webSocke数据的回调函数
wsOpen:webSocke打开事件,同时回送所创建的dsWebSocket对象,可以调用其send函数来发送数据
wsClosed:webSocke关闭事件
2、将此页面注册到系统中:
from jx.web import openWebSocketHandler
openWebSocketHandler.register('p1',webPage.__doc__)
register有两个参数:
page:该参数就是创建dsWebSocket对象时应给出的name,两者必须一致,否则无法正确访问到
html描述,我们是用一个函数的__doc__来存放的
注册后,我们只要访问:http://127.0.0.1:10028/openWS?page=p1。就会显示我们在webPage函数的__doc__中定义的web页面了。
3、定义处理webSocke数据的函数
def recvFromWS(event,ws,cid,data):
if event == 'open':
print(f'recvFromWS open {cid}:{data}')
t=threading.Thread(target=dispImg,name='dispImgThread',args=[ws])
t.start()
else:
print(type(data))
print(f'recvFromWS {cid}:{data}')
该函数有四个参数:
event:webSocke触发的事件,有两个:open和recv,前者是连接打开,后者是接收到webSocke发送的数据
ws:后端的webSocke管理对象,主要用来发送数据
cid:就是上面在创建dsWebSocket对象时给出的cid,用来区分不同的用户,具体含义由用户自己定义与设置
data:前端发送过来的数据,都是字符串类型,所以如果前端发送的是json对象,需要自己转换;如果是open事件,则为None
大家看到在open时,我们启动了一个线程并把ws作为参数送入。这是由于python的webSocke使用了asyncio,其需要启动事件循环来分发异步事件,而我们的主线程并没有启用这样的事件循环,所以我们单独启动一个线程来执行webSocke的数据发送工作,并在此线程中启动asyncio所需要的事件循环:
def dispImg(ws):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
while True:
image = getImage()
#print(f'ws send data:{len(image)}')
ws.write_message(image, binary=True)
time.sleep(0.005)
dispImg在启动asyncio所需要的事件循环之外,主要就是以5毫秒为间隔循环来生成数据对象并发送到web前端。
getImage的代码我们也贴一下吧:
import matplotlib.pyplot as plt
y_list = []
def getImage():
#用随机数填充数列,只保留最新的20个数据
y_list.append(random.randint(1, 100))
if len(y_list) > 20:
y_list.pop(0)
#清空当前图形
plt.clf()
fig, ax = plt.subplots()
#用y_list中的数据生成四分位图
ax.boxplot(y_list)
#设置x坐标
ax.set_xticklabels(['test123'], rotation=90)
#设置y轴的范围
ax.set_ylim(1,100)
#将生成的四分位图以png图片格式保存到内存的字节流中
memdata = io.BytesIO()
plt.savefig(memdata, format='png')
image = memdata.getvalue()
#我们是5毫秒生成一张图片,速度太快,手动清除以避免内存消耗过快而有溢出
plt.close()
return image
上述代码,都在【app/module/web_test.py】文件中。
注:需要pip install matplotlib
4、注册webSocke数据的接收函数
webSocketHandler.register('p1',recvFromWS)
大家看到,我们又使用了【p1】注册了recvFromWS函数,表明我们前端所使用的p1页面将和recvFromWS函数进行数据的交互。
这样一来,我们用一个共同的名字【应用名,这里是p1】将下面三个东东绑到了一起:
页面网址,通过uri【/openWS?page=应用名】来访问
前端页面,通过openWebSocketHandler.register(应用名,htmlDoc)进行注册
后端数据接收函数,通过webSocketHandler.register(应用名,处理函数)进行注册
完成上述工作后,我们需要通过命令行启用webSocket功能:
python3 main_slave.py --web --module --webSocket
其中:
--web开关是启动web服务,默认端口是10028
--module开关是加载module目录下的各个模块,因为我们的web_test.py文件就放在module目录下
--webSocket开关是启用webSocket功能,即自动注册【/openWS、/ws】两个webSocket相关的路由
webSocket开关单独启用是不起作用的,必须配合–web开关一起使用。
参考资料:
jxTMS设计思想
jxTMS编程手册
下面的系列文章讲述了如何用jxTMS开发一个实用的业务功能:
如何用jxTMS开发一个功能
下面的系列文章讲述了jxTMS的一些基本开发能力:
jxTMS的HelloWorld