嗯,这次真的仅仅是一个入门教程,因为老夫表示自己也不会。近期老夫参与开发公司的一个CRM系统,系统中有很多消息的推送,由一个同事负责,其用到了websocket技术,老夫比较感兴趣,删繁就简,整理了一个教程,留作自己笔记,因很多原理老夫也是不甚了了,以备将来用到了有资料可查。

  1. maven依赖
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.3.0</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.3.0</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-websocket</artifactId>
  <version>4.0.1.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-messaging</artifactId>
  <version>4.0.1.RELEASE</version>
</dependency>
  1. spring-servlet的配置
<?xml version="1.0" encoding="UTF-8"?>
  <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.1.xsd
      http://www.springframework.org/schema/mvc
      http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
      http://www.springframework.org/schema/tx
      http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
      http://www.springframework.org/schema/websocket
      http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    ......

    <!-- websocket -->
    <bean id="websocket" class="cn.bridgeli.websocket.WebsocketEndPoint"/>
    <websocket:handlers>
      <websocket:mapping path="/websocket" handler="websocket"/>
      <websocket:handshake-interceptors>
      <bean class="cn.bridgeli.websocket.HandshakeInterceptor"/>
      </websocket:handshake-interceptors>
    </websocket:handlers>
</beans>

其中,path对应的路径就是前段通过ws协议调的接口路径

  1. HandshakeInterceptor的实现
package cn.bridgeli.websocket;

import cn.bridgeli.utils.UserManager;
import cn.bridgeli.util.DateUtil;
import cn.bridgeli.sharesession.UserInfo;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import java.util.Date;
import java.util.Map;

/**
 * @Description :创建握手(handshake)接口
 * @Date : 16-3-3
 */
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{
  private static final Logger logger = LoggerFactory.getLogger(HandshakeInterceptor.class);

  @Override
  public boolean beforeHandshake(ServerHttpRequest request,ServerHttpResponse response, WebSocketHandler wsHandler, Map<String,Object> attributes) throws Exception {
    logger.info("建立握手前...");
    ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    UserInfo currUser = UserManager.getSessionUser(attrs.getRequest());
    UserSocketVo userSocketVo = new UserSocketVo();
    String email= "";

    if(null != currUser){
      email = currUser.getEmail();
    }
    if(StringUtils.isBlank(email)){
      email = DateUtil.date2String(new Date());
    }
    userSocketVo.setUserEmail(email);
    attributes.put("SESSION_USER", userSocketVo);

    return super.beforeHandshake(request, response, wsHandler, attributes);
  }

  @Override
  public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
    logger.info("建立握手后...");
    super.afterHandshake(request, response, wsHandler, ex);
  }
}

因为老夫不是很懂,所以最大限度的保留原代码,这其实就是从单点登录中取出当前登录用户,转成UserSocketVo对象,放到Map中。所以接下来我们看看UserSocketVo对象的定义

  1. UserSocketVo的定义
package cn.bridgeli.websocket;

import org.springframework.web.socket.WebSocketSession;

import java.util.Date;

/**
 * @Description : 用户socket连接实体
 * @Date : 16-3-7
 */
public class UserSocketVo {

  private String userEmail; //用户邮箱
  private Date connectionTime; //成功连接时间
  private Date preRequestTime; //上次请求时间
  private Date newRequestTime; //新请求时间
  private Date lastSendTime = new Date(); //下架消息最近一次发送时间
  private Date lastTaskSendTime = new Date(); //待处理任务最近一次发送时间
  private WebSocketSession webSocketSession; //用户对应的wsSession 默认仅缓存一个

  // getXX and setXX
}

其中最重要的就是这个WebSocketSession这个属性了,后面我们要用到

  1. WebsocketEndPoint的实现
package cn.bridgeli.websocket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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;

/**
 * @Description : websocket处理类
 * @Date : 16-3-3
 */
public class WebsocketEndPoint extends TextWebSocketHandler{
  private static final Logger logger = LoggerFactory.getLogger(WebsocketEndPoint.class);

  @Autowired
  private NewsListenerImpl newsListener;

  @Override
  protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    super.handleTextMessage(session, message);
    TextMessage returnMessage = new TextMessage(message.getPayload()+" received at server");
    session.sendMessage(returnMessage);
  }

  /**
   * @Description : 建立连接后
   * @param session
   * @throws Exception
   */
  @Override
  public void afterConnectionEstablished(WebSocketSession session) throws Exception{
    UserSocketVo userSocketVo = (UserSocketVo)session.getAttributes().get("SESSION_USER");
    if(null != userSocketVo){
      userSocketVo.setWebSocketSession(session);
      if(WSSessionLocalCache.exists(userSocketVo.getUserEmail())){
        WSSessionLocalCache.remove(userSocketVo.getUserEmail());
      }
      WSSessionLocalCache.put(userSocketVo.getUserEmail(), userSocketVo);
      newsListener.afterConnectionEstablished(userSocketVo.getUserEmail());
    }
    logger.info("socket成功建立连接...");
    super.afterConnectionEstablished(session);
  }

  @Override
  public void afterConnectionClosed(WebSocketSession session,CloseStatus status) throws Exception {
    UserSocketVo userSocketVo = (UserSocketVo)session.getAttributes().get("SESSION_USER");
    if(null != userSocketVo){
      WSSessionLocalCache.remove(userSocketVo.getUserEmail());
    }
    logger.info("socket成功关闭连接...");
    super.afterConnectionClosed(session, status);
  }
}
  1. WSSessionLocalCache的实现
package cn.bridgeli.websocket;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description :本地缓存WebSocketSession实例
 * @Date : 16-3-7
 */
public class WSSessionLocalCache implements Serializable {

  private static Map<String, UserSocketVo> wsSessionCache = new HashMap<>();

  public static boolean exists(String userEmail){
    if(!wsSessionCache.containsKey(userEmail)){
      return false;
    }else{
      return true;
    }
  }

  public static void put(String userEmail, UserSocketVo UserSocketVo){
    wsSessionCache.put(userEmail, UserSocketVo);
  }

  public static UserSocketVo get(String userEmail){
    return wsSessionCache.get(userEmail);
  }

  public static void remove(String userEmail){
    wsSessionCache.remove(userEmail);
  }

  public static List<UserSocketVo> getAllSessions(){

    return new ArrayList<>(wsSessionCache.values());
  }
}

看了其实现,作用就比较明显了吧,存放每个UserSocketVo的最新数据,其实到这里我们websocket的实现已经算完了,但还有一个核心类(关于业务逻辑查理的类)没有实现,下篇我们就看怎么实现这个类