Java实现websocket 的两种方法

原创:

锐洋智能 博客文  链接: 点击打开链接


孤傲苍狼  博客文 链接:点击打开链接

依据两者整理所得

一.WebSocket简单介绍

  随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。

  我们知道,传统的HTTP协议是无状态的,每次请求(request)都要由客户端(如 浏览器)主动发起,服务端进行处理后返回response结果,而服务端很难主动向客户端发送数据;这种客户端是主动方,服务端是被动方的传统Web模式 对于信息变化不频繁的Web应用来说造成的麻烦较小,而对于涉及实时信息的Web应用却带来了很大的不便,如带有即时通信、实时数据、订阅推送等功能的应 用。在WebSocket规范提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折衷的解决方法:轮询(polling)Comet技术。其实后者本质上也是一种轮询,只不过有所改进。

  轮询是最原始的实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。

  Comet技术又可以分为长轮询流技术长轮询改进了上述的轮询技术,减小了无用的请求。它会为某些数据设定过期时间,当数据过期后才会向服务端发送请求;这种机制适合数据的改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活;这样的话,服务端就可以通过这条长连接主动将数据发送给客户端;流技术在大并发环境下,可能会考验到服务端的性能。

  这两种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;它们的每一次请求、应答,都浪费了一定流量在相同的头部信息上,并且开发复杂度也较大。

  伴随着HTML5推出的WebSocket,真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。WebSocket的工作流程是这 样的:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术小 了很多。本文不详细地介绍WebSocket规范,主要介绍下WebSocket在Java Web中的实现。

  JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持 WebSocket,从7.0.47开始支持JSR-356,下面的Demo代码也是需要部署在Tomcat7.0.47以上的版本才能运行。

二.WebSocket示例

2.1.Tomcat实现websocket方法

新建JavaWeb测试项目


在pom.xml中添加Jar包依赖

 <dependency>
         <groupId>javax</groupId>
         <artifactId>javaee-api</artifactId>
         <version>7.0</version>
         <scope>provided</scope>
 </dependency>

2.2.spring整合websocket方法

spring整合,此方式基于spring mvc框架

WebSocketConfig.java

这个类是配置类,所以需要在spring mvc配置文件中加入对这个类的扫描,第一个addHandler是对正常连接的配置,第二个是如果浏览器不支持websocket,使用socketjs模拟websocket的连接。

package com.websocket;  
  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.socket.config.annotation.EnableWebSocket;  
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;  
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;  
import org.springframework.web.socket.handler.TextWebSocketHandler;  
  
@Configuration  
@EnableWebSocket  
public class WebSocketConfig implements WebSocketConfigurer {  
    @Override  
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {  
        registry.addHandler(chatMessageHandler(),"/webSocketServer").addInterceptors(new ChatHandshakeInterceptor());  
        registry.addHandler(chatMessageHandler(), "/sockjs/webSocketServer").addInterceptors(new ChatHandshakeInterceptor()).withSockJS();  
    }  
   
    @Bean  
    public TextWebSocketHandler chatMessageHandler(){  
        return new ChatMessageHandler();  
    }  
  
}  

ChatHandshakeInterceptor.java

这个类的作用就是在连接成功前和成功后增加一些额外的功能,Constants.java类是一个工具类,两个常量。

package com.websocket;  
  
import java.util.Map;  
import org.apache.shiro.SecurityUtils;  
import org.springframework.http.server.ServerHttpRequest;  
import org.springframework.http.server.ServerHttpResponse;  
import org.springframework.web.socket.WebSocketHandler;  
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;  
  
public class ChatHandshakeInterceptor extends HttpSessionHandshakeInterceptor {  
  
    @Override  
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,  
            Map<String, Object> attributes) throws Exception {  
        System.out.println("Before Handshake");  
        /* 
         * if (request instanceof ServletServerHttpRequest) { 
         * ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) 
         * request; HttpSession session = 
         * servletRequest.getServletRequest().getSession(false); if (session != 
         * null) { //使用userName区分WebSocketHandler,以便定向发送消息 String userName = 
         * (String) session.getAttribute(Constants.SESSION_USERNAME); if 
         * (userName==null) { userName="default-system"; } 
         * attributes.put(Constants.WEBSOCKET_USERNAME,userName); 
         *  
         * } } 
         */  
  
        //使用userName区分WebSocketHandler,以便定向发送消息(使用shiro获取session,或是使用上面的方式)  
        String userName = (String) SecurityUtils.getSubject().getSession().getAttribute(Constants.SESSION_USERNAME);  
        if (userName == null) {  
            userName = "default-system";  
        }  
        attributes.put(Constants.WEBSOCKET_USERNAME, userName);  
  
        return super.beforeHandshake(request, response, wsHandler, attributes);  
    }  
  
    @Override  
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,  
            Exception ex) {  
        System.out.println("After Handshake");  
        super.afterHandshake(request, response, wsHandler, ex);  
    }  
  
}  


