设为首页 收藏本站
查看: 868|回复: 0

[经验分享] HTML5 WebSocket 应用示例

[复制链接]

尚未签到

发表于 2017-2-28 08:36:06 | 显示全部楼层 |阅读模式
  继续上一篇《HTML5 WebSocket 技术介绍》的内容,本篇将以示例说明WebSocket的使用,这个示例同时结合了TWaver HTML5的使用,场景如下:后台提供拓扑数据,并以JSON格式通过WebSocket推送到各个客户端,客户端获取到拓扑信息后,通过TWaver HTML5的Network组件呈现于界面,客户端可以操作网元,操作结果通过WebSocket提交到后台,后台服务器更新并通知所有的客户端刷新界面,此外后台服务器端还会不断产生告警,并推送到各个客户端更新界面。

大体结构


准备
  需要用到jetty和twaver html5,可自行下载:
  jetty :http://www.eclipse.org/jetty/
twaver html5

jetty目录结构
  jetty下载解压后是下面的结构,运行start.jar(java -jar start.jar)启动jetty服务器,web项目可以发布在/webapps目录中,比如本例目录/webapps/alarm/
DSC0000.png


后台部分
  后台使用jetty,其使用风格延续servlet的api,可以按Serlvet的使用和部署方式来使用,本例中主要用到三个类


  • WebSocketServlet – WebSocket服务类
  • WebSocket – 对应一个WebSocket客户端
  • WebSocket.Conllection – 代表一个WebSocket连接
  WebSocketServlet
  全名为org.eclipse.jetty.websocket.WebSocketServlet,用于提供websocket服务,继承于HttpServlet,增加了方法public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol),在客户端第一次请求websocket连接时会调用该方法,如果允许建立连接,则返回一个WebSocket实例对象,否则返回null。
  本例中将定义一个AlarmServlet类,继承于WebSocketServlet,并实现doWebSocketConnect方法,返回一个AlarmWebSocket实例,代表一个客户端。

AlarmServlet
  AlarmWebSocket中有个clients属性,用于维持一个客户端(AlarmWebSocket)列表,当与客户端建立连接时,会将客户端对应的AlarmWebSocket实例添加到这个列表,当客户端关闭时,则从这个列表中删除。



public class AlarmServlet extends org.eclipse.jetty.websocket.WebSocketServlet {
private final Set<AlarmWebSocket> clients;//保存客户端列表
public AlarmServlet() {
initDatas();//初始化数据
}
@Override
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
return new AlarmWebSocket();
}
//...
}

