SpringBoot整合WebSocket

独自空忆成欢 提交于 2020-05-08 16:00:47

一、WebSocket概述

        Http协议是一种基于请求/响应的应用层协议,工作原理是客户端(浏览器)与服务端建立TCP连接->客户端向服务端发送http请求报文->服务端收到请求报文进行处理返回一个响应报文->释放TCP连接->浏览器解析响应报文进行渲染。整个过程中都是由客户端发起的,服务端被动反应。如果我们需要实时获取服务器数据,可以通过ajax轮询来实现,不停的进行请求/响应,不停的建立TCP连接,这就造成的资源的极大浪费,而且做不到服务端向客户端发送信息。

        由此诞生了websocket协议,最大的特点就是服务端可以主动向客户端发送信息,客户端也可主动向服务端发送信息,是一个双向平等的协议。工作原理是首先使用http建立一个TCP连接并告诉服务器将http协议升级为websocket协议此时就建立了一个持久连接->此时如果服务器由信息输出则直接传输给客户端,客户端不再询问知道客户端关闭连接为止。

二、SpringBoot整合WebSocket

1、导入pom.xml依赖

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

2、编写节点配置类和WebSocket配置类

EndpointConfig.java

public class EndpointConfig extends ServerEndpointConfig.Configurator implements ApplicationContextAware {

    private static volatile BeanFactory context;

    @Override
    public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
        return context.getBean(clazz);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        EndpointConfig.context = applicationContext;
    }
}

WebSocketConfig.java

@Configuration
public class WebSocketConfig {

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

    @Bean
    public EndpointConfig newConfig() {
        return new EndpointConfig();
    }
}

3、编写服务端内容

@Component
@ServerEndpoint(value = "/productWebSocket/{userId}", configurator = EndpointConfig.class)
@Slf4j
public class ProductWebSocket {

    //当前在线用户
    private static final AtomicInteger onlineCount = new AtomicInteger(0);

    //存放当前连接的用户
    private static CopyOnWriteArraySet<ProductWebSocket> webSocketSet = new CopyOnWriteArraySet<ProductWebSocket>();

    private Session session;

    /**
     * 连接开启时调用
     * @param userId
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam("userId") String userId, Session session) {
        log.info("新客户端连入,用户id:" + userId);
        this.session = session;
        webSocketSet.add(this);
        addOnlineCount();
        if (userId != null) {
            List<String> totalPushMsg = new ArrayList<>();
            totalPushMsg.add(userId+"连接成功-"+"-当前在线人数为:"+getOnlineCount());
            if (totalPushMsg != null && !totalPushMsg.isEmpty()) {
                totalPushMsg.forEach(e -> sendMessage(e));
            }
        }
    }

    /**
     * 连接关闭时调用
     */
    @OnClose
    public void onClose() {
        log.info("一个客户端关闭连接");
        webSocketSet.remove(this);
        subOnlineCount();
    }

    /**
     * 服务端接收到信息后调用
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("用户发送过来的消息为:"+message);
    }

    /**
     * 服务端websocket出错时调用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("websocket出现错误");
        error.printStackTrace();
    }

    /**
     * 服务端发送信息给客户端
     * @param message
     */
    public void sendMessage(String message) {
        try {this.session.getBasicRemote().sendText(message);
            log.info("推送消息成功,消息为:"+message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 群发自定义消息
     * @param message
     */
    public static void sendInfo(String message) {
        for (ProductWebSocket productWebSocket:webSocketSet) {
            productWebSocket.sendMessage(message);
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount.get();
    }

    public static synchronized void addOnlineCount() {
        onlineCount.incrementAndGet();
    }

    public static synchronized void subOnlineCount() {
        onlineCount.decrementAndGet();
    }
}

再编写一个定时任务进行实时推送数据

@Component
public class ProductExpireTask {

    @Scheduled(fixedRate = 2000)
    public void productExpire() {
        String[] strs={"Test随机消息 :30.1123",
                "Test随机消息 :32.1021",
                "Test随机消息 :33.1774",
                "Test随机消息 :33.2372",
                "Test随机消息 :31.0281",
                "Test随机消息 :30.0222",
                "Test随机消息 :32.1322",
                "Test随机消息 :33.3221",
                "Test随机消息 :31.2311",
                "Test随机消息 :32.3112"};

        ProductWebSocket.sendInfo(new Date().toGMTString()+"    Test 消息---->"+RandomStr(strs));
    }

    public static String RandomStr(String[] strs) {
        int random_index = (int)(Math.random()*strs.length);
        return strs[random_index];
    }
}

需要在SpringBoot启动类添加@EnableScheduling注解以开启定时任务

4、编写客户端进行信息接收和发送(html页面)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>websocket测试</title>
</head>
<body>
    TestWebSocket
    <input id="text" type="text" />
    <button onclick="send()">SEND MESSAGE</button>
    <button onclick="closeWebSocket()">CLOSE</button>
    <div id="message"></div>
</body>
<script type="text/javascript">
    var websocket = null;
    
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://localhost:8080/productWebSocket/001");
    }
    else {
        alert("Not support WebSocket!");
    }

    websocket.onerror = function () {
        setMessageInnerHTML("error");
    };

    websocket.onopen = function () {
        setMessageInnerHTML("open");
    };
    
    websocket.onmessage = function (e) {
        setMessageInnerHTML(e.data);
    };

    websocket.onclose = function () {
        setMessageInnerHTML("close");
    };

    window.onbeforeunload = function () {
        websocket.close();
    };
    
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML+'</br>';
    }

    function closeWebSocket() {
        websocket.close();
    }
    
    function send() {
        var message = document.getElementById('text').value
        websocket.send(message);
    }
</script>
</html>

5、测试

浏览器输入url为http://localhost:8080/websocket,就开启了websocket连接。

客户端输出如下:

 

 

 输入框中输入消息并点击发送后查看服务端输出:

 

 

 以上就是完整的整合过程,在前后端传输信息时也可采用JSON格式。

6、采用JSON进行前后端信息传输

a、客户端发送JSON

修改html页面中send()方法

function send() {
    var message = {
        date: new Date(),
        message: document.getElementById('text').value
    };
    websocket.send(JSON.stringify(message));
}     
b、服务端接收JSON

由于接收到JSON信息需要进行转化,新建一个实体类,这里使用lombok

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Message {
    private Date date;
    private String message;
}

修改ProductWebSocket类的onMessage()方法

@OnMessage
public void onMessage(String message, Session session) {
    try {
        log.info("用户发送过来的消息为:"+message);
        Message msg = new ObjectMapper().readValue(message, Message.class);
        System.out.println(msg);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
}
 c、服务端发送JSON

修改ProductWebSocket类的sendMessage()方法

public void sendMessage(String message) {
    try {
        Message msg = new Message();
        msg.setDate(new Date());
        msg.setMessage(message);
        this.session.getBasicRemote().sendText(new ObjectMapper().writeValueAsString(msg));
        log.info("推送消息成功,消息为:"+msg);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
d、客户端接收JSON

修改html页面

websocket.onmessage = function (e) {
    var msg = JSON.parse(e.data);
    setMessageInnerHTML(msg.date+", "+msg.message);
};

 测试结果:

客户端

 

 服务端

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!