springboot集成websocket和几种方式的个人见解

一、说明

最近没什么事,写下springboot集成websocket。记得之前要用的时候随便搜了下,相关文章很多,但是每个都不太一样,给我整的很疑惑。

最后找到篇文章才算是说的比较清楚,原来是有很多种方式。

参考文档:https://juejin.cn/post/6844903976727494669

     http://www.mydlq.club/article/86/

spring-boot 集成websocket 常见方式:
1、原生jdk注解。 太原生了,功能支持很少。用着不太方便。
2、spring封装。简单封装,消息处理基本与netty一致。本文使用这种方式。
3、spring封装STOMP。感觉有点过渡封装了。
4、还有一些其他的方式,如netty、tio等,这种与spring-boot基本没啥关系,不能复用spring-boot的http端口(或者我不知道)。
 

二、使用

1、maven引用

  spring-boot基础依赖这里不贴了

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

2、配置

 端口:使用的是 spring-boot的内嵌的tomcat配置的端口,application.properties 中的“server.port=9090”。

 添加个配置类:

testWs 是websocket路径,前端请求地址举例:ws://127.0.0.1:9090/testWs
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private WebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler, "testWs")
                //跨域忽略
                .setAllowedOrigins("*");
    }
}

3、WebSocketHandler 实现类,业务处理类

这里只演示文本类型消息。

 基本与netty的操作方式一致。如果想在项目中主动给客户端写消息,可以将session缓存到map里,然后需要发消息时取出session进行发送。

import cn.hutool.core.lang.Console;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

/**
 * websocket 连接和消息处理,这里继承TextWebSocketHandler,只处理文本消息。
 **/
@Component
public class MsgHandler extends TextWebSocketHandler {

    /**
     * 接收消息
     */
    @Override
    protected void handleTextMessage(@NotNull WebSocketSession session, TextMessage message) throws Exception {
        String msg = message.getPayload();
        Console.log("收到消息:{}",msg);
        //这里只是演示下如何给客户端写消息。
        session.sendMessage(new TextMessage("我收到你的消息了"));
    }

    /**
     * 连接成功
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        Console.log("连接connected:{}", session.getRemoteAddress());
    }

    /**
     * 连接关闭
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, @NotNull CloseStatus status) {
        Console.log("关闭close:{},status:{}", session.getRemoteAddress(), status.toString());
    }

    /**
     * 异常处理
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        Console.log(exception.getCause(), "异常error:{}", session.getRemoteAddress());
    }
}

4、粘一个写的演示基本功能的WebSocketHandler

功能主要有:登录验证,session缓存,超时断连(要求前端每隔10s发送心跳,否则超过30s无消息将断连)。

业务处理类:

import cn.hutool.core.date.SystemClock;
import cn.hutool.log.Log;
import cn.sanenen.demo.common.Constant;
import cn.sanenen.demo.entity.bean.UserBean;
import cn.sanenen.utils.other.Emptys;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * websocket 连接和消息处理,这里继承TextWebSocketHandler,只处理文本消息。
 **/
@Component
public class MsgHandler extends TextWebSocketHandler {
    private static final Log log = Log.get();
    private static final AtomicInteger userId = new AtomicInteger(0);

    /**
     * 接收消息
     */
    @Override
    protected void handleTextMessage(@NotNull WebSocketSession session, TextMessage message) throws Exception {
        String msg = message.getPayload();
        String resp = Emptys.STR;
        try {
            Object o = session.getAttributes().putIfAbsent(Constant.SESSION_RESPONSE, 1);
            //上次请求响应未返回,禁止请求。
            if (o != null) {
                resp = "上次请求为返回,禁止请求";
            } else {
                Object seatObj = session.getAttributes().get(Constant.SESSION_USER_KEY);
                //未登录 一般连接建立成功后的第一条消息都为 验证消息。
                if (seatObj == null) {
                    //就当是验证通过了,放入缓存。
                    UserBean userBean = new UserBean();
                    userBean.setId(userId.incrementAndGet());
                    session.getAttributes().put(Constant.SESSION_USER_KEY, userBean);
                    //这里如果想限制用户已存在,不允许重复登录,可以使用putIfAbsent
                    WebSocketSession webSocketSession = WebSocketMap.put(userBean.getId(), session);
                    if (webSocketSession != null) {
                        webSocketSession.sendMessage(new TextMessage("你被顶掉了。"));
                        webSocketSession.getAttributes().clear();
                        webSocketSession.close();
                    }
                    resp = "登录成功喽";
                } else {
                    //这里进行业务处理
                    //-----
                    resp = "我是业务处理完的响应噢";
                }
                session.getAttributes().put(Constant.SESSION_TIMEOUT, SystemClock.now());
                session.getAttributes().remove(Constant.SESSION_RESPONSE);
            }
            session.sendMessage(new TextMessage(resp));
        } catch (Exception e) {
            resp = "系统异常";
            log.error(e);
        } finally {
            log.info("ip:{},request:{},resp:{}", session.getRemoteAddress(), msg, resp);
        }
    }