AlarmWebSocket
  来看看AlarmWebSocket的实现,这里定义的是一个内部类,实现了接口org.eclipse.jetty.websocket.WebSocket.OnTextMessage的三个方法:onOpen/onMessage/onClose,这三个方法分别在连接建立,收到客户端消息,关闭连接时回调,如果需要向客户端发送消息,可以通过Connection#sendMessage(…)方法,消息统一使用JSON格式,下面是具体实现:



   class AlarmWebSocket implements org.eclipse.jetty.websocket.WebSocket.OnTextMessage
{
WebSocket.Connection connection;
@Override
public void onOpen(Connection connect) {
this.connection = connect;
clients.add(this);
sendMessage(this, "reload", loadDatas());
}
@Override
public void onClose(int code, String message) {
clients.remove(this);
}
@Override
public void onMessage(String message) {
Object json = JSON.parse(message);
if(!(json instanceof Map)){
return;
}
//解析消息,jetty中json数据将被解析成map对象
Map map = (Map)json;
//通过消息中的信息,更新后台数据模型
...
//处理消息,通知到其他各个客户端
for(AlarmWebSocket client : clients){
if(this.equals(client)){
continue;
}
sendMessage(client, null, message);
}
}
}
private void sendMessage(AlarmWebSocket client, String action, String message){
try {
if(message == null || message.isEmpty()){
message = "\"\"";
}
if(action != null){
message = "{\"action\":\"" + action + "\", \"data\":" + message + "}";
}
client.connection.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}

后台配置
  后台配置如serlvet相同,这里设置的url名称为/alarmServer



<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="false"
version="3.0">
<servlet>
<servlet-name>alarmServlet</servlet-name>
<servlet-class>web.AlarmServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>alarmServlet</servlet-name>
<url-pattern>/alarmServer</url-pattern>
</servlet-mapping>
</web-app>

前台部分
  看看前台的大体结构,创建websocket连接,监听相关事件,比如onmessage事件,可以收到后台发送的信息(JSON格式),解析后更新到界面,详细的处理函数将稍后介绍



function init(){
window.WebSocket = window.WebSocket || window.MozWebSocket;
if (!window.WebSocket){
alert("WebSocket not supported by this browser");
return;
}
var websocket = new WebSocket("ws://127.0.0.1:8080/alarm/alarmServer");
websocket.onopen = onopen;
websocket.onclose = onclose;
websocket.onmessage = onmessage;
...
}
function onmessage(evt){
var data = evt.data;
if(!data){
return;
}
data = stringToJson(data);
if(!data){
return;
}
...
}
function jsonToString(json){
return JSON.stringify(json);
}
function stringToJson(str){
try{
str = str.replace(/\'/g, "\"");
return JSON.parse(str);
}catch(error){
console.log(error);
}
}

WebSocket前后台流程
DSC0001.png


业务实现


数据模型
  本例需要用到三种业务类型,节点,连线和告警,后台分别提供了实现类,并定义了名称,位置,线宽等属性,此外还提供了导出json数据的功能。



  interface IJSON{
String toJSON();
}
class Data{
String name;
public Data(String name){
this.name = name;
}
}
class Node extends Data implements IJSON{
public Node(String name, double x, double y){
super(name);
this.x = x;
this.y = y;
}
double x, y;
public String toJSON(){
return "{\"name\":\"" + name + "\", \"x\":\"" + x + "\",\"y\":\"" + y + "\"}";
}
}
class Link extends Data implements IJSON{
public Link(String name, String from, String to, int width){
super(name);
this.from =from;
this.to = to;
this.width = width;
}
String from;
String to;
int width = 2;
public String toJSON(){
return "{\"name\":\"" + name + "\", \"from\":\"" + from + "\", \"to\":\"" + to + "\", \"width\":\"" + width + "\"}";
}
}
class Alarm implements IJSON{
public Alarm(String elementName, String alarmSeverity){
this.alarmSeverity = alarmSeverity;
this.elementName = elementName;
}
String alarmSeverity;
String elementName;
@Override
public String toJSON() {
return "{\"elementName\": \"" + elementName + "\", \"alarmSeverity\": \"" + alarmSeverity + "\"}";
}
}

  后台维持三个数据集合,分别存放节点,连线和告警信息,此外elementMap以节点名称为键,便于节点的快速查找  



Map<String, Data> elementMap = new HashMap<String, AlarmServlet.Data>();
List<Node> nodes = new ArrayList<AlarmServlet.Node>();
List<Link> links = new ArrayList<AlarmServlet.Link>();
List<Alarm> alarms = new ArrayList<AlarmServlet.Alarm>();

初始化数据
  在servlet构造中,我们添加了些模拟数据,在客户端建立连接时(AlarmWebSocket#onOpen(Connection connection)),后台将节点连线和告警信息以JSON格式发送到前台(sendMessage(this, “reload”, loadDatas());)



public AlarmServlet() {
initDatas();
...
}
public void initDatas() {
int i = 0;
double cx = 350, cy = 230, a = 250, b = 180;
nodes.add(new Node("center", cx, cy));
double angle = 0, perAngle = 2 * Math.PI/10;
while(i++ < 10){
Node node = new Node("node_" + i, cx + a * Math.cos(angle), cy + b * Math.sin(angle));
elementMap.put(node.name, node);
nodes.add(node);
angle += perAngle;
}
i = 0;
while(i++ < 10){
Link link = new Link("link_" + i, "center", "node_" + i, 1 + random.nextInt(10));
elementMap.put(link.name, link);
links.add(link);
}
}
private String loadDatas(){
StringBuffer result = new StringBuffer();
result.append("{\"nodes\":");
listToJSON(nodes, result);
result.append(", \"links\":");
listToJSON(links, result);
result.append(", \"alarms\":");
listToJSON(alarms, result);
result.append("}");
return result.toString();
}
class AlarmWebSocket implements org.eclipse.jetty.websocket.WebSocket.OnTextMessage
{
...
@Override
public void onOpen(Connection connect) {
this.connection = connect;
clients.add(this);
sendMessage(this, "reload", loadDatas());
}
...
}

初始数据前台展示
  初始数据通过后台的sendMessage(…)方法推送到客户端,客户端可以在onmessage回调函数中收到,本例我们使用twaver html5组件来展示这些信息。TWaver组件的使用流程一如既往,先作数据转换,将JSON数据转换成TWaver的网元类型,然后填充到ElementBox数据容器,最后关联上Network拓扑图组件,代码如下:   



<!DOCTYPE html>
<html>
<head>
<title>TWaver HTML5 Demo - Alarm</title>
<script type="text/javascript" src="./twaver.js"></script>
<script type="text/javascript">
var box, network, nameFinder;
function init(){
network = new twaver.network.Network();
box = network.getElementBox();
nameFinder = new twaver.QuickFinder(box, "name");
var networkDom = network.getView();
networkDom.style.width = "100%";
networkDom.style.height = "100%";
document.body.appendChild(networkDom);
window.WebSocket = window.WebSocket || window.MozWebSocket;
if (!window.WebSocket){
alert("WebSocket not supported by this browser");
return;
}
var websocket = new WebSocket("ws://127.0.0.1:8080/alarm/alarmServer");
...
websocket.onmessage = onmessage;
}
...
function onmessage(evt){
var data = evt.data;
if(!data){
return;
}
data = stringToJson(data);
if(!data){
return;
}
var action = data.action;
if(!action){
return;
}
if(action == "alarm.clear"){
box.getAlarmBox().clear();
return;
}
data = data.data;
if(!data){
return;
}
if(action == "reload"){
reloadDatas(data);
return;
}
if(action == "alarm.add"){
newAlarm(data)
return;
}
if(action == "node.move"){
modeMove(data);
return;
}
}
function reloadDatas(datas){
box.clear();
var nodes = datas.nodes;
var links = datas.links;
var alarms = datas.alarms;
for(var i=0,l=nodes.length; i < l; i++){
var data = nodes;
var node = new twaver.Node();
node.setName(data.name);
node.setCenterLocation(parseFloat(data.x), parseFloat(data.y));
box.add(node);
}
for(var i=0,l=links.length; i < l; i++){
var data = links;
var from = findFirst(data.from);
var to = findFirst(data.to);
var link = new twaver.Link(from, to);
link.setName(data.name);
link.setStyle("link.width", parseInt(data.width));
box.add(link);
}
var alarmBox = box.getAlarmBox();
for(var i=0,l=alarms.length; i < l; i++){
newAlarm(alarms);
}
}
function findFirst(name){
return nameFinder.findFirst(name);
}
function newAlarm(data){
var element = findFirst(data.elementName);
var alarmSeverity = twaver.AlarmSeverity.getByName(data.alarmSeverity);
if(!element || !alarmSeverity){
return;
}
addAlarm(element.getId(), alarmSeverity, box.getAlarmBox());
}
function addAlarm(elementID,alarmSeverity,alarmBox){
var alarm = new twaver.Alarm(null, elementID,alarmSeverity);
alarmBox.add(alarm);
}
function modeMove(datas){
for(var i=0,l=datas.length; i<l; i++){
var data = datas;
var node = findFirst(data.name);
if(node){
var x = parseFloat(data.x);
var y = parseFloat(data.y);
node.setCenterLocation(x, y);
}
}
}
...
</script>
</head>
<body></body>
</html>


界面效果
DSC0002.png


后台推送告警,前台实时更新
  增加后台推送告警的代码,这里我们在后台起了一个定时器,每隔两秒产生一条随机告警,或者清除所有告警,并将信息推送给所有的客户端
  后台代码如下:



public AlarmServlet() {
...
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if(random.nextInt(10) == 9){
alarms.clear();
sendMessage ("alarm.clear", "");
return;
}
sendMessage("alarm.add", randomAlarm());
}
}, 0, 2000);
}
public void sendMessage(String action, String message) {
for(AlarmWebSocket client : clients){
sendMessage(client, action, message);
}
}
private Random random = new Random();
private Data getRandomElement(){
if(random.nextBoolean()){
return nodes.get(random.nextInt(nodes.size()));
}
return links.get(random.nextInt(links.size()));
}
String[] alarmSeverities = new String[]{"Critical", "Major", "Minor", "Warning", "Indeterminate"};
private String randomAlarm(){
Alarm alarm = new Alarm(getRandomElement().name, alarmSeverities[random.nextInt(alarmSeverities.length)]);
alarms.add(alarm);
return alarm.toJSON();
}

  前台代码:
  客户端接收到消息后,需要对应的处理,增加对”alarm.clear”和”alarm.add”的处理,这样告警就能实时更新了



