您的位置:首页 > Web前端 > HTML5

HTML5 WebSocket 应用示例

2012-11-25 20:07 295 查看

大体结构

准备

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

 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     //


13 }

AlarmWebSocket

来看看AlarmWebSocket的实现,这里定义的是一个内部类,实现了接口

org.eclipse.jetty.websocket.WebSocket.OnTextMessage的三个方法:onOpen/onMessage/onClose,这三个方法分别在连接建立,收到客户端消息,关闭连接时回调,如果需要向客户端发送消息,可以通过

Connection#sendMessage(...)方法,消息统一使用JSON格式,下面是具体实现:

 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 }
33 private void sendMessage(AlarmWebSocket client, String action, String message){
34     try {
35         if(message == null || message.isEmpty()){
36             message = "\"\"";
37         }
38         if(action != null){
39             message = "{\"action\":\"" + action + "\", \"data\":" + message + "}";
40         }
41         client.connection.sendMessage(message);
42     } catch (IOException e) {
43         e.printStackTrace();
44     }
45 }

后台配置

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

 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格式),解析后更新到界面,详细的处理函数将稍后介绍

 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 }
13 function onmessage(evt){
14     var data = evt.data;
15     if(!data){
16         return;
17     }
18     data = stringToJson(data);
19     if(!data){
20         return;
21     }
22     


23 }
24 function jsonToString(json){
25     return JSON.stringify(json);
26 }
27 function stringToJson(str){
28     try{
29         str = str.replace(/\'/g, "\"");
30         return JSON.parse(str);
31     }catch(error){
32         console.log(error);
33     }
34 }

 

WebSocket前后台流程

 


业务实现



数据模型

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

 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 }
46   }

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

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());)

 1  public AlarmServlet() {
 2     initDatas();
 3     


 4 }
 5 
 6 public void initDatas() {
 7     int i = 0;
 8     double cx = 350, cy = 230, a = 250, b = 180;
 9     nodes.add(new Node("center", cx, cy));
10     double angle = 0, perAngle = 2 * Math.PI/10;
11     while(i++ < 10){
12         Node node = new Node("node_" + i, cx + a * Math.cos(angle), cy + b * Math.sin(angle));
13         elementMap.put(node.name, node);
14         nodes.add(node);
15         angle += perAngle;
16     }
17     i = 0;
18     while(i++ < 10){
19         Link link = new Link("link_" + i, "center", "node_" + i, 1 + random.nextInt(10));
20         elementMap.put(link.name, link);
21         links.add(link);
22     }
23 }
24 
25 private String loadDatas(){
26     StringBuffer result = new StringBuffer();
27    result.append("{\"nodes\":");
28     listToJSON(nodes, result);
29     result.append(", \"links\":");
30     listToJSON(links, result);
31     result.append(", \"alarms\":");
32     listToJSON(alarms, result);
33     result.append("}");
34     return result.toString();
35 }
36 
37    class AlarmWebSocket implements org.eclipse.jetty.websocket.WebSocket.OnTextMessage
38    {
39            


40     @Override
41     public void onOpen(Connection connect) {
42         this.connection = connect;
43         clients.add(this);
44         sendMessage(this, "reload", loadDatas());
45    }
46            


47    }

初始数据前台展示

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

  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 networkDom = network.getView();
 14             networkDom.style.width = "100%";
 15             networkDom.style.height = "100%";
 16             document.body.appendChild(networkDom);
 17 
 18             window.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             data = 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>

界面效果




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

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

后台代码如下:

 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 }
16 public void sendMessage(String action, String message) {
17     for(AlarmWebSocket client : clients){
18         sendMessage(client, action, message);
19     }
20 }
21 private Random random = new Random();
22 private Data getRandomElement(){
23     if(random.nextBoolean()){
24         return nodes.get(random.nextInt(nodes.size()));
25     }
26     return links.get(random.nextInt(links.size()));
27 }
28 String[] alarmSeverities = new String[]{"Critical", "Major", "Minor", "Warning", "Indeterminate"};
29 private String randomAlarm(){
30     Alarm alarm = new Alarm(getRandomElement().name, alarmSeverities[random.nextInt(alarmSeverities.length)]);
31     alarms.add(alarm);
32     return alarm.toJSON();
33 }
34 

前台代码:

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

 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     


17 }

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

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

前台代码:

 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 });

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

后台代码 

 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     }
43 }

完整代码

代码:webSocketDemo 

结构: 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: