MyException - 我的异常网
当前位置:我的异常网» Web前端 » HTML5 WebSocket 使用示例

HTML5 WebSocket 使用示例

www.MyException.Cn  网友分享于:2015-08-26  浏览:169次
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

    结构:

     

     

    文章评论

    60个开发者不容错过的免费资源库
    60个开发者不容错过的免费资源库
    要嫁就嫁程序猿—钱多话少死的早
    要嫁就嫁程序猿—钱多话少死的早
    “肮脏的”IT工作排行榜
    “肮脏的”IT工作排行榜
    写给自己也写给你 自己到底该何去何从
    写给自己也写给你 自己到底该何去何从
    2013年中国软件开发者薪资调查报告
    2013年中国软件开发者薪资调查报告
    总结2014中国互联网十大段子
    总结2014中国互联网十大段子
    初级 vs 高级开发者 哪个性价比更高?
    初级 vs 高级开发者 哪个性价比更高?
    团队中“技术大拿”并非越多越好
    团队中“技术大拿”并非越多越好
    十大编程算法助程序员走上高手之路
    十大编程算法助程序员走上高手之路
    Web开发者需具备的8个好习惯
    Web开发者需具备的8个好习惯
    Google伦敦新总部 犹如星级庄园
    Google伦敦新总部 犹如星级庄园
    科技史上最臭名昭著的13大罪犯
    科技史上最臭名昭著的13大罪犯
    那些争议最大的编程观点
    那些争议最大的编程观点
    不懂技术不要对懂技术的人说这很容易实现
    不懂技术不要对懂技术的人说这很容易实现
    什么才是优秀的用户界面设计
    什么才是优秀的用户界面设计
    10个帮程序员减压放松的网站
    10个帮程序员减压放松的网站
    看13位CEO、创始人和高管如何提高工作效率
    看13位CEO、创始人和高管如何提高工作效率
    我是如何打败拖延症的
    我是如何打败拖延症的
    当下全球最炙手可热的八位少年创业者
    当下全球最炙手可热的八位少年创业者
    中美印日四国程序员比较
    中美印日四国程序员比较
    程序员必看的十大电影
    程序员必看的十大电影
    做程序猿的老婆应该注意的一些事情
    做程序猿的老婆应该注意的一些事情
    为什么程序员都是夜猫子
    为什么程序员都是夜猫子
    如何成为一名黑客
    如何成为一名黑客
    程序员的一天:一寸光阴一寸金
    程序员的一天:一寸光阴一寸金
    代码女神横空出世
    代码女神横空出世
     程序员的样子
    程序员的样子
    程序猿的崛起——Growth Hacker
    程序猿的崛起——Growth Hacker
    聊聊HTTPS和SSL/TLS协议
    聊聊HTTPS和SSL/TLS协议
    Java程序员必看电影
    Java程序员必看电影
    程序员眼里IE浏览器是什么样的
    程序员眼里IE浏览器是什么样的
    程序员都该阅读的书
    程序员都该阅读的书
    2013年美国开发者薪资调查报告
    2013年美国开发者薪资调查报告
    程序员周末都喜欢做什么?
    程序员周末都喜欢做什么?
    程序员的鄙视链
    程序员的鄙视链
    “懒”出效率是程序员的美德
    “懒”出效率是程序员的美德
    5款最佳正则表达式编辑调试器
    5款最佳正则表达式编辑调试器
    漫画:程序员的工作
    漫画:程序员的工作
    鲜为人知的编程真相
    鲜为人知的编程真相
    一个程序员的时间管理
    一个程序员的时间管理
    我的丈夫是个程序员
    我的丈夫是个程序员
    程序员最害怕的5件事 你中招了吗?
    程序员最害怕的5件事 你中招了吗?
    老程序员的下场
    老程序员的下场
    如何区分一个程序员是“老手“还是“新手“?
    如何区分一个程序员是“老手“还是“新手“?
    为啥Android手机总会越用越慢?
    为啥Android手机总会越用越慢?
    Web开发人员为什么越来越懒了?
    Web开发人员为什么越来越懒了?
    编程语言是女人
    编程语言是女人
    旅行,写作,编程
    旅行,写作,编程
    程序员和编码员之间的区别
    程序员和编码员之间的区别
    软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有