【WEB篇】Spring Boot 整合 WebSocket 推送与接受消息

WebSocket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。WebSocket 通信协议于2011年被 IETF 定为标准 RFC 6455,并由 RFC7936 补充规范。WebSocket API 也被 W3C 定为标准。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

属性

属性 描述
Socket.readyState 只读属性 readyState 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。
Socket.bufferedAmount 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

事件

事件 事件处理程序 描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发

方法

方法 描述
Socket.send() 使用连接发送数据
Socket.close() 关闭连接

实例

WebSocket 协议本质上是一个基于 TCP 的协议。

为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息 “Upgrade: WebSocket” 表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。

Spring Boot 整合 WebSocket

Demo 地址:mingyue-springboot-websocket

1.引入依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-websocketartifactId>
dependency>

2.构建服务端

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

/**
 * WebSocket 服务端
 *
 * @author Strive
 * @date 2022/4/21 16:00
 * @description
 */
@Slf4j
@Component
public class MingYueTextWebSocketHandler extends TextWebSocketHandler {

  @Override
  public void afterConnectionEstablished(WebSocketSession session) {
    log.info("和客户端建立连接");
  }

  @Override
  public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
    session.close(CloseStatus.SERVER_ERROR);
    log.error("连接异常", exception);
  }

  @Override
  public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    super.afterConnectionClosed(session, status);
    log.info("和客户端断开连接");
  }

  @Override
  protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    // 获取到客户端发送过来的消息
    String receiveMessage = message.getPayload();
    log.info(receiveMessage);
    // 发送消息给客户端
    session.sendMessage(new TextMessage(fakeAi(receiveMessage)));
  }

  private static String fakeAi(String input) {
    if (StrUtil.isBlank(input)) {
      return "你说什么?没听清";
    }

    if ("晚安".equals(input)) {
      return "玛卡巴卡";
    }

    return input;
  }
}

3.定义拦截器

import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

/**
 * Socket 拦截器
 *
 * @author Strive
 * @date 2022/4/21 17:10
 * @description
 */
@Slf4j
@Component
public class MingYueSocketInterceptor implements HandshakeInterceptor {
  @Override
  public boolean beforeHandshake(
      ServerHttpRequest request,
      ServerHttpResponse response,
      WebSocketHandler wsHandler,
      Map<String, Object> attributes)
      throws Exception {
    log.info("握手开始");
    return true;
  }

  @Override
  public void afterHandshake(
      ServerHttpRequest request,
      ServerHttpResponse response,
      WebSocketHandler wsHandler,
      Exception exception) {
    log.info("握手完成");
  }
}

4.开启 WebSocket

注册处理器与拦截器

import com.csp.mingyue.webSocket.handler.MingYueTextWebSocketHandler;
import com.csp.mingyue.webSocket.interceptor.MingYueSocketInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * @author Strive
 * @date 2022/4/21 16:05
 * @description
 */
@Configuration
@RequiredArgsConstructor
@EnableWebSocket
public class WebSocketServerConfig implements WebSocketConfigurer {
  private final MingYueTextWebSocketHandler mingYueTextWebSocketHandler;

  private final MingYueSocketInterceptor mingYueSocketInterceptor;

  @Override
  public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry
        .addHandler(mingYueTextWebSocketHandler, "/connect")
        .addInterceptors(mingYueSocketInterceptor)
        .withSockJS();
  }
}

5.构建客户端

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket客户端title>
    <script src="./js/sockjs.min.js">script>
  	<link href="./css/bootstrap.min.css" rel="stylesheet">
head>
<body>
<style>
    .jumbotron {
        width: 100%;
    }

    #text {
        height: 3rem;
        font-size: 1rem;
        line-height: 3rem;
        margin: 1rem;
    }

    .btn {
        margin-right: 5px;
    }

    #connect {
        margin-left: 1rem;
    }

    #log {
        margin: 1rem 0 0 1rem;
    }

style>
<div class="container">
    <div class="row">
        <div class="jumbotron">
            <input type="text" placeholder="请输入你想传输的内容" id="text" class="col-lg-12"/>
            <input type="button" value="连接" class="btn btn-info" id="connect" onclick="connect()"/>
            <input type="button" value="发送" class="btn btn-success" id="sent" disabled="disabled" onclick="sent()"/>
            <input type="button" value="断开" class="btn btn-danger" id="disconnect" disabled="disabled"
                   onclick="disconnect()"/>

            <div id="log">
                <p>聊天记录:p>
            div>
        div>
    div>
div>
<script type="text/javascript">
    let text = document.querySelector('#text');
    let connectBtn = document.querySelector("#connect");
    let sentBtn = document.querySelector("#sent");
    let disconnectBtn = document.querySelector("#disconnect");
    let logDiv = document.querySelector("#log");

    let ws = null;

    function connect() {
        let targetUri = "/connect";
        ws = new SockJS(targetUri);
        ws.onopen = function () {
            setConnected(true);
            log('和服务端连接成功!');
        };
        ws.onmessage = function (event) {
            log('服务端说:' + event.data);
        };
        ws.onclose = function () {
            setConnected(false);
            log('和服务端断开连接!')
        }
    }

    function sent() {
        if (ws != null) {
            ws.send(text.value);
            log('客户端说:' + text.value);
        } else {
            log('请先建立连接!')
        }
    }

    function disconnect() {
        if (ws != null) {
            ws.close();
            ws = null;
        }
        setConnected(false);
    }

    function log(value) {
        let content = document.createElement('p');
        content.innerHTML = value;
        logDiv.appendChild(content);
        text.value = '';
    }

    function setConnected(connected) {
        connectBtn.disabled = connected;
        disconnectBtn.disabled = !connected;
        sentBtn.disabled = !connected;
    }
script>
body>
html>

6.测试连接

访问:http://localhost:8080/client.html

参考

https://juejin.cn/post/7028389192275083271

你可能感兴趣的:(Spring,全家桶,spring,boot,websocket)