Spring boot集成websocket通信方式二

229 阅读1分钟

Spring boot集成websocket通信

介绍

websocket是一个持久化的协议,实现了浏览器与服务器的全双工通信。不再像http那样,只有在浏览器发出request之后才有response,websocket能实现服务器主动向浏览器发出消息。

Spring boot项目集成实现

  1. 在pom.xml中添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  1. 集成TextWebSocketHandler类或实现AbstractWebSocketHandler接口
@Component
public class WebSocketMsgHandler extends TextWebSocketHandler {
    private final Logger logger = LoggerFactory.getLogger(WebSocketMsgHandler.class);

    /**
     * 会话保存集合
     */
    public static ConcurrentHashMap<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String userId = (String) session.getAttributes().get("userId");
        WebSocketSession old = SESSIONS.get(userId);
        // TODO 剔除当前节点旧连接,多实例多节点部署时,其他节点的旧会话可以通过MQ广播消息剔除,暂时不实现
        if (old != null && !old.getId().equals(session.getId())) {
            String oldId = old.getId();
            old.sendMessage(new TextMessage("logout"));
            old.close();
            logger.info("剔除旧连接,当前连接数:{},session key:{},session id:{}", SESSIONS.size(), userId,oldId);
        }
        SESSIONS.put(userId, session);
        logger.info("连接打开,当前连接数:{},session key:{},session id:{}", SESSIONS.size(), userId, session.getId());
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String userId = (String) session.getAttributes().get("userId");
        SESSIONS.remove(userId);
        logger.info("连接关闭,当前连接数:{},session key:{}", SESSIONS.size(), userId);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable e) throws Exception {
        String userId = (String) session.getAttributes().get("userId");
        logger.info("连接异常,userId:" + userId+ "," + e.getMessage(), e);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String userId = (String) session.getAttributes().get("userId");
        logger.info("接收到前端推送的消息,userId:{},消息内容:{}", userId, message);
    }


    /**
     * 找到会话,使用会话发送消息
     *
     * 如果是服务集群,则无法确定用户会话所在节点
     * 这种情况可借助MQ,通过MQ将消息广播到各个服务节点,服务节点收到广播消息后,如果存在对应的会话则执行消息推送至前端
     * @param userId
     * @param message
     * @throws IOException
     */
    public static void sendMsg(String userId, String message) throws IOException {
        WebSocketSession session = SESSIONS.get(userId);
        if (session != null) {
            if (session.isOpen()) {
                session.sendMessage(new TextMessage(message));
                return;
            }
            SESSIONS.remove(userId);
        }
    }

}

  1. websocket配置
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer  {
    @Autowired
    private WebSocketMsgHandler webSocketMsgHandler;

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

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        HandshakeInterceptor handshakeInterceptor = new HandshakeInterceptor() {
            // 在处理握手之前调用
            @Override
            public boolean beforeHandshake(ServerHttpRequest req, ServerHttpResponse resp, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
                // 可在此做鉴权,是进行握手( true )还是中止( false )
                // String userId = ((ServletServerHttpRequest) req).getServletRequest().getParameter("userId");
                // attributes.put("userId",userId);
                return true;
            }
            @Override
            public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
                //握手完成后调用
            }
        };
        registry.addHandler(webSocketMsgHandler, "/websocket")
                .addInterceptors(handshakeInterceptor)
                .setAllowedOrigins("*");
        registry.addHandler(webSocketMsgHandler, "/websocket_sockjs")
                .addInterceptors(handshakeInterceptor)
                 // 跨域
                .setAllowedOrigins("*")
                // 启用SockJS,前端需要导入sockjs.js库,并以sockjs的方式连接
                .withSockJS();
    }
}
  1. 原生websocket的前端示例页面,使用ws协议
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <base href="http://localhost:8080/">
    <title>My WebSocket</title>
</head>
<body lang="en">
<table>
    <tr>
        <td style="width: 80px;text-align: right;">url地址:</td>
        <td>
            <input id="websocket_url" type="text" value="ws://127.0.0.1:2001/websocket"/>
            <button onclick="openWs()" >Open</button>
            <button onclick="closeWs()" >Close</button>
        </td>
    </tr>
    <tr>
        <td style="width: 80px;text-align: right;">用户id:</td>
        <td><input id="userId" type="text" value="123456"/></td>
    </tr>
    <tr>
        <td style="width: 80px;text-align: right;">消息:</td>
        <td>
            <input id="msg" type="text"/>
            <button onclick="send()">Send</button>
        </td>
    </tr>
