继承AbstractwebSocketHandler实现WebSocket

1,144 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情

2.2. 通过继承AbstractwebSocketHandler实现

上面注解的版本感觉比较好理解,但是感觉不是很懂原理,所以想通过继承AbstractwebSocketHandler实现一下

  1. WebSocketConfig配置

    通过实现WebSocketConfigurer配置类,重写registerWebSocketHandlers方法,注册自定义的WebSocketHandler的实现类MyWebSocketHandler,并指定类对应的websocket访问的ServerEndpoint/webSocket。 通过@EnableWebSocket注解,启动spring-boot-starter-websocket的自动化配置。

    @Configuration
    public class WebSocketConfig implements WebSocketConfigurer {
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
            webSocketHandlerRegistry.addHandler(new MyWebSocketHandler(), "webSocket");
        }
    ​
        @Bean
        public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
            return builder.build();
        }
    }
    

    我直接在这个类里面配置了ThreadPoolTaskScheduler之前是没有这个bean的,但是定时任务有bug,我就浅配了一下,虽然不太懂为什么,反正与WebSocket冲突了

  2. 配置hander

    由于我们配置类中定义了MyWebSocketHandler的类,是websoket用于收发消息的

    @Component
    public class MyWebSocketHandler extends AbstractWebSocketHandler {
    ​
        /**
         * 建立 WebSocket 连接
         *
         * @param session
         * @throws Exception
         */
        @Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
            WebSocketManager.add(session.getId(), session);
        }
    ​
        @Override
        protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
            String payload = message.getPayload();
            session.sendMessage(new TextMessage(payload + LocalDateTime.now()));
        }
    ​
        /**
         * 发送二进制消息
         *
         * @param session
         * @param message
         * @throws Exception
         */
        @Override
        protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
            System.out.println("发送二进制消息");
        }
    ​
    ​
        /**
         * 异常处理
         *
         * @param session
         * @param exception
         * @throws Exception
         */
        @Override
        public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
            WebSocketManager.removeAndClose(session.getId());
        }
    ​
        /**
         * 关闭连接
         *
         * @param session
         * @param status
         * @throws Exception
         */
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
            WebSocketManager.removeAndClose(session.getId());
        }
    }
    
    • afterConnectionEstablished方法是在socket连接成功后被触发,同原生注解里的@OnOpen功能。
    • afterConnectionClosed方法是在socket连接关闭后被触发,同原生注解里的@OnClose 功能。
    • handleTextMessage方法是在客户端发送普通文件信息时触发,同原生注解里的@OnMessage功能。
    • handleBinaryMessage方法是在客户端发送二进制信息时触发,同原生注解里的@OnMessage功能。
    • handleTransportError方法同原生注解里的@OnError
    • ...
  3. 配置WebSocket的管理器

    public class WebSocketManager {
    ​
        // 保存 session 连接的地方
        public static ConcurrentHashMap<String, WebSocketSession> SESSION_POOL = new ConcurrentHashMap<>();
    ​
        /**
         * 添加 session
         *
         * @param key
         * @param session
         */
        public static void add(String key, WebSocketSession session) {
            SESSION_POOL.put(key, session);
        }
    ​
        /**
         * 删除并返回 session
         *
         * @param key
         * @return
         */
        public static WebSocketSession remove(String key) {
            return SESSION_POOL.remove(key);
        }
    ​
        /**
         * 关闭连接的操作
         *
         * @param key
         */
        public static void removeAndClose(String key) {
            WebSocketSession session = remove(key);
            if (session != null) {
                try {
                    session.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    ​
        /**
         * 获得 session
         *
         * @param key
         * @return
         */
        public static WebSocketSession get(String key) {
            return SESSION_POOL.get(key);
        }
    }
    

    用于增加和移除连接进入WebSocket的客户端

  4. 发送消息以及他的实现类

    public interface WebSocketService {
    ​
        /**
         * 给客户端发送消息
         * @param session
         * @param text
         * @throws IOException
         */
        public void sendMsg(WebSocketSession session, String text) throws IOException;
    ​
        /**
         * 给客户端广播消息
         * @param text
         * @throws IOException
         */
        public void broadcastMsg(String text) throws IOException;
    ​
    }
    

    实现,这里我直接用时间代替查数据库了

    @Service
    public class WebSocketServiceImpl implements WebSocketService {
    ​
        public void sendMsg(WebSocketSession session, String text) throws IOException {
            System.out.println(text);
            session.sendMessage(new TextMessage(text));
        }
    ​
        public void broadcastMsg(String text) throws IOException {
            for (WebSocketSession session : WebSocketManager.SESSION_POOL.values()) {
                session.sendMessage(new TextMessage(text));
            }
        }
    }
    
  5. 定时任务

    这个怎么写其实都ok的

    @Component
    public class MsgJob {
    ​
        @Autowired
        private WebSocketService webSocketService;
    ​
        @Scheduled(cron = "0/3 * * * * *")
        public void run() throws IOException {
            System.out.println(WebSocketManager.SESSION_POOL.size());
            webSocketService.broadcastMsg(LocalDateTime.now().toString());
        }
    }
    
  6. 主启动

    @SpringBootApplication(scanBasePackages = {"com.he.websocket2"})
    @EnableScheduling
    @EnableWebSocket
    public class WebSocketMain {
        public static void main(String[] args) {
            SpringApplication.run(WebSocketMain.class, args);
        }
    }
    

    这里需要注意的是@EnableWebSocket用于开启WebSocket,不过确实和schedule有问题,暂且不管他,等以后学深入再看

  7. 前端页面和之前的一样,懒得修改了

运行测试,没有问题