Spring Boot使用WebSocket模拟聊天

281 阅读4分钟
携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

前言

WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。WebSocket 协议在 2011 年由 IETF 标准化为 RFC 6455,后由 RFC 7936 补充规范。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

上面是百科对于WebSocket的一个解释,在早些时候或者一些传统项目上做站内推送或者消息通知等逻辑都是通过短轮询来实现的。也就是浏览器客户端定时的去请求服务端获取最新的通知结果返回客户端

但是短轮询的缺点也很明显,HTTP的请求本来就是`一次请求一次响应`,请求跟响应都会带有比较长的`请求/响应头`,但是因为`一次请求一次响应`的设计每次的请求又不可避免的重复带有`请求/响应头`,而真正的传输数据又很少,从而浪费了很多的带宽资源。

天下苦其久矣,这时候需要有一位猛士来解决这个问题,于是HTML5 定义了 WebSocket 协议,跟上面百科的解释一样WebSOcket只需要一次握手的设计让每次消息的传递不需要再带上请求/响应头,非常节省服务器资源和带宽,并且基于长链接的形式和服务端可以主动向客户端推送数据的设计让WebSocket能够更实时地进行通讯。

WebSocket有两种资源标识符(URI)

  • ws:默认情况下ws是80端口,对应Http协议,ws无法对应Https协议
  • wss:wss默认情况下是443端口,对应Https的协议,wss是ws基于TLS的安全传输

并且WebSocket 与 HTTP 和 HTTPS 使用相同的 TCP 端口,可以绕过大多数防火墙的限制。

WebSocket的优点

  • 保持链接状态:因为WebSocket通讯要先建立连接,这样WebSocket就成为了一种有状态的协议,后续的通讯也就无需每次传递部分状态信息,节省资源。
  • 更小的资源开销:根据上面的特性,建立链接后记录了状态,后续消息的发送跟接受都不需要再传递一些头部的状态信息,节省了这部分的带宽开销。
  • 更快的实时性:相对于HTTP下的轮询操作有一定轮询时间的滞后,WebSocket建立的双工协议让服务器可以随时主动给客户端下发数据,响应时间更快,不需要客户端触发。
  • 更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
  • 可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。

集成WebSocket

说千遍万遍不如做一遍

1. 创建Spring boot工程

创建工程会吧,使用 start.spring.io 自动创建一个demo工程

image.png

2. 引入依赖

加入pom文件的WebSocket依赖

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

3. 后台代码

3.1 编写配置类

编写WebSocket的配置类,使其交由Spring管理

@Configuration
public class WebSocketConfig {

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

}

3.2 编写Server类

Server类需要注解ServerEndpoint标明为WebSocket的服务,由此就可通过注解配置的地址进行建立连接和通讯

@OnOpen注解为客户端建立连接时触发的方法
@Slf4j
@Component
@ServerEndpoint("/api/websocket")
public class WebSocketServer {

    @OnOpen
    public void onOpen(Session session){
        log.info("客户端建立连接:{}",session);
    }

}

4. 前端代码

在工程的resources->static目录下建立一个index.html页面,代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
<script>
    var websocket = null;
    //判断当前浏览器是否支持WebSocket
    if('WebSocket' in window) {
        websocket = new WebSocket("ws://127.0.0.1:8080/api/websocket");
    } else {
        alert('当前浏览器 Not support websocket')
    }
</script>
</html>

5. 建立链接

启动项目,访问地址:http://127.0.0.1:8080/index.html

可以打Debug查看是否触发了onOpen方法,也可以查看控制台日志是否出现了客户端建立链接日志

image.png

如上图WebSocket链接就建立成功了

6. 发生消息

建立连接后当然要发消息了,发消息则是WebSocketsend函数

6.1 前端代码

在页面设置一个输入框和按钮,输入框写入信息,按钮触发WebSocketsend事件推送消息,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<input type="text" id="message"></in>
<button onclick="send()">发送</button>
</body>
<script>
    var websocket = null;
    //判断当前浏览器是否支持WebSocket
    if('WebSocket' in window) {
        websocket = new WebSocket("ws://127.0.0.1:8080/api/websocket");
    } else {
        alert('当前浏览器 Not support websocket')
    }

    function send() {
        var message = document.getElementById("message").value;
        websocket.send(message);
    }

</script>
</html>

6.2 后端代码

后端非常简单,增加@OnMessage注解方法来接受消息

@OnMessage
public void onMessage(String message,Session session){
    log.info("客户端:{},接受到消息:{}",session.getId(),message);
}

6.3 验证

重启服务,页面如下

image.png

链接建立

image.png

测试发送一段消息

image.png

查看控制台日志

image.png

再开一个浏览器Tab页会新建立一个WebSocket的连接,也可以发送消息,如下图

image.png