SpringBoot集成WebSocket实现服务器向前端推送数据(全双工通信)

SpringBoot集成WebSocket实现服务器向前端推送数据(全双工通信)

1.场景:

在一次开发中需要实时的向前端发送一些数据,一开始采用了前端轮询访问的方式,每隔一秒发送一次请求,这样虽然能达到项目的需求但是还是有些小问题,当我们的需求增加实时性方面的需求的时候就需要前端做修改,比如说新的需求是只有当前端进入这个页面的点击实时获取数据这个按钮的时候就开始接收消息,这个时候就需要知道前端第一次点击的时间,因为http请求的无状态的特性,没有办法让后端单独实现这个实时的功能,这个时候就需要前端去监听第一次点击的这个状态并记录时间,还要记录用户是否离开页面。所以为了一个简单的实时性功能前端需要额外做这么多的事情。

2. 解决方案:

使用webSocket实现服务器和前端双向通行,当数据有更新的时候后端主动群发给所有连接的用户

3. 实施

3.1依赖(正常情况下只需要这一个依赖就可以实现)

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-websocket</artifactId>
	</dependency>

3.2. 注入webSocket的支持

	package com.doudou.websocket;
	
	import org.springframework.boot.SpringBootConfiguration;
	import org.springframework.context.ApplicationContext;
	import org.springframework.context.annotation.Bean;
	import org.springframework.context.annotation.Configuration;
	import org.springframework.web.context.ServletContextAware;
	import org.springframework.web.socket.server.standard.ServerEndpointExporter;
	
	import javax.servlet.ServletContext;
	
	/**
	 * webSocket的配置类, 开启对webSocket的支持
	 * @author 豆豆
	 * @date 2019/3/27 15:25
	 * @flag 以万物智能,化百千万亿身
	 */
	@Configuration
	public class WebSocketConfig {
	    /*@Bean
		这个是为了解决在有些情况下出现serverEndpointExporter为null的情况(StackOverflow上看到的)
	    public ServletContextAware endpointExporterInitializer(final ApplicationContext applicationContext) {
	        return new ServletContextAware() {
	
	            @Override
	            public void setServletContext(ServletContext servletContext) {
	                ServerEndpointExporter serverEndpointExporter = new ServerEndpointExporter();
	                serverEndpointExporter.setApplicationContext(applicationContext);
	                try {
	                    serverEndpointExporter.afterPropertiesSet();
	                } catch (Exception e) {
	                    throw new RuntimeException(e);
	                }
	            }
	        };
	    }*/
	
	    @Bean
	    public ServerEndpointExporter serverEndpointExporter(){
	        return new ServerEndpointExporter();
	    }
	
	}

3.3. ws的实现类

这个类主要实现了两件事,一件是接收客户端的连接并且保存这些连接,另外一件是当服务器需要向前端发送数据的时候会遍历保存的这些状态,群发消息给连接着的客户端。实现保存这个功能的借助的是java.util.concurrent.CopyOnWriteArraySet,能够保存底层数据状态,并且是线程安全的。类中一些注释掉的代码是我在实际开发中查询数据库的数据。

	package com.doudou.websocket;
	
	import lombok.extern.slf4j.Slf4j;
	import org.springframework.stereotype.Component;
	
	import javax.websocket.*;
	import javax.websocket.server.PathParam;
	import javax.websocket.server.ServerEndpoint;
	import java.io.IOException;
	import java.util.Date;
	import java.util.List;
	import java.util.concurrent.CopyOnWriteArraySet;
	
	
	/**
	 * webSocket实现实时抓拍展示
	 * @author 豆豆
	 * @date 2019/3/27 15:29
	 * @flag 以万物智能,化百千万亿身
	 */
	@ServerEndpoint("/ws/{userId}")
	@Slf4j
	@Component
	public class WebSocketServer {
	
	    //这里需要注意我发现在实际的开发中无法注入,解决办法在问题中说明
	    /*@Autowired
	    private SnapRecordRepository snapRecordRepository;
	
	    @Autowired
	    private ResourcesService resourcesService;*/
	
	    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
	    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
	
	    //与某个客户端的连接会话,需要通过它来给客户端发送数据
	    private Session session;
	
	    private String dateStr;
	
	    private String userId;
	    private List<String> cameraIndexCodes;
	
	    /**
	     * 收到请求后调用的方法
	     * @param session
	     */
	    @OnMessage
	    public void handleMessage(Session session, String message){
	        log.info("收到客户端的ws请求");
	    }
	
	    /**
	     * 连接建立成功调用的方法
	     * */
	    @OnOpen
	    public void onOpen(Session session, @PathParam("userId")String userId) {
	        log.info("建立webSocket的连接");
	        this.session = session;
	        Date date = new Date(System.currentTimeMillis());
	        //this.dateStr = DateUtil.formatDate(date, "yyyy-MM-dd HH:mm:ss");
	        this.userId = userId;
	        //List<String> cameraIndexCodes = resourcesService.getCameraIndexCodeFromXres(ResourcesTypeConstant.CAMERA, userId);
	        /*if (cameraIndexCodes.size() < 1){
	            cameraIndexCodes.add("");
	        }*/
	        //this.cameraIndexCodes = cameraIndexCodes;
	        webSocketSet.add(this);
	    }
	
	    /**
	     * 连接关闭调用的方法
	     */
	    @OnClose
	    public void onClose() {
	        log.info("关闭webSocket的连接");
	        webSocketSet.remove(this);
	    }
	
	
	    /**
	     * 发生错误时调用的方法
	     * @param session
	     * @param error
	     */
	    @OnError
	    public void onError(Session session, Throwable error) {
	        log.error("发生错误");
	        error.printStackTrace();
	    }
	
	
	    /**
	     * 实现服务器主动推送
	     */
	    public void sendMessage(String message) throws IOException {
	        webSocketSet.forEach(x -> {
	            //List<SnapRecordEntity> snapRecordEntities = snapRecordRepository.findByCameraIdInOrderByEventTimeLimit(x.cameraIndexCodes,x.dateStr);
	            //String message = JSON.toJSONString(BaseResult.success(snapRecordEntities)) ;
	            try {
	                x.session.getBasicRemote().sendText(message);
	            } catch (IOException e) {
	                e.printStackTrace();
	            }
	        });
	    }
	
	
	
	
	}

