Spring Boot 整合 WebSocket 之 注解驱动

JSR 356, Java API for WebSocket(本篇文章记录注解实现websocket

websocket支持两种不同的编程方式:

1.基于注解驱动,使用带注释的POJO,可以与WebSocket生命周期事件进行交互

2.基于接口驱动,可以实现Endpoint与生命周期事件交互的接口和方法

官网参考资料:

ORACLE JAVA:https://www.oracle.com/technetwork/articles/java/jsr356-1937161.html

J2EE 7:https://docs.oracle.com/javaee/7/tutorial/websocket.htm

1.maven依赖


            org.springframework.boot
            spring-boot-starter-websocket

2.ServerEndpointExporter配置

/**
 * 外部tomcat由容器自己提供
 * @author Administrator
 *
 */
@Configuration
public class ServerEndpointExporterConfigure {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

3.创建WebSocket请求的端点

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.HttpSession;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@ServerEndpoint(value="/websocket/{sid}",configurator=HttpSessionConfigurator.class)
public class ServerEndpointConfigure {

	//设定原子整型,用来记录当前在线连接数
    private AtomicInteger onlineCount = new AtomicInteger(0);
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//    private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();
    //若要实现服务端与指定客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
//    public static ConcurrentHashMap webSocketMap = new ConcurrentHashMap();
    
    //绑定HttpSession与session
    public static ConcurrentHashMap bizSession = new ConcurrentHashMap();
	//与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
 
    /**
     * 

连接建立成功调用的方法

* @throws IOException **/ @OnOpen public void onOpen(Session session,EndpointConfig config,@PathParam("sid") String sid) throws IOException { this.session = session; HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName()); log.info("httpSessionId:{},websocketSessionId:{},sid:{}----",httpSession.getId(),session.getId(),sid); bizSession.put(httpSession.getId(), session);//建立关联 addOnlineCount();//在线数加1 log.info("有新连接加入!当前在线人数为" + getOnlineCount()); try { sendMessage("["+httpSession.getId()+"]连接成功",session); //设定模拟线程 new Thread(new Heartbeat(session)).start(); } catch (IOException e) { log.error("websocket IO异常"); } } /** *

连接关闭调用的方法

*/ @OnClose public void onClose(Session closeSession,CloseReason reason) { log.info(reason.toString()); for(String key:bizSession.keySet()){ Session session = bizSession.get(key); if(session.equals(closeSession)){ bizSession.remove(key); } } subOnlineCount(); //在线数减1 log.info("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** *

收到客户端消息后调用的方法

* * @param message 客户端发送过来的消息*/ @OnMessage public void onMessage(String message, Session mySession) { log.info("来自客户端的消息:" + message); try { sendMessage(message, mySession); } catch (IOException e) { e.printStackTrace(); } } /** * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("发生错误"); for(String key:bizSession.keySet()){ Session sessionc = bizSession.get(key); if(session.equals(sessionc)){ bizSession.remove(key); } } error.printStackTrace(); } public void sendMessage(String message,Session session) throws IOException { try { if(session.isOpen()){//该session如果已被删除,则不执行发送请求,防止报错 session.getBasicRemote().sendText(message); } } catch (IOException e) { e.printStackTrace(); } } public int getOnlineCount() {//获取当前值 return onlineCount.get(); } public int addOnlineCount() {//加1 return onlineCount.getAndIncrement(); } public int subOnlineCount() {//减1 return onlineCount.getAndDecrement(); } }

这里configurator指定了端点配置器类,端点类可以使用握手请求对象来访问初始HTTP请求的详细信息,获取HttpSession对象

,不需要的话可以去掉

HttpSessionConfigurator 类

public class HttpSessionConfigurator extends Configurator {

	@Override
	public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
		HttpSession httpSession = (HttpSession) request.getHttpSession();
		sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
	}
	
}

 以下配置是避免获取httpsession出现空指针的情况,事先带上

@Component
public class RequestListener implements ServletRequestListener {

	@Override
	public void requestDestroyed(ServletRequestEvent sre) {
		// TODO Auto-generated method stub
		ServletRequestListener.super.requestDestroyed(sre);
	}

	@Override
	public void requestInitialized(ServletRequestEvent sre) {
		 //将所有request请求都携带上httpSession
		HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
		req.getSession();
//        ((HttpServletRequest) sre.getServletRequest()).getSession();
	}
	
	
}

 放入监听

@SpringBootApplication
public class SpringbootWebsocketApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebsocketApplication.class, args);
    }
    
    @Autowired
    private RequestListener requestListener;
  
    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean() {
         ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean<>();
         servletListenerRegistrationBean.setListener(requestListener);
         return servletListenerRegistrationBean;
    }

}

Heartbeat类

public class Heartbeat implements Runnable {
	
	private Logger log = LoggerFactory.getLogger(Heartbeat.class);
	
	private Session session;
	
	public Heartbeat(Session session) {
		super();
		this.session = session;
	}

	
	public Heartbeat() {
		super();
	}


	@Override
	public void run() {
		log.info("服务端开启发送心跳模式");
		int i = 0;
		while(session.isOpen()) {
			try {
				String uuid = String.format("%04d", i++)+":the websoket heart is exist 3s";
				log.info(uuid);
				session.getBasicRemote().sendText(uuid);
				Thread.sleep(1000*3);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

}

3.Controller控制层

@Controller
@Slf4j
public class DemoController {
	
    @GetMapping("demo")
    @ResponseBody
    public String demo() {
        return "this is a demo~";
    }
    
    @RequestMapping("/")
    public String index(Model model,HttpServletRequest request) {
    	HttpSession session = request.getSession();
    	model.addAttribute("sid", session.getId());
    	log.info("初始化websocket页面~");
		return "index";   	
    }
    
}

4.html

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%
    String path = request.getContextPath();
    String basepath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

%>



websocket页面




	
websocket服务端返回信息:

5.启动最终效果

Spring Boot 整合 WebSocket 之 注解驱动_第1张图片

你可能感兴趣的:(springboot)