• SpringBoot + WebSocket 开发笔记


    1. 服务端的实现,我尝试了两种方式:

    • 第一种是用“@ServerEndPoint”注解来实现,实现简单;
    • 第二种稍显麻烦,但是可以添加拦截器在WebSocket连接建立和断开前进行一些额外操作。

      不管用哪种实现方式,都需要先导入jar包(如下),其中version根据实际springboot版本选择,避免冲突

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
        <!-- <version>1.3.5.RELEASE</version> -->
    </dependency>

    1.1 第一种实现方法

    (1)WebSocket 业务逻辑实现。参数传递采用路径参数的方法,通过以下方式获取参数:

    • @ServerEndpoint("/testWebSocket/{id}/{name}")

    • public void onOpen(Session session, @PathParam("id") long id, @PathParam("name") String name)

    import java.util.concurrent.CopyOnWriteArraySet;
    import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RestController; @ServerEndpoint("/testWebSocket/{id}/{name}") @RestController public class TestWebSocket { // 用来记录当前连接数的变量 private static volatile int onlineCount = 0; // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象 private static CopyOnWriteArraySet<TestWebSocket> webSocketSet = new CopyOnWriteArraySet<TestWebSocket>(); // 与某个客户端的连接会话,需要通过它来与客户端进行数据收发 private Session session; private static final Logger LOGGER = LoggerFactory.getLogger(TestWebSocket.class);
      @OnOpen
    public void onOpen(Session session, @PathParam("id") long id, @PathParam("name") String name) throws Exception { this.session = session; System.out.println(this.session.getId()); webSocketSet.add(this); LOGGER.info("Open a websocket. id={}, name={}", id, name); } @OnClose public void onClose() { webSocketSet.remove(this); LOGGER.info("Close a websocket. "); } @OnMessage public void onMessage(String message, Session session) { LOGGER.info("Receive a message from client: " + message); } @OnError public void onError(Session session, Throwable error) { LOGGER.error("Error while websocket. ", error); } public void sendMessage(String message) throws Exception { if (this.session.isOpen()) { this.session.getBasicRemote().sendText("Send a message from server. "); } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { TestWebSocket.onlineCount++; } public static synchronized void subOnlineCount() { TestWebSocket.onlineCount--; } }

    (2)配置ServerEndpointExporter,配置后会自动注册所有“@ServerEndpoint”注解声明的Websocket Endpoint

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    @Configuration
    public class WebSocketConfig {
    
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
        
    }

    1.2 第二种实现方法

    (1)WebSocket 业务逻辑实现。参数传递采用类似GET请求的方式传递,服务端的参数在拦截器中获取之后通过attributes传递给WebSocketHandler。

    import java.util.ArrayList;
    import java.util.concurrent.atomic.AtomicInteger;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.socket.CloseStatus;
    import org.springframework.web.socket.WebSocketHandler;
    import org.springframework.web.socket.WebSocketMessage;
    import org.springframework.web.socket.WebSocketSession;
    
    @RestController
    public class TestWebSocketController implements WebSocketHandler {
        
        private static AtomicInteger onlineCount = new AtomicInteger(0);
        
        private static final ArrayList<WebSocketSession> sessions = new ArrayList<>();
        
        private final Logger LOGGER = LoggerFactory.getLogger(TestWebSocketController.class);
        
        @Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
            sessions.add(session);
            int onlineNum = addOnlineCount();
            LOGGER.info("Oprn a WebSocket. Current connection number: " + onlineNum);
        }
    
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
            sessions.remove(session);
            int onlineNum = subOnlineCount();
            LOGGER.info("Close a webSocket. Current connection number: " + onlineNum);
        }
    
        @Override
        public void handleMessage(WebSocketSession wsSession, WebSocketMessage<?> message) throws Exception {
            LOGGER.info("Receive a message from client: " + message.toString());
        }
    
        @Override
        public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
            LOGGER.error("Exception occurs on webSocket connection. disconnecting....");
            if (session.isOpen()) {
                session.close();
            }
            sessions.remove(session);
            subOnlineCount();
        }
    
        /*
         * 是否支持消息拆分发送:如果接收的数据量比较大,最好打开(true), 否则可能会导致接收失败。
         * 如果出现WebSocket连接接收一次数据后就自动断开,应检查是否是这里的问题。
         */
        @Override
        public boolean supportsPartialMessages() {
            return true;
        }
    
        
        public static int getOnlineCount() {
            return onlineCount.get();
        }
        
        public static int addOnlineCount() {
            return onlineCount.incrementAndGet();
        }
        
        public static int subOnlineCount() {
            return onlineCount.decrementAndGet();
        }
    
    }

    (2)HandShake 拦截器实现

    import java.util.Map;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    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.support.HttpSessionHandshakeInterceptor;
    
    public class TestHandShakeInterceptor extends HttpSessionHandshakeInterceptor {
        
        private final Logger LOGGER = LoggerFactory.getLogger(TestHandShakeInterceptor.class);
        
        /*
         * 在WebSocket连接建立之前的操作,以鉴权为例
         */
        @Override
        public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, 
                WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
            
            LOGGER.info("Handle before webSocket connected. ");
            
            // 获取url传递的参数,通过attributes在Interceptor处理结束后传递给WebSocketHandler
            // WebSocketHandler可以通过WebSocketSession的getAttributes()方法获取参数
            ServletServerHttpRequest serverRequest = (ServletServerHttpRequest) request;
            String id = serverRequest.getServletRequest().getParameter("id");
            String name = serverRequest.getServletRequest().getParameter("name");
    
            if (tokenValidation.validateSign()) {
                LOGGER.info("Validation passed. WebSocket connecting.... ");
                attributes.put("id", id);
                attributes.put("name", name);
                return super.beforeHandshake(request, response, wsHandler, attributes);
            } else {
                LOGGER.error("Validation failed. WebSocket will not connect. ");
                return false;
            }
        }
        
        @Override
        public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                WebSocketHandler wsHandler, Exception ex) {
            // 省略
        }
    
    }

    (3)WebSocket 配置类实现(注册WebSocket实现类,绑定接口,同时将实现类和拦截器绑定)

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.socket.config.annotation.EnableWebSocket;
    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    
    import TestWebSocketController;
    import TestHandShakeInterceptor;
    
    @Configuration
    @EnableWebMvc
    @EnableWebSocket
    public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
    
        @Autowired
        private TestWebSocketController testWebSocketController;
    
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            registry.addHandler(TestWebSocketController, "/testWebSocket")
                    .addInterceptors(new TestHandShakeInterceptor()).setAllowedOrigins("*");
        }
    
    }

    1.3 补充说明

    (1)在WebSocket实现过程中,尤其是通过“@ServerEndpoint”实现的时候,可能会出现注入失败的问题,即注入的Bean为null的问题。可以通过手动注入的方式来解决,需要改造实现类和SpringBoot启动类,如下:

    @ServerEndpoint("testWebsocket")
    @RestController
    public class WebSocketController {
    
        private TestService testService;
        
        private static ApplicationContext applicationContext;
        
        @OnOpen
        public void onOpen(Session session) {
            testService = applicationContext.getBean(TestService.class);
        }
    
        @OnClose
        public void onClose() {}
    
        @OnMessage
        public void onMessage(String message, Session session) {}
    
        @OnError
        public void onError(Session session, Throwable error) {}
    
        public static void setApplicationContext(ApplicationContext applicationContext) {
            WebSocketController.applicationContext = applicationContext;
        }
        
    }
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    
    import WebSocketController;
    
    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
    //        SpringApplication.run(Application.class, args);
            SpringApplication springApplication = new SpringApplication(Application.class);
            ConfigurableApplicationContext configurableApplicationContext = springApplication.run(args);
            WebSocketController.setApplicationContext(configurableApplicationContext);  // 解决WebSocket不能注入的问题
        }
    
    }

    2. 客户端的实现,我尝试了html和java WebSocketClient两种方式

    2.1 html实现

    <!DOCTYPE html>
    <html>
    <head>
        <title>WebSocket示例</title>
        <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport' />
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    </head>
    <body>
        <input id="text" type="text"/>
        <button onclick="send()">发送消息</button>
        <hr/>
        <button onclick="closeWebSocket()">关闭WebSocket连接</button>
        <hr/>
        <div id="message"></div>
    </body>
    
    <script type="text/javascript">
        var websocket = null;
        //判断当前浏览器是否支持WebSocket
        if ('WebSocket' in window) {
            // 不带参数的写法
            websocket = new WebSocket("ws://127.0.0.1:18080/testWebsocket");
            // 通过路径传递参数的方法(服务端采用第一种方法"@ServerEndpoint"实现)
            websocket = new WebSocket("ws://127.0.0.1:18080/testWebsocket/23/Lebron");
            // 通过类似GET请求方式传递参数的方法(服务端采用第二种方法"WebSocketHandler"实现)
            websocket = new WebSocket("ws://127.0.0.1:18080/testWebsocket?id=23&name=Lebron");
        }
        else {
            alert('当前浏览器 Not support websocket')
        }
    
        //连接发生错误的回调方法
        websocket.onerror = function () {
            setMessageInnerHTML("WebSocket连接发生错误");
        };
    
        //连接成功建立的回调方法
        websocket.onopen = function () {
            setMessageInnerHTML("WebSocket连接成功");
        }
    
        //接收到消息的回调方法
        websocket.onmessage = function (event) {
            setMessageInnerHTML(event.data);
        }
    
        //连接关闭的回调方法
        websocket.onclose = function () {
            setMessageInnerHTML("WebSocket连接关闭");
        }
    
        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function () {
            closeWebSocket();
        }
    
        //将消息显示在网页上
        function setMessageInnerHTML(innerHTML) {
            document.getElementById('message').innerHTML += innerHTML + '<br/>';
        }
    
        //关闭WebSocket连接
        function closeWebSocket() {
            websocket.close();
        }
    
        //发送消息
        function send() {
            var message = document.getElementById('text').value;
            websocket.send(message);
        }
    </script>
    </html>

    2.2 Java WebSocketClient实现

    (1)WebSocketClient 实现类

    import java.net.URI;
    
    import org.java_websocket.client.WebSocketClient;
    import org.java_websocket.drafts.Draft;
    import org.java_websocket.handshake.ServerHandshake;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class TestWebSocketClient extends WebSocketClient {
        
        private final Logger LOGGER = LoggerFactory.getLogger(TestWebSocketClient.class);
        
        public TestWebSocketClient(URI serverUri) {
            super(serverUri);
        }
        
        public TestWebSocketClient(URI serverUri, Draft protocolDraft) {
            super(serverUri, protocolDraft);
        }
    
        @Override
        public void onOpen(ServerHandshake serverHandshake) {
            LOGGER.info("Open a WebSocket connection on client. ");
        }
        
        @Override
        public void onClose(int arg0, String arg1, boolean arg2) {
            LOGGER.info("Close a WebSocket connection on client. ");
        }
    
        @Override
        public void onMessage(String msg) {
            LOGGER.info("WebSocketClient receives a message: " + msg);
        }
    
        @Override
        public void onError(Exception exception) {
            LOGGER.error("WebSocketClient exception. ", exception);
        }
    
    }

    (2)WebSocketClient 发送数据

    String serverUrl = "ws://127.0.0.1:18080/testWebsocket"
    URI recognizeUri = new URI(serverUrl);
    client = new TestWebSocketClient(recognizeUri, new Draft_6455());
    client.connect();
    client.send("This is a message from client. ");
  • 相关阅读:
    类的组合
    类的继承和派生
    面向对象编程
    正则表达式
    sys模块 logging模块 序列化模块
    time 模块,random模块,os模块
    递归函数
    interface有没有继承Object
    UTF-8和GBK的区别
    九皇后
  • 原文地址:https://www.cnblogs.com/strugglion/p/10021173.html
Copyright © 2020-2023  润新知