案例

  Spring+Websocket实现消息的推送

步骤

  1、用户登录后建立websocket连接,默认选择websocket连接,如果浏览器不支持,则使用sockjs进行模拟连接

  2、建立连接后,服务端返回该用户的未读消息

  3、服务端进行相关操作后,推送给某一个用户或者所有用户新消息

相关环境

  spring4.0+ , tomcat7+

maven包

<!-- spring websocket库 -->    
  <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-websocket</artifactId>  
        <version>${spring.version}</version>  
    </dependency>  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-messaging</artifactId>  
        <version>${spring.version}</version>  
    </dependency>  
  
  <dependency>
        <groupId>javax.websocket</groupId>
        <artifactId>javax.websocket-api</artifactId>
        <version>1.0</version>
        <scope>provided</scope>
    </dependency>

Websocet服务端实现

1 @Configuration
 2 @EnableWebMvc
 3 @EnableWebSocket
 4 // 注册websocket server实现类,设置访问websocket,Sockjs的地址
 5 public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
 6     @Override    
 7     public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
 8         
 9         //允许连接的域,只能以http或https开头
10         String[] allowsOrigins = {"http://127.0.0.1:8080"};
11         
12         // 支持websocket 的访问链接
13         registry.addHandler(systemWebSocketHandler(), "/echo.send").setAllowedOrigins(allowsOrigins).
14             addInterceptors(handshakeInterceptor());
15         
16         // 不支持websocket的访问链接
17         registry.addHandler(systemWebSocketHandler(), "/sockjs/echo.send").setAllowedOrigins(allowsOrigins).
18             addInterceptors(handshakeInterceptor()).withSockJS();
19     }
20     
21     @Bean
22     public SystemWebSocketHandler systemWebSocketHandler(){
23         return new SystemWebSocketHandler();
24     }
25     
26     @Bean
27     public HandshakeInterceptor handshakeInterceptor(){
28         return new HandshakeInterceptor();
29     }
30 }

 

SystemWebSocketHandler

1 public class SystemWebSocketHandler extends TextWebSocketHandler{
  2     
  3     private static final Logger logger = Logger.getLogger(SystemWebSocketHandler.class);
  4 
  5     private static final ArrayList<WebSocketSession> users = new ArrayList<>();
  6     
  7     @Autowired
  8     private WebSocketService webSocketService;
  9 
 10     // 初次链接成功执行
 11     @Override
 12     public void afterConnectionEstablished(WebSocketSession session) throws Exception {
 13         logger.debug("链接成功......");
 14         users.add(session);
 15         Integer staffId = (Integer) session.getAttributes().get("staffId");
 16         String exitTime = (String) session.getAttributes().get(staffId+"_exitTime");
 17         logger.debug("staffId:" + staffId);
 18         if (staffId != null && staffId != 0) {
 19             // 这块会实现自己业务,比如,当用户登录后,会把离线消息推送给用户
 20             // 查询未读消息
 21             //TextMessage returnMessage = webSocketService.findCheckInfoCount(exitTime);
 22             //session.sendMessage(returnMessage);
 23             session.sendMessage(new TextMessage("xxxx"));
 24         }
 25     }
 26 
 27     /**
 28      * 接受消息处理消息 js用websocket.send()时候,会调用该方法
 29      */
 30     @Override
 31     public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
 32         
 33     }
 34     
 35     @Override
 36     public void handleTransportError(WebSocketSession session, Throwable throwable) throws Exception {
 37         if (session.isOpen()) {
 38             session.close();
 39         }
 40         logger.info("链接出错,关闭链接......");
 41         users.remove(session);
 42     }
 43 
 44     @Override
 45     public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {  
 46         Integer staffId = (Integer) session.getAttributes().get("staffId");
 47         webSocketService.setLogoutTime(staffId);
 48         users.remove(session);
 49         logger.info("链接关闭......" + closeStatus.toString()+",staffId:"+staffId);
 50     }
 51 
 52     @Override
 53     public boolean supportsPartialMessages() {
 54         return false;
 55     }
 56     
 57     
 58     /**
 59      * 给所有在线用户发送消息
 60      *
 61      * @param message
 62      */
 63     public void sendMessageToUsers(TextMessage message) {
 64         for (WebSocketSession user : users) {
 65             try {
 66                 if (user.isOpen()) {
 67                     user.sendMessage(message);
 68                 }
 69             } catch (IOException e) {
 70                 logger.error(e, e);
 71             }
 72         }
 73     }
 74 
 75     /**
 76      * 给某个用户发送消息
 77      *
 78      * @param userName
 79      * @param message
 80      */
 81     public void sendMessageToUser(int staffId, TextMessage message) {
 82         for (WebSocketSession user : users) {
 83             if ((Integer) user.getAttributes().get("staffId") == staffId) {
 84                 try {
 85                     if (user.isOpen()) {
 86                         user.sendMessage(message);
 87                     }
 88                 } catch (IOException e) {
 89                     logger.error(e, e);
 90                 }
 91                 break;
 92             }
 93         }
 94     }
 95 
 96     /**
 97      * 给某个权限集合的用户发送消息
 98      *
 99      * @param roleCode
100      *            角色代码 例如:|2|
101      * @param message
102      */
103     public void sendMessageToRole(String roleCode, TextMessage message) {
104         for (WebSocketSession user : users) {
105             if (((String) user.getAttributes().get("roleCode")).contains(roleCode)) {
106                 try {
107                     if (user.isOpen()) {
108                         user.sendMessage(message);
109                     }
110                 } catch (IOException e) {
111                     logger.error(e, e);
112                 }
113                 break;
114             }
115         }
116     }
117 }

 

