SSM框架整合WebSocket服务

WebSocketHTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket 的工作流程是这 样的:浏览器通过 js 向服务端发出建立 WebSocket 连接的请求,在 WebSocket 连接建立成功后,客户端和服务端就可以通过 TCP 连接传输数据。


添加依赖

本文使用 maven 方式,添加在 pom.xml 里面依赖部分。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>

在项目里面建立一个单独的 websocket 文件夹,再在配置文件 spring-mvc.xml 里面添加:

<context:component-scan base-package="com.stu.controller" />
<context:component-scan base-package="com.stu.websocket" />

这样 WebSocketConfig 就相当于是 controller 层了


Spring整合WebSocket

WebSocketConfig.java
配置 WebSocket 入口、允许访问的域,注册 HandlerSockJs 支持和拦截器。

用户登录后建立 WebSocket 连接,默认选择 WebSocket 连接,如果浏览器不支持,则使用 Sockjs 注册方法进行模拟连接

package com.stu.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
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;

@Component
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig  implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    	/* 当客户端发起 websocket 连接,把 /path 交给对应的 handler 处理,而不实现具体的业务逻辑,
    	   第一个参数用来注册 websocket server 实现类,第二个参数是访问 websocket 的地址  */
        registry.addHandler(myHandler(), "/ws").addInterceptors(new HandShake());
        registry.addHandler(myHandler(), "/ws/sockjs").addInterceptors(new HandShake()).withSockJS();
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyWebSocketHandler();
    }
}

HandShake.java
握手拦截器

此处为 WebSocket 连接的第一步验证,必须要满足定义的条件才能初步建立连接,满足条件后可以再绑定身份信息传给下一步以便于区分用户

package com.stu.websocket;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.HttpSession;
import java.util.Map;

public class HandShake implements HandshakeInterceptor {

    public boolean beforeHandshake(ServerHttpRequest request,
                                   ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) throws Exception {
        ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
        String parameter = servletRequest.getServletRequest().getParameter("id");
        // 登陆时自己手动绑定的 session,非 websocket session
        HttpSession session = servletRequest.getServletRequest().getSession(false);
        System.out.println("Websocket:用户[ID:" + parameter + "]握手成功");
		System.out.println(request.getURI().toString());
		// 若要编写 websocket 客户端进行连接,则再定义一个不需要 session 的判定进行绑定
		if (session != null) {
            // 使用 userCd 区分 WebSocketHandler,以便定向发送消息
            String userCd = session.getAttribute("uid").toString();
            attributes.put("uid", userCd);
        }
    }

    public void afterHandshake(ServerHttpRequest request,
                               ServerHttpResponse response, WebSocketHandler wsHandler,
                               Exception exception) {
        System.out.println("After Handshake");
    }
}

MyWebSocketHandler.java
消息拦截器

提供了客户端连接、关闭、错误、发送等方法,重写这几个方法即可实现自定义业务逻辑

这里通过 Map 绑定 IDWebSocketSession,以便于后期的消息发送,也可以自己定义用户的判定方式

package com.stu.websocket;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.util.*;

@Component
@Service
public class MyWebSocketHandler implements WebSocketHandler {

    // 当 MyWebSocketHandler 类被加载时就会创建该 Map
    private static final Map<String, WebSocketSession> userSocketSessionMap;

    static {
        userSocketSessionMap = new HashMap<String, WebSocketSession>();
    }

    /**
     * 连接成功后
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session)
            throws Exception {
            
		// 此处为 WebSocketSession,uid 是由拦截器已绑定好的,用于区分用户
        String uid = session.getAttributes().get("uid").toString();

        // 绑定session
        if (userSocketSessionMap.get(uid) == null) {
            userSocketSessionMap.put(uid, session);
        }
        session.sendMessage(new TextMessage("handler已连接"));

    }

    /**
     * 消息处理
     */
    @Override
    public void handleMessage(WebSocketSession session,
                              WebSocketMessage<?> message) throws Exception {
                              
        if (message.getPayloadLength() == 0) return;
        // 把消息发送给消息来源方
        session.sendMessage(message);
        // 自己定义的函数,发送给在线的所有人
        sendMessageToAllUsers(message);
        
    }

    /**
     * 消息传输错误处理
     */
    @Override
    public void handleTransportError(WebSocketSession session,
                                     Throwable exception) throws Exception {

		String uid = session.getAttributes().get("uid").toString();
        if (session.isOpen()) {
            session.close();
        }
        System.out.println(uid + " 连接失败");
        userSocketSessionMap.remove(session);

    }

    /**
     * 关闭连接后
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session,
                                      CloseStatus closeStatus) throws Exception {

		String uid = session.getAttributes().get("uid").toString();
		System.out.println("UserWebSocket:" + uid + " close connection");
        Iterator<Map.Entry<String, WebSocketSession>> iterator = userSocketSessionMap.entrySet().iterator();
            // 移除
            while (iterator.hasNext()) {
                // 取值查询
                Map.Entry<String, WebSocketSession> entry = iterator.next();
                if (entry.getValue().getAttributes().get("uid") == uid) {
                    iterator.remove();
                    System.out.println("WebSocket in UserMap:" + uid + " removed");
                }
            }

    }

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

	// 发送给所有用户
    private boolean sendMessageToAllUsers(WebSocketMessage message) {
        boolean allSendSuccess = true;
        Set<String> clientIds = userSocketSessionMap.keySet();
        WebSocketSession session = null;
        for (String clientId : clientIds) {
            try {
                session = userSocketSessionMap.get(clientId);
                if (session.isOpen()) {
                    session.sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
                allSendSuccess = false;
            }
        }
        return allSendSuccess;
    }

}

HTML连接

打开网页后,js 就会给 WebSocket 服务器发送连接请求,要注意链接的形式及正确性。

连接建立成功后便可以与服务器互相通信

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<html>
	<head>
	    <meta charset="utf-8">
	    <base href="<%=basePath%>">
	</head>
	<body>
		<script type="text/javascript">
			var host = window.location.host;
		      var webSocket =
		          new WebSocket("ws://" + host+ "/ws?id=1");
		      var hum = null;
		      var s_json = null;
		      webSocket.onerror = function(event) {
		          onError(event);
		      };
		      webSocket.onopen = function(event) {
		          onOpen(event);
		      };
		      webSocket.onmessage = function(event) {
		          onMessage(event);
		      };
		      function onMessage(event) {
		          console.log(event.data);
		      }
		      function onOpen(event) {
		          console.log("握手成功");
		          webSocket.send("连接上了");
		      }
		      function onError(event) {
		          alert(event.data);
		      }
	     </script>
	 </body>
</html>

配置路由

MyController.java

通过建立专门的路由,当用户访问相应链接时,便把上面的 HTML 对应展示,此处可用于用户验证并传递对应 ID,便于服务器区分

package com.stu.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
@RequestMapping("/condition")
public class SerialController {
    @RequestMapping("/show")
    public String list(HttpServletRequest request) {
    	/* 此处就是拦截器接收的 session,uid 绑定一个默认值用来测试
    	   生产环境要绑定一个能区分用户的值 */
        request.getSession().setAttribute("uid", "con");
        return "show";
    }
}
  • 7
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值