SpringBoot+Websocket两种实现方式

SpringBoot+Websocket两种实现方式

在学习使用中,记录此文,避免踩坑,如有不好见谅。

1、引入依赖

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

 

2、yml

spring:
  thymeleaf:
    prefix: classpath:/templates/
    cache: false

 

3、原始实现方式

(1)新建websocket配置类

@Configuration
@EnableWebSocket
public class WebSocketConfig {

    /**
     * 注入ServerEndpointExporter
     * 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

 

(2)新建websocket处理类

@Component
@ServerEndpoint("/websocket/{clientId}")
public class MyWebSocket {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 在线人数
     */
    public static int onlineNumber = 0;

    /**
     * 以id为key,MyWebSocket为对象保存起来
     */
    private static Map<Integer, MyWebSocket> clients = new ConcurrentHashMap<>();

    /**
     * 会话
     */
    private Session session;

    /**
     * 用户名称
     */
    private int clientId;

    /**
     * 建立连接
     */
    @OnOpen
    public void onOpen(@PathParam(value = "clientId") int clientId, Session session) {
        onlineNumber++;
        this.clientId = clientId;
        this.session = session;
        logger.info("有新连接加入!当前在线人数" + onlineNumber);
    }

    @OnError
    public void onError(Throwable error) {
        logger.info("服务端发生了错误" + error.getMessage());
    }

    /**
     * 连接关闭
     */
    @OnClose
    public void onClose() {
        onlineNumber--;
        logger.info("有连接关闭! 当前在线人数" + onlineNumber);
    }

    /**
     * 收到客户端的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {

    }


    public void sendMessageTo(String message, int ToTableId) throws IOException {
        for (MyWebSocket item : clients.values()) {
            if (item.clientId == ToTableId) {
                item.session.getAsyncRemote().sendText(message);
                break;
            }
        }
    }

    public void sendMessageAll(String message) throws IOException {
        for (MyWebSocket item : clients.values()) {
            item.session.getAsyncRemote().sendText(message);
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineNumber;
    }

}

 

(3)新建socket.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>websocket</title>
    <script type="text/javascript" src="https://ajax.microsoft.com/ajax/jquery/jquery-1.4.min.js"></script>
    <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
    <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
</head>

<body>
<div style="margin: auto;text-align: center">
    <h1>Welcome to websocket</h1>
</div>
<br/>
<br>
<div style="margin-right: 10px;text-align: right">
    <button οnclick="closeWebSocket()">关闭连接</button>
</div>
<hr/>
<div id="message" style="text-align: center;"></div>
<input  type="text" th:value="${clientId}" id="clientId" style="display: none" />
</body>


<script type="text/javascript">
    var webSocket;
    if ("WebSocket" in window)
    {
        var clientId = document.getElementById('clientId').value;
        webSocket = new WebSocket("ws://localhost:8080/websocket/"+clientId);

        //连通之后的回调事件
        webSocket.onopen = function()
        {
            console.log("已经连通了websocket");
        };

        //接收后台服务端的消息
        webSocket.onmessage = function (evt)
        {
            var received_msg = evt.data;
            console.log("数据已接收:" +received_msg);
            var obj = JSON.parse(received_msg);
            console.log("可以解析成json:"+obj.messageType);
        };

        //连接关闭的回调事件
        webSocket.onclose = function()
        {
            console.log("连接已关闭...");
        };
    }
    else{
        // 浏览器不支持 WebSocket
        alert("您的浏览器不支持 WebSocket!");
    }

    function closeWebSocket() {
        //直接关闭websocket的连接
        webSocket.close();
    }
</script>

</html>

 

(4)新建测试类

@Controller
public class SocketController {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @RequestMapping("/websocket/{tableId}")
    public String webSocket(@PathVariable String tableId, Model model){
        try{
            logger.info("跳转到websocket的页面上");
            //通过Model进行对象数据的传递
            model.addAttribute("clientId",tableId);
            return "socket";
        }
        catch (Exception e){
            logger.info("跳转到websocket的页面上发生异常,异常信息是:"+e.getMessage());
            return "error";
        }
    }

}

 

(5)运行测试

 

 

到这,第一种方式已连接成功。

 

4、第二种方式(spring)

(1)新建websocket配置类

@Component
@EnableWebSocket
public class OtherWebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private MyWebSocketHandler myWebSocketHandler;

    @Autowired
    private MyWebSocketInterceptor myWebSocketInterceptor;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myWebSocketHandler, "/websocket/{clientId}")
                .setAllowedOrigins("*")
                .addInterceptors(myWebSocketInterceptor);
    }
}

注意:

如果使用第一种方式中的socket.html,使用此处的websocket配置类,连接会失败,浏览器将会报错200。如下:

 

registry.addHandler(myWebSocketHandler, "/websocket/{clientId}")

在此处的处理中,websocket消息处理器无法像第一种方式的

@ServerEndpoint("/websocket/{clientId}")

处理路径中的参数。所以导致socket.html中访问websocket链接的ws无法和配置类中要检测的路径相匹配,导致连接失败。

webSocket = new WebSocket("ws://localhost:8080/websocket/"+clientId);

 

所以此处要处理配置类的路径如下:

 

还有处理socket.html访问websocket路径如下:

 

这样访问就可以成功。在这个问题上踩坑严重,所以特地记录一下。

 

 

(2)新建websocket消息处理器

@Component
public class MyWebSocketHandler implements WebSocketHandler {

    private static final Map<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>();

    /**
     * 建立连接成功后回调
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        SESSIONS.put(session.getId(), session);
        System.out.println(String.format("成功建立连接~ tableId: %s", session.getId()));
    }

    /**
     * 接收客户端发送的Socket
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> webSocketMessage) throws Exception {
        String msg = webSocketMessage.getPayload().toString();
        System.out.println(msg);
    }

    /**
     * 连接出错回调
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable throwable) throws Exception {
        System.out.println("连接出错");
        if (session.isOpen()) {
            session.close();
        }
    }

    /**
     * 连接关闭回调
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        System.out.println("连接已关闭,status:" + closeStatus);
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

 

(3)websocket自定义拦截器

@Component
public class MyWebSocketInterceptor implements HandshakeInterceptor {

    /**
     * 前置拦截用来注册用户信息,绑定 WebSocketSession
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                   WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {

        System.out.println("前置拦截~~");
        if (!(request instanceof ServletServerHttpRequest)) {
            return true;
        }

        //HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
        //String clientId = (String) servletRequest.getSession().getAttribute("clientId");
        //attributes.put("clientId", clientId);

        return true;

    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                               WebSocketHandler wsHandler, Exception exception) {
        System.out.println("后置拦截~~");
    }

}

 

到此,第二种方式结束。

因为在第二种方式无法直接在配置中加入参数,那么就考虑到传参,处理参数问题,这个可以在websocket的自定义拦截器中,在前置拦截中通过request获取参数

代码已上传github:https://github.com/liwangC/springboot

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值