WebSocketHandshakeInterceptor

// 这个的主要作用是取得当前请求中的用户名,并且保存到当前的WebSocketHandler中,以便确定WebSocketHandler所对应的用户
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {
    
    private static final Logger logger = Logger.getLogger(HandshakeInterceptor.class);

    // 进入hander之前的拦截
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
        logger.info("beforeHandshake...start");
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            //HttpSession session = ((ServletServerHttpRequest) request).getServletRequest().getSession();
            if (session != null) {
                // 使用userName区分WebSocketHandler,以便定向发送消息  如: String userName = (String) session.getAttribute("WEBSOCKET_USERNAME");
                //attributes.put("staffId", session.getAttribute("staffId"));
                //attributes.put("roleCode", session.getAttribute("roleCode"));
                //attributes.put(session.getAttribute("staffId") + "_exitTime", session.getAttribute(session.getAttribute("staffId") + "_exitTime"));
            }
        }
        logger.info("beforeHandshake...end");
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
        logger.debug("afterHandshake...start");
        super.afterHandshake(serverHttpRequest, serverHttpResponse, webSocketHandler, e);
        logger.debug("afterHandshake...end");
    }
}

 

socketHelper.js

1 var websocket;
 2 $(window).load(function() {
 3     socketInit();
 4 });
 5 
 6 function socketInit(){
 7     if ('WebSocket' in window) {
 8         
 9         websocket = new WebSocket("ws://127.0.0.1:8080/echo.send");
10     } else if ('MozWebSocket' in window) {
11         
12         websocket = new MozWebSocket("ws://127.0.0.1:8080/echo.send");
13     } else {
14         
15         websocket = new SockJS("http://127.0.0.1:8080/echo.send");
16     }
17     
18     websocket.onopen = function(event) {
19          console.log('Info: connection opened.');
20     };
21     websocket.onmessage = function(event) {
22          console.log('Received.message: ' + event.data); //处理服务端返回消息
23     };
24     websocket.onerror = function(event) {
25         alert('Received.error: ' + event.data);
26     };
27     websocket.onclose = function(event) {
28         console.log('Info: connection closed.');
29         console.log(event);
30     }
31 }

 如果使用注解方式则添加相应注解 也可使用xml配置方式(注解的我没弄好使 错误代码:403 估计也是下面说的访问源问题)

更新applicationContext.xml(默认配置文件,根据自己情况修改即可)的xmlns

xmlns:websocket="http://www.springframework.org/schema/websocket"

xsi:schemaLocation="http://www.springframework.org/schema/websocket
    http://www.springframework.org/schema/websocket/spring-websocket.xsd"

 配置如下:

<websocket:handlers allowed-origins="*">
        <websocket:mapping handler="handler" path="/echo.send"/>
        <websocket:handshake-interceptors>
            <bean class="com.zhuo.console.core.interceptor.HandshakeInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>
    <websocket:handlers allowed-origins="*">
        <websocket:mapping handler="handler" path="/echo.send"/>
        <websocket:handshake-interceptors>
            <bean class="com.zhuo.console.core.interceptor.HandshakeInterceptor"/>
        </websocket:handshake-interceptors>
        <websocket:sockjs/>
    </websocket:handlers>
    
    <bean id="handler" class="com.zhuo.console.websocket.SystemWebSocketHandler" />

 注意: allowed-origins="*" spring javadoc的说明是默认情况下,允许所有来源访问,但我跑下来发现不配置allowed-origins的话总是报403错误。 有的说sockjs是不允许有后缀的,否则将无法匹配,这个还没试过等试过后再确认 

web.xml中 web-app 的 version 和 xsi:schemaLocation 必须要3.0+

servlet与filter需要加 <async-supported>true</async-supported>

(上面的说必须加我也没试过不加行不行)  

配置nginx支持websocket,默认情况下,nginx不支持自动升级至websocket协议,否则js中会出现连接时异常"Error during WebSocket handshake: Unexpected response code: 400",需在恰当的位置加上如下设置:

server {
    listen 8020;
    location / {
        proxy_pass http://websocket;
        proxy_set_header Host $host:8020; #注意, 原host必须配置, 否则传递给后台的值是websocket,端口如果没有输入的话会是80, 这会导致连接失败
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
}
upstream websocket {
    server 192.168.100.10:8081;
}

经上述调整后,websocket就可以同时支持通过nginx代理的https协议,结合MQ机制,可以做到B端实时推送、B端/C端实时通信。
nginx的https(自动跳转http->https)+nginx+websocket的完整配置可参考http://www.cnblogs.com/zhjh256/p/6262620.html。