spring-websocket实现(一)

11 篇文章 0 订阅

websocket介绍

        WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

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

实现

        在spring中,使用最基础的spring-websocket的方式有两种。一种是利用注解的方式,一种是利用直接通过实现WebSocketConfigurer配置来实现。

本次先实现第一种实现WebSocketConfigurer配置来实现。

后端实现

先引入依赖:

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

 处理器

        首先,需要实现WebSocketHandler接口,WebSocketHandler定义了处理连接、处理数据、关闭连接、以及连接异常锁调用的方法,在实现类中我们可以定义需要的操作。

public class WebSocketEndPointHandler implements WebSocketHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketEndPointHandler.class);

    private static WebSessionSendService webSessionSendService = new WebSessionSendService();

    /**
     * 连接处理
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        LOGGER.info("WebSocketEndPointHandler:afterConnectionEstablished");
        List<String> appIds = session.getHandshakeHeaders().get("Sec-WebSocket-Protocol");
        webSessionSendService.addAppSession(appIds.get(0), session);
    }

    /**
     * 处理客户端发送过来的数据
     * @param message 消息
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        LOGGER.info("收到消息:{}", message.getPayload());
    }

    /**
     * 处理传输过程中发生的错误
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        LOGGER.info("------WebSocketEndPointHandler:handleTransportError");
    }


    /**
     * 处理连接关闭之后触发操作
     * @param closeStatus 关闭的状态信息
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        LOGGER.info("------WebSocketEndPointHandler:afterConnectionClosed");
        List<String> appIds = session.getHandshakeHeaders().get("Sec-WebSocket-Protocol");
        webSessionSendService.deleteAppSession(appIds.get(0));
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

        为了实现从后端将数据发送到前端。所以我们需要记录下每一个前端websocket连接的WebSocketSession。在需要发送数据的时候取出来判断并发送消息。

@Service
public class WebSessionSendService {

    private static ConcurrentHashMap<String, WebSocketSession> sessionPool = new ConcurrentHashMap<>();


    public void addAppSession(String appId, WebSocketSession session){
        sessionPool.put(appId, session);
    }

    public void deleteAppSession(String appId){
        sessionPool.remove(appId);
    }

    public void sendMessage(String appId, String message){
        WebSocketSession session = sessionPool.get(appId);
        if(session == null || !session.isOpen()){
            return;
        }
        TextMessage textMessage = new TextMessage(message);
        try {
            session.sendMessage(textMessage);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

拦截器 

        其次,实现一个拦截器,用户拦截websocket过来的握手请求,这个可以根据自身需要决定要不要实现。拦截可以实现 HttpSessionHandshakeInterceptorHandshakeInterceptor都可以。

public class MessageHandshakeInterceptor extends HttpSessionHandshakeInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(MessageHandshakeInterceptor.class);

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
       LOGGER.info("---------MessageHandshakeInterceptor:beforeHandshake");
       super.beforeHandshake(request, response, wsHandler, attributes);
        //这里可以拦截请求头, 做用户认证。
        List<String> appIds = request.getHeaders().get("Sec-WebSocket-Protocol");
        if(CollectionUtils.isEmpty(appIds)){
            LOGGER.info("websocket缺少用户认证信息");
            return false;
        }
        response.getHeaders().set("Sec-WebSocket-Protocol", appIds.get(0));
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
       LOGGER.info("--------MessageHandshakeInterceptor:afterHandshake");
        super.afterHandshake(request, response, wsHandler, exception);
    }
}

配置项

最后是配置项。通过实现WebSocketConfigurer配置相关信息。

@Configuration
@EnableWebSocket
public class PathWebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
                //websocket地址以及处理器,可以多个
        registry.addHandler(new WebSocketEndPointHandler(), "/websocket/log/getLog")
                .setAllowedOrigins("*")
                //自定义的 HandshakeHandler实现类会报错,springboot默认需要用 AbstractHandshakeHandler
                //.setHandshakeHandler(new WebSocketHandshakeHandler())
                .addInterceptors(new MessageHandshakeInterceptor());    //设置拦截器
    }

}

前端测试代码

        我的前端代码是在自己的搭建的一个以vue为基础的项目中测试的。

<template>
  <div class="f_c float_l">
    <div class="m_10">
      <el-input class='input_common' v-model="websocketPath" placeholder="请输入后端websocket地址" ></el-input>
      <el-button v-if="!connected" @click=" initWebSocket" >连接</el-button>
      <el-button v-else @click="disConnectWebsocket" >断开</el-button>
    </div>
    <div class="f_c" v-if="connected">
      <el-button @click="sendMessage">发送</el-button>
      <el-input class='input_common mt_10' v-model="message" type="textarea" :rows="2" placeholder="请输入需要发送的消息"></el-input>
    </div>
    <el-divider />
    <div class="m_10 f_c" v-if="connected">
      <span class="float_l">收到消息</span>
      <span style="border: aqua; width: 500px; height: 100px">{{receiveMessage}}</span>
    </div>

  </div>
</template>

<script>
export default {
  name: "index",
  data(){
    return {
      websocketPath:'localhost:7000/websocket-demo/websocket/log/getLog',
      receiveMessage:'',
      message:'',
      connected: false,
      ws:null,
      number:0,
      heartbeatIntervalId: null,   //客户端心跳定时发送
      timeoutId: null    //定时检测服务端发送过来的心跳
    }
  },
  methods: {
    initWebSocket() {
      this.ws = new WebSocket('ws://' + this.websocketPath, ['123456789']);
      this.ws.onmessage = this.websocketOnmessage;
      this.ws.onopen = this.websocketOpen;
      this.ws.onerror = this.websocketError;
      this.ws.onclose = this.websocketOnclose;
    },
    websocketOpen() {
      console.log('websocket onopen', this.ws.readyState);
      this.connected = true;
      // 开始心跳检测
      this.startHeartbeat();
    },
    websocketError(event) {
      console.log('websocket onerror', this.ws.readyState);
      this.connected =false
      clearInterval(this.heartbeatIntervalId);
      clearTimeout(this.timeoutId);
    },
    websocketOnclose(event){
      // WebSocket关闭时的处理
        console.log('WebSocket disconnected');
        // 清除心跳检测定时器
        clearInterval(this.heartbeatIntervalId);
        clearTimeout(this.timeoutId);
        this.connected = false;
    },
    websocketOnmessage(event) {
      console.log(`收到消息:${event.data}`);
      let nowMessage = '第' + this.number + '次收到订阅的消息:' + event.data + '\r\n';
      this.receiveMessage += nowMessage;
      this.number++
      this.resetHeartbeat();
    },
    sendMessage() {
      if (this.ws.readyState === WebSocket.OPEN) {
        let param  = {
          message: this.message
        }
        this.ws.send(JSON.stringify(param));
      }
    },
    startHeartbeat() {
      let _that = this
      //开始心跳检测
      this.heartbeatIntervalId = setInterval( function () {
        //根据实际情况发送正确的心跳消息
        _that.ws.send('HEARTBEAT');
      }, 8 * 1000)
      this.timeoutId = setTimeout(() => {
        console.error('Heartbeat timeout, reconnecting...');
        console.log(this.ws)
        this.ws.close();
      }, 15 * 1000)
    },
    resetHeartbeat() {
      clearTimeout(this.timeoutId);
      let _that = this
      this.timeoutId = window.setTimeout(function() {
        console.error('Heartbeat timeout, reconnecting...');
        _that.ws.close();
      }, 15 * 1000);
    },
    disConnectWebsocket(){
      this.ws.close();
      clearInterval(this.heartbeatIntervalId);
      clearTimeout(this.timeoutId);
    }
  }
}
</script>

<style scoped>

</style>

启动之后就可以填写我们的websocket地址。就可以连接了。

  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring WebSocket连接可以通过使用netty-websocket-spring-boot-starter来实现。这个脚手架可以让我们在Spring Boot中使用Netty来开发WebSocket服务器,并且像使用spring-websocket的注解开发一样简单。\[1\] 要实现Spring WebSocket连接,可以按照以下步骤进行操作: 1. 继承AbstractWebSocketHandler类,重写afterConnectionEstablished、handleTextMessage、handleBinaryMessage、handleTransportError、afterConnectionClosed等方法,这些方法可以与jdk的原生WebSocket注解对应上。\[2\] 2. 实现WebSocketConfigurer接口,进行WebSocket的相关配置,通过WebSocketHandlerRegistry注册自定义的WebSocketHandler。 3. 通过WebSocketSession来实现消息的发送。当接收到字符串消息时,可以回调相应的方法。\[3\] 在配置方面,可以设置WebSocket的path、host、端口号等属性。还可以设置最大允许帧载荷长度、IdleStateHandler中的allIdleTimeSeconds等参数。\[3\] 总结起来,使用netty-websocket-spring-boot-starter可以简化Spring WebSocket连接的开发过程,并提供了一些核心注解和配置选项来方便开发者进行使用。 #### 引用[.reference_title] - *1* *3* [【项目实战】基于netty-websocket-spring-boot-starter实现WebSocket服务器长链接处理](https://blog.csdn.net/wstever/article/details/129427160)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [websocket系列基于spring-boot-starter-websocket实现](https://blog.csdn.net/web13618542420/article/details/126551816)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值