`

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/

后台部分

      后台使用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前后台流程


    业务实现

    数据模型

          本例需要用到三种业务类型,节点,连线和告警,后台分别提供了实现类,并定义了名称,位置,线宽等属性,此外还提供了导出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[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>
    
     

    界面效果

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

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

    后台代码如下:

    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

    结构:

     

     

    分享到:
    评论

    相关推荐

      Spring4+Hibernate4+HTML5WebSocket简单应用示例

      NULL 博文链接:https://quarterlifeforjava.iteye.com/blog/2163576

      SpringBoot2.0开发WebSocket应用完整示例

      java及html源码 博文链接:https://wallimn.iteye.com/blog/2425666

      jetty-websocket-example:使用嵌入式Jetty设置WebSocket服务的示例代码

      使用Java SE的Websocket的示例与其使用Java EE及其附带的所有功能,不如使用Java EE,而不是WebSocket的一种较小的实现方式,可能是针对独立程序或嵌入式应用程序的。 该示例将显示使码头服务器运行,为websocket...

      HTML5移动应用开发入门经典

      《HTML5移动应用开发入门经典》总共分为24章,以示例的方式对如何使用HTML5及相关技术进行移动应用开发做了全面而细致的介绍。《HTML5移动应用开发入门经典》首先讲解了HTML5的起源以及它为什么适用于移动设备,然后...

      [HTML5移动应用开发入门经典].(美)凯瑞恩.扫描版

      《HTML5移动应用开发入门经典》总共分为24章,以示例的方式对如何使用HTML5及相关技术进行移动应用开发做了全面而细致的介绍。《HTML5移动应用开发入门经典》首先讲解了HTML5的起源以及它为什么适用于移动设备,然后...

      HTML5高级程序设计(中文版 附HTML5参考手册)

      为读者清晰解读了HTML5规范的缘由、发展和现状,全面展示了如何使用WebSocket、Geolocation、WebStorage、Carlvas及音频视频等前所未有的新特性构建先进的Web应用。并以大量的示例涵盖全部HTML5APl。此外,还介绍了...

      HTML5高级程序设计

      从第2 章起,分别围绕构建令人神往的富web 应用,逐一讨论了html5 的canvas、geolocation 、communication、websocket、forms、web workers、storage 等api 的使用, 辅以直观明了的客户端和服务器端示例代码,让...

      HTML5高级程序设计,第2版(Pro HTML5 Programming,2nd Edition)

      从第2 章起,分别围绕构建令人神往的富web 应用,逐一讨论了html5 的canvas、geolocation 、communication、websocket、forms、web workers、storage 等api 的使用, 辅以直观明了的客户端和服务器端示例代码,让...

      完整版《HTML5高级程序设计》5

      为读者清晰解读了HTML5规范的缘由、发展和现状,全面展示了如何使用 WebSocket、Geolocation、Web Storage、Canvas及音频视频等前所未有的新特性构建先进的Web应用,并以大量的示例涵盖全部HTML5 API。此外,还介绍...

      完整版《HTML5高级程序设计》2

      为读者清晰解读了HTML5规范的缘由、发展和现状,全面展示了如何使用 WebSocket、Geolocation、Web Storage、Canvas及音频视频等前所未有的新特性构建先进的Web应用,并以大量的示例涵盖全部HTML5 API。此外,还介绍...

      调用电子口岸IC卡/UKEY控件WebSocket方式进行数据加签的示例

      本示例通过一个纯html+js的页面文件,演示了以WebSocket方式调用电子口岸IC卡/UKEY控件,对数据进行加签。 应用场景包括:海关业务报文申报、海关总署2018年179号公告对接加签要求等。

      server-sent-events-and-websockets:带有服务器发送事件和WebSocket的示例Node.js应用

      这是基于Node.js后端和HTML / JS前端的服务器发送事件和WebSocket的简单演示。 (SSE)是一种规范,用于通过普通的HTTP对Web前端应用程序实施服务器端推送事件。 与最好的对比是 ,它通过自定义协议提供全双工消息...

      完整版《HTML5高级程序设计》4

      为读者清晰解读了HTML5规范的缘由、发展和现状,全面展示了如何使用 WebSocket、Geolocation、Web Storage、Canvas及音频视频等前所未有的新特性构建先进的Web应用,并以大量的示例涵盖全部HTML5 API。此外,还介绍...

      HTML5 video 事件应用示例

      1、获取视频时间长度 当视频载入video后,使用 onloadedmetadata 事件获取视频的时间长度。 复制代码代码如下: video.onloadedmetadata = function () { var vLength = video.duration; console.log(vLength);...

      HTML5程序设计(中文第2版)

      为读者清晰解读了HTML5规范的缘由、发展和现状,全面展示了如何使用WebSocket、Geolocation、Web Storage、Canvas及音频视频等前所未有的新特性构建先进的Web应用,并以大量的示例涵盖全部HTML5 API。此外,还介绍了...

      Demo_Socket_Python:Websocket的使用示例(Python3和Html的源代码)

      Websocket的使用示例(Python3和Html的源代码) 2,更新信息 开发者:沙振宇(沙师弟专栏)创建时间:2020-1-14最后一次更新时间:2020-1-14 CSDN博客名称:Python开发之Websocket的使用示例CSDN博客地址: : 3,...

      html网页制作手册.zip

      《HTML5开发手册》采用菜谱的方式为HTML5新手以及有一定经验的用户提供了一些专家级的建议和经过测试的代码示例,可以帮助其顺利创建高质量的HTML5应用。《HTML5开发手册》总共分为15章,先后讲解了HTML5中新增的...

      《HTML5高级程序设计》(源代码)

      从第2章起,分别围绕构建令人神往的富Web应用,逐一讨论了HTML5的Canvas、Geolocation、Communication、WebSocket、Forms、Web Workers、Storage等APⅡ的使用,辅以直观明了的客户端和服务器端示例代码,让开发人员...

    Global site tag (gtag.js) - Google Analytics