    /**
     * 连接成功
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        log.info("连接connected:{}", session.getRemoteAddress());
    }

    /**
     * 连接关闭
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, @NotNull CloseStatus status) {
        UserBean user = (UserBean) session.getAttributes().get(Constant.SESSION_USER_KEY);
        if (user != null) {
            //做一些连接断开后的操作。
            WebSocketMap.remove(user.getId());
        }
        log.info("关闭close:{},status:{},user:{}", session.getRemoteAddress(), status.toString(), user);
    }

    /**
     * 异常处理
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        log.error(exception.getCause(), "异常error:{}", session.getRemoteAddress());
    }
}

 

session缓存map,附带超时处理逻辑。

import cn.hutool.core.date.SystemClock;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.log.Log;
import cn.sanenen.demo.common.Constant;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 连接缓存,便于给前端推送消息
 **/
public class WebSocketMap {
    private static final Map<Integer, WebSocketSession> MAP = new ConcurrentHashMap<>();
    private static final Log log = Log.get();

    static {
        new Thread(() -> {
            while (true) {
                try {
                    Iterator<Map.Entry<Integer, WebSocketSession>> iterator = MAP.entrySet().iterator();
                    while (iterator.hasNext()) {
                        Map.Entry<Integer, WebSocketSession> entry = iterator.next();
                        //最后一次收到消息的时间
                        Long endReadTime = (Long) entry.getValue().getAttributes().get(Constant.SESSION_TIMEOUT);
                        if (endReadTime == null){
                            endReadTime = 0L;
                        }
                        long timeOut = SystemClock.now() - endReadTime;
                        //超过30秒,断开连接。
                        if (timeOut > 30000) {
                            WebSocketSession value = entry.getValue();
                            if (value != null && value.isOpen()) {
                                Object o = value.getAttributes().get(Constant.SESSION_USER_KEY);
                                log.info("sessionTimeoutClose:ip:{}:{}", value.getRemoteAddress(), o);
                                value.close();
                            }
                            iterator.remove();
                        }
                    }
                    ThreadUtil.sleep(10000);
                } catch (Exception e) {
                    log.error(e);
                    ThreadUtil.sleep(10000);
                }
            }
        }).start();
    }

    /**
     * 添加 session
     */
    public static WebSocketSession put(Integer jobId, WebSocketSession session) {
        return MAP.put(jobId, session);
    }

    /**
     * 删除 session,会返回删除的 session
     */
    public static WebSocketSession remove(Integer jobId) {
        // 删除 session
        return MAP.remove(jobId);
    }

    /**
     * 删除并同步关闭连接
     */
    public static void removeAndClose(Integer jobId) {
        WebSocketSession session = remove(jobId);
        if (session != null && session.isOpen()) {
            try {
                session.close();
            } catch (IOException e) {
                log.error(e);
            }
        }
    }

    /**
     * 获得 session
     */
    public static WebSocketSession get(Integer jobId) {
        return MAP.get(jobId);
    }

    /**
     * 给指定用户发送消息
     */
    public static void send(Integer jobId, String msg) throws IOException {
        log.debug("sendEvent:{},{}", jobId, msg);
        WebSocketSession session = MAP.get(jobId);
        if (session != null && session.isOpen()) {
            session.sendMessage(new TextMessage(msg));
        } else {
            log.info("session is null or closed:{},msg:{}", jobId, msg);
        }
    }


    /**
     * 给所有用户发送消息

     public static void sendAll(String msg) throws IOException {
     for (WebSocketSession session : MAP.values()) {
     if (session != null && session.isOpen()) {
     session.sendMessage(new TextMessage(msg));
     }
     }
     }*/
}

 

MsgHandler

posted on 2023-02-17 15:59  SunEn  阅读(942)  评论(0编辑  收藏  举报

导航