ChatMessageHandler.java

这个类是对消息的一些处理,比如是发给一个人,还是发给所有人,并且前端连接时触发的一些动作

package com.websocket;  
  
import java.io.IOException;  
import java.util.ArrayList;  
import org.apache.log4j.Logger;  
import org.springframework.web.socket.CloseStatus;  
import org.springframework.web.socket.TextMessage;  
import org.springframework.web.socket.WebSocketSession;  
import org.springframework.web.socket.handler.TextWebSocketHandler;  
  
public class ChatMessageHandler extends TextWebSocketHandler {  
  
    private static final ArrayList<WebSocketSession> users;// 这个会出现性能问题,最好用Map来存储,key用userid  
    private static Logger logger = Logger.getLogger(ChatMessageHandler.class);  
  
    static {  
        users = new ArrayList<WebSocketSession>();  
    }  
  
    /** 
     * 连接成功时候,会触发UI上onopen方法 
     */  
    @Override  
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {  
        System.out.println("connect to the websocket success......");  
        users.add(session);  
        // 这块会实现自己业务,比如,当用户登录后,会把离线消息推送给用户  
        // TextMessage returnMessage = new TextMessage("你将收到的离线");  
        // session.sendMessage(returnMessage);  
    }  
  
    /** 
     * 在UI在用js调用websocket.send()时候,会调用该方法 
     */  
    @Override  
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {  
        sendMessageToUsers(message);  
        //super.handleTextMessage(session, message);  
    }  
  
    /** 
     * 给某个用户发送消息 
     * 
     * @param userName 
     * @param message 
     */  
    public void sendMessageToUser(String userName, TextMessage message) {  
        for (WebSocketSession user : users) {  
            if (user.getAttributes().get(Constants.WEBSOCKET_USERNAME).equals(userName)) {  
                try {  
                    if (user.isOpen()) {  
                        user.sendMessage(message);  
                    }  
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  
                break;  
            }  
        }  
    }  
  
    /** 
     * 给所有在线用户发送消息 
     * 
     * @param message 
     */  
    public void sendMessageToUsers(TextMessage message) {  
        for (WebSocketSession user : users) {  
            try {  
                if (user.isOpen()) {  
                    user.sendMessage(message);  
                }  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
  
    @Override  
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {  
        if (session.isOpen()) {  
            session.close();  
        }  
        logger.debug("websocket connection closed......");  
        users.remove(session);  
    }  
  
    @Override  
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {  
        logger.debug("websocket connection closed......");  
        users.remove(session);  
    }  
  
    @Override  
    public boolean supportsPartialMessages() {  
        return false;  
    }  
  
}  

spring-mvc.xml

正常的配置文件,同时需要增加对WebSocketConfig.java类的扫描,并且增加

xmlns:websocket="http://www.springframework.org/schema/websocket"  
              http://www.springframework.org/schema/websocket   
              <a target="_blank" href="http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd</a>  


客户端

<script type="text/javascript"  
        src="http://localhost:8080/Bank/js/sockjs-0.3.min.js"></script>  
    <script>  
        var websocket;  
      
        if ('WebSocket' in window) {  
            websocket = new WebSocket("ws://" + document.location.host + "/Bank/webSocketServer");  
        } else if ('MozWebSocket' in window) {  
            websocket = new MozWebSocket("ws://" + document.location.host + "/Bank/webSocketServer");  
        } else {  
            websocket = new SockJS("http://" + document.location.host + "/Bank/sockjs/webSocketServer");  
        }  
      
        websocket.onopen = function(evnt) {};  
        websocket.onmessage = function(evnt) {  
            $("#test").html("(<font color='red'>" + evnt.data + "</font>)")  
        };  
      
        websocket.onerror = function(evnt) {};  
        websocket.onclose = function(evnt) {}  
      
        $('#btn').on('click', function() {  
            if (websocket.readyState == websocket.OPEN) {  
                var msg = $('#id').val();  
                //调用后台handleTextMessage方法  
                websocket.send(msg);  
            } else {  
                alert("连接失败!");  
            }  
        });  
    </script>  

注意导入socketjs时要使用地址全称,并且连接使用的是http而不是websocket的ws


声明: 整理本文仅供参考,若有疑问可以同时参考原文。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章