WebSocket使用例子

http://www.html5china.com/HTML5features/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/

后台部分

后台使用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实例添加到这个列表,当客户端关闭时,则从这个列表中删除。

JavaScript Code 复制内容到剪贴板
  1. public class AlarmServlet extends org.eclipse.jetty.websocket.WebSocketServlet { 
  2.     private final Set<AlarmWebSocket> clients;//保存客户端列表 
  3.   
  4.     public AlarmServlet() { 
  5.         initDatas();//初始化数据 
  6.     } 
  7.   
  8.     @Override 
  9.     public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { 
  10.         return new AlarmWebSocket(); 
  11.     } 
  12.     //... 
AlarmWebSocket

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

Java Code 复制内容到剪贴板
  1. class AlarmWebSocket implements org.eclipse.jetty.websocket.WebSocket.OnTextMessage 
  2.    { 
  3.     WebSocket.Connection connection; 
  4.     @Override 
  5.     public void onOpen(Connection connect) { 
  6.         this.connection = connect; 
  7.         clients.add(this); 
  8.         sendMessage(this, "reload", loadDatas()); 
  9.     } 
  10.     @Override 
  11.     public void onClose(int code, String message) { 
  12.         clients.remove(this); 
  13.     } 
  14.     @Override 
  15.     public void onMessage(String message) { 
  16.         Object json = JSON.parse(message); 
  17.         if(!(json instanceof Map)){ 
  18.             return
  19.         } 
  20.         //解析消息,jetty中json数据将被解析成map对象 
  21.         Map map = (Map)json; 
  22.         //通过消息中的信息,更新后台数据模型 
  23.         ... 
  24.         //处理消息,通知到其他各个客户端 
  25.         for(AlarmWebSocket client : clients){ 
  26.             if(this.equals(client)){ 
  27.                 continue
  28.             } 
  29.             sendMessage(client, null, message); 
  30.         } 
  31.     } 
  32. private void sendMessage(AlarmWebSocket client, String action, String message){ 
  33.     try
  34.         if(message == null || message.isEmpty()){ 
  35.             message = "\"\""
  36.         } 
  37.         if(action != null){ 
  38.             message = "{\"action\":\"" + action + "\", \"data\":" + message + "}"
  39.         } 
  40.         client.connection.sendMessage(message); 
  41.     } catch (IOException e) { 
  42.         e.printStackTrace(); 
  43.     } 
后台配置

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

XML/HTML Code 复制内容到剪贴板
  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <web-app 
  3.     xmlns="http://java.sun.com/xml/ns/javaee" 
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  5.     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
  6.     metadata-complete="false" 
  7.     version="3.0"> 
  8.     <servlet> 
  9.         <servlet-name>alarmServlet</servlet-name> 
  10.         <servlet-class>web.AlarmServlet</servlet-class> 
  11.         <load-on-startup>1</load-on-startup> 
  12.     </servlet> 
  13.   
  14.     <servlet-mapping> 
  15.         <servlet-name>alarmServlet</servlet-name> 
  16.         <url-pattern>/alarmServer</url-pattern> 
  17.     </servlet-mapping> 
  18. </web-app> 

前台部分

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

JavaScript Code 复制内容到剪贴板
  1. function init(){ 
  2.     window.WebSocket = window.WebSocket || window.MozWebSocket; 
  3.     if (!window.WebSocket){ 
  4.         alert("WebSocket not supported by this browser"); 
  5.         return
  6.     } 
  7.     var websocket = new WebSocket("ws://127.0.0.1:8080/alarm/alarmServer"); 
  8.     websocket.onopen = onopen; 
  9.     websocket.onclose = onclose; 
  10.     websocket.onmessage = onmessage; 
  11.     ... 
  12. function onmessage(evt){ 
  13.     var data = evt.data; 
  14.     if(!data){ 
  15.         return
  16.     } 
  17.     data = stringToJson(data); 
  18.     if(!data){ 
  19.         return
  20.     } 
  21.     ... 
  22. function jsonToString(json){ 
  23.     return JSON.stringify(json); 
  24. function stringToJson(str){ 
  25.     try
  26.         str = str.replace(/\'/g, "\""); 
  27.         return JSON.parse(str); 
  28.     }catch(error){ 
  29.         console.log(error); 
  30.     } 

WebSocket前后台流程

业务实现


数据模型

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

Java Code 复制内容到剪贴板
  1. interface IJSON{ 
  2.     String toJSON(); 
  3.   } 
  4.   class Data{ 
  5.     String name; 
  6.     public Data(String name){ 
  7.         this.name = name; 
  8.     } 
  9.   } 
  10.   class Node extends Data implements IJSON{ 
  11.     public Node(String name, double x, double y){ 
  12.         super(name); 
  13.         this.x = x; 
  14.         this.y = y; 
  15.     } 
  16.     double x, y; 
  17.     public String toJSON(){ 
  18.         return "{\"name\":\"" + name + "\", \"x\":\"" + x + "\",\"y\":\"" + y + "\"}"
  19.     } 
  20.   } 
  21.   class Link extends Data implements IJSON{ 
  22.     public Link(String name, String from, String to, int width){ 
  23.         super(name); 
  24.         this.from =from; 
  25.         this.to = to; 
  26.         this.width = width; 
  27.     } 
  28.     String from; 
  29.     String to; 
  30.     int width = 2
  31.     public String toJSON(){ 
  32.         return "{\"name\":\"" + name + "\", \"from\":\"" + from + "\", \"to\":\"" + to + "\", \"width\":\"" + width + "\"}"
  33.     } 
  34.   } 
  35.   class Alarm implements IJSON{ 
  36.     public Alarm(String elementName, String alarmSeverity){ 
  37.         this.alarmSeverity = alarmSeverity; 
  38.         this.elementName = elementName; 
  39.     } 
  40.     String alarmSeverity; 
  41.     String elementName; 
  42. @Override 
  43. public String toJSON() { 
  44.     return "{\"elementName\": \"" + elementName + "\", \"alarmSeverity\": \"" + alarmSeverity + "\"}"
  45.   } 

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

Java Code 复制内容到剪贴板
  1. Map<String, Data> elementMap = new HashMap<String, AlarmServlet.Data>(); 
  2. List<Node> nodes = new ArrayList<AlarmServlet.Node>(); 
  3. List<Link> links = new ArrayList<AlarmServlet.Link>(); 
  4. List<Alarm> alarms = new ArrayList<AlarmServlet.Alarm>(); 
初始化数据

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

Java Code 复制内容到剪贴板
  1. public AlarmServlet() { 
  2.     initDatas(); 
  3.     ... 
  4.   
  5. public void initDatas() { 
  6.     int i = 0
  7.     double cx = 350, cy = 230, a = 250, b = 180
  8.     nodes.add(new Node("center", cx, cy)); 
  9.     double angle = 0, perAngle = 2 * Math.PI/10
  10.     while(i++ < 10){ 
  11.         Node node = new Node("node_" + i, cx + a * Math.cos(angle), cy + b * Math.sin(angle)); 
  12.         elementMap.put(node.name, node); 
  13.         nodes.add(node); 
  14.         angle += perAngle; 
  15.     } 
  16.     i = 0
  17.     while(i++ < 10){ 
  18.         Link link = new Link("link_" + i, "center", "node_" + i, 1 + random.nextInt(10)); 
  19.         elementMap.put(link.name, link); 
  20.         links.add(link); 
  21.     } 
  22.   
  23. private String loadDatas(){ 
  24.     StringBuffer result = new StringBuffer(); 
  25.     result.append("{\"nodes\":"); 
  26.     listToJSON(nodes, result); 
  27.     result.append(", \"links\":"); 
  28.     listToJSON(links, result); 
  29.     result.append(", \"alarms\":"); 
  30.     listToJSON(alarms, result); 
  31.     result.append("}"); 
  32.     return result.toString(); 
  33.   
  34.    class AlarmWebSocket implements org.eclipse.jetty.websocket.WebSocket.OnTextMessage 
  35.    { 
  36.         ... 
  37.     @Override 
  38.     public void onOpen(Connection connect) { 
  39.         this.connection = connect; 
  40.         clients.add(this); 
  41.         sendMessage(this, "reload", loadDatas()); 
  42.     } 
  43.         ... 
  44.    } 
初始数据前台展示

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

XML/HTML Code 复制内容到剪贴板
  1. <!DOCTYPE html> 
  2. <html> 
  3. <head> 
  4.     <title>TWaver HTML5 Demo - Alarm</title> 
  5.     <script type="text/javascript" src="./twaver.js"></script> 
  6.     <script type="text/javascript"> 
  7.         var box, network, nameFinder; 
  8.         function init(){ 
  9.             network = new twaver.network.Network(); 
  10.             box = network.getElementBox(); 
  11.             nameFinder = new twaver.QuickFinder(box, "name"); 
  12.   
  13.             var networknetworkDom = network.getView(); 
  14.             networkDom.style.width = "100%"
  15.             networkDom.style.height = "100%"
  16.             document.body.appendChild(networkDom); 
  17.   
  18.             windowwindow.WebSocket = window.WebSocket || window.MozWebSocket; 
  19.             if (!window.WebSocket){ 
  20.                 alert("WebSocket not supported by this browser"); 
  21.                 return; 
  22.             } 
  23.             var websocket = new WebSocket("ws://127.0.0.1:8080/alarm/alarmServer"); 
  24.             ... 
  25.             websocket.onmessage = onmessage; 
  26.   
  27.         } 
  28.         ... 
  29.         function onmessage(evt){ 
  30.             var data = evt.data; 
  31.             if(!data){ 
  32.                 return; 
  33.             } 
  34.             data = stringToJson(data); 
  35.             if(!data){ 
  36.                 return; 
  37.             } 
  38.             var action = data.action; 
  39.             if(!action){ 
  40.                 return; 
  41.             } 
  42.             if(action == "alarm.clear"){ 
  43.                 box.getAlarmBox().clear(); 
  44.                 return; 
  45.             } 
  46.             datadata = data.data; 
  47.             if(!data){ 
  48.                 return; 
  49.             } 
  50.             if(action == "reload"){ 
  51.                 reloadDatas(data); 
  52.                 return; 
  53.             } 
  54.             if(action == "alarm.add"){ 
  55.                 newAlarm(data) 
  56.                 return; 
  57.             } 
  58.             if(action == "node.move"){ 
  59.                 modeMove(data); 
  60.                 return; 
  61.             } 
  62.         } 
  63.   
  64.         function reloadDatas(datas){ 
  65.             box.clear(); 
  66.             var nodes = datas.nodes; 
  67.             var links = datas.links; 
  68.             var alarms = datas.alarms; 
  69.   
  70.             for(var i=0,l=nodes.length; i < l; i++){ 
  71.                 var data = nodes[i]; 
  72.                 var node = new twaver.Node(); 
  73.                 node.setName(data.name); 
  74.                 node.setCenterLocation(parseFloat(data.x), parseFloat(data.y)); 
  75.                 box.add(node); 
  76.             } 
  77.   
  78.             for(var i=0,l=links.length; i < l; i++){ 
  79.                 var data = links[i]; 
  80.                 var from = findFirst(data.from); 
  81.                 var to = findFirst(data.to); 
  82.                 var link = new twaver.Link(from, to); 
  83.                 link.setName(data.name); 
  84.                 link.setStyle("link.width", parseInt(data.width)); 
  85.                 box.add(link); 
  86.             } 
  87.   
  88.             var alarmBox = box.getAlarmBox(); 
  89.             for(var i=0,l=alarms.length; i < l; i++){ 
  90.                 newAlarm(alarms[i]); 
  91.             } 
  92.         } 
  93.         function findFirst(name){ 
  94.             return nameFinder.findFirst(name); 
  95.         } 
  96.         function newAlarm(data){ 
  97.             var element = findFirst(data.elementName); 
  98.             var alarmSeverity = twaver.AlarmSeverity.getByName(data.alarmSeverity); 
  99.             if(!element || !alarmSeverity){ 
  100.                 return; 
  101.             } 
  102.             addAlarm(element.getId(), alarmSeverity, box.getAlarmBox()); 
  103.         } 
  104.         function addAlarm(elementID,alarmSeverity,alarmBox){ 
  105.             var alarm = new twaver.Alarm(null, elementID,alarmSeverity); 
  106.             alarmBox.add(alarm); 
  107.         } 
  108.         function modeMove(datas){ 
  109.             for(var i=0,l=datas.length; i<l; i++){ 
  110.                 var data = datas[i]; 
  111.                 var node = findFirst(data.name); 
  112.                 if(node){ 
  113.                     var x = parseFloat(data.x); 
  114.                     var y = parseFloat(data.y); 
  115.                     node.setCenterLocation(x, y); 
  116.                 } 
  117.             } 
  118.         } 
  119.         ... 
  120.     </script> 
  121. </head> 
  122. <body onload="init()" style="margin:0;"></body> 
  123. </html> 

  

界面效果

  

后台推送告警,前台实时更新

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

后台代码如下:

Java Code 复制内容到剪贴板
  1. public AlarmServlet() { 
  2.     ... 
  3.     Timer timer = new Timer(); 
  4.     timer.schedule(new TimerTask() { 
  5.         @Override 
  6.         public void run() { 
  7.             if(random.nextInt(10) == 9){ 
  8.                 alarms.clear(); 
  9.                 sendMessage ("alarm.clear", ""); 
  10.                 return
  11.             } 
  12.             sendMessage("alarm.add", randomAlarm()); 
  13.         } 
  14.     }, 0, 2000); 
  15. public void sendMessage(String action, String message) { 
  16.     for(AlarmWebSocket client : clients){ 
  17.         sendMessage(client, action, message); 
  18.     } 
  19. private Random random = new Random(); 
  20. private Data getRandomElement(){ 
  21.     if(random.nextBoolean()){ 
  22.         return nodes.get(random.nextInt(nodes.size())); 
  23.     } 
  24.     return links.get(random.nextInt(links.size())); 
  25. String[] alarmSeverities = new String[]{"Critical", "Major", "Minor", "Warning", "Indeterminate"}; 
  26. private String randomAlarm(){ 
  27.     Alarm alarm = new Alarm(getRandomElement().name, alarmSeverities[random.nextInt(alarmSeverities.length)]); 
  28.     alarms.add(alarm); 
  29.     return alarm.toJSON(); 

前台代码:

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

JavaScript Code 复制内容到剪贴板
  1. function onmessage(evt){ 
  2.     ... 
  3.     if(action == "alarm.clear"){ 
  4.         box.getAlarmBox().clear(); 
  5.         return
  6.     } 
  7.     data = data.data; 
  8.     if(!data){ 
  9.         return
  10.     } 
  11.     ... 
  12.     if(action == "alarm.add"){ 
  13.         newAlarm(data) 
  14.         return
  15.     } 
  16.     ... 

客户端拖拽节点,同步到其他客户端

最后增加拖拽同步,监听network网元拖拽监听,在网元拖拽放手后,将节点位置信息发送给后台

前台代码:

JavaScript Code 复制内容到剪贴板
  1. network.addInteractionListener(function(evt){ 
  2.     var moveEnd = "MoveEnd"
  3.     if(evt.kind.substr(-moveEnd.length) == moveEnd){ 
  4.         var nodes = []; 
  5.         var selection = box.getSelectionModel().getSelection(); 
  6.         selection.forEach(function(element){ 
  7.             if(element instanceof twaver.Node){ 
  8.                 var xy = element.getCenterLocation(); 
  9.                 nodes.push({name: element.getName(), x: xy.x, y: xy.y}); 
  10.             } 
  11.         }); 
  12.         websocket.send(jsonToString({action: "node.move", data: nodes})); 
  13.     } 
  14. }); 

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

Java Code 复制内容到剪贴板
  1. class AlarmWebSocket implements org.eclipse.jetty.websocket.WebSocket.OnTextMessage 
  2.    { 
  3.         ... 
  4.     @Override 
  5.     public void onMessage(String message) { 
  6.         Object json = JSON.parse(message); 
  7.         if(!(json instanceof Map)){ 
  8.             return
  9.         } 
  10.         Map map = (Map)json; 
  11.         Object action = map.get("action"); 
  12.         Object data = map.get("data"); 
  13.         if("node.move".equals(action)){ 
  14.             if(!(data instanceof Object[])){ 
  15.                 return
  16.             } 
  17.             Object[] nodes = (Object[])data; 
  18.             for(Object nodeData : nodes){ 
  19.                 if(!(nodeData instanceof Map) || !((Map)nodeData).containsKey("name") || !((Map)nodeData).containsKey("x") || !((Map)nodeData).containsKey("y")){ 
  20.                     continue
  21.                 } 
  22.                 String name = ((Map)nodeData).get("name").toString(); 
  23.                 Data element = elementMap.get(name); 
  24.                 if(!(element instanceof Node)){ 
  25.                     continue
  26.                 } 
  27.                 double x = Double.parseDouble(((Map)nodeData).get("x").toString()); 
  28.                 double y = Double.parseDouble(((Map)nodeData).get("y").toString()); 
  29.                 ((Node)element).x = x; 
  30.                 ((Node)element).y = y; 
  31.             } 
  32.   
  33.         }else
  34.             return
  35.         } 
  36.         for(AlarmWebSocket client : clients){ 
  37.             if(this.equals(client)){ 
  38.                 continue
  39.             } 
  40.             sendMessage(client, null, message); 
  41.         } 
  42.     } 

完整代码

代码:webSocketDemo

结构:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值