SpringBoot-集成 webSocket

1. WebSocket 简介

2. springboot 集成 javax 注解方式

  1. pom
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  1. 配置类
/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author buhao
 * @version WebSocketConfig.java, v 0.1 2019-10-18 15:45 buhao
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig {

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

这个配置类很简单,通过这个配置 spring boot 才能去扫描后面的关于 websocket 的注解

  1. 定义一个 WebSocket
/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.ws;

import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author buhao
 * @version WsServerEndpoint.java, v 0.1 2019-10-18 16:06 buhao
 */
@ServerEndpoint("/myWs")
@Component
public class WsServerEndpoint {

    /**
     * 连接成功
     *
     * @param session
     */
    @OnOpen
    public void onOpen(Session session) {
        System.out.println("连接成功");
    }

    /**
     * 连接关闭
     *
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        System.out.println("连接关闭");
    }

    /**
     * 接收到消息
     *
     * @param text
     */
    @OnMessage
    public String onMsg(String text) throws IOException {
        return "servet 发送:" + text;
    }
}

这里有几个注解需要注意一下,首先是他们的包都在 **javax.websocket **下。并不是 spring 提供的,而 jdk 自带的,下面是他们的具体作用。

@ServerEndpoint:通过这个 spring boot 就可以知道你暴露出去的 ws 应用的路径,有点类似我们经常用的@RequestMapping。比如你的启动端口是 8080,而这个注解的值是 ws,那我们就可以通过 ws://127.0.0.1:8080/ws 来连接你的应用

@OnOpen:当 websocket 建立连接成功后会触发这个注解修饰的方法,注意它有一个 Session 参数

@OnClose:当 websocket 建立的连接断开后会触发这个注解修饰的方法,注意它有一个 Session 参数

@OnMessage:当客户端发送消息到服务端时,会触发这个注解修改的方法,它有一个 String 入参表明客户端传入的值

@OnError:当 websocket 建立连接时出现异常会触发这个注解修饰的方法,注意它有一个 Session 参数
另外一点就是服务端如何发送消息给客户端,服务端发送消息必须通过上面说的 Session 类,通常是在@OnOpen 方法中,当连接成功后把 session 存入 Map 的 value,key 是与 session 对应的用户标识,当要发送的时候通过 key 获得 session 再发送,这里可以通过 session.getBasicRemote().sendText() 来对客户端发送消息。

3. Spring 封装的版本

  1. pom
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  1. 定义一个 SocketService
@Component
public class SocketService {

    //用来存放所有客户端连接
    private ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<String, WebSocketSession>();

    //添加连接
    public void add(String key, WebSocketSession session){
        sessions.put(key, session);
    }

    //删除链接
    public WebSocketSession remove(String key){
        return sessions.remove(key);
    }

    //删除并关闭链接
    public void removeAndClose(String key){
        WebSocketSession removeSession = remove(key);
        if(removeSession != null){
            try {
                removeSession.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //获取 session
    public WebSocketSession get(String key){
        return sessions.get(key);
    }

    //向客户端发送消息
    private void sendMessage(ComparisonSocketVo key, TextMessage textMessage){
        List<WebSocketSession> webSocketSessions = sessions.get(key);
        if(webSocketSessions != null) {
            webSocketSessions.forEach(ws -> {
                try {
                    //测试的
                    ws.sendMessage(textMessage);

                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

这个不是必要的,用来配合我们的业务,将客户端 session 连接管理起来,key 具体存什么

  1. 定义一个处理器
@Component
public class MyMessageHandler extends TextWebSocketHandler {

    @Autowired
    private SocketService socketService;


    //建立连接成功事件
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("连接成功");
        socketService.add(session.getId(), session);
    }

    //关闭连接成功事件,客户端主动关闭
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("关闭成功");
        socketService.remove(session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println(message.toString());
    }
}

相当于 @Open 等注解。

  1. 定义拦截器
@Component
public class MyInterceptor implements HandshakeInterceptor {

    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        System.out.println("握手开始。。。。");
        return true;
    }

    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        System.out.println("握手结束。。。。");
    }
}

通过实现 HandshakeInterceptor 接口来定义握手拦截器,注意这里与上面 Handler 的事件是不同的,这里是建立握手时的事件,分为握手前与握手后,而 Handler 的事件是在握手成功后的基础上建立 socket 的连接。所以在如果把认证放在这个步骤相对来说最节省服务器资源。它主要有两个方法 beforeHandshake 与 **afterHandshake **,顾名思义一个在握手前触发,一个在握手后触发。

  1. 配置文件

@Configuration
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private MyInterceptor myInterceptor;

    @Autowired
    private MyMessageHandler myMessageHandler;

    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myMessageHandler, "/test1")
                .addInterceptors(myInterceptor)
                .setAllowedOrigins("*");
    }
}

将对应的 handler 配置成一个 webSocket 路径,类似 http 接口的 url。

  1. 启动类
@SpringBootApplication
@EnableWebSocket
public class SocketDemoMain {
    public static void main(String[] args) {
        SpringApplication.run(SocketDemoMain.class, args);
    }
}
  1. 问题

向客户端推送消息,使用 WebSocketSession.sendMessage() 方法,实际是调用 AbstractWebSocketSession.sendMessage() 方法

    @Override
	public final void sendMessage(WebSocketMessage<?> message) throws IOException {
		checkNativeSessionInitialized();

		if (logger.isTraceEnabled()) {
			logger.trace("Sending " + message + ", " + this);
		}

		if (message instanceof TextMessage) {
			sendTextMessage((TextMessage) message);
		}
		else if (message instanceof BinaryMessage) {
			sendBinaryMessage((BinaryMessage) message);
		}
		else if (message instanceof PingMessage) {
			sendPingMessage((PingMessage) message);
		}
		else if (message instanceof PongMessage) {
			sendPongMessage((PongMessage) message);
		}
		else {
			throw new IllegalStateException("Unexpected WebSocketMessage type: " + message);
		}
	}

由源码可见,我们只能用这几种消息。如果需要发送一个 Object 类的数据,需要转换成 json 字符串,然后封装成 TextMessage 发出去。

  public static TextMessage errMsg(String message){
        CompareSocketMsg msg = new CompareSocketMsg();
        msg.type = 0;
        msg.code = 500;
        msg.message = message;
        return new TextMessage(JSON.toJSONString(msg));
    }

参考文件

https://cloud.tencent.com/developer/article/1530872

posted @ 2024-07-16 16:39  primaryC  阅读(15)  评论(0编辑  收藏  举报