</table>
<div>服务器推送的消息:</div>
<div id="message" style="border: 1px solid grey;width: 600px;min-height: 150px;"></div>
</body>

<script type="text/javascript">
    var websocket = null;
    var isOpen = false;
    function openWs() {
        if (websocket != null) {
            websocket.close();
        }
        var ws_url = document.getElementById('websocket_url').value;
        var userId = document.getElementById('userId').value;
        websocket = new WebSocket(ws_url + "?userId=" + userId);

        //连接发生错误的回调方法
        websocket.onerror = function (e) {
            setMessageInnerHTML("error");
            console.info("error:");
            console.info(e);
        };

        //连接成功建立的回调方法
        websocket.onopen = function (event) {
            isOpen = true;
            setMessageInnerHTML("open");
            console.info("onopen:");
            console.info(event)
        }
        //接收到消息的回调方法
        websocket.onmessage = function (event) {
            setMessageInnerHTML(event.data);
        }
        //连接关闭的回调方法
        websocket.onclose = function (e) {
            isOpen = false;
            setMessageInnerHTML("close");
            console.info("onclose:");
            console.info(e);
        }
    }


    //关闭连接
    function closeWs() {
        if (websocket != null) {
            websocket.close();
        }
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function () {
        closeWs();
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //发送消息
    function send() {
        if (!isOpen) {
            setMessageInnerHTML("websocket is close");
            return;
        }
        var message = document.getElementById('msg').value;
        websocket.send(message);
    }
</script>
</html>
  1. 原生sockjs的前端示例页面,使用http协议,需导入sockjs.js库
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <base href="http://localhost:8080/">
    <title>My WebSocket</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/sockjs-client/1.5.0/sockjs.js"></script>
</head>
<body lang="en">
<table>
    <tr>
        <td style="width: 80px;text-align: right;">url地址:</td>
        <td>
            <input id="websocket_url" type="text" value="http://127.0.0.1:2001/websocket_sockjs"/>
            <button onclick="openWs()" >Open</button>
            <button onclick="closeWs()" >Close</button>
        </td>
    </tr>
    <tr>
        <td style="width: 80px;text-align: right;">用户id:</td>
        <td><input id="userId" type="text" value="123456"/></td>
    </tr>
    <tr>
        <td style="width: 80px;text-align: right;">消息:</td>
        <td>
            <input id="msg" type="text"/>
            <button onclick="send()">Send</button>
        </td>
    </tr>
</table>
<div>服务器推送的消息:</div>
<div id="message" style="border: 1px solid grey;width: 600px;min-height: 150px;"></div>
</body>

<script type="text/javascript">
    var websocket = null;
    var isOpen = false;
    function openWs() {
        if (websocket != null) {
            websocket.close();
        }
        var ws_url = document.getElementById('websocket_url').value;
        var userId = document.getElementById('userId').value;
        websocket = new SockJS(ws_url + "?userId=" + userId);

        //连接发生错误的回调方法
        websocket.onerror = function (e) {
            setMessageInnerHTML("error");
            console.info("error:");
            console.info(e);
        };

        //连接成功建立的回调方法
        websocket.onopen = function (event) {
            isOpen = true;
            setMessageInnerHTML("open");
            console.info("onopen:");
            console.info(event)
        }
        //接收到消息的回调方法
        websocket.onmessage = function (event) {
            setMessageInnerHTML(event.data);
        }
        //连接关闭的回调方法
        websocket.onclose = function (e) {
            isOpen = false;
            setMessageInnerHTML("close");
            console.info("onclose:");
            console.info(e);
        }
    }


    //关闭连接
    function closeWs() {
        if (websocket != null) {
            websocket.close();
        }
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function () {
        closeWs();
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //发送消息
    function send() {
        if (!isOpen) {
            setMessageInnerHTML("websocket is close");
            return;
        }
        var message = document.getElementById('msg').value;
        websocket.send(message);
    }
</script>
</html>