4. 碰到的问题

4.1. 项目使用undertow作为启动容器,引用的依赖是tomcat-webSocket是不是不可以
(本来直接引入springboot对websocket的支持就可以了,但是公司的父级依赖中排除了一些jboss的依赖所以只能使用替代的这个问题还会导致一些类和注解找不到,所以具体情况按照自己的实际来)?
答案: tomcat-websocket是websocket的一种实现跟容器没有关系。
4.2. 测试demo中ws可以正常连接但是在项目中就是无法访问?
答案:排查是因为单点登录拦截了ws请求。
4.3 . ws的实现类中无法注入一些service数据库访问的repository。
解决:使用ApplicationContext.getBean(“name”);这种形式来获取容器中的bean。
4.4. 如何实现获取application呢?
答案: 可以自定义一个类,实现ApplicationContextAware接口就可以拿到,然后自己包装一个getBean的泛型方法就可以随意获取容器中的bean。
4.5. 获取bean的泛型方法怎么实现?

  	 public static <T> T getBean(String name) {
  		if (context == null)
  			throw new IllegalStateException("applicaitonContext未注入");
  		try {
  			return (T) context.getBean(name);
  		} catch (BeansException e) {
  			e.printStackTrace();
  		}
  		return (T) null;
  	}
1. 添加WebSocket依赖 在pom.xml中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建WebSocket配置类 创建一个WebSocket配置类,配置WebSocket相关的信息: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { // 注册WebSocket处理器,并指定WebSocket连接路径 registry.addHandler(myHandler(), "/myHandler") .setAllowedOrigins("*"); // 允许跨域 } @Bean public WebSocketHandler myHandler() { return new MyHandler(); } } ``` 其中,MyHandler是自定义的WebSocket处理器,后面会讲到。 3. 创建WebSocket处理器 创建一个WebSocket处理器,实现WebSocketHandler接口: ```java public class MyHandler implements WebSocketHandler { private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); // 连接建立后 @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); } // 接收到消息后 @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { // 处理消息 } // 连接关闭后 @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { sessions.remove(session); } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { // 处理异常 } // 广播消息 public static void broadcast(String message) { for (WebSocketSession session : sessions) { try { session.sendMessage(new TextMessage(message)); } catch (IOException e) { e.printStackTrace(); } } } } ``` 其中,sessions存储所有连接的WebSocketSession对象,可以用于广播消息。 4. 向前端推送数据 在需要推送数据的地方,调用MyHandler的broadcast方法即可: ```java MyHandler.broadcast("Hello world!"); ``` 这样,所有连接的WebSocket客户端都会收到消息。 5. 前端代码 在前端代码中,使用WebSocket连接到后端,并监听消息: ```javascript var socket = new WebSocket("ws://localhost:8080/myHandler"); socket.onmessage = function(event) { // 处理消息 }; ``` 注意,WebSocket连接路径需要和后端配置的一致。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值