SpringBoot框架:集成websocket实现即时通讯

一、导入依赖

  分别导入websocket依赖包和处理json格式数据的fastjson依赖包。

<dependency>
      <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
      <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>

二、注解处理

  以下注解可以将配置为@ServerEndpoint标注的类在调用时自动导出为实体,类编写完成后使用@Configuration将config加入配置。

package com.example.demo2.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

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

三、websocket类编写

package com.example.demo2.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.util.JSONPObject;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;

@Component
@ServerEndpoint("/websocket/{userId}")
public class MessageController {

    //与某个客户端的连接会话,需要用来发送数据
    private Session session;

    //concurrent线程安全set,存放每个客户端对应的websocket对象
    private static CopyOnWriteArraySet<MessageController> messageControllers = new CopyOnWriteArraySet<>();

    //存储session池
    private static Map<String,Session> sessionPool = new HashMap<String,Session>();

    /**
     * 链接成功调用
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId")String userId){
        try{
            //session获取
            this.session = session;
            //静态存储的websocket长链接集合中,添加本次调用成功所创建的这个实体
            messageControllers.add(this);
            //session缓存池添加用户的用户ID和对应的session
            sessionPool.put(userId,session);
            //将用户加入链接和当前连接数的消息广播给当前所有已连接的用户
            sendAllMessage("【" + userId +"】加入链接,当前连接数:" + messageControllers.size());
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 链接关闭调用
     */
    @OnClose
    public void onClose(){
        try{
            //已连接的对象列表中移除当前调用此关闭方法的对象
            messageControllers.remove(this);
            //将用户断开链接和链接总数信息推送给现在还链接着的用户
            System.out.println("用户断开链接,当前链接总数为:" + messageControllers.size());
        }catch (Exception e){

        }
    }

    /**
     * 收到客户端消息
     */
    @OnMessage
    public void onMessage(String message){
        //收到客户端消息后可以根据实际需求做一些处理操作,也可以直接返回,到页面进行处理
        //广播给群中现在处理已连接状态的所有用户
        sendAllMessage(message);
    }

    /**
     * 发送错误处理
     */
    @OnError
    public void onError(Session session,Throwable error){
        //输出错误信息
        System.out.println("【发送错误】:" + error.getMessage());
        error.printStackTrace();
    }

    /**
     * 广播(全体)消息
     */
    public void sendAllMessage(String message){
        //遍历当前所有已连接的用户,循环发送
        for(MessageController messageController : messageControllers){
            try{
                //session处于打开状态,则发送消息
                if(messageController.session.isOpen()){
                    //最好是使用这个getAsyncRemote,另一个(getBasicRemote)在多个线程同时进行时容易报错,这两个是异步和同步的区别
                    messageController.session.getAsyncRemote().sendText(message);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 单点消息
     */
    public void sendOneMessage(String userId,String message){
        //根据userId获取到对应的session
        Session session = sessionPool.get(userId);
        //这个session不为空并且还在开放状态
        if(session != null && session.isOpen()){
            try{
                //将消息单独推送到这个userId对应的session里
                session.getAsyncRemote().sendText(message);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 多点消息(多选发给一部分人)
     */
    public void sendManyMessage(String[] userIds,String message){
        //遍历参数中的用户id数组,进行多个推送,介於单个和广播(全体)之间的部分发送
        for(String userId:userIds){
            Session session = sessionPool.get(userId);
            if(session != null && session.isOpen()){
                try{
                    session.getAsyncRemote().sendText(message);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

}

  除了在这个类内部调用发送信息的方法之外,其他可以通过类中注入此类后,使用接口进行方法调用。(之前本来想用main方法测试一下发现这种方式不行,还是要在接口中调用)

四、websocket在vue页面里进行应用

<template>
  <div>
    <!--填写个人信息  -->
    <input v-model="userId" />
    <input v-model="userName" />
    <!-- 绑定后根据个人信息创建websocket连接 -->
    <button @click="andUser">绑定</button>
    <br />
    <!-- 填写要发送的消息 -->
    <input v-model="message" />
    <!-- 发送信息 -->
    <button @click="webSocketSend">发送</button>
    <br />
    <!-- 遍历显示所有信息列表 -->
    <div v-for="item in infoList">
      <span>{{ item }}</span>
      <br />
    </div>
  </div>
</template>

<script>
    export default {
      name: "Index",
      data(){
        return{
          //默认用户信息
          userId:1,
          userName:"张三",
          //预置websocket,设为undefined
          websock:undefined,
          //要发送的消息
          message:"",
          //消息列表
          infoList:[],
        }
      },
      mounted() {
        //如果多地进行测试,可以在这里进行初始化
        //一个人开多个窗口的话,可以在填写不同用户信息后,点击绑定时使用不同的userId进行初始化
      },
      destroyed() {
        //页面销毁时触发,关闭此用户的websocket连接
        this.webSocketOnClose();
      },
      methods:{
        //建立连接
        initWebSocket(){
          //websocket服务链接的地址
          let url = "ws://127.0.0.1:8080/websocket/" + this.userId;
          //创建连接
          this.websock = new WebSocket(url);
          //将自定义的方法替换websocket自带的方法
          this.websock.onopen = this.webSocketOnOpen;
          // this.websock.send = this.webSocketSend;
          this.websock.onerror = this.webSocketOnError;
          this.websock.onmessage = this.webSocketOnMessage;
          this.websock.onclose = this.webSocketOnClose;
        },
        //连接上之后触发
        webSocketOnOpen(){
          //将用户连接成功的消息放到列表进行展示
          this.infoList.push("" + this.userName + "】连接成功");
        },
        //发送消息时触发
        webSocketSend(){
          //将用户信息和要发送的消息包装为json
          let data = {
            userId:this.userId,
            userName:this.userName,
            message:this.message
          }
          //将json格式化为字符串之后发送到服务端
          this.websock.send(JSON.stringify(data));
          //自己发送的信息添加到列表进行展示
          this.infoList.push(this.userName + ":" + this.message);
        },
        //发送错误时触发
        webSocketOnError(e){
          //将错误信息添加到列表展示在页面
          this.infoList.push("【错误】:" + e.toString());
        },
        //接收消息时触发
        webSocketOnMessage(e){
          //返回的信息存储在此方法参数的data字段
          if(e.data != undefined && e.data != null && e.data != ''){
            //信息非空时,通过json格式化为表单
            let json = JSON.parse(e.data);
            //如果返回消息的用户id是本人,则为自己发送的信息,不需要显示
            if(json.userId != this.userId){
              //判断是别人发送的信息,则拼接字段并添加到列表中用于展示
              this.infoList.push(json.userName + ":" + json.message);
            }
          }
        },
        //关闭连接时触发
        webSocketOnClose(){
          //将用户关闭链接的消息写入列表
          this.infoList.push("" + this.userName + "】关闭连接");
        },
        //绑定用户
        andUser(){
          //初始化websocket
          this.initWebSocket();
        }
      }
    }
</script>

<style scoped>

</style>

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章