Spring Boot 技术探索

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".

22、Spring Boot之WebSocket的使用

平台环境:

名称

版本号

Mac OS X

10.14.6

JDK

1.8.0_201

Apache Maven

3.6.0

IntelliJ IDEA

2019.1 (Ultimate Edition)

Spring Boot

2.1.8.RELEASE

 

  什么是WebSocket?

  WebSocket是一种在单个TCP连接上进行全双工通信的协议。可以使客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。WebSocket 协议能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

  而传统的采用FORM表单提交与服务器数据交互的方式采用的是HTTP协议。这种方式的缺点通信只能由客户端发起,做不到服务器主动向客户端推送信息。

  Websocket使用和HTTP相同的TCP端口,可以通过大多数防火墙的限制。默认情况下,Websocket协议使用80端口。运行在TLS之上时,默认使用443端口。

 

  优缺点对比:

  1. HTTP每次都需要携带完整头部,其中真正有效的数据可能只是很小的一部分,这样会浪费很多的带宽等资源。使用WebSocket,客户端与服务端进行数据交换时,服务端到客户端的数据包头只有2到10字节,客户端到服务端需要加上另外4字节的掩码,数据格式轻量,性能开销小,实时性更好。
  2. HTTP只能由客户端发送强求,Websocket具有双向通信功能,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息。
  3. AJAX轮询(客户端定时以固定的间隔向服务端发送请求)与Long Polling长轮询(客户端和浏览器保持一个长连接,等服务端有消息返回,断开。然后再重新连接,等服务端有消息返回,断开。然后再重新连接.......)。WebSocket一次握手,持久连接,以及主动推送的特点可以解决上边的问题,又不至于损耗性能
  4. Websocket支持持久连接,HTTP不支持持久连接。

 

  Talk is cheap. Show me the code.

  协议终究是协议,还得需要实实在在的代码来落地。到此就该轮到Spring Boot的WebSocket组件spring-boot-starter-websocket出场了。用来实现Spring Boot框架下WebSocket协议的使用。

 

pom.xml中引入依赖

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

 

启动类中增加@EnableWebSocket标签,并且定义ServerEndpointExporter实例Bean

package com.example.demo;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;


@EnableWebSocket
@SpringBootApplication
public class DemoApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(DemoApplication.class, args);
    }


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

 

新建工具类WebSocketUtils,用来封装WebSocket的操作。

package com.example.demo.utils;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public final class WebSocketUtils
{
    private static final Logger logger = LoggerFactory.getLogger(WebSocketUtils.class);


    // 存储WebSocket连接的Session实例
    public static final Map<String, Session> ONLINE_USER_SESSIONS = new ConcurrentHashMap<>();


    /**
     * @param session 当前WebSocket连接的Session实例
     * @param message 消息内容
     */
    public static void sendMessage(Session session, String message)
    {
        if (session == null)
        {
            return;
        }
        final RemoteEndpoint.Basic basic = session.getBasicRemote();
        if (basic == null)
        {
            return;
        }
        try
        {
            basic.sendText(message);
        }
        catch (IOException e)
        {
            logger.error("sendMessage IOException ", e);
        }
    }


    // 给当前所有的WebSocket连接发送消息
    public static void sendMessageAll(String message)
    {
        ONLINE_USER_SESSIONS.forEach((sessionId, session) -> sendMessage(session, message));
    }
}

WebSocket会为每一个连接都建立一个对应的Session实例,拿到这个Session实例就可以与客户端发送消息。

其中,ONLINE_USER_SESSIONS.forEach((sessionId, session) -> sendMessage(session, message))是JDK1.8 中forEach的简洁写法。

 

新建WebSocket服务端类WebSocketServer

package com.example.demo;


import org.springframework.web.bind.annotation.RestController;


import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;


import static com.example.demo.utils.WebSocketUtils.*;


