读书人

HTML5 WebSocket 使用示例

发布时间: 2012-09-28 00:03:35 作者: rapoo

HTML5 WebSocket 应用示例

? ? ?继续上一篇《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/

HTML5 WebSocket 使用示例

后台部分

? ? ? 后台使用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();//初始化数据}@Overridepublic 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;@Overridepublic void onOpen(Connection connect) {this.connection = connect;clients.add(this);sendMessage(this, "reload", loadDatas());}@Overridepublic void onClose(int code, String message) {clients.remove(this);}@Overridepublic 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前后台流程

    ?
    HTML5 WebSocket 使用示例

    业务实现

    数据模型

    ? ? ? 本例需要用到三种业务类型,节点,连线和告警,后台分别提供了实现类,并定义了名称,位置,线宽等属性,此外还提供了导出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;@Overridepublic 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   {   ...@Overridepublic 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[i];                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[i];                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[i]);            }        }        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[i];                var node = findFirst(data.name);                if(node){                    var x = parseFloat(data.x);                    var y = parseFloat(data.y);                    node.setCenterLocation(x, y);                }            }        }        ...    </script></head><body onload="init()" style="margin:0;"></body></html>
    ?

    界面效果

    ?HTML5 WebSocket 使用示例

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

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

    后台代码如下:

    public AlarmServlet() {...Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic 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   {   ...@Overridepublic 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

    结构:

    HTML5 WebSocket 使用示例

    ?

    ?

  • 读书人网 >Web前端

    热点推荐