项目地址:https://github.com/Laity000/SmartBed-Workerman-AngularJS
部分功能展示:
最近开发了一款关于物联网项目的后端管理平台,可以实现对设备的管理,包括设备的连接、区分,状态反馈的推送、记录,对设备的控制等操作(这里的设备是护理床,主要可以实现对姿态的实时记录、控制)。以及,用户端可以实现多种终端多对一设备绑定,监控。
其管理平台的业务特征如下:
这里把项目(后端,前端)中的一些设计思路和开发心得整理记录下来。如果你的业务中也需要设备管理模块的设计开发,或者正好需要上述特征,下面的介绍或许对你有所帮助。当然有错误之处,也请批评指正。
(~ ̄▽ ̄)~
首先我们思考下物联网后端的设计难点。如何选择和设计物理网项目的后端框架呢?
需要什么样的通信方式(协议),决定了使用什么样的服务器框架。
与传统的web服务器不同,我们这里的设备由于需要基于TCP的长连接、实时通信,在后端中我们需要实现socket通信。而用户端采用应用层的协议通信,便于终端的开发。为此,我们需要一款支持长连接的、多协议转化的服务器框架。
其次,根据业务的特征:由于设备端的数据需要实时推送给绑定的用户,用户也需要给绑定的设备发送控制指令。
整个业务场景类似于IM聊天室的感觉,只不过这里的角色不是平等的,具体划分为设备端和用户端(给予不同用户权限)。我们的后端分别需要对设备管理和用户管理。
项目后台的架构图如上所示。采用模块化的方式,包括两个大的模块:
web模块在wm模块(后端)的基础上,便于后续的扩展。(目前还没有设计web模块,用户不需要注册和登录,直接匿名登录的方式连接到wm模块后绑定设备。后面业务逻辑章节有具体说明。)
区别:Workerman是一个通用的socket服务器框架,支持长连接,支持各种协议如HTTP、WebSocket以及自定义协议。而Apache/nginx/php-fpm一般来说只用于开发HTTP协议的Web项目。
我们目前首要的任务是关注设备端的管理,设备与用户的通信交互。也就是物联网后端的选择和设计(设备管理,用户操作的业务)才是项目开发的重中之重。
我们后端选择的是workman框架。前面开题提到的管理平台的业务特征,其实,80%都是建立在其中的GatewayWorker框架之上的(文中用gw框架代替)。
初期调研的时候开始寻找是一些物联网的公有云平台,但是发现大多都是面向企业的(收费滴~~ ⊙︿⊙),不能个性化定制。对于个人开发者来说主要是不开源的,没有学习的机会。
后来了解到了Workerman(传送门),一款纯PHP开发的开源高性能的PHP socket 服务器框架。
其中二次开发的GatewayWorker(传送门)进程模型具有高并发,长连接,支持单播、多播(一对多模式)、广播方式推送消息,支持多协议等特征。
gw框架是为聊天室设计的IM服务端,并且开发文档丰富,上手起来也很快(为了学习后来自己也基于此DIY了一个聊天室DEMO:基于JavaFX的聊天室ChatRoom)
具体的workerman介绍就不在这班门弄斧了,官方的介绍和文档在上面传送门中。总之,GatewayWorker的业务逻辑也适合物联网项目,比如上面提到的GatewayWorker特征都可以用到项目的业务中。
工欲善其事,必先利其器。在介绍如何设计后端之前,先传授下协议大法。
通信协议可以简单解释为双方“说话”的规则,需要考虑的问题:数据的形式,怎么发送数据,数据怎么被对方理解,等等。
由于TCP是基于流的,客户端发送的请求数据是像水流一样流入到服务端,服务端探测到有数据到来后应该检查数据是否是完整的,因为可能只是一个请求的部分数据到达服务端,甚至可能是多个请求连在一起到达服务端。如何判断请求是否全部到达或者从多个连在一起的请求中分离请求,就需要规定一套通讯协议。——workerman
简单来说,我们这里说的协议可以归纳为网络中应用层的协议,是在TCP基础上,为解决网络中数据包粘包问题。当然协议的作用这只是其中一点。我们主要关心的key:
一般通信协议都有现成的技术,根据不同的场景有不同的应用,比如我们熟悉的:
当然有时候需要自定义协议,比如用于单片机不需要这么复杂的:
但是本质上还是在TCP基础上简单实现的。
——>下面具体介绍用户端和设备端两种数据的消息类型。
如果说通信协议是各种终端与服务器沟通的桥梁,那么消息类型就是其中行驶的各类车辆。不同的汽车代表不同的数据,不管是终端还是服务器都需要提前知道其含义(这里就是设计封装/解析消息的业务逻辑)。
只有将数据(指令/反馈)封装成消息类型,数据才有了灵魂。我们需要考虑的key:
首先一条消息要根据需要分为几个字段,比如:①既然要区分消息类型,就要有类型字段,②消息的来源,③有些消息还要携带内容。
其实,封装消息就是相当于关联数组的形式。然后将数组转换成字符串(用户端)或字节流(设备端)发送。
项目中的用法:
有哪些消息类型就是业务逻辑的事情。
再介绍之前首先明确三种***消息的流向***(结合下章的分析效果更佳):
消息类型具体参见(结合下章的分析效果更佳):
建议在思考自己的业务逻辑之前,首先整体阅读下GatewayWorker文档(传送门)!
建议在思考自己的业务逻辑之前,首先整体阅读下GatewayWorker文档(传送门)!
建议在思考自己的业务逻辑之前,首先整体阅读下GatewayWorker文档(传送门)!
好了,现在万事具备,只欠东风。接下来我们可以大干一场,开发出属于自己的需求。当然啦,不同的需求需要不同的业务逻辑。
这里提炼下我们项目中用到的一些业务逻辑和心得体会,避免走弯路,也许对你会有所启发。
首先,我们的后端特征如下所示:
简单的流程图如下所示:
首要解决的问题便是设备连接服务器后的识别、管理,然后用户绑定对应的设备进行交互。
项目中用到的识别号及作用:
具体的逻辑如下:
Gateway::bindUid
(gw框架uid与用户uid不同,原理请查看官方手册)Gateway::unbindUid
结合上文的三种消息流向,我们设计了两种用户权限:
**一个合法的用户可以有观察者权限,但是不一定会有控制者权限。**控制者权限需要授权后获得。
可以在后期的开发中(web开发)加入用户角色的管理,目前用户是直接匿名连接gw框架的。至于控制者权限,需要输入对应设备的密码,服务器才接受该用户对设备的控制。
具体反馈和指令的消息流向是怎么传输的,还需要结合下文的一对多模式来分析。
如果设备和用户只需一对一的绑定,那么开发起来很简单,可以直接在数据库或session中建立用户和设备的映射关系,来实现指令和反馈的传输。
但是,我们项目考虑用户可以实现多种终端监控,那么多个用户都可以直接绑定到同一个设备上,那么用户与设备之间是如何交互的呢?比如,设备发送反馈需要考虑是发送给所有绑定用户,还是发送给一个用户。用户如何通过pid找到绑定的设备id。
下面具体分析之:
//检查用户是否绑定了PID
$boundPID = Gateway::getUidByClientId($user_id);
//设备集session
$bed_sessions = Gateway::getAllClientSessions();
//检查设备是否在线
foreach ($bed_sessions as $temp_client_id => $temp_sessions)
{
if(!empty($temp_sessions['PID']) && $temp_sessions['PID'] == $boundPID)
{
return $temp_client_id;
}
}
Gateway::sendToUid
什么是发布订阅模式:观察者模式和“发布-订阅”模式有区别吗?
具体实现原理详见gw手册中的介绍:这里
应答模式:用户发送一个合法指令给设备,设备解析后必须应答该指令发送反馈给原用户。(不合法的指令服务器会直接拒绝不转给设备,但也会给原用户发送相应的服务器反馈)
由于用户对设备来说是无状态的,设备就不知道发来的消息是哪个用户,无法直接点对点回复。那么怎么实现?
首先前提是我们的设备在收到指令后才发送反馈消息,是典型的点对点中的应答模式。
一种方式是:发送给设备的消息字段中包括用户的uid或id,设备发送反馈消息携带该uid或id,服务器就知道了该发给哪个用户。
另一种方式:我们的设备本质上是资源互斥。在同一时刻只能接收唯一用户的指令,当工作完后再发送相应的反馈。那么可以:①在发送指令时,给设备进程设置sessionGateway::setSession(array('work01_userid'=>xxx)
对应用户的id,②工作完成后释放设备进程中该sessionGateway::updateSession
,③当有其他用户想要操作设备时,首先检查设备进程中该操作的session释放被释放,没有释放则直接反馈正在工作的服务器反馈。
如果你的需求不是互斥的应答模式,选择第一种。我们这用第二种。
上面用户与设备的交互都经过服务器,服务器接收到消息解析后决定怎么处理,发送给哪个、全部用户或哪个设备。但是,如果服务器处理发来的消息不需要转发,而是直接反馈给原设备或原用户(服务器反馈)呢:使用Gateway::sendToCurrentClient
心跳检测在设备与服务器交互中也是不可忽略的一部分。由于设备自定义协议部分没有完善的断开连接检测机制,设备掉电的等情况下,服务器端无法短时间内知道设备的连接情况(长时间后可以通过TCP层知道连接断开)。为此,需要心跳检测的设置。
gw框架中有心跳检测的详细介绍和几种用法:这里
我们项目中是通过设备定时向服务器发送ping数据包,如果超过一定时间没有发送,则认为设备断开。(主要考虑到设备同时接受和发送数据存在丢失的情况,设置优先级)
当后端以守护进程的方式运行时,需要将后端的运行日志输出到文件中保留。
这里采用monolog日志类库(这里)。
按日期生成日志文件。(日志位置:Applications\SmartBed\log)
——>好了,我们的业务逻辑就分析到这里了~~
我们前端的特征:
简单介绍下前端的选择和设计。由于我们的后端已经预留好了用户端的应用层协议(WebSocket)和消息类型(Json的形式),可以支持多种终端的开发:web端,手机端,PC端。
从技术的角度来说以上都可以实现,开发中主要关注几点:
从用户的角度来说,网页版的轻应用(web app)无疑是首选,也是当前物联网前端开发的趋势。
为此,我们web app的搭建选择AngularJS(web后台)和 jQuery WEUI(web前端)。
首先,需要了解下什么是单页面应用。
单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。 浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制
通俗一点说就是指只有一个主页面的应用,浏览器一开始就必须加载所有的html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。
我们项目中,处理websocket的业务逻辑(连接ws,接收/发送数据)都放在js中,在加载首页面时就建立起ws连接。但是,如果使用传统的多页面,每次跳转页面都行需要重新加载js,也就需要重新建立ws连接。
这就是项目中使用单页面应用的原因。其中,AngularJS就是一款优秀的前端JS框架, 使得开发现代的单一页面应用程序变得更加容易。由于自己在前端也是小白一枚,就不做过多介绍了。
主要说下我们项目中选择angularJS的优势在于:
其中,最主要的关注就是业务逻辑的处理部分:
在项目中我们用到了angular-websocket的开源库,结合项目中的业务逻辑,写了一篇详细的用法介绍和心得体会:angular-websocket学习笔记(这里)
Query WeUI 是专为微信公众账号开发而设计的一个简洁而强大的UI库,包含全部WeUI官方的CSS组件,并且额外提供了大量的拓展组件,丰富的组件库可以极大减少前端开发时间。
基本上需要的UI界面和动画插件都有,并且开发起来也易上手。传送门:jQuery WeUI
护理床的项目算是自己的第一次,o( ̄︶ ̄)o。
花了不少的时间,一边学习一边上手开发。从开始的安卓开发:手机局域网内的连接监控(这里),到后端设备管理平台的搭建。
收获:虽然只算的上是一个学习研究的项目,离最终的目标还差的远,其中很多功能也不够完善和稳定。但这期间从后端到前端也学到了很多技术,知道项目开发的一些基本知识,锻炼了撸代码的能力。
不足之处:第一,因为都是自己一个人在折腾,视野和技术的选择会不够好。第二,目前只是做到拿来即用,技术栈的研究不够深入。后面多看看源码和技术细节,知其然知其所以然。
仅此附上项目开发的心路历程。