function onmessage(evt){
...
if(action == "alarm.clear"){
box.getAlarmBox().clear();
return;
}
data = data.data;
if(!data){
return;
}
...
if(action == "alarm.add"){
newAlarm(data)
return;
}
...
}

客户端拖拽节点,同步到其他客户端
  最后增加拖拽同步,监听network网元拖拽监听,在网元拖拽放手后,将节点位置信息发送给后台
  前台代码:



network.addInteractionListener(function(evt){
var moveEnd = "MoveEnd";
if(evt.kind.substr(-moveEnd.length) == moveEnd){
var nodes = [];
var selection = box.getSelectionModel().getSelection();
selection.forEach(function(element){
if(element instanceof twaver.Node){
var xy = element.getCenterLocation();
nodes.push({name: element.getName(), x: xy.x, y: xy.y});
}
});
websocket.send(jsonToString({action: "node.move", data: nodes}));
}
});

  后台接收到节点位置信息后,首先更新后台数据(节点位置),然后将消息转发给其他客户端,这样各个客户端就实现了同步操作
后台代码:  



   class AlarmWebSocket implements org.eclipse.jetty.websocket.WebSocket.OnTextMessage
{
...
@Override
public void onMessage(String message) {
Object json = JSON.parse(message);
if(!(json instanceof Map)){
return;
}
Map map = (Map)json;
Object action = map.get("action");
Object data = map.get("data");
if("node.move".equals(action)){
if(!(data instanceof Object[])){
return;
}
Object[] nodes = (Object[])data;
for(Object nodeData : nodes){
if(!(nodeData instanceof Map) || !((Map)nodeData).containsKey("name") || !((Map)nodeData).containsKey("x") || !((Map)nodeData).containsKey("y")){
continue;
}
String name = ((Map)nodeData).get("name").toString();
Data element = elementMap.get(name);
if(!(element instanceof Node)){
continue;
}
double x = Double.parseDouble(((Map)nodeData).get("x").toString());
double y = Double.parseDouble(((Map)nodeData).get("y").toString());
((Node)element).x = x;
((Node)element).y = y;
}
}else{
return;
}
for(AlarmWebSocket client : clients){
if(this.equals(client)){
continue;
}
sendMessage(client, null, message);
}
}
}

完整代码
  代码:webSocketDemo
  结构:
DSC0003.png

  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-348166-1-1.html 上篇帖子: maven 常用插件汇总 下篇帖子: 利用maven构建一个spring mvc的helloworld实例
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表