@RestController
@ServerEndpoint("/msg/")
public class WebSocketServer
{
    @OnOpen
    public void openSession(Session session)
    {
        // 获取当前连接的sessionID
        String sessionID = session.getId();


        // 保存当前连接
        ONLINE_USER_SESSIONS.put(sessionID, session);


        // 建立连接成功并发送通知
        sendMessage(session, "建立WebSocket连接成功!您的sessionID=" + sessionID);


        // 通知当前在线人数
        sendCurrentOnlineUsersSizeMsg();


        System.out.println("建立连接成功!sessionID=" + sessionID);
    }


    @OnMessage
    public void onMessage(Session session, String message)
    {
        System.out.println("接收到消息:" + message);
        if (message.equals("siri现在几点了"))
        {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH点mm分ss秒");
            Date now = new Date();
            String nowTime = sdf.format(now);


            // 给客户端发送消息
            sendMessage(session, "现在是" + nowTime);
        }
        else
        {
            // 给客户端发送消息
            sendMessage(session, "收到你发的消息了:" + message);
        }
    }


    @OnClose
    public void onClose(Session session)
    {
        // 当前的Session移除
        ONLINE_USER_SESSIONS.remove(session.getId());


        // 通知当前在线人数
        sendCurrentOnlineUsersSizeMsg();


        try
        {
            session.close();
        } catch (IOException e)
        {
            e.printStackTrace();
        }
        System.out.println("已断开连接!");
    }


    @OnError
    public void onError(Throwable throwable)
    {
        System.out.println("报错了。错误信息:" + throwable.getMessage());
    }


    // 通知当前在线人数
    public void sendCurrentOnlineUsersSizeMsg()
    {
        sendMessageAll("当前在线人数:" + ONLINE_USER_SESSIONS.size());
    }
}

@ServerEndpoint("/msg/“) 配置监听此地址的WebSocket信息。

新建测试页面

src/main/resources/static/test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SpringBoot WebSocket Test</title>
    <script src="jquery-3.3.1.slim.min.js" ></script>
    <script type="text/javascript">
        $(document).ready(function()
        {
            var urlPrefix ='ws://localhost:8080/msg/';
            var ws = null;
            $('#btnConnect').click(function()
            {
                if(ws == null)
                {
                    var url = urlPrefix;
                    ws = new WebSocket(url);
                    ws.onopen = function () {
                        // 与服务端建立连接事件
                        console.log("建立WebSocket连接成功!");
                    };
                    ws.onmessage = function (event) {
                        // 收到服务端消息事件
                        $('#msgReceive').append(event.data + '\n');
                    };
                    ws.onclose = function () {
                        // 服务端断开连接事件
                        $('#msgReceive').append("已断开WebSocket连接!\n");
                    }
                }
            });


            // 向服务端发送消息
            $('#btnSendMsg').click(function()
            {
                var msg = $('#message').val();
                if(msg=='')
                {
                    alert("请填写消息!");
                    return;
                }
                if(ws && msg!='')
                {
                    ws.send(msg);
                }
            });


            // 通知服务端关闭连接
            $('#btnClose').click(function()
            {
                if(ws)
                {
                    ws.close();
                    ws = null;
                }
            });
        })
    </script>
</head>
<body>
    连接控制:<button id="btnConnect">建立连接</button>
    <button id="btnClose">断开连接</button></br>
    消息内容:<input id="message" value="" />
    <button id="btnSendMsg">发送</button>
    </br></br>
    服务器应答:</br>
    <textarea id="msgReceive" readonly="readonly" cols="50" rows="20"></textarea>


</body>
</html>

页面中使用了jquery的类库,下载地址见文章最后参考资料。

 

测试

启动项目,打开两个浏览器窗口。

浏览器1:点击“建立连接”,并发送消息。

 

浏览器2:点击“建立连接”

 

浏览器1:显示

 

浏览器2:点击“断开连接”

 

浏览器1:显示

 

浏览器1:发送消息“siri现在几点了”

 

浏览器1:点“断开连接”

 

 

参考网址:

https://jquery.com/download/

https://blog.csdn.net/maidu_xbd/article/details/100730667

 

Bootstrap Thumbnail Second
MySQL

MySQL is the world's most popular open source database.

GO

Bootstrap Thumbnail Third
算法基础

本书介绍了什么是计算机算法,如何描述它们,以及如何来评估它们。

GO