TWaver - 專注UI技術(shù)

          http://twaver.servasoft.com/
          posts - 171, comments - 191, trackbacks - 0, articles - 2
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          HTML5 WebSocket 應(yīng)用示例

          Posted on 2012-05-03 11:52 TWaver 閱讀(4842) 評論(2)  編輯  收藏
          繼續(xù)上一篇《HTML5 WebSocket 技術(shù)介紹》的內(nèi)容,本篇將以示例說明WebSocket的使用,這個示例同時結(jié)合了TWaver HTML5的使用,場景如下:后臺提供拓?fù)鋽?shù)據(jù),并以JSON格式通過WebSocket推送到各個客戶端,客戶端獲取到拓?fù)湫畔⒑螅ㄟ^TWaver HTML5的Network組件呈現(xiàn)于界面,客戶端可以操作網(wǎng)元,操作結(jié)果通過WebSocket提交到后臺,后臺服務(wù)器更新并通知所有的客戶端刷新界面,此外后臺服務(wù)器端還會不斷產(chǎn)生告警,并推送到各個客戶端更新界面。

          大體結(jié)構(gòu)


          準(zhǔn)備

          需要用到j(luò)etty和twaver html5,可自行下載:
          jetty :http://www.eclipse.org/jetty/
          twaver html5

          jetty目錄結(jié)構(gòu)

          jetty下載解壓后是下面的結(jié)構(gòu),運行start.jar(java -jar start.jar)啟動jetty服務(wù)器,web項目可以發(fā)布在/webapps目錄中,比如本例目錄/webapps/alarm/ 

          后臺部分

          后臺使用jetty,其使用風(fēng)格延續(xù)servlet的api,可以按Serlvet的使用和部署方式來使用,本例中主要用到三個類
        1. WebSocketServlet - WebSocket服務(wù)類
        2. WebSocket - 對應(yīng)一個WebSocket客戶端
        3. WebSocket.Conllection - 代表一個WebSocket連接
        4. WebSocketServlet

          全名為org.eclipse.jetty.websocket.WebSocketServlet,用于提供websocket服務(wù),繼承于HttpServlet,增加了方法public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol),在客戶端第一次請求websocket連接時會調(diào)用該方法,如果允許建立連接,則返回一個WebSocket實例對象,否則返回null。
          本例中將定義一個AlarmServlet類,繼承于WebSocketServlet,并實現(xiàn)doWebSocketConnect方法,返回一個AlarmWebSocket實例,代表一個客戶端。

          AlarmServlet

          AlarmWebSocket中有個clients屬性,用于維持一個客戶端(AlarmWebSocket)列表,當(dāng)與客戶端建立連接時,會將客戶端對應(yīng)的AlarmWebSocket實例添加到這個列表,當(dāng)客戶端關(guān)閉時,則從這個列表中刪除。
           1 public class AlarmServlet extends org.eclipse.jetty.websocket.WebSocketServlet {
           2     private final Set<AlarmWebSocket> clients;//保存客戶端列表
           3 
           4     public AlarmServlet() {
           5         initDatas();//初始化數(shù)據(jù)
           6     }
           7 
           8     @Override
           9     public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
          10         return new AlarmWebSocket();
          11     }
          12     //
          13 }

          AlarmWebSocket

          來看看AlarmWebSocket的實現(xiàn),這里定義的是一個內(nèi)部類,實現(xiàn)了接口
          org.eclipse.jetty.websocket.WebSocket.OnTextMessage的三個方法:onOpen/onMessage/onClose,這三個方法分別在連接建立,收到客戶端消息,關(guān)閉連接時回調(diào),如果需要向客戶端發(fā)送消息,可以通過
          Connection#sendMessage(...)方法,消息統(tǒng)一使用JSON格式,下面是具體實現(xiàn):
           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數(shù)據(jù)將被解析成map對象
          21         Map map = (Map)json;
          22         //通過消息中的信息,更新后臺數(shù)據(jù)模型
          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相同,這里設(shè)置的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>

          前臺部分

          看看前臺的大體結(jié)構(gòu),創(chuàng)建websocket連接,監(jiān)聽相關(guān)事件,比如onmessage事件,可以收到后臺發(fā)送的信息(JSON格式),解析后更新到界面,詳細(xì)的處理函數(shù)將稍后介紹
           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前后臺流程

          業(yè)務(wù)實現(xiàn)


          數(shù)據(jù)模型

          本例需要用到三種業(yè)務(wù)類型,節(jié)點,連線和告警,后臺分別提供了實現(xiàn)類,并定義了名稱,位置,線寬等屬性,此外還提供了導(dǎo)出json數(shù)據(jù)的功能。
           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   }
          后臺維持三個數(shù)據(jù)集合,分別存放節(jié)點,連線和告警信息,此外elementMap以節(jié)點名稱為鍵,便于節(jié)點的快速查找 
          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>();

          初始化數(shù)據(jù)

          在servlet構(gòu)造中,我們添加了些模擬數(shù)據(jù),在客戶端建立連接時(AlarmWebSocket#onOpen(Connection connection)),后臺將節(jié)點連線和告警信息以JSON格式發(fā)送到前臺(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    }

          初始數(shù)據(jù)前臺展示

          初始數(shù)據(jù)通過后臺的sendMessage(...)方法推送到客戶端,客戶端可以在onmessage回調(diào)函數(shù)中收到,本例我們使用twaver html5組件來展示這些信息。TWaver組件的使用流程一如既往,先作數(shù)據(jù)轉(zhuǎn)換,將JSON數(shù)據(jù)轉(zhuǎn)換成TWaver的網(wǎng)元類型,然后填充到ElementBox數(shù)據(jù)容器,最后關(guān)聯(lián)上Network拓?fù)鋱D組件,代碼如下:
            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>

          界面效果

          ?

          后臺推送告警,前臺實時更新

          增加后臺推送告警的代碼,這里我們在后臺起了一個定時器,每隔兩秒產(chǎn)生一條隨機告警,或者清除所有告警,并將信息推送給所有的客戶端
          后臺代碼如下:
           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 

          前臺代碼:
          客戶端接收到消息后,需要對應(yīng)的處理,增加對"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 }

          客戶端拖拽節(jié)點,同步到其他客戶端

          最后增加拖拽同步,監(jiān)聽network網(wǎng)元拖拽監(jiān)聽,在網(wǎng)元拖拽放手后,將節(jié)點位置信息發(fā)送給后臺
          前臺代碼:
           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 });
          后臺接收到節(jié)點位置信息后,首先更新后臺數(shù)據(jù)(節(jié)點位置),然后將消息轉(zhuǎn)發(fā)給其他客戶端,這樣各個客戶端就實現(xiàn)了同步操作
          后臺代碼
           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

          結(jié)構(gòu): 

          評論

          # re: HTML5 WebSocket 應(yīng)用示例  回復(fù)  更多評論   

          2012-05-07 12:09 by 條碼打印機
          大體結(jié)構(gòu)還是不錯的

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 曲靖市| 比如县| 高邑县| 武山县| 临沧市| 京山县| 永嘉县| 筠连县| 静安区| 太湖县| 三亚市| 台北县| 石河子市| 赤壁市| 平果县| 石阡县| 都匀市| 蓬溪县| 湘阴县| 汨罗市| 开原市| 西城区| 兴仁县| 桐柏县| 高平市| 阳曲县| 榕江县| 阜宁县| 贺州市| 浮山县| 武穴市| 腾冲县| 神池县| 孟津县| 海口市| 安塞县| 威宁| 探索| 珲春市| 桓台县| 永定县|